2023-05-10 12:45:49 +00:00
|
|
|
import { existsSync } from 'node:fs'
|
2021-01-22 22:02:33 +00:00
|
|
|
import * as vite from 'vite'
|
2024-02-13 09:54:13 +00:00
|
|
|
import { dirname, join, normalize, resolve } from 'pathe'
|
2023-08-08 11:33:10 +00:00
|
|
|
import type { Nuxt, NuxtBuilder, ViteConfig } from '@nuxt/schema'
|
2024-07-02 17:01:52 +00:00
|
|
|
import { addVitePlugin, isIgnored, logger, resolvePath, useNitro } from '@nuxt/kit'
|
2022-05-13 10:49:30 +00:00
|
|
|
import replace from '@rollup/plugin-replace'
|
2024-06-13 22:35:00 +00:00
|
|
|
import type { RollupReplaceOptions } from '@rollup/plugin-replace'
|
2021-11-03 13:04:42 +00:00
|
|
|
import { sanitizeFilePath } from 'mlly'
|
2022-09-12 10:12:41 +00:00
|
|
|
import { withoutLeadingSlash } from 'ufo'
|
|
|
|
import { filename } from 'pathe/utils'
|
2022-10-03 13:37:36 +00:00
|
|
|
import { resolveTSConfig } from 'pkg-types'
|
2023-08-08 11:33:10 +00:00
|
|
|
|
2021-05-24 11:14:10 +00:00
|
|
|
import { buildClient } from './client'
|
|
|
|
import { buildServer } from './server'
|
|
|
|
import { warmupViteServer } from './utils/warmup'
|
2021-10-13 20:08:26 +00:00
|
|
|
import { resolveCSSOptions } from './css'
|
2023-03-08 11:56:41 +00:00
|
|
|
import { logLevelMap } from './utils/logger'
|
2023-06-20 18:28:44 +00:00
|
|
|
import { ssrStylesPlugin } from './plugins/ssr-styles'
|
2024-03-14 00:18:44 +00:00
|
|
|
import { VitePublicDirsPlugin } from './plugins/public-dirs'
|
2024-07-02 18:28:48 +00:00
|
|
|
import { distDir } from './dirs'
|
2021-04-29 11:51:54 +00:00
|
|
|
|
|
|
|
export interface ViteBuildContext {
|
2021-01-22 22:02:33 +00:00
|
|
|
nuxt: Nuxt
|
2023-03-29 10:59:57 +00:00
|
|
|
config: ViteConfig
|
2022-08-04 15:24:35 +00:00
|
|
|
entry: string
|
2022-03-11 08:41:27 +00:00
|
|
|
clientServer?: vite.ViteDevServer
|
|
|
|
ssrServer?: vite.ViteDevServer
|
2021-01-22 22:02:33 +00:00
|
|
|
}
|
|
|
|
|
2023-08-08 11:33:10 +00:00
|
|
|
export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
|
2024-06-19 12:57:08 +00:00
|
|
|
const useAsyncEntry = nuxt.options.experimental.asyncEntry || nuxt.options.dev
|
2022-09-09 09:54:20 +00:00
|
|
|
const entry = await resolvePath(resolve(nuxt.options.appDir, useAsyncEntry ? 'entry.async' : 'entry'))
|
2023-05-10 12:45:49 +00:00
|
|
|
|
2024-07-02 18:28:48 +00:00
|
|
|
nuxt.options.modulesDir.push(distDir)
|
|
|
|
|
2023-05-10 12:45:49 +00:00
|
|
|
let allowDirs = [
|
|
|
|
nuxt.options.appDir,
|
|
|
|
nuxt.options.workspaceDir,
|
|
|
|
...nuxt.options._layers.map(l => l.config.rootDir),
|
|
|
|
...Object.values(nuxt.apps).flatMap(app => [
|
|
|
|
...app.components.map(c => dirname(c.filePath)),
|
|
|
|
...app.plugins.map(p => dirname(p.src)),
|
|
|
|
...app.middleware.map(m => dirname(m.path)),
|
|
|
|
...Object.values(app.layouts || {}).map(l => dirname(l.file)),
|
2024-06-27 14:27:08 +00:00
|
|
|
dirname(nuxt.apps.default!.rootComponent!),
|
|
|
|
dirname(nuxt.apps.default!.errorComponent!),
|
2024-04-05 18:08:32 +00:00
|
|
|
]),
|
2023-05-10 12:45:49 +00:00
|
|
|
].filter(d => d && existsSync(d))
|
|
|
|
|
|
|
|
for (const dir of allowDirs) {
|
|
|
|
allowDirs = allowDirs.filter(d => !d.startsWith(dir) || d === dir)
|
|
|
|
}
|
|
|
|
|
2023-07-24 17:32:12 +00:00
|
|
|
const { $client, $server, ...viteConfig } = nuxt.options.vite
|
|
|
|
|
2021-01-22 22:02:33 +00:00
|
|
|
const ctx: ViteBuildContext = {
|
|
|
|
nuxt,
|
2022-09-09 09:54:20 +00:00
|
|
|
entry,
|
2021-04-29 11:51:54 +00:00
|
|
|
config: vite.mergeConfig(
|
|
|
|
{
|
2023-03-07 12:18:47 +00:00
|
|
|
logLevel: logLevelMap[nuxt.options.logLevel] ?? logLevelMap.info,
|
2021-04-29 11:51:54 +00:00
|
|
|
resolve: {
|
|
|
|
alias: {
|
|
|
|
...nuxt.options.alias,
|
|
|
|
'#app': nuxt.options.appDir,
|
|
|
|
'web-streams-polyfill/ponyfill/es2018': 'unenv/runtime/mock/empty',
|
|
|
|
// Cannot destructure property 'AbortController' of ..
|
2024-04-05 18:08:32 +00:00
|
|
|
'abort-controller': 'unenv/runtime/mock/empty',
|
|
|
|
},
|
2021-04-29 11:51:54 +00:00
|
|
|
},
|
2024-07-02 18:28:48 +00:00
|
|
|
css: await resolveCSSOptions(nuxt),
|
2023-08-07 22:57:35 +00:00
|
|
|
define: {
|
|
|
|
__NUXT_VERSION__: JSON.stringify(nuxt._version),
|
2024-04-05 18:08:32 +00:00
|
|
|
__NUXT_ASYNC_CONTEXT__: nuxt.options.experimental.asyncContext,
|
2023-08-07 22:57:35 +00:00
|
|
|
},
|
2021-04-29 11:51:54 +00:00
|
|
|
build: {
|
2023-01-10 11:51:53 +00:00
|
|
|
copyPublicDir: false,
|
2021-07-15 10:18:34 +00:00
|
|
|
rollupOptions: {
|
2022-09-12 10:12:41 +00:00
|
|
|
output: {
|
2023-03-02 15:28:15 +00:00
|
|
|
sourcemapIgnoreList: (relativeSourcePath) => {
|
2023-03-03 10:35:25 +00:00
|
|
|
return relativeSourcePath.includes('node_modules') || relativeSourcePath.includes(ctx.nuxt.options.buildDir)
|
2023-03-02 15:28:15 +00:00
|
|
|
},
|
2022-09-12 10:12:41 +00:00
|
|
|
sanitizeFileName: sanitizeFilePath,
|
|
|
|
// https://github.com/vitejs/vite/tree/main/packages/vite/src/node/build.ts#L464-L478
|
|
|
|
assetFileNames: nuxt.options.dev
|
|
|
|
? undefined
|
2024-04-05 18:08:32 +00:00
|
|
|
: chunk => withoutLeadingSlash(join(nuxt.options.app.buildAssetsDir, `${sanitizeFilePath(filename(chunk.name!))}.[hash].[ext]`)),
|
|
|
|
},
|
2022-07-17 15:10:27 +00:00
|
|
|
},
|
|
|
|
watch: {
|
2024-04-05 18:08:32 +00:00
|
|
|
exclude: nuxt.options.ignore,
|
|
|
|
},
|
2021-04-29 11:51:54 +00:00
|
|
|
},
|
2021-07-15 10:18:34 +00:00
|
|
|
plugins: [
|
2024-03-14 00:18:44 +00:00
|
|
|
// add resolver for files in public assets directories
|
2024-08-09 12:46:38 +00:00
|
|
|
VitePublicDirsPlugin.vite({
|
|
|
|
dev: nuxt.options.dev,
|
|
|
|
sourcemap: !!nuxt.options.sourcemap.server,
|
|
|
|
baseURL: nuxt.options.app.baseURL,
|
|
|
|
}),
|
2024-06-13 22:35:00 +00:00
|
|
|
replace({ preventAssignment: true, ...globalThisReplacements }),
|
2021-07-15 10:18:34 +00:00
|
|
|
],
|
2021-07-14 14:37:07 +00:00
|
|
|
server: {
|
2022-06-22 18:07:54 +00:00
|
|
|
watch: { ignored: isIgnored },
|
2021-07-14 14:37:07 +00:00
|
|
|
fs: {
|
2024-04-05 18:08:32 +00:00
|
|
|
allow: [...new Set(allowDirs)],
|
|
|
|
},
|
|
|
|
},
|
2023-03-29 10:59:57 +00:00
|
|
|
} satisfies ViteConfig,
|
2024-04-05 18:08:32 +00:00
|
|
|
viteConfig,
|
|
|
|
),
|
2021-01-22 22:02:33 +00:00
|
|
|
}
|
|
|
|
|
2022-06-22 18:07:54 +00:00
|
|
|
// In build mode we explicitly override any vite options that vite is relying on
|
|
|
|
// to detect whether to inject production or development code (such as HMR code)
|
|
|
|
if (!nuxt.options.dev) {
|
2022-08-15 13:40:06 +00:00
|
|
|
ctx.config.server!.watch = undefined
|
|
|
|
ctx.config.build!.watch = undefined
|
2022-06-22 18:07:54 +00:00
|
|
|
}
|
|
|
|
|
2024-07-02 18:26:58 +00:00
|
|
|
// TODO: this may no longer be needed with most recent vite version
|
2024-02-13 09:54:13 +00:00
|
|
|
if (nuxt.options.dev) {
|
|
|
|
// Identify which layers will need to have an extra resolve step.
|
|
|
|
const layerDirs: string[] = []
|
|
|
|
const delimitedRootDir = nuxt.options.rootDir + '/'
|
|
|
|
for (const layer of nuxt.options._layers) {
|
|
|
|
if (layer.config.srcDir !== nuxt.options.srcDir && !layer.config.srcDir.startsWith(delimitedRootDir)) {
|
|
|
|
layerDirs.push(layer.config.srcDir + '/')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (layerDirs.length > 0) {
|
2024-07-02 18:26:58 +00:00
|
|
|
// Reverse so longest/most specific directories are searched first
|
|
|
|
layerDirs.sort().reverse()
|
|
|
|
ctx.nuxt.hook('vite:extendConfig', (config) => {
|
|
|
|
const dirs = [...layerDirs]
|
|
|
|
config.plugins!.push({
|
|
|
|
name: 'nuxt:optimize-layer-deps',
|
|
|
|
enforce: 'pre',
|
|
|
|
async resolveId (source, _importer) {
|
|
|
|
if (!_importer || !dirs.length) { return }
|
|
|
|
const importer = normalize(_importer)
|
|
|
|
const layerIndex = dirs.findIndex(dir => importer.startsWith(dir))
|
2024-02-13 09:54:13 +00:00
|
|
|
// Trigger vite to optimize dependencies imported within a layer, just as if they were imported in final project
|
2024-07-02 18:26:58 +00:00
|
|
|
if (layerIndex !== -1) {
|
|
|
|
dirs.splice(layerIndex, 1)
|
|
|
|
await this.resolve(source, join(nuxt.options.srcDir, 'index.html'), { skipSelf: true }).catch(() => null)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2024-02-13 09:54:13 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-03 13:37:36 +00:00
|
|
|
// Add type-checking
|
2023-12-05 17:09:46 +00:00
|
|
|
if (!ctx.nuxt.options.test && (ctx.nuxt.options.typescript.typeCheck === true || (ctx.nuxt.options.typescript.typeCheck === 'build' && !ctx.nuxt.options.dev))) {
|
2022-10-03 13:37:36 +00:00
|
|
|
const checker = await import('vite-plugin-checker').then(r => r.default)
|
|
|
|
addVitePlugin(checker({
|
|
|
|
vueTsc: {
|
2024-04-05 18:08:32 +00:00
|
|
|
tsconfigPath: await resolveTSConfig(ctx.nuxt.options.rootDir),
|
|
|
|
},
|
2023-06-05 14:58:18 +00:00
|
|
|
}), { server: nuxt.options.ssr })
|
2022-10-03 13:37:36 +00:00
|
|
|
}
|
|
|
|
|
2023-06-05 14:58:18 +00:00
|
|
|
await nuxt.callHook('vite:extend', ctx)
|
|
|
|
|
2023-08-07 22:03:40 +00:00
|
|
|
nuxt.hook('vite:extendConfig', (config) => {
|
2024-06-13 22:35:00 +00:00
|
|
|
const replaceOptions: RollupReplaceOptions = Object.create(null)
|
|
|
|
replaceOptions.preventAssignment = true
|
|
|
|
|
|
|
|
for (const key in config.define!) {
|
|
|
|
if (key.startsWith('import.meta.')) {
|
|
|
|
replaceOptions[key] = config.define![key]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
config.plugins!.push(replace(replaceOptions))
|
2023-08-07 22:03:40 +00:00
|
|
|
})
|
|
|
|
|
2023-06-20 18:28:44 +00:00
|
|
|
if (!ctx.nuxt.options.dev) {
|
|
|
|
const chunksWithInlinedCSS = new Set<string>()
|
|
|
|
const clientCSSMap = {}
|
|
|
|
|
|
|
|
nuxt.hook('vite:extendConfig', (config, { isServer }) => {
|
|
|
|
config.plugins!.push(ssrStylesPlugin({
|
|
|
|
srcDir: ctx.nuxt.options.srcDir,
|
|
|
|
clientCSSMap,
|
|
|
|
chunksWithInlinedCSS,
|
2023-12-25 14:03:29 +00:00
|
|
|
shouldInline: ctx.nuxt.options.features.inlineStyles,
|
2024-06-27 14:27:08 +00:00
|
|
|
components: ctx.nuxt.apps.default!.components || [],
|
2023-06-20 18:28:44 +00:00
|
|
|
globalCSS: ctx.nuxt.options.css,
|
|
|
|
mode: isServer ? 'server' : 'client',
|
2024-04-05 18:08:32 +00:00
|
|
|
entry: ctx.entry,
|
2023-06-20 18:28:44 +00:00
|
|
|
}))
|
|
|
|
})
|
|
|
|
|
|
|
|
// Remove CSS entries for files that will have inlined styles
|
|
|
|
ctx.nuxt.hook('build:manifest', (manifest) => {
|
2024-09-18 19:41:53 +00:00
|
|
|
for (const [key, entry] of Object.entries(manifest)) {
|
2023-06-20 18:28:44 +00:00
|
|
|
const shouldRemoveCSS = chunksWithInlinedCSS.has(key) && !entry.isEntry
|
2023-06-30 04:25:43 +00:00
|
|
|
if (entry.isEntry && chunksWithInlinedCSS.has(key)) {
|
|
|
|
// @ts-expect-error internal key
|
|
|
|
entry._globalCSS = true
|
|
|
|
}
|
2023-06-20 18:28:44 +00:00
|
|
|
if (shouldRemoveCSS && entry.css) {
|
|
|
|
entry.css = []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-03-14 10:19:37 +00:00
|
|
|
nuxt.hook('vite:serverCreated', (server: vite.ViteDevServer, env) => {
|
2022-02-08 00:10:42 +00:00
|
|
|
// Invalidate virtual modules when templates are re-generated
|
|
|
|
ctx.nuxt.hook('app:templatesGenerated', () => {
|
|
|
|
for (const [id, mod] of server.moduleGraph.idToModuleMap) {
|
2024-10-09 12:58:05 +00:00
|
|
|
if (id.startsWith('virtual:') || id.startsWith('\0virtual:')) {
|
2022-02-08 00:10:42 +00:00
|
|
|
server.moduleGraph.invalidateModule(mod)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-10-30 21:02:41 +00:00
|
|
|
if (nuxt.options.vite.warmupEntry !== false) {
|
2024-07-02 17:01:52 +00:00
|
|
|
// Don't delay nitro build for warmup
|
|
|
|
useNitro().hooks.hookOnce('compiled', () => {
|
|
|
|
const start = Date.now()
|
|
|
|
warmupViteServer(server, [ctx.entry], env.isServer)
|
|
|
|
.then(() => logger.info(`Vite ${env.isClient ? 'client' : 'server'} warmed up in ${Date.now() - start}ms`))
|
|
|
|
.catch(logger.error)
|
|
|
|
})
|
2022-08-15 16:01:34 +00:00
|
|
|
}
|
2021-04-29 11:51:54 +00:00
|
|
|
})
|
2022-07-17 14:17:07 +00:00
|
|
|
|
2024-07-02 17:01:52 +00:00
|
|
|
await withLogs(() => buildClient(ctx), 'Vite client built', ctx.nuxt.options.dev)
|
|
|
|
await withLogs(() => buildServer(ctx), 'Vite server built', ctx.nuxt.options.dev)
|
2021-01-22 22:02:33 +00:00
|
|
|
}
|
2024-06-13 22:35:00 +00:00
|
|
|
|
|
|
|
const globalThisReplacements = Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`]))
|
2024-07-02 17:01:52 +00:00
|
|
|
|
|
|
|
async function withLogs (fn: () => Promise<void>, message: string, enabled = true) {
|
|
|
|
if (!enabled) { return fn() }
|
|
|
|
|
|
|
|
const start = performance.now()
|
|
|
|
await fn()
|
|
|
|
const duration = performance.now() - start
|
|
|
|
logger.success(`${message} in ${Math.round(duration)}ms`)
|
|
|
|
}
|