From e023c0635366e87a66c90470081c110ae7ef6595 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 28 Jun 2023 14:49:50 +0100 Subject: [PATCH] fix(nuxt, vite): do not use cjs utils to resolve/alias vue (#21837) --- .gitignore | 1 + packages/nuxt/src/core/nitro.ts | 9 +-- packages/vite/src/client.ts | 1 - packages/vite/src/server.ts | 21 ++----- test/bundle.test.ts | 92 ++++++++++++++++++++-------- test/fixtures/minimal/nuxt.config.ts | 11 ++++ 6 files changed, 85 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index f074d86ef3..621e946021 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ dist .vercel .netlify .output +.output-* .gen # Junit reports diff --git a/packages/nuxt/src/core/nitro.ts b/packages/nuxt/src/core/nitro.ts index 98a41994c9..a18148fe23 100644 --- a/packages/nuxt/src/core/nitro.ts +++ b/packages/nuxt/src/core/nitro.ts @@ -2,7 +2,7 @@ import { existsSync, promises as fsp, readFileSync } from 'node:fs' import { join, relative, resolve } from 'pathe' import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } 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 { defu } from 'defu' import fsExtra from 'fs-extra' @@ -165,13 +165,6 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) { ] }, 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 ...nuxt.options.vue.runtimeCompiler || nuxt.options.experimental.externalVue ? {} diff --git a/packages/vite/src/client.ts b/packages/vite/src/client.ts index 4dc963d133..065cc441b6 100644 --- a/packages/vite/src/client.ts +++ b/packages/vite/src/client.ts @@ -58,7 +58,6 @@ export async function buildClient (ctx: ViteBuildContext) { // runtime compiler '@vue/compiler-sfc', '@vue/compiler-dom', '@vue/compiler-core', '@vue/compiler-ssr' ] - }, cacheDir: resolve(ctx.nuxt.options.rootDir, 'node_modules/.cache/vite', 'client'), build: { diff --git a/packages/vite/src/server.ts b/packages/vite/src/server.ts index 8c1886e3e7..6a1b1d60c6 100644 --- a/packages/vite/src/server.ts +++ b/packages/vite/src/server.ts @@ -2,7 +2,7 @@ import { resolve } from 'pathe' import * as vite from 'vite' import vuePlugin from '@vitejs/plugin-vue' 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 type { ViteConfig } from '@nuxt/schema' import type { ViteBuildContext } from './vite' @@ -13,7 +13,6 @@ import { writeManifest } from './manifest' import { transpile } from './utils/transpile' 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 entry = ctx.nuxt.options.ssr ? ctx.entry : await resolvePath(resolve(ctx.nuxt.options.appDir, 'entry-spa')) const serverConfig: ViteConfig = vite.mergeConfig(ctx.config, { @@ -53,23 +52,11 @@ export async function buildServer (ctx: ViteBuildContext) { }, resolve: { alias: { - '#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`) - } + '#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/server') } }, ssr: { - external: ctx.nuxt.options.experimental.externalVue - ? ['#internal/nitro', '#internal/nitro/utils', 'vue', 'vue-router'] - : ['#internal/nitro', '#internal/nitro/utils'], + external: ['#internal/nitro', '#internal/nitro/utils'], noExternal: [ ...transpile({ isServer: true, isDev: ctx.nuxt.options.dev }), // TODO: Use externality for production (rollup) build @@ -88,7 +75,7 @@ export async function buildServer (ctx: ViteBuildContext) { ssr: true, rollupOptions: { input: { server: entry }, - external: ['#internal/nitro', ...ctx.nuxt.options.experimental.externalVue ? ['vue', 'vue-router'] : []], + external: ['#internal/nitro'], output: { entryFileNames: '[name].mjs', format: 'module', diff --git a/test/bundle.test.ts b/test/bundle.test.ts index 72a4215dc8..9eea8a1910 100644 --- a/test/bundle.test.ts +++ b/test/bundle.test.ts @@ -1,41 +1,38 @@ import { fileURLToPath } from 'node:url' 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 { globby } from 'globby' import { join } from 'pathe' 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 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 () => { - 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) - afterAll(async () => { - await fsp.writeFile(join(rootDir, '.output/test-stats.json'), JSON.stringify(stats, null, 2)) - }) - - it('default client bundle size', async () => { - stats.client = await analyzeSizes('**/*.js', publicDir) - expect.soft(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"97.2k"') - expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(` - [ - "_nuxt/entry.js", - ] - `) - }) + // Identical behaviour between inline/external vue options as this should only affect the server build + for (const outputDir of ['.output', '.output-inline']) { + it('default client bundle size', async () => { + const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public')) + expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"97.2k"') + expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(` + [ + "_nuxt/entry.js", + ] + `) + }) + } it('default server bundle size', async () => { - stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) - expect.soft(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"63.9k"') + const serverDir = join(rootDir, '.output/server') + + const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) + expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"63.9k"') const modules = await analyzeSizes('node_modules/**/*', serverDir) 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) { diff --git a/test/fixtures/minimal/nuxt.config.ts b/test/fixtures/minimal/nuxt.config.ts index fd9728f99a..c7e627e35e 100644 --- a/test/fixtures/minimal/nuxt.config.ts +++ b/test/fixtures/minimal/nuxt.config.ts @@ -1,3 +1,14 @@ +import { fileURLToPath } from 'node:url' + +const testWithInlineVue = process.env.EXTERNAL_VUE === 'false' + 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 })