Skip to content

Commit 700fad1

Browse files
brc-ddbojanrajh
andauthored
feat(build): add metaChunk option to extract metadata to separate chunk (#2626)
Co-authored-by: Bojan Rajh <b.rajh@shopware.com>
1 parent 64d7c3b commit 700fad1

File tree

4 files changed

+74
-33
lines changed

4 files changed

+74
-33
lines changed

src/node/build/build.ts

+55-14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createHash } from 'crypto'
12
import fs from 'fs-extra'
23
import { createRequire } from 'module'
34
import ora from 'ora'
@@ -7,9 +8,9 @@ import { rimraf } from 'rimraf'
78
import type { OutputAsset, OutputChunk } from 'rollup'
89
import { pathToFileURL } from 'url'
910
import type { BuildOptions } from 'vite'
10-
import { resolveConfig } from '../config'
11-
import type { HeadConfig } from '../shared'
12-
import { serializeFunctions } from '../utils/fnSerialize'
11+
import { resolveConfig, type SiteConfig } from '../config'
12+
import { slash, type HeadConfig } from '../shared'
13+
import { deserializeFunctions, serializeFunctions } from '../utils/fnSerialize'
1314
import { bundle, failMark, okMark } from './bundle'
1415
import { renderPage } from './render'
1516

@@ -79,6 +80,8 @@ export async function build(
7980
chunk.moduleIds.some((id) => id.includes('client/theme-default'))
8081
)
8182

83+
const metadataScript = generateMetadataScript(pageToHashMap, siteConfig)
84+
8285
if (isDefaultTheme) {
8386
const fontURL = assets.find((file) =>
8487
/inter-roman-latin\.\w+\.woff2/.test(file)
@@ -97,15 +100,6 @@ export async function build(
97100
}
98101
}
99102

100-
// We embed the hash map and site config strings into each page directly
101-
// so that it doesn't alter the main chunk's hash on every build.
102-
// It's also embedded as a string and JSON.parsed from the client because
103-
// it's faster than embedding as JS object literal.
104-
const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap))
105-
const siteDataString = JSON.stringify(
106-
JSON.stringify(serializeFunctions({ ...siteConfig.site, head: [] }))
107-
)
108-
109103
await Promise.all(
110104
['404.md', ...siteConfig.pages]
111105
.map((page) => siteConfig.rewrites.map[page] || page)
@@ -119,8 +113,7 @@ export async function build(
119113
cssChunk,
120114
assets,
121115
pageToHashMap,
122-
hashMapString,
123-
siteDataString,
116+
metadataScript,
124117
additionalHeadTags
125118
)
126119
)
@@ -168,3 +161,51 @@ function linkVue() {
168161
}
169162
return () => {}
170163
}
164+
165+
function generateMetadataScript(
166+
pageToHashMap: Record<string, string>,
167+
config: SiteConfig
168+
) {
169+
if (config.mpa) {
170+
return { html: '', inHead: false }
171+
}
172+
173+
// We embed the hash map and site config strings into each page directly
174+
// so that it doesn't alter the main chunk's hash on every build.
175+
// It's also embedded as a string and JSON.parsed from the client because
176+
// it's faster than embedding as JS object literal.
177+
const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap))
178+
const siteDataString = JSON.stringify(
179+
JSON.stringify(serializeFunctions({ ...config.site, head: [] }))
180+
)
181+
182+
const metadataContent = `window.__VP_HASH_MAP__=JSON.parse(${hashMapString});${
183+
siteDataString.includes('_vp-fn_')
184+
? `${deserializeFunctions.toString()};window.__VP_SITE_DATA__=deserializeFunctions(JSON.parse(${siteDataString}));`
185+
: `window.__VP_SITE_DATA__=JSON.parse(${siteDataString});`
186+
}`
187+
188+
if (!config.metaChunk) {
189+
return { html: `<script>${metadataContent}</script>`, inHead: false }
190+
}
191+
192+
const metadataFile = path.join(
193+
config.assetsDir,
194+
'chunks',
195+
`metadata.${createHash('sha256')
196+
.update(metadataContent)
197+
.digest('hex')
198+
.slice(0, 8)}.js`
199+
)
200+
201+
const resolvedMetadataFile = path.join(config.outDir, metadataFile)
202+
const metadataFileURL = slash(`${config.site.base}${metadataFile}`)
203+
204+
fs.ensureDirSync(path.dirname(resolvedMetadataFile))
205+
fs.writeFileSync(resolvedMetadataFile, metadataContent)
206+
207+
return {
208+
html: `<script type="module" src="${metadataFileURL}"></script>`,
209+
inHead: true
210+
}
211+
}

src/node/build/render.ts

+11-19
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
type PageData,
1919
type SSGContext
2020
} from '../shared'
21-
import { deserializeFunctions } from '../utils/fnSerialize'
2221

2322
export async function renderPage(
2423
render: (path: string) => Promise<SSGContext>,
@@ -29,8 +28,7 @@ export async function renderPage(
2928
cssChunk: OutputAsset | null,
3029
assets: string[],
3130
pageToHashMap: Record<string, string>,
32-
hashMapString: string,
33-
siteDataString: string,
31+
metadataScript: { html: string; inHead: boolean },
3432
additionalHeadTags: HeadConfig[]
3533
) {
3634
const routePath = `/${page.replace(/\.md$/, '')}`
@@ -150,37 +148,30 @@ export async function renderPage(
150148
}
151149
}
152150

153-
let metadataScript = `__VP_HASH_MAP__ = JSON.parse(${hashMapString})\n`
154-
if (siteDataString.includes('_vp-fn_')) {
155-
metadataScript += `${deserializeFunctions.toString()}\n__VP_SITE_DATA__ = deserializeFunctions(JSON.parse(${siteDataString}))`
156-
} else {
157-
metadataScript += `__VP_SITE_DATA__ = JSON.parse(${siteDataString})`
158-
}
159-
160-
const html = `
161-
<!DOCTYPE html>
151+
const html = `<!DOCTYPE html>
162152
<html lang="${siteData.lang}" dir="${siteData.dir}">
163153
<head>
164154
<meta charset="utf-8">
165155
<meta name="viewport" content="width=device-width,initial-scale=1">
166156
<title>${title}</title>
167157
<meta name="description" content="${description}">
168158
${stylesheetLink}
159+
${metadataScript.inHead ? metadataScript.html : ''}
169160
${
170161
appChunk
171162
? `<script type="module" src="${siteData.base}${appChunk.fileName}"></script>`
172-
: ``
163+
: ''
173164
}
174165
${await renderHead(head)}
175166
</head>
176167
<body>${teleports?.body || ''}
177168
<div id="app">${content}</div>
178-
${config.mpa ? '' : `<script>${metadataScript}</script>`}
169+
${metadataScript.inHead ? '' : metadataScript.html}
179170
${inlinedScript}
180171
</body>
181-
</html>`.trim()
182-
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html'))
172+
</html>`
183173

174+
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html'))
184175
await fs.ensureDir(path.dirname(htmlFileName))
185176
const transformedHtml = await config.transformHtml?.(html, htmlFileName, {
186177
page,
@@ -224,8 +215,8 @@ function resolvePageImports(
224215
]
225216
}
226217

227-
function renderHead(head: HeadConfig[]): Promise<string> {
228-
return Promise.all(
218+
async function renderHead(head: HeadConfig[]): Promise<string> {
219+
const tags = await Promise.all(
229220
head.map(async ([tag, attrs = {}, innerHTML = '']) => {
230221
const openTag = `<${tag}${renderAttrs(attrs)}>`
231222
if (tag !== 'link' && tag !== 'meta') {
@@ -244,7 +235,8 @@ function renderHead(head: HeadConfig[]): Promise<string> {
244235
return openTag
245236
}
246237
})
247-
).then((tags) => tags.join('\n '))
238+
)
239+
return tags.join('\n ')
248240
}
249241

250242
function renderAttrs(attrs: Record<string, string>): string {

src/node/config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export async function resolveConfig(
115115
vite: userConfig.vite,
116116
shouldPreload: userConfig.shouldPreload,
117117
mpa: !!userConfig.mpa,
118+
metaChunk: !!userConfig.metaChunk,
118119
ignoreDeadLinks: userConfig.ignoreDeadLinks,
119120
cleanUrls: !!userConfig.cleanUrls,
120121
useWebFonts:

src/node/siteConfig.ts

+7
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ export interface UserConfig<ThemeConfig = any>
9898
*/
9999
mpa?: boolean
100100

101+
/**
102+
* Extracts metadata to a separate chunk.
103+
* @experimental
104+
*/
105+
metaChunk?: boolean
106+
101107
/**
102108
* Don't fail builds due to dead links.
103109
*
@@ -176,6 +182,7 @@ export interface SiteConfig<ThemeConfig = any>
176182
| 'vite'
177183
| 'shouldPreload'
178184
| 'mpa'
185+
| 'metaChunk'
179186
| 'lastUpdated'
180187
| 'ignoreDeadLinks'
181188
| 'cleanUrls'

0 commit comments

Comments
 (0)