Skip to content

Commit

Permalink
feat: use hashed page file names
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed May 17, 2020
1 parent b61e239 commit a873564
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 31 deletions.
23 changes: 17 additions & 6 deletions lib/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions lib/shim.d.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
27 changes: 25 additions & 2 deletions src/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down
33 changes: 24 additions & 9 deletions src/build/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -146,5 +161,5 @@ export async function bundle(
outDir: config.tempDir
})

return [clientResult, serverResult]
return [clientResult, serverResult, pageToHashMap]
}
36 changes: 22 additions & 14 deletions src/build/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}">`
Expand All @@ -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'))
Expand All @@ -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
Expand Down

0 comments on commit a873564

Please sign in to comment.