diff --git a/packages/vite/src/dev-bundler.ts b/packages/vite/src/dev-bundler.ts index 9d841558a1f..81f0be8c3f4 100644 --- a/packages/vite/src/dev-bundler.ts +++ b/packages/vite/src/dev-bundler.ts @@ -1,11 +1,17 @@ import { pathToFileURL } from 'node:url' import { existsSync } from 'node:fs' import { builtinModules } from 'node:module' -import { isAbsolute, resolve } from 'pathe' +import { isAbsolute, normalize, resolve } from 'pathe' import * as vite from 'vite' -import { ExternalsOptions, isExternal as _isExternal, ExternalsDefaults } from 'externality' +import { isExternal } from 'externality' import { genDynamicImport, genObjectFromRawEntries } from 'knitwork' -import { hashId, uniq } from './utils' +import fse from 'fs-extra' +import { debounce } from 'perfect-debounce' +import { isIgnored, logger } from '@nuxt/kit' +import { hashId, isCSS, uniq } from './utils' +import { createIsExternal } from './utils/external' +import { writeManifest } from './manifest' +import { ViteBuildContext } from './vite' export interface TransformChunk { id: string, @@ -23,29 +29,7 @@ export interface SSRTransformResult { export interface TransformOptions { viteServer: vite.ViteDevServer -} - -function isExternal (opts: TransformOptions, id: string) { - // Externals - const ssrConfig = (opts.viteServer.config as any).ssr - - const externalOpts: ExternalsOptions = { - inline: [ - /virtual:/, - /\.ts$/, - ...ExternalsDefaults.inline, - ...ssrConfig.noExternal - ], - external: [ - ...ssrConfig.external, - /node_modules/ - ], - resolve: { - type: 'module', - extensions: ['.ts', '.js', '.json', '.vue', '.mjs', '.jsx', '.tsx', '.wasm'] - } - } - return _isExternal(id, opts.viteServer.config.root, externalOpts) + isExternal(id: string): ReturnType } async function transformRequest (opts: TransformOptions, id: string) { @@ -74,7 +58,7 @@ async function transformRequest (opts: TransformOptions, id: string) { // Vite will add ?v=123 to bypass browser cache // Remove for externals const withoutVersionQuery = id.replace(/\?v=\w+$/, '') - if (await isExternal(opts, withoutVersionQuery)) { + if (await opts.isExternal(withoutVersionQuery)) { const path = builtinModules.includes(withoutVersionQuery.split('node:').pop()) ? withoutVersionQuery : isAbsolute(withoutVersionQuery) ? pathToFileURL(withoutVersionQuery).href : withoutVersionQuery @@ -245,3 +229,36 @@ async function __instantiateModule__(url, urlStack) { ids: chunks.map(i => i.id) } } + +export async function initViteDevBundler (ctx: ViteBuildContext, onBuild: () => Promise) { + const viteServer = ctx.ssrServer + const options: TransformOptions = { + viteServer, + isExternal: createIsExternal(viteServer, ctx.nuxt.options.rootDir) + } + + // Build and watch + const _doBuild = async () => { + const start = Date.now() + const { code, ids } = await bundleRequest(options, resolve(ctx.nuxt.options.appDir, 'entry')) + await fse.writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'), code, 'utf-8') + // Have CSS in the manifest to prevent FOUC on dev SSR + await writeManifest(ctx, ids.filter(isCSS).map(i => i.slice(1))) + const time = (Date.now() - start) + logger.success(`Vite server built in ${time}ms`) + await onBuild() + } + const doBuild = debounce(_doBuild) + + // Initial build + await _doBuild() + + // Watch + viteServer.watcher.on('all', (_event, file) => { + file = normalize(file) // Fix windows paths + if (file.indexOf(ctx.nuxt.options.buildDir) === 0 || isIgnored(file)) { return } + doBuild() + }) + // ctx.nuxt.hook('builder:watch', () => doBuild()) + ctx.nuxt.hook('app:templatesGenerated', () => doBuild()) +} diff --git a/packages/vite/src/server.ts b/packages/vite/src/server.ts index ea0c1ce5bc0..aed7ff4c8cf 100644 --- a/packages/vite/src/server.ts +++ b/packages/vite/src/server.ts @@ -1,22 +1,16 @@ import { resolveTSConfig } from 'pkg-types' -import { resolve, normalize } from 'pathe' +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, isIgnored } from '@nuxt/kit' -import fse from 'fs-extra' -import { debounce } from 'perfect-debounce' +import { logger, resolveModule } from '@nuxt/kit' import { joinURL, withoutLeadingSlash, withTrailingSlash } from 'ufo' import { ViteBuildContext, ViteOptions } from './vite' import { wpfs } from './utils/wpfs' import { cacheDirPlugin } from './plugins/cache-dir' -import { prepareDevServerEntry } from './vite-node' -import { isCSS } from './utils' -import { bundleRequest } from './dev-bundler' -import { writeManifest } from './manifest' export async function buildServer (ctx: ViteBuildContext) { - const _resolve = id => resolveModule(id, { paths: ctx.nuxt.options.modulesDir }) + const _resolve = (id: string) => resolveModule(id, { paths: ctx.nuxt.options.modulesDir }) const serverConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, { base: ctx.nuxt.options.dev ? joinURL(ctx.nuxt.options.app.baseURL.replace(/^\.\//, '/') || '/', ctx.nuxt.options.app.buildAssetsDir) @@ -149,31 +143,8 @@ export async function buildServer (ctx: ViteBuildContext) { if (ctx.nuxt.options.experimental.viteNode) { logger.info('Vite server using experimental `vite-node`...') - await prepareDevServerEntry(ctx) + await import('./vite-node').then(r => r.initViteNodeServer(ctx)) } else { - // Build and watch - const _doBuild = async () => { - const start = Date.now() - const { code, ids } = await bundleRequest({ viteServer }, resolve(ctx.nuxt.options.appDir, 'entry')) - await fse.writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'), code, 'utf-8') - // Have CSS in the manifest to prevent FOUC on dev SSR - await writeManifest(ctx, ids.filter(isCSS).map(i => i.slice(1))) - const time = (Date.now() - start) - logger.success(`Vite server built in ${time}ms`) - await onBuild() - } - const doBuild = debounce(_doBuild) - - // Initial build - await _doBuild() - - // Watch - viteServer.watcher.on('all', (_event, file) => { - file = normalize(file) // Fix windows paths - if (file.indexOf(ctx.nuxt.options.buildDir) === 0 || isIgnored(file)) { return } - doBuild() - }) - // ctx.nuxt.hook('builder:watch', () => doBuild()) - ctx.nuxt.hook('app:templatesGenerated', () => doBuild()) + await import('./dev-bundler').then(r => r.initViteDevBundler(ctx, onBuild)) } } diff --git a/packages/vite/src/utils/external.ts b/packages/vite/src/utils/external.ts new file mode 100644 index 00000000000..09764a33930 --- /dev/null +++ b/packages/vite/src/utils/external.ts @@ -0,0 +1,23 @@ +import { ExternalsOptions, ExternalsDefaults, isExternal } from 'externality' +import { ViteDevServer } from 'vite' + +export function createIsExternal (viteServer: ViteDevServer, rootDir: string) { + const externalOpts: ExternalsOptions = { + inline: [ + /virtual:/, + /\.ts$/, + ...ExternalsDefaults.inline, + ...viteServer.config.ssr.noExternal as string[] + ], + external: [ + ...viteServer.config.ssr.external, + /node_modules/ + ], + resolve: { + type: 'module', + extensions: ['.ts', '.js', '.json', '.vue', '.mjs', '.jsx', '.tsx', '.wasm'] + } + } + + return (id:string) => isExternal(id, rootDir, externalOpts) +} diff --git a/packages/vite/src/vite-node.ts b/packages/vite/src/vite-node.ts index 160213be4e5..7efb545f615 100644 --- a/packages/vite/src/vite-node.ts +++ b/packages/vite/src/vite-node.ts @@ -5,11 +5,11 @@ import fse from 'fs-extra' import { resolve } from 'pathe' import { addServerMiddleware } from '@nuxt/kit' import type { Plugin as VitePlugin, ViteDevServer } from 'vite' -import { ExternalsOptions, isExternal, ExternalsDefaults } from 'externality' import { resolve as resolveModule } from 'mlly' import { distDir } from './dirs' import type { ViteBuildContext } from './vite' import { isCSS } from './utils' +import { createIsExternal } from './utils/external' // TODO: Remove this in favor of registerViteNodeMiddleware // after Nitropack or h3 fixed for adding middlewares after setup @@ -72,27 +72,11 @@ function createViteNodeMiddleware (ctx: ViteBuildContext) { web: [] } }) - const externalOpts: ExternalsOptions = { - inline: [ - /virtual:/, - /\.ts$/, - ...ExternalsDefaults.inline, - ...viteServer.config.ssr.noExternal as string[] - ], - external: [ - ...viteServer.config.ssr.external, - /node_modules/ - ], - resolve: { - type: 'module', - extensions: ['.ts', '.js', '.json', '.vue', '.mjs', '.jsx', '.tsx', '.wasm'] - } - } - const rootDir = ctx.nuxt.options.rootDir + const isExternal = createIsExternal(viteServer, ctx.nuxt.options.rootDir) node.shouldExternalize = async (id: string) => { - const result = await isExternal(id, rootDir, externalOpts) + const result = await isExternal(id) if (result?.external) { - return resolveModule(result.id, { url: rootDir }) + return resolveModule(result.id, { url: ctx.nuxt.options.rootDir }) } return false } @@ -110,7 +94,7 @@ function createViteNodeMiddleware (ctx: ViteBuildContext) { return app.nodeHandler } -export async function prepareDevServerEntry (ctx: ViteBuildContext) { +export async function initViteNodeServer (ctx: ViteBuildContext) { let entryPath = resolve(ctx.nuxt.options.appDir, 'entry.async.mjs') if (!fse.existsSync(entryPath)) { entryPath = resolve(ctx.nuxt.options.appDir, 'entry.async')