diff --git a/packages/nuxt/src/core/plugins/resolve-deep-imports.ts b/packages/nuxt/src/core/plugins/resolve-deep-imports.ts index 949573559d..2af0062c7c 100644 --- a/packages/nuxt/src/core/plugins/resolve-deep-imports.ts +++ b/packages/nuxt/src/core/plugins/resolve-deep-imports.ts @@ -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 + 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('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) }, } diff --git a/packages/vite/src/server.ts b/packages/vite/src/server.ts index 90c23a129d..76b3fce2a4 100644 --- a/packages/vite/src/server.ts +++ b/packages/vite/src/server.ts @@ -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('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() diff --git a/test/bundle.test.ts b/test/bundle.test.ts index 5582d2c149..0cd70b986d 100644 --- a/test/bundle.test.ts +++ b/test/bundle.test.ts @@ -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"`)