mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat: rewrite webpack config (#30)
This commit is contained in:
parent
dab1a831a6
commit
d6ed1dfc2c
@ -289,7 +289,7 @@ export default () => ({
|
|||||||
runtimeChunk: 'single',
|
runtimeChunk: 'single',
|
||||||
minimize: undefined as boolean | undefined,
|
minimize: undefined as boolean | undefined,
|
||||||
minimizer: undefined,
|
minimizer: undefined,
|
||||||
cssMinimizer: undefined,
|
// cssMinimizer: undefined,
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
name: undefined,
|
name: undefined,
|
||||||
@ -299,7 +299,9 @@ export default () => ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as WebpackConfiguration['optimization'] & { cssMinimizer: undefined | boolean | Record<string, any> },
|
} as WebpackConfiguration['optimization'] & {
|
||||||
|
// cssMinimizer: undefined | boolean | Record<string, any>
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Enable [thread-loader](https://github.com/webpack-contrib/thread-loader#thread-loader) in webpack building
|
* Enable [thread-loader](https://github.com/webpack-contrib/thread-loader#thread-loader) in webpack building
|
||||||
*
|
*
|
||||||
|
@ -346,9 +346,9 @@ function normalizeConfig (_options: CliConfiguration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable cssMinimizer only when extractCSS is enabled
|
// Enable cssMinimizer only when extractCSS is enabled
|
||||||
if (options.build.optimization.cssMinimizer === undefined) {
|
// if (options.build.optimization.cssMinimizer === undefined) {
|
||||||
options.build.optimization.cssMinimizer = options.build.extractCSS ? {} : false
|
// options.build.optimization.cssMinimizer = options.build.extractCSS ? {} : false
|
||||||
}
|
// }
|
||||||
|
|
||||||
const { loaders } = options.build
|
const { loaders } = options.build
|
||||||
// const vueLoader = loaders.vue
|
// const vueLoader = loaders.vue
|
||||||
|
@ -5,13 +5,11 @@ import Glob from 'glob'
|
|||||||
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 consola from 'consola'
|
import consola from 'consola'
|
||||||
|
|
||||||
import { Nuxt } from 'src/core'
|
import { Nuxt } from 'src/core'
|
||||||
import { TARGETS, parallel, sequence, wrapArray, isModernRequest } from 'src/utils'
|
import { TARGETS, parallel, sequence, wrapArray, isModernRequest } from 'src/utils'
|
||||||
import { createMFS } from './utils/mfs'
|
import { createMFS } from './utils/mfs'
|
||||||
|
import { client, server } from './configs'
|
||||||
import * as WebpackConfigs from './config'
|
import { createWebpackConfigContext, applyPresets, getWebpackConfig } from './utils/config'
|
||||||
import PerfLoader from './utils/perf-loader'
|
|
||||||
|
|
||||||
const glob = pify(Glob)
|
const glob = pify(Glob)
|
||||||
|
|
||||||
@ -40,27 +38,32 @@ export class WebpackBundler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getWebpackConfig (name) {
|
getWebpackConfig (name) {
|
||||||
const Config = WebpackConfigs[name.toLowerCase()] // eslint-disable-line import/namespace
|
const ctx = createWebpackConfigContext({ nuxt: this.nuxt })
|
||||||
if (!Config) {
|
|
||||||
|
if (name === 'client') {
|
||||||
|
applyPresets(ctx, client)
|
||||||
|
} else if (name === 'server') {
|
||||||
|
applyPresets(ctx, server)
|
||||||
|
} else {
|
||||||
throw new Error(`Unsupported webpack config ${name}`)
|
throw new Error(`Unsupported webpack config ${name}`)
|
||||||
}
|
}
|
||||||
const config = new Config(this)
|
|
||||||
return config.config()
|
return getWebpackConfig(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
async build () {
|
async build () {
|
||||||
const { options } = this.nuxt
|
const { options } = this.nuxt
|
||||||
|
|
||||||
const webpackConfigs = [
|
const webpackConfigs = [
|
||||||
this.getWebpackConfig('Client')
|
this.getWebpackConfig('client')
|
||||||
]
|
]
|
||||||
|
|
||||||
if (options.modern) {
|
if (options.modern) {
|
||||||
webpackConfigs.push(this.getWebpackConfig('Modern'))
|
webpackConfigs.push(this.getWebpackConfig('modern'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.build.ssr) {
|
if (options.build.ssr) {
|
||||||
webpackConfigs.push(this.getWebpackConfig('Server'))
|
webpackConfigs.push(this.getWebpackConfig('server'))
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.nuxt.callHook('webpack:config', webpackConfigs)
|
await this.nuxt.callHook('webpack:config', webpackConfigs)
|
||||||
@ -95,13 +98,6 @@ export class WebpackBundler {
|
|||||||
return compiler
|
return compiler
|
||||||
})
|
})
|
||||||
|
|
||||||
// Warm up perfLoader before build
|
|
||||||
if (options.build.parallel) {
|
|
||||||
consola.info('Warming up worker pools')
|
|
||||||
PerfLoader.warmupAll({ dev: options.dev })
|
|
||||||
consola.success('Worker pools ready')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start Builds
|
// Start Builds
|
||||||
const runner = options.dev ? parallel : sequence
|
const runner = options.dev ? parallel : sequence
|
||||||
|
|
||||||
@ -128,7 +124,7 @@ export class WebpackBundler {
|
|||||||
|
|
||||||
// --- Dev Build ---
|
// --- Dev Build ---
|
||||||
if (options.dev) {
|
if (options.dev) {
|
||||||
// Client buiild
|
// Client build
|
||||||
if (['client', 'modern'].includes(name)) {
|
if (['client', 'modern'].includes(name)) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
compiler.hooks.done.tap('nuxt-dev', () => { resolve() })
|
compiler.hooks.done.tap('nuxt-dev', () => { resolve() })
|
||||||
|
@ -1,521 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import consola from 'consola'
|
|
||||||
import TimeFixPlugin from 'time-fix-plugin'
|
|
||||||
import cloneDeep from 'lodash/cloneDeep'
|
|
||||||
import escapeRegExp from 'lodash/escapeRegExp'
|
|
||||||
import VueLoaderPlugin from 'vue-loader/dist/pluginWebpack5'
|
|
||||||
// import ExtractCssChunksPlugin from 'extract-css-chunks-webpack-plugin'
|
|
||||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
|
|
||||||
import TerserWebpackPlugin from 'terser-webpack-plugin'
|
|
||||||
import WebpackBar from 'webpackbar'
|
|
||||||
import env from 'std-env'
|
|
||||||
import semver from 'semver'
|
|
||||||
import type { NormalizedConfiguration } from 'src/config'
|
|
||||||
import { TARGETS, isUrl, urlJoin, getPKG } from 'src/utils'
|
|
||||||
import PerfLoader from '../utils/perf-loader'
|
|
||||||
import StyleLoader from '../utils/style-loader'
|
|
||||||
import WarningIgnorePlugin from '../plugins/warning-ignore'
|
|
||||||
import { reservedVueTags } from '../utils/reserved-tags'
|
|
||||||
|
|
||||||
export default class WebpackBaseConfig {
|
|
||||||
options: NormalizedConfiguration
|
|
||||||
|
|
||||||
constructor (builder) {
|
|
||||||
this.builder = builder
|
|
||||||
this.options = builder.nuxt.options
|
|
||||||
}
|
|
||||||
|
|
||||||
get colors () {
|
|
||||||
return {
|
|
||||||
client: 'green',
|
|
||||||
server: 'orange',
|
|
||||||
modern: 'blue'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get devtool () {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
get nuxtEnv () {
|
|
||||||
return {
|
|
||||||
isDev: this.dev,
|
|
||||||
isServer: this.isServer,
|
|
||||||
isClient: !this.isServer,
|
|
||||||
isModern: Boolean(this.isModern),
|
|
||||||
isLegacy: Boolean(!this.isModern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get mode () {
|
|
||||||
return this.dev ? 'development' : 'production'
|
|
||||||
}
|
|
||||||
|
|
||||||
get target () {
|
|
||||||
return this.options.target
|
|
||||||
}
|
|
||||||
|
|
||||||
get dev () {
|
|
||||||
return this.options.dev
|
|
||||||
}
|
|
||||||
|
|
||||||
get loaders () {
|
|
||||||
if (!this._loaders) {
|
|
||||||
this._loaders = cloneDeep(this.options.build.loaders)
|
|
||||||
// sass-loader<8 support (#6460)
|
|
||||||
const sassLoaderPKG = getPKG('sass-loader')
|
|
||||||
if (sassLoaderPKG && semver.lt(sassLoaderPKG.version, '8.0.0')) {
|
|
||||||
const { sass } = this._loaders
|
|
||||||
sass.indentedSyntax = sass.sassOptions.indentedSyntax
|
|
||||||
delete sass.sassOptions.indentedSyntax
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this._loaders
|
|
||||||
}
|
|
||||||
|
|
||||||
get modulesToTranspile () {
|
|
||||||
return [
|
|
||||||
/\.vue\.js/i, // include SFCs in node_modules
|
|
||||||
/consola\/src/,
|
|
||||||
...this.normalizeTranspile({ pathNormalize: true })
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeTranspile ({ pathNormalize = false } = {}) {
|
|
||||||
const transpile = []
|
|
||||||
for (let pattern of this.options.build.transpile) {
|
|
||||||
if (typeof pattern === 'function') {
|
|
||||||
pattern = pattern(this.nuxtEnv)
|
|
||||||
}
|
|
||||||
if (pattern instanceof RegExp) {
|
|
||||||
transpile.push(pattern)
|
|
||||||
} else if (typeof pattern === 'string') {
|
|
||||||
const posixModule = pattern.replace(/\\/g, '/')
|
|
||||||
transpile.push(new RegExp(escapeRegExp(
|
|
||||||
pathNormalize ? path.normalize(posixModule) : posixModule
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return transpile
|
|
||||||
}
|
|
||||||
|
|
||||||
getBabelOptions () {
|
|
||||||
const envName = this.name
|
|
||||||
const options = {
|
|
||||||
...this.options.build.babel,
|
|
||||||
envName
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.configFile || options.babelrc) {
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof options.plugins === 'function') {
|
|
||||||
options.plugins = options.plugins(
|
|
||||||
{
|
|
||||||
envName,
|
|
||||||
...this.nuxtEnv
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultPreset = [require.resolve('../../babel-preset-app'), {}]
|
|
||||||
|
|
||||||
if (typeof options.presets === 'function') {
|
|
||||||
options.presets = options.presets(
|
|
||||||
{
|
|
||||||
envName,
|
|
||||||
...this.nuxtEnv
|
|
||||||
},
|
|
||||||
defaultPreset
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.presets) {
|
|
||||||
options.presets = [defaultPreset]
|
|
||||||
}
|
|
||||||
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
getFileName (key) {
|
|
||||||
let fileName = this.options.build.filenames[key]
|
|
||||||
if (typeof fileName === 'function') {
|
|
||||||
fileName = fileName(this.nuxtEnv)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof fileName === 'string' && this.dev) {
|
|
||||||
const hash = /\[(chunkhash|contenthash|hash)(?::(\d+))?]/.exec(fileName)
|
|
||||||
if (hash) {
|
|
||||||
consola.warn(`Notice: Please do not use ${hash[1]} in dev mode to prevent memory leak`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileName
|
|
||||||
}
|
|
||||||
|
|
||||||
env () {
|
|
||||||
const env = {
|
|
||||||
'process.env.NODE_ENV': JSON.stringify(this.mode),
|
|
||||||
'process.mode': JSON.stringify(this.mode),
|
|
||||||
'process.dev': this.dev,
|
|
||||||
'process.static': this.target === TARGETS.static,
|
|
||||||
'process.target': JSON.stringify(this.target)
|
|
||||||
}
|
|
||||||
if (this.options.build.aggressiveCodeRemoval) {
|
|
||||||
env['typeof process'] = JSON.stringify(this.isServer ? 'object' : 'undefined')
|
|
||||||
env['typeof window'] = JSON.stringify(!this.isServer ? 'object' : 'undefined')
|
|
||||||
env['typeof document'] = JSON.stringify(!this.isServer ? 'object' : 'undefined')
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.entries(this.options.env).forEach(([key, value]) => {
|
|
||||||
env['process.env.' + key] =
|
|
||||||
['boolean', 'number'].includes(typeof value)
|
|
||||||
? value
|
|
||||||
: JSON.stringify(value)
|
|
||||||
})
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
output () {
|
|
||||||
const {
|
|
||||||
build: { publicPath },
|
|
||||||
buildDir,
|
|
||||||
router
|
|
||||||
} = this.options
|
|
||||||
return {
|
|
||||||
path: path.resolve(buildDir, 'dist', this.isServer ? 'server' : 'client'),
|
|
||||||
filename: this.getFileName('app'),
|
|
||||||
chunkFilename: this.getFileName('chunk'),
|
|
||||||
publicPath: isUrl(publicPath) ? publicPath : urlJoin(router.base, publicPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache () {
|
|
||||||
if (!this.options.build.cache) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'filesystem',
|
|
||||||
cacheDirectory: path.resolve('node_modules/.cache/@nuxt/webpack/'),
|
|
||||||
buildDependencies: {
|
|
||||||
config: [...this.options._nuxtConfigFiles]
|
|
||||||
},
|
|
||||||
...this.options.build.cache,
|
|
||||||
name: this.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optimization () {
|
|
||||||
const optimization = cloneDeep(this.options.build.optimization)
|
|
||||||
|
|
||||||
if (optimization.minimize && optimization.minimizer === undefined) {
|
|
||||||
optimization.minimizer = this.minimizer()
|
|
||||||
}
|
|
||||||
|
|
||||||
return optimization
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve () {
|
|
||||||
// Prioritize nested node_modules in webpack search path (#2558)
|
|
||||||
const webpackModulesDir = ['node_modules'].concat(this.options.modulesDir)
|
|
||||||
|
|
||||||
return {
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.wasm', '.mjs', '.js', '.ts', '.json', '.vue', '.jsx', '.tsx'],
|
|
||||||
alias: this.alias(),
|
|
||||||
modules: webpackModulesDir
|
|
||||||
},
|
|
||||||
resolveLoader: {
|
|
||||||
modules: webpackModulesDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
minimizer () {
|
|
||||||
const minimizer = []
|
|
||||||
const { terser, cache } = this.options.build
|
|
||||||
|
|
||||||
// https://github.com/webpack-contrib/terser-webpack-plugin
|
|
||||||
if (terser) {
|
|
||||||
minimizer.push(
|
|
||||||
new TerserWebpackPlugin(Object.assign({
|
|
||||||
cache,
|
|
||||||
extractComments: {
|
|
||||||
condition: 'some',
|
|
||||||
filename: 'LICENSES'
|
|
||||||
},
|
|
||||||
terserOptions: {
|
|
||||||
compress: {
|
|
||||||
ecma: this.isModern ? 6 : undefined
|
|
||||||
},
|
|
||||||
mangle: {
|
|
||||||
reserved: reservedVueTags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, terser))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return minimizer
|
|
||||||
}
|
|
||||||
|
|
||||||
alias () {
|
|
||||||
return {
|
|
||||||
...this.options.alias,
|
|
||||||
app: this.options.appDir,
|
|
||||||
'nuxt-build': this.options.buildDir,
|
|
||||||
'vue-meta': require.resolve(`vue-meta${this.isServer ? '' : '/dist/vue-meta.esm.browser.js'}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rules () {
|
|
||||||
const perfLoader = new PerfLoader(this.name, this.options)
|
|
||||||
const styleLoader = new StyleLoader(
|
|
||||||
this.builder.nuxt,
|
|
||||||
{ isServer: this.isServer, perfLoader }
|
|
||||||
)
|
|
||||||
|
|
||||||
const babelLoader = {
|
|
||||||
loader: require.resolve('babel-loader'),
|
|
||||||
options: this.getBabelOptions()
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
test: /\.vue$/i,
|
|
||||||
loader: 'vue-loader',
|
|
||||||
options: this.loaders.vue
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.pug$/i,
|
|
||||||
oneOf: [
|
|
||||||
{
|
|
||||||
resourceQuery: /^\?vue/i,
|
|
||||||
use: [{
|
|
||||||
loader: 'pug-plain-loader',
|
|
||||||
options: this.loaders.pugPlain
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
use: [
|
|
||||||
'raw-loader',
|
|
||||||
{
|
|
||||||
loader: 'pug-plain-loader',
|
|
||||||
options: this.loaders.pugPlain
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 !this.modulesToTranspile.some(module => module.test(file))
|
|
||||||
},
|
|
||||||
use: perfLoader.js().concat(babelLoader)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css$/i,
|
|
||||||
oneOf: styleLoader.apply('css')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.p(ost)?css$/i,
|
|
||||||
oneOf: styleLoader.apply('postcss')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.less$/i,
|
|
||||||
oneOf: styleLoader.apply('less', {
|
|
||||||
loader: 'less-loader',
|
|
||||||
options: this.loaders.less
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.sass$/i,
|
|
||||||
oneOf: styleLoader.apply('sass', {
|
|
||||||
loader: 'sass-loader',
|
|
||||||
options: this.loaders.sass
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/i,
|
|
||||||
oneOf: styleLoader.apply('scss', {
|
|
||||||
loader: 'sass-loader',
|
|
||||||
options: this.loaders.scss
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.styl(us)?$/i,
|
|
||||||
oneOf: styleLoader.apply('stylus', {
|
|
||||||
loader: 'stylus-loader',
|
|
||||||
options: this.loaders.stylus
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|jpe?g|gif|svg|webp)$/i,
|
|
||||||
use: [{
|
|
||||||
loader: 'url-loader',
|
|
||||||
options: Object.assign(
|
|
||||||
this.loaders.imgUrl,
|
|
||||||
{ name: this.getFileName('img') }
|
|
||||||
)
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
|
|
||||||
use: [{
|
|
||||||
loader: 'url-loader',
|
|
||||||
options: Object.assign(
|
|
||||||
this.loaders.fontUrl,
|
|
||||||
{ name: this.getFileName('font') }
|
|
||||||
)
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(webm|mp4|ogv)$/i,
|
|
||||||
use: [{
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: Object.assign(
|
|
||||||
this.loaders.file,
|
|
||||||
{ name: this.getFileName('video') }
|
|
||||||
)
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins () {
|
|
||||||
const plugins = []
|
|
||||||
const { nuxt } = this.builder
|
|
||||||
const { build: buildOptions } = this.options
|
|
||||||
|
|
||||||
// Add timefix-plugin before others plugins
|
|
||||||
if (this.dev) {
|
|
||||||
plugins.push(new TimeFixPlugin())
|
|
||||||
}
|
|
||||||
|
|
||||||
// CSS extraction)
|
|
||||||
if (buildOptions.extractCSS) {
|
|
||||||
plugins.push(new MiniCssExtractPlugin(Object.assign({
|
|
||||||
filename: this.getFileName('css'),
|
|
||||||
chunkFilename: this.getFileName('css')
|
|
||||||
}, buildOptions.extractCSS)))
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.push(new VueLoaderPlugin())
|
|
||||||
|
|
||||||
plugins.push(...(buildOptions.plugins || []))
|
|
||||||
|
|
||||||
plugins.push(new WarningIgnorePlugin(this.warningIgnoreFilter()))
|
|
||||||
|
|
||||||
// Build progress indicator
|
|
||||||
plugins.push(new WebpackBar({
|
|
||||||
name: this.name,
|
|
||||||
color: this.colors[this.name],
|
|
||||||
reporters: [
|
|
||||||
'basic',
|
|
||||||
'fancy',
|
|
||||||
'profile',
|
|
||||||
'stats'
|
|
||||||
],
|
|
||||||
basic: !buildOptions.quiet && env.minimalCLI,
|
|
||||||
fancy: !buildOptions.quiet && !env.minimalCLI,
|
|
||||||
profile: !buildOptions.quiet && buildOptions.profile,
|
|
||||||
stats: !buildOptions.quiet && !this.dev && buildOptions.stats,
|
|
||||||
reporter: {
|
|
||||||
change: (_, { shortPath }) => {
|
|
||||||
if (!this.isServer) {
|
|
||||||
nuxt.callHook('bundler:change', shortPath)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
done: (stats) => {
|
|
||||||
if (stats.hasErrors) {
|
|
||||||
nuxt.callHook('bundler:error')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
allDone: () => {
|
|
||||||
nuxt.callHook('bundler:done')
|
|
||||||
},
|
|
||||||
progress ({ statesArray }) {
|
|
||||||
nuxt.callHook('bundler:progress', statesArray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
// CSS extraction
|
|
||||||
if (this.options.build.extractCSS) {
|
|
||||||
plugins.push(new MiniCssExtractPlugin(Object.assign({
|
|
||||||
filename: this.getFileName('css'),
|
|
||||||
chunkFilename: this.getFileName('css'),
|
|
||||||
// TODO: https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/issues/132
|
|
||||||
reloadAll: true
|
|
||||||
}, this.options.build.extractCSS)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return plugins
|
|
||||||
}
|
|
||||||
|
|
||||||
warningIgnoreFilter () {
|
|
||||||
const filters = [
|
|
||||||
// Hide warnings about plugins without a default export (#1179)
|
|
||||||
warn => warn.name === 'ModuleDependencyWarning' &&
|
|
||||||
warn.message.includes('export \'default\'') &&
|
|
||||||
warn.message.includes('nuxt_plugin_'),
|
|
||||||
...(this.options.build.warningIgnoreFilters || [])
|
|
||||||
]
|
|
||||||
|
|
||||||
return warn => !filters.some(ignoreFilter => ignoreFilter(warn))
|
|
||||||
}
|
|
||||||
|
|
||||||
extendConfig (config) {
|
|
||||||
const { extend } = this.options.build
|
|
||||||
if (typeof extend === 'function') {
|
|
||||||
const extendedConfig = extend.call(
|
|
||||||
this.builder, config, { loaders: this.loaders, ...this.nuxtEnv }
|
|
||||||
) || config
|
|
||||||
|
|
||||||
const pragma = /@|#/
|
|
||||||
const { devtool } = extendedConfig
|
|
||||||
if (typeof devtool === 'string' && pragma.test(devtool)) {
|
|
||||||
extendedConfig.devtool = devtool.replace(pragma, '')
|
|
||||||
consola.warn(`devtool has been normalized to ${extendedConfig.devtool} as webpack documented value`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return extendedConfig
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
config () {
|
|
||||||
const config = {
|
|
||||||
name: this.name,
|
|
||||||
mode: this.mode,
|
|
||||||
devtool: this.devtool,
|
|
||||||
cache: this.cache(),
|
|
||||||
optimization: this.optimization(),
|
|
||||||
output: this.output(),
|
|
||||||
performance: {
|
|
||||||
maxEntrypointSize: 1000 * 1024,
|
|
||||||
hints: this.dev ? false : 'warning'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: this.rules()
|
|
||||||
},
|
|
||||||
plugins: this.plugins(),
|
|
||||||
...this.resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone deep avoid leaking config between Client and Server
|
|
||||||
const extendedConfig = cloneDeep(this.extendConfig(config))
|
|
||||||
|
|
||||||
return extendedConfig
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,233 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import querystring from 'querystring'
|
|
||||||
import webpack from 'webpack'
|
|
||||||
import HTMLPlugin from 'html-webpack-plugin'
|
|
||||||
import BundleAnalyzer from 'webpack-bundle-analyzer'
|
|
||||||
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
|
|
||||||
import FriendlyErrorsWebpackPlugin from '@nuxt/friendly-errors-webpack-plugin'
|
|
||||||
|
|
||||||
import CorsPlugin from '../plugins/vue/cors'
|
|
||||||
import ModernModePlugin from '../plugins/vue/modern'
|
|
||||||
import VueSSRClientPlugin from '../plugins/vue/client'
|
|
||||||
import WebpackBaseConfig from './base'
|
|
||||||
|
|
||||||
export default class WebpackClientConfig extends WebpackBaseConfig {
|
|
||||||
constructor (builder) {
|
|
||||||
super(builder)
|
|
||||||
this.name = 'client'
|
|
||||||
this.isServer = false
|
|
||||||
this.isModern = false
|
|
||||||
}
|
|
||||||
|
|
||||||
get devtool () {
|
|
||||||
if (!this.dev) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const scriptPolicy = this.getCspScriptPolicy()
|
|
||||||
const noUnsafeEval = scriptPolicy && !scriptPolicy.includes('\'unsafe-eval\'')
|
|
||||||
return noUnsafeEval
|
|
||||||
? 'cheap-module-source-map'
|
|
||||||
: 'eval-cheap-module-source-map'
|
|
||||||
}
|
|
||||||
|
|
||||||
getCspScriptPolicy () {
|
|
||||||
const { csp } = this.options.render
|
|
||||||
if (typeof csp === 'object') {
|
|
||||||
const { policies = {} } = csp
|
|
||||||
return policies['script-src'] || policies['default-src'] || []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
env () {
|
|
||||||
return Object.assign(
|
|
||||||
super.env(),
|
|
||||||
{
|
|
||||||
'process.env.VUE_ENV': JSON.stringify('client'),
|
|
||||||
'process.browser': true,
|
|
||||||
'process.client': true,
|
|
||||||
'process.server': false,
|
|
||||||
'process.modern': false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
optimization () {
|
|
||||||
const optimization = super.optimization()
|
|
||||||
const { splitChunks } = optimization
|
|
||||||
const { cacheGroups } = splitChunks
|
|
||||||
|
|
||||||
// Small, known and common modules which are usually used project-wise
|
|
||||||
// Sum of them may not be more than 244 KiB
|
|
||||||
if (
|
|
||||||
this.options.build.splitChunks.commons === true &&
|
|
||||||
cacheGroups.commons === undefined
|
|
||||||
) {
|
|
||||||
cacheGroups.commons = {
|
|
||||||
test: /node_modules[\\/](vue|vue-loader|vue-router|vuex|vue-meta|core-js|@babel\/runtime|axios|webpack|setimmediate|timers-browserify|process|regenerator-runtime|cookie|js-cookie|is-buffer|dotprop|nuxt\.js)[\\/]/,
|
|
||||||
chunks: 'all',
|
|
||||||
priority: 10,
|
|
||||||
name: 'commons',
|
|
||||||
automaticNameDelimiter: '/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.dev && cacheGroups.default && cacheGroups.default.name === undefined) {
|
|
||||||
cacheGroups.default.name = (_module, chunks) => {
|
|
||||||
// Use default name for single chunks
|
|
||||||
if (chunks.length === 1) {
|
|
||||||
return chunks[0].name || ''
|
|
||||||
}
|
|
||||||
// Use compact name for concatinated modules
|
|
||||||
return 'commons/' + chunks.filter(c => c.name).map(c =>
|
|
||||||
c.name.replace(/\//g, '.').replace(/_/g, '').replace('pages.', '')
|
|
||||||
).join('~')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return optimization
|
|
||||||
}
|
|
||||||
|
|
||||||
minimizer () {
|
|
||||||
const minimizer = super.minimizer()
|
|
||||||
const { cssMinimizer } = this.options.build.optimization
|
|
||||||
|
|
||||||
if (cssMinimizer) {
|
|
||||||
minimizer.push(new CssMinimizerPlugin(Object.assign({}, cssMinimizer)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return minimizer
|
|
||||||
}
|
|
||||||
|
|
||||||
alias () {
|
|
||||||
const aliases = super.alias()
|
|
||||||
|
|
||||||
for (const p of this.builder.plugins) {
|
|
||||||
if (!aliases[p.name]) {
|
|
||||||
// Do not load server-side plugins on client-side
|
|
||||||
aliases[p.name] = p.mode === 'server' ? './empty.js' : p.src
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return aliases
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins () {
|
|
||||||
const plugins = super.plugins()
|
|
||||||
const { build: buildOptions, appTemplatePath, buildDir, modern, render } = this.options
|
|
||||||
|
|
||||||
// Generate output HTML for SSR
|
|
||||||
if (buildOptions.ssr) {
|
|
||||||
plugins.push(
|
|
||||||
new HTMLPlugin({
|
|
||||||
filename: '../server/index.ssr.html',
|
|
||||||
template: appTemplatePath,
|
|
||||||
minify: buildOptions.html.minify,
|
|
||||||
inject: false // Resources will be injected using bundleRenderer
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.push(
|
|
||||||
new HTMLPlugin({
|
|
||||||
filename: '../server/index.spa.html',
|
|
||||||
template: appTemplatePath,
|
|
||||||
minify: buildOptions.html.minify,
|
|
||||||
inject: true
|
|
||||||
}),
|
|
||||||
new VueSSRClientPlugin({
|
|
||||||
filename: `../server/${this.name}.manifest.json`
|
|
||||||
}),
|
|
||||||
new webpack.DefinePlugin(this.env())
|
|
||||||
)
|
|
||||||
|
|
||||||
if (this.dev) {
|
|
||||||
// TODO: webpackHotUpdate is not defined: https://github.com/webpack/webpack/issues/6693
|
|
||||||
plugins.push(new webpack.HotModuleReplacementPlugin())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Webpack Bundle Analyzer
|
|
||||||
// https://github.com/webpack-contrib/webpack-bundle-analyzer
|
|
||||||
if (!this.dev && buildOptions.analyze) {
|
|
||||||
const statsDir = path.resolve(buildDir, 'stats')
|
|
||||||
|
|
||||||
plugins.push(new BundleAnalyzer.BundleAnalyzerPlugin(Object.assign({
|
|
||||||
analyzerMode: 'static',
|
|
||||||
defaultSizes: 'gzip',
|
|
||||||
generateStatsFile: true,
|
|
||||||
openAnalyzer: !buildOptions.quiet,
|
|
||||||
reportFilename: path.resolve(statsDir, `${this.name}.html`),
|
|
||||||
statsFilename: path.resolve(statsDir, `${this.name}.json`)
|
|
||||||
}, buildOptions.analyze)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modern) {
|
|
||||||
const scriptPolicy = this.getCspScriptPolicy()
|
|
||||||
const noUnsafeInline = scriptPolicy && !scriptPolicy.includes('\'unsafe-inline\'')
|
|
||||||
plugins.push(new ModernModePlugin({
|
|
||||||
targetDir: path.resolve(buildDir, 'dist', 'client'),
|
|
||||||
isModernBuild: this.isModern,
|
|
||||||
noUnsafeInline
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (render.crossorigin) {
|
|
||||||
plugins.push(new CorsPlugin({
|
|
||||||
crossorigin: render.crossorigin
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return plugins
|
|
||||||
}
|
|
||||||
|
|
||||||
config () {
|
|
||||||
const config = super.config()
|
|
||||||
const {
|
|
||||||
router,
|
|
||||||
buildDir,
|
|
||||||
build: { hotMiddleware, quiet, friendlyErrors }
|
|
||||||
} = this.options
|
|
||||||
|
|
||||||
const { client = {} } = hotMiddleware || {}
|
|
||||||
const { ansiColors, overlayStyles, ...options } = client
|
|
||||||
|
|
||||||
const hotMiddlewareClientOptions = {
|
|
||||||
reload: true,
|
|
||||||
timeout: 30000,
|
|
||||||
ansiColors: JSON.stringify(ansiColors),
|
|
||||||
overlayStyles: JSON.stringify(overlayStyles),
|
|
||||||
path: `${router.base}/__webpack_hmr/${this.name}`.replace(/\/\//g, '/'),
|
|
||||||
...options,
|
|
||||||
name: this.name
|
|
||||||
}
|
|
||||||
|
|
||||||
const hotMiddlewareClientOptionsStr = querystring.stringify(hotMiddlewareClientOptions)
|
|
||||||
|
|
||||||
// Entry points
|
|
||||||
config.entry = Object.assign({}, config.entry, {
|
|
||||||
app: [path.resolve(buildDir, 'entry.client.ts')]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add HMR support
|
|
||||||
if (this.dev) {
|
|
||||||
config.entry.app.unshift(
|
|
||||||
// https://github.com/webpack-contrib/webpack-hot-middleware/issues/53#issuecomment-162823945
|
|
||||||
'eventsource-polyfill',
|
|
||||||
// https://github.com/glenjamin/webpack-hot-middleware#config
|
|
||||||
`webpack-hot-middleware/client?${hotMiddlewareClientOptionsStr}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add friendly error plugin
|
|
||||||
if (this.dev && !quiet && friendlyErrors) {
|
|
||||||
config.plugins.push(
|
|
||||||
new FriendlyErrorsWebpackPlugin({
|
|
||||||
clearConsole: false,
|
|
||||||
reporter: 'consola',
|
|
||||||
logLevel: 'WARNING'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export { default as client } from './client'
|
|
||||||
export { default as modern } from './modern'
|
|
||||||
export { default as server } from './server'
|
|
@ -1,15 +0,0 @@
|
|||||||
import WebpackClientConfig from './client'
|
|
||||||
|
|
||||||
export default class WebpackModernConfig extends WebpackClientConfig {
|
|
||||||
constructor (...args) {
|
|
||||||
super(...args)
|
|
||||||
this.name = 'modern'
|
|
||||||
this.isModern = true
|
|
||||||
}
|
|
||||||
|
|
||||||
env () {
|
|
||||||
return Object.assign(super.env(), {
|
|
||||||
'process.modern': true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import fs from 'fs'
|
|
||||||
import { DefinePlugin, ProvidePlugin } from 'webpack'
|
|
||||||
import FriendlyErrorsWebpackPlugin from '@nuxt/friendly-errors-webpack-plugin'
|
|
||||||
|
|
||||||
// TODO: remove when webpack-node-externals support webpack5
|
|
||||||
// import nodeExternals from 'webpack-node-externals'
|
|
||||||
import nodeExternals from '../plugins/externals'
|
|
||||||
import VueSSRServerPlugin from '../plugins/vue/server'
|
|
||||||
|
|
||||||
import WebpackBaseConfig from './base'
|
|
||||||
|
|
||||||
const nativeFileExtensions = [
|
|
||||||
'.json',
|
|
||||||
'.js'
|
|
||||||
]
|
|
||||||
|
|
||||||
export default class WebpackServerConfig extends WebpackBaseConfig {
|
|
||||||
constructor (...args) {
|
|
||||||
super(...args)
|
|
||||||
this.name = 'server'
|
|
||||||
this.isServer = true
|
|
||||||
}
|
|
||||||
|
|
||||||
get devtool () {
|
|
||||||
return 'cheap-module-source-map'
|
|
||||||
}
|
|
||||||
|
|
||||||
get externalsWhitelist () {
|
|
||||||
return [
|
|
||||||
this.isNonNativeImport.bind(this),
|
|
||||||
...this.normalizeTranspile()
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* files *not* ending on js|json should be processed by webpack
|
|
||||||
*
|
|
||||||
* this might generate false-positives for imports like
|
|
||||||
* - "someFile.umd" (actually requiring someFile.umd.js)
|
|
||||||
* - "some.folder" (some.folder being a directory containing a package.json)
|
|
||||||
*/
|
|
||||||
isNonNativeImport (modulePath) {
|
|
||||||
const extname = path.extname(modulePath)
|
|
||||||
return extname !== '' && !nativeFileExtensions.includes(extname)
|
|
||||||
}
|
|
||||||
|
|
||||||
env () {
|
|
||||||
return Object.assign(
|
|
||||||
super.env(),
|
|
||||||
{
|
|
||||||
'process.env.VUE_ENV': JSON.stringify('server'),
|
|
||||||
'process.browser': false,
|
|
||||||
'process.client': false,
|
|
||||||
'process.server': true,
|
|
||||||
'process.modern': false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
optimization () {
|
|
||||||
const { _minifyServer } = this.options.build
|
|
||||||
|
|
||||||
return {
|
|
||||||
splitChunks: false,
|
|
||||||
minimizer: _minifyServer ? this.minimizer() : []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve () {
|
|
||||||
const resolveConfig = super.resolve()
|
|
||||||
|
|
||||||
resolveConfig.resolve.mainFields = ['main', 'module']
|
|
||||||
|
|
||||||
return resolveConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
alias () {
|
|
||||||
const aliases = super.alias()
|
|
||||||
|
|
||||||
for (const p of this.builder.plugins) {
|
|
||||||
if (!aliases[p.name]) {
|
|
||||||
// Do not load client-side plugins on server-side
|
|
||||||
aliases[p.name] = p.mode === 'client' ? './empty.js' : p.src
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return aliases
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins () {
|
|
||||||
const plugins = super.plugins()
|
|
||||||
plugins.push(
|
|
||||||
new VueSSRServerPlugin({ filename: `${this.name}.manifest.json` }),
|
|
||||||
new DefinePlugin(this.env())
|
|
||||||
)
|
|
||||||
|
|
||||||
const { serverURLPolyfill } = this.options.build
|
|
||||||
|
|
||||||
if (serverURLPolyfill) {
|
|
||||||
plugins.push(new ProvidePlugin({
|
|
||||||
URL: [serverURLPolyfill, 'URL'],
|
|
||||||
URLSearchParams: [serverURLPolyfill, 'URLSearchParams']
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return plugins
|
|
||||||
}
|
|
||||||
|
|
||||||
config () {
|
|
||||||
const config = super.config()
|
|
||||||
|
|
||||||
Object.assign(config, {
|
|
||||||
target: 'node',
|
|
||||||
node: false,
|
|
||||||
entry: Object.assign({}, config.entry, {
|
|
||||||
app: [path.resolve(this.options.buildDir, 'entry.server.ts')]
|
|
||||||
}),
|
|
||||||
output: Object.assign({}, config.output, {
|
|
||||||
filename: 'server.js',
|
|
||||||
chunkFilename: '[name].js',
|
|
||||||
libraryTarget: 'commonjs2'
|
|
||||||
}),
|
|
||||||
performance: {
|
|
||||||
hints: false,
|
|
||||||
maxEntrypointSize: Infinity,
|
|
||||||
maxAssetSize: Infinity
|
|
||||||
},
|
|
||||||
externals: [].concat(config.externals || [])
|
|
||||||
})
|
|
||||||
|
|
||||||
// https://webpack.js.org/configuration/externals/#externals
|
|
||||||
// https://github.com/liady/webpack-node-externals
|
|
||||||
// https://vue-loader.vuejs.org/migrating.html#ssr-externals
|
|
||||||
if (!this.options.build.standalone) {
|
|
||||||
this.options.modulesDir.forEach((dir) => {
|
|
||||||
if (fs.existsSync(dir)) {
|
|
||||||
config.externals.push(
|
|
||||||
nodeExternals({
|
|
||||||
whitelist: this.externalsWhitelist,
|
|
||||||
modulesDir: dir
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add friendly error plugin
|
|
||||||
if (this.dev) {
|
|
||||||
config.plugins.push(
|
|
||||||
new FriendlyErrorsWebpackPlugin({
|
|
||||||
clearConsole: false,
|
|
||||||
reporter: 'consola',
|
|
||||||
logLevel: 'WARNING'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
}
|
|
147
packages/nuxt3/src/webpack/configs/client.ts
Normal file
147
packages/nuxt3/src/webpack/configs/client.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import querystring from 'querystring'
|
||||||
|
import webpack from 'webpack'
|
||||||
|
import HTMLPlugin from 'html-webpack-plugin'
|
||||||
|
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
|
||||||
|
import CorsPlugin from '../plugins/vue/cors'
|
||||||
|
import { applyPresets, WebpackConfigContext } from '../utils/config'
|
||||||
|
import { nuxt } from '../presets/nuxt'
|
||||||
|
|
||||||
|
export function client (ctx: WebpackConfigContext) {
|
||||||
|
ctx.name = 'client'
|
||||||
|
ctx.isClient = true
|
||||||
|
|
||||||
|
applyPresets(ctx, [
|
||||||
|
nuxt,
|
||||||
|
clientPlugins,
|
||||||
|
clientOptimization,
|
||||||
|
clientDevtool,
|
||||||
|
clientPerformance,
|
||||||
|
clientHMR,
|
||||||
|
clientHTML
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientDevtool (ctx: WebpackConfigContext) {
|
||||||
|
if (!ctx.isDev) {
|
||||||
|
ctx.config.devtool = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptPolicy = getCspScriptPolicy(ctx)
|
||||||
|
const noUnsafeEval = scriptPolicy && !scriptPolicy.includes('\'unsafe-eval\'')
|
||||||
|
ctx.config.devtool = noUnsafeEval
|
||||||
|
? 'cheap-module-source-map'
|
||||||
|
: 'eval-cheap-module-source-map'
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientPerformance (ctx: WebpackConfigContext) {
|
||||||
|
ctx.config.performance = {
|
||||||
|
maxEntrypointSize: 1000 * 1024,
|
||||||
|
hints: ctx.isDev ? false : 'warning',
|
||||||
|
...ctx.config.performance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientHMR (ctx: WebpackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
if (!ctx.isDev) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientOptions = options.build.hotMiddleware?.client || {}
|
||||||
|
const hotMiddlewareClientOptions = {
|
||||||
|
reload: true,
|
||||||
|
timeout: 30000,
|
||||||
|
ansiColors: JSON.stringify(clientOptions.ansiColors || {}),
|
||||||
|
overlayStyles: JSON.stringify(clientOptions.overlayStyles || {}),
|
||||||
|
path: `${options.router.base}/__webpack_hmr/${ctx.name}`.replace(/\/\//g, '/'),
|
||||||
|
...clientOptions,
|
||||||
|
name: ctx.name
|
||||||
|
}
|
||||||
|
const hotMiddlewareClientOptionsStr = querystring.stringify(hotMiddlewareClientOptions)
|
||||||
|
|
||||||
|
// Add HMR support
|
||||||
|
const app = (config.entry as any).app as any
|
||||||
|
app.unshift(
|
||||||
|
// https://github.com/webpack-contrib/webpack-hot-middleware/issues/53#issuecomment-162823945
|
||||||
|
'eventsource-polyfill',
|
||||||
|
// https://github.com/glenjamin/webpack-hot-middleware#config
|
||||||
|
`webpack-hot-middleware/client?${hotMiddlewareClientOptionsStr}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: webpackHotUpdate is not defined: https://github.com/webpack/webpack/issues/6693
|
||||||
|
config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientOptimization (ctx: WebpackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
config.optimization = {
|
||||||
|
...config.optimization,
|
||||||
|
...options.build.optimization as any
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Improve optimization.splitChunks.cacheGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientHTML (ctx: WebpackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// Generate output HTML for SSR
|
||||||
|
if (options.build.ssr) {
|
||||||
|
config.plugins.push(
|
||||||
|
new HTMLPlugin({
|
||||||
|
filename: '../server/index.ssr.html',
|
||||||
|
template: options.appTemplatePath,
|
||||||
|
minify: options.build.html.minify as any,
|
||||||
|
inject: false // Resources will be injected using bundleRenderer
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.plugins.push(
|
||||||
|
new HTMLPlugin({
|
||||||
|
filename: '../server/index.spa.html',
|
||||||
|
template: options.appTemplatePath,
|
||||||
|
minify: options.build.html.minify as any,
|
||||||
|
inject: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientPlugins (ctx: WebpackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// Webpack Bundle Analyzer
|
||||||
|
// https://github.com/webpack-contrib/webpack-bundle-analyzer
|
||||||
|
if (!ctx.isDev && options.build.analyze) {
|
||||||
|
const statsDir = path.resolve(options.buildDir, 'stats')
|
||||||
|
|
||||||
|
config.plugins.push(new BundleAnalyzerPlugin({
|
||||||
|
analyzerMode: 'static',
|
||||||
|
defaultSizes: 'gzip',
|
||||||
|
generateStatsFile: true,
|
||||||
|
openAnalyzer: !options.build.quiet,
|
||||||
|
reportFilename: path.resolve(statsDir, `${ctx.name}.html`),
|
||||||
|
statsFilename: path.resolve(statsDir, `${ctx.name}.json`),
|
||||||
|
...options.build.analyze as any
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS
|
||||||
|
if (ctx.options.render.crossorigin) {
|
||||||
|
ctx.config.plugins.push(new CorsPlugin({
|
||||||
|
crossorigin: ctx.options.render.crossorigin
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCspScriptPolicy (ctx: WebpackConfigContext) {
|
||||||
|
const { csp } = ctx.options.render
|
||||||
|
if (typeof csp === 'object') {
|
||||||
|
const { policies = {} } = csp
|
||||||
|
return policies['script-src'] || policies['default-src'] || []
|
||||||
|
}
|
||||||
|
}
|
2
packages/nuxt3/src/webpack/configs/index.ts
Normal file
2
packages/nuxt3/src/webpack/configs/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { client } from './client'
|
||||||
|
export { server } from './server'
|
69
packages/nuxt3/src/webpack/configs/server.ts
Normal file
69
packages/nuxt3/src/webpack/configs/server.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { ProvidePlugin } from 'webpack'
|
||||||
|
import nodeExternals from '../plugins/externals'
|
||||||
|
import { WebpackConfigContext, applyPresets, getWebpackConfig } from '../utils/config'
|
||||||
|
import { nuxt } from '../presets/nuxt'
|
||||||
|
import { node } from '../presets/node'
|
||||||
|
|
||||||
|
export function server (ctx: WebpackConfigContext) {
|
||||||
|
ctx.name = 'server'
|
||||||
|
ctx.isServer = true
|
||||||
|
|
||||||
|
applyPresets(ctx, [
|
||||||
|
nuxt,
|
||||||
|
node,
|
||||||
|
serverStandalone,
|
||||||
|
serverPreset,
|
||||||
|
serverPlugins
|
||||||
|
])
|
||||||
|
|
||||||
|
return getWebpackConfig(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverPreset (ctx: WebpackConfigContext) {
|
||||||
|
const { config } = ctx
|
||||||
|
|
||||||
|
config.output.filename = 'server.js'
|
||||||
|
config.devtool = 'cheap-module-source-map'
|
||||||
|
|
||||||
|
config.optimization = {
|
||||||
|
splitChunks: false,
|
||||||
|
minimize: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverStandalone (ctx: WebpackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// https://webpack.js.org/configuration/externals/#externals
|
||||||
|
// https://github.com/liady/webpack-node-externals
|
||||||
|
// https://vue-loader.vuejs.org/migrating.html#ssr-externals
|
||||||
|
if (!options.build.standalone) {
|
||||||
|
options.modulesDir.forEach((dir) => {
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
(config.externals as any[]).push(
|
||||||
|
nodeExternals({
|
||||||
|
whitelist: [
|
||||||
|
modulePath => !['.js', '.json', ''].includes(path.extname(modulePath)),
|
||||||
|
ctx.transpile
|
||||||
|
],
|
||||||
|
modulesDir: dir
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverPlugins (ctx: WebpackConfigContext) {
|
||||||
|
const { config, options } = ctx
|
||||||
|
|
||||||
|
// Server polyfills
|
||||||
|
if (options.build.serverURLPolyfill) {
|
||||||
|
config.plugins.push(new ProvidePlugin({
|
||||||
|
URL: [options.build.serverURLPolyfill, 'URL'],
|
||||||
|
URLSearchParams: [options.build.serverURLPolyfill, 'URLSearchParams']
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
38
packages/nuxt3/src/webpack/presets/assets.ts
Normal file
38
packages/nuxt3/src/webpack/presets/assets.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { fileName, WebpackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function assets (ctx: WebpackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
ctx.config.module.rules.push(
|
||||||
|
{
|
||||||
|
test: /\.(png|jpe?g|gif|svg|webp)$/i,
|
||||||
|
use: [{
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
...options.build.loaders.imgUrl,
|
||||||
|
name: fileName(ctx, 'img')
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
|
||||||
|
use: [{
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
...options.build.loaders.fontUrl,
|
||||||
|
name: fileName(ctx, 'font')
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(webm|mp4|ogv)$/i,
|
||||||
|
use: [{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
...options.build.loaders.file,
|
||||||
|
name: fileName(ctx, 'video')
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
78
packages/nuxt3/src/webpack/presets/babel.ts
Normal file
78
packages/nuxt3/src/webpack/presets/babel.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import TerserWebpackPlugin from 'terser-webpack-plugin'
|
||||||
|
import { WebpackPluginInstance } from 'webpack'
|
||||||
|
import { reservedVueTags } from '../utils/reserved-tags'
|
||||||
|
import { WebpackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function babel (ctx: WebpackConfigContext) {
|
||||||
|
const { config, options } = ctx
|
||||||
|
|
||||||
|
const babelLoader = {
|
||||||
|
loader: 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))
|
||||||
|
},
|
||||||
|
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 WebpackPluginInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = [require.resolve('../../babel-preset-app'), {}]
|
||||||
|
|
||||||
|
if (typeof babelOptions.presets === 'function') {
|
||||||
|
babelOptions.presets = babelOptions.presets(ctx, defaultPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!babelOptions.presets) {
|
||||||
|
babelOptions.presets = [defaultPreset]
|
||||||
|
}
|
||||||
|
|
||||||
|
return babelOptions
|
||||||
|
}
|
240
packages/nuxt3/src/webpack/presets/base.ts
Normal file
240
packages/nuxt3/src/webpack/presets/base.ts
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
import { resolve, normalize } from 'path'
|
||||||
|
import TimeFixPlugin from 'time-fix-plugin'
|
||||||
|
import WebpackBar from 'webpackbar'
|
||||||
|
import stdEnv from 'std-env'
|
||||||
|
import { DefinePlugin, Configuration } from 'webpack'
|
||||||
|
import FriendlyErrorsWebpackPlugin from '@nuxt/friendly-errors-webpack-plugin'
|
||||||
|
import { isUrl, urlJoin, TARGETS } from 'src/utils'
|
||||||
|
import escapeRegExp from 'lodash/escapeRegExp'
|
||||||
|
import WarningIgnorePlugin from '../plugins/warning-ignore'
|
||||||
|
import { WebpackConfigContext, applyPresets, fileName } from '../utils/config'
|
||||||
|
|
||||||
|
export function base (ctx: WebpackConfigContext) {
|
||||||
|
applyPresets(ctx, [
|
||||||
|
baseAlias,
|
||||||
|
baseConfig,
|
||||||
|
basePlugins,
|
||||||
|
baseResolve
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseConfig (ctx: WebpackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
ctx.config = {
|
||||||
|
name: ctx.name,
|
||||||
|
entry: { app: [resolve(options.buildDir, `entry.${ctx.name}.ts`)] },
|
||||||
|
module: { rules: [] },
|
||||||
|
plugins: [],
|
||||||
|
externals: [],
|
||||||
|
optimization: {
|
||||||
|
...options.build.optimization as any,
|
||||||
|
minimizer: []
|
||||||
|
},
|
||||||
|
mode: ctx.isDev ? 'development' : 'production',
|
||||||
|
cache: getCache(ctx),
|
||||||
|
output: getOutput(ctx),
|
||||||
|
...ctx.config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function basePlugins (ctx: WebpackConfigContext) {
|
||||||
|
const { config, options, nuxt } = ctx
|
||||||
|
|
||||||
|
// Add timefix-plugin before others plugins
|
||||||
|
if (options.dev) {
|
||||||
|
config.plugins.push(new TimeFixPlugin())
|
||||||
|
}
|
||||||
|
|
||||||
|
// User plugins
|
||||||
|
config.plugins.push(...(options.build.plugins || []))
|
||||||
|
|
||||||
|
// Ignore empty warnings
|
||||||
|
config.plugins.push(new WarningIgnorePlugin(getWarningIgnoreFilter(ctx)))
|
||||||
|
|
||||||
|
// Provide env via DefinePlugin
|
||||||
|
config.plugins.push(new DefinePlugin(getEnv(ctx)))
|
||||||
|
|
||||||
|
// Friendly errors
|
||||||
|
if (
|
||||||
|
ctx.isServer ||
|
||||||
|
(ctx.isDev && !options.build.quiet && options.build.friendlyErrors)
|
||||||
|
) {
|
||||||
|
ctx.config.plugins.push(
|
||||||
|
new FriendlyErrorsWebpackPlugin({
|
||||||
|
clearConsole: false,
|
||||||
|
reporter: 'consola',
|
||||||
|
logLevel: 'WARNING'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webpackbar
|
||||||
|
const colors = {
|
||||||
|
client: 'green',
|
||||||
|
server: 'orange',
|
||||||
|
modern: 'blue'
|
||||||
|
}
|
||||||
|
config.plugins.push(new WebpackBar({
|
||||||
|
name: ctx.name,
|
||||||
|
color: colors[ctx.name],
|
||||||
|
reporters: [
|
||||||
|
'basic',
|
||||||
|
'fancy',
|
||||||
|
'profile',
|
||||||
|
'stats'
|
||||||
|
],
|
||||||
|
basic: !options.build.quiet && stdEnv.minimalCLI,
|
||||||
|
fancy: !options.build.quiet && !stdEnv.minimalCLI,
|
||||||
|
profile: !options.build.quiet && options.build.profile,
|
||||||
|
stats: !options.build.quiet && !options.dev && options.build.stats,
|
||||||
|
reporter: {
|
||||||
|
change: (_, { shortPath }) => {
|
||||||
|
if (!ctx.isServer) {
|
||||||
|
nuxt.callHook('bundler:change', shortPath)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
done: (stats) => {
|
||||||
|
if (stats.hasErrors) {
|
||||||
|
nuxt.callHook('bundler:error')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
allDone: () => {
|
||||||
|
nuxt.callHook('bundler:done')
|
||||||
|
},
|
||||||
|
progress ({ statesArray }) {
|
||||||
|
nuxt.callHook('bundler:progress', statesArray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseAlias (ctx: WebpackConfigContext) {
|
||||||
|
const { options, isServer } = ctx
|
||||||
|
|
||||||
|
ctx.alias = {
|
||||||
|
app: options.appDir,
|
||||||
|
'nuxt-build': options.buildDir,
|
||||||
|
'vue-meta': require.resolve(`vue-meta${isServer ? '' : '/dist/vue-meta.esm.browser.js'}`),
|
||||||
|
...options.alias,
|
||||||
|
...ctx.alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseResolve (ctx: WebpackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// Prioritize nested node_modules in webpack search path (#2558)
|
||||||
|
// TODO: this might be refactored as default modulesDir?
|
||||||
|
const webpackModulesDir = ['node_modules'].concat(options.modulesDir)
|
||||||
|
|
||||||
|
config.resolve = {
|
||||||
|
extensions: ['.wasm', '.mjs', '.js', '.ts', '.json', '.vue', '.jsx', '.tsx'],
|
||||||
|
alias: ctx.alias,
|
||||||
|
modules: webpackModulesDir,
|
||||||
|
...config.resolve
|
||||||
|
}
|
||||||
|
|
||||||
|
config.resolveLoader = {
|
||||||
|
modules: webpackModulesDir,
|
||||||
|
...config.resolveLoader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function baseTranspile (ctx: WebpackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
const transpile = [
|
||||||
|
/\.vue\.js/i, // include SFCs in node_modules
|
||||||
|
/consola\/src/
|
||||||
|
]
|
||||||
|
|
||||||
|
for (let pattern of options.build.transpile) {
|
||||||
|
if (typeof pattern === 'function') {
|
||||||
|
pattern = pattern(ctx)
|
||||||
|
}
|
||||||
|
if (typeof pattern === 'string') {
|
||||||
|
const posixModule = pattern.replace(/\\/g, '/')
|
||||||
|
// TODO: should only do for clientside? (hint: pathNormalize)
|
||||||
|
transpile.push(new RegExp(escapeRegExp(normalize(posixModule))))
|
||||||
|
} else if (pattern instanceof RegExp) {
|
||||||
|
transpile.push(pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: unique
|
||||||
|
ctx.transpile = [...transpile, ...ctx.transpile]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCache (ctx: WebpackConfigContext): Configuration['cache'] {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
if (!options.build.cache) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'filesystem',
|
||||||
|
cacheDirectory: resolve('node_modules/.cache/@nuxt/webpack/'),
|
||||||
|
buildDependencies: {
|
||||||
|
config: [...options._nuxtConfigFiles]
|
||||||
|
},
|
||||||
|
...(options.build.cache as any),
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutput (ctx: WebpackConfigContext): Configuration['output'] {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: resolve(options.buildDir, 'dist', ctx.isServer ? 'server' : 'client'),
|
||||||
|
filename: fileName(ctx, 'app'),
|
||||||
|
chunkFilename: fileName(ctx, 'chunk'),
|
||||||
|
publicPath: isUrl(options.build.publicPath) ? options.build.publicPath
|
||||||
|
: urlJoin(options.router.base, options.build.publicPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWarningIgnoreFilter (ctx: WebpackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
const filters = [
|
||||||
|
// Hide warnings about plugins without a default export (#1179)
|
||||||
|
warn => warn.name === 'ModuleDependencyWarning' &&
|
||||||
|
warn.message.includes('export \'default\'') &&
|
||||||
|
warn.message.includes('nuxt_plugin_'),
|
||||||
|
...(options.build.warningIgnoreFilters || [])
|
||||||
|
]
|
||||||
|
|
||||||
|
return warn => !filters.some(ignoreFilter => ignoreFilter(warn))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnv (ctx: WebpackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
const _env = {
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(ctx.config.mode),
|
||||||
|
'process.mode': JSON.stringify(ctx.config.mode),
|
||||||
|
'process.dev': options.dev,
|
||||||
|
'process.static': options.target === TARGETS.static,
|
||||||
|
'process.target': JSON.stringify(options.target),
|
||||||
|
'process.env.VUE_ENV': JSON.stringify(ctx.name),
|
||||||
|
'process.browser': ctx.isClient,
|
||||||
|
'process.client': ctx.isClient,
|
||||||
|
'process.server': ctx.isServer,
|
||||||
|
'process.modern': ctx.isModern
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.build.aggressiveCodeRemoval) {
|
||||||
|
_env['typeof process'] = JSON.stringify(ctx.isServer ? 'object' : 'undefined')
|
||||||
|
_env['typeof window'] = _env['typeof document'] = JSON.stringify(!ctx.isServer ? 'object' : 'undefined')
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(options.env).forEach(([key, value]) => {
|
||||||
|
const isNative = ['boolean', 'number'].includes(typeof value)
|
||||||
|
_env['process.env.' + key] = isNative ? value : JSON.stringify(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return _env
|
||||||
|
}
|
40
packages/nuxt3/src/webpack/presets/esbuild.ts
Normal file
40
packages/nuxt3/src/webpack/presets/esbuild.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { ESBuildPlugin, ESBuildMinifyPlugin } from 'esbuild-loader'
|
||||||
|
import { WebpackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function esbuild (ctx: WebpackConfigContext) {
|
||||||
|
const { config } = ctx
|
||||||
|
|
||||||
|
config.optimization.minimizer.push(new ESBuildMinifyPlugin())
|
||||||
|
|
||||||
|
config.plugins.push(new ESBuildPlugin())
|
||||||
|
|
||||||
|
config.module.rules.push(
|
||||||
|
{
|
||||||
|
test: /\.[jt]sx?$/,
|
||||||
|
loader: 'esbuild-loader',
|
||||||
|
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))
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
loader: 'ts',
|
||||||
|
target: 'es2015'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.tsx$/,
|
||||||
|
loader: 'esbuild-loader',
|
||||||
|
options: {
|
||||||
|
loader: 'tsx',
|
||||||
|
target: 'es2015'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
23
packages/nuxt3/src/webpack/presets/node.ts
Normal file
23
packages/nuxt3/src/webpack/presets/node.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { WebpackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function node (ctx: WebpackConfigContext) {
|
||||||
|
const { config } = ctx
|
||||||
|
|
||||||
|
config.target = 'node'
|
||||||
|
config.node = false
|
||||||
|
|
||||||
|
config.resolve.mainFields = ['main', 'module']
|
||||||
|
|
||||||
|
config.output = {
|
||||||
|
...config.output,
|
||||||
|
chunkFilename: '[name].js',
|
||||||
|
libraryTarget: 'commonjs2'
|
||||||
|
}
|
||||||
|
|
||||||
|
config.performance = {
|
||||||
|
...config.performance,
|
||||||
|
hints: false,
|
||||||
|
maxEntrypointSize: Infinity,
|
||||||
|
maxAssetSize: Infinity
|
||||||
|
}
|
||||||
|
}
|
20
packages/nuxt3/src/webpack/presets/nuxt.ts
Normal file
20
packages/nuxt3/src/webpack/presets/nuxt.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { WebpackConfigContext, applyPresets } from '../utils/config'
|
||||||
|
|
||||||
|
import { assets } from './assets'
|
||||||
|
import { base } from './base'
|
||||||
|
import { esbuild } from './esbuild'
|
||||||
|
import { pug } from './pug'
|
||||||
|
import { style } from './style'
|
||||||
|
import { vue } from './vue'
|
||||||
|
|
||||||
|
export function nuxt (ctx: WebpackConfigContext) {
|
||||||
|
applyPresets(ctx, [
|
||||||
|
base,
|
||||||
|
assets,
|
||||||
|
// babel,
|
||||||
|
esbuild,
|
||||||
|
pug,
|
||||||
|
style,
|
||||||
|
vue
|
||||||
|
])
|
||||||
|
}
|
25
packages/nuxt3/src/webpack/presets/pug.ts
Normal file
25
packages/nuxt3/src/webpack/presets/pug.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { WebpackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function pug (ctx: WebpackConfigContext) {
|
||||||
|
ctx.config.module.rules.push({
|
||||||
|
test: /\.pug$/i,
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
resourceQuery: /^\?vue/i,
|
||||||
|
use: [{
|
||||||
|
loader: 'pug-plain-loader',
|
||||||
|
options: ctx.options.build.loaders.pugPlain
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
use: [
|
||||||
|
'raw-loader',
|
||||||
|
{
|
||||||
|
loader: 'pug-plain-loader',
|
||||||
|
options: ctx.options.build.loaders.pugPlain
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
62
packages/nuxt3/src/webpack/presets/style.ts
Normal file
62
packages/nuxt3/src/webpack/presets/style.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// import MiniCssExtractPlugin from 'mini-css-extract-plugin'
|
||||||
|
// import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin'
|
||||||
|
// import StyleLoader from '../utils/style-loader'
|
||||||
|
|
||||||
|
import { WebpackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function style (_ctx: WebpackConfigContext) {
|
||||||
|
// // CSS extraction)
|
||||||
|
// if (options.build.extractCSS) {
|
||||||
|
// plugins.push(new MiniCssExtractPlugin(Object.assign({
|
||||||
|
// filename: fileName(ctx, 'css'),
|
||||||
|
// chunkFilename: fileName(ctx, 'css')
|
||||||
|
// }, options.build.extractCSS)))
|
||||||
|
// }
|
||||||
|
// CSS extraction
|
||||||
|
// if (options.build.extractCSS) {
|
||||||
|
// plugins.push(new MiniCssExtractPlugin(Object.assign({
|
||||||
|
// filename: fileName(ctx, 'css'),
|
||||||
|
// chunkFilename: fileName(ctx, 'css'),
|
||||||
|
// // TODO: https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/issues/132
|
||||||
|
// reloadAll: true
|
||||||
|
// }, options.build.extractCSS)))
|
||||||
|
// }
|
||||||
|
return [
|
||||||
|
// {
|
||||||
|
// test: /\.css$/i,
|
||||||
|
// oneOf: styleLoader.apply('css')
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// test: /\.p(ost)?css$/i,
|
||||||
|
// oneOf: styleLoader.apply('postcss')
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// test: /\.less$/i,
|
||||||
|
// oneOf: styleLoader.apply('less', {
|
||||||
|
// loader: 'less-loader',
|
||||||
|
// options: loaders.less
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// test: /\.sass$/i,
|
||||||
|
// oneOf: styleLoader.apply('sass', {
|
||||||
|
// loader: 'sass-loader',
|
||||||
|
// options: loaders.sass
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// test: /\.scss$/i,
|
||||||
|
// oneOf: styleLoader.apply('scss', {
|
||||||
|
// loader: 'sass-loader',
|
||||||
|
// options: loaders.scss
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// test: /\.styl(us)?$/i,
|
||||||
|
// oneOf: styleLoader.apply('stylus', {
|
||||||
|
// loader: 'stylus-loader',
|
||||||
|
// options: loaders.stylus
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
26
packages/nuxt3/src/webpack/presets/vue.ts
Normal file
26
packages/nuxt3/src/webpack/presets/vue.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import VueLoaderPlugin from 'vue-loader/dist/pluginWebpack5'
|
||||||
|
import VueSSRClientPlugin from '../plugins/vue/client'
|
||||||
|
import VueSSRServerPlugin from '../plugins/vue/server'
|
||||||
|
import { WebpackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function vue (ctx: WebpackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
config.plugins.push(new VueLoaderPlugin())
|
||||||
|
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.vue$/i,
|
||||||
|
loader: 'vue-loader',
|
||||||
|
options: options.build.loaders.vue
|
||||||
|
})
|
||||||
|
|
||||||
|
if (ctx.isClient) {
|
||||||
|
config.plugins.push(new VueSSRClientPlugin({
|
||||||
|
filename: `../server/${ctx.name}.manifest.json`
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
config.plugins.push(new VueSSRServerPlugin({
|
||||||
|
filename: `${ctx.name}.manifest.json`
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
88
packages/nuxt3/src/webpack/utils/config.ts
Normal file
88
packages/nuxt3/src/webpack/utils/config.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import consola from 'consola'
|
||||||
|
import cloneDeep from 'lodash/cloneDeep'
|
||||||
|
import { Configuration } from 'webpack'
|
||||||
|
import { Nuxt } from 'src/core'
|
||||||
|
|
||||||
|
export interface WebpackConfigContext extends ReturnType<typeof createWebpackConfigContext>{ }
|
||||||
|
|
||||||
|
type WebpackConfigPreset = (ctx: WebpackConfigContext, options?: object) => void
|
||||||
|
type WebpackConfigPresetItem = WebpackConfigPreset | [WebpackConfigPreset, any]
|
||||||
|
|
||||||
|
export function createWebpackConfigContext ({ nuxt }) {
|
||||||
|
return {
|
||||||
|
nuxt: nuxt as Nuxt,
|
||||||
|
options: nuxt.options as Nuxt['options'],
|
||||||
|
config: {} as Configuration,
|
||||||
|
|
||||||
|
name: 'base',
|
||||||
|
isDev: nuxt.options.dev,
|
||||||
|
isServer: false,
|
||||||
|
isClient: false,
|
||||||
|
isModern: undefined, // TODO
|
||||||
|
isLegacy: false,
|
||||||
|
|
||||||
|
alias: {} as Configuration['resolve']['alias'],
|
||||||
|
transpile: [] as any[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPresets (ctx: WebpackConfigContext, presets: WebpackConfigPresetItem | WebpackConfigPresetItem[]) {
|
||||||
|
if (!Array.isArray(presets)) {
|
||||||
|
presets = [presets]
|
||||||
|
}
|
||||||
|
for (const preset of presets) {
|
||||||
|
if (Array.isArray(preset)) {
|
||||||
|
preset[0](ctx, preset[1])
|
||||||
|
} else {
|
||||||
|
preset(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fileName (ctx: WebpackConfigContext, key: string) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
let fileName = options.build.filenames[key]
|
||||||
|
|
||||||
|
if (typeof fileName === 'function') {
|
||||||
|
fileName = fileName(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof fileName === 'string' && options.dev) {
|
||||||
|
const hash = /\[(chunkhash|contenthash|hash)(?::(\d+))?]/.exec(fileName)
|
||||||
|
if (hash) {
|
||||||
|
consola.warn(`Notice: Please do not use ${hash[1]} in dev mode to prevent memory leak`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWebpackConfig (ctx: WebpackConfigContext): Configuration {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
const builder = {}
|
||||||
|
const loaders = []
|
||||||
|
|
||||||
|
const { extend } = options.build
|
||||||
|
if (typeof extend === 'function') {
|
||||||
|
const extendedConfig = extend.call(
|
||||||
|
builder,
|
||||||
|
config,
|
||||||
|
{ loaders, ...ctx }
|
||||||
|
) || config
|
||||||
|
|
||||||
|
const pragma = /@|#/
|
||||||
|
const { devtool } = extendedConfig
|
||||||
|
if (typeof devtool === 'string' && pragma.test(devtool)) {
|
||||||
|
extendedConfig.devtool = devtool.replace(pragma, '')
|
||||||
|
consola.warn(`devtool has been normalized to ${extendedConfig.devtool} as webpack documented value`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return extendedConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone deep avoid leaking config between Client and Server
|
||||||
|
return cloneDeep(config)
|
||||||
|
}
|
@ -1,3 +1,2 @@
|
|||||||
export { default as PerfLoader } from './perf-loader'
|
|
||||||
export { default as StyleLoader } from './style-loader'
|
export { default as StyleLoader } from './style-loader'
|
||||||
export { reservedVueTags } from './reserved-tags'
|
export { reservedVueTags } from './reserved-tags'
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
import { warmup } from 'thread-loader'
|
|
||||||
|
|
||||||
// https://github.com/webpack-contrib/thread-loader
|
|
||||||
|
|
||||||
export default class PerfLoader {
|
|
||||||
constructor (name, options) {
|
|
||||||
this.name = name
|
|
||||||
this.options = options
|
|
||||||
this.workerPools = PerfLoader.defaultPools({ dev: options.dev })
|
|
||||||
return new Proxy(this, {
|
|
||||||
get (target, name) {
|
|
||||||
return target[name] ? target[name] : target.use.bind(target, name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultPools ({ dev }) {
|
|
||||||
const poolTimeout = dev ? Infinity : 2000
|
|
||||||
return {
|
|
||||||
js: { name: 'js', poolTimeout },
|
|
||||||
css: { name: 'css', poolTimeout }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static warmupAll ({ dev }) {
|
|
||||||
const pools = PerfLoader.defaultPools({ dev })
|
|
||||||
PerfLoader.warmup(pools.js, [
|
|
||||||
require.resolve('babel-loader'),
|
|
||||||
require.resolve('@babel/preset-env')
|
|
||||||
])
|
|
||||||
PerfLoader.warmup(pools.css, ['css-loader'])
|
|
||||||
}
|
|
||||||
|
|
||||||
static warmup (...args) {
|
|
||||||
warmup(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
use (poolName) {
|
|
||||||
const loaders = []
|
|
||||||
|
|
||||||
if (this.options.build.buildOptions) {
|
|
||||||
const pool = this.workerPools[poolName]
|
|
||||||
if (pool) {
|
|
||||||
loaders.push({
|
|
||||||
loader: 'thread-loader',
|
|
||||||
options: pool
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return loaders
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,10 +11,9 @@ import PostcssConfig from './postcss'
|
|||||||
export default class StyleLoader {
|
export default class StyleLoader {
|
||||||
options: NormalizedConfiguration
|
options: NormalizedConfiguration
|
||||||
|
|
||||||
constructor (nuxt: Nuxt, { isServer, perfLoader }) {
|
constructor (nuxt: Nuxt, { isServer }) {
|
||||||
this.options = nuxt.options
|
this.options = nuxt.options
|
||||||
this.isServer = isServer
|
this.isServer = isServer
|
||||||
this.perfLoader = perfLoader
|
|
||||||
|
|
||||||
if (this.options.build.postcss) {
|
if (this.options.build.postcss) {
|
||||||
this.postcssConfig = new PostcssConfig(nuxt)
|
this.postcssConfig = new PostcssConfig(nuxt)
|
||||||
@ -121,17 +120,17 @@ export default class StyleLoader {
|
|||||||
// This matches <style module>
|
// This matches <style module>
|
||||||
{
|
{
|
||||||
resourceQuery: /module/,
|
resourceQuery: /module/,
|
||||||
use: this.perfLoader.css().concat(
|
use: [
|
||||||
this.cssModules(cssModules),
|
this.cssModules(cssModules),
|
||||||
customLoaders
|
customLoaders
|
||||||
)
|
]
|
||||||
},
|
},
|
||||||
// This matches plain <style> or <style scoped>
|
// This matches plain <style> or <style scoped>
|
||||||
{
|
{
|
||||||
use: this.perfLoader.css().concat(
|
use: [
|
||||||
this.css(css),
|
this.css(css),
|
||||||
customLoaders
|
customLoaders
|
||||||
)
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user