Skip to content

Commit

Permalink
feat(www): ssr
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonyrota committed Sep 17, 2020
1 parent 33e7028 commit 0c9d7a7
Show file tree
Hide file tree
Showing 14 changed files with 482 additions and 80 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ docs/api
temp/*.api.json
.vercel/project.json
www/public
www/template
3 changes: 2 additions & 1 deletion www/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
public
/public
/template
43 changes: 43 additions & 0 deletions www/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:docs": "cd .. && npm install && npm run build && cd www && npx -s sh ts-node --project ./tsconfig.ts-node.json ./script/docs/apigen/index.ts",
"build:public": "parcel build src/index.html --dist-dir public --detailed-report --no-source-maps",
"build": "rimraf public && mkdir public && npm run build:docs && npm run build:public",
"dev": "parcel serve src/index.html --dist-dir public --detailed-report --port 8080 --open",
"build:template": "rimraf template && ncp src template --stopOnErr && npx -s sh ts-node --project ./tsconfig.ts-node.json ./script/template/index.tsx",
"build:public": "npm run build:template && parcel build 'template/**/*.html' --dist-dir public --detailed-report --no-source-maps",
"build": "rimraf public && npm run build:docs && npm run build:public",
"postinstall": "cpy .browserslistrc node_modules/unfetch"
},
"dependencies": {
"@hapi/accept": "5.0.1",
"@vercel/node": "1.8.1",
"preact": "10.4.8",
"preact-router": "^3.2.1",
"tslib": "2.0.1"
},
"devDependencies": {
Expand All @@ -31,7 +32,9 @@
"glob": "7.1.6",
"htmlparser2": "4.1.0",
"mdast-util-definitions": "3.0.1",
"npc": "0.0.1",
"parcel": "2.0.0-beta.1",
"preact-render-to-string": "5.1.10",
"prettier": "2.1.1",
"remark-parse": "8.0.3",
"rimraf": "3.0.2",
Expand Down
12 changes: 12 additions & 0 deletions www/script/docs/apigen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
PageNodeMap,
PageNodeMapWithMetadata,
PageNodeMapMetadata,
ApiDocMapPathList,
} from './types';
import {
addFileToFolder,
Expand Down Expand Up @@ -421,13 +422,24 @@ const hash = crypto
.update(pageNodeMapWithMetadataStringified)
.digest('hex')
.slice(0, 8);
const pathList: ApiDocMapPathList = [...pageMap.keys()];

addFileToFolder(
outFolder,
`www/public/apiDocMap.${hash}.json`,
pageNodeMapWithMetadataStringified,
);
addFileToFolder(
outFolder,
`www/temp/apiDocMap.json`,
pageNodeMapWithMetadataStringified,
);
addFileToFolder(outFolder, 'www/temp/apiDocMapHash', hash);
addFileToFolder(
outFolder,
'www/temp/apiDocMapPathList.json',
JSON.stringify(pathList),
);

const outApiFolder = getNestedFolderAtPath(outFolder, context.outDir);
for (const [path, fileOrFolder] of renderedApiFolder) {
Expand Down
2 changes: 2 additions & 0 deletions www/script/docs/apigen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface PageNodeMapWithMetadata {
pageNodeMap: PageNodeMap;
}

export type ApiDocMapPathList = string[];

export interface TableOfContentsInlineReference {
text: string;
urlHashText: string;
Expand Down
10 changes: 5 additions & 5 deletions www/src/index.html → www/script/template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preload" href="./index.tsx" as="script" />
<link rel="preload" href="::index.tsx::" as="script" />
<style>
@import './index.css';
@import '::index.css::';
</style>
</head>
<body>
<div id="root"></div>
<div id="root">::ssr::</div>
<script>
import { makeRequest } from './loadApiDocMap';
import { makeRequest } from '::loadApiDocMap.ts::';
makeRequest();
</script>
<script async src="./index.tsx"></script>
<script async src="::index.tsx::"></script>
</body>
</html>
110 changes: 110 additions & 0 deletions www/script/template/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as fs from 'fs';
import * as path from 'path';
import { h } from 'preact';
import { render } from 'preact-render-to-string';
import { convertApiDocMapPathToUrlPathName } from '../../src/apiDocMapPathList';
import { ApiDocMapResponseContextProvider } from '../../src/ApiDocMapResponseContext';
import { ApiDocPage, IndexPage, NotFoundPage } from '../../src/App';
import { ResponseDoneType, ResponseState } from '../../src/loadApiDocMap';
import { PageNodeMapWithMetadata } from '../docs/apigen/types';
import {
addFileToFolder,
Folder,
writeFolderToDirectoryPath,
} from '../docs/apigen/util/Folder';
import { getRelativePath } from '../docs/apigen/util/getRelativePath';

const template = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8');
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
const apiDocMap: PageNodeMapWithMetadata = require('../../temp/apiDocMap.json');

function shouldNotBeCalled(): never {
throw new Error('This should not be called.');
}

const outFolder = Folder();

function ensureRelative(path: string): string {
if (path[0] !== '.') {
return `./${path}`;
}
return path;
}

function addRenderedHtmlToFolder(html: string, filePath: string): void {
const contents = template
.replace('::ssr::', html)
.replace(
/::index\.tsx::/g,
ensureRelative(getRelativePath(filePath, 'index.tsx')),
)
.replace(
/::index\.css::/g,
ensureRelative(getRelativePath(filePath, 'index.css')),
)
.replace(
/::loadApiDocMap\.ts::/g,
ensureRelative(getRelativePath(filePath, 'loadApiDocMap.ts')),
);
addFileToFolder(outFolder, filePath, contents);
}

addRenderedHtmlToFolder(
render(
<ApiDocMapResponseContextProvider
value={{
getCurrentResponseState: shouldNotBeCalled,
onResponseStateChange: shouldNotBeCalled,
}}
>
<IndexPage />
</ApiDocMapResponseContextProvider>,
),
'index.html',
);

addRenderedHtmlToFolder(
render(
<ApiDocMapResponseContextProvider
value={{
getCurrentResponseState: shouldNotBeCalled,
onResponseStateChange: shouldNotBeCalled,
}}
>
<NotFoundPage />
</ApiDocMapResponseContextProvider>,
),
'404.html',
);

const responseState: ResponseState = {
type: ResponseDoneType,
data: apiDocMap,
};

for (const apiDocMapPath of Object.keys(apiDocMap.pageNodeMap)) {
addRenderedHtmlToFolder(
render(
<ApiDocMapResponseContextProvider
value={{
getCurrentResponseState: () => responseState,
onResponseStateChange: shouldNotBeCalled,
}}
>
<ApiDocPage pagePath={apiDocMapPath}></ApiDocPage>
</ApiDocMapResponseContextProvider>,
),
`${convertApiDocMapPathToUrlPathName(apiDocMapPath).replace(
/^\//,
'',
)}.html`,
);
}

export const rootDir = path.join(__dirname, '..', '..', 'template');

writeFolderToDirectoryPath(outFolder, rootDir).catch((error) => {
console.error('error writing pages to out directory...');
throw error;
});
36 changes: 36 additions & 0 deletions www/src/ApiDocMapResponseContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createContext } from 'preact';
import { useState, useContext, useEffect } from 'preact/hooks';
import { ResponseState, NonLoadingResponseState } from './loadApiDocMap';

export interface ApiDocMapResponseContextValue {
getCurrentResponseState: () => ResponseState;
onResponseStateChange: (
setResponseState: (responseState: NonLoadingResponseState) => void,
) => () => void;
}

const apiDocMapResponseContext = createContext<ApiDocMapResponseContextValue>(
(null as unknown) as ApiDocMapResponseContextValue,
);

export const ApiDocMapResponseContextProvider =
apiDocMapResponseContext.Provider;

export function useApiDocMapResponseState(): ResponseState {
const { getCurrentResponseState, onResponseStateChange } = useContext(
apiDocMapResponseContext,
);
const { 0: responseState, 1: setResponseState } = useState(
getCurrentResponseState(),
);

useEffect(() => {
const currentResponseState = getCurrentResponseState();
if (currentResponseState !== responseState) {
setResponseState(currentResponseState);
}
return onResponseStateChange(setResponseState);
});

return responseState;
}
Loading

1 comment on commit 0c9d7a7

@vercel
Copy link

@vercel vercel bot commented on 0c9d7a7 Sep 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.