From 5e40e1c89e7d70df1a8ace12897e533e5c6b3006 Mon Sep 17 00:00:00 2001 From: Willy Brauner Date: Thu, 22 Dec 2022 10:46:34 +0100 Subject: [PATCH 01/22] Create index inside index-server.tsx --- prerender/prerender.ts | 15 +---- server/server.js | 28 +++------ src/index-server.tsx | 100 +++++++++++++++++++++++++------- src/pages/homePage/HomePage.tsx | 1 - 4 files changed, 89 insertions(+), 55 deletions(-) diff --git a/prerender/prerender.ts b/prerender/prerender.ts index cd5d8a5..4a57418 100644 --- a/prerender/prerender.ts +++ b/prerender/prerender.ts @@ -23,22 +23,11 @@ export const prerender = async (urls: string[], outDirStatic = config.outDirStat try { // Request information from render method - const { renderToString, ssrStaticProps, globalData, meta, lang } = await render( - preparedUrl, - true - ) + const html = await render(preparedUrl, true) // Case url is index of root or of index of a group if (isRouteIndex(preparedUrl, urls)) preparedUrl = `${preparedUrl}/index` - const template = prepareTemplate(layout, { - app: renderToString, - ssrStaticProps, - globalData, - meta, - lang, - }) - // prepare sub folder templates if exist const routePath = path.resolve(`${outDirStatic}/${preparedUrl}`) @@ -46,7 +35,7 @@ export const prerender = async (urls: string[], outDirStatic = config.outDirStat const htmlFilePath = `${routePath}.html` // write file on the server - await mfs.createFile(htmlFilePath, template) + await mfs.createFile(htmlFilePath, html) console.log(palette.green(` → ${htmlFilePath.split("static")[1]}`)) } catch (e) { diff --git a/server/server.js b/server/server.js index cf7cdc1..48e1767 100644 --- a/server/server.js +++ b/server/server.js @@ -35,32 +35,22 @@ async function createDevServer() { const url = req.originalUrl try { - // 1. Read index.html - let layout = await mfs.readFile(resolve("index.html")) - - // 2. Apply Vite HTML transforms. This injects the Vite HMR client, and - // also applies HTML transforms from Vite plugins, e.g. global preambles - // from @vitejs/plugin-react - layout = await vite.transformIndexHtml(url, layout) - // 3. Load the server entry. vite.ssrLoadModule automatically transforms // your ESM source code to be usable in Node.js! There is no bundling // required, and provides efficient invalidation similar to HMR. const { render } = await vite.ssrLoadModule(`${config.srcDir}/index-server.tsx`) - // 4. render the app HTML. This assumes entry-server.js's exported `render` // function calls appropriate framework SSR APIs - const { meta, renderToString, ssrStaticProps, globalData, lang } = await render(url) - log({ url, renderToString, ssrStaticProps, globalData, lang }) + // log({ url, renderToString, ssrStaticProps, globalData, lang }) - // 5. Inject the app-rendered HTML into the template. - const html = prepareTemplate(layout, { - app: renderToString, - ssrStaticProps, - globalData, - meta, - lang, - }) + // 1. Read index.html + // let layout = await mfs.readFile(resolve("index.html")) + + // 2. Apply Vite HTML transforms. This injects the Vite HMR client, and + // also applies HTML transforms from Vite plugins, e.g. global preambles + // from @vitejs/plugin-react + let html = await render(url) + html = await vite.transformIndexHtml(url, html) // 6. Send the rendered HTML back. res.status(200).set({ "Content-Type": "text/html" }).end(html) diff --git a/src/index-server.tsx b/src/index-server.tsx index ac1ad64..fee2752 100644 --- a/src/index-server.tsx +++ b/src/index-server.tsx @@ -1,6 +1,6 @@ import fetch from "cross-fetch" import * as React from "react" -import ReactDOMServer from "react-dom/server" +import { renderToString } from "react-dom/server" import { routes } from "./routes" import App from "./components/app/App" import { langServiceInstance } from "./LangService" @@ -9,6 +9,29 @@ import { GlobalDataContext } from "./GlobalDataContext" import { preventSlashes } from "../config/helpers/prevent-slashes.js" import palette from "../config/helpers/palette.js" import { loadEnv } from "vite" + +/** + * Insert script + * @param name + * @param obj + */ +const InsertScript = ({ name, data }) => { + const stringify = (e): string => JSON.stringify(e, null, 2).replace(/\s/g, "") + return ( + + + + + + ) } diff --git a/src/pages/homePage/HomePage.tsx b/src/pages/homePage/HomePage.tsx index 6076dfb..e0ceb36 100644 --- a/src/pages/homePage/HomePage.tsx +++ b/src/pages/homePage/HomePage.tsx @@ -5,7 +5,6 @@ import debug from "@wbe/debug" import { MetasManager, TMetaTags } from "~/managers/MetaManager" interface IProps { - // data from getStaticProps meta: TMetaTags time?: { utc_datetime: string From 9ccf9db8fa7805385f4ede096dac79fb29629c30 Mon Sep 17 00:00:00 2001 From: Willy Brauner Date: Wed, 4 Jan 2023 18:37:35 +0100 Subject: [PATCH 02/22] Create manifest parser --- package-lock.json | 242 +++++++++++++++++++-- package.json | 9 +- prerender/__tests__/ManifestParser.test.ts | 26 +++ prerender/__tests__/fixtures/manifest.json | 44 ++++ prerender/helpers/ManifestParser.ts | 61 ++++++ prerender/prerender.ts | 7 +- 6 files changed, 361 insertions(+), 28 deletions(-) create mode 100644 prerender/__tests__/ManifestParser.test.ts create mode 100644 prerender/__tests__/fixtures/manifest.json create mode 100644 prerender/helpers/ManifestParser.ts diff --git a/package-lock.json b/package-lock.json index c1ceb73..1a4c988 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "@wbe/debug": "^1.1.0", "cross-fetch": "^3.1.5", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "ts-node": "^10.9.1" }, "devDependencies": { "@types/events": "^3.0.0", @@ -125,6 +126,26 @@ "resolved": "https://registry.npmjs.org/@cher-ami/utils/-/utils-1.0.1.tgz", "integrity": "sha512-6/nbm0LvAhiyNUZe1OkJZVOkyUwQj28Cf2h1hWu+l2eYemauGNKlfzA//VZcsuLmNaI2yFhOJ2xH6aUlBrK+cw==" }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.16.7", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.7.tgz", @@ -481,7 +502,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -522,8 +542,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.14", @@ -574,7 +593,7 @@ "version": "1.3.23", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.23.tgz", "integrity": "sha512-Aa7yw5+7ErOxr+G0J1eU2hkb9nEMSdt1Ye3isdAgg9mrsPuttk+cfLp6nP/Lux/VUnu5k4eOxeTy9UhjJhRAFw==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "bin": { "swcx": "run_swcx.js" @@ -759,6 +778,26 @@ "node": ">=10" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" + }, "node_modules/@types/chai": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", @@ -783,8 +822,7 @@ "node_modules/@types/node": { "version": "18.11.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.15.tgz", - "integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==", - "dev": true + "integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -872,7 +910,6 @@ "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -884,7 +921,6 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -954,6 +990,11 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1702,6 +1743,11 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -1796,6 +1842,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", @@ -3183,6 +3237,11 @@ "semver": "bin/semver" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4537,6 +4596,48 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -4581,7 +4682,6 @@ "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4658,6 +4758,11 @@ "node": ">= 0.4.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5214,6 +5319,14 @@ "engines": { "node": ">=8" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } } }, "dependencies": { @@ -5277,6 +5390,25 @@ "resolved": "https://registry.npmjs.org/@cher-ami/utils/-/utils-1.0.1.tgz", "integrity": "sha512-6/nbm0LvAhiyNUZe1OkJZVOkyUwQj28Cf2h1hWu+l2eYemauGNKlfzA//VZcsuLmNaI2yFhOJ2xH6aUlBrK+cw==" }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@esbuild/android-arm": { "version": "0.16.7", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.7.tgz", @@ -5434,8 +5566,7 @@ "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { "version": "1.1.2", @@ -5469,8 +5600,7 @@ "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { "version": "0.3.14", @@ -5512,7 +5642,7 @@ "version": "1.3.23", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.23.tgz", "integrity": "sha512-Aa7yw5+7ErOxr+G0J1eU2hkb9nEMSdt1Ye3isdAgg9mrsPuttk+cfLp6nP/Lux/VUnu5k4eOxeTy9UhjJhRAFw==", - "dev": true, + "devOptional": true, "requires": { "@swc/core-darwin-arm64": "1.3.23", "@swc/core-darwin-x64": "1.3.23", @@ -5596,6 +5726,26 @@ "dev": true, "optional": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" + }, "@types/chai": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", @@ -5620,8 +5770,7 @@ "@types/node": { "version": "18.11.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.15.tgz", - "integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==", - "dev": true + "integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==" }, "@types/prop-types": { "version": "15.7.5", @@ -5695,14 +5844,12 @@ "acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" }, "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" }, "aggregate-error": { "version": "3.1.0", @@ -5748,6 +5895,11 @@ "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -6289,6 +6441,11 @@ "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", "dev": true }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, "cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -6359,6 +6516,11 @@ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + }, "dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", @@ -7367,6 +7529,11 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8351,6 +8518,26 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -8382,8 +8569,7 @@ "typescript": { "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==" }, "unpipe": { "version": "1.0.0", @@ -8431,6 +8617,11 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -8785,6 +8976,11 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } } diff --git a/package.json b/package.json index 80ae25c..9354370 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build:server": "vite build --ssr src/index-server.tsx --outDir dist/front/server", "build:scripts": "vite build -c vite.scripts.config.ts", "build:static": "vite build --outDir dist/front/static", - "build": "npm run build:client && npm run build:server && npm run build:scripts && npm run build:static && npm run generate", + "build": "npm run build:server && npm run build:scripts && npm run build:static && npm run generate", "serve": "NODE_ENV=production node server", "generate": "node dist/front/_scripts/exe-prerender.js", "preview": "serve dist/front/static", @@ -29,7 +29,8 @@ "@wbe/debug": "^1.1.0", "cross-fetch": "^3.1.5", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "ts-node": "^10.9.1" }, "devDependencies": { "@types/events": "^3.0.0", @@ -49,12 +50,12 @@ "lint-staged": "^13.1.0", "portfinder-sync": "^0.0.2", "prettier": "^2.8.1", + "rollup-plugin-visualizer": "^5.8.3", "terser": "^5.16.1", "typescript": "^4.9.4", "vite": "^4.0.1", "vite-plugin-checker": "^0.5.2", - "vitest": "^0.25.8", - "rollup-plugin-visualizer": "^5.8.3" + "vitest": "^0.25.8" }, "optionalDependencies": { "esbuild-android-arm64": "^0.15.18", diff --git a/prerender/__tests__/ManifestParser.test.ts b/prerender/__tests__/ManifestParser.test.ts new file mode 100644 index 0000000..89b2d2b --- /dev/null +++ b/prerender/__tests__/ManifestParser.test.ts @@ -0,0 +1,26 @@ +import { expect, it } from "vitest" +import { ManifestParser } from "../helpers/ManifestParser" +import * as mfs from "../../config/helpers/mfs.js" +import config from "../../config/config.js" + +const manifestRaw = await mfs.readFile(`${config.outDirStatic}/manifest.json`) +const parser = new ManifestParser(manifestRaw) + +it("should return assets list", () => { + expect(parser.getAssets()).toEqual([ + "index-legacy-e92b0b23.js", + "roboto-regular-8cef0863.woff2", + "roboto-regular-18ab5ae4.woff", + "roboto-regular-b122d9b1.ttf", + "polyfills-legacy-163e9122.js", + "index-475b5da0.js", + "roboto-regular-8cef0863.woff2", + "roboto-regular-18ab5ae4.woff", + "roboto-regular-b122d9b1.ttf", + "index-ef71c845.css", + ]) +}) + +it("should return l", () => { + expect(parser.getAssetsByType(parser.getAssets())).toEqual("") +}) diff --git a/prerender/__tests__/fixtures/manifest.json b/prerender/__tests__/fixtures/manifest.json new file mode 100644 index 0000000..0f66bb4 --- /dev/null +++ b/prerender/__tests__/fixtures/manifest.json @@ -0,0 +1,44 @@ +{ + "index-legacy.html": { + "file": "index-legacy-e92b0b23.js", + "src": "index-legacy.html", + "isEntry": true, + "assets": [ + "roboto-regular-8cef0863.woff2", + "roboto-regular-18ab5ae4.woff", + "roboto-regular-b122d9b1.ttf" + ] + }, + "vite/legacy-polyfills-legacy": { + "file": "polyfills-legacy-163e9122.js", + "src": "vite/legacy-polyfills-legacy", + "isEntry": true + }, + "src/fonts/roboto-regular/roboto-regular.woff2": { + "file": "roboto-regular-8cef0863.woff2", + "src": "src/fonts/roboto-regular/roboto-regular.woff2" + }, + "src/fonts/roboto-regular/roboto-regular.woff": { + "file": "roboto-regular-18ab5ae4.woff", + "src": "src/fonts/roboto-regular/roboto-regular.woff" + }, + "src/fonts/roboto-regular/roboto-regular.ttf": { + "file": "roboto-regular-b122d9b1.ttf", + "src": "src/fonts/roboto-regular/roboto-regular.ttf" + }, + "index.css": { + "file": "index-ef71c845.css", + "src": "index.css" + }, + "index.html": { + "file": "index-475b5da0.js", + "src": "index.html", + "isEntry": true, + "css": ["index-ef71c845.css"], + "assets": [ + "roboto-regular-8cef0863.woff2", + "roboto-regular-18ab5ae4.woff", + "roboto-regular-b122d9b1.ttf" + ] + } +} diff --git a/prerender/helpers/ManifestParser.ts b/prerender/helpers/ManifestParser.ts new file mode 100644 index 0000000..4b51514 --- /dev/null +++ b/prerender/helpers/ManifestParser.ts @@ -0,0 +1,61 @@ +/** + * + * @param manifestRawFile + */ + +export class ManifestParser { + public manifestRawFile: string + public assetsList: string[] + public assetListByType: string[][] + + constructor(manifestRawFile: string) { + this.manifestRawFile = manifestRawFile + this.assetsList = this.getAssets(this.manifestRawFile) + // this.assetListByType = this.getAssetsByType(this.assetsList) + } + + getScriptTags() {} + + /** + * Group by extensions + * @param assetList + */ + getAssetsByType(assetList: string[]) { + return assetList.reduce((a, b) => { + const ext = b.split(".")[b.split(".").length - 1] + if (a?.[ext]) { + a[ext].push(b) + return a + } else { + return { + ...a, + [ext]: [b], + } + } + }, {}) + } + + /** + * Return all assets + * @param manifestRawFile + */ + getAssets(manifestRawFile: string = this.manifestRawFile): string[] { + const jsonManifest = JSON.parse(manifestRawFile) + console.log("jsonManifest", jsonManifest) + + return Object.keys(jsonManifest) + .reduce( + (a, b) => + jsonManifest[b].isEntry + ? [ + ...a, + jsonManifest[b].file, + ...(jsonManifest[b]?.assets || []), + ...(jsonManifest[b]?.css || []), + ] + : a, + [] + ) + .filter((e) => e) + } +} diff --git a/prerender/prerender.ts b/prerender/prerender.ts index 4a57418..f087ffa 100644 --- a/prerender/prerender.ts +++ b/prerender/prerender.ts @@ -4,7 +4,7 @@ import { render } from "~/index-server" import config from "../config/config.js" import palette from "../config/helpers/palette.js" import { isRouteIndex } from "./helpers/isRouteIndex" -import { prepareTemplate } from "../server/prepareTemplate.js" +import { ManifestParser } from "./helpers/ManifestParser" export const prerender = async (urls: string[], outDirStatic = config.outDirStatic) => { console.log("URLs to generate", urls) @@ -17,6 +17,11 @@ export const prerender = async (urls: string[], outDirStatic = config.outDirStat // now the layout is index-template.html const layout = await mfs.readFile(indexTemplateSrc) + const manifest = await mfs.readFile(`${outDirStatic}/manifest.json`) + const parser = new ManifestParser(manifest) + + // console.log("assets", parser.getScriptTags()) + // pre-render each route... for (const url of urls) { let preparedUrl = url.startsWith("/") ? url : `/${url}` From 83a4410ce10f9be77ba5750cebb8ead12a29dca9 Mon Sep 17 00:00:00 2001 From: Willy Brauner Date: Thu, 5 Jan 2023 11:20:25 +0100 Subject: [PATCH 03/22] Continue Manifest Parser --- prerender/__tests__/ManifestParser.test.ts | 25 ++++- prerender/helpers/ManifestParser.ts | 112 ++++++++++++++++++--- 2 files changed, 118 insertions(+), 19 deletions(-) diff --git a/prerender/__tests__/ManifestParser.test.ts b/prerender/__tests__/ManifestParser.test.ts index 89b2d2b..00ee437 100644 --- a/prerender/__tests__/ManifestParser.test.ts +++ b/prerender/__tests__/ManifestParser.test.ts @@ -4,10 +4,10 @@ import * as mfs from "../../config/helpers/mfs.js" import config from "../../config/config.js" const manifestRaw = await mfs.readFile(`${config.outDirStatic}/manifest.json`) -const parser = new ManifestParser(manifestRaw) it("should return assets list", () => { - expect(parser.getAssets()).toEqual([ + const assets = ManifestParser.getAssets(manifestRaw) + expect(assets).toEqual([ "index-legacy-e92b0b23.js", "roboto-regular-8cef0863.woff2", "roboto-regular-18ab5ae4.woff", @@ -21,6 +21,23 @@ it("should return assets list", () => { ]) }) -it("should return l", () => { - expect(parser.getAssetsByType(parser.getAssets())).toEqual("") +it("should return a list of assets by type", () => { + const assets = ManifestParser.getAssets(manifestRaw) + const assetsByType = ManifestParser.sortAssetsByType(assets) + const extensions = ["js", "woff2", "woff", "ttf", "css"] + expect(Object.keys(assetsByType)).toEqual(extensions) + + extensions.forEach((e) => { + expect( + assetsByType[e].every((f) => f.split(".")[f.split(".").length - 1] === e) + ).toBe(true) + }) +}) + +it("should return a list of script tag", () => { + const assets = ManifestParser.getAssets(manifestRaw) + const assetsByType = ManifestParser.sortAssetsByType(assets) + const scriptTags = ManifestParser.getScriptTags(assetsByType) + const scriptTagsFromManifest = ManifestParser.getScriptTagFromManifest(manifestRaw) + expect(scriptTags).toEqual(scriptTagsFromManifest) }) diff --git a/prerender/helpers/ManifestParser.ts b/prerender/helpers/ManifestParser.ts index 4b51514..03ce847 100644 --- a/prerender/helpers/ManifestParser.ts +++ b/prerender/helpers/ManifestParser.ts @@ -1,29 +1,100 @@ +export type TAssetsList = string[] +export type TAssetsByType = { [x: string]: string[] } +export type TScriptTags = { [x: string]: string[] } + /** + * ManifestParser + * Allow to get scriptTags + * + * * - * @param manifestRawFile */ - export class ManifestParser { - public manifestRawFile: string - public assetsList: string[] - public assetListByType: string[][] - - constructor(manifestRawFile: string) { - this.manifestRawFile = manifestRawFile - this.assetsList = this.getAssets(this.manifestRawFile) - // this.assetListByType = this.getAssetsByType(this.assetsList) + /** + * Directly get script Tags from raw manifest string + * @param manifestRaw + */ + static getScriptTagFromManifest(manifestRaw: string): TScriptTags { + const assets = ManifestParser.getAssets(manifestRaw) + const assetsByType = ManifestParser.sortAssetsByType(assets) + return ManifestParser.getScriptTags(assetsByType) } - getScriptTags() {} + /** + * Get script tags + * ex: + * + * { + * js: [ + * '', + * '' + * ], + * woff2: [ + * '' + * ], + * css: [ '' ] + * } + * + * @param assetListByType + * @param base + */ + static getScriptTags(assetListByType: TAssetsByType, base = "/"): TScriptTags { + if (typeof assetListByType !== "object" || !assetListByType) { + console.error("assetListByType is not valid, return", assetListByType) + return + } + // prettier-ignore + return Object.keys(assetListByType).reduce((a, b: string) => + { + const scriptURLs = assetListByType[b] + let scriptTags: string[] + if (b === "js") { + scriptTags = scriptURLs.map(url => { + const moduleType = url.includes("legacy") ? "nomodule" : "module" + return `` + }) + } + else if (b === "css") { + scriptTags = scriptURLs.map(url => + `` + ) + } + else if (b === "woff2") { + scriptTags = scriptURLs.map(url => + `` + ) + } + return { ...a, ...(scriptTags ? {[b]: scriptTags} : {}) } + },{}) + } /** + * + * Get assets by type (by extension): + * ex: + * { + * js: [ + * 'index-legacy-e92b0b23.js', + * 'polyfills-legacy-163e9122.js', + * 'index-475b5da0.js' + * ], + * woff2: [ + * 'roboto-regular-8cef0863.woff2', + * 'roboto-regular-8cef0863.woff2' + * ], + * css: [ + * 'index-ef71c845.css' + * ], + * ... + * } + * * Group by extensions * @param assetList */ - getAssetsByType(assetList: string[]) { + static sortAssetsByType(assetList: TAssetsList): TAssetsByType { return assetList.reduce((a, b) => { const ext = b.split(".")[b.split(".").length - 1] - if (a?.[ext]) { + if (a?.[ext] && !a[ext].includes(b)) { a[ext].push(b) return a } else { @@ -36,12 +107,23 @@ export class ManifestParser { } /** + * Get assets list + * + * [ + * 'index-legacy-e92b0b23.js', + * 'roboto-regular-8cef0863.woff2', + * 'roboto-regular-18ab5ae4.woff', + * 'roboto-regular-b122d9b1.ttf', + * 'polyfills-legacy-163e9122.js', + * 'index-475b5da0.js', + * ] + * + * * Return all assets * @param manifestRawFile */ - getAssets(manifestRawFile: string = this.manifestRawFile): string[] { + static getAssets(manifestRawFile: string): TAssetsList { const jsonManifest = JSON.parse(manifestRawFile) - console.log("jsonManifest", jsonManifest) return Object.keys(jsonManifest) .reduce( From 2d1da849bbf35ccde71188a6f804420cc41b8396 Mon Sep 17 00:00:00 2001 From: Willy Brauner Date: Thu, 5 Jan 2023 15:49:16 +0100 Subject: [PATCH 04/22] AddScripts --- prerender/__tests__/ManifestParser.test.ts | 3 +- prerender/helpers/ManifestParser.ts | 68 ++++++++++++++-------- prerender/prerender.ts | 4 +- server/server.js | 34 ++++++----- src/index-server.tsx | 61 +++++++++++-------- 5 files changed, 108 insertions(+), 62 deletions(-) diff --git a/prerender/__tests__/ManifestParser.test.ts b/prerender/__tests__/ManifestParser.test.ts index 00ee437..b80accd 100644 --- a/prerender/__tests__/ManifestParser.test.ts +++ b/prerender/__tests__/ManifestParser.test.ts @@ -37,7 +37,8 @@ it("should return a list of assets by type", () => { it("should return a list of script tag", () => { const assets = ManifestParser.getAssets(manifestRaw) const assetsByType = ManifestParser.sortAssetsByType(assets) - const scriptTags = ManifestParser.getScriptTags(assetsByType) + const scriptTags = ManifestParser.getScripts(assetsByType) const scriptTagsFromManifest = ManifestParser.getScriptTagFromManifest(manifestRaw) + console.log(scriptTags) expect(scriptTags).toEqual(scriptTagsFromManifest) }) diff --git a/prerender/helpers/ManifestParser.ts b/prerender/helpers/ManifestParser.ts index 03ce847..b3d333e 100644 --- a/prerender/helpers/ManifestParser.ts +++ b/prerender/helpers/ManifestParser.ts @@ -1,6 +1,10 @@ export type TAssetsList = string[] export type TAssetsByType = { [x: string]: string[] } -export type TScriptTags = { [x: string]: string[] } + +export type TScript = { tag: string; attr: { [x: string]: string } } +export type TScriptsObj = { + [ext: string]: TScript[] +} /** * ManifestParser @@ -14,31 +18,32 @@ export class ManifestParser { * Directly get script Tags from raw manifest string * @param manifestRaw */ - static getScriptTagFromManifest(manifestRaw: string): TScriptTags { + static getScriptTagFromManifest(manifestRaw: string): TScriptsObj { const assets = ManifestParser.getAssets(manifestRaw) const assetsByType = ManifestParser.sortAssetsByType(assets) - return ManifestParser.getScriptTags(assetsByType) + return ManifestParser.getScripts(assetsByType) } /** * Get script tags - * ex: * + * ex: * { * js: [ - * '', - * '' - * ], - * woff2: [ - * '' - * ], - * css: [ '' ] + * { + * tag: 'script', + * attr: { + * src: "" + * noModule: "", + * } + * }, * } + * ... * * @param assetListByType * @param base */ - static getScriptTags(assetListByType: TAssetsByType, base = "/"): TScriptTags { + static getScripts(assetListByType: TAssetsByType, base = "/"): TScriptsObj { if (typeof assetListByType !== "object" || !assetListByType) { console.error("assetListByType is not valid, return", assetListByType) return @@ -47,24 +52,41 @@ export class ManifestParser { return Object.keys(assetListByType).reduce((a, b: string) => { const scriptURLs = assetListByType[b] - let scriptTags: string[] + let scripts: TScript[] if (b === "js") { - scriptTags = scriptURLs.map(url => { - const moduleType = url.includes("legacy") ? "nomodule" : "module" - return `` + scripts = scriptURLs.map(url => { + return { + tag: "script", + attr: { + ...(url.includes("legacy") ? {noModule: ""} : {type: "module"}), + crossOrigin:"anonymous", + src: `${base}${url}` + } + } }) } else if (b === "css") { - scriptTags = scriptURLs.map(url => - `` - ) + scripts = scriptURLs.map(url => ({ + tag: "link", + attr: { + rel: "stylesheet", + href: `${base}${url}` + } + })) } else if (b === "woff2") { - scriptTags = scriptURLs.map(url => - `` - ) + scripts = scriptURLs.map(url => ({ + tag: "link", + attr: { + rel: "preload", + as: "font", + type:"font/woff2", + crossOrigin:"anonymous", + href: `${base}${url}` + } + })) } - return { ...a, ...(scriptTags ? {[b]: scriptTags} : {}) } + return { ...a, ...(scripts ? {[b]: scripts} : {}) } },{}) } diff --git a/prerender/prerender.ts b/prerender/prerender.ts index f087ffa..666f940 100644 --- a/prerender/prerender.ts +++ b/prerender/prerender.ts @@ -18,7 +18,7 @@ export const prerender = async (urls: string[], outDirStatic = config.outDirStat const layout = await mfs.readFile(indexTemplateSrc) const manifest = await mfs.readFile(`${outDirStatic}/manifest.json`) - const parser = new ManifestParser(manifest) + const scriptTags = ManifestParser.getScriptTagFromManifest(manifest) // console.log("assets", parser.getScriptTags()) @@ -28,7 +28,7 @@ export const prerender = async (urls: string[], outDirStatic = config.outDirStat try { // Request information from render method - const html = await render(preparedUrl, true) + const html = await render(preparedUrl, scriptTags, true) // Case url is index of root or of index of a group if (isRouteIndex(preparedUrl, urls)) preparedUrl = `${preparedUrl}/index` diff --git a/server/server.js b/server/server.js index 48e1767..149d4ed 100644 --- a/server/server.js +++ b/server/server.js @@ -1,5 +1,5 @@ +import * as React from "react" import * as mfs from "../config/helpers/mfs.js" -import { resolve } from "path" import express from "express" import { createServer as createViteServer } from "vite" import compression from "compression" @@ -35,21 +35,29 @@ async function createDevServer() { const url = req.originalUrl try { - // 3. Load the server entry. vite.ssrLoadModule automatically transforms - // your ESM source code to be usable in Node.js! There is no bundling - // required, and provides efficient invalidation similar to HMR. + // Load the server entry. vite.ssrLoadModule automatically transforms + // your ESM source code to be usable in Node.js! There is no bundling + // required, and provides efficient invalidation similar to HMR. const { render } = await vite.ssrLoadModule(`${config.srcDir}/index-server.tsx`) - // 4. render the app HTML. This assumes entry-server.js's exported `render` - // function calls appropriate framework SSR APIs - // log({ url, renderToString, ssrStaticProps, globalData, lang }) - // 1. Read index.html - // let layout = await mfs.readFile(resolve("index.html")) + const scripts = { + js: [ + { + tag: "script", + attr: { type: "module", src: "/src/index.tsx" }, + }, + ], + css: [ + { + tag: "link", + attr: { rel: "stylesheet", href: `/test.css` }, + }, + ], + } + // get HTML from JSX + let html = await render(url, scripts, false) - // 2. Apply Vite HTML transforms. This injects the Vite HMR client, and - // also applies HTML transforms from Vite plugins, e.g. global preambles - // from @vitejs/plugin-react - let html = await render(url) + // Apply Vite HTML and plugins transforms. This injects the Vite HMR client html = await vite.transformIndexHtml(url, html) // 6. Send the rendered HTML back. diff --git a/src/index-server.tsx b/src/index-server.tsx index fee2752..83f02ea 100644 --- a/src/index-server.tsx +++ b/src/index-server.tsx @@ -9,7 +9,10 @@ import { GlobalDataContext } from "./GlobalDataContext" import { preventSlashes } from "../config/helpers/prevent-slashes.js" import palette from "../config/helpers/palette.js" import { loadEnv } from "vite" +import { TScript, TScriptsObj } from "../prerender/helpers/ManifestParser" +// ----------------------------------------------------------------------------- PREPARE +// TODO à sortir /** * Insert script * @param name @@ -26,13 +29,29 @@ const InsertScript = ({ name, data }) => { /> ) } +const ScriptTag = ({ tag, attr }: TScript): JSX.Element => { + const T = tag + // @ts-ignore + if (attr.noModule === "") return + else return +} +const CherScripts = ({ scripts }: { scripts: TScript[] }): JSX.Element => ( + <> + {scripts?.map((script, i) => ( + + ))} + +) + +// ----------------------------------------------------------------------------- PREPARE /** * Server render * @param url * @param isPrerender + * @param scripts */ -export async function render(url: string, isPrerender = false) { +export async function render(url: string, scripts: TScriptsObj, isPrerender = false) { const loadEnvVars = loadEnv("", process.cwd(), "") // Load process.env base if is available by external load, else we get vite app base @@ -56,18 +75,13 @@ export async function render(url: string, isPrerender = false) { routes, langService, }) - - const { - props: { meta }, - } = ssrStaticProps - + const meta = ssrStaticProps.props?.meta // Request Global data example-client const requestGlobalData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users") const users = await res.json() return { users } } - const globalData = await requestGlobalData() // Template for server @@ -79,24 +93,25 @@ export async function render(url: string, isPrerender = false) { - {meta?.title} - - - + + - - - - - - + + + + + - - - - - + + + + + + + {/* INJECT */} + + {/* ROOT */} @@ -116,7 +131,7 @@ export async function render(url: string, isPrerender = false) { {/* INJECT */} - + From f16b7bfdf27c7003d16c238ac7cf4db8211465ad Mon Sep 17 00:00:00 2001 From: Willy Brauner Date: Sat, 7 Jan 2023 00:38:41 +0100 Subject: [PATCH 05/22] Patch attr nomodule --- prerender/prerender.ts | 23 ++++-------- server/prepareTemplate.js | 26 ------------- server/server.js | 36 +++++------------- src/index-server.tsx | 43 ++++------------------ src/server/helpers/CherScripts.tsx | 17 +++++++++ src/server/helpers/InsertScript.tsx | 18 +++++++++ src/server/helpers/patchScriptAttribute.ts | 7 ++++ 7 files changed, 67 insertions(+), 103 deletions(-) delete mode 100644 server/prepareTemplate.js create mode 100644 src/server/helpers/CherScripts.tsx create mode 100644 src/server/helpers/InsertScript.tsx create mode 100644 src/server/helpers/patchScriptAttribute.ts diff --git a/prerender/prerender.ts b/prerender/prerender.ts index 666f940..dd664b7 100644 --- a/prerender/prerender.ts +++ b/prerender/prerender.ts @@ -7,41 +7,32 @@ import { isRouteIndex } from "./helpers/isRouteIndex" import { ManifestParser } from "./helpers/ManifestParser" export const prerender = async (urls: string[], outDirStatic = config.outDirStatic) => { - console.log("URLs to generate", urls) const indexTemplateSrc = `${outDirStatic}/index-template.html` // copy index as template to avoid the override with the generated static index.html bellow - if (!(await mfs.fileExists(indexTemplateSrc))) + if (!(await mfs.fileExists(indexTemplateSrc))) { await mfs.copyFile(`${outDirStatic}/index.html`, indexTemplateSrc) + } - // now the layout is index-template.html - const layout = await mfs.readFile(indexTemplateSrc) - + // get script tags to inject in render const manifest = await mfs.readFile(`${outDirStatic}/manifest.json`) const scriptTags = ManifestParser.getScriptTagFromManifest(manifest) - // console.log("assets", parser.getScriptTags()) - - // pre-render each route... + // pre-render each route for (const url of urls) { let preparedUrl = url.startsWith("/") ? url : `/${url}` try { // Request information from render method const html = await render(preparedUrl, scriptTags, true) - // Case url is index of root or of index of a group if (isRouteIndex(preparedUrl, urls)) preparedUrl = `${preparedUrl}/index` - - // prepare sub folder templates if exist + // Prepare sub folder templates if exist const routePath = path.resolve(`${outDirStatic}/${preparedUrl}`) - - // add .html to the end of th pat + // Add .html to the end of th pat const htmlFilePath = `${routePath}.html` - - // write file on the server + // Write file on the server await mfs.createFile(htmlFilePath, html) - console.log(palette.green(` → ${htmlFilePath.split("static")[1]}`)) } catch (e) { console.log(e) diff --git a/server/prepareTemplate.js b/server/prepareTemplate.js deleted file mode 100644 index 731a974..0000000 --- a/server/prepareTemplate.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Replace strings - * @param layout - * @param meta - * @param app - * @param ssrStaticProps - * @param globalData - * @param lang - * @return {string} - */ -export const prepareTemplate = ( - layout, - { meta, app, ssrStaticProps, globalData, lang } -) => { - if (!layout) return "" - return layout - .replaceAll(``, meta?.title ?? "") - .replaceAll(``, meta?.description ?? "") - .replaceAll(``, meta?.imageUrl ?? "") - .replaceAll(``, meta?.url ?? "") - .replaceAll(``, meta?.siteName ?? "") - .replace(``, lang) - .replace(``, app) - .replace(`""`, JSON.stringify(ssrStaticProps)) - .replace(`""`, JSON.stringify(globalData)) -} diff --git a/server/server.js b/server/server.js index 149d4ed..5b91e44 100644 --- a/server/server.js +++ b/server/server.js @@ -5,7 +5,6 @@ import { createServer as createViteServer } from "vite" import compression from "compression" import portFinderSync from "portfinder-sync" import config from "../config/config.js" -import { prepareTemplate } from "./prepareTemplate.js" import debug from "@wbe/debug" const log = debug("server:server") @@ -40,21 +39,10 @@ async function createDevServer() { // required, and provides efficient invalidation similar to HMR. const { render } = await vite.ssrLoadModule(`${config.srcDir}/index-server.tsx`) + // get HTML from JSX const scripts = { - js: [ - { - tag: "script", - attr: { type: "module", src: "/src/index.tsx" }, - }, - ], - css: [ - { - tag: "link", - attr: { rel: "stylesheet", href: `/test.css` }, - }, - ], + js: [{ tag: "script", attr: { type: "module", src: "/src/index.tsx" } }], } - // get HTML from JSX let html = await render(url, scripts, false) // Apply Vite HTML and plugins transforms. This injects the Vite HMR client @@ -75,7 +63,7 @@ async function createDevServer() { /** * Create production server - * + * TODO * */ async function createProdServer() { @@ -91,17 +79,13 @@ async function createProdServer() { app.use((await import("compression")).default()) app.use("*", async (req, res) => { try { - const url = req.originalUrl - const layout = await mfs.readFile(`${config.outDirClient}/index.html`) - const { render } = await import(`${config.outDirServer}/index-server.js`) - const { meta, renderToString, ssrStaticProps, globalData } = await render(url) - const html = prepareTemplate(layout, { - app: renderToString, - ssrStaticProps, - globalData, - meta, - }) - res.status(200).set({ "Content-Type": "text/html" }).end(html) + // TODO + // const url = req.originalUrl + // const layout = await mfs.readFile(`${config.outDirClient}/index.html`) + // const { render } = await import(`${config.outDirServer}/index-server.js`) + // const { meta, renderToString, ssrStaticProps, globalData } = await render(url) + // ... + // res.status(200).set({ "Content-Type": "text/html" }).end(html) } catch (e) { console.log(e.stack) res.status(500).end(e.stack) diff --git a/src/index-server.tsx b/src/index-server.tsx index 83f02ea..2716a7d 100644 --- a/src/index-server.tsx +++ b/src/index-server.tsx @@ -10,38 +10,9 @@ import { preventSlashes } from "../config/helpers/prevent-slashes.js" import palette from "../config/helpers/palette.js" import { loadEnv } from "vite" import { TScript, TScriptsObj } from "../prerender/helpers/ManifestParser" - -// ----------------------------------------------------------------------------- PREPARE -// TODO à sortir -/** - * Insert script - * @param name - * @param obj - */ -const InsertScript = ({ name, data }) => { - const stringify = (e): string => JSON.stringify(e, null, 2).replace(/\s/g, "") - return ( - + + {/* react plugin HMR */} + - diff --git a/package.json b/package.json index d039acb..8ee6e29 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "build:scripts": "vite build -c vite.scripts.config.ts", "build:static": "vite build --outDir dist/front/static", "build": "npm run build:client && npm run build:server && npm run build:scripts && npm run build:static && npm run generate", - "serve": "NODE_ENV=production node server", "generate": "node dist/front/_scripts/exe-prerender.js", "preview": "serve dist/front/static", "setup": "node config/tasks/setup/setup.js && npm run prepare", @@ -20,6 +19,7 @@ "scaffold-wp": "node config/tasks/scaffold-wp/scaffold-wp.js", "prepare": "husky install", "prettier": "prettier --write .", + "test:watch": "vitest", "test": "vitest run" }, "dependencies": { diff --git a/prerender/prerender.ts b/prerender/prerender.ts index df99f44..8909fa2 100644 --- a/prerender/prerender.ts +++ b/prerender/prerender.ts @@ -6,8 +6,8 @@ import palette from "../config/helpers/palette.js" import { isRouteIndex } from "./helpers/isRouteIndex" import { ManifestParser } from "./helpers/ManifestParser" import { renderToPipeableStream, renderToString } from "react-dom/server" -import { patchScriptAttribute } from "~/server/helpers/patchScriptAttribute" import { JSXElementConstructor, ReactElement } from "react" +import { htmlReplacement } from "../src/server/helpers/htmlReplacement" /** * Prerender @@ -67,6 +67,6 @@ const createHtmlFile = async ( const routePath = path.resolve(`${outDir}/${url}`) const htmlFilePath = `${routePath}.html` // Create file - await mfs.createFile(htmlFilePath, patchScriptAttribute(renderToString(dom))) + await mfs.createFile(htmlFilePath, htmlReplacement(renderToString(dom))) console.log(palette.green(` → ${htmlFilePath.split("static")[1]}`)) } diff --git a/src/index-server.tsx b/src/index-server.tsx index 60c8249..3f739af 100644 --- a/src/index-server.tsx +++ b/src/index-server.tsx @@ -6,9 +6,8 @@ import { langServiceInstance } from "./LangService" import { requestStaticPropsFromRoute, Router } from "@cher-ami/router" import { GlobalDataContext } from "./GlobalDataContext" import { preventSlashes } from "../config/helpers/prevent-slashes.js" -import palette from "../config/helpers/palette.js" import { loadEnv } from "vite" -import { TScript, TScriptsObj } from "../prerender/helpers/ManifestParser" +import { TScriptsObj } from "../prerender/helpers/ManifestParser" import { CherScripts } from "~/server/helpers/CherScripts" import { InsertScript } from "~/server/helpers/InsertScript" import { ViteDevScripts } from "~/server/helpers/ViteDevScripts" @@ -39,18 +38,20 @@ export async function render(url: string, scripts: TScriptsObj, isPrerender = fa langService, }) const meta = ssrStaticProps?.props?.meta + // Request Global data example-client const requestGlobalData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users/1") const users = await res.json() return { users } } + const globalData = await requestGlobalData() // Template for server - const dom = ( - - {/* prettier-ignore */} + // prettier-ignore + return ( + @@ -63,7 +64,6 @@ export async function render(url: string, scripts: TScriptsObj, isPrerender = fa - {/* ROOT */}
- {/* INJECT */} ) - - return dom } diff --git a/src/server/helpers/htmlReplacement.ts b/src/server/helpers/htmlReplacement.ts new file mode 100644 index 0000000..7aed2af --- /dev/null +++ b/src/server/helpers/htmlReplacement.ts @@ -0,0 +1,9 @@ +/** + * Ugly render patch middleware + * @param render + */ +export const htmlReplacement = (render: string): string => + render + .replace(" - render.replaceAll('