fix(nuxt): resolve shared externals to absolute paths (#31227)

This commit is contained in:
Daniel Roe 2025-03-06 09:59:36 +00:00 committed by GitHub
parent 37ec28c46b
commit 16e09391b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 39 additions and 24 deletions

View File

@ -2,9 +2,11 @@ import { parseNodeModulePath } from 'mlly'
import { resolveModulePath } from 'exsolve'
import { isAbsolute, normalize, resolve } from 'pathe'
import type { Plugin } from 'vite'
import { directoryToURL, resolveAlias } from '@nuxt/kit'
import { directoryToURL, resolveAlias, tryImportModule } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema'
import type { Nitro } from 'nitropack'
import type { PackageJson } from 'pkg-types'
import { pkgDir } from '../../dirs'
import { logger } from '../../utils'
@ -13,10 +15,12 @@ const VIRTUAL_RE = /^\0?virtual:(?:nuxt:)?/
export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
const exclude: string[] = ['virtual:', '\0virtual:', '/__skip_vite', '@vitest/']
let conditions: string[]
let external: Set<string>
return {
name: 'nuxt:resolve-bare-imports',
enforce: 'post',
configResolved (config) {
async configResolved (config) {
const resolvedConditions = new Set([nuxt.options.dev ? 'development' : 'production', ...config.resolve.conditions])
if (resolvedConditions.has('browser')) {
resolvedConditions.add('web')
@ -29,12 +33,27 @@ export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
resolvedConditions.add('require')
}
conditions = [...resolvedConditions]
const runtimeDependencies = await tryImportModule<PackageJson>('nitropack/package.json', {
url: new URL(import.meta.url),
})?.then(r => r?.dependencies ? Object.keys(r.dependencies) : []).catch(() => []) || []
external = new Set([
// explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build
'unhead', '@unhead/vue', 'unctx', 'h3', 'devalue', '@nuxt/devalue', 'radix3', 'rou3', 'unstorage', 'hookable',
// ensure we only have one version of vue if nitro is going to inline anyway
...((nuxt as any)._nitro as Nitro).options.inlineDynamicImports ? ['vue', '@vue/server-renderer', '@unhead/vue'] : [],
// dependencies we might share with nitro - these can be inlined (if necessary) in the nitro build
...runtimeDependencies,
])
},
async resolveId (id, importer) {
if (!importer || isAbsolute(id) || (!isAbsolute(importer) && !VIRTUAL_RE.test(importer)) || exclude.some(e => id.startsWith(e))) {
return
}
const overrides = external.has(id) ? { external: 'absolute' } as const : {}
const normalisedId = resolveAlias(normalize(id), nuxt.options.alias)
const isNuxtTemplate = importer.startsWith('virtual:nuxt')
const normalisedImporter = (isNuxtTemplate ? decodeURIComponent(importer) : importer).replace(VIRTUAL_RE, '')
@ -44,7 +63,10 @@ export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
if (template?._path) {
const res = await this.resolve?.(normalisedId, template._path, { skipSelf: true })
if (res !== undefined && res !== null) {
return res
return {
...res,
...overrides,
}
}
}
}
@ -53,7 +75,10 @@ export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
const res = await this.resolve?.(normalisedId, dir, { skipSelf: true })
if (res !== undefined && res !== null) {
return res
return {
...res,
...overrides,
}
}
const path = resolveModulePath(id, {
@ -68,6 +93,13 @@ export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
return null
}
if (external.has(id)) {
return {
id: normalize(path),
external: 'absolute',
}
}
return normalize(path)
},
}

View File

@ -2,10 +2,9 @@ import { resolve } from 'pathe'
import * as vite from 'vite'
import vuePlugin from '@vitejs/plugin-vue'
import viteJsxPlugin from '@vitejs/plugin-vue-jsx'
import { directoryToURL, logger, resolvePath, tryImportModule } from '@nuxt/kit'
import { logger, resolvePath } from '@nuxt/kit'
import { joinURL, withTrailingSlash, withoutLeadingSlash } from 'ufo'
import type { ViteConfig } from '@nuxt/schema'
import type { PackageJson } from 'pkg-types'
import defu from 'defu'
import type { Nitro } from 'nitropack'
import escapeStringRegexp from 'escape-string-regexp'
@ -115,22 +114,6 @@ export async function buildServer (ctx: ViteBuildContext) {
},
} satisfies vite.InlineConfig, ctx.nuxt.options.vite.$server || {}))
if (!ctx.nuxt.options.dev) {
const runtimeDependencies = await tryImportModule<PackageJson>('nitropack/package.json', {
url: ctx.nuxt.options.modulesDir.map(d => directoryToURL(d)),
})?.then(r => r?.dependencies ? Object.keys(r.dependencies) : []).catch(() => []) || []
if (Array.isArray(serverConfig.ssr!.external)) {
serverConfig.ssr!.external.push(
// explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build
'unhead', '@unhead/vue', 'unctx', 'h3', 'devalue', '@nuxt/devalue', 'radix3', 'rou3', 'unstorage', 'hookable',
// ensure we only have one version of vue if nitro is going to inline anyway
...((ctx.nuxt as any)._nitro as Nitro).options.inlineDynamicImports ? ['vue', '@vue/server-renderer', '@unhead/vue'] : [],
// dependencies we might share with nitro - these can be inlined (if necessary) in the nitro build
...runtimeDependencies,
)
}
}
// tell rollup's nitro build about the original sources of the generated vite server build
if (ctx.nuxt.options.sourcemap.server && !ctx.nuxt.options.dev) {
const { vitePlugin, nitroPlugin } = createSourcemapPreserver()

View File

@ -37,7 +37,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"197k"`)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`)
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1384k"`)
@ -74,7 +74,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"547k"`)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"559k"`)
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"77.8k"`)