Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/bundler-vite/src/build/build.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { PageChunkFilesMap } from '@vuepress/bundlerutils'
import { createVueServerApp, getSsrTemplate } from '@vuepress/bundlerutils'
import type { App, Bundler } from '@vuepress/core'
import { colors, debug, fs, withSpinner } from '@vuepress/utils'
Expand All @@ -7,6 +8,7 @@ import { build as viteBuild } from 'vite'
import { resolveViteConfig } from '../resolveViteConfig.js'
import type { ViteBundlerOptions } from '../types.js'
import { renderPage } from './renderPage.js'
import { resolvePageChunkFiles } from './resolvePageChunkFiles.js'

const log = debug('vuepress:bundler-vite/build')

Expand Down Expand Up @@ -65,6 +67,15 @@ export const build = async (
)
const ssrTemplate = await getSsrTemplate(app)

// build page path -> chunk files map for 'as-needed' strategy
Comment thread
Mister-Hope marked this conversation as resolved.
Outdated
const pageChunkFilesMap: PageChunkFilesMap = new Map()
for (const page of app.pages) {
pageChunkFilesMap.set(
page.path,
resolvePageChunkFiles({ page, output: clientOutput.output }),
)
}

// pre-render pages to html files
for (const page of app.pages) {
if (spinner) spinner.text = `Rendering pages ${colors.magenta(page.path)}`
Expand All @@ -77,6 +88,7 @@ export const build = async (
output: clientOutput.output,
outputEntryChunk: clientEntryChunk,
outputCssAsset: clientCssAsset,
pageChunkFilesMap,
})
}
})
Expand Down
7 changes: 7 additions & 0 deletions packages/bundler-vite/src/build/renderPage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { PageChunkFilesMap } from '@vuepress/bundlerutils'
import { renderPageToString } from '@vuepress/bundlerutils'
import type { App, Page } from '@vuepress/core'
import { fs, renderHead } from '@vuepress/utils'
Expand All @@ -20,6 +21,7 @@ export const renderPage = async ({
output,
outputEntryChunk,
outputCssAsset,
pageChunkFilesMap,
}: {
app: App
page: Page
Expand All @@ -29,6 +31,7 @@ export const renderPage = async ({
output: RolldownOutput['output']
outputEntryChunk: OutputChunk
outputCssAsset: OutputAsset | undefined
pageChunkFilesMap: PageChunkFilesMap
}): Promise<void> => {
// render current page to string
const { ssrContext, ssrString } = await renderPageToString({
Expand All @@ -49,11 +52,15 @@ export const renderPage = async ({
app,
outputEntryChunk,
pageChunkFiles,
page,
pageChunkFilesMap,
}),
preload: renderPagePreloadLinks({
app,
outputEntryChunk,
pageChunkFiles,
page,
pageChunkFilesMap,
}),
scripts: renderPageScripts({ app, outputEntryChunk }),
styles: renderPageStyles({ app, outputCssAsset }),
Expand Down
49 changes: 42 additions & 7 deletions packages/bundler-vite/src/build/renderPagePrefetchLinks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { App } from '@vuepress/core'
import type { PageChunkFilesMap } from '@vuepress/bundlerutils'
import { resolveLinkRoutePath } from '@vuepress/bundlerutils'
import type { App, Page } from '@vuepress/core'
import type { OutputChunk } from 'rolldown'

/**
Expand All @@ -8,10 +10,14 @@ export const renderPagePrefetchLinks = ({
app,
outputEntryChunk,
pageChunkFiles,
page,
pageChunkFilesMap,
}: {
app: App
outputEntryChunk: OutputChunk
pageChunkFiles: string[]
page: Page
pageChunkFilesMap: PageChunkFilesMap
}): string => {
// shouldPrefetch option
const { shouldPrefetch } = app.options
Expand All @@ -21,12 +27,37 @@ export const renderPagePrefetchLinks = ({
return ''
}

// dynamic imports excluding current page chunks
const prefetchFiles = outputEntryChunk.dynamicImports.filter(
(item) => !pageChunkFiles.some((file) => file === item),
)
let candidateFiles: string[]

return prefetchFiles
if (shouldPrefetch === 'as-needed') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe extract this snippet to bundlerutils?

@Mister-Hope Mister-Hope Jun 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How? The data structures are different accross bundlers, and rely on bundler types.

// collect linked page chunk file names
const linkedFileNames = new Set<string>()
for (const link of page.links) {
const routePath = resolveLinkRoutePath(link.absolute, app.options.base)
if (routePath) {
const targetChunks = pageChunkFilesMap.get(routePath)
if (targetChunks) {
for (const file of targetChunks) {
linkedFileNames.add(file)
}
}
}
}
// dynamic imports excluding current page chunks
// filtered to only linked pages' chunk files
candidateFiles = outputEntryChunk.dynamicImports.filter(
(item) =>
linkedFileNames.has(item) &&
!pageChunkFiles.some((file) => file === item),
)
} else {
// dynamic imports excluding current page chunks
candidateFiles = outputEntryChunk.dynamicImports.filter(
(item) => !pageChunkFiles.some((file) => file === item),
)
}

return candidateFiles
.map((item) => {
// resolve file type
const type = item.endsWith('.js')
Expand All @@ -36,7 +67,11 @@ export const renderPagePrefetchLinks = ({
: ''

// user wants to explicitly control what to prefetch
if (shouldPrefetch !== true && !shouldPrefetch(item, type)) {
if (
shouldPrefetch !== true &&
shouldPrefetch !== 'as-needed' &&
!shouldPrefetch(item, type)
) {
return ''
}
return `<link rel="prefetch" href="${app.options.base}${item}" as="${type}">`
Expand Down
31 changes: 29 additions & 2 deletions packages/bundler-vite/src/build/renderPagePreloadLinks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { App } from '@vuepress/core'
import type { PageChunkFilesMap } from '@vuepress/bundlerutils'
import { resolveLinkRoutePath } from '@vuepress/bundlerutils'
import type { App, Page } from '@vuepress/core'
import type { OutputChunk } from 'rolldown'

/**
Expand All @@ -8,10 +10,14 @@ export const renderPagePreloadLinks = ({
app,
outputEntryChunk,
pageChunkFiles,
page,
pageChunkFilesMap,
}: {
app: App
outputEntryChunk: OutputChunk
pageChunkFiles: string[]
page: Page
pageChunkFilesMap: PageChunkFilesMap
}): string => {
// shouldPreload option
const { shouldPreload } = app.options
Expand All @@ -30,6 +36,23 @@ export const renderPagePreloadLinks = ({
]),
)

// when 'as-needed', also add linked pages' chunk files
if (shouldPreload === 'as-needed') {
for (const link of page.links) {
const routePath = resolveLinkRoutePath(link.absolute, app.options.base)
if (routePath) {
const targetChunks = pageChunkFilesMap.get(routePath)
if (targetChunks) {
for (const file of targetChunks) {
if (!preloadFiles.includes(file)) {
preloadFiles.push(file)
}
}
}
}
}
}

return preloadFiles
.map((item) => {
// resolve file type
Expand All @@ -45,7 +68,11 @@ export const renderPagePreloadLinks = ({
}

// user wants to explicitly control what to preload
if (shouldPreload !== true && !shouldPreload(item, type)) {
if (
shouldPreload !== true &&
shouldPreload !== 'as-needed' &&
!shouldPreload(item, type)
) {
return ''
}

Expand Down
12 changes: 12 additions & 0 deletions packages/bundler-webpack/src/build/build.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { PageChunkFilesMap } from '@vuepress/bundlerutils'
import { createVueServerApp, getSsrTemplate } from '@vuepress/bundlerutils'
import type { App, Bundler } from '@vuepress/core'
import { colors, debug, fs, logger, withSpinner } from '@vuepress/utils'
Expand Down Expand Up @@ -78,6 +79,16 @@ export const build = async (
const { initialFilesMeta, asyncFilesMeta, moduleFilesMetaMap } =
resolveClientManifestMeta(clientManifest)

// build page path -> chunk files map for 'as-needed' strategy
Comment thread
Mister-Hope marked this conversation as resolved.
Outdated
const pageChunkFilesMap: PageChunkFilesMap = new Map()
for (const page of app.pages) {
const pageFileNames = clientManifest.chunks[page.chunkName] ?? []

if (pageFileNames.length) {
pageChunkFilesMap.set(page.path, pageFileNames)
}
}

// create vue ssr app and get ssr template
const { vueApp, vueRouter } = await createVueServerApp(
app.dir.temp('.server/app.cjs'),
Expand All @@ -98,6 +109,7 @@ export const build = async (
initialFilesMeta,
asyncFilesMeta,
moduleFilesMetaMap,
pageChunkFilesMap,
})
}
})
Expand Down
11 changes: 11 additions & 0 deletions packages/bundler-webpack/src/build/createClientPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,23 @@ export const createClientPlugin = (
if (request) manifestModules[request] = files
})

// build chunk name -> file names mapping
const chunkFiles: Record<string, string[]> = {}
chunks.forEach((chunk) => {
if (chunk.names.length && chunk.files.length) {
chunk.names.forEach((name) => {
chunkFiles[name] = [...(chunkFiles[name] ?? []), ...chunk.files]
})
}
})

// generate client manifest json file
const clientManifest: ClientManifest = {
all: allFiles,
initial: initialFiles,
async: asyncFiles,
modules: manifestModules,
chunks: chunkFiles,
}

const clientManifestJson = JSON.stringify(clientManifest, null, 2)
Expand Down
8 changes: 7 additions & 1 deletion packages/bundler-webpack/src/build/renderPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PageSSRContext } from '@vuepress/bundlerutils'
import type { PageChunkFilesMap, PageSSRContext } from '@vuepress/bundlerutils'
import { renderPageToString } from '@vuepress/bundlerutils'
import type { App, Page } from '@vuepress/core'
import { fs, renderHead } from '@vuepress/utils'
Expand Down Expand Up @@ -33,6 +33,7 @@ export const renderPage = async ({
initialFilesMeta,
asyncFilesMeta,
moduleFilesMetaMap,
pageChunkFilesMap,
}: {
app: App
page: Page
Expand All @@ -42,6 +43,7 @@ export const renderPage = async ({
initialFilesMeta: FileMeta[]
asyncFilesMeta: FileMeta[]
moduleFilesMetaMap: ModuleFilesMetaMap
pageChunkFilesMap: PageChunkFilesMap
}): Promise<void> => {
// render current page to string
const { ssrContext, ssrString } =
Expand All @@ -67,11 +69,15 @@ export const renderPage = async ({
app,
asyncFilesMeta,
pageClientFilesMeta,
page,
pageChunkFilesMap,
}),
preload: renderPagePreloadLinks({
app,
initialFilesMeta,
pageClientFilesMeta,
page,
pageChunkFilesMap,
}),
scripts: renderPageScripts({ app, initialFilesMeta, pageClientFilesMeta }),
styles: renderPageStyles({ app, initialFilesMeta, pageClientFilesMeta }),
Expand Down
47 changes: 41 additions & 6 deletions packages/bundler-webpack/src/build/renderPagePrefetchLinks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { App } from '@vuepress/core'
import type { PageChunkFilesMap } from '@vuepress/bundlerutils'
import { resolveLinkRoutePath } from '@vuepress/bundlerutils'
import type { App, Page } from '@vuepress/core'

import type { FileMeta } from './types.js'

Expand All @@ -9,10 +11,14 @@ export const renderPagePrefetchLinks = ({
app,
asyncFilesMeta,
pageClientFilesMeta,
page,
pageChunkFilesMap,
}: {
app: App
asyncFilesMeta: FileMeta[]
pageClientFilesMeta: FileMeta[]
page: Page
pageChunkFilesMap: PageChunkFilesMap
}): string => {
// shouldPrefetch option
const { shouldPrefetch } = app.options
Expand All @@ -22,15 +28,44 @@ export const renderPagePrefetchLinks = ({
return ''
}

// async files excluding files used by current page should be prefetch
const prefetchFilesMeta = asyncFilesMeta.filter(
({ file }) => !pageClientFilesMeta.some((f) => f.file === file),
)
let prefetchFilesMeta: FileMeta[]

if (shouldPrefetch === 'as-needed') {
// collect linked page chunk file names
const linkedFileNames = new Set<string>()
for (const link of page.links) {
const routePath = resolveLinkRoutePath(link.absolute, app.options.base)
if (routePath) {
const targetChunks = pageChunkFilesMap.get(routePath)
if (targetChunks) {
for (const file of targetChunks) {
linkedFileNames.add(file)
}
}
}
}
// async files excluding files used by current page
// filtered to only linked pages' chunk files
prefetchFilesMeta = asyncFilesMeta.filter(
({ file }) =>
linkedFileNames.has(file) &&
!pageClientFilesMeta.some((f) => f.file === file),
)
} else {
// async files excluding files used by current page should be prefetch
prefetchFilesMeta = asyncFilesMeta.filter(
({ file }) => !pageClientFilesMeta.some((f) => f.file === file),
)
}

return prefetchFilesMeta
.map(({ file, type }) => {
// user wants to explicitly control what to prefetch
if (shouldPrefetch !== true && !shouldPrefetch(file, type)) {
if (
shouldPrefetch !== true &&
shouldPrefetch !== 'as-needed' &&
!shouldPrefetch(file, type)
) {
return ''
}
return `<link rel="prefetch" href="${app.options.base}${file}" as="${type}">`
Expand Down
Loading
Loading