feat(build): add `metaChunk` option to extract metadata to separate chunk (#2626)

Co-authored-by: Bojan Rajh <b.rajh@shopware.com>
pull/2635/head
Divyansh Singh 1 year ago committed by GitHub
parent 64d7c3ba54
commit 700fad192e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,3 +1,4 @@
import { createHash } from 'crypto'
import fs from 'fs-extra'
import { createRequire } from 'module'
import ora from 'ora'
@ -7,9 +8,9 @@ import { rimraf } from 'rimraf'
import type { OutputAsset, OutputChunk } from 'rollup'
import { pathToFileURL } from 'url'
import type { BuildOptions } from 'vite'
import { resolveConfig } from '../config'
import type { HeadConfig } from '../shared'
import { serializeFunctions } from '../utils/fnSerialize'
import { resolveConfig, type SiteConfig } from '../config'
import { slash, type HeadConfig } from '../shared'
import { deserializeFunctions, serializeFunctions } from '../utils/fnSerialize'
import { bundle, failMark, okMark } from './bundle'
import { renderPage } from './render'
@ -79,6 +80,8 @@ export async function build(
chunk.moduleIds.some((id) => id.includes('client/theme-default'))
)
const metadataScript = generateMetadataScript(pageToHashMap, siteConfig)
if (isDefaultTheme) {
const fontURL = assets.find((file) =>
/inter-roman-latin\.\w+\.woff2/.test(file)
@ -97,15 +100,6 @@ export async function build(
}
}
// We embed the hash map and site config strings into each page directly
// so that it doesn't alter the main chunk's hash on every build.
// It's also embedded as a string and JSON.parsed from the client because
// it's faster than embedding as JS object literal.
const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap))
const siteDataString = JSON.stringify(
JSON.stringify(serializeFunctions({ ...siteConfig.site, head: [] }))
)
await Promise.all(
['404.md', ...siteConfig.pages]
.map((page) => siteConfig.rewrites.map[page] || page)
@ -119,8 +113,7 @@ export async function build(
cssChunk,
assets,
pageToHashMap,
hashMapString,
siteDataString,
metadataScript,
additionalHeadTags
)
)
@ -168,3 +161,51 @@ function linkVue() {
}
return () => {}
}
function generateMetadataScript(
pageToHashMap: Record<string, string>,
config: SiteConfig
) {
if (config.mpa) {
return { html: '', inHead: false }
}
// We embed the hash map and site config strings into each page directly
// so that it doesn't alter the main chunk's hash on every build.
// It's also embedded as a string and JSON.parsed from the client because
// it's faster than embedding as JS object literal.
const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap))
const siteDataString = JSON.stringify(
JSON.stringify(serializeFunctions({ ...config.site, head: [] }))
)
const metadataContent = `window.__VP_HASH_MAP__=JSON.parse(${hashMapString});${
siteDataString.includes('_vp-fn_')
? `${deserializeFunctions.toString()};window.__VP_SITE_DATA__=deserializeFunctions(JSON.parse(${siteDataString}));`
: `window.__VP_SITE_DATA__=JSON.parse(${siteDataString});`
}`
if (!config.metaChunk) {
return { html: `<script>${metadataContent}</script>`, inHead: false }
}
const metadataFile = path.join(
config.assetsDir,
'chunks',
`metadata.${createHash('sha256')
.update(metadataContent)
.digest('hex')
.slice(0, 8)}.js`
)
const resolvedMetadataFile = path.join(config.outDir, metadataFile)
const metadataFileURL = slash(`${config.site.base}${metadataFile}`)
fs.ensureDirSync(path.dirname(resolvedMetadataFile))
fs.writeFileSync(resolvedMetadataFile, metadataContent)
return {
html: `<script type="module" src="${metadataFileURL}"></script>`,
inHead: true
}
}

@ -18,7 +18,6 @@ import {
type PageData,
type SSGContext
} from '../shared'
import { deserializeFunctions } from '../utils/fnSerialize'
export async function renderPage(
render: (path: string) => Promise<SSGContext>,
@ -29,8 +28,7 @@ export async function renderPage(
cssChunk: OutputAsset | null,
assets: string[],
pageToHashMap: Record<string, string>,
hashMapString: string,
siteDataString: string,
metadataScript: { html: string; inHead: boolean },
additionalHeadTags: HeadConfig[]
) {
const routePath = `/${page.replace(/\.md$/, '')}`
@ -150,15 +148,7 @@ export async function renderPage(
}
}
let metadataScript = `__VP_HASH_MAP__ = JSON.parse(${hashMapString})\n`
if (siteDataString.includes('_vp-fn_')) {
metadataScript += `${deserializeFunctions.toString()}\n__VP_SITE_DATA__ = deserializeFunctions(JSON.parse(${siteDataString}))`
} else {
metadataScript += `__VP_SITE_DATA__ = JSON.parse(${siteDataString})`
}
const html = `
<!DOCTYPE html>
const html = `<!DOCTYPE html>
<html lang="${siteData.lang}" dir="${siteData.dir}">
<head>
<meta charset="utf-8">
@ -166,21 +156,22 @@ export async function renderPage(
<title>${title}</title>
<meta name="description" content="${description}">
${stylesheetLink}
${metadataScript.inHead ? metadataScript.html : ''}
${
appChunk
? `<script type="module" src="${siteData.base}${appChunk.fileName}"></script>`
: ``
: ''
}
${await renderHead(head)}
</head>
<body>${teleports?.body || ''}
<div id="app">${content}</div>
${config.mpa ? '' : `<script>${metadataScript}</script>`}
${metadataScript.inHead ? '' : metadataScript.html}
${inlinedScript}
</body>
</html>`.trim()
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html'))
</html>`
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html'))
await fs.ensureDir(path.dirname(htmlFileName))
const transformedHtml = await config.transformHtml?.(html, htmlFileName, {
page,
@ -224,8 +215,8 @@ function resolvePageImports(
]
}
function renderHead(head: HeadConfig[]): Promise<string> {
return Promise.all(
async function renderHead(head: HeadConfig[]): Promise<string> {
const tags = await Promise.all(
head.map(async ([tag, attrs = {}, innerHTML = '']) => {
const openTag = `<${tag}${renderAttrs(attrs)}>`
if (tag !== 'link' && tag !== 'meta') {
@ -244,7 +235,8 @@ function renderHead(head: HeadConfig[]): Promise<string> {
return openTag
}
})
).then((tags) => tags.join('\n '))
)
return tags.join('\n ')
}
function renderAttrs(attrs: Record<string, string>): string {

@ -115,6 +115,7 @@ export async function resolveConfig(
vite: userConfig.vite,
shouldPreload: userConfig.shouldPreload,
mpa: !!userConfig.mpa,
metaChunk: !!userConfig.metaChunk,
ignoreDeadLinks: userConfig.ignoreDeadLinks,
cleanUrls: !!userConfig.cleanUrls,
useWebFonts:

@ -98,6 +98,12 @@ export interface UserConfig<ThemeConfig = any>
*/
mpa?: boolean
/**
* Extracts metadata to a separate chunk.
* @experimental
*/
metaChunk?: boolean
/**
* Don't fail builds due to dead links.
*
@ -176,6 +182,7 @@ export interface SiteConfig<ThemeConfig = any>
| 'vite'
| 'shouldPreload'
| 'mpa'
| 'metaChunk'
| 'lastUpdated'
| 'ignoreDeadLinks'
| 'cleanUrls'

Loading…
Cancel
Save