feat(nuxt): exclude page chunks from being prefetched (#6662)

This commit is contained in:
Daniel Roe 2022-08-16 12:19:39 +01:00 committed by GitHub
parent 3730ba88f5
commit 94214d6b32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 47 additions and 14 deletions

View File

@ -1,6 +1,6 @@
import { existsSync } from 'node:fs' import { existsSync } from 'node:fs'
import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath } from '@nuxt/kit' import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath } from '@nuxt/kit'
import { resolve } from 'pathe' import { relative, resolve } from 'pathe'
import { genString, genImport, genObjectFromRawEntries } from 'knitwork' import { genString, genImport, genObjectFromRawEntries } from 'knitwork'
import escapeRE from 'escape-string-regexp' import escapeRE from 'escape-string-regexp'
import type { NuxtApp, NuxtPage } from '@nuxt/schema' import type { NuxtApp, NuxtPage } from '@nuxt/schema'
@ -99,6 +99,24 @@ export default defineNuxtModule({
// Add router plugin // Add router plugin
addPlugin(resolve(runtimeDir, 'router')) addPlugin(resolve(runtimeDir, 'router'))
const getSources = (pages: NuxtPage[]): string[] => pages.flatMap(p =>
[relative(nuxt.options.srcDir, p.file), ...getSources(p.children || [])]
)
// Do not prefetch page chunks
nuxt.hook('build:manifest', async (manifest) => {
const pages = await resolvePagesRoutes()
await nuxt.callHook('pages:extend', pages)
const sourceFiles = getSources(pages)
for (const key in manifest) {
if (manifest[key].isEntry) {
manifest[key].dynamicImports =
manifest[key].dynamicImports?.filter(i => !sourceFiles.includes(i))
}
}
})
// Add routes template // Add routes template
addTemplate({ addTemplate({
filename: 'routes.mjs', filename: 'routes.mjs',

View File

@ -21,6 +21,7 @@ export default defineBuildConfig({
// Type imports // Type imports
'vue-meta', 'vue-meta',
'vue-router', 'vue-router',
'vue-bundle-renderer',
'vue', 'vue',
'hookable', 'hookable',
'nitropack', 'nitropack',

View File

@ -3,6 +3,7 @@ import type { Server as HttpsServer } from 'node:https'
import type { Compiler, Configuration, Stats } from 'webpack' import type { Compiler, Configuration, Stats } from 'webpack'
import type { TSConfig } from 'pkg-types' import type { TSConfig } from 'pkg-types'
import type { InlineConfig as ViteInlineConfig, ViteDevServer } from 'vite' import type { InlineConfig as ViteInlineConfig, ViteDevServer } from 'vite'
import type { Manifest } from 'vue-bundle-renderer'
import type { ModuleContainer } from './module' import type { ModuleContainer } from './module'
import type { NuxtTemplate, Nuxt, NuxtApp } from './nuxt' import type { NuxtTemplate, Nuxt, NuxtApp } from './nuxt'
import type { Preset as ImportPreset, Import } from 'unimport' import type { Preset as ImportPreset, Import } from 'unimport'
@ -72,6 +73,7 @@ export interface NuxtHooks {
'app:templatesGenerated': (app: NuxtApp) => HookResult 'app:templatesGenerated': (app: NuxtApp) => HookResult
'builder:generateApp': () => HookResult 'builder:generateApp': () => HookResult
'pages:extend': (pages: NuxtPage[]) => HookResult 'pages:extend': (pages: NuxtPage[]) => HookResult
'build:manifest': (manifest: Manifest) => HookResult
// Auto imports // Auto imports
'autoImports:sources': (presets: ImportPresetWithDeprecation[]) => HookResult 'autoImports:sources': (presets: ImportPresetWithDeprecation[]) => HookResult

View File

@ -2,7 +2,8 @@ import fse from 'fs-extra'
import { resolve } from 'pathe' import { resolve } from 'pathe'
import { withoutLeadingSlash, withTrailingSlash } from 'ufo' import { withoutLeadingSlash, withTrailingSlash } from 'ufo'
import escapeRE from 'escape-string-regexp' import escapeRE from 'escape-string-regexp'
import { normalizeViteManifest, Manifest } from 'vue-bundle-renderer' import { normalizeViteManifest } from 'vue-bundle-renderer'
import type { Manifest } from 'vue-bundle-renderer'
import type { ViteBuildContext } from './vite' import type { ViteBuildContext } from './vite'
export async function writeManifest (ctx: ViteBuildContext, css: string[] = []) { export async function writeManifest (ctx: ViteBuildContext, css: string[] = []) {
@ -45,7 +46,10 @@ export async function writeManifest (ctx: ViteBuildContext, css: string[] = [])
} }
await fse.mkdirp(serverDist) await fse.mkdirp(serverDist)
const manifest = normalizeViteManifest(clientManifest) const manifest = normalizeViteManifest(clientManifest)
await ctx.nuxt.callHook('build:manifest', manifest)
await fse.writeFile(resolve(serverDist, 'client.manifest.json'), JSON.stringify(manifest, null, 2), 'utf8') await fse.writeFile(resolve(serverDist, 'client.manifest.json'), JSON.stringify(manifest, null, 2), 'utf8')
await fse.writeFile(resolve(serverDist, 'client.manifest.mjs'), 'export default ' + JSON.stringify(manifest, null, 2), 'utf8') await fse.writeFile(resolve(serverDist, 'client.manifest.mjs'), 'export default ' + JSON.stringify(manifest, null, 2), 'utf8')
} }

View File

@ -9,14 +9,18 @@ import hash from 'hash-sum'
import { uniq } from 'lodash-es' import { uniq } from 'lodash-es'
import fse from 'fs-extra' import fse from 'fs-extra'
import type { Nuxt } from '@nuxt/schema'
import { isJS, isCSS, isHotUpdate } from './util' import { isJS, isCSS, isHotUpdate } from './util'
export default class VueSSRClientPlugin { interface PluginOptions {
options: {
filename: string filename: string
nuxt: Nuxt
} }
constructor (options = {}) { export default class VueSSRClientPlugin {
options: PluginOptions
constructor (options: PluginOptions) {
this.options = Object.assign({ this.options = Object.assign({
filename: null filename: null
}, options) }, options)
@ -53,7 +57,7 @@ export default class VueSSRClientPlugin {
assetsMapping[componentHash].push(name) assetsMapping[componentHash].push(name)
}) })
const manifest = { const webpackManifest = {
publicPath: stats.publicPath, publicPath: stats.publicPath,
all: allFiles, all: allFiles,
initial: initialFiles, initial: initialFiles,
@ -64,7 +68,7 @@ export default class VueSSRClientPlugin {
const { entrypoints, namedChunkGroups } = stats const { entrypoints, namedChunkGroups } = stats
const assetModules = stats.modules.filter(m => m.assets.length) const assetModules = stats.modules.filter(m => m.assets.length)
const fileToIndex = file => manifest.all.indexOf(file) const fileToIndex = file => webpackManifest.all.indexOf(file)
stats.modules.forEach((m) => { stats.modules.forEach((m) => {
// Ignore modules duplicated in multiple chunks // Ignore modules duplicated in multiple chunks
if (m.chunks.length === 1) { if (m.chunks.length === 1) {
@ -88,15 +92,15 @@ export default class VueSSRClientPlugin {
} }
const files = Array.from(filesSet) const files = Array.from(filesSet)
manifest.modules[hash(id)] = files webpackManifest.modules[hash(id)] = files
// In production mode, modules may be concatenated by scope hoisting // In production mode, modules may be concatenated by scope hoisting
// Include ConcatenatedModule for not losing module-component mapping // Include ConcatenatedModule for not losing module-component mapping
if (Array.isArray(m.modules)) { if (Array.isArray(m.modules)) {
for (const concatenatedModule of m.modules) { for (const concatenatedModule of m.modules) {
const id = hash(concatenatedModule.identifier.replace(/\s\w+$/, '')) const id = hash(concatenatedModule.identifier.replace(/\s\w+$/, ''))
if (!manifest.modules[id]) { if (!webpackManifest.modules[id]) {
manifest.modules[id] = files webpackManifest.modules[id] = files
} }
} }
} }
@ -110,7 +114,10 @@ export default class VueSSRClientPlugin {
} }
}) })
const src = JSON.stringify(normalizeWebpackManifest(manifest), null, 2) const manifest = normalizeWebpackManifest(webpackManifest)
await this.options.nuxt.callHook('build:manifest', manifest)
const src = JSON.stringify(manifest, null, 2)
await fse.mkdirp(dirname(this.options.filename)) await fse.mkdirp(dirname(this.options.filename))
await fse.writeFile(this.options.filename, src) await fse.writeFile(this.options.filename, src)

View File

@ -22,7 +22,8 @@ export function vue (ctx: WebpackConfigContext) {
if (ctx.isClient) { if (ctx.isClient) {
config.plugins.push(new VueSSRClientPlugin({ config.plugins.push(new VueSSRClientPlugin({
filename: resolve(options.buildDir, 'dist/server', `${ctx.name}.manifest.json`) filename: resolve(options.buildDir, 'dist/server', `${ctx.name}.manifest.json`),
nuxt: ctx.nuxt
})) }))
} else { } else {
config.plugins.push(new VueSSRServerPlugin({ config.plugins.push(new VueSSRServerPlugin({