mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 23:52:06 +00:00
chore: add more checks around indexed access (#29060)
This commit is contained in:
parent
2b73e1690c
commit
efae3a4f3c
@ -54,8 +54,10 @@ const NuxtClientFallbackServer = defineComponent({
|
|||||||
const defaultSlot = ctx.slots.default?.()
|
const defaultSlot = ctx.slots.default?.()
|
||||||
const ssrVNodes = createBuffer()
|
const ssrVNodes = createBuffer()
|
||||||
|
|
||||||
for (let i = 0; i < (defaultSlot?.length || 0); i++) {
|
if (defaultSlot) {
|
||||||
ssrRenderVNode(ssrVNodes.push, defaultSlot![i], vm!)
|
for (let i = 0; i < defaultSlot.length; i++) {
|
||||||
|
ssrRenderVNode(ssrVNodes.push, defaultSlot[i]!, vm!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = ssrVNodes.getBuffer()
|
const buffer = ssrVNodes.getBuffer()
|
||||||
|
@ -29,17 +29,20 @@ const getId = import.meta.client ? () => (id++).toString() : randomUUID
|
|||||||
const components = import.meta.client ? new Map<string, Component>() : undefined
|
const components = import.meta.client ? new Map<string, Component>() : undefined
|
||||||
|
|
||||||
async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['components']) {
|
async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['components']) {
|
||||||
|
if (!paths) { return }
|
||||||
|
|
||||||
const promises: Array<Promise<void>> = []
|
const promises: Array<Promise<void>> = []
|
||||||
|
|
||||||
for (const component in paths) {
|
for (const [component, item] of Object.entries(paths)) {
|
||||||
if (!(components!.has(component))) {
|
if (!(components!.has(component))) {
|
||||||
promises.push((async () => {
|
promises.push((async () => {
|
||||||
const chunkSource = join(source, paths[component].chunk)
|
const chunkSource = join(source, item.chunk)
|
||||||
const c = await import(/* @vite-ignore */ chunkSource).then(m => m.default || m)
|
const c = await import(/* @vite-ignore */ chunkSource).then(m => m.default || m)
|
||||||
components!.set(component, c)
|
components!.set(component, c)
|
||||||
})())
|
})())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +279,7 @@ export default defineComponent({
|
|||||||
teleports.push(createVNode(Teleport,
|
teleports.push(createVNode(Teleport,
|
||||||
// use different selectors for even and odd teleportKey to force trigger the teleport
|
// use different selectors for even and odd teleportKey to force trigger the teleport
|
||||||
{ to: import.meta.client ? `${isKeyOdd ? 'div' : ''}[data-island-uid="${uid.value}"][data-island-slot="${slot}"]` : `uid=${uid.value};slot=${slot}` },
|
{ to: import.meta.client ? `${isKeyOdd ? 'div' : ''}[data-island-uid="${uid.value}"][data-island-slot="${slot}"]` : `uid=${uid.value};slot=${slot}` },
|
||||||
{ default: () => (payloads.slots?.[slot].props?.length ? payloads.slots[slot].props : [{}]).map((data: any) => slots[slot]?.(data)) }),
|
{ default: () => (payloads.slots?.[slot]?.props?.length ? payloads.slots[slot].props : [{}]).map((data: any) => slots[slot]?.(data)) }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ export default defineComponent({
|
|||||||
const islandContext = nuxtApp.ssrContext!.islandContext!
|
const islandContext = nuxtApp.ssrContext!.islandContext!
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const slot = slots.default!()[0]
|
const slot = slots.default!()[0]!
|
||||||
const slotType = slot.type as ExtendedComponent
|
const slotType = slot.type as ExtendedComponent
|
||||||
const name = (slotType.__name || slotType.name) as string
|
const name = (slotType.__name || slotType.name) as string
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ export function vforToArray (source: any): any[] {
|
|||||||
const keys = Object.keys(source)
|
const keys = Object.keys(source)
|
||||||
const array = new Array(keys.length)
|
const array = new Array(keys.length)
|
||||||
for (let i = 0, l = keys.length; i < l; i++) {
|
for (let i = 0, l = keys.length; i < l; i++) {
|
||||||
const key = keys[i]
|
const key = keys[i]!
|
||||||
array[i] = source[key]
|
array[i] = source[key]
|
||||||
}
|
}
|
||||||
return array
|
return array
|
||||||
|
@ -23,7 +23,7 @@ export async function loadPayload (url: string, opts: LoadPayloadOptions = {}):
|
|||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
|
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
|
||||||
if (payloadURL in cache) {
|
if (payloadURL in cache) {
|
||||||
return cache[payloadURL]
|
return cache[payloadURL] || null
|
||||||
}
|
}
|
||||||
cache[payloadURL] = isPrerendered(url).then((prerendered) => {
|
cache[payloadURL] = isPrerendered(url).then((prerendered) => {
|
||||||
if (!prerendered) {
|
if (!prerendered) {
|
||||||
|
@ -14,7 +14,12 @@ export const preloadComponents = async (components: string | string[]) => {
|
|||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
|
||||||
components = toArray(components)
|
components = toArray(components)
|
||||||
await Promise.all(components.map(name => _loadAsyncComponent(nuxtApp.vueApp._context.components[name])))
|
await Promise.all(components.map((name) => {
|
||||||
|
const component = nuxtApp.vueApp._context.components[name]
|
||||||
|
if (component) {
|
||||||
|
return _loadAsyncComponent(component)
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,18 +7,18 @@ import { defineNuxtPlugin, useNuxtApp } from '../nuxt'
|
|||||||
// @ts-expect-error Virtual file.
|
// @ts-expect-error Virtual file.
|
||||||
import { componentIslands } from '#build/nuxt.config.mjs'
|
import { componentIslands } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
const revivers: Record<string, (data: any) => any> = {
|
const revivers: [string, (data: any) => any][] = [
|
||||||
NuxtError: data => createError(data),
|
['NuxtError', data => createError(data)],
|
||||||
EmptyShallowRef: data => shallowRef(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data)),
|
['EmptyShallowRef', data => shallowRef(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data))],
|
||||||
EmptyRef: data => ref(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data)),
|
['EmptyRef', data => ref(data === '_' ? undefined : data === '0n' ? BigInt(0) : destr(data))],
|
||||||
ShallowRef: data => shallowRef(data),
|
['ShallowRef', data => shallowRef(data)],
|
||||||
ShallowReactive: data => shallowReactive(data),
|
['ShallowReactive', data => shallowReactive(data)],
|
||||||
Ref: data => ref(data),
|
['Ref', data => ref(data)],
|
||||||
Reactive: data => reactive(data),
|
['Reactive', data => reactive(data)],
|
||||||
}
|
]
|
||||||
|
|
||||||
if (componentIslands) {
|
if (componentIslands) {
|
||||||
revivers.Island = ({ key, params, result }: any) => {
|
revivers.push(['Island', ({ key, params, result }: any) => {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
if (!nuxtApp.isHydrating) {
|
if (!nuxtApp.isHydrating) {
|
||||||
nuxtApp.payload.data[key] = nuxtApp.payload.data[key] || $fetch(`/__nuxt_island/${key}.json`, {
|
nuxtApp.payload.data[key] = nuxtApp.payload.data[key] || $fetch(`/__nuxt_island/${key}.json`, {
|
||||||
@ -33,15 +33,15 @@ if (componentIslands) {
|
|||||||
html: '',
|
html: '',
|
||||||
...result,
|
...result,
|
||||||
}
|
}
|
||||||
}
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtPlugin({
|
export default defineNuxtPlugin({
|
||||||
name: 'nuxt:revive-payload:client',
|
name: 'nuxt:revive-payload:client',
|
||||||
order: -30,
|
order: -30,
|
||||||
async setup (nuxtApp) {
|
async setup (nuxtApp) {
|
||||||
for (const reviver in revivers) {
|
for (const [reviver, fn] of revivers) {
|
||||||
definePayloadReviver(reviver, revivers[reviver as keyof typeof revivers])
|
definePayloadReviver(reviver, fn)
|
||||||
}
|
}
|
||||||
Object.assign(nuxtApp.payload, await nuxtApp.runWithContext(getNuxtClientPayload))
|
Object.assign(nuxtApp.payload, await nuxtApp.runWithContext(getNuxtClientPayload))
|
||||||
delete window.__NUXT__
|
delete window.__NUXT__
|
||||||
|
@ -78,7 +78,7 @@ export const islandsTransform = createUnplugin((options: ServerOnlyComponentTran
|
|||||||
|
|
||||||
if (attributes.name) { delete attributes.name }
|
if (attributes.name) { delete attributes.name }
|
||||||
if (attributes['v-bind']) {
|
if (attributes['v-bind']) {
|
||||||
attributes._bind = extractAttributes(attributes, ['v-bind'])['v-bind']
|
attributes._bind = extractAttributes(attributes, ['v-bind'])['v-bind']!
|
||||||
}
|
}
|
||||||
const teleportAttributes = extractAttributes(attributes, ['v-if', 'v-else-if', 'v-else'])
|
const teleportAttributes = extractAttributes(attributes, ['v-if', 'v-else-if', 'v-else'])
|
||||||
const bindings = getPropsToString(attributes)
|
const bindings = getPropsToString(attributes)
|
||||||
@ -137,7 +137,7 @@ function extractAttributes (attributes: Record<string, string>, names: string[])
|
|||||||
const extracted: Record<string, string> = {}
|
const extracted: Record<string, string> = {}
|
||||||
for (const name of names) {
|
for (const name of names) {
|
||||||
if (name in attributes) {
|
if (name in attributes) {
|
||||||
extracted[name] = attributes[name]
|
extracted[name] = attributes[name]!
|
||||||
delete attributes[name]
|
delete attributes[name]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,10 +140,10 @@ export default defineNuxtModule<ComponentsOptions>({
|
|||||||
nuxt.hook('build:manifest', (manifest) => {
|
nuxt.hook('build:manifest', (manifest) => {
|
||||||
const sourceFiles = getComponents().filter(c => c.global).map(c => relative(nuxt.options.srcDir, c.filePath))
|
const sourceFiles = getComponents().filter(c => c.global).map(c => relative(nuxt.options.srcDir, c.filePath))
|
||||||
|
|
||||||
for (const key in manifest) {
|
for (const chunk of Object.values(manifest)) {
|
||||||
if (manifest[key].isEntry) {
|
if (chunk.isEntry) {
|
||||||
manifest[key].dynamicImports =
|
chunk.dynamicImports =
|
||||||
manifest[key].dynamicImports?.filter(i => !sourceFiles.includes(i))
|
chunk.dynamicImports?.filter(i => !sourceFiles.includes(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -56,6 +56,8 @@ export const TreeShakeTemplatePlugin = createUnplugin((options: TreeShakeTemplat
|
|||||||
const node = _node as AcornNode<Node>
|
const node = _node as AcornNode<Node>
|
||||||
if (isSsrRender(node)) {
|
if (isSsrRender(node)) {
|
||||||
const [componentCall, _, children] = node.arguments
|
const [componentCall, _, children] = node.arguments
|
||||||
|
if (!componentCall) { return }
|
||||||
|
|
||||||
if (componentCall.type === 'Identifier' || componentCall.type === 'MemberExpression' || componentCall.type === 'CallExpression') {
|
if (componentCall.type === 'Identifier' || componentCall.type === 'MemberExpression' || componentCall.type === 'CallExpression') {
|
||||||
const componentName = getComponentName(node)
|
const componentName = getComponentName(node)
|
||||||
const isClientComponent = COMPONENTS_IDENTIFIERS_RE.test(componentName)
|
const isClientComponent = COMPONENTS_IDENTIFIERS_RE.test(componentName)
|
||||||
@ -137,8 +139,10 @@ function removeFromSetupReturn (codeAst: Program, name: string, magicString: Mag
|
|||||||
const variableList = node.value.body.body.filter((statement): statement is VariableDeclaration => statement.type === 'VariableDeclaration')
|
const variableList = node.value.body.body.filter((statement): statement is VariableDeclaration => statement.type === 'VariableDeclaration')
|
||||||
const returnedVariableDeclaration = variableList.find(declaration => declaration.declarations[0]?.id.type === 'Identifier' && declaration.declarations[0]?.id.name === '__returned__' && declaration.declarations[0]?.init?.type === 'ObjectExpression')
|
const returnedVariableDeclaration = variableList.find(declaration => declaration.declarations[0]?.id.type === 'Identifier' && declaration.declarations[0]?.id.name === '__returned__' && declaration.declarations[0]?.init?.type === 'ObjectExpression')
|
||||||
if (returnedVariableDeclaration) {
|
if (returnedVariableDeclaration) {
|
||||||
const init = returnedVariableDeclaration.declarations[0].init as ObjectExpression
|
const init = returnedVariableDeclaration.declarations[0]?.init as ObjectExpression | undefined
|
||||||
removePropertyFromObject(init, name, magicString)
|
if (init) {
|
||||||
|
removePropertyFromObject(init, name, magicString)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,7 +254,7 @@ export async function annotatePlugins (nuxt: Nuxt, plugins: NuxtPlugin[]) {
|
|||||||
const _plugins: Array<NuxtPlugin & Omit<PluginMeta, 'enforce'>> = []
|
const _plugins: Array<NuxtPlugin & Omit<PluginMeta, 'enforce'>> = []
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
try {
|
try {
|
||||||
const code = plugin.src in nuxt.vfs ? nuxt.vfs[plugin.src] : await fsp.readFile(plugin.src!, 'utf-8')
|
const code = plugin.src in nuxt.vfs ? nuxt.vfs[plugin.src]! : await fsp.readFile(plugin.src!, 'utf-8')
|
||||||
_plugins.push({
|
_plugins.push({
|
||||||
...await extractMetadata(code, IS_TSX.test(plugin.src) ? 'tsx' : 'ts'),
|
...await extractMetadata(code, IS_TSX.test(plugin.src) ? 'tsx' : 'ts'),
|
||||||
...plugin,
|
...plugin,
|
||||||
|
@ -138,9 +138,9 @@ function createGranularWatcher () {
|
|||||||
delete watchers[path]
|
delete watchers[path]
|
||||||
}
|
}
|
||||||
if (event === 'addDir' && path !== dir && !ignoredDirs.has(path) && !pathsToWatch.includes(path) && !(path in watchers) && !isIgnored(path)) {
|
if (event === 'addDir' && path !== dir && !ignoredDirs.has(path) && !pathsToWatch.includes(path) && !(path in watchers) && !isIgnored(path)) {
|
||||||
watchers[path] = chokidarWatch(path, { ...nuxt.options.watchers.chokidar, ignored: [isIgnored] })
|
const pathWatcher = watchers[path] = chokidarWatch(path, { ...nuxt.options.watchers.chokidar, ignored: [isIgnored] })
|
||||||
watchers[path].on('all', (event, p) => nuxt.callHook('builder:watch', event, normalize(p)))
|
pathWatcher.on('all', (event, p) => nuxt.callHook('builder:watch', event, normalize(p)))
|
||||||
nuxt.hook('close', () => watchers[path]?.close())
|
nuxt.hook('close', () => pathWatcher?.close())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
watcher.on('ready', () => {
|
watcher.on('ready', () => {
|
||||||
|
@ -102,7 +102,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
baseURL: nuxt.options.app.baseURL,
|
baseURL: nuxt.options.app.baseURL,
|
||||||
virtual: {
|
virtual: {
|
||||||
'#internal/nuxt.config.mjs': () => nuxt.vfs['#build/nuxt.config'],
|
'#internal/nuxt.config.mjs': () => nuxt.vfs['#build/nuxt.config'],
|
||||||
'#internal/nuxt/app-config': () => nuxt.vfs['#build/app.config'].replace(/\/\*\* client \*\*\/[\s\S]*\/\*\* client-end \*\*\//, ''),
|
'#internal/nuxt/app-config': () => nuxt.vfs['#build/app.config']?.replace(/\/\*\* client \*\*\/[\s\S]*\/\*\* client-end \*\*\//, ''),
|
||||||
'#spa-template': async () => `export const template = ${JSON.stringify(await spaLoadingTemplate(nuxt))}`,
|
'#spa-template': async () => `export const template = ${JSON.stringify(await spaLoadingTemplate(nuxt))}`,
|
||||||
},
|
},
|
||||||
routeRules: {
|
routeRules: {
|
||||||
|
@ -345,10 +345,10 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
// TODO: [Experimental] Avoid emitting assets when flag is enabled
|
// TODO: [Experimental] Avoid emitting assets when flag is enabled
|
||||||
if (nuxt.options.features.noScripts && !nuxt.options.dev) {
|
if (nuxt.options.features.noScripts && !nuxt.options.dev) {
|
||||||
nuxt.hook('build:manifest', async (manifest) => {
|
nuxt.hook('build:manifest', async (manifest) => {
|
||||||
for (const file in manifest) {
|
for (const chunk of Object.values(manifest)) {
|
||||||
if (manifest[file].resourceType === 'script') {
|
if (chunk.resourceType === 'script') {
|
||||||
await rm(resolve(nuxt.options.buildDir, 'dist/client', withoutLeadingSlash(nuxt.options.app.buildAssetsDir), manifest[file].file), { force: true })
|
await rm(resolve(nuxt.options.buildDir, 'dist/client', withoutLeadingSlash(nuxt.options.app.buildAssetsDir), chunk.file), { force: true })
|
||||||
manifest[file].file = ''
|
chunk.file = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -64,7 +64,7 @@ export const LayerAliasingPlugin = createUnplugin((options: LayerAliasingOptions
|
|||||||
if (!layer || !ALIAS_RE_SINGLE.test(code)) { return }
|
if (!layer || !ALIAS_RE_SINGLE.test(code)) { return }
|
||||||
|
|
||||||
const s = new MagicString(code)
|
const s = new MagicString(code)
|
||||||
s.replace(ALIAS_RE, r => aliases[layer][r as '~'] || r)
|
s.replace(ALIAS_RE, r => aliases[layer]?.[r as '~'] || r)
|
||||||
|
|
||||||
if (s.hasChanged()) {
|
if (s.hasChanged()) {
|
||||||
return {
|
return {
|
||||||
|
@ -70,7 +70,7 @@ export async function extractMetadata (code: string, loader = 'ts' as 'ts' | 'ts
|
|||||||
}
|
}
|
||||||
|
|
||||||
const plugin = node.arguments[0]
|
const plugin = node.arguments[0]
|
||||||
if (plugin.type === 'ObjectExpression') {
|
if (plugin?.type === 'ObjectExpression') {
|
||||||
meta = defu(extractMetaFromObject(plugin.properties), meta)
|
meta = defu(extractMetaFromObject(plugin.properties), meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ export const RemovePluginMetadataPlugin = (nuxt: Nuxt) => createUnplugin(() => {
|
|||||||
name: 'nuxt:remove-plugin-metadata',
|
name: 'nuxt:remove-plugin-metadata',
|
||||||
transform (code, id) {
|
transform (code, id) {
|
||||||
id = normalize(id)
|
id = normalize(id)
|
||||||
const plugin = nuxt.apps.default.plugins.find(p => p.src === id)
|
const plugin = nuxt.apps.default?.plugins.find(p => p.src === id)
|
||||||
if (!plugin) { return }
|
if (!plugin) { return }
|
||||||
|
|
||||||
const s = new MagicString(code)
|
const s = new MagicString(code)
|
||||||
|
@ -32,6 +32,7 @@ export function prehydrateTransformPlugin (nuxt: Nuxt) {
|
|||||||
const node = _node as SimpleCallExpression & { start: number, end: number }
|
const node = _node as SimpleCallExpression & { start: number, end: number }
|
||||||
const name = 'name' in node.callee && node.callee.name
|
const name = 'name' in node.callee && node.callee.name
|
||||||
if (name === 'onPrehydrate') {
|
if (name === 'onPrehydrate') {
|
||||||
|
if (!node.arguments[0]) { return }
|
||||||
if (node.arguments[0].type !== 'ArrowFunctionExpression' && node.arguments[0].type !== 'FunctionExpression') { return }
|
if (node.arguments[0].type !== 'ArrowFunctionExpression' && node.arguments[0].type !== 'FunctionExpression') { return }
|
||||||
|
|
||||||
const needsAttr = node.arguments[0].params.length > 0
|
const needsAttr = node.arguments[0].params.length > 0
|
||||||
|
@ -325,7 +325,9 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
// Render 103 Early Hints
|
// Render 103 Early Hints
|
||||||
if (process.env.NUXT_EARLY_HINTS && !isRenderingPayload && !import.meta.prerender) {
|
if (process.env.NUXT_EARLY_HINTS && !isRenderingPayload && !import.meta.prerender) {
|
||||||
const { link } = renderResourceHeaders({}, renderer.rendererContext)
|
const { link } = renderResourceHeaders({}, renderer.rendererContext)
|
||||||
writeEarlyHints(event, link)
|
if (link) {
|
||||||
|
writeEarlyHints(event, link)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NUXT_INLINE_STYLES && !isRenderingIsland) {
|
if (process.env.NUXT_INLINE_STYLES && !isRenderingIsland) {
|
||||||
@ -394,8 +396,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
}
|
}
|
||||||
if (!isRenderingIsland || import.meta.dev) {
|
if (!isRenderingIsland || import.meta.dev) {
|
||||||
const link: Link[] = []
|
const link: Link[] = []
|
||||||
for (const style in styles) {
|
for (const resource of Object.values(styles)) {
|
||||||
const resource = styles[style]
|
|
||||||
// Do not add links to resources that are inlined (vite v5+)
|
// Do not add links to resources that are inlined (vite v5+)
|
||||||
if (import.meta.dev && 'inline' in getURLQuery(resource.file)) {
|
if (import.meta.dev && 'inline' in getURLQuery(resource.file)) {
|
||||||
continue
|
continue
|
||||||
@ -566,7 +567,7 @@ async function renderInlineStyles (usedModules: Set<string> | string[]): Promise
|
|||||||
const styleMap = await getSSRStyles()
|
const styleMap = await getSSRStyles()
|
||||||
const inlinedStyles = new Set<string>()
|
const inlinedStyles = new Set<string>()
|
||||||
for (const mod of usedModules) {
|
for (const mod of usedModules) {
|
||||||
if (mod in styleMap) {
|
if (mod in styleMap && styleMap[mod]) {
|
||||||
for (const style of await styleMap[mod]()) {
|
for (const style of await styleMap[mod]()) {
|
||||||
inlinedStyles.add(style)
|
inlinedStyles.add(style)
|
||||||
}
|
}
|
||||||
@ -650,7 +651,7 @@ function splitPayload (ssrContext: NuxtSSRContext) {
|
|||||||
*/
|
*/
|
||||||
function getServerComponentHTML (body: string): string {
|
function getServerComponentHTML (body: string): string {
|
||||||
const match = body.match(ROOT_NODE_REGEX)
|
const match = body.match(ROOT_NODE_REGEX)
|
||||||
return match ? match[1] : body[0]
|
return match?.[1] || body
|
||||||
}
|
}
|
||||||
|
|
||||||
const SSR_SLOT_TELEPORT_MARKER = /^uid=([^;]*);slot=(.*)$/
|
const SSR_SLOT_TELEPORT_MARKER = /^uid=([^;]*);slot=(.*)$/
|
||||||
@ -660,10 +661,10 @@ const SSR_CLIENT_SLOT_MARKER = /^island-slot=[^;]*;(.*)$/
|
|||||||
function getSlotIslandResponse (ssrContext: NuxtSSRContext): NuxtIslandResponse['slots'] {
|
function getSlotIslandResponse (ssrContext: NuxtSSRContext): NuxtIslandResponse['slots'] {
|
||||||
if (!ssrContext.islandContext || !Object.keys(ssrContext.islandContext.slots).length) { return undefined }
|
if (!ssrContext.islandContext || !Object.keys(ssrContext.islandContext.slots).length) { return undefined }
|
||||||
const response: NuxtIslandResponse['slots'] = {}
|
const response: NuxtIslandResponse['slots'] = {}
|
||||||
for (const slot in ssrContext.islandContext.slots) {
|
for (const [name, slot] of Object.entries(ssrContext.islandContext.slots)) {
|
||||||
response[slot] = {
|
response[name] = {
|
||||||
...ssrContext.islandContext.slots[slot],
|
...slot,
|
||||||
fallback: ssrContext.teleports?.[`island-fallback=${slot}`],
|
fallback: ssrContext.teleports?.[`island-fallback=${name}`],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
@ -673,11 +674,11 @@ function getClientIslandResponse (ssrContext: NuxtSSRContext): NuxtIslandRespons
|
|||||||
if (!ssrContext.islandContext || !Object.keys(ssrContext.islandContext.components).length) { return undefined }
|
if (!ssrContext.islandContext || !Object.keys(ssrContext.islandContext.components).length) { return undefined }
|
||||||
const response: NuxtIslandResponse['components'] = {}
|
const response: NuxtIslandResponse['components'] = {}
|
||||||
|
|
||||||
for (const clientUid in ssrContext.islandContext.components) {
|
for (const [clientUid, component] of Object.entries(ssrContext.islandContext.components)) {
|
||||||
// remove teleport anchor to avoid hydration issues
|
// remove teleport anchor to avoid hydration issues
|
||||||
const html = ssrContext.teleports?.[clientUid].replaceAll('<!--teleport start anchor-->', '') || ''
|
const html = ssrContext.teleports?.[clientUid]?.replaceAll('<!--teleport start anchor-->', '') || ''
|
||||||
response[clientUid] = {
|
response[clientUid] = {
|
||||||
...ssrContext.islandContext.components[clientUid],
|
...component,
|
||||||
html,
|
html,
|
||||||
slots: getComponentSlotTeleport(ssrContext.teleports ?? {}),
|
slots: getComponentSlotTeleport(ssrContext.teleports ?? {}),
|
||||||
}
|
}
|
||||||
|
@ -28,11 +28,12 @@ export function resolveComponentNameSegments (fileName: string, prefixParts: str
|
|||||||
let index = prefixParts.length - 1
|
let index = prefixParts.length - 1
|
||||||
const matchedSuffix: string[] = []
|
const matchedSuffix: string[] = []
|
||||||
while (index >= 0) {
|
while (index >= 0) {
|
||||||
matchedSuffix.unshift(...splitByCase(prefixParts[index]).map(p => p.toLowerCase()))
|
const prefixPart = prefixParts[index]!
|
||||||
|
matchedSuffix.unshift(...splitByCase(prefixPart).map(p => p.toLowerCase()))
|
||||||
const matchedSuffixContent = matchedSuffix.join('/')
|
const matchedSuffixContent = matchedSuffix.join('/')
|
||||||
if ((fileNamePartsContent === matchedSuffixContent || fileNamePartsContent.startsWith(matchedSuffixContent + '/')) ||
|
if ((fileNamePartsContent === matchedSuffixContent || fileNamePartsContent.startsWith(matchedSuffixContent + '/')) ||
|
||||||
// e.g Item/Item/Item.vue -> Item
|
// e.g Item/Item/Item.vue -> Item
|
||||||
(prefixParts[index].toLowerCase() === fileNamePartsContent &&
|
(prefixPart.toLowerCase() === fileNamePartsContent &&
|
||||||
prefixParts[index + 1] &&
|
prefixParts[index + 1] &&
|
||||||
prefixParts[index] === prefixParts[index + 1])) {
|
prefixParts[index] === prefixParts[index + 1])) {
|
||||||
componentNameParts.length = index
|
componentNameParts.length = index
|
||||||
|
@ -160,7 +160,7 @@ export const Title = defineComponent({
|
|||||||
if (import.meta.dev) {
|
if (import.meta.dev) {
|
||||||
const defaultSlot = slots.default?.()
|
const defaultSlot = slots.default?.()
|
||||||
|
|
||||||
if (defaultSlot && (defaultSlot.length > 1 || typeof defaultSlot[0].children !== 'string')) {
|
if (defaultSlot && (defaultSlot.length > 1 || (defaultSlot[0] && typeof defaultSlot[0].children !== 'string'))) {
|
||||||
console.error('<Title> can take only one string in its default slot.')
|
console.error('<Title> can take only one string in its default slot.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ export default defineNuxtModule({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
function isPage (file: string, pages = nuxt.apps.default.pages): boolean {
|
function isPage (file: string, pages = nuxt.apps.default?.pages): boolean {
|
||||||
if (!pages) { return false }
|
if (!pages) { return false }
|
||||||
return pages.some(page => page.file === file) || pages.some(page => page.children && isPage(file, page.children))
|
return pages.some(page => page.file === file) || pages.some(page => page.children && isPage(file, page.children))
|
||||||
}
|
}
|
||||||
@ -359,7 +359,7 @@ export default defineNuxtModule({
|
|||||||
|
|
||||||
const updatePage = async function updatePage (path: string) {
|
const updatePage = async function updatePage (path: string) {
|
||||||
const glob = pageToGlobMap[path]
|
const glob = pageToGlobMap[path]
|
||||||
const code = path in nuxt.vfs ? nuxt.vfs[path] : await readFile(path!, 'utf-8')
|
const code = path in nuxt.vfs ? nuxt.vfs[path]! : await readFile(path!, 'utf-8')
|
||||||
try {
|
try {
|
||||||
const extractedRule = await extractRouteRules(code)
|
const extractedRule = await extractRouteRules(code)
|
||||||
if (extractedRule) {
|
if (extractedRule) {
|
||||||
@ -408,8 +408,7 @@ export default defineNuxtModule({
|
|||||||
nuxt.hook('pages:extend', (routes) => {
|
nuxt.hook('pages:extend', (routes) => {
|
||||||
const nitro = useNitro()
|
const nitro = useNitro()
|
||||||
let resolvedRoutes: string[]
|
let resolvedRoutes: string[]
|
||||||
for (const path in nitro.options.routeRules) {
|
for (const [path, rule] of Object.entries(nitro.options.routeRules)) {
|
||||||
const rule = nitro.options.routeRules[path]
|
|
||||||
if (!rule.redirect) { continue }
|
if (!rule.redirect) { continue }
|
||||||
resolvedRoutes ||= routes.flatMap(route => resolveRoutePaths(route))
|
resolvedRoutes ||= routes.flatMap(route => resolveRoutePaths(route))
|
||||||
// skip if there's already a route matching this path
|
// skip if there's already a route matching this path
|
||||||
@ -455,14 +454,14 @@ export default defineNuxtModule({
|
|||||||
if (nuxt.options.dev) { return }
|
if (nuxt.options.dev) { return }
|
||||||
const sourceFiles = nuxt.apps.default?.pages?.length ? getSources(nuxt.apps.default.pages) : []
|
const sourceFiles = nuxt.apps.default?.pages?.length ? getSources(nuxt.apps.default.pages) : []
|
||||||
|
|
||||||
for (const key in manifest) {
|
for (const [key, chunk] of Object.entries(manifest)) {
|
||||||
if (manifest[key].src && Object.values(nuxt.apps).some(app => app.pages?.some(page => page.mode === 'server' && page.file === join(nuxt.options.srcDir, manifest[key].src!)))) {
|
if (chunk.src && Object.values(nuxt.apps).some(app => app.pages?.some(page => page.mode === 'server' && page.file === join(nuxt.options.srcDir, chunk.src!)))) {
|
||||||
delete manifest[key]
|
delete manifest[key]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (manifest[key].isEntry) {
|
if (chunk.isEntry) {
|
||||||
manifest[key].dynamicImports =
|
chunk.dynamicImports =
|
||||||
manifest[key].dynamicImports?.filter(i => !sourceFiles.includes(i))
|
chunk.dynamicImports?.filter(i => !sourceFiles.includes(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -14,7 +14,7 @@ const ruleCache: Record<string, NitroRouteConfig | null> = {}
|
|||||||
|
|
||||||
export async function extractRouteRules (code: string): Promise<NitroRouteConfig | null> {
|
export async function extractRouteRules (code: string): Promise<NitroRouteConfig | null> {
|
||||||
if (code in ruleCache) {
|
if (code in ruleCache) {
|
||||||
return ruleCache[code]
|
return ruleCache[code] || null
|
||||||
}
|
}
|
||||||
if (!ROUTE_RULE_RE.test(code)) { return null }
|
if (!ROUTE_RULE_RE.test(code)) { return null }
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
|
|||||||
// Array where routes should be added, useful when adding child routes
|
// Array where routes should be added, useful when adding child routes
|
||||||
let parent = routes
|
let parent = routes
|
||||||
|
|
||||||
const lastSegment = segments[segments.length - 1]
|
const lastSegment = segments[segments.length - 1]!
|
||||||
if (lastSegment.endsWith('.server')) {
|
if (lastSegment.endsWith('.server')) {
|
||||||
segments[segments.length - 1] = lastSegment.replace('.server', '')
|
segments[segments.length - 1] = lastSegment.replace('.server', '')
|
||||||
if (options.shouldUseServerComponents) {
|
if (options.shouldUseServerComponents) {
|
||||||
@ -116,7 +116,7 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
|
|||||||
for (let i = 0; i < segments.length; i++) {
|
for (let i = 0; i < segments.length; i++) {
|
||||||
const segment = segments[i]
|
const segment = segments[i]
|
||||||
|
|
||||||
const tokens = parseSegment(segment)
|
const tokens = parseSegment(segment!)
|
||||||
|
|
||||||
// Skip group segments
|
// Skip group segments
|
||||||
if (tokens.every(token => token.type === SegmentTokenType.group)) {
|
if (tokens.every(token => token.type === SegmentTokenType.group)) {
|
||||||
@ -151,7 +151,7 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
|
|||||||
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, augmentedPages = new Set<string>()) {
|
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, augmentedPages = new Set<string>()) {
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
if (route.file && !augmentedPages.has(route.file)) {
|
if (route.file && !augmentedPages.has(route.file)) {
|
||||||
const fileContent = route.file in vfs ? vfs[route.file] : fs.readFileSync(await resolvePath(route.file), 'utf-8')
|
const fileContent = route.file in vfs ? vfs[route.file]! : fs.readFileSync(await resolvePath(route.file), 'utf-8')
|
||||||
const routeMeta = await getRouteMeta(fileContent, route.file)
|
const routeMeta = await getRouteMeta(fileContent, route.file)
|
||||||
if (route.meta) {
|
if (route.meta) {
|
||||||
routeMeta.meta = { ...routeMeta.meta, ...route.meta }
|
routeMeta.meta = { ...routeMeta.meta, ...route.meta }
|
||||||
@ -174,7 +174,7 @@ export function extractScriptContent (html: string) {
|
|||||||
for (const match of html.matchAll(SFC_SCRIPT_RE)) {
|
for (const match of html.matchAll(SFC_SCRIPT_RE)) {
|
||||||
if (match?.groups?.content) {
|
if (match?.groups?.content) {
|
||||||
contents.push({
|
contents.push({
|
||||||
loader: match.groups.attrs.includes('tsx') ? 'tsx' : 'ts',
|
loader: match.groups.attrs?.includes('tsx') ? 'tsx' : 'ts',
|
||||||
code: match.groups.content.trim(),
|
code: match.groups.content.trim(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -196,7 +196,9 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
|
|||||||
delete metaCache[absolutePath]
|
delete metaCache[absolutePath]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (absolutePath in metaCache) { return metaCache[absolutePath] }
|
if (absolutePath in metaCache && metaCache[absolutePath]) {
|
||||||
|
return metaCache[absolutePath]
|
||||||
|
}
|
||||||
|
|
||||||
const loader = getLoader(absolutePath)
|
const loader = getLoader(absolutePath)
|
||||||
const scriptBlocks = !loader ? null : loader === 'vue' ? extractScriptContent(contents) : [{ code: contents, loader }]
|
const scriptBlocks = !loader ? null : loader === 'vue' ? extractScriptContent(contents) : [{ code: contents, loader }]
|
||||||
@ -403,7 +405,7 @@ function parseSegment (segment: string) {
|
|||||||
consumeBuffer()
|
consumeBuffer()
|
||||||
}
|
}
|
||||||
state = SegmentParserState.initial
|
state = SegmentParserState.initial
|
||||||
} else if (PARAM_CHAR_RE.test(c)) {
|
} else if (c && PARAM_CHAR_RE.test(c)) {
|
||||||
buffer += c
|
buffer += c
|
||||||
} else {
|
} else {
|
||||||
// console.debug(`[pages]Ignored character "${c}" while building param "${buffer}" from "segment"`)
|
// console.debug(`[pages]Ignored character "${c}" while building param "${buffer}" from "segment"`)
|
||||||
|
@ -297,8 +297,8 @@ async function getResolvedApp (files: Array<string | { name: string, contents: s
|
|||||||
mw.path = normaliseToRepo(mw.path)!
|
mw.path = normaliseToRepo(mw.path)!
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const layout in app.layouts) {
|
for (const layout of Object.values(app.layouts)) {
|
||||||
app.layouts[layout].file = normaliseToRepo(app.layouts[layout].file)!
|
layout.file = normaliseToRepo(layout.file)!
|
||||||
}
|
}
|
||||||
|
|
||||||
await nuxt.close()
|
await nuxt.close()
|
||||||
|
@ -653,7 +653,7 @@ describe('pages:generateRoutesFromFiles', () => {
|
|||||||
}))).map((route, index) => {
|
}))).map((route, index) => {
|
||||||
return {
|
return {
|
||||||
...route,
|
...route,
|
||||||
meta: test.files![index].meta,
|
meta: test.files![index]!.meta,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ export const DevRenderingPlugin = () => {
|
|||||||
const chunks = contents.split(/\{{2,3}[^{}]+\}{2,3}/g)
|
const chunks = contents.split(/\{{2,3}[^{}]+\}{2,3}/g)
|
||||||
let templateString = chunks.shift()
|
let templateString = chunks.shift()
|
||||||
for (const expression of contents.matchAll(/\{{2,3}([^{}]+)\}{2,3}/g)) {
|
for (const expression of contents.matchAll(/\{{2,3}([^{}]+)\}{2,3}/g)) {
|
||||||
const value = runInNewContext(expression[1].trim(), {
|
const value = runInNewContext(expression[1]!.trim(), {
|
||||||
version,
|
version,
|
||||||
messages: { ...genericMessages, ...messages },
|
messages: { ...genericMessages, ...messages },
|
||||||
})
|
})
|
||||||
|
@ -5,11 +5,13 @@ import { relative, resolve } from 'pathe'
|
|||||||
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
import { withTrailingSlash, withoutLeadingSlash } from 'ufo'
|
||||||
import escapeRE from 'escape-string-regexp'
|
import escapeRE from 'escape-string-regexp'
|
||||||
import { normalizeViteManifest } from 'vue-bundle-renderer'
|
import { normalizeViteManifest } from 'vue-bundle-renderer'
|
||||||
|
import type { Manifest as RendererManifest } from 'vue-bundle-renderer'
|
||||||
|
import type { Manifest as ViteClientManifest } from 'vite'
|
||||||
import type { ViteBuildContext } from './vite'
|
import type { ViteBuildContext } from './vite'
|
||||||
|
|
||||||
export async function writeManifest (ctx: ViteBuildContext) {
|
export async function writeManifest (ctx: ViteBuildContext) {
|
||||||
// This is only used for ssr: false - when ssr is enabled we use vite-node runtime manifest
|
// This is only used for ssr: false - when ssr is enabled we use vite-node runtime manifest
|
||||||
const devClientManifest = {
|
const devClientManifest: RendererManifest = {
|
||||||
'@vite/client': {
|
'@vite/client': {
|
||||||
isEntry: true,
|
isEntry: true,
|
||||||
file: '@vite/client',
|
file: '@vite/client',
|
||||||
@ -30,17 +32,17 @@ export async function writeManifest (ctx: ViteBuildContext) {
|
|||||||
const serverDist = resolve(ctx.nuxt.options.buildDir, 'dist/server')
|
const serverDist = resolve(ctx.nuxt.options.buildDir, 'dist/server')
|
||||||
|
|
||||||
const manifestFile = resolve(clientDist, 'manifest.json')
|
const manifestFile = resolve(clientDist, 'manifest.json')
|
||||||
const clientManifest = ctx.nuxt.options.dev ? devClientManifest : JSON.parse(readFileSync(manifestFile, 'utf-8'))
|
const clientManifest = ctx.nuxt.options.dev ? devClientManifest : JSON.parse(readFileSync(manifestFile, 'utf-8')) as ViteClientManifest
|
||||||
|
const manifestEntries = Object.values(clientManifest)
|
||||||
|
|
||||||
const buildAssetsDir = withTrailingSlash(withoutLeadingSlash(ctx.nuxt.options.app.buildAssetsDir))
|
const buildAssetsDir = withTrailingSlash(withoutLeadingSlash(ctx.nuxt.options.app.buildAssetsDir))
|
||||||
const BASE_RE = new RegExp(`^${escapeRE(buildAssetsDir)}`)
|
const BASE_RE = new RegExp(`^${escapeRE(buildAssetsDir)}`)
|
||||||
|
|
||||||
for (const key in clientManifest) {
|
for (const entry of manifestEntries) {
|
||||||
const entry = clientManifest[key]
|
|
||||||
if (entry.file) {
|
if (entry.file) {
|
||||||
entry.file = entry.file.replace(BASE_RE, '')
|
entry.file = entry.file.replace(BASE_RE, '')
|
||||||
}
|
}
|
||||||
for (const item of ['css', 'assets']) {
|
for (const item of ['css', 'assets'] as const) {
|
||||||
if (entry[item]) {
|
if (entry[item]) {
|
||||||
entry[item] = entry[item].map((i: string) => i.replace(BASE_RE, ''))
|
entry[item] = entry[item].map((i: string) => i.replace(BASE_RE, ''))
|
||||||
}
|
}
|
||||||
@ -50,12 +52,11 @@ export async function writeManifest (ctx: ViteBuildContext) {
|
|||||||
await mkdir(serverDist, { recursive: true })
|
await mkdir(serverDist, { recursive: true })
|
||||||
|
|
||||||
if (ctx.config.build?.cssCodeSplit === false) {
|
if (ctx.config.build?.cssCodeSplit === false) {
|
||||||
for (const key in clientManifest as Record<string, { file?: string }>) {
|
for (const entry of manifestEntries) {
|
||||||
const val = clientManifest[key]
|
if (entry.file?.endsWith('.css')) {
|
||||||
if (val.file?.endsWith('.css')) {
|
|
||||||
const key = relative(ctx.config.root!, ctx.entry)
|
const key = relative(ctx.config.root!, ctx.entry)
|
||||||
clientManifest[key].css ||= []
|
clientManifest[key]!.css ||= []
|
||||||
clientManifest[key].css!.push(val.file)
|
;(clientManifest[key]!.css as string[]).push(entry.file)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,7 @@ export function analyzePlugin (ctx: ViteBuildContext): Plugin[] {
|
|||||||
const bundle = outputBundle[_bundleId]
|
const bundle = outputBundle[_bundleId]
|
||||||
if (!bundle || bundle.type !== 'chunk') { continue }
|
if (!bundle || bundle.type !== 'chunk') { continue }
|
||||||
const minifiedModuleEntryPromises: Array<Promise<[string, RenderedModule]>> = []
|
const minifiedModuleEntryPromises: Array<Promise<[string, RenderedModule]>> = []
|
||||||
for (const moduleId in bundle.modules) {
|
for (const [moduleId, module] of Object.entries(bundle.modules)) {
|
||||||
const module = bundle.modules[moduleId]!
|
|
||||||
minifiedModuleEntryPromises.push(
|
minifiedModuleEntryPromises.push(
|
||||||
transform(module.code || '', { minify: true })
|
transform(module.code || '', { minify: true })
|
||||||
.then(result => [moduleId, { ...module, code: result.code }]),
|
.then(result => [moduleId, { ...module, code: result.code }]),
|
||||||
|
@ -86,8 +86,7 @@ export const VitePublicDirsPlugin = createUnplugin((options: VitePublicDirsPlugi
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
generateBundle (_outputOptions, bundle) {
|
generateBundle (_outputOptions, bundle) {
|
||||||
for (const file in bundle) {
|
for (const [file, chunk] of Object.entries(bundle)) {
|
||||||
const chunk = bundle[file]!
|
|
||||||
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
|
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
|
||||||
|
|
||||||
let css = chunk.source.toString()
|
let css = chunk.source.toString()
|
||||||
|
@ -62,8 +62,7 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
if (options.mode === 'client') { return }
|
if (options.mode === 'client') { return }
|
||||||
|
|
||||||
const emitted: Record<string, string> = {}
|
const emitted: Record<string, string> = {}
|
||||||
for (const file in cssMap) {
|
for (const [file, { files, inBundle }] of Object.entries(cssMap)) {
|
||||||
const { files, inBundle } = cssMap[file]!
|
|
||||||
// File has been tree-shaken out of build (or there are no styles to inline)
|
// File has been tree-shaken out of build (or there are no styles to inline)
|
||||||
if (!files.length || !inBundle) { continue }
|
if (!files.length || !inBundle) { continue }
|
||||||
const fileName = filename(file)
|
const fileName = filename(file)
|
||||||
|
@ -207,8 +207,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
|||||||
|
|
||||||
// Remove CSS entries for files that will have inlined styles
|
// Remove CSS entries for files that will have inlined styles
|
||||||
ctx.nuxt.hook('build:manifest', (manifest) => {
|
ctx.nuxt.hook('build:manifest', (manifest) => {
|
||||||
for (const key in manifest) {
|
for (const [key, entry] of Object.entries(manifest)) {
|
||||||
const entry = manifest[key]!
|
|
||||||
const shouldRemoveCSS = chunksWithInlinedCSS.has(key) && !entry.isEntry
|
const shouldRemoveCSS = chunksWithInlinedCSS.has(key) && !entry.isEntry
|
||||||
if (entry.isEntry && chunksWithInlinedCSS.has(key)) {
|
if (entry.isEntry && chunksWithInlinedCSS.has(key)) {
|
||||||
// @ts-expect-error internal key
|
// @ts-expect-error internal key
|
||||||
|
@ -33,9 +33,10 @@ export default class VueSSRClientPlugin {
|
|||||||
const stats = compilation.getStats().toJson()
|
const stats = compilation.getStats().toJson()
|
||||||
|
|
||||||
const initialFiles = new Set<string>()
|
const initialFiles = new Set<string>()
|
||||||
for (const name in stats.entrypoints!) {
|
for (const { assets } of Object.values(stats.entrypoints!)) {
|
||||||
const entryAssets = stats.entrypoints![name]!.assets!
|
if (!assets) { continue }
|
||||||
for (const asset of entryAssets) {
|
|
||||||
|
for (const asset of assets) {
|
||||||
const file = asset.name
|
const file = asset.name
|
||||||
if ((isJS(file) || isCSS(file)) && !isHotUpdate(file)) {
|
if ((isJS(file) || isCSS(file)) && !isHotUpdate(file)) {
|
||||||
initialFiles.add(file)
|
initialFiles.add(file)
|
||||||
@ -71,52 +72,53 @@ export default class VueSSRClientPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { entrypoints = {}, namedChunkGroups = {} } = stats
|
const { entrypoints = {}, namedChunkGroups = {} } = stats
|
||||||
const assetModules = stats.modules!.filter(m => m.assets!.length)
|
const fileToIndex = (file: string | number) => webpackManifest.all.indexOf(String(file))
|
||||||
const fileToIndex = (file: string) => webpackManifest.all.indexOf(file)
|
for (const m of stats.modules!) {
|
||||||
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) { continue }
|
||||||
const [cid] = m.chunks!
|
|
||||||
const chunk = stats.chunks!.find(c => c.id === cid)
|
|
||||||
if (!chunk || !chunk.files || !cid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const id = m.identifier!.replace(/\s\w+$/, '') // remove appended hash
|
|
||||||
const filesSet = new Set(chunk.files.map(fileToIndex).filter(i => i !== -1))
|
|
||||||
|
|
||||||
for (const chunkName of chunk.names!) {
|
const [cid] = m.chunks
|
||||||
if (!entrypoints[chunkName]) {
|
const chunk = stats.chunks!.find(c => c.id === cid)
|
||||||
const chunkGroup = namedChunkGroups[chunkName]
|
if (!chunk || !chunk.files || !cid) {
|
||||||
if (chunkGroup) {
|
continue
|
||||||
for (const asset of chunkGroup.assets!) {
|
|
||||||
filesSet.add(fileToIndex(asset.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = Array.from(filesSet)
|
|
||||||
webpackManifest.modules[hash(id)] = files
|
|
||||||
|
|
||||||
// In production mode, modules may be concatenated by scope hoisting
|
|
||||||
// Include ConcatenatedModule for not losing module-component mapping
|
|
||||||
if (Array.isArray(m.modules)) {
|
|
||||||
for (const concatenatedModule of m.modules) {
|
|
||||||
const id = hash(concatenatedModule.identifier!.replace(/\s\w+$/, ''))
|
|
||||||
if (!webpackManifest.modules[id]) {
|
|
||||||
webpackManifest.modules[id] = files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all asset modules associated with the same chunk
|
|
||||||
assetModules.forEach((m) => {
|
|
||||||
if (m.chunks!.includes(cid)) {
|
|
||||||
files.push(...(m.assets as string[]).map(fileToIndex))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
const id = m.identifier!.replace(/\s\w+$/, '') // remove appended hash
|
||||||
|
const filesSet = new Set(chunk.files.map(fileToIndex).filter(i => i !== -1))
|
||||||
|
|
||||||
|
for (const chunkName of chunk.names!) {
|
||||||
|
if (!entrypoints[chunkName]) {
|
||||||
|
const chunkGroup = namedChunkGroups[chunkName]
|
||||||
|
if (chunkGroup) {
|
||||||
|
for (const asset of chunkGroup.assets!) {
|
||||||
|
filesSet.add(fileToIndex(asset.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = Array.from(filesSet)
|
||||||
|
webpackManifest.modules[hash(id)] = files
|
||||||
|
|
||||||
|
// In production mode, modules may be concatenated by scope hoisting
|
||||||
|
// Include ConcatenatedModule for not losing module-component mapping
|
||||||
|
if (Array.isArray(m.modules)) {
|
||||||
|
for (const concatenatedModule of m.modules) {
|
||||||
|
const id = hash(concatenatedModule.identifier!.replace(/\s\w+$/, ''))
|
||||||
|
if (!webpackManifest.modules[id]) {
|
||||||
|
webpackManifest.modules[id] = files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all asset modules associated with the same chunk
|
||||||
|
if (stats.modules) {
|
||||||
|
for (const m of stats.modules) {
|
||||||
|
if (m.assets?.length && m.chunks?.includes(cid)) {
|
||||||
|
files.push(...m.assets.map(fileToIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const manifest = normalizeWebpackManifest(webpackManifest as any)
|
const manifest = normalizeWebpackManifest(webpackManifest as any)
|
||||||
await this.options.nuxt.callHook('build:manifest', manifest)
|
await this.options.nuxt.callHook('build:manifest', manifest)
|
||||||
|
@ -18,13 +18,13 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
// Identical behaviour between inline/external vue options as this should only affect the server build
|
// Identical behaviour between inline/external vue options as this should only affect the server build
|
||||||
|
|
||||||
it('default client bundle size', async () => {
|
it('default client bundle size', async () => {
|
||||||
const [clientStats, clientStatsInlined] = await Promise.all(['.output', '.output-inline']
|
const [clientStats, clientStatsInlined] = await Promise.all((['.output', '.output-inline'])
|
||||||
.map(outputDir => analyzeSizes(['**/*.js'], join(rootDir, outputDir, 'public'))))
|
.map(outputDir => analyzeSizes(['**/*.js'], join(rootDir, outputDir, 'public'))))
|
||||||
|
|
||||||
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot(`"114k"`)
|
expect.soft(roundToKilobytes(clientStats!.totalBytes)).toMatchInlineSnapshot(`"114k"`)
|
||||||
expect.soft(roundToKilobytes(clientStatsInlined.totalBytes)).toMatchInlineSnapshot(`"114k"`)
|
expect.soft(roundToKilobytes(clientStatsInlined!.totalBytes)).toMatchInlineSnapshot(`"114k"`)
|
||||||
|
|
||||||
const files = new Set([...clientStats.files, ...clientStatsInlined.files].map(f => f.replace(/\..*\.js/, '.js')))
|
const files = new Set([...clientStats!.files, ...clientStatsInlined!.files].map(f => f.replace(/\..*\.js/, '.js')))
|
||||||
|
|
||||||
expect([...files]).toMatchInlineSnapshot(`
|
expect([...files]).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
|
@ -12,8 +12,7 @@
|
|||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
/* Strictness */
|
/* Strictness */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
// TODO: enable noUncheckedIndexedAccess
|
"noUncheckedIndexedAccess": true,
|
||||||
// "noUncheckedIndexedAccess": true,
|
|
||||||
"noUncheckedSideEffectImports": true,
|
"noUncheckedSideEffectImports": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
|
Loading…
Reference in New Issue
Block a user