fix(nuxt, vite): do not use cjs utils to resolve/alias vue (#21837)

This commit is contained in:
Daniel Roe 2023-06-28 14:49:50 +01:00 committed by GitHub
parent 1cc22aa4aa
commit e023c06353
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 50 deletions

1
.gitignore vendored
View File

@ -23,6 +23,7 @@ dist
.vercel .vercel
.netlify .netlify
.output .output
.output-*
.gen .gen
# Junit reports # Junit reports

View File

@ -2,7 +2,7 @@ import { existsSync, promises as fsp, readFileSync } from 'node:fs'
import { join, relative, resolve } from 'pathe' import { join, relative, resolve } from 'pathe'
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack' import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack'
import type { Nitro, NitroConfig } from 'nitropack' import type { Nitro, NitroConfig } from 'nitropack'
import { logger, resolvePath } from '@nuxt/kit' import { logger } from '@nuxt/kit'
import escapeRE from 'escape-string-regexp' import escapeRE from 'escape-string-regexp'
import { defu } from 'defu' import { defu } from 'defu'
import fsExtra from 'fs-extra' import fsExtra from 'fs-extra'
@ -165,13 +165,6 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
] ]
}, },
alias: { alias: {
...nuxt.options.experimental.externalVue
? {}
: {
'vue/compiler-sfc': 'vue/compiler-sfc',
'vue/server-renderer': 'vue/server-renderer',
vue: await resolvePath(`vue/dist/vue.cjs${nuxt.options.dev ? '' : '.prod'}.js`)
},
// Vue 3 mocks // Vue 3 mocks
...nuxt.options.vue.runtimeCompiler || nuxt.options.experimental.externalVue ...nuxt.options.vue.runtimeCompiler || nuxt.options.experimental.externalVue
? {} ? {}

View File

@ -58,7 +58,6 @@ export async function buildClient (ctx: ViteBuildContext) {
// runtime compiler // runtime compiler
'@vue/compiler-sfc', '@vue/compiler-dom', '@vue/compiler-core', '@vue/compiler-ssr' '@vue/compiler-sfc', '@vue/compiler-dom', '@vue/compiler-core', '@vue/compiler-ssr'
] ]
}, },
cacheDir: resolve(ctx.nuxt.options.rootDir, 'node_modules/.cache/vite', 'client'), cacheDir: resolve(ctx.nuxt.options.rootDir, 'node_modules/.cache/vite', 'client'),
build: { build: {

View File

@ -2,7 +2,7 @@ import { resolve } from 'pathe'
import * as vite from 'vite' import * as vite from 'vite'
import vuePlugin from '@vitejs/plugin-vue' import vuePlugin from '@vitejs/plugin-vue'
import viteJsxPlugin from '@vitejs/plugin-vue-jsx' import viteJsxPlugin from '@vitejs/plugin-vue-jsx'
import { logger, resolveModule, resolvePath } from '@nuxt/kit' import { logger, resolvePath } from '@nuxt/kit'
import { joinURL, withTrailingSlash, withoutLeadingSlash } from 'ufo' import { joinURL, withTrailingSlash, withoutLeadingSlash } from 'ufo'
import type { ViteConfig } from '@nuxt/schema' import type { ViteConfig } from '@nuxt/schema'
import type { ViteBuildContext } from './vite' import type { ViteBuildContext } from './vite'
@ -13,7 +13,6 @@ import { writeManifest } from './manifest'
import { transpile } from './utils/transpile' import { transpile } from './utils/transpile'
export async function buildServer (ctx: ViteBuildContext) { export async function buildServer (ctx: ViteBuildContext) {
const _resolve = (id: string) => resolveModule(id, { paths: ctx.nuxt.options.modulesDir })
const helper = ctx.nuxt.options.nitro.imports !== false ? '' : 'globalThis.' const helper = ctx.nuxt.options.nitro.imports !== false ? '' : 'globalThis.'
const entry = ctx.nuxt.options.ssr ? ctx.entry : await resolvePath(resolve(ctx.nuxt.options.appDir, 'entry-spa')) const entry = ctx.nuxt.options.ssr ? ctx.entry : await resolvePath(resolve(ctx.nuxt.options.appDir, 'entry-spa'))
const serverConfig: ViteConfig = vite.mergeConfig(ctx.config, { const serverConfig: ViteConfig = vite.mergeConfig(ctx.config, {
@ -53,23 +52,11 @@ export async function buildServer (ctx: ViteBuildContext) {
}, },
resolve: { resolve: {
alias: { alias: {
'#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/server'), '#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/server')
...ctx.nuxt.options.experimental.externalVue || ctx.nuxt.options.dev
? {}
: {
'@vue/reactivity': _resolve(`@vue/reactivity/dist/reactivity.cjs${ctx.nuxt.options.dev ? '' : '.prod'}.js`),
'@vue/shared': _resolve(`@vue/shared/dist/shared.cjs${ctx.nuxt.options.dev ? '' : '.prod'}.js`),
'vue-router': _resolve(`vue-router/dist/vue-router.cjs${ctx.nuxt.options.dev ? '' : '.prod'}.js`),
'vue/server-renderer': _resolve('vue/server-renderer'),
'vue/compiler-sfc': _resolve('vue/compiler-sfc'),
vue: _resolve(`vue/dist/vue.cjs${ctx.nuxt.options.dev ? '' : '.prod'}.js`)
}
} }
}, },
ssr: { ssr: {
external: ctx.nuxt.options.experimental.externalVue external: ['#internal/nitro', '#internal/nitro/utils'],
? ['#internal/nitro', '#internal/nitro/utils', 'vue', 'vue-router']
: ['#internal/nitro', '#internal/nitro/utils'],
noExternal: [ noExternal: [
...transpile({ isServer: true, isDev: ctx.nuxt.options.dev }), ...transpile({ isServer: true, isDev: ctx.nuxt.options.dev }),
// TODO: Use externality for production (rollup) build // TODO: Use externality for production (rollup) build
@ -88,7 +75,7 @@ export async function buildServer (ctx: ViteBuildContext) {
ssr: true, ssr: true,
rollupOptions: { rollupOptions: {
input: { server: entry }, input: { server: entry },
external: ['#internal/nitro', ...ctx.nuxt.options.experimental.externalVue ? ['vue', 'vue-router'] : []], external: ['#internal/nitro'],
output: { output: {
entryFileNames: '[name].mjs', entryFileNames: '[name].mjs',
format: 'module', format: 'module',

View File

@ -1,41 +1,38 @@
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import fsp from 'node:fs/promises' import fsp from 'node:fs/promises'
import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { beforeAll, describe, expect, it } from 'vitest'
import { execaCommand } from 'execa' import { execaCommand } from 'execa'
import { globby } from 'globby' import { globby } from 'globby'
import { join } from 'pathe' import { join } from 'pathe'
describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM_CI)('minimal nuxt application', () => { describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM_CI)('minimal nuxt application', () => {
const rootDir = fileURLToPath(new URL('./fixtures/minimal', import.meta.url)) const rootDir = fileURLToPath(new URL('./fixtures/minimal', import.meta.url))
const publicDir = join(rootDir, '.output/public')
const serverDir = join(rootDir, '.output/server')
const stats = {
client: { totalBytes: 0, files: [] as string[] },
server: { totalBytes: 0, files: [] as string[] }
}
beforeAll(async () => { beforeAll(async () => {
await execaCommand(`pnpm nuxi build ${rootDir}`) await Promise.all([
execaCommand(`pnpm nuxi build ${rootDir}`, { env: { EXTERNAL_VUE: 'false' } }),
execaCommand(`pnpm nuxi build ${rootDir}`, { env: { EXTERNAL_VUE: 'true' } })
])
}, 120 * 1000) }, 120 * 1000)
afterAll(async () => { // Identical behaviour between inline/external vue options as this should only affect the server build
await fsp.writeFile(join(rootDir, '.output/test-stats.json'), JSON.stringify(stats, null, 2)) for (const outputDir of ['.output', '.output-inline']) {
}) it('default client bundle size', async () => {
const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public'))
it('default client bundle size', async () => { expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"97.2k"')
stats.client = await analyzeSizes('**/*.js', publicDir) expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
expect.soft(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"97.2k"') [
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(` "_nuxt/entry.js",
[ ]
"_nuxt/entry.js", `)
] })
`) }
})
it('default server bundle size', async () => { it('default server bundle size', async () => {
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) const serverDir = join(rootDir, '.output/server')
expect.soft(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"63.9k"')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"63.9k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir) const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2329k"') expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2329k"')
@ -90,6 +87,53 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
] ]
`) `)
}) })
it('default server bundle size (inlined vue modules)', async () => {
const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"370k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"590k"')
const packages = modules.files
.filter(m => m.endsWith('package.json'))
.map(m => m.replace('/package.json', '').replace('node_modules/', ''))
.sort()
expect(packages).toMatchInlineSnapshot(`
[
"@unhead/dom",
"@unhead/shared",
"@unhead/ssr",
"cookie-es",
"debug",
"defu",
"destr",
"devalue",
"h3",
"has-flag",
"hookable",
"http-graceful-shutdown",
"iron-webcrypto",
"klona",
"ms",
"node-fetch-native",
"ofetch",
"ohash",
"pathe",
"radix3",
"scule",
"supports-color",
"ufo",
"uncrypto",
"unctx",
"unenv",
"unhead",
"unstorage",
]
`)
})
}) })
async function analyzeSizes (pattern: string | string[], rootDir: string) { async function analyzeSizes (pattern: string | string[], rootDir: string) {

View File

@ -1,3 +1,14 @@
import { fileURLToPath } from 'node:url'
const testWithInlineVue = process.env.EXTERNAL_VUE === 'false'
export default defineNuxtConfig({ export default defineNuxtConfig({
experimental: {
externalVue: !testWithInlineVue
},
buildDir: testWithInlineVue ? '.nuxt-inline' : '.nuxt',
nitro: {
output: { dir: fileURLToPath(new URL(testWithInlineVue ? './.output-inline' : './.output', import.meta.url)) }
},
sourcemap: false sourcemap: false
}) })