Skip to content

Commit a52d573

Browse files
committed
perf(kit,nuxt): reduce unnecessary iteration in nuxt code (#33212)
1 parent aab43c5 commit a52d573

File tree

17 files changed

+177
-94
lines changed

17 files changed

+177
-94
lines changed

packages/kit/src/loader/nuxt.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
3131
// Apply dev as config override
3232
opts.overrides.dev = !!opts.dev
3333

34-
const resolvedPath = ['nuxt-nightly', 'nuxt3', 'nuxt', 'nuxt-edge']
35-
.map(pkg => resolveModulePath(pkg, { try: true, from: [directoryToURL(opts.cwd!)] }))
36-
.filter((p): p is NonNullable<typeof p> => !!p)
37-
.sort((a, b) => b.length - a.length)[0]
34+
const resolvedPath = ['nuxt-nightly', 'nuxt3', 'nuxt', 'nuxt-edge'].reduce((resolvedPath, pkg) => {
35+
const path = resolveModulePath(pkg, { try: true, from: [directoryToURL(opts.cwd!)] })
36+
return path && path.length > resolvedPath.length ? path : resolvedPath
37+
}, '')
3838

3939
if (!resolvedPath) {
4040
throw new Error(`Cannot find any nuxt version from ${opts.cwd}`)

packages/nuxt/src/app/composables/preload.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ export async function preloadRouteComponents (to: RouteLocationRaw, router: Rout
6161

6262
router._routePreloaded.add(path)
6363

64-
const components = matched
65-
.map(component => component.components?.default)
66-
.filter(component => typeof component === 'function')
67-
68-
for (const component of components) {
64+
for (const route of matched) {
65+
const component = route.components?.default
66+
if (typeof component !== 'function') {
67+
continue
68+
}
6969
const promise = Promise.resolve((component as () => unknown)())
7070
.catch(() => {})
7171
.finally(() => promises.splice(promises.indexOf(promise)))

packages/nuxt/src/app/composables/router.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,14 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
146146
if (import.meta.client && options?.open) {
147147
const { target = '_blank', windowFeatures = {} } = options.open
148148

149-
const features = Object.entries(windowFeatures)
150-
.filter(([_, value]) => value !== undefined)
151-
.map(([feature, value]) => `${feature.toLowerCase()}=${value}`)
152-
.join(', ')
149+
const features: string[] = []
150+
for (const [feature, value] of Object.entries(windowFeatures)) {
151+
if (value !== undefined) {
152+
features.push(`${feature.toLowerCase()}=${value}`)
153+
}
154+
}
153155

154-
open(toPath, target, features)
156+
open(toPath, target, features.join(', '))
155157
return Promise.resolve()
156158
}
157159

packages/nuxt/src/app/nuxt.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,10 @@ export function defineAppConfig<C extends AppConfigInput> (config: C): C {
586586
const loggedKeys = new Set<string>()
587587
function wrappedConfig (runtimeConfig: Record<string, unknown>) {
588588
if (!import.meta.dev || import.meta.server) { return runtimeConfig }
589-
const keys = Object.keys(runtimeConfig).map(key => `\`${key}\``)
589+
const keys: string[] = []
590+
for (const key in runtimeConfig) {
591+
keys.push(`\`${key}\``)
592+
}
590593
const lastKey = keys.pop()
591594
return new Proxy(runtimeConfig, {
592595
get (target, p, receiver) {

packages/nuxt/src/components/plugins/islands-transform.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,13 @@ function isBinding (attr: string): boolean {
167167
function getPropsToString (bindings: Record<string, string>): string {
168168
const vfor = bindings['v-for']?.split(' in ').map((v: string) => v.trim()) as [string, string] | undefined
169169
if (Object.keys(bindings).length === 0) { return 'undefined' }
170-
const content = Object.entries(bindings).filter(b => b[0] && (b[0] !== '_bind' && b[0] !== 'v-for')).map(([name, value]) => isBinding(name) ? `[\`${name.slice(1)}\`]: ${value}` : `[\`${name}\`]: \`${value}\``).join(',')
170+
const contentParts: string[] = []
171+
for (const [name, value] of Object.entries(bindings)) {
172+
if (name && (name !== '_bind' && name !== 'v-for')) {
173+
contentParts.push(isBinding(name) ? `[\`${name.slice(1)}\`]: ${value}` : `[\`${name}\`]: \`${value}\``)
174+
}
175+
}
176+
const content = contentParts.join(',')
171177
const data = bindings._bind ? `__mergeProps(${bindings._bind}, { ${content} })` : `{ ${content} }`
172178
if (!vfor) {
173179
return `[${data}]`

packages/nuxt/src/components/templates.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,13 @@ export default defineNuxtPlugin({
6565
export const componentNamesTemplate: NuxtTemplate = {
6666
filename: 'component-names.mjs',
6767
getContents ({ app }) {
68-
return `export const componentNames = ${JSON.stringify(app.components.filter(c => !c.island).map(c => c.pascalName))}`
68+
const componentNames: string[] = []
69+
for (const c of app.components) {
70+
if (!c.island) {
71+
componentNames.push(c.pascalName)
72+
}
73+
}
74+
return `export const componentNames = ${JSON.stringify(componentNames)}`
6975
},
7076
}
7177

@@ -106,16 +112,19 @@ export const componentsIslandsTemplate: NuxtTemplate = {
106112
const NON_VUE_RE = /\b\.(?!vue)\w+$/g
107113
function resolveComponentTypes (app: NuxtApp, baseDir: string) {
108114
const serverPlaceholderPath = resolve(distDir, 'app/components/server-placeholder')
109-
const componentTypes = app.components.filter(c => !c.island).map((c) => {
110-
const type = `typeof ${genDynamicImport(isAbsolute(c.filePath)
111-
? relative(baseDir, c.filePath).replace(NON_VUE_RE, '')
112-
: c.filePath.replace(NON_VUE_RE, ''), { wrapper: false })}['${c.export}']`
113-
const isServerOnly = c.mode === 'server' && c.filePath !== serverPlaceholderPath && !app.components.some(other => other.pascalName === c.pascalName && other.mode === 'client')
114-
return [
115-
c.pascalName,
116-
isServerOnly ? `IslandComponent<${type}>` : type,
117-
]
118-
})
115+
const componentTypes: Array<[string, string]> = []
116+
for (const c of app.components) {
117+
if (!c.island) {
118+
const type = `typeof ${genDynamicImport(isAbsolute(c.filePath)
119+
? relative(baseDir, c.filePath).replace(NON_VUE_RE, '')
120+
: c.filePath.replace(NON_VUE_RE, ''), { wrapper: false })}['${c.export}']`
121+
const isServerOnly = c.mode === 'server' && c.filePath !== serverPlaceholderPath && !app.components.some(other => other.pascalName === c.pascalName && other.mode === 'client')
122+
componentTypes.push([
123+
c.pascalName,
124+
isServerOnly ? `IslandComponent<${type}>` : type,
125+
])
126+
}
127+
}
119128

120129
return componentTypes
121130
}

packages/nuxt/src/core/modules.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,30 @@
1-
import { normalizeModuleTranspilePath, useNuxt } from '@nuxt/kit'
2-
3-
interface AddModuleTranspilesOptions {
4-
additionalModules?: string[]
5-
}
6-
7-
export const addModuleTranspiles = (opts: AddModuleTranspilesOptions = {}) => {
8-
const nuxt = useNuxt()
9-
10-
const modules = [
11-
...opts.additionalModules || [],
12-
...nuxt.options.modules,
13-
...nuxt.options._modules,
14-
]
15-
.map(m => typeof m === 'string' ? m : Array.isArray(m) ? m[0] : m.src)
16-
.filter(m => typeof m === 'string')
17-
.map(normalizeModuleTranspilePath)
1+
import { normalizeModuleTranspilePath } from '@nuxt/kit'
2+
import type { Nuxt } from '@nuxt/schema'
3+
import escapeStringRegexp from 'escape-string-regexp'
184

5+
export const addModuleTranspiles = (nuxt: Nuxt) => {
196
// Try to sanitize modules to better match imports
20-
nuxt.options.build.transpile =
21-
nuxt.options.build.transpile.map(m => typeof m === 'string' ? m.split('node_modules/').pop() : m)
22-
.filter(<T>(x: T | undefined): x is T => !!x)
23-
24-
function isTranspilePresent (mod: string) {
25-
return nuxt.options.build.transpile.some(t => !(t instanceof Function) && (t instanceof RegExp ? t.test(mod) : new RegExp(t).test(mod)))
7+
const transpile: RegExp[] = []
8+
for (const t of nuxt.options.build.transpile) {
9+
if (t instanceof Function) {
10+
continue
11+
}
12+
if (typeof t === 'string') {
13+
transpile.push(new RegExp(escapeStringRegexp(t)))
14+
} else {
15+
transpile.push(t)
16+
}
2617
}
2718

28-
// Automatically add used modules to the transpile
29-
for (const module of modules) {
30-
if (!isTranspilePresent(module)) {
31-
nuxt.options.build.transpile.push(module)
19+
for (const m of [...nuxt.options.modules, ...nuxt.options._modules]) {
20+
const mod = typeof m === 'string' ? m : Array.isArray(m) ? m[0] : m.src
21+
if (typeof mod !== 'string') {
22+
continue
23+
}
24+
const path = normalizeModuleTranspilePath(mod)
25+
// Automatically add used modules to the transpile
26+
if (!transpile.some(t => t.test(path))) {
27+
nuxt.options.build.transpile.push(path)
3228
}
3329
}
3430
}

packages/nuxt/src/core/nitro.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,25 @@ const PNPM_NODE_MODULES_RE = /\.pnpm\/.+\/node_modules\/(.+)$/
3434
export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
3535
// Resolve config
3636
const layerDirs = getLayerDirectories(nuxt)
37-
const excludePaths = layerDirs.flatMap(dirs => [
38-
dirs.root.match(NODE_MODULES_RE)?.[1]?.replace(/\/$/, ''),
39-
dirs.root.match(PNPM_NODE_MODULES_RE)?.[1]?.replace(/\/$/, ''),
40-
].filter((dir): dir is string => Boolean(dir)).map(dir => escapeRE(dir)))
37+
const excludePaths: string[] = []
38+
for (const dirs of layerDirs) {
39+
const paths = [
40+
dirs.root.match(NODE_MODULES_RE)?.[1]?.replace(/\/$/, ''),
41+
dirs.root.match(PNPM_NODE_MODULES_RE)?.[1]?.replace(/\/$/, ''),
42+
]
43+
for (const dir of paths) {
44+
if (dir) {
45+
excludePaths.push(escapeRE(dir))
46+
}
47+
}
48+
}
49+
50+
const layerPublicAssetsDirs: Array<{ dir: string }> = []
51+
for (const dirs of layerDirs) {
52+
if (existsSync(dirs.public)) {
53+
layerPublicAssetsDirs.push({ dir: dirs.public })
54+
}
55+
}
4156

4257
const excludePattern = excludePaths.length
4358
? [new RegExp(`node_modules\\/(?!${excludePaths.join('|')})`)]
@@ -155,7 +170,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
155170
join(moduleDir, 'dist/runtime/server'),
156171
]
157172
}),
158-
...getLayerDirectories(nuxt).map(dirs => relativeWithDot(nuxt.options.buildDir, join(dirs.shared, '**/*.d.ts'))),
173+
...layerDirs.map(dirs => relativeWithDot(nuxt.options.buildDir, join(dirs.shared, '**/*.d.ts'))),
159174
],
160175
exclude: [
161176
...nuxt.options.modulesDir.map(m => relativeWithDot(nuxt.options.buildDir, m)),
@@ -172,9 +187,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
172187
maxAge: 31536000 /* 1 year */,
173188
baseURL: nuxt.options.app.buildAssetsDir,
174189
},
175-
...getLayerDirectories(nuxt)
176-
.filter(dirs => existsSync(dirs.public))
177-
.map(dirs => ({ dir: dirs.public })),
190+
...layerPublicAssetsDirs,
178191
],
179192
prerender: {
180193
ignoreUnprefixedPublicAssets: true,
@@ -198,7 +211,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
198211
'nuxt-nightly/dist',
199212
distDir,
200213
// Ensure app config files have auto-imports injected even if they are pure .js files
201-
...getLayerDirectories(nuxt).map(dirs => join(dirs.app, 'app.config')),
214+
...layerDirs.map(dirs => join(dirs.app, 'app.config')),
202215
],
203216
traceInclude: [
204217
// force include files used in generated code from the runtime-compiler

packages/nuxt/src/core/nuxt.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,11 @@ async function initNuxt (nuxt: Nuxt) {
398398
nuxt.options.build.transpile.push('nuxt/app')
399399

400400
// Transpile layers within node_modules
401-
nuxt.options.build.transpile.push(
402-
...layerDirs.filter(i => i.root.includes('node_modules')).map(i => i.root.replace(/\/$/, '')),
403-
)
401+
for (const layer of layerDirs) {
402+
if (layer.root.includes('node_modules')) {
403+
nuxt.options.build.transpile.push(layer.root.replace(/\/$/, ''))
404+
}
405+
}
404406

405407
// Ensure we can resolve dependencies within layers - filtering out local `~~/layers` directories
406408
const locallyScannedLayersDirs = layerDirs.map(l => join(l.root, 'layers/'))
@@ -731,10 +733,15 @@ export default defineNuxtPlugin({
731733
}
732734
})
733735

734-
// Normalize windows transpile paths added by modules
735-
nuxt.options.build.transpile = nuxt.options.build.transpile.map(t => typeof t === 'string' ? normalize(t) : t)
736+
nuxt.options.build.transpile = nuxt.options.build.transpile.map((t) => {
737+
if (typeof t !== 'string') {
738+
return t
739+
}
740+
// Normalize windows transpile paths added by modules
741+
return normalize(t).split('node_modules/').pop()!
742+
})
736743

737-
addModuleTranspiles()
744+
addModuleTranspiles(nuxt)
738745

739746
// Init nitro
740747
await initNitro(nuxt)
@@ -820,11 +827,15 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
820827

821828
// Add core modules
822829
options._modules.push(pagesModule, metaModule, componentsModule)
830+
const importIncludes: RegExp[] = []
831+
for (const layer of options._layers) {
832+
if (layer.cwd && layer.cwd.includes('node_modules')) {
833+
importIncludes.push(new RegExp(`(^|\\/)${escapeRE(layer.cwd.split('node_modules/').pop()!)}(\\/|$)(?!node_modules\\/)`))
834+
}
835+
}
823836
options._modules.push([importsModule, {
824837
transform: {
825-
include: options._layers
826-
.filter(i => i.cwd && i.cwd.includes('node_modules'))
827-
.map(i => new RegExp(`(^|\\/)${escapeRE(i.cwd!.split('node_modules/').pop()!)}(\\/|$)(?!node_modules\\/)`)),
838+
include: importIncludes,
828839
},
829840
}])
830841
options._modules.push(schemaModule)

packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,14 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
328328
})
329329

330330
function normalizeChunks (chunks: (string | undefined)[]) {
331-
return chunks.filter(Boolean).map(i => i!.trim())
331+
const result: string[] = []
332+
for (const _chunk of chunks) {
333+
const chunk = _chunk?.trim()
334+
if (chunk) {
335+
result.push(chunk)
336+
}
337+
}
338+
return result
332339
}
333340

334341
function joinTags (tags: Array<string | undefined>) {

0 commit comments

Comments
 (0)