feat: use hashed page file names

pull/18/head
Evan You 4 years ago
parent b61e2398fc
commit a8735646e8

@ -56,12 +56,23 @@ export function createApp() {
} else {
// in production, each .md file is built into a .md.js file following
// the path conversion scheme.
// /foo/bar.html -> /js/foo_bar.md.js
const useLeanBuild = isInitialPageLoad || initialPath === pagePath
pagePath =
(inBrowser ? __BASE__ + '_assets/' : './') +
pagePath.slice(inBrowser ? __BASE__.length : 1).replace(/\//g, '_') +
(useLeanBuild ? '.md.lean.js' : '.md.js')
// /foo/bar.html -> ./foo_bar.md
if (inBrowser) {
pagePath = pagePath.slice(__BASE__.length).replace(/\//g, '_') + '.md'
// client production build needs to account for page hash, which is
// injected directly in the page's html
const pageHash = __VP_HASH_MAP__[pagePath]
// use lean build if this is the initial page load or navigating back
// to the initial loaded path (the static vnodes already adopted the
// static content on that load so no need to re-fetch the page)
const ext =
isInitialPageLoad || initialPath === pagePath ? 'lean.js' : 'js'
pagePath = `${__BASE__}_assets/${pagePath}.${pageHash}.${ext}`
} else {
// ssr build uses much simpler name mapping
pagePath = `./${pagePath.slice(1).replace(/\//g, '_')}.md.js`
}
}
if (inBrowser) {

1
lib/shim.d.ts vendored

@ -1,5 +1,6 @@
declare const __DEV__: boolean
declare const __BASE__: string
declare const __VP_HASH_MAP__: Record<string, string>
declare module '*.vue' {
import { ComponentOptions } from 'vue'

@ -3,6 +3,7 @@ import { bundle } from './bundle'
import { BuildConfig as ViteBuildOptions } from 'vite'
import { resolveConfig } from '../config'
import { renderPage } from './render'
import { OutputChunk } from 'rollup'
export type BuildOptions = Pick<
ViteBuildOptions,
@ -17,10 +18,32 @@ export const ASSETS_DIR = '_assets/'
export async function build(buildOptions: BuildOptions = {}) {
const siteConfig = await resolveConfig(buildOptions.root)
try {
const [clientResult] = await bundle(siteConfig, buildOptions)
const [clientResult, , pageToHashMap] = await bundle(
siteConfig,
buildOptions
)
console.log('rendering pages...')
const indexChunk = clientResult.assets.find(
(chunk) =>
chunk.type === 'chunk' && chunk.fileName.match(/^index\.\w+\.js$/)
) as OutputChunk
// We embed the hash map string 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 hashMapStirng = JSON.stringify(JSON.stringify(pageToHashMap))
for (const page of siteConfig.pages) {
await renderPage(siteConfig, page, clientResult)
await renderPage(
siteConfig,
page,
clientResult,
indexChunk,
pageToHashMap,
hashMapStirng
)
}
} finally {
await fs.remove(siteConfig.tempDir)

@ -31,12 +31,13 @@ const isPageChunk = (
export async function bundle(
config: SiteConfig,
options: BuildOptions
): Promise<BuildResult[]> {
): Promise<[BuildResult, BuildResult, Record<string, string>]> {
const root = config.root
const resolver = createResolver(config.themeDir)
const markdownToVue = createMarkdownToVueRenderFn(root)
let isClientBuild = true
const pageToHashMap = Object.create(null)
const VitePressPlugin: Plugin = {
name: 'vitepress',
@ -82,13 +83,18 @@ export async function bundle(
const chunk = bundle[name]
if (isPageChunk(chunk)) {
// foo/bar.md -> foo_bar.md.js
chunk.fileName =
slash(path.relative(root, chunk.facadeModuleId)).replace(
/\//g,
'_'
) + '.js'
const hash = isClientBuild
? chunk.fileName.match(/\.(\w+)\.js$/)![1]
: ``
const pageName = slash(
path.relative(root, chunk.facadeModuleId)
).replace(/\//g, '_')
chunk.fileName = `${pageName}${hash ? `.${hash}` : ``}.js`
if (isClientBuild) {
// record page -> hash relations
pageToHashMap[pageName] = hash
// inject another chunk with the content stripped
bundle[name + '-lean'] = {
...chunk,
@ -131,13 +137,22 @@ export async function bundle(
preserveEntrySignatures: 'allow-extension',
plugins: [VitePressPlugin, ...(rollupInputOptions.plugins || [])]
},
rollupOutputOptions,
rollupOutputOptions: {
...rollupOutputOptions,
chunkFileNames: `common-[hash].js`
},
silent: !process.env.DEBUG,
minify: !process.env.DEBUG
}
console.log('building client bundle...')
const clientResult = await build(viteOptions)
const clientResult = await build({
...viteOptions,
rollupOutputOptions: {
...viteOptions.rollupOutputOptions,
entryFileNames: `[name].[hash].js`
}
})
console.log('building server bundle...')
isClientBuild = false
@ -146,5 +161,5 @@ export async function bundle(
outDir: config.tempDir
})
return [clientResult, serverResult]
return [clientResult, serverResult, pageToHashMap]
}

@ -11,7 +11,10 @@ const escape = require('escape-html')
export async function renderPage(
config: SiteConfig,
page: string, // foo.md
result: BuildResult
result: BuildResult,
indexChunk: OutputChunk,
pageToHashMap: Record<string, string>,
hashMapStirng: string
) {
const { createApp } = require(path.join(
config.tempDir,
@ -23,27 +26,30 @@ export async function renderPage(
router.go(routePath)
const content = await renderToString(app)
const pageJsFileName = page.replace(/\//g, '_') + '.js'
const pageName = page.replace(/\//g, '_')
// server build doesn't need hash
const pageServerJsFileName = pageName + '.js'
// for any initial page load, we only need the lean version of the page js
// since the static content is already on the page!
const pageHash = pageToHashMap[pageName]
const pageClientJsFileName = pageName + `.` + pageHash + '.lean.js'
// resolve page data so we can render head tags
const { __pageData } = require(path.join(
config.tempDir,
ASSETS_DIR,
pageJsFileName
pageServerJsFileName
))
const pageData = JSON.parse(__pageData)
const assetPath = `${config.site.base}${ASSETS_DIR}`
const preloadLinks = [
// resolve imports for index.js + page.md.js and inject script tags for
// them as well so we fetch everything as early as possible without having
// to wait for entry chunks to parse
...resolvePageImports(config, page, result),
// for any initial page load, we only need the lean version of the page js
// since the static content is already on the page!
pageJsFileName.replace(/\.js$/, '.lean.js'),
'index.js'
...resolvePageImports(config, page, result, indexChunk),
pageClientJsFileName,
indexChunk.fileName
]
.map((file) => {
return `<link rel="modulepreload" href="${assetPath}${file}">`
@ -64,7 +70,10 @@ export async function renderPage(
</head>
<body>
<div id="app">${content}</div>
<script type="module" async src="${assetPath}index.js"></script>
<script>__VP_HASH_MAP__ = JSON.parse(${hashMapStirng})</script>
<script type="module" async src="${assetPath}${
indexChunk.fileName
}"></script>
</body>
</html>`.trim()
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html'))
@ -75,13 +84,12 @@ export async function renderPage(
function resolvePageImports(
config: SiteConfig,
page: string,
result: BuildResult
result: BuildResult,
indexChunk: OutputChunk
) {
// find the page's js chunk and inject script tags for its imports so that
// they are start fetching as early as possible
const indexChunk = result.assets.find(
(chunk) => chunk.type === 'chunk' && chunk.fileName === `index.js`
) as OutputChunk
const srcPath = path.join(config.root, page)
const pageChunk = result.assets.find(
(chunk) => chunk.type === 'chunk' && chunk.facadeModuleId === srcPath

Loading…
Cancel
Save