refactor(schema,webpack)!: split out webpack and postcss build options (#2812)

* refactor: split out webpack and postcss build options

* feat(schema): set nuxt3 builder in config

* fix(schema): default postcss config file to false

* chore: update lockfile

* style: remove unused imports

* refactor(webpack): remove (previously disabled) babel config

* refactor: move shared vite options into schema

* fix(schema): omit __NUXT_BASE__ from defaults

* fix: move appDir-dependent options back to vite

* refactor: split out virtual modules

* refactor: extract compile/createDevMiddleware

* refactor: further improvements

* chore: remove `@nuxt/webpack-builder` dependency

* chore: update lockfile

* refactor: move `builder` option to top level

* fix: bind close to watcher instance

* chore: update lockfile

* fix: create portal between postcss & build.postcss.postcssOptions

* fix: remove duplicate

* fix: revert

* fix: use `postcss` directly

* fix: import builder from rootDir

* chore: dedupe webpack install

* test: update fixture to use `builder`

* fix: bind class in pify

Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
Daniel Roe 2022-02-25 19:11:01 +00:00 committed by GitHub
parent 2b3dbed594
commit 73ba30fb69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1363 additions and 1576 deletions

View File

@ -83,6 +83,10 @@ export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer {
extendBuild (fn) { extendBuild (fn) {
// @ts-ignore // @ts-ignore
nuxt.options.build.extend = chainFn(nuxt.options.build.extend, fn) nuxt.options.build.extend = chainFn(nuxt.options.build.extend, fn)
if (!isNuxt2(nuxt)) {
console.warn('[kit] [compat] Using `extendBuild` in Nuxt 3 has no effect. Instead call extendWebpackConfig and extendViteConfig.')
}
}, },
extendRoutes (fn) { extendRoutes (fn) {

View File

@ -35,7 +35,6 @@
"@nuxt/nitro": "3.0.0", "@nuxt/nitro": "3.0.0",
"@nuxt/schema": "3.0.0", "@nuxt/schema": "3.0.0",
"@nuxt/vite-builder": "3.0.0", "@nuxt/vite-builder": "3.0.0",
"@nuxt/webpack-builder": "3.0.0",
"@vue/reactivity": "^3.2.31", "@vue/reactivity": "^3.2.31",
"@vue/shared": "^3.2.31", "@vue/shared": "^3.2.31",
"@vueuse/head": "^0.7.5", "@vueuse/head": "^0.7.5",

View File

@ -1,5 +1,6 @@
import chokidar from 'chokidar' import chokidar from 'chokidar'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import { tryImportModule } from '@nuxt/kit'
import { createApp, generateApp } from './app' import { createApp, generateApp } from './app'
export async function build (nuxt: Nuxt) { export async function build (nuxt: Nuxt) {
@ -48,8 +49,9 @@ function watch (nuxt: Nuxt) {
} }
async function bundle (nuxt: Nuxt) { async function bundle (nuxt: Nuxt) {
const useVite = nuxt.options.vite !== false const { bundle } = typeof nuxt.options.builder === 'string'
const { bundle } = await (useVite ? import('@nuxt/vite-builder') : import('@nuxt/webpack-builder')) ? await tryImportModule(nuxt.options.builder, { paths: nuxt.options.rootDir })
: nuxt.options.builder
try { try {
return bundle(nuxt) return bundle(nuxt)
} catch (error) { } catch (error) {

View File

@ -9,7 +9,10 @@ export default defineBuildConfig({
name: 'config', name: 'config',
builder: 'untyped', builder: 'untyped',
defaults: { defaults: {
rootDir: '/<rootDir>/' rootDir: '/<rootDir>/',
vite: {
base: '/'
}
} }
}, },
'src/index' 'src/index'
@ -24,6 +27,12 @@ export default defineBuildConfig({
'webpack-bundle-analyzer', 'webpack-bundle-analyzer',
'rollup-plugin-visualizer', 'rollup-plugin-visualizer',
'vite', 'vite',
'mini-css-extract-plugin',
'terser-webpack-plugin',
'css-minimizer-webpack-plugin',
'webpack-dev-middleware',
'webpack-hot-middleware',
'postcss',
'consola', 'consola',
// Implicit // Implicit
'@vue/compiler-core', '@vue/compiler-core',

View File

@ -24,6 +24,7 @@
"defu": "^5.0.1", "defu": "^5.0.1",
"jiti": "^1.13.0", "jiti": "^1.13.0",
"pathe": "^0.2.0", "pathe": "^0.2.0",
"postcss-import-resolver": "^2.0.0",
"scule": "^0.2.1", "scule": "^0.2.1",
"std-env": "^3.0.1", "std-env": "^3.0.1",
"ufo": "^0.7.9" "ufo": "^0.7.9"

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,10 @@ import router from './router'
import server from './server' import server from './server'
import cli from './cli' import cli from './cli'
import generate from './generate' import generate from './generate'
import postcss from './postcss'
import typescript from './typescript' import typescript from './typescript'
import vite from './vite'
import webpack from './webpack'
import nitro from './nitro' import nitro from './nitro'
import experimental from './experimental' import experimental from './experimental'
@ -37,25 +40,18 @@ export default {
..._app, ..._app,
..._common, ..._common,
..._internal, ..._internal,
...postcss,
...typescript,
...vite,
...webpack,
...nitro, ...nitro,
build, // Legacy
...build,
messages, messages,
render, render,
router, router,
server, server,
cli, cli,
generate, generate,
typescript,
experimental, experimental,
/**
* Configuration that will be passed directly to Vite.
*
* See https://vitejs.dev/config for more information.
* Please note that not all vite options are supported in Nuxt.
*
* @type {boolean | typeof import('vite').InlineConfig}
* @version 3
*/
vite: undefined,
} }

View File

@ -0,0 +1,53 @@
import defu from 'defu'
import createResolver from 'postcss-import-resolver'
export default {
/** @version 3 */
postcss: {
/** Path to postcss config file. */
/** @type string | false */
config: false,
/**
* Options for configuring PostCSS plugins.
*
* https://postcss.org/
*/
plugins: {
/**
* https://github.com/postcss/postcss-import
*/
'postcss-import': {
$resolve: (val, get) => val !== false ? defu(val || {}, {
resolve: createResolver({
alias: { ...get('alias') },
modules: [
get('srcDir'),
get('rootDir'),
...get('modulesDir')
]
})
}) : val,
},
/**
* https://github.com/postcss/postcss-url
*/
'postcss-url': {},
/**
* https://github.com/postcss/autoprefixer
*/
autoprefixer: {},
cssnano: {
$resolve: (val, get) => val ?? (get('dev') && {
preset: ['default', {
// Keep quotes in font values to prevent from HEX conversion
// https://github.com/nuxt/nuxt.js/issues/6306
minifyFontValues: { removeQuotes: false }
}]
})
}
}
}
}

View File

@ -1,21 +1,23 @@
export default { export default {
/** typescript: {
/**
* TypeScript comes with certain checks to give you more safety and analysis of your program. * TypeScript comes with certain checks to give you more safety and analysis of your program.
* Once youve converted your codebase to TypeScript, you can start enabling these checks for greater safety. * Once youve converted your codebase to TypeScript, you can start enabling these checks for greater safety.
* [Read More](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html#getting-stricter-checks) * [Read More](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html#getting-stricter-checks)
*/ */
strict: false, strict: false,
/** /**
* You can extend generated `.nuxt/tsconfig.json` using this option * You can extend generated `.nuxt/tsconfig.json` using this option
* @typedef {Awaited<ReturnType<typeof import('pkg-types')['readPackageJSON']>>} * @typedef {Awaited<ReturnType<typeof import('pkg-types')['readPackageJSON']>>}
*/ */
tsConfig: {}, tsConfig: {},
/** /**
* Generate a `*.vue` shim. * Generate a `*.vue` shim.
* *
* We recommend instead either enabling [**Take Over Mode**](https://github.com/johnsoncodehk/volar/discussions/471) or adding **TypeScript Vue Plugin (Volar)** 👉 [[Download](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.vscode-typescript-vue-plugin)]. * We recommend instead either enabling [**Take Over Mode**](https://github.com/johnsoncodehk/volar/discussions/471) or adding **TypeScript Vue Plugin (Volar)** 👉 [[Download](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.vscode-typescript-vue-plugin)].
*/ */
shim: true shim: true
}
} }

View File

@ -0,0 +1,83 @@
import { resolve } from 'pathe'
import { joinURL, withoutLeadingSlash } from 'ufo'
export default {
/**
* Configuration that will be passed directly to Vite.
*
* See https://vitejs.dev/config for more information.
* Please note that not all vite options are supported in Nuxt.
*
* @type {typeof import('vite').InlineConfig}
* @version 3
*/
vite: {
root: {
$resolve: (val, get) => val ?? get('srcDir'),
},
mode: {
$resolve: (val, get) => val ?? get('dev') ? 'development' : 'production',
},
logLevel: 'warn',
define: {
$resolve: (val, get) => ({
'process.dev': get('dev'),
...val || {}
})
},
resolve: {
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
},
base: {
$resolve: (val, get) => val ?? get('dev')
? joinURL(get('app').baseURL, get('app').buildAssetsDir)
: '/__NUXT_BASE__/',
},
publicDir: {
$resolve: (val, get) => val ?? resolve(get('srcDir'), get('dir').public),
},
vue: {
isProduction: {
$resolve: (val, get) => val ?? !get('dev'),
},
template: { compilerOptions: {
$resolve: (val, get) => val ?? get('vue').compilerOptions }
},
},
optimizeDeps: {
exclude: {
$resolve: (val, get) => [
...val || [],
...get('build.transpile').filter(i => typeof i === 'string'),
'vue-demi'
],
},
},
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment',
tsconfigRaw: '{}'
},
clearScreen: false,
build: {
assetsDir: {
$resolve: (val, get) => val ?? get('dev') ? withoutLeadingSlash(get('app').buildAssetsDir) : '.',
},
emptyOutDir: false,
},
server: {
fs: {
strict: false,
allow: {
$resolve: (val, get) => [
get('buildDir'),
get('srcDir'),
get('rootDir'),
...get('modulesDir'),
...val ?? []
]
}
}
}
}
}

View File

@ -0,0 +1,294 @@
import { join } from 'pathe'
export default {
/** @version 3 */
webpack: {
/**
* Nuxt uses `webpack-bundle-analyzer` to visualize your bundles and how to optimize them.
*
* Set to `true` to enable bundle analysis, or pass an object with options: [for webpack](https://github.com/webpack-contrib/webpack-bundle-analyzer#options-for-plugin) or [for vite](https://github.com/btd/rollup-plugin-visualizer#options).
*
* @example
* ```js
* analyze: {
* analyzerMode: 'static'
* }
* ```
* @type {boolean | typeof import('webpack-bundle-analyzer').BundleAnalyzerPlugin.Options}
*/
analyze: {
$resolve: (val, get) => {
if(val !== true) {
return val ?? false
}
const rootDir = get('rootDir')
return {
template: 'treemap',
projectRoot: rootDir,
filename: join(rootDir, '.nuxt/stats', '{name}.html')
}
}
},
/**
* Enable the profiler in webpackbar.
*
* It is normally enabled by CLI argument `--profile`.
*
* @see [webpackbar](https://github.com/unjs/webpackbar#profile)
*/
profile: process.argv.includes('--profile'),
/**
* Enables Common CSS Extraction using
* [Vue Server Renderer guidelines](https://ssr.vuejs.org/guide/css.html).
*
* Using [extract-css-chunks-webpack-plugin](https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/) under the hood, your CSS will be extracted
* into separate files, usually one per component. This allows caching your CSS and
* JavaScript separately and is worth trying if you have a lot of global or shared CSS.
*
* @example
* ```js
* export default {
* build: {
* extractCSS: true,
* // or
* extractCSS: {
* ignoreOrder: true
* }
* }
* }
* ```
*
* If you want to extract all your CSS to a single file, there is a workaround for this.
* However, note that it is not recommended to extract everything into a single file.
* Extracting into multiple CSS files is better for caching and preload isolation. It
* can also improve page performance by downloading and resolving only those resources
* that are needed.
*
* @example
* ```js
* export default {
* build: {
* extractCSS: true,
* optimization: {
* splitChunks: {
* cacheGroups: {
* styles: {
* name: 'styles',
* test: /\.(css|vue)$/,
* chunks: 'all',
* enforce: true
* }
* }
* }
* }
* }
* }
* ```
* @type {false | typeof import('mini-css-extract-plugin').PluginOptions}
*/
extractCSS: false,
/**
* Enables CSS source map support (defaults to true in development)
*/
cssSourceMap: {
$resolve: (val, get) => val ?? get('dev')
},
/**
* The polyfill library to load to provide URL and URLSearchParams.
*
* Defaults to `'url'` ([see package](https://www.npmjs.com/package/url)).
*/
serverURLPolyfill: 'url',
/**
* Customize bundle filenames.
*
* To understand a bit more about the use of manifests, take a look at [this webpack documentation](https://webpack.js.org/guides/code-splitting/).
*
* @note Be careful when using non-hashed based filenames in production
* as most browsers will cache the asset and not detect the changes on first load.
*
* This example changes fancy chunk names to numerical ids:
*
* @example
* ```js
* filenames: {
* chunk: ({ isDev }) => (isDev ? '[name].js' : '[id].[contenthash].js')
* }
* ```
*/
filenames: {
app: ({ isDev }) => isDev ? `[name].js` : `[contenthash:7].js`,
chunk: ({ isDev }) => isDev ? `[name].js` : `[contenthash:7].js`,
css: ({ isDev }) => isDev ? '[name].css' : 'css/[contenthash:7].css',
img: ({ isDev }) => isDev ? '[path][name].[ext]' : 'img/[name].[contenthash:7].[ext]',
font: ({ isDev }) => isDev ? '[path][name].[ext]' : 'fonts/[name].[contenthash:7].[ext]',
video: ({ isDev }) => isDev ? '[path][name].[ext]' : 'videos/[name].[contenthash:7].[ext]'
},
/**
* Customize the options of Nuxt's integrated webpack loaders.
*/
loaders: {
$resolve: (val, get) => {
const styleLoaders = [
'css', 'cssModules', 'less',
'sass', 'scss', 'stylus', 'vueStyle'
]
for (const name of styleLoaders) {
const loader = val[name]
if (loader && loader.sourceMap === undefined) {
loader.sourceMap = Boolean(get('build.cssSourceMap'))
}
}
return val
},
file: { esModule: false },
fontUrl: { esModule: false, limit: 1000 },
imgUrl: { esModule: false, limit: 1000 },
pugPlain: {},
vue: {
productionMode: { $resolve: (val, get) => val ?? !get('dev') },
transformAssetUrls: {
video: 'src',
source: 'src',
object: 'src',
embed: 'src'
},
compilerOptions: { $resolve: (val, get) => val ?? get('vue.compilerOptions') },
},
css: {
importLoaders: 0,
esModule: false
},
cssModules: {
importLoaders: 0,
esModule: false,
modules: {
localIdentName: '[local]_[hash:base64:5]'
}
},
less: {},
sass: {
sassOptions: {
indentedSyntax: true
}
},
scss: {},
stylus: {},
vueStyle: {}
},
/**
* Add webpack plugins.
*
* @example
* ```js
* import webpack from 'webpack'
* import { version } from './package.json'
* // ...
* plugins: [
* new webpack.DefinePlugin({
* 'process.VERSION': version
* })
* ]
* ```
*/
plugins: [],
/**
* Terser plugin options.
*
* Set to false to disable this plugin, or pass an object of options.
*
* @see [terser-webpack-plugin documentation](https://github.com/webpack-contrib/terser-webpack-plugin)
*
* @note Enabling sourceMap will leave `//# sourceMappingURL` linking comment at
* the end of each output file if webpack `config.devtool` is set to `source-map`.
*
* @type {false | typeof import('terser-webpack-plugin').BasePluginOptions & typeof import('terser-webpack-plugin').DefinedDefaultMinimizerAndOptions<any>}
*/
terser: {
},
/**
* Hard-replaces `typeof process`, `typeof window` and `typeof document` to tree-shake bundle.
*/
aggressiveCodeRemoval: false,
/**
* OptimizeCSSAssets plugin options.
*
* Defaults to true when `extractCSS` is enabled.
*
* @see [css-minimizer-webpack-plugin documentation](https://github.com/webpack-contrib/css-minimizer-webpack-plugin).
*
* @type {false | typeof import('css-minimizer-webpack-plugin').BasePluginOptions & typeof import('css-minimizer-webpack-plugin').DefinedDefaultMinimizerAndOptions<any>}
*/
optimizeCSS: {
$resolve: (val, get) => val ?? (get('build.extractCSS') ? {} : false)
},
/**
* Configure [webpack optimization](https://webpack.js.org/configuration/optimization/).
* @type {false | typeof import('webpack').Configuration['optimization']}
*/
optimization: {
runtimeChunk: 'single',
/** Set minimize to false to disable all minimizers. (It is disabled in development by default) */
minimize: { $resolve: (val, get) => val ?? !get('dev') },
/** You can set minimizer to a customized array of plugins. */
minimizer: undefined,
splitChunks: {
chunks: 'all',
automaticNameDelimiter: '/',
cacheGroups: {}
}
},
/**
* Customize PostCSS Loader.
* Same options as https://github.com/webpack-contrib/postcss-loader#options
*/
postcss: {
execute: undefined,
postcssOptions: {
config: {
$resolve: (val, get) => val ?? get('postcss.config')
},
plugins: {
$resolve: (val, get) => val ?? get('postcss.plugins')
}
},
sourceMap: undefined,
implementation: undefined,
order: ''
},
/**
* See [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) for available options.
* @type {typeof import('webpack-dev-middleware').Options<typeof import('http').IncomingMessage, typeof import('http').ServerResponse>}
*/
devMiddleware: {
stats: 'none'
},
/**
* See [webpack-hot-middleware](https://github.com/webpack-contrib/webpack-hot-middleware) for available options.
* @type {typeof import('webpack-hot-middleware').MiddlewareOptions & { client?: typeof import('webpack-hot-middleware').ClientOptions }}
*/
hotMiddleware: {},
/**
* Set to `false` to disable the overlay provided by [FriendlyErrorsWebpackPlugin](https://github.com/nuxt/friendly-errors-webpack-plugin)
*/
friendlyErrors: true,
/**
* Filters to hide build warnings.
* @type {Array<(warn: typeof import('webpack').WebpackError) => boolean>}
*/
warningIgnoreFilters: [],
}
}

View File

@ -70,7 +70,7 @@ export interface ModuleContainer {
/** Allows extending webpack build config by chaining `options.build.extend` function. */ /** Allows extending webpack build config by chaining `options.build.extend` function. */
extendBuild(fn): void extendBuild(fn): void
/** Allows extending routes by chaining `options.build.extendRoutes` function. */ /** Allows extending routes by chaining `options.router.extendRoutes` function. */
extendRoutes(fn): void extendRoutes(fn): void
/** Registers a module */ /** Registers a module */

View File

@ -35,7 +35,6 @@
"p-debounce": "^4.0.0", "p-debounce": "^4.0.0",
"pathe": "^0.2.0", "pathe": "^0.2.0",
"postcss-import": "^14.0.2", "postcss-import": "^14.0.2",
"postcss-import-resolver": "^2.0.0",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"rollup-plugin-visualizer": "^5.6.0", "rollup-plugin-visualizer": "^5.6.0",
"ufo": "^0.7.9", "ufo": "^0.7.9",

View File

@ -1,8 +1,6 @@
import createResolver from 'postcss-import-resolver'
import defu from 'defu'
import { requireModule } from '@nuxt/kit' import { requireModule } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import { ViteOptions } from './vite' import type { ViteOptions } from './vite'
import { distDir } from './dirs' import { distDir } from './dirs'
export function resolveCSSOptions (nuxt: Nuxt): ViteOptions['css'] { export function resolveCSSOptions (nuxt: Nuxt): ViteOptions['css'] {
@ -12,25 +10,7 @@ export function resolveCSSOptions (nuxt: Nuxt): ViteOptions['css'] {
} }
} }
const plugins = defu(nuxt.options.build.postcss.postcssOptions.plugins, { const plugins = nuxt.options.postcss.plugins
// https://github.com/postcss/postcss-import
'postcss-import': {
resolve: createResolver({
alias: { ...nuxt.options.alias },
modules: [
nuxt.options.srcDir,
nuxt.options.rootDir,
...nuxt.options.modulesDir
]
})
},
// https://github.com/postcss/postcss-url
'postcss-url': {},
// https://github.com/postcss/autoprefixer
autoprefixer: {}
})
for (const name in plugins) { for (const name in plugins) {
const opts = plugins[name] const opts = plugins[name]

View File

@ -5,7 +5,6 @@ import type { InlineConfig, SSROptions } from 'vite'
import { logger } from '@nuxt/kit' import { logger } from '@nuxt/kit'
import type { Options } from '@vitejs/plugin-vue' import type { Options } from '@vitejs/plugin-vue'
import { sanitizeFilePath } from 'mlly' import { sanitizeFilePath } from 'mlly'
import { joinURL, withoutLeadingSlash } from 'ufo'
import { getPort } from 'get-port-please' import { getPort } from 'get-port-please'
import { buildClient } from './client' import { buildClient } from './client'
import { buildServer } from './server' import { buildServer } from './server'
@ -35,14 +34,7 @@ export async function bundle (nuxt: Nuxt) {
nuxt, nuxt,
config: vite.mergeConfig( config: vite.mergeConfig(
{ {
root: nuxt.options.srcDir,
mode: nuxt.options.dev ? 'development' : 'production',
logLevel: 'warn',
define: {
'process.dev': nuxt.options.dev
},
resolve: { resolve: {
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
alias: { alias: {
...nuxt.options.alias, ...nuxt.options.alias,
'#app': nuxt.options.appDir, '#app': nuxt.options.appDir,
@ -56,37 +48,16 @@ export async function bundle (nuxt: Nuxt) {
'abort-controller': 'unenv/runtime/mock/empty' 'abort-controller': 'unenv/runtime/mock/empty'
} }
}, },
base: nuxt.options.dev
? joinURL(nuxt.options.app.baseURL, nuxt.options.app.buildAssetsDir)
: '/__NUXT_BASE__/',
publicDir: resolve(nuxt.options.srcDir, nuxt.options.dir.public),
// TODO: move to kit schema when it exists
vue: {
isProduction: !nuxt.options.dev,
template: { compilerOptions: nuxt.options.vue.compilerOptions }
},
css: resolveCSSOptions(nuxt),
optimizeDeps: { optimizeDeps: {
exclude: [
...nuxt.options.build.transpile.filter(i => typeof i === 'string'),
'vue-demi'
],
entries: [ entries: [
resolve(nuxt.options.appDir, 'entry.ts') resolve(nuxt.options.appDir, 'entry.ts')
] ]
}, },
esbuild: { css: resolveCSSOptions(nuxt),
jsxFactory: 'h',
jsxFragment: 'Fragment',
tsconfigRaw: '{}'
},
clearScreen: false,
build: { build: {
assetsDir: nuxt.options.dev ? withoutLeadingSlash(nuxt.options.app.buildAssetsDir) : '.',
emptyOutDir: false,
rollupOptions: { rollupOptions: {
input: resolve(nuxt.options.appDir, 'entry'), output: { sanitizeFileName: sanitizeFilePath },
output: { sanitizeFileName: sanitizeFilePath } input: resolve(nuxt.options.appDir, 'entry')
} }
}, },
plugins: [ plugins: [
@ -98,18 +69,13 @@ export async function bundle (nuxt: Nuxt) {
port: hmrPort port: hmrPort
}, },
fs: { fs: {
strict: false,
allow: [ allow: [
nuxt.options.buildDir, nuxt.options.appDir
nuxt.options.appDir,
nuxt.options.srcDir,
nuxt.options.rootDir,
...nuxt.options.modulesDir
] ]
} }
} }
} as ViteOptions, },
nuxt.options.vite as any || {} nuxt.options.vite
) )
} }

View File

@ -9,18 +9,14 @@ export default defineBuildConfig({
'@nuxt/kit', '@nuxt/kit',
'unplugin', 'unplugin',
'webpack-virtual-modules', 'webpack-virtual-modules',
'@vue/babel-preset-jsx',
'postcss', 'postcss',
'postcss-import-resolver',
'postcss-loader', 'postcss-loader',
'babel-loader',
'vue-loader', 'vue-loader',
'css-loader', 'css-loader',
'file-loader', 'file-loader',
'style-resources-loader', 'style-resources-loader',
'url-loader', 'url-loader',
'vue-style-loader', 'vue-style-loader',
'@babel/core',
'vue' 'vue'
], ],
externals: [ externals: [

View File

@ -19,9 +19,7 @@
"@babel/core": "^7.17.5", "@babel/core": "^7.17.5",
"@nuxt/friendly-errors-webpack-plugin": "^2.5.2", "@nuxt/friendly-errors-webpack-plugin": "^2.5.2",
"@nuxt/kit": "3.0.0", "@nuxt/kit": "3.0.0",
"@vue/babel-preset-jsx": "^1.2.4",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.2",
"babel-loader": "^8.2.3",
"css-loader": "^6.6.0", "css-loader": "^6.6.0",
"css-minimizer-webpack-plugin": "^3.4.1", "css-minimizer-webpack-plugin": "^3.4.1",
"cssnano": "^5.0.17", "cssnano": "^5.0.17",
@ -29,7 +27,6 @@
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fs-extra": "^10.0.1", "fs-extra": "^10.0.1",
"glob": "^7.2.0",
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"memfs": "^3.4.1", "memfs": "^3.4.1",
@ -39,7 +36,6 @@
"pify": "^5.0.0", "pify": "^5.0.0",
"postcss": "^8.4.7", "postcss": "^8.4.7",
"postcss-import": "^14.0.2", "postcss-import": "^14.0.2",
"postcss-import-resolver": "^2.0.0",
"postcss-loader": "^6.2.1", "postcss-loader": "^6.2.1",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"style-resources-loader": "^1.5.0", "style-resources-loader": "^1.5.0",
@ -59,7 +55,6 @@
"devDependencies": { "devDependencies": {
"@nuxt/schema": "3.0.0", "@nuxt/schema": "3.0.0",
"@types/pify": "^5.0.1", "@types/pify": "^5.0.1",
"@types/terser-webpack-plugin": "^5.0.4",
"@types/webpack-bundle-analyzer": "^4.4.1", "@types/webpack-bundle-analyzer": "^4.4.1",
"@types/webpack-dev-middleware": "^5.0.2", "@types/webpack-dev-middleware": "^5.0.2",
"@types/webpack-hot-middleware": "^2.25.6", "@types/webpack-hot-middleware": "^2.25.6",

View File

@ -3,7 +3,6 @@ import { resolve } from 'pathe'
import webpack from 'webpack' import webpack from 'webpack'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
import type { ClientOptions } from 'webpack-hot-middleware'
import { joinURL } from 'ufo' import { joinURL } from 'ufo'
import { applyPresets, WebpackConfigContext } from '../utils/config' import { applyPresets, WebpackConfigContext } from '../utils/config'
import { nuxt } from '../presets/nuxt' import { nuxt } from '../presets/nuxt'
@ -50,7 +49,7 @@ function clientHMR (ctx: WebpackConfigContext) {
return return
} }
const clientOptions = options.build.hotMiddleware?.client || {} as ClientOptions const clientOptions = options.webpack.hotMiddleware?.client || {}
const hotMiddlewareClientOptions = { const hotMiddlewareClientOptions = {
reload: true, reload: true,
timeout: 30000, timeout: 30000,
@ -81,7 +80,7 @@ function clientPlugins (ctx: WebpackConfigContext) {
// 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.build.analyze) { if (!ctx.isDev && ctx.name === 'client' && options.webpack.analyze) {
const statsDir = resolve(options.buildDir, 'stats') const statsDir = resolve(options.buildDir, 'stats')
// @ts-ignore // @ts-ignore
@ -92,7 +91,7 @@ function clientPlugins (ctx: WebpackConfigContext) {
openAnalyzer: !options.build.quiet, openAnalyzer: !options.build.quiet,
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.build.analyze as any ...options.webpack.analyze === true ? {} : options.webpack.analyze
})) }))
} }
} }

View File

@ -71,10 +71,10 @@ function serverPlugins (ctx: WebpackConfigContext) {
const { config, options } = ctx const { config, options } = ctx
// Server polyfills // Server polyfills
if (options.build.serverURLPolyfill) { if (options.webpack.serverURLPolyfill) {
config.plugins.push(new webpack.ProvidePlugin({ config.plugins.push(new webpack.ProvidePlugin({
URL: [options.build.serverURLPolyfill, 'URL'], URL: [options.webpack.serverURLPolyfill, 'URL'],
URLSearchParams: [options.build.serverURLPolyfill, 'URLSearchParams'] URLSearchParams: [options.webpack.serverURLPolyfill, 'URLSearchParams']
})) }))
} }
} }

View File

@ -1,12 +1,15 @@
import type { WebpackError } from 'webpack' import type { Compiler, WebpackError } from 'webpack'
export default class WarningIgnorePlugin {
filter: (warn: WebpackError) => boolean
constructor (filter) { export type WarningFilter = (warn: WebpackError) => boolean
export default class WarningIgnorePlugin {
filter: WarningFilter
constructor (filter: WarningFilter) {
this.filter = filter this.filter = filter
} }
apply (compiler) /* istanbul ignore next */ { apply (compiler: Compiler) {
compiler.hooks.done.tap('warnfix-plugin', (stats) => { compiler.hooks.done.tap('warnfix-plugin', (stats) => {
stats.compilation.warnings = stats.compilation.warnings.filter(this.filter) stats.compilation.warnings = stats.compilation.warnings.filter(this.filter)
}) })

View File

@ -1,15 +1,13 @@
import { fileName, WebpackConfigContext } from '../utils/config' import { fileName, WebpackConfigContext } from '../utils/config'
export function assets (ctx: WebpackConfigContext) { export function assets (ctx: WebpackConfigContext) {
const { options } = ctx
ctx.config.module.rules.push( ctx.config.module.rules.push(
{ {
test: /\.(png|jpe?g|gif|svg|webp)$/i, test: /\.(png|jpe?g|gif|svg|webp)$/i,
use: [{ use: [{
loader: 'url-loader', loader: 'url-loader',
options: { options: {
...options.build.loaders.imgUrl, ...ctx.options.webpack.loaders.imgUrl,
name: fileName(ctx, 'img') name: fileName(ctx, 'img')
} }
}] }]
@ -19,7 +17,7 @@ export function assets (ctx: WebpackConfigContext) {
use: [{ use: [{
loader: 'url-loader', loader: 'url-loader',
options: { options: {
...options.build.loaders.fontUrl, ...ctx.options.webpack.loaders.fontUrl,
name: fileName(ctx, 'font') name: fileName(ctx, 'font')
} }
}] }]
@ -29,7 +27,7 @@ export function assets (ctx: WebpackConfigContext) {
use: [{ use: [{
loader: 'file-loader', loader: 'file-loader',
options: { options: {
...options.build.loaders.file, ...ctx.options.webpack.loaders.file,
name: fileName(ctx, 'video') name: fileName(ctx, 'video')
} }
}] }]

View File

@ -1,84 +0,0 @@
import { createRequire } from 'module'
import { normalize } from 'pathe'
import TerserWebpackPlugin from 'terser-webpack-plugin'
import { reservedVueTags } from '../utils/reserved-tags'
import { WebpackConfigContext } from '../utils/config'
const _require = createRequire(import.meta.url)
export function babel (ctx: WebpackConfigContext) {
const { config, options } = ctx
const babelLoader = {
loader: normalize(_require.resolve('babel-loader')),
options: getBabelOptions(ctx)
}
config.module.rules.push({
test: /\.m?[jt]sx?$/i,
exclude: (file) => {
file = file.split('node_modules', 2)[1]
// not exclude files outside node_modules
if (!file) {
return false
}
// item in transpile can be string or regex object
return !ctx.transpile.some(module => module.test(file))
},
resolve: {
fullySpecified: false
},
use: babelLoader
})
// https://github.com/webpack-contrib/terser-webpack-plugin
if (options.build.terser) {
const terser = new TerserWebpackPlugin({
// cache, TODO
extractComments: {
condition: 'some',
filename: 'LICENSES'
},
terserOptions: {
compress: {
ecma: ctx.isModern ? 6 : undefined
},
mangle: {
reserved: reservedVueTags
}
},
...options.build.terser as any
})
config.plugins.push(terser as any)
}
}
function getBabelOptions (ctx: WebpackConfigContext) {
const { options } = ctx
const babelOptions: any = {
...options.build.babel,
envName: ctx.name
}
if (babelOptions.configFile || babelOptions.babelrc) {
return babelOptions
}
if (typeof babelOptions.plugins === 'function') {
babelOptions.plugins = babelOptions.plugins(ctx)
}
const defaultPreset = [normalize(_require.resolve('../../babel-preset-app')), {}]
if (typeof babelOptions.presets === 'function') {
babelOptions.presets = babelOptions.presets(ctx, defaultPreset)
}
if (!babelOptions.presets) {
babelOptions.presets = [defaultPreset]
}
return babelOptions
}

View File

@ -6,7 +6,7 @@ import { logger } from '@nuxt/kit'
import FriendlyErrorsWebpackPlugin from '@nuxt/friendly-errors-webpack-plugin' import FriendlyErrorsWebpackPlugin from '@nuxt/friendly-errors-webpack-plugin'
import escapeRegExp from 'escape-string-regexp' import escapeRegExp from 'escape-string-regexp'
import { joinURL } from 'ufo' import { joinURL } from 'ufo'
import WarningIgnorePlugin from '../plugins/warning-ignore' import WarningIgnorePlugin, { WarningFilter } from '../plugins/warning-ignore'
import { WebpackConfigContext, applyPresets, fileName } from '../utils/config' import { WebpackConfigContext, applyPresets, fileName } from '../utils/config'
export function base (ctx: WebpackConfigContext) { export function base (ctx: WebpackConfigContext) {
@ -28,7 +28,7 @@ function baseConfig (ctx: WebpackConfigContext) {
plugins: [], plugins: [],
externals: [], externals: [],
optimization: { optimization: {
...options.build.optimization as any, ...options.webpack.optimization,
minimizer: [] minimizer: []
}, },
experiments: {}, experiments: {},
@ -43,13 +43,13 @@ function baseConfig (ctx: WebpackConfigContext) {
function basePlugins (ctx: WebpackConfigContext) { function basePlugins (ctx: WebpackConfigContext) {
const { config, options, nuxt } = ctx const { config, options, nuxt } = ctx
// Add timefix-plugin before others plugins // Add timefix-plugin before other plugins
if (options.dev) { if (options.dev) {
config.plugins.push(new TimeFixPlugin()) config.plugins.push(new TimeFixPlugin())
} }
// User plugins // User plugins
config.plugins.push(...(options.build.plugins || [])) config.plugins.push(...(options.webpack.plugins || []))
// Ignore empty warnings // Ignore empty warnings
config.plugins.push(new WarningIgnorePlugin(getWarningIgnoreFilter(ctx))) config.plugins.push(new WarningIgnorePlugin(getWarningIgnoreFilter(ctx)))
@ -60,7 +60,7 @@ function basePlugins (ctx: WebpackConfigContext) {
// Friendly errors // Friendly errors
if ( if (
ctx.isServer || ctx.isServer ||
(ctx.isDev && !options.build.quiet && options.build.friendlyErrors) (ctx.isDev && !options.build.quiet && options.webpack.friendlyErrors)
) { ) {
ctx.config.plugins.push( ctx.config.plugins.push(
new FriendlyErrorsWebpackPlugin({ new FriendlyErrorsWebpackPlugin({
@ -71,39 +71,41 @@ function basePlugins (ctx: WebpackConfigContext) {
) )
} }
// Webpackbar if (nuxt.options.webpack.profile) {
const colors = { // Webpackbar
client: 'green', const colors = {
server: 'orange', client: 'green',
modern: 'blue' server: 'orange',
} modern: 'blue'
config.plugins.push(new WebpackBar({
name: ctx.name,
color: colors[ctx.name],
reporters: ['stats'],
stats: !ctx.isDev,
reporter: {
// @ts-ignore
change: (_, { shortPath }) => {
if (!ctx.isServer) {
nuxt.callHook('bundler:change', shortPath)
}
},
done: ({ state }) => {
if (state.hasErrors) {
nuxt.callHook('bundler:error')
} else {
logger.success(`${state.name} ${state.message}`)
}
},
allDone: () => {
nuxt.callHook('bundler:done')
},
progress ({ statesArray }) {
nuxt.callHook('bundler:progress', statesArray)
}
} }
})) config.plugins.push(new WebpackBar({
name: ctx.name,
color: colors[ctx.name],
reporters: ['stats'],
stats: !ctx.isDev,
reporter: {
// @ts-ignore
change: (_, { shortPath }) => {
if (!ctx.isServer) {
nuxt.callHook('bundler:change', shortPath)
}
},
done: ({ state }) => {
if (state.hasErrors) {
nuxt.callHook('bundler:error')
} else {
logger.success(`${state.name} ${state.message}`)
}
},
allDone: () => {
nuxt.callHook('bundler:done')
},
progress ({ statesArray }) {
nuxt.callHook('bundler:progress', statesArray)
}
}
}))
}
} }
function baseAlias (ctx: WebpackConfigContext) { function baseAlias (ctx: WebpackConfigContext) {
@ -198,15 +200,15 @@ function getOutput (ctx: WebpackConfigContext): webpack.Configuration['output']
} }
} }
function getWarningIgnoreFilter (ctx: WebpackConfigContext) { function getWarningIgnoreFilter (ctx: WebpackConfigContext): WarningFilter {
const { options } = ctx const { options } = ctx
const filters = [ 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.build.warningIgnoreFilters || []) ...(options.webpack.warningIgnoreFilters || [])
] ]
return warn => !filters.some(ignoreFilter => ignoreFilter(warn)) return warn => !filters.some(ignoreFilter => ignoreFilter(warn))
@ -224,11 +226,10 @@ function getEnv (ctx: WebpackConfigContext) {
'process.env.VUE_ENV': JSON.stringify(ctx.name), 'process.env.VUE_ENV': JSON.stringify(ctx.name),
'process.browser': ctx.isClient, 'process.browser': ctx.isClient,
'process.client': ctx.isClient, 'process.client': ctx.isClient,
'process.server': ctx.isServer, 'process.server': ctx.isServer
'process.modern': ctx.isModern
} }
if (options.build.aggressiveCodeRemoval) { if (options.webpack.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

@ -17,14 +17,10 @@ export function esbuild (ctx: WebpackConfigContext) {
test: /\.m?[jt]s$/i, test: /\.m?[jt]s$/i,
loader: 'esbuild-loader', loader: 'esbuild-loader',
exclude: (file) => { exclude: (file) => {
file = file.split('node_modules', 2)[1]
// Not exclude files outside node_modules // Not exclude files outside node_modules
if (!file) { file = file.split('node_modules', 2)[1]
return false if (!file) { return false }
}
// Item in transpile can be string or regex object
return !ctx.transpile.some(module => module.test(file)) return !ctx.transpile.some(module => module.test(file))
}, },
resolve: { resolve: {

View File

@ -1,4 +1,4 @@
import { WebpackConfigContext } from '../utils/config' import type { WebpackConfigContext } from '../utils/config'
export function node (ctx: WebpackConfigContext) { export function node (ctx: WebpackConfigContext) {
const { config } = ctx const { config } = ctx

View File

@ -11,7 +11,6 @@ export function nuxt (ctx: WebpackConfigContext) {
applyPresets(ctx, [ applyPresets(ctx, [
base, base,
assets, assets,
// babel,
esbuild, esbuild,
pug, pug,
style, style,

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.build.loaders.pugPlain options: ctx.options.webpack.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.build.loaders.pugPlain options: ctx.options.webpack.loaders.pugPlain
} }
] ]
} }

View File

@ -1,8 +1,7 @@
import { resolve } from 'pathe'
import MiniCssExtractPlugin from 'mini-css-extract-plugin' import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin' import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
import { fileName, WebpackConfigContext, applyPresets } from '../utils/config' import { fileName, WebpackConfigContext, applyPresets } from '../utils/config'
import { PostcssConfig } from '../utils/postcss' import { getPostcssConfig } from '../utils/postcss'
export function style (ctx: WebpackConfigContext) { export function style (ctx: WebpackConfigContext) {
applyPresets(ctx, [ applyPresets(ctx, [
@ -15,9 +14,9 @@ export function style (ctx: WebpackConfigContext) {
function minimizer (ctx: WebpackConfigContext) { function minimizer (ctx: WebpackConfigContext) {
const { options, config } = ctx const { options, config } = ctx
if (options.build.optimizeCSS && Array.isArray(config.optimization.minimizer)) { if (options.webpack.optimizeCSS && Array.isArray(config.optimization.minimizer)) {
config.optimization.minimizer.push(new CssMinimizerPlugin({ config.optimization.minimizer.push(new CssMinimizerPlugin({
...options.build.optimizeCSS as any ...options.webpack.optimizeCSS
})) }))
} }
} }
@ -26,11 +25,11 @@ function extractCSS (ctx: WebpackConfigContext) {
const { options, config } = ctx const { options, config } = ctx
// CSS extraction // CSS extraction
if (options.build.extractCSS) { if (options.webpack.extractCSS) {
config.plugins.push(new MiniCssExtractPlugin({ config.plugins.push(new MiniCssExtractPlugin({
filename: fileName(ctx, 'css'), filename: fileName(ctx, 'css'),
chunkFilename: fileName(ctx, 'css'), chunkFilename: fileName(ctx, 'css'),
...(options.build.extractCSS as any) ...options.webpack.extractCSS
})) }))
} }
} }
@ -45,18 +44,18 @@ function loaders (ctx: WebpackConfigContext) {
config.module.rules.push(createdStyleRule('postcss', /\.p(ost)?css$/i, null, ctx)) config.module.rules.push(createdStyleRule('postcss', /\.p(ost)?css$/i, null, ctx))
// Less // Less
const lessLoader = { loader: 'less-loader', options: options.build.loaders.less } const lessLoader = { loader: 'less-loader', options: options.webpack.loaders.less }
config.module.rules.push(createdStyleRule('less', /\.less$/i, lessLoader, 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.build.loaders.sass } const sassLoader = { loader: 'sass-loader', options: options.webpack.loaders.sass }
config.module.rules.push(createdStyleRule('sass', /\.sass$/i, sassLoader, ctx)) config.module.rules.push(createdStyleRule('sass', /\.sass$/i, sassLoader, ctx))
const scssLoader = { loader: 'sass-loader', options: options.build.loaders.scss } const scssLoader = { loader: 'sass-loader', options: options.webpack.loaders.scss }
config.module.rules.push(createdStyleRule('scss', /\.scss$/i, scssLoader, ctx)) config.module.rules.push(createdStyleRule('scss', /\.scss$/i, scssLoader, ctx))
// Stylus // Stylus
const stylusLoader = { loader: 'stylus-loader', options: options.build.loaders.stylus } const stylusLoader = { loader: 'stylus-loader', options: options.webpack.loaders.stylus }
config.module.rules.push(createdStyleRule('stylus', /\.styl(us)?$/i, stylusLoader, ctx)) config.module.rules.push(createdStyleRule('stylus', /\.styl(us)?$/i, stylusLoader, ctx))
} }
@ -65,16 +64,15 @@ function createdStyleRule (lang: string, test: RegExp, processorLoader, ctx: Web
const styleLoaders = [ const styleLoaders = [
createPostcssLoadersRule(ctx), createPostcssLoadersRule(ctx),
processorLoader, processorLoader
createStyleResourcesLoaderRule(lang, options.build.styleResources, options.rootDir)
].filter(Boolean) ].filter(Boolean)
options.build.loaders.css.importLoaders = options.webpack.loaders.css.importLoaders =
options.build.loaders.cssModules.importLoaders = options.webpack.loaders.cssModules.importLoaders =
styleLoaders.length styleLoaders.length
const cssLoaders = createCssLoadersRule(ctx, options.build.loaders.css) const cssLoaders = createCssLoadersRule(ctx, options.webpack.loaders.css)
const cssModuleLoaders = createCssLoadersRule(ctx, options.build.loaders.cssModules) const cssModuleLoaders = createCssLoadersRule(ctx, options.webpack.loaders.cssModules)
return { return {
test, test,
@ -97,7 +95,7 @@ function createCssLoadersRule (ctx: WebpackConfigContext, cssLoaderOptions) {
const cssLoader = { loader: 'css-loader', options: cssLoaderOptions } const cssLoader = { loader: 'css-loader', options: cssLoaderOptions }
if (options.build.extractCSS) { if (options.webpack.extractCSS) {
if (ctx.isServer) { if (ctx.isServer) {
return [cssLoader] return [cssLoader]
} }
@ -113,37 +111,18 @@ function createCssLoadersRule (ctx: WebpackConfigContext, cssLoaderOptions) {
return [ return [
{ {
loader: 'vue-style-loader', loader: 'vue-style-loader',
options: options.build.loaders.vueStyle options: options.webpack.loaders.vueStyle
}, },
cssLoader cssLoader
] ]
} }
function createStyleResourcesLoaderRule (styleLang, styleResources, rootDir) {
// style-resources-loader
// https://github.com/yenshih/style-resources-loader
if (!styleResources[styleLang]) {
return
}
return {
loader: 'style-resources-loader',
options: {
patterns: Array.from(styleResources[styleLang]).map(p => resolve(rootDir, p as string)),
...styleResources.options
}
}
}
function createPostcssLoadersRule (ctx: WebpackConfigContext) { function createPostcssLoadersRule (ctx: WebpackConfigContext) {
const { options, nuxt } = ctx const { options, nuxt } = ctx
if (!options.build.postcss) { if (!options.postcss) { return }
return
}
const postcssConfig = new PostcssConfig(nuxt) const config = getPostcssConfig(nuxt)
const config = postcssConfig.config()
if (!config) { if (!config) {
return return

View File

@ -14,7 +14,7 @@ export function vue (ctx: WebpackConfigContext) {
config.module.rules.push({ config.module.rules.push({
test: /\.vue$/i, test: /\.vue$/i,
loader: 'vue-loader', loader: 'vue-loader',
options: options.build.loaders.vue options: options.webpack.loaders.vue
}) })
if (ctx.isClient) { if (ctx.isClient) {

View File

@ -1,202 +0,0 @@
const coreJsMeta = {
2: {
prefixes: {
es6: 'es6',
es7: 'es7'
},
builtIns: '@babel/compat-data/corejs2-built-ins'
},
3: {
prefixes: {
es6: 'es',
es7: 'es'
},
builtIns: 'core-js-compat/data'
}
}
function getDefaultPolyfills (corejs) {
const { prefixes: { es6, es7 } } = coreJsMeta[corejs.version]
return [
// Promise polyfill alone doesn't work in IE,
// Needs this as well. see: #1642
`${es6}.array.iterator`,
// This is required for webpack code splitting, vuex etc.
`${es6}.promise`,
// this is needed for object rest spread support in templates
// as vue-template-es2015-compiler 1.8+ compiles it to Object.assign() calls.
`${es6}.object.assign`,
// #2012 es7.promise replaces native Promise in FF and causes missing finally
`${es7}.promise.finally`
]
}
function getPolyfills (targets, includes, { ignoreBrowserslistConfig, configPath, corejs }) {
const { default: getTargets, isRequired } = require('@babel/helper-compilation-targets')
const builtInsList = require(coreJsMeta[corejs.version].builtIns)
const builtInTargets = getTargets(targets, {
ignoreBrowserslistConfig,
configPath
})
return includes.filter(item => isRequired(
'nuxt-polyfills',
builtInTargets,
{
compatData: { 'nuxt-polyfills': builtInsList[item] }
}
))
}
function isPackageHoisted (packageName) {
const path = require('pathe')
const installedPath = path.normalize(require.resolve(packageName))
const relativePath = path.resolve(__dirname, '..', 'node_modules', packageName)
return installedPath !== relativePath
}
module.exports = (api, options = {}) => {
const presets = []
const plugins = []
const envName = api.env()
const {
bugfixes,
polyfills: userPolyfills,
loose = false,
debug = false,
useBuiltIns = 'usage',
modules = false,
spec,
ignoreBrowserslistConfig = envName === 'modern',
configPath,
include,
exclude,
shippedProposals,
forceAllTransforms,
decoratorsBeforeExport,
decoratorsLegacy,
absoluteRuntime
} = options
let { corejs = { version: 3 } } = options
if (typeof corejs !== 'object') {
corejs = { version: Number(corejs) }
}
const isCorejs3Hoisted = isPackageHoisted('core-js')
if (
(corejs.version === 3 && !isCorejs3Hoisted) ||
(corejs.version === 2 && isCorejs3Hoisted)
) {
// eslint-disable-next-line no-console
(console.fatal || console.error)(`babel corejs option is ${corejs.version}, please directly install core-js@${corejs.version}.`)
}
const defaultTargets = {
server: { node: 'current' },
client: { ie: 9 },
modern: { esmodules: true }
}
let { targets = defaultTargets[envName] } = options
// modern mode can only be { esmodules: true }
if (envName === 'modern') {
targets = defaultTargets.modern
}
const polyfills = []
if (envName === 'client' && useBuiltIns === 'usage') {
polyfills.push(
...getPolyfills(
targets,
userPolyfills || getDefaultPolyfills(corejs),
{
ignoreBrowserslistConfig,
configPath,
corejs
}
)
)
plugins.push([polyfillsPlugin, { polyfills }])
}
// Pass options along to babel-preset-env
presets.push([
require('@babel/preset-env'), {
bugfixes,
spec,
loose,
debug,
modules,
targets,
useBuiltIns,
corejs,
ignoreBrowserslistConfig,
configPath,
include,
exclude: polyfills.concat(exclude || []),
shippedProposals,
forceAllTransforms
}
], [
require('@babel/preset-typescript')
])
// JSX
if (options.jsx !== false) {
// presets.push([require('@vue/babel-preset-jsx'), Object.assign({}, options.jsx)])
}
plugins.push(
[require('@babel/plugin-proposal-decorators'), {
decoratorsBeforeExport,
legacy: decoratorsLegacy !== false
}],
[require('@babel/plugin-proposal-class-properties'), { loose: true }]
)
// Transform runtime, but only for helpers
plugins.push([require('@babel/plugin-transform-runtime'), {
regenerator: useBuiltIns !== 'usage',
corejs: false,
helpers: useBuiltIns === 'usage',
useESModules: envName !== 'server',
absoluteRuntime
}])
return {
sourceType: 'unambiguous',
presets,
plugins
}
}
// Add polyfill imports to the first file encountered.
function polyfillsPlugin ({ _types }) {
let entryFile
return {
name: 'inject-polyfills',
visitor: {
Program (path, state) {
if (!entryFile) {
entryFile = state.filename
} else if (state.filename !== entryFile) {
return
}
const { polyfills } = state.opts
const { createImport } = require('@babel/preset-env/lib/utils')
// Imports are injected in reverse order
polyfills.slice().reverse().forEach((p) => {
createImport(path, p)
})
}
}
}
}

View File

@ -8,21 +8,19 @@ export interface WebpackConfigContext extends ReturnType<typeof createWebpackCon
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 }) { export function createWebpackConfigContext (nuxt: Nuxt) {
return { return {
nuxt: nuxt as Nuxt, nuxt,
options: nuxt.options as Nuxt['options'], options: nuxt.options,
config: {} as Configuration, config: {} as Configuration,
name: 'base', name: 'base',
isDev: nuxt.options.dev, isDev: nuxt.options.dev,
isServer: false, isServer: false,
isClient: false, isClient: false,
isModern: undefined, // TODO
isLegacy: false,
alias: {} as Configuration['resolve']['alias'], alias: {} as Configuration['resolve']['alias'],
transpile: [] as any[] transpile: [] as RegExp[]
} }
} }
@ -42,7 +40,7 @@ export function applyPresets (ctx: WebpackConfigContext, presets: WebpackConfigP
export function fileName (ctx: WebpackConfigContext, key: string) { export function fileName (ctx: WebpackConfigContext, key: string) {
const { options } = ctx const { options } = ctx
let fileName = options.build.filenames[key] let fileName = options.webpack.filenames[key]
if (typeof fileName === 'function') { if (typeof fileName === 'function') {
fileName = fileName(ctx) fileName = fileName(ctx)

View File

@ -9,7 +9,7 @@ export function createMFS () {
const fs = createFsFromVolume(new Volume()) const fs = createFsFromVolume(new Volume())
// Clone to extend // Clone to extend
const _fs : Partial<IFs> & { join?(...paths: string[]): string } = { ...fs } const _fs: Partial<IFs> & { join?(...paths: string[]): string } = { ...fs }
// fs.join method is (still) expected by webpack-dev-middleware // fs.join method is (still) expected by webpack-dev-middleware
// There might be differences with https://github.com/webpack/memory-fs/blob/master/lib/join.js // There might be differences with https://github.com/webpack/memory-fs/blob/master/lib/join.js
@ -19,5 +19,5 @@ export function createMFS () {
_fs.exists = p => Promise.resolve(_fs.existsSync(p)) _fs.exists = p => Promise.resolve(_fs.existsSync(p))
_fs.readFile = pify(_fs.readFile) _fs.readFile = pify(_fs.readFile)
return _fs return _fs as IFs & { join?(...paths: string[]): string }
} }

View File

@ -1,136 +1,41 @@
import fs from 'fs'
import { resolve } from 'pathe'
import { logger, requireModule } from '@nuxt/kit'
import { createCommonJS } from 'mlly' import { createCommonJS } from 'mlly'
import { defaults, merge, cloneDeep } from 'lodash-es' import { defaults, merge, cloneDeep } from 'lodash-es'
import createResolver from 'postcss-import-resolver' import { requireModule } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
const isPureObject = obj => obj !== null && !Array.isArray(obj) && typeof obj === 'object' const isPureObject = (obj: unknown): obj is Object => obj !== null && !Array.isArray(obj) && typeof obj === 'object'
export const orderPresets = { export const orderPresets = {
cssnanoLast (names) { cssnanoLast (names: string[]) {
const nanoIndex = names.indexOf('cssnano') const nanoIndex = names.indexOf('cssnano')
if (nanoIndex !== names.length - 1) { if (nanoIndex !== names.length - 1) {
names.push(names.splice(nanoIndex, 1)[0]) names.push(names.splice(nanoIndex, 1)[0])
} }
return names return names
}, },
autoprefixerLast (names) { autoprefixerLast (names: string[]) {
const nanoIndex = names.indexOf('autoprefixer') const nanoIndex = names.indexOf('autoprefixer')
if (nanoIndex !== names.length - 1) { if (nanoIndex !== names.length - 1) {
names.push(names.splice(nanoIndex, 1)[0]) names.push(names.splice(nanoIndex, 1)[0])
} }
return names return names
}, },
autoprefixerAndCssnanoLast (names) { autoprefixerAndCssnanoLast (names: string[]) {
return orderPresets.cssnanoLast(orderPresets.autoprefixerLast(names)) return orderPresets.cssnanoLast(orderPresets.autoprefixerLast(names))
} }
} }
let _postcssConfigFileWarningShown export const getPostcssConfig = (nuxt: Nuxt) => {
function postcssConfigFileWarning () { function defaultConfig () {
if (_postcssConfigFileWarningShown) {
return
}
logger.warn('Please use `build.postcss` in your nuxt.config.js instead of an external config file. Support for such files will be removed in Nuxt 3 as they remove all defaults set by Nuxt and can cause severe problems with features like alias resolving inside your CSS.')
_postcssConfigFileWarningShown = true
}
export class PostcssConfig {
nuxt: Nuxt
options: Nuxt['options']
constructor (nuxt) {
this.nuxt = nuxt
this.options = nuxt.options
}
get postcssOptions () {
return this.options.build.postcss.postcssOptions
}
get postcssImportAlias () {
const alias = { ...this.options.alias }
for (const key in alias) {
if (key.startsWith('~')) {
continue
}
const newKey = '~' + key
if (!alias[newKey]) {
alias[newKey] = alias[key]
}
}
return alias
}
get defaultConfig () {
const { dev, srcDir, rootDir, modulesDir } = this.options
return { return {
sourceMap: this.options.build.cssSourceMap, sourceMap: nuxt.options.webpack.cssSourceMap,
plugins: { plugins: nuxt.options.postcss.plugins,
// https://github.com/postcss/postcss-import
'postcss-import': {
resolve: createResolver({
alias: this.postcssImportAlias,
modules: [srcDir, rootDir, ...modulesDir]
})
},
// https://github.com/postcss/postcss-url
'postcss-url': {},
autoprefixer: {},
cssnano: dev
? false
: {
preset: ['default', {
// Keep quotes in font values to prevent from HEX conversion
// https://github.com/nuxt/nuxt.js/issues/6306
minifyFontValues: { removeQuotes: false }
}]
}
},
// Array, String or Function // Array, String or Function
order: 'autoprefixerAndCssnanoLast' order: 'autoprefixerAndCssnanoLast'
} }
} }
searchConfigFile () { function sortPlugins ({ plugins, order }) {
// Search for postCSS config file and use it if exists
// https://github.com/michael-ciniawsky/postcss-load-config
// TODO: Remove in Nuxt 3
const { srcDir, rootDir } = this.options
for (const dir of [srcDir, rootDir]) {
for (const file of [
'postcss.config.js',
'.postcssrc.js',
'.postcssrc',
'.postcssrc.json',
'.postcssrc.yaml'
]) {
const configFile = resolve(dir, file)
if (fs.existsSync(configFile)) {
postcssConfigFileWarning()
return configFile
}
}
}
}
getConfigFile () {
const loaderConfig = this.postcssOptions && this.postcssOptions.config
const postcssConfigFile = loaderConfig || this.searchConfigFile()
if (typeof postcssConfigFile === 'string') {
return postcssConfigFile
}
}
sortPlugins ({ plugins, order }) {
const names = Object.keys(plugins) const names = Object.keys(plugins)
if (typeof order === 'string') { if (typeof order === 'string') {
order = orderPresets[order] order = orderPresets[order]
@ -138,11 +43,12 @@ export class PostcssConfig {
return typeof order === 'function' ? order(names, orderPresets) : (order || names) return typeof order === 'function' ? order(names, orderPresets) : (order || names)
} }
loadPlugins (config) { function loadPlugins (config) {
if (!isPureObject(config.plugins)) { return } if (!isPureObject(config.plugins)) { return }
// Map postcss plugins into instances on object mode once // Map postcss plugins into instances on object mode once
const cjs = createCommonJS(import.meta.url) const cjs = createCommonJS(import.meta.url)
config.plugins = this.sortPlugins(config).map((pluginName) => { config.plugins = sortPlugins(config).map((pluginName) => {
const pluginFn = requireModule(pluginName, { paths: [cjs.__dirname] }) const pluginFn = requireModule(pluginName, { paths: [cjs.__dirname] })
const pluginOptions = config.plugins[pluginName] const pluginOptions = config.plugins[pluginName]
if (!pluginOptions || typeof pluginFn !== 'function') { return null } if (!pluginOptions || typeof pluginFn !== 'function') { return null }
@ -150,41 +56,37 @@ export class PostcssConfig {
}).filter(Boolean) }).filter(Boolean)
} }
config () { if (!nuxt.options.webpack.postcss || !nuxt.options.postcss) {
/* istanbul ignore if */ return false
if (!this.options.build.postcss) { }
return false
const configFile = nuxt.options.postcss?.config
if (configFile) {
return {
postcssOptions: {
config: configFile
},
sourceMap: nuxt.options.webpack.cssSourceMap
}
}
let postcssOptions = cloneDeep(nuxt.options.postcss)
// Apply default plugins
if (isPureObject(postcssOptions)) {
if (Array.isArray(postcssOptions.plugins)) {
defaults(postcssOptions, defaultConfig())
} else {
// Keep the order of default plugins
postcssOptions = merge({}, defaultConfig(), postcssOptions)
loadPlugins(postcssOptions)
} }
const configFile = this.getConfigFile() delete nuxt.options.webpack.postcss.order
if (configFile) {
return {
postcssOptions: {
config: configFile
},
sourceMap: this.options.build.cssSourceMap
}
}
let postcssOptions = cloneDeep(this.postcssOptions) return {
sourceMap: nuxt.options.webpack.cssSourceMap,
// Apply default plugins ...nuxt.options.webpack.postcss,
if (isPureObject(postcssOptions)) { postcssOptions
if (Array.isArray(postcssOptions.plugins)) {
defaults(postcssOptions, this.defaultConfig)
} else {
// Keep the order of default plugins
postcssOptions = merge({}, this.defaultConfig, postcssOptions)
this.loadPlugins(postcssOptions)
}
delete this.options.build.postcss.order
return {
sourceMap: this.options.build.cssSourceMap,
...this.options.build.postcss,
postcssOptions
}
} }
} }
} }

View File

@ -1,23 +0,0 @@
export const reservedVueTags = [
// HTML tags
'html', 'body', 'base', 'head', 'link', 'meta', 'style', 'title', 'address',
'article', 'aside', 'footer', 'header', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'hgroup', 'nav', 'section', 'div', 'dd', 'dl', 'dt', 'figcaption', 'figure',
'picture', 'hr', 'img', 'li', 'main', 'ol', 'p', 'pre', 'ul', 'a', 'b', 'abbr',
'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd', 'mark',
'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub',
'sup', 'time', 'u', 'var', 'wbr', 'area', 'audio', 'map', 'track', 'video',
'embed', 'object', 'param', 'source', 'canvas', 'script', 'noscript', 'del',
'ins', 'caption', 'col', 'colgroup', 'table', 'thead', 'tbody', 'td', 'th', 'tr',
'button', 'datalist', 'fieldset', 'form', 'input', 'label', 'legend', 'meter',
'optgroup', 'option', 'output', 'progress', 'select', 'textarea', 'details',
'dialog', 'menu', 'menuitem', 'summary', 'content', 'element', 'shadow', 'template',
'blockquote', 'iframe', 'tfoot',
// SVG tags
'svg', 'animate', 'circle', 'clippath', 'cursor', 'defs', 'desc', 'ellipse', 'filter',
'font-face', 'foreignObject', 'g', 'glyph', 'image', 'line', 'marker', 'mask',
'missing-glyph', 'path', 'pattern', 'polygon', 'polyline', 'rect', 'switch', 'symbol',
'text', 'textpath', 'tspan', 'use', 'view',
// Vue built-in tags
'slot', 'component'
]

View File

@ -0,0 +1,26 @@
import { useNuxt } from '@nuxt/kit'
import VirtualModulesPlugin from 'webpack-virtual-modules'
export function registerVirtualModules () {
const nuxt = useNuxt()
// Initialize virtual modules instance
const virtualModules = new VirtualModulesPlugin(nuxt.vfs)
const writeFiles = () => {
for (const filePath in nuxt.vfs) {
virtualModules.writeModule(filePath, nuxt.vfs[filePath])
}
}
// Workaround to initialize virtual modules
nuxt.hook('build:compile', ({ compiler }) => {
if (compiler.name === 'server') { writeFiles() }
})
// Update virtual modules when templates are updated
nuxt.hook('app:templatesGenerated', writeFiles)
nuxt.hook('webpack:config', configs => configs.forEach((config) => {
// Support virtual modules (input)
config.plugins.push(virtualModules)
}))
}

View File

@ -1,302 +1,162 @@
import type { IncomingMessage, ServerResponse } from 'http' import type { IncomingMessage, ServerResponse } from 'http'
import { resolve } from 'pathe'
import pify from 'pify' import pify from 'pify'
import webpack from 'webpack' import webpack from 'webpack'
import Glob from 'glob' import webpackDevMiddleware, { API } from 'webpack-dev-middleware'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware' import webpackHotMiddleware from 'webpack-hot-middleware'
import VirtualModulesPlugin from 'webpack-virtual-modules'
import { logger } from '@nuxt/kit'
import type { Compiler, Watching } from 'webpack' import type { Compiler, Watching } from 'webpack'
import type { Context as WebpackDevMiddlewareContext, Options as WebpackDevMiddlewareOptions } from 'webpack-dev-middleware'
import type { MiddlewareOptions as WebpackHotMiddlewareOptions } from 'webpack-hot-middleware'
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 { DynamicBasePlugin } from '../../vite/src/plugins/dynamic-base' import { DynamicBasePlugin } from '../../vite/src/plugins/dynamic-base'
import { createMFS } from './utils/mfs' import { createMFS } from './utils/mfs'
import { registerVirtualModules } from './virtual-modules'
import { client, server } from './configs' import { client, server } from './configs'
import { createWebpackConfigContext, applyPresets, getWebpackConfig } from './utils/config' import { createWebpackConfigContext, applyPresets, getWebpackConfig } from './utils/config'
const glob = pify(Glob) // TODO: Support plugins
class WebpackBundler { // const plugins: string[] = []
nuxt: Nuxt
plugins: Array<string>
compilers: Array<Compiler>
compilersWatching: Array<Watching & { closeAsync?: () => void }>
// TODO: change this when pify has better types https://github.com/sindresorhus/pify/pull/76
devMiddleware: Record<string, Function & { close?: () => Promise<void>, context?: WebpackDevMiddlewareContext<IncomingMessage, ServerResponse> }>
hotMiddleware: Record<string, Function>
virtualModules: VirtualModulesPlugin
mfs?: Compiler['outputFileSystem']
__closed?: boolean
constructor (nuxt: Nuxt) { export async function bundle (nuxt: Nuxt) {
this.nuxt = nuxt await registerVirtualModules()
// TODO: plugins
this.plugins = []
// Class fields
this.compilers = []
this.compilersWatching = []
this.devMiddleware = {}
this.hotMiddleware = {}
// Bind middleware to self
this.middleware = this.middleware.bind(this)
// Initialize shared MFS for dev
if (this.nuxt.options.dev) {
this.mfs = createMFS() as Compiler['outputFileSystem']
}
// Initialize virtual modules instance
this.virtualModules = new VirtualModulesPlugin(nuxt.vfs)
const writeFiles = () => {
for (const filePath in nuxt.vfs) {
this.virtualModules.writeModule(filePath, nuxt.vfs[filePath])
}
}
// Workaround to initialize virtual modules
nuxt.hook('build:compile', ({ compiler }) => {
if (compiler.name === 'server') { writeFiles() }
})
// Update virtual modules when templates are updated
nuxt.hook('app:templatesGenerated', writeFiles)
}
getWebpackConfig (name) {
const ctx = createWebpackConfigContext({ nuxt: this.nuxt })
if (name === 'client') {
applyPresets(ctx, client)
} else if (name === 'server') {
applyPresets(ctx, server)
} else {
throw new Error(`Unsupported webpack config ${name}`)
}
const webpackConfigs = [client, ...nuxt.options.ssr ? [server] : []].map((preset) => {
const ctx = createWebpackConfigContext(nuxt)
applyPresets(ctx, preset)
return getWebpackConfig(ctx) return getWebpackConfig(ctx)
} })
async build () { await nuxt.callHook('webpack:config', webpackConfigs)
const { options } = this.nuxt
const webpackConfigs = [ // Initialize shared MFS for dev
this.getWebpackConfig('client') const mfs = nuxt.options.dev ? createMFS() : null
]
if (options.ssr) { // Configure compilers
webpackConfigs.push(this.getWebpackConfig('server')) const compilers = webpackConfigs.map((config) => {
config.plugins.push(DynamicBasePlugin.webpack({
env: nuxt.options.dev ? 'dev' : config.name as 'client',
devAppConfig: nuxt.options.app,
globalPublicPath: '__webpack_public_path__'
}))
// Create compiler
const compiler = webpack(config)
// In dev, write files in memory FS
if (nuxt.options.dev) {
compiler.outputFileSystem = mfs
} }
await this.nuxt.callHook('webpack:config', webpackConfigs) return compiler
})
// Check styleResource existence nuxt.hook('close', async () => {
const { styleResources } = this.nuxt.options.build for (const compiler of compilers) {
if (styleResources && Object.keys(styleResources).length) {
logger.warn(
'Using styleResources without the @nuxtjs/style-resources is not suggested and can lead to severe performance issues.',
'Please use https://github.com/nuxt-community/style-resources-module'
)
for (const ext of Object.keys(styleResources)) {
await Promise.all(Array.from(styleResources[ext]).map(async (p) => {
const styleResourceFiles = await glob(resolve(this.nuxt.options.rootDir, p as string))
if (!styleResourceFiles || styleResourceFiles.length === 0) {
throw new Error(`Style Resource not found: ${p}`)
}
}))
}
}
// Configure compilers
this.compilers = webpackConfigs.map((config) => {
// Support virtual modules (input)
config.plugins.push(this.virtualModules)
config.plugins.push(DynamicBasePlugin.webpack({
env: this.nuxt.options.dev ? 'dev' : config.name as 'client',
devAppConfig: this.nuxt.options.app,
globalPublicPath: '__webpack_public_path__'
}))
// Create compiler
const compiler = webpack(config)
// In dev, write files in memory FS
if (options.dev) {
compiler.outputFileSystem = this.mfs!
}
return compiler
})
// Start Builds
if (options.dev) {
return Promise.all(this.compilers.map(c => this.webpackCompile(c)))
} else {
for (const c of this.compilers) {
await this.webpackCompile(c)
}
}
}
async webpackCompile (compiler) {
const { name } = compiler.options
const { options } = this.nuxt
await this.nuxt.callHook('build:compile', { name, compiler })
// Load renderer resources after build
compiler.hooks.done.tap('load-resources', async (stats) => {
await this.nuxt.callHook('build:compiled', {
name,
compiler,
stats
})
// Reload renderer
await this.nuxt.callHook('build:resources', this.mfs)
})
// --- Dev Build ---
if (options.dev) {
// Client build
if (['client', 'modern'].includes(name)) {
return new Promise((resolve, reject) => {
compiler.hooks.done.tap('nuxt-dev', () => { resolve(null) })
compiler.hooks.failed.tap('nuxt-errorlog', (err) => { reject(err) })
// Start watch
this.webpackDev(compiler)
})
}
// Server, build and watch for changes
return new Promise((resolve, reject) => {
const watching = compiler.watch(options.watchers.webpack, (err) => {
if (err) {
return reject(err)
}
resolve(null)
})
watching.closeAsync = pify(watching.close)
this.compilersWatching.push(watching)
})
}
// --- Production Build ---
compiler.run = pify(compiler.run)
const stats = await compiler.run()
if (stats.hasErrors()) {
// non-quiet mode: errors will be printed by webpack itself
const error = new Error('Nuxt build error')
if (options.build.quiet === true) {
error.stack = stats.toString('errors-only')
}
throw error
}
// Await for renderer to load resources (programmatic, tests and generate)
await this.nuxt.callHook('build:resources')
}
async webpackDev (compiler: Compiler) {
logger.debug('Creating webpack middleware...')
const { name } = compiler.options
const buildOptions = this.nuxt.options.build
const { client, ...hotMiddlewareOptions } = buildOptions.hotMiddleware || {}
// Create webpack dev middleware
this.devMiddleware[name] = pify(
// @ts-ignore
webpackDevMiddleware(
// @ts-ignore
compiler,
{
publicPath: joinURL(this.nuxt.options.app.baseURL, this.nuxt.options.app.buildAssetsDir),
outputFileSystem: this.mfs,
stats: 'none',
...buildOptions.devMiddleware
} as WebpackDevMiddlewareOptions<IncomingMessage, ServerResponse>
)
)
this.devMiddleware[name].close = pify(this.devMiddleware[name].close)
// @ts-ignore
this.compilersWatching.push(this.devMiddleware[name].context.watching)
this.hotMiddleware[name] = pify(
webpackHotMiddleware(
compiler as any,
{
log: false,
heartbeat: 10000,
path: joinURL(this.nuxt.options.app.baseURL, '__webpack_hmr', name),
...hotMiddlewareOptions
} as WebpackHotMiddlewareOptions
)
)
// Register devMiddleware on server
await this.nuxt.callHook('server:devMiddleware', this.middleware)
}
async middleware (req: IncomingMessage, res: ServerResponse, next: () => any) {
if (this.devMiddleware && this.devMiddleware.client) {
await this.devMiddleware.client(req, res)
}
if (this.hotMiddleware && this.hotMiddleware.client) {
await this.hotMiddleware.client(req, res)
}
next()
}
async unwatch () {
await Promise.all(this.compilersWatching.map(watching => watching.closeAsync()))
}
async close () {
if (this.__closed) {
return
}
this.__closed = true
// Unwatch
await this.unwatch()
// Stop webpack middleware
for (const devMiddleware of Object.values(this.devMiddleware)) {
await devMiddleware.close()
}
for (const compiler of this.compilers) {
await new Promise(resolve => compiler.close(resolve)) await new Promise(resolve => compiler.close(resolve))
} }
})
// Cleanup MFS // Start Builds
if (this.mfs) { if (nuxt.options.dev) {
delete this.mfs return Promise.all(compilers.map(c => compile(c)))
}
for (const c of compilers) {
await compile(c)
}
}
async function createDevMiddleware (compiler: Compiler) {
const nuxt = useNuxt()
logger.debug('Creating webpack middleware...')
// Create webpack dev middleware
const devMiddleware = pify(webpackDevMiddleware(compiler, {
publicPath: joinURL(nuxt.options.app.baseURL, nuxt.options.app.buildAssetsDir),
outputFileSystem: compiler.outputFileSystem as any,
stats: 'none',
...nuxt.options.webpack.devMiddleware
})) as API<IncomingMessage, ServerResponse>
nuxt.hook('close', () => pify(devMiddleware.close.bind(devMiddleware))())
const { client: _client, ...hotMiddlewareOptions } = nuxt.options.webpack.hotMiddleware || {}
const hotMiddleware = pify(webpackHotMiddleware(compiler, {
log: false,
heartbeat: 10000,
path: joinURL(nuxt.options.app.baseURL, '__webpack_hmr', compiler.options.name),
...hotMiddlewareOptions
}))
// Register devMiddleware on server
await nuxt.callHook('server:devMiddleware', async (req, res, next) => {
for (const mw of [devMiddleware, hotMiddleware]) {
await mw?.(req, res)
}
next()
})
return devMiddleware
}
async function compile (compiler: Compiler) {
const nuxt = useNuxt()
const { name } = compiler.options
await nuxt.callHook('build:compile', { name, compiler })
// Load renderer resources after build
compiler.hooks.done.tap('load-resources', async (stats) => {
await nuxt.callHook('build:compiled', { name, compiler, stats })
// Reload renderer
await nuxt.callHook('build:resources', compiler.outputFileSystem)
})
// --- Dev Build ---
if (nuxt.options.dev) {
const compilersWatching: Watching[] = []
nuxt.hook('close', async () => {
await Promise.all(compilersWatching.map(watching => pify(watching.close.bind(watching))()))
})
// Client build
if (name === 'client') {
return new Promise((resolve, reject) => {
compiler.hooks.done.tap('nuxt-dev', () => { resolve(null) })
compiler.hooks.failed.tap('nuxt-errorlog', (err) => { reject(err) })
// Start watch
createDevMiddleware(compiler).then((devMiddleware) => {
compilersWatching.push(devMiddleware.context.watching)
})
})
} }
// Cleanup more resources // Server, build and watch for changes
delete this.compilers return new Promise((resolve, reject) => {
delete this.compilersWatching const watching = compiler.watch(nuxt.options.watchers.webpack, (err) => {
delete this.devMiddleware if (err) { return reject(err) }
delete this.hotMiddleware resolve(null)
})
compilersWatching.push(watching)
})
} }
forGenerate () { // --- Production Build ---
this.nuxt.options.target = 'static' const stats = await new Promise<webpack.Stats>((resolve, reject) => compiler.run((err, stats) => err ? reject(err) : resolve(stats)))
}
}
export function bundle (nuxt: Nuxt) { if (stats.hasErrors()) {
const bundler = new WebpackBundler(nuxt) // non-quiet mode: errors will be printed by webpack itself
return bundler.build() const error = new Error('Nuxt build error')
if (nuxt.options.build.quiet === true) {
error.stack = stats.toString('errors-only')
}
throw error
}
// Await for renderer to load resources (programmatic, tests and generate)
await nuxt.callHook('build:resources')
} }

View File

@ -3,7 +3,7 @@ import { addComponent } from '@nuxt/kit'
export default defineNuxtConfig({ export default defineNuxtConfig({
buildDir: process.env.NITRO_BUILD_DIR, buildDir: process.env.NITRO_BUILD_DIR,
vite: !process.env.TEST_WITH_WEBPACK, builder: process.env.TEST_WITH_WEBPACK ? 'webpack' : 'vite',
nitro: { nitro: {
output: { dir: process.env.NITRO_OUTPUT_DIR } output: { dir: process.env.NITRO_OUTPUT_DIR }
}, },

View File

@ -3163,6 +3163,7 @@ __metadata:
defu: ^5.0.1 defu: ^5.0.1
jiti: ^1.13.0 jiti: ^1.13.0
pathe: ^0.2.0 pathe: ^0.2.0
postcss-import-resolver: ^2.0.0
scule: ^0.2.1 scule: ^0.2.1
std-env: ^3.0.1 std-env: ^3.0.1
ufo: ^0.7.9 ufo: ^0.7.9
@ -3393,7 +3394,6 @@ __metadata:
p-debounce: ^4.0.0 p-debounce: ^4.0.0
pathe: ^0.2.0 pathe: ^0.2.0
postcss-import: ^14.0.2 postcss-import: ^14.0.2
postcss-import-resolver: ^2.0.0
postcss-url: ^10.1.3 postcss-url: ^10.1.3
rollup-plugin-visualizer: ^5.6.0 rollup-plugin-visualizer: ^5.6.0
ufo: ^0.7.9 ufo: ^0.7.9
@ -3480,7 +3480,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@nuxt/webpack-builder@3.0.0, @nuxt/webpack-builder@workspace:packages/webpack": "@nuxt/webpack-builder@workspace:packages/webpack":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@nuxt/webpack-builder@workspace:packages/webpack" resolution: "@nuxt/webpack-builder@workspace:packages/webpack"
dependencies: dependencies:
@ -3489,14 +3489,11 @@ __metadata:
"@nuxt/kit": 3.0.0 "@nuxt/kit": 3.0.0
"@nuxt/schema": 3.0.0 "@nuxt/schema": 3.0.0
"@types/pify": ^5.0.1 "@types/pify": ^5.0.1
"@types/terser-webpack-plugin": ^5.0.4
"@types/webpack-bundle-analyzer": ^4.4.1 "@types/webpack-bundle-analyzer": ^4.4.1
"@types/webpack-dev-middleware": ^5.0.2 "@types/webpack-dev-middleware": ^5.0.2
"@types/webpack-hot-middleware": ^2.25.6 "@types/webpack-hot-middleware": ^2.25.6
"@types/webpack-virtual-modules": ^0 "@types/webpack-virtual-modules": ^0
"@vue/babel-preset-jsx": ^1.2.4
autoprefixer: ^10.4.2 autoprefixer: ^10.4.2
babel-loader: ^8.2.3
css-loader: ^6.6.0 css-loader: ^6.6.0
css-minimizer-webpack-plugin: ^3.4.1 css-minimizer-webpack-plugin: ^3.4.1
cssnano: ^5.0.17 cssnano: ^5.0.17
@ -3504,7 +3501,6 @@ __metadata:
escape-string-regexp: ^5.0.0 escape-string-regexp: ^5.0.0
file-loader: ^6.2.0 file-loader: ^6.2.0
fs-extra: ^10.0.1 fs-extra: ^10.0.1
glob: ^7.2.0
hash-sum: ^2.0.0 hash-sum: ^2.0.0
lodash-es: ^4.17.21 lodash-es: ^4.17.21
memfs: ^3.4.1 memfs: ^3.4.1
@ -3514,7 +3510,6 @@ __metadata:
pify: ^5.0.0 pify: ^5.0.0
postcss: ^8.4.7 postcss: ^8.4.7
postcss-import: ^14.0.2 postcss-import: ^14.0.2
postcss-import-resolver: ^2.0.0
postcss-loader: ^6.2.1 postcss-loader: ^6.2.1
postcss-url: ^10.1.3 postcss-url: ^10.1.3
style-resources-loader: ^1.5.0 style-resources-loader: ^1.5.0
@ -4630,16 +4625,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/terser-webpack-plugin@npm:^5.0.4":
version: 5.0.4
resolution: "@types/terser-webpack-plugin@npm:5.0.4"
dependencies:
terser: ^5.3.8
webpack: ^5.1.0
checksum: 7b3845526d4ed7b636f7b2f8f35b5d7e45211f7a55afc85e32a66655eaf65cd193e2fec195bcdb473c0d9a5b2828798b9c23196fdc86e9cd2e9420227012f438
languageName: node
linkType: hard
"@types/throttle-debounce@npm:^2.1.0": "@types/throttle-debounce@npm:^2.1.0":
version: 2.1.0 version: 2.1.0
resolution: "@types/throttle-debounce@npm:2.1.0" resolution: "@types/throttle-debounce@npm:2.1.0"
@ -15675,7 +15660,6 @@ __metadata:
"@nuxt/nitro": 3.0.0 "@nuxt/nitro": 3.0.0
"@nuxt/schema": 3.0.0 "@nuxt/schema": 3.0.0
"@nuxt/vite-builder": 3.0.0 "@nuxt/vite-builder": 3.0.0
"@nuxt/webpack-builder": 3.0.0
"@types/fs-extra": ^9.0.13 "@types/fs-extra": ^9.0.13
"@types/hash-sum": ^1.0.0 "@types/hash-sum": ^1.0.0
"@vue/reactivity": ^3.2.31 "@vue/reactivity": ^3.2.31
@ -20771,7 +20755,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"terser@npm:^5.0.0, terser@npm:^5.3.4, terser@npm:^5.3.8, terser@npm:^5.7.2": "terser@npm:^5.0.0, terser@npm:^5.3.4, terser@npm:^5.7.2":
version: 5.10.0 version: 5.10.0
resolution: "terser@npm:5.10.0" resolution: "terser@npm:5.10.0"
dependencies: dependencies:
@ -22553,44 +22537,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"webpack@npm:^5, webpack@npm:^5.1.0, webpack@npm:^5.38.1": "webpack@npm:^5, webpack@npm:^5.38.1, webpack@npm:^5.69.1":
version: 5.69.0
resolution: "webpack@npm:5.69.0"
dependencies:
"@types/eslint-scope": ^3.7.3
"@types/estree": ^0.0.51
"@webassemblyjs/ast": 1.11.1
"@webassemblyjs/wasm-edit": 1.11.1
"@webassemblyjs/wasm-parser": 1.11.1
acorn: ^8.4.1
acorn-import-assertions: ^1.7.6
browserslist: ^4.14.5
chrome-trace-event: ^1.0.2
enhanced-resolve: ^5.9.0
es-module-lexer: ^0.9.0
eslint-scope: 5.1.1
events: ^3.2.0
glob-to-regexp: ^0.4.1
graceful-fs: ^4.2.9
json-parse-better-errors: ^1.0.2
loader-runner: ^4.2.0
mime-types: ^2.1.27
neo-async: ^2.6.2
schema-utils: ^3.1.0
tapable: ^2.1.1
terser-webpack-plugin: ^5.1.3
watchpack: ^2.3.1
webpack-sources: ^3.2.3
peerDependenciesMeta:
webpack-cli:
optional: true
bin:
webpack: bin/webpack.js
checksum: 0b0fc2ffcb926fc7506ec56901ae9a69f1ae969a6afc63395a3ed56f938d385c1d16d1dea39fd56dbb9362189c05da49174219904e5a337040aed5a46080e926
languageName: node
linkType: hard
"webpack@npm:^5.69.1":
version: 5.69.1 version: 5.69.1
resolution: "webpack@npm:5.69.1" resolution: "webpack@npm:5.69.1"
dependencies: dependencies: