Skip to content

Commit

Permalink
feat: Working, now just need to patch React
Browse files Browse the repository at this point in the history
  • Loading branch information
EntraptaJ committed Nov 26, 2020
1 parent 3992bec commit fb3904e
Show file tree
Hide file tree
Showing 13 changed files with 2,151 additions and 103 deletions.
1,916 changes: 1,868 additions & 48 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@types/workbox-sw": "^4.3.1",
"@typescript-eslint/eslint-plugin": "^4.8.2",
"@typescript-eslint/parser": "^4.8.2",
"apollo-server-testing": "^2.19.0",
"eslint": "^7.14.0",
"eslint-config-prettier": "^6.15.0",
"eslint-config-standard": "^16.0.2",
Expand All @@ -45,10 +46,12 @@
"license": "MIT",
"dependencies": {
"@k-foss/ts-worker": "^1.0.5",
"apollo-server-fastify": "^2.19.0",
"cjstoesm": "^0.1.3",
"class-validator": "^0.12.2",
"fastify": "^3.8.0",
"fastify-websocket": "^2.0.11",
"graphql": "^15.4.0",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.1.1"
}
Expand Down
52 changes: 52 additions & 0 deletions src/Library/Apollo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// src/
import { getGQLContext } from './Context';

type ApolloServer = import('apollo-server-fastify').ApolloServer;
type ApolloServerTestClient = import('apollo-server-testing').ApolloServerTestClient;

let gqlServer: ApolloServer;

/**
* Create Apollo GraphQL Server
*
* @returns Promise that resolves to a ApolloServer Instance
*/
export async function createApolloServer(): Promise<ApolloServer> {
if (!gqlServer) {
const [
{ ApolloServer },
{ getResolvers, buildGQLSchema },
] = await Promise.all([
import('apollo-server-fastify'),
import('./Resolvers'),
]);

const resolvers = await getResolvers();

gqlServer = new ApolloServer({
schema: await buildGQLSchema(resolvers),
context: getGQLContext,
introspection: true,
playground: {
settings: {
'editor.theme': 'light',
'general.betaUpdates': true,
},
workspaceName: 'TS-ESWeb',
},
});
}

return gqlServer;
}

/**
* Create a Apollo Server Testing instance
*/
export async function createApolloTestClient(): Promise<ApolloServerTestClient> {
const { createTestClient } = await import('apollo-server-testing');

const gqlServer = await createApolloServer();

return createTestClient(gqlServer);
}
19 changes: 19 additions & 0 deletions src/Library/Context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// src/Library/Context.ts

interface Context {
randomValue: 42;
}

/**
* Get the GraphQL Context
*/
export async function getGQLContext(): Promise<Context> {
/**
* The answer to life and everything in the universe
*/
const answerToEverything = 42;

return {
randomValue: answerToEverything,
};
}
16 changes: 6 additions & 10 deletions src/Modules/SSR/SSRRoute.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
/* eslint-disable @typescript-eslint/ban-types */
// src/Modules/SSR/SSRRoute.ts
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { HMRLoader } from '../../Utils/hmrLoader';
import { Route } from '../../Library/Fastify';
import { ssrServer } from '../Server/createSSRServer';

/**
* Route to serve the rendered React SSR Routes
*/
export default class SSRRoute implements Route {
public options: Route['options'] = {
method: 'GET',
url: '/*',
url: '/Test',
};

public async handler(
this: FastifyInstance,
request: FastifyRequest,
reply: FastifyReply,
): Promise<Function> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { renderWeb } = await HMRLoader(
'../../../Web/Server',
import.meta.url,
);
): Promise<string> {
reply.type('text/html');

await reply.type('text/html');
const appHTML = ssrServer.renderApp(request.url);

return renderWeb(request.req.url!);
return appHTML;
}
}
64 changes: 59 additions & 5 deletions src/Modules/Server/SSRServer.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// src/Modules/Server/SSRServer.ts
import fastify, { FastifyInstance } from 'fastify';
import { resolve } from 'path';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { createApolloServer } from '../../Library/Apollo';
import { getRoutes, Route } from '../../Library/Fastify';
import { ReactFunction } from '../../Utils/React';
import { startWebTranspiler } from '../TypeScript';

interface ClientOptions {
export interface ClientOptions {
appComponent: ReactFunction;

/**
Expand All @@ -22,6 +25,11 @@ interface ClientOptions {
* Entrypoint Path relative to @see webRoot
*/
entrypoint: string;

/**
* Additional Fastify Routes
*/
serverRoutes: Route[];
}

/**
Expand All @@ -35,12 +43,23 @@ export class SSRServer {
appComponent: () => <div>HelloWorld</div>,
entrypoint: 'Imports.ts',
webRoot: 'Web',
serverRoutes: [],
};

public webServer: FastifyInstance = fastify();

public get entrypoint(): string {
return resolve(this.options.webRoot, this.options.entrypoint);
}

/**
* Get Absolute Path from a relative path
* @param relativePath Relative Path to Web Root
*/
public getFilePath(relativePath: string): string {
return resolve(this.options.webRoot, relativePath);
}

/**
* Create a new SSRServer Instance
* @param opt Properties of SSRServer
Expand All @@ -55,13 +74,46 @@ export class SSRServer {
public async startTranspiler(): Promise<void> {
console.log(`Starting transpiler for ${this.entrypoint}`);

console.log(this.entrypoint, this.options.webRoot);

await startWebTranspiler(this.entrypoint);
}

public async createFastifyServer(): Promise<FastifyInstance> {
/**
* Get All Route Modules.
*/
const routes = await getRoutes();

/**
* For each Route Module in routes destructure handler and options and register as a webServer Route.
*/
routes.map(({ handler, options }) => {
return this.webServer.route({
...options,
handler,
});
});

/**
* Apollo GraphQL Server
*/
const gqlServer = await createApolloServer();

const apiPlugin = gqlServer.createHandler();

/**
* Register the Apollo Server Routes into the Fastify instance
*/
await this.webServer.register(apiPlugin);

return this.webServer;
}

public renderApp(path: string): string {
const App = this.options.appComponent;

console.log(`Rendering for ${path}`);
console.log(`Rendering Path ${path}`);

const appHTML = renderToString(<App />);

Expand All @@ -75,7 +127,9 @@ export class SSRServer {
<script type="module">
import { Workbox } from 'https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-window.prod.mjs';
const wb = new Workbox('/Static/ServiceWorker.ts', {
const wb = new Workbox('/Static/${this.getFilePath(
'ServiceWorker.ts',
)}', {
scope: '/'
});
Expand All @@ -95,7 +149,7 @@ export class SSRServer {
const channel = new BroadcastChannel('sw-messages');
channel.addEventListener('message', event => {
if (event.data.type === 'READY') {
import('/Static//workspace/src/Web/Entry.ts')
import('/Static/${this.getFilePath('Client.tsx')}')
}
});
Expand All @@ -104,7 +158,7 @@ export class SSRServer {
window.wb = wb;
wb.active.then(() => import('/Static//workspace/src/Web/Entry.ts'));
wb.active.then(() => import('/Static/${this.getFilePath('Client.tsx')}'));
</script>
</body>
</html>`;
Expand Down
33 changes: 9 additions & 24 deletions src/Modules/Server/createSSRServer.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,21 @@
// src/Modules/Server/createSSRServer.ts
import { ReactFunction } from '../../Utils/React';
import { timeout } from '../../Utils/timeout';
import { SSRServer } from './SSRServer';
import { ClientOptions, SSRServer } from './SSRServer';

interface SSRServerOptions {
/**
* App
*/
appComponent: ReactFunction;

/**
* Root Directory for Web/Client
*
* @example
* ```ts
* webRoot: path.resolve('./Web')
* ```
*/
webRoot: string;

/**
* Entrypoint Path relative to @see webRoot
*/
entrypoint: string;
options: ClientOptions;
}

export let ssrServer: SSRServer;

export async function createSSRServer({
...opts
options,
}: SSRServerOptions): Promise<SSRServer> {
await timeout(100);
await timeout(50);

console.log(`Creating SSR Server with specified configuration`);

return new SSRServer(opts);
ssrServer = new SSRServer(options);

return ssrServer;
}
4 changes: 2 additions & 2 deletions src/Modules/TypeScript/transpileWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ parentPort.on('message', (filePath: string) => {

transpilePath(filePath)
.then(sendReady)
.catch(() => {
console.error('Testing');
.catch((err) => {
console.error('Transpile has errorer', err);
});
});

Expand Down
15 changes: 9 additions & 6 deletions src/Modules/WebModule/WebModuleRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ export default class WebModuleRoute implements Route {

public async handler(
this: FastifyInstance,
request: FastifyRequest,
request: FastifyRequest<{ Params: { '*': string } }>,
reply: FastifyReply,
): Promise<void> {
const filePath = request.params['*'] as string;
): Promise<string> {
console.log('Request to static', request);

const filePath = request.params['*'];
if (!filePath) {
const err = (new Error() as unknown) as {
statusCode: number;
Expand All @@ -39,8 +41,9 @@ export default class WebModuleRoute implements Route {
throw err;
}

await reply.type('text/javascript');
await reply.header('Service-Worker-Allowed', '/');
await reply.send(webModule.code);
reply.type('text/javascript');
reply.header('Service-Worker-Allowed', '/');

return webModule.code;
}
}
2 changes: 1 addition & 1 deletion src/Web_Test/Client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { App } from './App';
/**
* Count needed so we can request an import with a new param each HMR
*/
//let count = 0;
// let count = 0;

/**
* Render the Client Side
Expand Down
6 changes: 6 additions & 0 deletions src/Web_Test/Imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ export async function importClient(): Promise<typeof import('./Client')> {
export async function importApp(): Promise<typeof import('./App')> {
return import('./App');
}

export async function importServiceWorker(): Promise<
typeof import('./ServiceWorker')
> {
return import('./ServiceWorker');
}
Loading

0 comments on commit fb3904e

Please sign in to comment.