From ad0d7a6f2acfc524f922ebc480f6708c36e7f410 Mon Sep 17 00:00:00 2001 From: Willy Brauner Date: Thu, 15 Dec 2022 17:34:08 +0100 Subject: [PATCH] Externalize prepare template (#100) * Externalize prepare template * Update doc * Add route example * Add lang in template --- docs/02.vite-configuration.md | 21 ++-------- docs/09.php-service-integration.md | 2 +- index.html | 2 +- package.json | 2 +- prerender/prerender.ts | 22 ++++------ server/prepareTemplate.js | 26 ++++++++++++ server.js => server/server.js | 66 +++++++++++++++--------------- src/index-server.tsx | 1 + src/routes.ts | 8 ++-- vite.scripts.config.ts | 2 +- 10 files changed, 81 insertions(+), 71 deletions(-) create mode 100644 server/prepareTemplate.js rename server.js => server/server.js (65%) diff --git a/docs/02.vite-configuration.md b/docs/02.vite-configuration.md index 7ca8806..1aac6f2 100644 --- a/docs/02.vite-configuration.md +++ b/docs/02.vite-configuration.md @@ -8,25 +8,12 @@ This one supports the dev-server, HMR and transformation & compilation. Vite's configuration is managed by two main files: - [vite.config.ts](vite.config.ts): contains the whole vite config [(vite config documentation)](https://vitejs.dev/config/) +- [vite.scripts.config.ts](vite.scripts.config.ts): contains the whole vite scripts config. It built scripts files relative to the SSR and SSG part. - [config/config.js](config/config.js): is the internal paths and tasks config file. ### Entry points -By default, the single application entrypoint is [src/index.tsx](src/index.tsx). It initializes a react App. -But this file can be changed as `index.ts` or `index.js`. In case this filename or type change, [config/config.js](config/config.js) -`input` array need to be modified. +Two entry points are set: -```js -input: [ - "src/index.tsx", // -> ex: modified as 'src/main.js' -] -``` - -Being an array, it is possible to define several application entry points. - -```js -input: ["src/main.ts", "src/second.ts"] -``` - -The default [base.twig](dist/front/www/views/layouts/base.twig) template, -is configured to add as script each input entrypoint automatically. +- server side [src/index-server.tsx](src/index-server.tsx) +- client side [src/index-client.tsx](src/index.tsx) diff --git a/docs/09.php-service-integration.md b/docs/09.php-service-integration.md index 6efba3b..fa94c74 100644 --- a/docs/09.php-service-integration.md +++ b/docs/09.php-service-integration.md @@ -4,7 +4,7 @@ - Add `./composer-install.sh` script in the project root. - Add docker apache volume in `docker-compose.yml` - Remove ssr ssg files - - [server.js](../server.js) + - [server.js](../server/server.js) - [vite.scripts.config.js](../vite.scripts.config.js) - [index.html](index.html) - Update [config/config.js](../config/config.js) diff --git a/index.html b/index.html index 0b908ae..5f36c91 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + diff --git a/package.json b/package.json index 79638a0..4a973c9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "src/index.tsx", "type": "module", "scripts": { - "dev": "node server.js", + "dev": "node server/server.js", "build:client": "vite build --outDir dist/front/client", "build:server": "vite build --ssr src/index-server.tsx --outDir dist/front/server", "build:scripts": "vite build -c vite.scripts.config.ts", diff --git a/prerender/prerender.ts b/prerender/prerender.ts index e4477d5..cd5d8a5 100644 --- a/prerender/prerender.ts +++ b/prerender/prerender.ts @@ -4,6 +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" export const prerender = async (urls: string[], outDirStatic = config.outDirStatic) => { console.log("URLs to generate", urls) @@ -22,7 +23,7 @@ export const prerender = async (urls: string[], outDirStatic = config.outDirStat try { // Request information from render method - const { renderToString, ssrStaticProps, globalData, meta } = await render( + const { renderToString, ssrStaticProps, globalData, meta, lang } = await render( preparedUrl, true ) @@ -30,18 +31,13 @@ export const prerender = async (urls: string[], outDirStatic = config.outDirStat // Case url is index of root or of index of a group if (isRouteIndex(preparedUrl, urls)) preparedUrl = `${preparedUrl}/index` - // include it in the template - const template = layout - ? layout - .replaceAll(``, meta?.title ?? "") - .replaceAll(``, meta?.description ?? "") - .replaceAll(``, meta?.imageUrl ?? "") - .replaceAll(``, meta?.url ?? "") - .replaceAll(``, meta?.siteName ?? "") - .replace(``, renderToString) - .replace(`""`, JSON.stringify(ssrStaticProps)) - .replace(`""`, JSON.stringify(globalData)) - : "" + const template = prepareTemplate(layout, { + app: renderToString, + ssrStaticProps, + globalData, + meta, + lang, + }) // prepare sub folder templates if exist const routePath = path.resolve(`${outDirStatic}/${preparedUrl}`) diff --git a/server/prepareTemplate.js b/server/prepareTemplate.js new file mode 100644 index 0000000..731a974 --- /dev/null +++ b/server/prepareTemplate.js @@ -0,0 +1,26 @@ +/** + * 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.js b/server/server.js similarity index 65% rename from server.js rename to server/server.js index 4dccd31..cf7cdc1 100644 --- a/server.js +++ b/server/server.js @@ -1,17 +1,20 @@ -import * as mfs from "./config/helpers/mfs.js" +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" import portFinderSync from "portfinder-sync" -import config from "./config/config.js" +import config from "../config/config.js" +import { prepareTemplate } from "./prepareTemplate.js" import debug from "@wbe/debug" const log = debug("server:server") /** * Create development server + * + * + * */ - async function createDevServer() { const app = express() @@ -33,13 +36,12 @@ async function createDevServer() { try { // 1. Read index.html - let template = await mfs.readFile(resolve("index.html")) - //console.log('template', template) + 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 - template = await vite.transformIndexHtml(url, template) + 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 @@ -47,22 +49,18 @@ async function createDevServer() { 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, - // e.g. ReactDOMServer.renderToString() - const { meta, renderToString, ssrStaticProps, globalData } = await render(url) - - log({ url, renderToString, ssrStaticProps, globalData }) + // function calls appropriate framework SSR APIs + const { meta, renderToString, ssrStaticProps, globalData, lang } = await render(url) + log({ url, renderToString, ssrStaticProps, globalData, lang }) // 5. Inject the app-rendered HTML into the template. - let html = template - .replaceAll(``, meta?.title ?? "") - .replaceAll(``, meta?.description ?? "") - .replaceAll(``, meta?.imageUrl ?? "") - .replaceAll(``, meta?.url ?? "") - .replaceAll(``, meta?.siteName ?? "") - .replace(``, renderToString) - .replace(`""`, JSON.stringify(ssrStaticProps)) - .replace(`""`, JSON.stringify(globalData)) + const html = prepareTemplate(layout, { + app: renderToString, + ssrStaticProps, + globalData, + meta, + lang, + }) // 6. Send the rendered HTML back. res.status(200).set({ "Content-Type": "text/html" }).end(html) @@ -79,6 +77,8 @@ async function createDevServer() { /** * Create production server + * + * */ async function createProdServer() { console.log("prod server") @@ -90,17 +90,19 @@ async function createProdServer() { appType: "custom", // don't include Vite's default HTML handling middlewares }) - app.use(compression()) - // @ts-ignore - app.use(serveStatic(config.outDirClient), { index: false }) - + app.use((await import("compression")).default()) app.use("*", async (req, res) => { try { const url = req.originalUrl - const template = await mfs.readFile(`${config.outDirClient}/index.html`) - const { render } = require(`${config.outDirServer}/index-server.js`) - const appHtml = render(url) - const html = template.replace(``, appHtml) + 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) } catch (e) { console.log(e.stack) @@ -117,8 +119,6 @@ async function createProdServer() { const isProd = process.env.NODE_ENV === "production" const port = process.env.DOCKER_NODE_PORT ?? portFinderSync.getPort(3000) -if (!isProd) { - createDevServer().then(({ app }) => app.listen(port)) -} else { - createProdServer().then(({ app }) => app.listen(port)) -} +isProd + ? createProdServer().then(({ app }) => app.listen(port)) + : createDevServer().then(({ app }) => app.listen(port)) diff --git a/src/index-server.tsx b/src/index-server.tsx index 128f348..ac1ad64 100644 --- a/src/index-server.tsx +++ b/src/index-server.tsx @@ -64,5 +64,6 @@ export async function render(url: string, isPrerender = false) { ssrStaticProps, globalData, languages: langService.languages, + lang: langService.currentLang?.key, } } diff --git a/src/routes.ts b/src/routes.ts index d633663..b22b799 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,5 +1,5 @@ import fetch from "cross-fetch" -import { TRoute } from "@cher-ami/router" +import { TLanguage, TRoute } from "@cher-ami/router" import { TMetaTags } from "./managers/MetaManager" import HomePage from "./pages/homePage/HomePage" import WorkPage from "./pages/workPage/WorkPage" @@ -16,7 +16,7 @@ export const routes: TRoute[] = [ path: "/", component: HomePage, name: EPages.HOME, - getStaticProps: async (props) => { + getStaticProps: async (props, currentLang: TLanguage) => { const res = await fetch("https://worldtimeapi.org/api/ip") const time = await res.json() const meta: TMetaTags = { @@ -31,7 +31,7 @@ export const routes: TRoute[] = [ path: "/work/:slug?", name: EPages.WORK, component: WorkPage, - getStaticProps: async (props) => { + getStaticProps: async (props, currentLang: TLanguage) => { const meta: TMetaTags = { title: `Work - ${props.params.slug}`, description: "Work description", @@ -44,7 +44,7 @@ export const routes: TRoute[] = [ path: "/:rest", name: EPages.NOT_FOUND, component: NotFoundPage, - getStaticProps: async (props) => { + getStaticProps: async (props, currentLang: TLanguage) => { const meta = { title: `404`, description: "Not found", diff --git a/vite.scripts.config.ts b/vite.scripts.config.ts index 1290001..9981f4f 100644 --- a/vite.scripts.config.ts +++ b/vite.scripts.config.ts @@ -23,7 +23,7 @@ export default defineConfig(({ command, mode }) => { }, rollupOptions: { input: [ - resolve("server.js"), + resolve("server/server.js"), resolve("prerender/prerender.ts"), resolve("prerender/exe-prerender-server.ts"), resolve("prerender/exe-prerender.ts"),