Skip to content

Commit

Permalink
Externalize prepare template (#100)
Browse files Browse the repository at this point in the history
* Externalize prepare template

* Update doc

* Add route example

* Add lang in template
  • Loading branch information
Willy Brauner authored Dec 15, 2022
1 parent 1f1ee4b commit ad0d7a6
Show file tree
Hide file tree
Showing 10 changed files with 81 additions and 71 deletions.
21 changes: 4 additions & 17 deletions docs/02.vite-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

### <a name="Entrypoints"></a>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)
2 changes: 1 addition & 1 deletion docs/09.php-service-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="<!--lang-->">
<head>
<meta charset="UTF-8" />
<meta http-equiv="imagetoolbar" content="no" />
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
22 changes: 9 additions & 13 deletions prerender/prerender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -22,26 +23,21 @@ 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
)

// 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-->`, meta?.title ?? "")
.replaceAll(`<!--meta-description-->`, meta?.description ?? "")
.replaceAll(`<!--meta-imageUrl-->`, meta?.imageUrl ?? "")
.replaceAll(`<!--meta-url-->`, meta?.url ?? "")
.replaceAll(`<!--meta-siteName-->`, meta?.siteName ?? "")
.replace(`<!--app-html-->`, renderToString)
.replace(`"<!--ssr-static-props-->"`, JSON.stringify(ssrStaticProps))
.replace(`"<!--ssr-global-data-->"`, 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}`)
Expand Down
26 changes: 26 additions & 0 deletions server/prepareTemplate.js
Original file line number Diff line number Diff line change
@@ -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-->`, meta?.title ?? "")
.replaceAll(`<!--meta-description-->`, meta?.description ?? "")
.replaceAll(`<!--meta-imageUrl-->`, meta?.imageUrl ?? "")
.replaceAll(`<!--meta-url-->`, meta?.url ?? "")
.replaceAll(`<!--meta-siteName-->`, meta?.siteName ?? "")
.replace(`<!--lang-->`, lang)
.replace(`<!--app-html-->`, app)
.replace(`"<!--ssr-static-props-->"`, JSON.stringify(ssrStaticProps))
.replace(`"<!--ssr-global-data-->"`, JSON.stringify(globalData))
}
66 changes: 33 additions & 33 deletions server.js → server/server.js
Original file line number Diff line number Diff line change
@@ -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()

Expand All @@ -33,36 +36,31 @@ 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
// 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,
// 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-->`, meta?.title ?? "")
.replaceAll(`<!--meta-description-->`, meta?.description ?? "")
.replaceAll(`<!--meta-imageUrl-->`, meta?.imageUrl ?? "")
.replaceAll(`<!--meta-url-->`, meta?.url ?? "")
.replaceAll(`<!--meta-siteName-->`, meta?.siteName ?? "")
.replace(`<!--app-html-->`, renderToString)
.replace(`"<!--ssr-static-props-->"`, JSON.stringify(ssrStaticProps))
.replace(`"<!--ssr-global-data-->"`, 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)
Expand All @@ -79,6 +77,8 @@ async function createDevServer() {

/**
* Create production server
*
*
*/
async function createProdServer() {
console.log("prod server")
Expand All @@ -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(`<!--app-html-->`, 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)
Expand All @@ -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))
1 change: 1 addition & 0 deletions src/index-server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@ export async function render(url: string, isPrerender = false) {
ssrStaticProps,
globalData,
languages: langService.languages,
lang: langService.currentLang?.key,
}
}
8 changes: 4 additions & 4 deletions src/routes.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 = {
Expand All @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion vite.scripts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down

0 comments on commit ad0d7a6

Please sign in to comment.