Skip to content

Commit

Permalink
feat(utils): add templateRenderer utils
Browse files Browse the repository at this point in the history
BREAKING CHANGE: the outlets of templateBuild has been updated, see `TEMPLATE_RENDERER_OUTLETS` in `@vuepress/utils` package
  • Loading branch information
meteorlxy committed Sep 14, 2023
1 parent 2ac78bd commit 6e5c3e4
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 90 deletions.
5 changes: 3 additions & 2 deletions ecosystem/theme-default/templates/build.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
</script>
<!--vuepress-ssr-head-->
<!--vuepress-ssr-styles-->
<!--vuepress-ssr-resources-->
<!--vuepress-ssr-preload-->
<!--vuepress-ssr-prefetch-->
</head>
<body>
<div id="app"><!--vuepress-ssr-app--></div>
<div id="app"><!--vuepress-ssr-content--></div>
<!--vuepress-ssr-scripts-->
</body>
</html>
10 changes: 5 additions & 5 deletions packages/bundler-vite/src/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ export const build = async (

// render pages
await withSpinner(`Rendering ${app.pages.length} pages`)(async (spinner) => {
// load ssr template file
const ssrTemplate = (
await fs.readFile(app.options.templateBuild)
).toString()

// get client bundle entry chunk and css asset
const clientEntryChunk = clientOutput.output.find(
(item) => item.type === 'chunk' && item.isEntry,
Expand All @@ -72,6 +67,11 @@ export const build = async (
const { app: vueApp, router: vueRouter } = await createVueApp()
const { renderToString } = await import('vue/server-renderer')

// load ssr template file
const ssrTemplate = await fs.readFile(app.options.templateBuild, {
encoding: 'utf8',
})

// pre-render pages to html files
for (const page of app.pages) {
if (spinner) spinner.text = `Rendering pages ${colors.magenta(page.path)}`
Expand Down
58 changes: 19 additions & 39 deletions packages/bundler-vite/src/build/renderPage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { App, Page } from '@vuepress/core'
import { fs, renderHead } from '@vuepress/utils'
import { fs, renderHead, templateRenderer } from '@vuepress/utils'
import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup'
import { ssrContextKey } from 'vue'
import type { App as VueApp } from 'vue'
Expand Down Expand Up @@ -50,44 +50,24 @@ export const renderPage = async ({
const pageChunkFiles = resolvePageChunkFiles({ page, output })

// generate html string
const html = ssrTemplate
// vuepress version
.replace('{{ version }}', app.version)
// page lang
.replace('{{ lang }}', ssrContext.lang)
// page head
.replace(
'<!--vuepress-ssr-head-->',
ssrContext.head.map(renderHead).join(''),
)
// page preload & prefetch links
.replace(
'<!--vuepress-ssr-resources-->',
`${renderPagePreloadLinks({
app,
outputEntryChunk,
pageChunkFiles,
})}${renderPagePrefetchLinks({
app,
outputEntryChunk,
pageChunkFiles,
})}`,
)
// page styles
.replace(
'<!--vuepress-ssr-styles-->',
renderPageStyles({ app, outputCssAsset }),
)
// page content
// notice that some special chars in string like `$&` would be recognized by `replace()`,
// and they won't be html-escaped and will be kept as is when they are inside a code block,
// so we use a replace function as the second param to avoid those potential issues
.replace('<!--vuepress-ssr-app-->', () => pageRendered)
// page scripts
.replace(
'<!--vuepress-ssr-scripts-->',
renderPageScripts({ app, outputEntryChunk }),
)
const html = await templateRenderer(ssrTemplate, {
content: pageRendered,
head: ssrContext.head.map(renderHead).join(''),
lang: ssrContext.lang,
prefetch: renderPagePrefetchLinks({
app,
outputEntryChunk,
pageChunkFiles,
}),
preload: renderPagePreloadLinks({
app,
outputEntryChunk,
pageChunkFiles,
}),
scripts: renderPageScripts({ app, outputEntryChunk }),
styles: renderPageStyles({ app, outputCssAsset }),
version: app.version,
})

// write html file
await fs.outputFile(page.htmlFilePath, html)
Expand Down
10 changes: 5 additions & 5 deletions packages/bundler-webpack/src/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,6 @@ export const build = async (

// render pages
await withSpinner(`Rendering ${app.pages.length} pages`)(async (spinner) => {
// load ssr template file
const ssrTemplate = (
await fs.readFile(app.options.templateBuild)
).toString()

// load the client manifest file
const clientManifestPath = app.dir.temp(clientManifestFilename)
const clientManifest: ClientManifest = await fs.readJson(clientManifestPath)
Expand All @@ -91,6 +86,11 @@ export const build = async (
const { app: vueApp, router: vueRouter } = await createVueApp()
const { renderToString } = await import('vue/server-renderer')

// load ssr template file
const ssrTemplate = await fs.readFile(app.options.templateBuild, {
encoding: 'utf8',
})

// pre-render pages to html files
for (const page of app.pages) {
if (spinner) {
Expand Down
58 changes: 19 additions & 39 deletions packages/bundler-webpack/src/build/renderPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { App, Page } from '@vuepress/core'
import type { VuepressSSRContext } from '@vuepress/shared'
import { fs, renderHead } from '@vuepress/utils'
import { fs, renderHead, templateRenderer } from '@vuepress/utils'
import { ssrContextKey } from 'vue'
import type { App as VueApp } from 'vue'
import type { SSRContext } from 'vue/server-renderer'
Expand Down Expand Up @@ -67,44 +67,24 @@ export const renderPage = async ({
})

// generate html string
const html = ssrTemplate
// vuepress version
.replace('{{ version }}', app.version)
// page lang
.replace('{{ lang }}', ssrContext.lang)
// page head
.replace(
'<!--vuepress-ssr-head-->',
ssrContext.head.map(renderHead).join(''),
)
// page preload & prefetch links
.replace(
'<!--vuepress-ssr-resources-->',
`${renderPagePreloadLinks({
app,
initialFilesMeta,
pageClientFilesMeta,
})}${renderPagePrefetchLinks({
app,
asyncFilesMeta,
pageClientFilesMeta,
})}`,
)
// page styles
.replace(
'<!--vuepress-ssr-styles-->',
renderPageStyles({ app, initialFilesMeta, pageClientFilesMeta }),
)
// page content
// notice that some special chars in string like `$&` would be recognized by `replace()`,
// and they won't be html-escaped and will be kept as is when they are inside a code block,
// so we use a replace function as the second param to avoid those potential issues
.replace('<!--vuepress-ssr-app-->', () => pageRendered)
// page scripts
.replace(
'<!--vuepress-ssr-scripts-->',
renderPageScripts({ app, initialFilesMeta, pageClientFilesMeta }),
)
const html = await templateRenderer(ssrTemplate, {
content: pageRendered,
head: ssrContext.head.map(renderHead).join(''),
lang: ssrContext.lang,
prefetch: renderPagePrefetchLinks({
app,
asyncFilesMeta,
pageClientFilesMeta,
}),
preload: renderPagePreloadLinks({
app,
initialFilesMeta,
pageClientFilesMeta,
}),
scripts: renderPageScripts({ app, initialFilesMeta, pageClientFilesMeta }),
styles: renderPageStyles({ app, initialFilesMeta, pageClientFilesMeta }),
version: app.version,
})

// write html file
await fs.outputFile(page.htmlFilePath, html)
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/ssr/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './renderHead.js'
export * from './renderHeadAttrs.js'
export * from './templateRenderer.js'
86 changes: 86 additions & 0 deletions packages/utils/src/ssr/templateRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* HTML outlets of the template renderer
*/
export const TEMPLATE_RENDERER_OUTLETS = {
CONTENT: '<!--vuepress-ssr-content-->',
HEAD: '<!--vuepress-ssr-head-->',
LANG: '{{ lang }}',
PREFETCH: '<!--vuepress-ssr-prefetch-->',
PRELOAD: '<!--vuepress-ssr-preload-->',
SCRIPTS: '<!--vuepress-ssr-scripts-->',
STYLES: '<!--vuepress-ssr-styles-->',
VERSION: '{{ version }}',
}

/**
* Context type of the template renderer
*/
export interface TemplateRendererContext {
/**
* The rendered page content. Typically to be put inside `<div id="app"></div>`
*/
content: string

/**
* The rendered page head. Typically to be put inside `<head></head>`
*/
head: string

/**
* The language of the page. Typically to be put inside `<html lang="{{ lang }}">`
*/
lang: string

/**
* The rendered prefetch links. Typically to be put inside `<head></head>`
*/
prefetch: string

/**
* The rendered preload links. Typically to be put inside `<head></head>`
*/
preload: string

/**
* The rendered scripts. Typically to be put before `</body>`
*/
scripts: string

/**
* The rendered styles. Typically to be put inside `<head></head>`
*/
styles: string

/**
* The version of VuePress
*/
version: string
}

/**
* Type of the template renderer function
*/
export type TemplateRenderer = (
template: string,
context: TemplateRendererContext,
) => string | Promise<string>

/**
* The default template renderer implementation
*/
export const templateRenderer: TemplateRenderer = (
template: string,
{ content, head, lang, prefetch, preload, scripts, styles, version },
): string =>
template
// notice that some special chars in string like `$&` would be recognized by `replace()`,
// and they won't be html-escaped and will be kept as is when they are inside a code block,
// so we use a replace function as the second param to avoid those potential issues
.replace(TEMPLATE_RENDERER_OUTLETS.CONTENT, () => content)
.replace(TEMPLATE_RENDERER_OUTLETS.HEAD, head)
.replace(TEMPLATE_RENDERER_OUTLETS.LANG, lang)
.replace(TEMPLATE_RENDERER_OUTLETS.PREFETCH, prefetch)
.replace(TEMPLATE_RENDERER_OUTLETS.PRELOAD, preload)
.replace(TEMPLATE_RENDERER_OUTLETS.SCRIPTS, scripts)
.replace(TEMPLATE_RENDERER_OUTLETS.STYLES, styles)
.replace(TEMPLATE_RENDERER_OUTLETS.VERSION, version)

0 comments on commit 6e5c3e4

Please sign in to comment.