feat(schema,webpack): allow $client and $server overrides (#22304)

This commit is contained in:
Daniel Roe 2023-07-24 20:46:09 +01:00 committed by GitHub
parent 1b5a8bb924
commit 491ebff3b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 135 additions and 157 deletions

View File

@ -68,6 +68,10 @@ export interface NuxtConfig extends DeepPartial<Omit<ConfigSchema, 'vite' | 'run
// Avoid DeepPartial for vite config interface (#4772) // Avoid DeepPartial for vite config interface (#4772)
vite?: ConfigSchema['vite'] vite?: ConfigSchema['vite']
runtimeConfig?: Overrideable<RuntimeConfig> runtimeConfig?: Overrideable<RuntimeConfig>
webpack?: ConfigSchema['webpack'] & {
$client?: ConfigSchema['webpack']
$server?: ConfigSchema['webpack']
}
/** /**
* Experimental custom config schema * Experimental custom config schema
@ -89,9 +93,13 @@ export type NuxtConfigLayer = ConfigLayer<NuxtConfig & {
}> }>
/** Normalized Nuxt options available as `nuxt.options.*` */ /** Normalized Nuxt options available as `nuxt.options.*` */
export interface NuxtOptions extends Omit<ConfigSchema, 'builder'> { export interface NuxtOptions extends Omit<ConfigSchema, 'builder' | 'webpack'> {
sourcemap: Required<Exclude<ConfigSchema['sourcemap'], boolean>> sourcemap: Required<Exclude<ConfigSchema['sourcemap'], boolean>>
builder: '@nuxt/vite-builder' | '@nuxt/webpack-builder' | { bundle: (nuxt: Nuxt) => Promise<void> } builder: '@nuxt/vite-builder' | '@nuxt/webpack-builder' | { bundle: (nuxt: Nuxt) => Promise<void> }
webpack: ConfigSchema['webpack'] & {
$client: ConfigSchema['webpack']
$server: ConfigSchema['webpack']
}
_layers: NuxtConfigLayer[] _layers: NuxtConfigLayer[]
$schema: SchemaDefinition $schema: SchemaDefinition
} }

View File

@ -25,6 +25,7 @@
"css-loader": "^6.8.1", "css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1", "css-minimizer-webpack-plugin": "^5.0.1",
"cssnano": "^6.0.1", "cssnano": "^6.0.1",
"defu": "^6.1.2",
"esbuild-loader": "^3.0.1", "esbuild-loader": "^3.0.1",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",

View File

@ -47,17 +47,15 @@ function clientPerformance (ctx: WebpackConfigContext) {
} }
function clientHMR (ctx: WebpackConfigContext) { function clientHMR (ctx: WebpackConfigContext) {
const { options, config } = ctx
if (!ctx.isDev) { if (!ctx.isDev) {
return return
} }
const clientOptions = options.webpack.hotMiddleware?.client || {} const clientOptions = ctx.userConfig.hotMiddleware?.client || {}
const hotMiddlewareClientOptions = { const hotMiddlewareClientOptions = {
reload: true, reload: true,
timeout: 30000, timeout: 30000,
path: joinURL(options.app.baseURL, '__webpack_hmr', ctx.name), path: joinURL(ctx.options.app.baseURL, '__webpack_hmr', ctx.name),
...clientOptions, ...clientOptions,
ansiColors: JSON.stringify(clientOptions.ansiColors || {}), ansiColors: JSON.stringify(clientOptions.ansiColors || {}),
overlayStyles: JSON.stringify(clientOptions.overlayStyles || {}), overlayStyles: JSON.stringify(clientOptions.overlayStyles || {}),
@ -66,14 +64,14 @@ function clientHMR (ctx: WebpackConfigContext) {
const hotMiddlewareClientOptionsStr = querystring.stringify(hotMiddlewareClientOptions) const hotMiddlewareClientOptionsStr = querystring.stringify(hotMiddlewareClientOptions)
// Add HMR support // Add HMR support
const app = (config.entry as any).app as any const app = (ctx.config.entry as any).app as any
app.unshift( app.unshift(
// https://github.com/glenjamin/webpack-hot-middleware#config // https://github.com/glenjamin/webpack-hot-middleware#config
`webpack-hot-middleware/client?${hotMiddlewareClientOptionsStr}` `webpack-hot-middleware/client?${hotMiddlewareClientOptionsStr}`
) )
config.plugins = config.plugins || [] ctx.config.plugins = ctx.config.plugins || []
config.plugins.push(new webpack.HotModuleReplacementPlugin()) ctx.config.plugins.push(new webpack.HotModuleReplacementPlugin())
} }
function clientOptimization (_ctx: WebpackConfigContext) { function clientOptimization (_ctx: WebpackConfigContext) {
@ -81,21 +79,19 @@ function clientOptimization (_ctx: WebpackConfigContext) {
} }
function clientPlugins (ctx: WebpackConfigContext) { function clientPlugins (ctx: WebpackConfigContext) {
const { options, config } = ctx
// webpack Bundle Analyzer // webpack Bundle Analyzer
// https://github.com/webpack-contrib/webpack-bundle-analyzer // https://github.com/webpack-contrib/webpack-bundle-analyzer
if (!ctx.isDev && ctx.name === 'client' && options.webpack.analyze) { if (!ctx.isDev && ctx.name === 'client' && ctx.userConfig.analyze) {
const statsDir = resolve(options.analyzeDir) const statsDir = resolve(ctx.options.analyzeDir)
config.plugins!.push(new BundleAnalyzerPlugin({ ctx.config.plugins!.push(new BundleAnalyzerPlugin({
analyzerMode: 'static', analyzerMode: 'static',
defaultSizes: 'gzip', defaultSizes: 'gzip',
generateStatsFile: true, generateStatsFile: true,
openAnalyzer: true, openAnalyzer: true,
reportFilename: resolve(statsDir, `${ctx.name}.html`), reportFilename: resolve(statsDir, `${ctx.name}.html`),
statsFilename: resolve(statsDir, `${ctx.name}.json`), statsFilename: resolve(statsDir, `${ctx.name}.json`),
...options.webpack.analyze === true ? {} : options.webpack.analyze ...ctx.userConfig.analyze === true ? {} : ctx.userConfig.analyze
})) }))
} }
@ -103,7 +99,7 @@ function clientPlugins (ctx: WebpackConfigContext) {
// no server build, so we inject here instead. // no server build, so we inject here instead.
if (!ctx.nuxt.options.ssr) { if (!ctx.nuxt.options.ssr) {
if (ctx.nuxt.options.typescript.typeCheck === true || (ctx.nuxt.options.typescript.typeCheck === 'build' && !ctx.nuxt.options.dev)) { if (ctx.nuxt.options.typescript.typeCheck === true || (ctx.nuxt.options.typescript.typeCheck === 'build' && !ctx.nuxt.options.dev)) {
config.plugins!.push(new ForkTSCheckerWebpackPlugin({ ctx.config.plugins!.push(new ForkTSCheckerWebpackPlugin({
logger logger
})) }))
} }

View File

@ -25,13 +25,11 @@ export function server (ctx: WebpackConfigContext) {
} }
function serverPreset (ctx: WebpackConfigContext) { function serverPreset (ctx: WebpackConfigContext) {
const { config } = ctx ctx.config.output!.filename = 'server.mjs'
config.output!.filename = 'server.mjs' ctx.config.devtool = ctx.nuxt.options.sourcemap.server ? ctx.isDev ? 'cheap-module-source-map' : 'source-map' : false
config.devtool = ctx.nuxt.options.sourcemap.server ? ctx.isDev ? 'cheap-module-source-map' : 'source-map' : false ctx.config.optimization = {
config.optimization = {
splitChunks: false, splitChunks: false,
minimize: false minimize: false
} }
@ -76,21 +74,19 @@ function serverStandalone (ctx: WebpackConfigContext) {
} }
function serverPlugins (ctx: WebpackConfigContext) { function serverPlugins (ctx: WebpackConfigContext) {
const { config, options } = ctx ctx.config.plugins = ctx.config.plugins || []
config.plugins = config.plugins || []
// Server polyfills // Server polyfills
if (options.webpack.serverURLPolyfill) { if (ctx.userConfig.serverURLPolyfill) {
config.plugins.push(new webpack.ProvidePlugin({ ctx.config.plugins.push(new webpack.ProvidePlugin({
URL: [options.webpack.serverURLPolyfill, 'URL'], URL: [ctx.userConfig.serverURLPolyfill, 'URL'],
URLSearchParams: [options.webpack.serverURLPolyfill, 'URLSearchParams'] URLSearchParams: [ctx.userConfig.serverURLPolyfill, 'URLSearchParams']
})) }))
} }
// Add type-checking // Add type-checking
if (ctx.nuxt.options.typescript.typeCheck === true || (ctx.nuxt.options.typescript.typeCheck === 'build' && !ctx.nuxt.options.dev)) { if (ctx.nuxt.options.typescript.typeCheck === true || (ctx.nuxt.options.typescript.typeCheck === 'build' && !ctx.nuxt.options.dev)) {
config.plugins!.push(new ForkTSCheckerWebpackPlugin({ ctx.config.plugins!.push(new ForkTSCheckerWebpackPlugin({
logger logger
})) }))
} }

View File

@ -8,7 +8,7 @@ export function assets (ctx: WebpackConfigContext) {
use: [{ use: [{
loader: 'url-loader', loader: 'url-loader',
options: { options: {
...ctx.options.webpack.loaders.imgUrl, ...ctx.userConfig.loaders.imgUrl,
name: fileName(ctx, 'img') name: fileName(ctx, 'img')
} }
}] }]
@ -18,7 +18,7 @@ export function assets (ctx: WebpackConfigContext) {
use: [{ use: [{
loader: 'url-loader', loader: 'url-loader',
options: { options: {
...ctx.options.webpack.loaders.fontUrl, ...ctx.userConfig.loaders.fontUrl,
name: fileName(ctx, 'font') name: fileName(ctx, 'font')
} }
}] }]
@ -28,7 +28,7 @@ export function assets (ctx: WebpackConfigContext) {
use: [{ use: [{
loader: 'file-loader', loader: 'file-loader',
options: { options: {
...ctx.options.webpack.loaders.file, ...ctx.userConfig.loaders.file,
name: fileName(ctx, 'video') name: fileName(ctx, 'video')
} }
}] }]

View File

@ -27,20 +27,18 @@ export function base (ctx: WebpackConfigContext) {
} }
function baseConfig (ctx: WebpackConfigContext) { function baseConfig (ctx: WebpackConfigContext) {
const { options } = ctx
ctx.config = { ctx.config = {
name: ctx.name, name: ctx.name,
entry: { app: [resolve(options.appDir, options.experimental.asyncEntry ? 'entry.async' : 'entry')] }, entry: { app: [resolve(ctx.options.appDir, ctx.options.experimental.asyncEntry ? 'entry.async' : 'entry')] },
module: { rules: [] }, module: { rules: [] },
plugins: [], plugins: [],
externals: [], externals: [],
optimization: { optimization: {
...options.webpack.optimization, ...ctx.userConfig.optimization,
minimizer: [] minimizer: []
}, },
experiments: { experiments: {
...options.webpack.experiments ...ctx.userConfig.experiments
}, },
mode: ctx.isDev ? 'development' : 'production', mode: ctx.isDev ? 'development' : 'production',
cache: getCache(ctx), cache: getCache(ctx),
@ -51,27 +49,25 @@ function baseConfig (ctx: WebpackConfigContext) {
} }
function basePlugins (ctx: WebpackConfigContext) { function basePlugins (ctx: WebpackConfigContext) {
const { config, options, nuxt } = ctx ctx.config.plugins = ctx.config.plugins || []
config.plugins = config.plugins || []
// Add timefix-plugin before other plugins // Add timefix-plugin before other plugins
if (options.dev) { if (ctx.options.dev) {
config.plugins.push(new TimeFixPlugin()) ctx.config.plugins.push(new TimeFixPlugin())
} }
// User plugins // User plugins
config.plugins.push(...(options.webpack.plugins || [])) ctx.config.plugins.push(...(ctx.userConfig.plugins || []))
// Ignore empty warnings // Ignore empty warnings
config.plugins.push(new WarningIgnorePlugin(getWarningIgnoreFilter(ctx))) ctx.config.plugins.push(new WarningIgnorePlugin(getWarningIgnoreFilter(ctx)))
// Provide env via DefinePlugin // Provide env via DefinePlugin
config.plugins.push(new webpack.DefinePlugin(getEnv(ctx))) ctx.config.plugins.push(new webpack.DefinePlugin(getEnv(ctx)))
// Friendly errors // Friendly errors
if (ctx.isServer || (ctx.isDev && options.webpack.friendlyErrors)) { if (ctx.isServer || (ctx.isDev && ctx.userConfig.friendlyErrors)) {
config.plugins.push( ctx.config.plugins.push(
new FriendlyErrorsWebpackPlugin({ new FriendlyErrorsWebpackPlugin({
clearConsole: false, clearConsole: false,
reporter: 'consola', reporter: 'consola',
@ -80,14 +76,14 @@ function basePlugins (ctx: WebpackConfigContext) {
) )
} }
if (nuxt.options.webpack.profile) { if (ctx.nuxt.options.webpack.profile) {
// Webpackbar // Webpackbar
const colors = { const colors = {
client: 'green', client: 'green',
server: 'orange', server: 'orange',
modern: 'blue' modern: 'blue'
} }
config.plugins.push(new WebpackBar({ ctx.config.plugins.push(new WebpackBar({
name: ctx.name, name: ctx.name,
color: colors[ctx.name as keyof typeof colors], color: colors[ctx.name as keyof typeof colors],
reporters: ['stats'], reporters: ['stats'],
@ -97,21 +93,21 @@ function basePlugins (ctx: WebpackConfigContext) {
reporter: { reporter: {
change: (_, { shortPath }) => { change: (_, { shortPath }) => {
if (!ctx.isServer) { if (!ctx.isServer) {
nuxt.callHook('webpack:change', shortPath) ctx.nuxt.callHook('webpack:change', shortPath)
} }
}, },
done: ({ state }) => { done: ({ state }) => {
if (state.hasErrors) { if (state.hasErrors) {
nuxt.callHook('webpack:error') ctx.nuxt.callHook('webpack:error')
} else { } else {
logger.success(`${state.name} ${state.message}`) logger.success(`${state.name} ${state.message}`)
} }
}, },
allDone: () => { allDone: () => {
nuxt.callHook('webpack:done') ctx.nuxt.callHook('webpack:done')
}, },
progress ({ statesArray }) { progress ({ statesArray }) {
nuxt.callHook('webpack:progress', statesArray) ctx.nuxt.callHook('webpack:progress', statesArray)
} }
} }
} }
@ -120,13 +116,11 @@ function basePlugins (ctx: WebpackConfigContext) {
} }
function baseAlias (ctx: WebpackConfigContext) { function baseAlias (ctx: WebpackConfigContext) {
const { options } = ctx
ctx.alias = { ctx.alias = {
'#app': options.appDir, '#app': ctx.options.appDir,
'#build/plugins': resolve(options.buildDir, 'plugins', ctx.isClient ? 'client' : 'server'), '#build/plugins': resolve(ctx.options.buildDir, 'plugins', ctx.isClient ? 'client' : 'server'),
'#build': options.buildDir, '#build': ctx.options.buildDir,
...options.alias, ...ctx.options.alias,
...ctx.alias ...ctx.alias
} }
if (ctx.isClient) { if (ctx.isClient) {
@ -135,29 +129,25 @@ function baseAlias (ctx: WebpackConfigContext) {
} }
function baseResolve (ctx: WebpackConfigContext) { function baseResolve (ctx: WebpackConfigContext) {
const { options, config } = ctx
// Prioritize nested node_modules in webpack search path (#2558) // Prioritize nested node_modules in webpack search path (#2558)
// TODO: this might be refactored as default modulesDir? // TODO: this might be refactored as default modulesDir?
const webpackModulesDir = ['node_modules'].concat(options.modulesDir) const webpackModulesDir = ['node_modules'].concat(ctx.options.modulesDir)
config.resolve = { ctx.config.resolve = {
extensions: ['.wasm', '.mjs', '.js', '.ts', '.json', '.vue', '.jsx', '.tsx'], extensions: ['.wasm', '.mjs', '.js', '.ts', '.json', '.vue', '.jsx', '.tsx'],
alias: ctx.alias, alias: ctx.alias,
modules: webpackModulesDir, modules: webpackModulesDir,
fullySpecified: false, fullySpecified: false,
...config.resolve ...ctx.config.resolve
} }
config.resolveLoader = { ctx.config.resolveLoader = {
modules: webpackModulesDir, modules: webpackModulesDir,
...config.resolveLoader ...ctx.config.resolveLoader
} }
} }
function baseTranspile (ctx: WebpackConfigContext) { function baseTranspile (ctx: WebpackConfigContext) {
const { options } = ctx
const transpile = [ const transpile = [
/\.vue\.js/i, // include SFCs in node_modules /\.vue\.js/i, // include SFCs in node_modules
/consola\/src/, /consola\/src/,
@ -165,7 +155,7 @@ function baseTranspile (ctx: WebpackConfigContext) {
/(^|\/)nuxt\/(dist\/)?(app|[^/]+\/runtime)($|\/)/ /(^|\/)nuxt\/(dist\/)?(app|[^/]+\/runtime)($|\/)/
] ]
for (let pattern of options.build.transpile) { for (let pattern of ctx.options.build.transpile) {
if (typeof pattern === 'function') { if (typeof pattern === 'function') {
const result = pattern(ctx) const result = pattern(ctx)
if (result) { pattern = result } if (result) { pattern = result }
@ -182,9 +172,7 @@ function baseTranspile (ctx: WebpackConfigContext) {
} }
function getCache (ctx: WebpackConfigContext): webpack.Configuration['cache'] { function getCache (ctx: WebpackConfigContext): webpack.Configuration['cache'] {
const { options } = ctx if (!ctx.options.dev) {
if (!options.dev) {
return false return false
} }
@ -205,37 +193,31 @@ function getCache (ctx: WebpackConfigContext): webpack.Configuration['cache'] {
} }
function getOutput (ctx: WebpackConfigContext): webpack.Configuration['output'] { function getOutput (ctx: WebpackConfigContext): webpack.Configuration['output'] {
const { options } = ctx
return { return {
path: resolve(options.buildDir, 'dist', ctx.isServer ? 'server' : joinURL('client', options.app.buildAssetsDir)), path: resolve(ctx.options.buildDir, 'dist', ctx.isServer ? 'server' : joinURL('client', ctx.options.app.buildAssetsDir)),
filename: fileName(ctx, 'app'), filename: fileName(ctx, 'app'),
chunkFilename: fileName(ctx, 'chunk'), chunkFilename: fileName(ctx, 'chunk'),
publicPath: joinURL(options.app.baseURL, options.app.buildAssetsDir) publicPath: joinURL(ctx.options.app.baseURL, ctx.options.app.buildAssetsDir)
} }
} }
function getWarningIgnoreFilter (ctx: WebpackConfigContext): WarningFilter { function getWarningIgnoreFilter (ctx: WebpackConfigContext): WarningFilter {
const { options } = ctx
const filters: WarningFilter[] = [ const filters: WarningFilter[] = [
// Hide warnings about plugins without a default export (#1179) // Hide warnings about plugins without a default export (#1179)
warn => warn.name === 'ModuleDependencyWarning' && warn => warn.name === 'ModuleDependencyWarning' &&
warn.message.includes('export \'default\'') && warn.message.includes('export \'default\'') &&
warn.message.includes('nuxt_plugin_'), warn.message.includes('nuxt_plugin_'),
...(options.webpack.warningIgnoreFilters || []) ...(ctx.userConfig.warningIgnoreFilters || [])
] ]
return warn => !filters.some(ignoreFilter => ignoreFilter(warn)) return warn => !filters.some(ignoreFilter => ignoreFilter(warn))
} }
function getEnv (ctx: WebpackConfigContext) { function getEnv (ctx: WebpackConfigContext) {
const { options } = ctx
const _env: Record<string, string | boolean> = { const _env: Record<string, string | boolean> = {
'process.env.NODE_ENV': JSON.stringify(ctx.config.mode), 'process.env.NODE_ENV': JSON.stringify(ctx.config.mode),
'process.mode': JSON.stringify(ctx.config.mode), 'process.mode': JSON.stringify(ctx.config.mode),
'process.dev': options.dev, 'process.dev': ctx.options.dev,
'process.test': isTest, 'process.test': isTest,
__NUXT_VERSION__: JSON.stringify(ctx.nuxt._version), __NUXT_VERSION__: JSON.stringify(ctx.nuxt._version),
'process.env.VUE_ENV': JSON.stringify(ctx.name), 'process.env.VUE_ENV': JSON.stringify(ctx.name),
@ -244,7 +226,7 @@ function getEnv (ctx: WebpackConfigContext) {
'process.server': ctx.isServer 'process.server': ctx.isServer
} }
if (options.webpack.aggressiveCodeRemoval) { if (ctx.userConfig.aggressiveCodeRemoval) {
_env['typeof process'] = JSON.stringify(ctx.isServer ? 'object' : 'undefined') _env['typeof process'] = JSON.stringify(ctx.isServer ? 'object' : 'undefined')
_env['typeof window'] = _env['typeof document'] = JSON.stringify(!ctx.isServer ? 'object' : 'undefined') _env['typeof window'] = _env['typeof document'] = JSON.stringify(!ctx.isServer ? 'object' : 'undefined')
} }

View File

@ -2,17 +2,15 @@ import { EsbuildPlugin } from 'esbuild-loader'
import type { WebpackConfigContext } from '../utils/config' import type { WebpackConfigContext } from '../utils/config'
export function esbuild (ctx: WebpackConfigContext) { export function esbuild (ctx: WebpackConfigContext) {
const { config } = ctx
// https://esbuild.github.io/getting-started/#bundling-for-the-browser // https://esbuild.github.io/getting-started/#bundling-for-the-browser
// https://gs.statcounter.com/browser-version-market-share // https://gs.statcounter.com/browser-version-market-share
// https://nodejs.org/en/ // https://nodejs.org/en/
const target = ctx.isServer ? 'es2019' : 'chrome85' const target = ctx.isServer ? 'es2019' : 'chrome85'
// https://github.com/nuxt/nuxt/issues/13052 // https://github.com/nuxt/nuxt/issues/13052
config.optimization!.minimizer!.push(new EsbuildPlugin()) ctx.config.optimization!.minimizer!.push(new EsbuildPlugin())
config.module!.rules!.push( ctx.config.module!.rules!.push(
{ {
test: /\.m?[jt]s$/i, test: /\.m?[jt]s$/i,
loader: 'esbuild-loader', loader: 'esbuild-loader',

View File

@ -1,15 +1,13 @@
import type { WebpackConfigContext } from '../utils/config' import type { WebpackConfigContext } from '../utils/config'
export function node (ctx: WebpackConfigContext) { export function node (ctx: WebpackConfigContext) {
const { config } = ctx ctx.config.target = 'node'
ctx.config.node = false
config.target = 'node' ctx.config.experiments!.outputModule = true
config.node = false
config.experiments!.outputModule = true ctx.config.output = {
...ctx.config.output,
config.output = {
...config.output,
chunkFilename: '[name].mjs', chunkFilename: '[name].mjs',
chunkFormat: 'module', chunkFormat: 'module',
chunkLoading: 'import', chunkLoading: 'import',
@ -28,8 +26,8 @@ export function node (ctx: WebpackConfigContext) {
} }
} }
config.performance = { ctx.config.performance = {
...config.performance, ...ctx.config.performance,
hints: false, hints: false,
maxEntrypointSize: Infinity, maxEntrypointSize: Infinity,
maxAssetSize: Infinity maxAssetSize: Infinity

View File

@ -8,7 +8,7 @@ export function pug (ctx: WebpackConfigContext) {
resourceQuery: /^\?vue/i, resourceQuery: /^\?vue/i,
use: [{ use: [{
loader: 'pug-plain-loader', loader: 'pug-plain-loader',
options: ctx.options.webpack.loaders.pugPlain options: ctx.userConfig.loaders.pugPlain
}] }]
}, },
{ {
@ -16,7 +16,7 @@ export function pug (ctx: WebpackConfigContext) {
'raw-loader', 'raw-loader',
{ {
loader: 'pug-plain-loader', loader: 'pug-plain-loader',
options: ctx.options.webpack.loaders.pugPlain options: ctx.userConfig.loaders.pugPlain
} }
] ]
} }

View File

@ -13,67 +13,59 @@ export function style (ctx: WebpackConfigContext) {
} }
function minimizer (ctx: WebpackConfigContext) { function minimizer (ctx: WebpackConfigContext) {
const { options, config } = ctx if (ctx.userConfig.optimizeCSS && Array.isArray(ctx.config.optimization!.minimizer)) {
ctx.config.optimization!.minimizer.push(new CssMinimizerPlugin({
if (options.webpack.optimizeCSS && Array.isArray(config.optimization!.minimizer)) { ...ctx.userConfig.optimizeCSS
config.optimization!.minimizer.push(new CssMinimizerPlugin({
...options.webpack.optimizeCSS
})) }))
} }
} }
function extractCSS (ctx: WebpackConfigContext) { function extractCSS (ctx: WebpackConfigContext) {
const { options, config } = ctx
// CSS extraction // CSS extraction
if (options.webpack.extractCSS) { if (ctx.userConfig.extractCSS) {
config.plugins!.push(new MiniCssExtractPlugin({ ctx.config.plugins!.push(new MiniCssExtractPlugin({
filename: fileName(ctx, 'css'), filename: fileName(ctx, 'css'),
chunkFilename: fileName(ctx, 'css'), chunkFilename: fileName(ctx, 'css'),
...options.webpack.extractCSS === true ? {} : options.webpack.extractCSS ...ctx.userConfig.extractCSS === true ? {} : ctx.userConfig.extractCSS
})) }))
} }
} }
function loaders (ctx: WebpackConfigContext) { function loaders (ctx: WebpackConfigContext) {
const { config, options } = ctx
// CSS // CSS
config.module!.rules!.push(createdStyleRule('css', /\.css$/i, null, ctx)) ctx.config.module!.rules!.push(createdStyleRule('css', /\.css$/i, null, ctx))
// PostCSS // PostCSS
config.module!.rules!.push(createdStyleRule('postcss', /\.p(ost)?css$/i, null, ctx)) ctx.config.module!.rules!.push(createdStyleRule('postcss', /\.p(ost)?css$/i, null, ctx))
// Less // Less
const lessLoader = { loader: 'less-loader', options: options.webpack.loaders.less } const lessLoader = { loader: 'less-loader', options: ctx.userConfig.loaders.less }
config.module!.rules!.push(createdStyleRule('less', /\.less$/i, lessLoader, ctx)) ctx.config.module!.rules!.push(createdStyleRule('less', /\.less$/i, lessLoader, ctx))
// Sass (TODO: optional dependency) // Sass (TODO: optional dependency)
const sassLoader = { loader: 'sass-loader', options: options.webpack.loaders.sass } const sassLoader = { loader: 'sass-loader', options: ctx.userConfig.loaders.sass }
config.module!.rules!.push(createdStyleRule('sass', /\.sass$/i, sassLoader, ctx)) ctx.config.module!.rules!.push(createdStyleRule('sass', /\.sass$/i, sassLoader, ctx))
const scssLoader = { loader: 'sass-loader', options: options.webpack.loaders.scss } const scssLoader = { loader: 'sass-loader', options: ctx.userConfig.loaders.scss }
config.module!.rules!.push(createdStyleRule('scss', /\.scss$/i, scssLoader, ctx)) ctx.config.module!.rules!.push(createdStyleRule('scss', /\.scss$/i, scssLoader, ctx))
// Stylus // Stylus
const stylusLoader = { loader: 'stylus-loader', options: options.webpack.loaders.stylus } const stylusLoader = { loader: 'stylus-loader', options: ctx.userConfig.loaders.stylus }
config.module!.rules!.push(createdStyleRule('stylus', /\.styl(us)?$/i, stylusLoader, ctx)) ctx.config.module!.rules!.push(createdStyleRule('stylus', /\.styl(us)?$/i, stylusLoader, ctx))
} }
function createdStyleRule (lang: string, test: RegExp, processorLoader: any, ctx: WebpackConfigContext) { function createdStyleRule (lang: string, test: RegExp, processorLoader: any, ctx: WebpackConfigContext) {
const { options } = ctx
const styleLoaders = [ const styleLoaders = [
createPostcssLoadersRule(ctx), createPostcssLoadersRule(ctx),
processorLoader processorLoader
].filter(Boolean) ].filter(Boolean)
options.webpack.loaders.css.importLoaders = ctx.userConfig.loaders.css.importLoaders =
options.webpack.loaders.cssModules.importLoaders = ctx.userConfig.loaders.cssModules.importLoaders =
styleLoaders.length styleLoaders.length
const cssLoaders = createCssLoadersRule(ctx, options.webpack.loaders.css) const cssLoaders = createCssLoadersRule(ctx, ctx.userConfig.loaders.css)
const cssModuleLoaders = createCssLoadersRule(ctx, options.webpack.loaders.cssModules) const cssModuleLoaders = createCssLoadersRule(ctx, ctx.userConfig.loaders.cssModules)
return { return {
test, test,
@ -92,11 +84,9 @@ function createdStyleRule (lang: string, test: RegExp, processorLoader: any, ctx
} }
function createCssLoadersRule (ctx: WebpackConfigContext, cssLoaderOptions: any) { function createCssLoadersRule (ctx: WebpackConfigContext, cssLoaderOptions: any) {
const { options } = ctx
const cssLoader = { loader: 'css-loader', options: cssLoaderOptions } const cssLoader = { loader: 'css-loader', options: cssLoaderOptions }
if (options.webpack.extractCSS) { if (ctx.userConfig.extractCSS) {
if (ctx.isServer) { if (ctx.isServer) {
// https://webpack.js.org/loaders/css-loader/#exportonlylocals // https://webpack.js.org/loaders/css-loader/#exportonlylocals
if (cssLoader.options.modules) { if (cssLoader.options.modules) {
@ -124,11 +114,9 @@ function createCssLoadersRule (ctx: WebpackConfigContext, cssLoaderOptions: any)
} }
function createPostcssLoadersRule (ctx: WebpackConfigContext) { function createPostcssLoadersRule (ctx: WebpackConfigContext) {
const { options, nuxt } = ctx if (!ctx.options.postcss) { return }
if (!options.postcss) { return } const config = getPostcssConfig(ctx.nuxt)
const config = getPostcssConfig(nuxt)
if (!config) { if (!config) {
return return

View File

@ -6,27 +6,25 @@ import VueSSRServerPlugin from '../plugins/vue/server'
import type { WebpackConfigContext } from '../utils/config' import type { WebpackConfigContext } from '../utils/config'
export function vue (ctx: WebpackConfigContext) { export function vue (ctx: WebpackConfigContext) {
const { options, config } = ctx
// @ts-expect-error de-default vue-loader // @ts-expect-error de-default vue-loader
config.plugins!.push(new (VueLoaderPlugin.default || VueLoaderPlugin)()) ctx.config.plugins!.push(new (VueLoaderPlugin.default || VueLoaderPlugin)())
config.module!.rules!.push({ ctx.config.module!.rules!.push({
test: /\.vue$/i, test: /\.vue$/i,
loader: 'vue-loader', loader: 'vue-loader',
options: { options: {
reactivityTransform: ctx.nuxt.options.experimental.reactivityTransform, reactivityTransform: ctx.nuxt.options.experimental.reactivityTransform,
...options.webpack.loaders.vue ...ctx.userConfig.loaders.vue
} }
}) })
if (ctx.isClient) { if (ctx.isClient) {
config.plugins!.push(new VueSSRClientPlugin({ ctx.config.plugins!.push(new VueSSRClientPlugin({
filename: resolve(options.buildDir, 'dist/server', `${ctx.name}.manifest.json`), filename: resolve(ctx.options.buildDir, 'dist/server', `${ctx.name}.manifest.json`),
nuxt: ctx.nuxt nuxt: ctx.nuxt
})) }))
} else { } else {
config.plugins!.push(new VueSSRServerPlugin({ ctx.config.plugins!.push(new VueSSRServerPlugin({
filename: `${ctx.name}.manifest.json` filename: `${ctx.name}.manifest.json`
})) }))
} }
@ -34,7 +32,7 @@ export function vue (ctx: WebpackConfigContext) {
// Feature flags // Feature flags
// https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags // https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags
// TODO: Provide options to toggle // TODO: Provide options to toggle
config.plugins!.push(new webpack.DefinePlugin({ ctx.config.plugins!.push(new webpack.DefinePlugin({
__VUE_OPTIONS_API__: 'true', __VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false' __VUE_PROD_DEVTOOLS__: 'false'
})) }))

View File

@ -1,26 +1,38 @@
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import type { Configuration } from 'webpack' import type { Configuration } from 'webpack'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt, NuxtOptions } from '@nuxt/schema'
import { logger } from '@nuxt/kit' import { logger } from '@nuxt/kit'
export interface WebpackConfigContext extends ReturnType<typeof createWebpackConfigContext> {} export interface WebpackConfigContext {
nuxt: Nuxt
options: NuxtOptions
userConfig: Omit<NuxtOptions['webpack'], '$client' | '$server'>
config: Configuration
name: string
isDev: boolean
isServer: boolean
isClient: boolean
alias: { [index: string]: string | false | string[] }
transpile: RegExp[]
}
type WebpackConfigPreset = (ctx: WebpackConfigContext, options?: object) => void type WebpackConfigPreset = (ctx: WebpackConfigContext, options?: object) => void
type WebpackConfigPresetItem = WebpackConfigPreset | [WebpackConfigPreset, any] type WebpackConfigPresetItem = WebpackConfigPreset | [WebpackConfigPreset, any]
export function createWebpackConfigContext (nuxt: Nuxt) { export function createWebpackConfigContext (nuxt: Nuxt): WebpackConfigContext {
return { return {
nuxt, nuxt,
options: nuxt.options, options: nuxt.options,
config: {} as Configuration, userConfig: nuxt.options.webpack,
config: {},
name: 'base', name: 'base',
isDev: nuxt.options.dev, isDev: nuxt.options.dev,
isServer: false, isServer: false,
isClient: false, isClient: false,
alias: {} as { [index: string]: string | false | string[] }, alias: {},
transpile: [] as RegExp[] transpile: []
} }
} }
@ -38,15 +50,13 @@ export function applyPresets (ctx: WebpackConfigContext, presets: WebpackConfigP
} }
export function fileName (ctx: WebpackConfigContext, key: string) { export function fileName (ctx: WebpackConfigContext, key: string) {
const { options } = ctx let fileName = ctx.userConfig.filenames[key]
let fileName = options.webpack.filenames[key]
if (typeof fileName === 'function') { if (typeof fileName === 'function') {
fileName = fileName(ctx) fileName = fileName(ctx)
} }
if (typeof fileName === 'string' && options.dev) { if (typeof fileName === 'string' && ctx.options.dev) {
const hash = /\[(chunkhash|contenthash|hash)(?::(\d+))?]/.exec(fileName) const hash = /\[(chunkhash|contenthash|hash)(?::(\d+))?]/.exec(fileName)
if (hash) { if (hash) {
logger.warn(`Notice: Please do not use ${hash[1]} in dev mode to prevent memory leak`) logger.warn(`Notice: Please do not use ${hash[1]} in dev mode to prevent memory leak`)

View File

@ -6,10 +6,11 @@ import type { OutputFileSystem } from 'webpack-dev-middleware'
import webpackDevMiddleware from 'webpack-dev-middleware' import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware' import webpackHotMiddleware from 'webpack-hot-middleware'
import type { Compiler, Watching } from 'webpack' import type { Compiler, Watching } from 'webpack'
import { defu } from 'defu'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import { joinURL } from 'ufo' import { joinURL } from 'ufo'
import { logger, useNuxt } from '@nuxt/kit' import { logger, useNuxt } from '@nuxt/kit'
import { composableKeysPlugin } from '../../vite/src/plugins/composable-keys' import { composableKeysPlugin } from '../../vite/src/plugins/composable-keys'
import { DynamicBasePlugin } from './plugins/dynamic-base' import { DynamicBasePlugin } from './plugins/dynamic-base'
import { ChunkErrorPlugin } from './plugins/chunk' import { ChunkErrorPlugin } from './plugins/chunk'
@ -26,6 +27,7 @@ export async function bundle (nuxt: Nuxt) {
const webpackConfigs = [client, ...nuxt.options.ssr ? [server] : []].map((preset) => { const webpackConfigs = [client, ...nuxt.options.ssr ? [server] : []].map((preset) => {
const ctx = createWebpackConfigContext(nuxt) const ctx = createWebpackConfigContext(nuxt)
ctx.userConfig = defu(nuxt.options.webpack[`$${preset.name as 'client' | 'server'}`], ctx.userConfig)
applyPresets(ctx, preset) applyPresets(ctx, preset)
return getWebpackConfig(ctx) return getWebpackConfig(ctx)
}) })
@ -119,13 +121,11 @@ async function createDevMiddleware (compiler: Compiler) {
async function compile (compiler: Compiler) { async function compile (compiler: Compiler) {
const nuxt = useNuxt() const nuxt = useNuxt()
const { name } = compiler.options await nuxt.callHook('webpack:compile', { name: compiler.options.name!, compiler })
await nuxt.callHook('webpack:compile', { name: name!, compiler })
// Load renderer resources after build // Load renderer resources after build
compiler.hooks.done.tap('load-resources', async (stats) => { compiler.hooks.done.tap('load-resources', async (stats) => {
await nuxt.callHook('webpack:compiled', { name: name!, compiler, stats }) await nuxt.callHook('webpack:compiled', { name: compiler.options.name!, compiler, stats })
}) })
// --- Dev Build --- // --- Dev Build ---
@ -137,7 +137,7 @@ async function compile (compiler: Compiler) {
}) })
// Client build // Client build
if (name === 'client') { if (compiler.options.name === 'client') {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
compiler.hooks.done.tap('nuxt-dev', () => { resolve(null) }) compiler.hooks.done.tap('nuxt-dev', () => { resolve(null) })
compiler.hooks.failed.tap('nuxt-errorlog', (err) => { reject(err) }) compiler.hooks.failed.tap('nuxt-errorlog', (err) => { reject(err) })

View File

@ -806,6 +806,9 @@ importers:
cssnano: cssnano:
specifier: ^6.0.1 specifier: ^6.0.1
version: 6.0.1(postcss@8.4.27) version: 6.0.1(postcss@8.4.27)
defu:
specifier: ^6.1.2
version: 6.1.2
esbuild-loader: esbuild-loader:
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.1(webpack@5.88.2) version: 3.0.1(webpack@5.88.2)