refactor: unify context in webpack module (#5054)

This commit is contained in:
Xin Du (Clark) 2019-02-18 17:00:51 +00:00 committed by GitHub
parent 3ed9f3e6a6
commit 9860eb6a7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 194 additions and 163 deletions

View File

@ -6,6 +6,10 @@ export default class BuildContext {
this.isStatic = false this.isStatic = false
} }
get buildOptions() {
return this.options.build
}
get plugins() { get plugins() {
return this._builder.plugins return this._builder.plugins
} }

View File

@ -20,4 +20,14 @@ describe('builder: buildContext', () => {
const context = new BuildContext(builder) const context = new BuildContext(builder)
expect(context.plugins).toEqual(builder.plugins) expect(context.plugins).toEqual(builder.plugins)
}) })
test('should return builder build options', () => {
const buildOptions = { id: 'test-build-options' }
const builder = {
plugins: [],
nuxt: { options: { build: buildOptions } }
}
const context = new BuildContext(builder)
expect(context.buildOptions).toEqual(buildOptions)
})
}) })

View File

@ -19,8 +19,8 @@ import PerfLoader from './utils/perf-loader'
const glob = pify(Glob) const glob = pify(Glob)
export class WebpackBundler { export class WebpackBundler {
constructor(context) { constructor(buildContext) {
this.context = context this.buildContext = buildContext
// Fields that set on build // Fields that set on build
this.compilers = [] this.compilers = []
this.compilersWatching = [] this.compilersWatching = []
@ -28,7 +28,7 @@ export class WebpackBundler {
this.hotMiddleware = {} this.hotMiddleware = {}
// Initialize shared MFS for dev // Initialize shared MFS for dev
if (this.context.options.dev) { if (this.buildContext.options.dev) {
this.mfs = new MFS() this.mfs = new MFS()
// TODO: Enable when async FS required // TODO: Enable when async FS required
@ -38,7 +38,7 @@ export class WebpackBundler {
} }
async build() { async build() {
const { options } = this.context const { options } = this.buildContext
const compilersOptions = [] const compilersOptions = []
@ -60,7 +60,7 @@ export class WebpackBundler {
compilersOptions.push(serverConfig) compilersOptions.push(serverConfig)
} }
for (const p of this.context.plugins) { for (const p of this.buildContext.plugins) {
// Client config // Client config
if (!clientConfig.resolve.alias[p.name]) { if (!clientConfig.resolve.alias[p.name]) {
clientConfig.resolve.alias[p.name] = p.mode === 'server' ? './empty.js' : p.src clientConfig.resolve.alias[p.name] = p.mode === 'server' ? './empty.js' : p.src
@ -78,7 +78,7 @@ export class WebpackBundler {
} }
// Check styleResource existence // Check styleResource existence
const { styleResources } = this.context.options.build const { styleResources } = this.buildContext.options.build
if (styleResources && Object.keys(styleResources).length) { if (styleResources && Object.keys(styleResources).length) {
consola.warn( consola.warn(
'Using styleResources without the nuxt-style-resources-module is not suggested and can lead to severe performance issues.', 'Using styleResources without the nuxt-style-resources-module is not suggested and can lead to severe performance issues.',
@ -86,7 +86,7 @@ export class WebpackBundler {
) )
for (const ext of Object.keys(styleResources)) { for (const ext of Object.keys(styleResources)) {
await Promise.all(wrapArray(styleResources[ext]).map(async (p) => { await Promise.all(wrapArray(styleResources[ext]).map(async (p) => {
const styleResourceFiles = await glob(path.resolve(this.context.options.rootDir, p)) const styleResourceFiles = await glob(path.resolve(this.buildContext.options.rootDir, p))
if (!styleResourceFiles || styleResourceFiles.length === 0) { if (!styleResourceFiles || styleResourceFiles.length === 0) {
throw new Error(`Style Resource not found: ${p}`) throw new Error(`Style Resource not found: ${p}`)
@ -122,7 +122,7 @@ export class WebpackBundler {
async webpackCompile(compiler) { async webpackCompile(compiler) {
const { name } = compiler.options const { name } = compiler.options
const { nuxt, options } = this.context const { nuxt, options } = this.buildContext
await nuxt.callHook('build:compile', { name, compiler }) await nuxt.callHook('build:compile', { name, compiler })
@ -179,7 +179,7 @@ export class WebpackBundler {
consola.debug('Adding webpack middleware...') consola.debug('Adding webpack middleware...')
const { name } = compiler.options const { name } = compiler.options
const { nuxt: { server }, options } = this.context const { nuxt: { server }, options } = this.buildContext
const { client, ...hotMiddlewareOptions } = options.build.hotMiddleware || {} const { client, ...hotMiddlewareOptions } = options.build.hotMiddleware || {}
// Create webpack dev middleware // Create webpack dev middleware
@ -255,6 +255,6 @@ export class WebpackBundler {
} }
forGenerate() { forGenerate() {
this.context.isStatic = true this.buildContext.isStatic = true
} }
} }

View File

@ -20,16 +20,9 @@ import WarnFixPlugin from '../plugins/warnfix'
import { reservedVueTags } from '../utils/reserved-tags' import { reservedVueTags } from '../utils/reserved-tags'
export default class WebpackBaseConfig { export default class WebpackBaseConfig {
constructor(builder, options) { constructor(builder) {
this.name = options.name
this.isServer = options.isServer
this.isModern = options.isModern
this.builder = builder this.builder = builder
this.nuxt = builder.context.nuxt this.buildContext = builder.buildContext
this.isStatic = builder.context.isStatic
this.options = builder.context.options
this.loaders = this.options.build.loaders
this.buildMode = this.options.dev ? 'development' : 'production'
this.modulesToTranspile = this.normalizeTranspile() this.modulesToTranspile = this.normalizeTranspile()
} }
@ -43,17 +36,29 @@ export default class WebpackBaseConfig {
get nuxtEnv() { get nuxtEnv() {
return { return {
isDev: this.options.dev, isDev: this.dev,
isServer: this.isServer, isServer: this.isServer,
isClient: !this.isServer, isClient: !this.isServer,
isModern: !!this.isModern isModern: !!this.isModern
} }
} }
get mode() {
return this.dev ? 'development' : 'production'
}
get dev() {
return this.buildContext.options.dev
}
get loaders() {
return this.buildContext.buildOptions.loaders
}
normalizeTranspile() { normalizeTranspile() {
// include SFCs in node_modules // include SFCs in node_modules
const items = [/\.vue\.js/i] const items = [/\.vue\.js/i]
for (const pattern of this.options.build.transpile) { for (const pattern of this.buildContext.buildOptions.transpile) {
if (pattern instanceof RegExp) { if (pattern instanceof RegExp) {
items.push(pattern) items.push(pattern)
} else { } else {
@ -65,7 +70,7 @@ export default class WebpackBaseConfig {
} }
getBabelOptions() { getBabelOptions() {
const options = clone(this.options.build.babel) const options = clone(this.buildContext.buildOptions.babel)
if (typeof options.presets === 'function') { if (typeof options.presets === 'function') {
options.presets = options.presets({ isServer: this.isServer }) options.presets = options.presets({ isServer: this.isServer })
@ -86,11 +91,11 @@ export default class WebpackBaseConfig {
} }
getFileName(key) { getFileName(key) {
let fileName = this.options.build.filenames[key] let fileName = this.buildContext.buildOptions.filenames[key]
if (typeof fileName === 'function') { if (typeof fileName === 'function') {
fileName = fileName(this.nuxtEnv) fileName = fileName(this.nuxtEnv)
} }
if (this.options.dev) { if (this.dev) {
const hash = /\[(chunkhash|contenthash|hash)(?::(\d+))?]/.exec(fileName) const hash = /\[(chunkhash|contenthash|hash)(?::(\d+))?]/.exec(fileName)
if (hash) { if (hash) {
consola.warn(`Notice: Please do not use ${hash[1]} in dev mode to prevent memory leak`) consola.warn(`Notice: Please do not use ${hash[1]} in dev mode to prevent memory leak`)
@ -105,11 +110,11 @@ export default class WebpackBaseConfig {
env() { env() {
const env = { const env = {
'process.env.NODE_ENV': JSON.stringify(this.buildMode), 'process.env.NODE_ENV': JSON.stringify(this.mode),
'process.mode': JSON.stringify(this.options.mode), 'process.mode': JSON.stringify(this.mode),
'process.static': this.isStatic 'process.static': this.buildContext.isStatic
} }
Object.entries(this.options.env).forEach(([key, value]) => { Object.entries(this.buildContext.options.env).forEach(([key, value]) => {
env['process.env.' + key] = env['process.env.' + key] =
['boolean', 'number'].includes(typeof value) ['boolean', 'number'].includes(typeof value)
? value ? value
@ -119,19 +124,21 @@ export default class WebpackBaseConfig {
} }
output() { output() {
const {
options: { buildDir, router },
buildOptions: { publicPath }
} = this.buildContext
return { return {
path: path.resolve(this.options.buildDir, 'dist', this.isServer ? 'server' : 'client'), path: path.resolve(buildDir, 'dist', this.isServer ? 'server' : 'client'),
filename: this.getFileName('app'), filename: this.getFileName('app'),
futureEmitAssets: true, // TODO: Remove when using webpack 5 futureEmitAssets: true, // TODO: Remove when using webpack 5
chunkFilename: this.getFileName('chunk'), chunkFilename: this.getFileName('chunk'),
publicPath: isUrl(this.options.build.publicPath) publicPath: isUrl(publicPath) ? publicPath : urlJoin(router.base, publicPath)
? this.options.build.publicPath
: urlJoin(this.options.router.base, this.options.build.publicPath)
} }
} }
optimization() { optimization() {
const optimization = cloneDeep(this.options.build.optimization) const optimization = cloneDeep(this.buildContext.buildOptions.optimization)
if (optimization.minimize && optimization.minimizer === undefined) { if (optimization.minimize && optimization.minimizer === undefined) {
optimization.minimizer = this.minimizer() optimization.minimizer = this.minimizer()
@ -142,13 +149,14 @@ export default class WebpackBaseConfig {
minimizer() { minimizer() {
const minimizer = [] const minimizer = []
const { terser, cache } = this.buildContext.buildOptions
// https://github.com/webpack-contrib/terser-webpack-plugin // https://github.com/webpack-contrib/terser-webpack-plugin
if (this.options.build.terser) { if (terser) {
minimizer.push( minimizer.push(
new TerserWebpackPlugin(Object.assign({ new TerserWebpackPlugin(Object.assign({
parallel: true, parallel: true,
cache: this.options.build.cache, cache,
sourceMap: this.devtool && /source-?map/.test(this.devtool), sourceMap: this.devtool && /source-?map/.test(this.devtool),
extractComments: { extractComments: {
filename: 'LICENSES' filename: 'LICENSES'
@ -164,7 +172,7 @@ export default class WebpackBaseConfig {
reserved: reservedVueTags reserved: reservedVueTags
} }
} }
}, this.options.build.terser)) }, terser))
) )
} }
@ -172,7 +180,7 @@ export default class WebpackBaseConfig {
} }
alias() { alias() {
const { srcDir, rootDir, dir: { assets: assetsDir, static: staticDir } } = this.options const { srcDir, rootDir, dir: { assets: assetsDir, static: staticDir } } = this.buildContext.options
return { return {
'~': path.join(srcDir), '~': path.join(srcDir),
@ -185,10 +193,9 @@ export default class WebpackBaseConfig {
} }
rules() { rules() {
const perfLoader = new PerfLoader(this) const perfLoader = new PerfLoader(this.name, this.buildContext)
const styleLoader = new StyleLoader( const styleLoader = new StyleLoader(
this.options, this.buildContext,
this.nuxt,
{ isServer: this.isServer, perfLoader } { isServer: this.isServer, perfLoader }
) )
const babelLoader = { const babelLoader = {
@ -329,25 +336,26 @@ export default class WebpackBaseConfig {
plugins() { plugins() {
const plugins = [] const plugins = []
const { nuxt, buildOptions } = this.buildContext
// Add timefix-plugin before others plugins // Add timefix-plugin before others plugins
if (this.options.dev) { if (this.dev) {
plugins.push(new TimeFixPlugin()) plugins.push(new TimeFixPlugin())
} }
// CSS extraction) // CSS extraction)
if (this.options.build.extractCSS) { if (buildOptions.extractCSS) {
plugins.push(new ExtractCssChunksPlugin(Object.assign({ plugins.push(new ExtractCssChunksPlugin(Object.assign({
filename: this.getFileName('css'), filename: this.getFileName('css'),
chunkFilename: this.getFileName('css'), chunkFilename: this.getFileName('css'),
// TODO: https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/issues/132 // TODO: https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/issues/132
reloadAll: true reloadAll: true
}, this.options.build.extractCSS))) }, buildOptions.extractCSS)))
} }
plugins.push(new VueLoader.VueLoaderPlugin()) plugins.push(new VueLoader.VueLoaderPlugin())
plugins.push(...(this.options.build.plugins || [])) plugins.push(...(buildOptions.plugins || []))
// Hide warnings about plugins without a default export (#1179) // Hide warnings about plugins without a default export (#1179)
plugins.push(new WarnFixPlugin()) plugins.push(new WarnFixPlugin())
@ -362,37 +370,38 @@ export default class WebpackBaseConfig {
'profile', 'profile',
'stats' 'stats'
], ],
basic: !this.options.build.quiet && env.minimalCLI, basic: !buildOptions.quiet && env.minimalCLI,
fancy: !this.options.build.quiet && !env.minimalCLI, fancy: !buildOptions.quiet && !env.minimalCLI,
profile: !this.options.build.quiet && this.options.build.profile, profile: !buildOptions.quiet && buildOptions.profile,
stats: !this.options.build.quiet && !this.options.dev && this.options.build.stats, stats: !buildOptions.quiet && !this.dev && buildOptions.stats,
reporter: { reporter: {
change: (_, { shortPath }) => { change: (_, { shortPath }) => {
if (!this.isServer) { if (!this.isServer) {
this.nuxt.callHook('bundler:change', shortPath) nuxt.callHook('bundler:change', shortPath)
} }
}, },
done: (context) => { done: (buildContext) => {
if (context.hasErrors) { if (buildContext.hasErrors) {
this.nuxt.callHook('bundler:error') nuxt.callHook('bundler:error')
} }
}, },
allDone: () => { allDone: () => {
this.nuxt.callHook('bundler:done') nuxt.callHook('bundler:done')
} }
} }
})) }))
if (this.options.build.hardSource) { if (buildOptions.hardSource) {
plugins.push(new HardSourcePlugin(Object.assign({}, this.options.build.hardSource))) plugins.push(new HardSourcePlugin(Object.assign({}, buildOptions.hardSource)))
} }
return plugins return plugins
} }
extendConfig(config) { extendConfig(config) {
if (typeof this.options.build.extend === 'function') { const { extend } = this.buildContext.buildOptions
const extendedConfig = this.options.build.extend.call( if (typeof extend === 'function') {
const extendedConfig = extend.call(
this.builder, config, { loaders: this.loaders, ...this.nuxtEnv } this.builder, config, { loaders: this.loaders, ...this.nuxtEnv }
) )
// Only overwrite config when something is returned for backwards compatibility // Only overwrite config when something is returned for backwards compatibility
@ -405,17 +414,17 @@ export default class WebpackBaseConfig {
config() { config() {
// Prioritize nested node_modules in webpack search path (#2558) // Prioritize nested node_modules in webpack search path (#2558)
const webpackModulesDir = ['node_modules'].concat(this.options.modulesDir) const webpackModulesDir = ['node_modules'].concat(this.buildContext.options.modulesDir)
const config = { const config = {
name: this.name, name: this.name,
mode: this.buildMode, mode: this.mode,
devtool: this.devtool, devtool: this.devtool,
optimization: this.optimization(), optimization: this.optimization(),
output: this.output(), output: this.output(),
performance: { performance: {
maxEntrypointSize: 1000 * 1024, maxEntrypointSize: 1000 * 1024,
hints: this.options.dev ? false : 'warning' hints: this.dev ? false : 'warning'
}, },
resolve: { resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.vue', '.jsx', '.ts', '.tsx'], extensions: ['.wasm', '.mjs', '.js', '.json', '.vue', '.jsx', '.ts', '.tsx'],

View File

@ -14,12 +14,15 @@ import VueSSRClientPlugin from '../plugins/vue/client'
import WebpackBaseConfig from './base' import WebpackBaseConfig from './base'
export default class WebpackClientConfig extends WebpackBaseConfig { export default class WebpackClientConfig extends WebpackBaseConfig {
constructor(builder, options) { constructor(builder) {
super(builder, options || { name: 'client', isServer: false }) super(builder)
this.name = 'client'
this.isServer = false
this.isModern = false
} }
getFileName(...args) { getFileName(...args) {
if (this.options.build.analyze) { if (this.buildContext.buildOptions.analyze) {
const [key] = args const [key] = args
if (['app', 'chunk'].includes(key)) { if (['app', 'chunk'].includes(key)) {
return `${this.isModern ? 'modern-' : ''}[name].js` return `${this.isModern ? 'modern-' : ''}[name].js`
@ -44,7 +47,7 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
// Small, known and common modules which are usually used project-wise // Small, known and common modules which are usually used project-wise
// Sum of them may not be more than 244 KiB // Sum of them may not be more than 244 KiB
if ( if (
this.options.build.splitChunks.commons === true && this.buildContext.buildOptions.splitChunks.commons === true &&
optimization.splitChunks.cacheGroups.commons === undefined optimization.splitChunks.cacheGroups.commons === undefined
) { ) {
optimization.splitChunks.cacheGroups.commons = { optimization.splitChunks.cacheGroups.commons = {
@ -60,14 +63,13 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
minimizer() { minimizer() {
const minimizer = super.minimizer() const minimizer = super.minimizer()
const { optimizeCSS } = this.buildContext.buildOptions
// https://github.com/NMFR/optimize-css-assets-webpack-plugin // https://github.com/NMFR/optimize-css-assets-webpack-plugin
// https://github.com/webpack-contrib/mini-css-extract-plugin#minimizing-for-production // https://github.com/webpack-contrib/mini-css-extract-plugin#minimizing-for-production
// TODO: Remove OptimizeCSSAssetsPlugin when upgrading to webpack 5 // TODO: Remove OptimizeCSSAssetsPlugin when upgrading to webpack 5
if (this.options.build.optimizeCSS) { if (optimizeCSS) {
minimizer.push( minimizer.push(new OptimizeCSSAssetsPlugin(Object.assign({}, optimizeCSS)))
new OptimizeCSSAssetsPlugin(Object.assign({}, this.options.build.optimizeCSS))
)
} }
return minimizer return minimizer
@ -75,14 +77,15 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
plugins() { plugins() {
const plugins = super.plugins() const plugins = super.plugins()
const { buildOptions, options: { appTemplatePath, buildDir, rootDir, modern } } = this.buildContext
// Generate output HTML for SSR // Generate output HTML for SSR
if (this.options.build.ssr) { if (buildOptions.ssr) {
plugins.push( plugins.push(
new HTMLPlugin({ new HTMLPlugin({
filename: '../server/index.ssr.html', filename: '../server/index.ssr.html',
template: this.options.appTemplatePath, template: appTemplatePath,
minify: this.options.build.html.minify, minify: buildOptions.html.minify,
inject: false // Resources will be injected using bundleRenderer inject: false // Resources will be injected using bundleRenderer
}) })
) )
@ -91,8 +94,8 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
plugins.push( plugins.push(
new HTMLPlugin({ new HTMLPlugin({
filename: '../server/index.spa.html', filename: '../server/index.spa.html',
template: this.options.appTemplatePath, template: appTemplatePath,
minify: this.options.build.html.minify, minify: buildOptions.html.minify,
inject: true, inject: true,
chunksSortMode: 'dependency' chunksSortMode: 'dependency'
}), }),
@ -102,53 +105,53 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
new webpack.DefinePlugin(this.env()) new webpack.DefinePlugin(this.env())
) )
if (this.options.dev) { if (this.dev) {
// TODO: webpackHotUpdate is not defined: https://github.com/webpack/webpack/issues/6693 // TODO: webpackHotUpdate is not defined: https://github.com/webpack/webpack/issues/6693
plugins.push(new webpack.HotModuleReplacementPlugin()) plugins.push(new webpack.HotModuleReplacementPlugin())
} }
// Webpack Bundle Analyzer // Webpack Bundle Analyzer
// https://github.com/webpack-contrib/webpack-bundle-analyzer // https://github.com/webpack-contrib/webpack-bundle-analyzer
if (!this.options.dev && this.options.build.analyze) { if (!this.dev && buildOptions.analyze) {
const statsDir = path.resolve(this.options.buildDir, 'stats') const statsDir = path.resolve(buildDir, 'stats')
plugins.push(new BundleAnalyzer.BundleAnalyzerPlugin(Object.assign({ plugins.push(new BundleAnalyzer.BundleAnalyzerPlugin(Object.assign({
analyzerMode: 'static', analyzerMode: 'static',
defaultSizes: 'gzip', defaultSizes: 'gzip',
generateStatsFile: true, generateStatsFile: true,
openAnalyzer: !this.options.build.quiet, openAnalyzer: !buildOptions.quiet,
reportFilename: path.resolve(statsDir, `${this.name}.html`), reportFilename: path.resolve(statsDir, `${this.name}.html`),
statsFilename: path.resolve(statsDir, `${this.name}.json`) statsFilename: path.resolve(statsDir, `${this.name}.json`)
}, this.options.build.analyze))) }, buildOptions.analyze)))
} }
if (this.options.modern) { if (modern) {
plugins.push(new ModernModePlugin({ plugins.push(new ModernModePlugin({
targetDir: path.resolve(this.options.buildDir, 'dist', 'client'), targetDir: path.resolve(buildDir, 'dist', 'client'),
isModernBuild: this.isModern isModernBuild: this.isModern
})) }))
} }
if (this.options.build.crossorigin) { if (buildOptions.crossorigin) {
plugins.push(new CorsPlugin({ plugins.push(new CorsPlugin({
crossorigin: this.options.build.crossorigin crossorigin: buildOptions.crossorigin
})) }))
} }
// TypeScript type checker // TypeScript type checker
// Only performs once per client compilation and only if `ts-loader` checker is not used (transpileOnly: true) // Only performs once per client compilation and only if `ts-loader` checker is not used (transpileOnly: true)
if (!this.isModern && this.loaders.ts.transpileOnly && this.options.build.useForkTsChecker) { if (!this.isModern && this.loaders.ts.transpileOnly && buildOptions.useForkTsChecker) {
const forkTsCheckerResolvedPath = this.nuxt.resolver.resolveModule('fork-ts-checker-webpack-plugin') const forkTsCheckerResolvedPath = this.buildContext.nuxt.resolver.resolveModule('fork-ts-checker-webpack-plugin')
if (forkTsCheckerResolvedPath) { if (forkTsCheckerResolvedPath) {
const ForkTsCheckerWebpackPlugin = require(forkTsCheckerResolvedPath) const ForkTsCheckerWebpackPlugin = require(forkTsCheckerResolvedPath)
plugins.push(new ForkTsCheckerWebpackPlugin(Object.assign({ plugins.push(new ForkTsCheckerWebpackPlugin(Object.assign({
vue: true, vue: true,
tsconfig: path.resolve(this.options.rootDir, 'tsconfig.json'), tsconfig: path.resolve(rootDir, 'tsconfig.json'),
// https://github.com/Realytics/fork-ts-checker-webpack-plugin#options - tslint: boolean | string - So we set it false if file not found // https://github.com/Realytics/fork-ts-checker-webpack-plugin#options - tslint: boolean | string - So we set it false if file not found
tslint: (tslintPath => fs.existsSync(tslintPath) && tslintPath)(path.resolve(this.options.rootDir, 'tslint.json')), tslint: (tslintPath => fs.existsSync(tslintPath) && tslintPath)(path.resolve(rootDir, 'tslint.json')),
formatter: 'codeframe', formatter: 'codeframe',
logger: consola logger: consola
}, this.options.build.useForkTsChecker))) }, buildOptions.useForkTsChecker)))
} else { } else {
consola.warn('You need to install `fork-ts-checker-webpack-plugin` as devDependency to enable TypeScript type checking !') consola.warn('You need to install `fork-ts-checker-webpack-plugin` as devDependency to enable TypeScript type checking !')
} }
@ -159,8 +162,12 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
config() { config() {
const config = super.config() const config = super.config()
const {
options: { router, buildDir },
buildOptions: { hotMiddleware, quiet, friendlyErrors }
} = this.buildContext
const { client = {} } = this.options.build.hotMiddleware || {} const { client = {} } = hotMiddleware || {}
const { ansiColors, overlayStyles, ...options } = client const { ansiColors, overlayStyles, ...options } = client
const hotMiddlewareClientOptions = { const hotMiddlewareClientOptions = {
reload: true, reload: true,
@ -170,17 +177,17 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
...options, ...options,
name: this.name name: this.name
} }
const clientPath = `${this.options.router.base}/__webpack_hmr/${this.name}` const clientPath = `${router.base}/__webpack_hmr/${this.name}`
const hotMiddlewareClientOptionsStr = const hotMiddlewareClientOptionsStr =
`${querystring.stringify(hotMiddlewareClientOptions)}&path=${clientPath}`.replace(/\/\//g, '/') `${querystring.stringify(hotMiddlewareClientOptions)}&path=${clientPath}`.replace(/\/\//g, '/')
// Entry points // Entry points
config.entry = { config.entry = {
app: [path.resolve(this.options.buildDir, 'client.js')] app: [path.resolve(buildDir, 'client.js')]
} }
// Add HMR support // Add HMR support
if (this.options.dev) { if (this.dev) {
config.entry.app.unshift( config.entry.app.unshift(
// https://github.com/webpack-contrib/webpack-hot-middleware/issues/53#issuecomment-162823945 // https://github.com/webpack-contrib/webpack-hot-middleware/issues/53#issuecomment-162823945
'eventsource-polyfill', 'eventsource-polyfill',
@ -190,7 +197,7 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
} }
// Add friendly error plugin // Add friendly error plugin
if (this.options.dev && !this.options.build.quiet && this.options.build.friendlyErrors) { if (this.dev && !quiet && friendlyErrors) {
config.plugins.push( config.plugins.push(
new FriendlyErrorsWebpackPlugin({ new FriendlyErrorsWebpackPlugin({
clearConsole: false, clearConsole: false,

View File

@ -2,8 +2,10 @@ import clone from 'lodash/clone'
import WebpackClientConfig from './client' import WebpackClientConfig from './client'
export default class WebpackModernConfig extends WebpackClientConfig { export default class WebpackModernConfig extends WebpackClientConfig {
constructor(builder) { constructor(...args) {
super(builder, { name: 'modern', isServer: false, isModern: true }) super(...args)
this.name = 'modern'
this.isModern = true
} }
env() { env() {
@ -13,7 +15,7 @@ export default class WebpackModernConfig extends WebpackClientConfig {
} }
getBabelOptions() { getBabelOptions() {
const options = clone(this.options.build.babel) const options = clone(this.buildContext.buildOptions.babel)
options.presets = [ options.presets = [
[ [

View File

@ -9,8 +9,10 @@ import VueSSRServerPlugin from '../plugins/vue/server'
import WebpackBaseConfig from './base' import WebpackBaseConfig from './base'
export default class WebpackServerConfig extends WebpackBaseConfig { export default class WebpackServerConfig extends WebpackBaseConfig {
constructor(builder) { constructor(...args) {
super(builder, { name: 'server', isServer: true }) super(...args)
this.name = 'server'
this.isServer = true
this.whitelist = this.normalizeWhitelist() this.whitelist = this.normalizeWhitelist()
} }
@ -18,7 +20,7 @@ export default class WebpackServerConfig extends WebpackBaseConfig {
const whitelist = [ const whitelist = [
/\.(?!js(x|on)?$)/i /\.(?!js(x|on)?$)/i
] ]
for (const pattern of this.options.build.transpile) { for (const pattern of this.buildContext.buildOptions.transpile) {
if (pattern instanceof RegExp) { if (pattern instanceof RegExp) {
whitelist.push(pattern) whitelist.push(pattern)
} else { } else {
@ -68,7 +70,7 @@ export default class WebpackServerConfig extends WebpackBaseConfig {
target: 'node', target: 'node',
node: false, node: false,
entry: { entry: {
app: [path.resolve(this.options.buildDir, 'server.js')] app: [path.resolve(this.buildContext.options.buildDir, 'server.js')]
}, },
output: Object.assign({}, config.output, { output: Object.assign({}, config.output, {
filename: 'server.js', filename: 'server.js',
@ -85,8 +87,8 @@ export default class WebpackServerConfig extends WebpackBaseConfig {
// https://webpack.js.org/configuration/externals/#externals // https://webpack.js.org/configuration/externals/#externals
// https://github.com/liady/webpack-node-externals // https://github.com/liady/webpack-node-externals
// https://vue-loader.vuejs.org/migrating.html#ssr-externals // https://vue-loader.vuejs.org/migrating.html#ssr-externals
if (!this.options.build.standalone) { if (!this.buildContext.buildOptions.standalone) {
this.options.modulesDir.forEach((dir) => { this.buildContext.options.modulesDir.forEach((dir) => {
if (fs.existsSync(dir)) { if (fs.existsSync(dir)) {
config.externals.push( config.externals.push(
nodeExternals({ nodeExternals({

View File

@ -6,10 +6,10 @@ import { warmup } from 'thread-loader'
// https://github.com/webpack-contrib/cache-loader // https://github.com/webpack-contrib/cache-loader
export default class PerfLoader { export default class PerfLoader {
constructor(config) { constructor(name, buildContext) {
this.name = config.name this.name = name
this.options = config.options this.buildContext = buildContext
this.workerPools = PerfLoader.defaultPools(this.options) this.workerPools = PerfLoader.defaultPools({ dev: buildContext.options.dev })
return new Proxy(this, { return new Proxy(this, {
get(target, name) { get(target, name) {
return target[name] ? target[name] : target.use.bind(target, name) return target[name] ? target[name] : target.use.bind(target, name)
@ -25,13 +25,13 @@ export default class PerfLoader {
} }
} }
static warmupAll(options) { static warmupAll({ dev }) {
options = PerfLoader.defaultPools(options) const pools = PerfLoader.defaultPools({ dev })
PerfLoader.warmup(options.js, [ PerfLoader.warmup(pools.js, [
require.resolve('babel-loader'), require.resolve('babel-loader'),
require.resolve('@babel/preset-env') require.resolve('@babel/preset-env')
]) ])
PerfLoader.warmup(options.css, ['css-loader']) PerfLoader.warmup(pools.css, ['css-loader'])
} }
static warmup(...args) { static warmup(...args) {
@ -41,7 +41,7 @@ export default class PerfLoader {
use(poolName) { use(poolName) {
const loaders = [] const loaders = []
if (this.options.build.cache) { if (this.buildContext.buildOptions.cache) {
loaders.push({ loaders.push({
loader: 'cache-loader', loader: 'cache-loader',
options: { options: {
@ -50,7 +50,7 @@ export default class PerfLoader {
}) })
} }
if (this.options.build.parallel) { if (this.buildContext.buildOptions.parallel) {
const pool = this.workerPools[poolName] const pool = this.workerPools[poolName]
if (pool) { if (pool) {
loaders.push({ loaders.push({

View File

@ -19,34 +19,29 @@ export const orderPresets = {
} }
export default class PostcssConfig { export default class PostcssConfig {
constructor(options, nuxt) { constructor(buildContext) {
this.nuxt = nuxt this.buildContext = buildContext
this.dev = options.dev }
this.postcss = options.build.postcss
this.srcDir = options.srcDir get postcssOptions() {
this.rootDir = options.rootDir return this.buildContext.buildOptions.postcss
this.cssSourceMap = options.build.cssSourceMap
this.modulesDir = options.modulesDir
} }
get defaultConfig() { get defaultConfig() {
const { dev, srcDir, rootDir, modulesDir } = this.buildContext.options
return { return {
sourceMap: this.cssSourceMap, sourceMap: this.buildContext.buildOptions.cssSourceMap,
plugins: { plugins: {
// https://github.com/postcss/postcss-import // https://github.com/postcss/postcss-import
'postcss-import': { 'postcss-import': {
resolve: createResolver({ resolve: createResolver({
alias: { alias: {
'~': path.join(this.srcDir), '~': path.join(srcDir),
'~~': path.join(this.rootDir), '~~': path.join(rootDir),
'@': path.join(this.srcDir), '@': path.join(srcDir),
'@@': path.join(this.rootDir) '@@': path.join(rootDir)
}, },
modules: [ modules: [ srcDir, rootDir, ...modulesDir ]
this.srcDir,
this.rootDir,
...this.modulesDir
]
}) })
}, },
@ -55,7 +50,7 @@ export default class PostcssConfig {
// https://github.com/csstools/postcss-preset-env // https://github.com/csstools/postcss-preset-env
'postcss-preset-env': this.preset || {}, 'postcss-preset-env': this.preset || {},
'cssnano': this.dev ? false : { preset: 'default' } 'cssnano': dev ? false : { preset: 'default' }
}, },
// Array, String or Function // Array, String or Function
order: 'cssnanoLast' order: 'cssnanoLast'
@ -65,7 +60,8 @@ export default class PostcssConfig {
searchConfigFile() { searchConfigFile() {
// Search for postCSS config file and use it if exists // Search for postCSS config file and use it if exists
// https://github.com/michael-ciniawsky/postcss-load-config // https://github.com/michael-ciniawsky/postcss-load-config
for (const dir of [this.srcDir, this.rootDir]) { const { srcDir, rootDir } = this.buildContext.options
for (const dir of [ srcDir, rootDir ]) {
for (const file of [ for (const file of [
'postcss.config.js', 'postcss.config.js',
'.postcssrc.js', '.postcssrc.js',
@ -82,12 +78,12 @@ export default class PostcssConfig {
} }
configFromFile() { configFromFile() {
const loaderConfig = (this.postcss && this.postcss.config) || {} const loaderConfig = (this.postcssOptions && this.postcssOptions.config) || {}
loaderConfig.path = loaderConfig.path || this.searchConfigFile() loaderConfig.path = loaderConfig.path || this.searchConfigFile()
if (loaderConfig.path) { if (loaderConfig.path) {
return { return {
sourceMap: this.cssSourceMap, sourceMap: this.buildContext.buildOptions.cssSourceMap,
config: loaderConfig config: loaderConfig
} }
} }
@ -117,7 +113,7 @@ export default class PostcssConfig {
// Map postcss plugins into instances on object mode once // Map postcss plugins into instances on object mode once
config.plugins = this.sortPlugins(config) config.plugins = this.sortPlugins(config)
.map((p) => { .map((p) => {
const plugin = this.nuxt.resolver.requireModule(p) const plugin = this.buildContext.nuxt.resolver.requireModule(p)
const opts = plugins[p] const opts = plugins[p]
if (opts === false) { if (opts === false) {
return // Disabled return // Disabled
@ -130,7 +126,7 @@ export default class PostcssConfig {
config() { config() {
/* istanbul ignore if */ /* istanbul ignore if */
if (!this.postcss) { if (!this.postcssOptions) {
return false return false
} }
@ -139,7 +135,7 @@ export default class PostcssConfig {
return config return config
} }
config = this.normalize(cloneDeep(this.postcss)) config = this.normalize(cloneDeep(this.postcssOptions))
// Apply default plugins // Apply default plugins
if (isPureObject(config)) { if (isPureObject(config)) {

View File

@ -6,24 +6,20 @@ import { wrapArray } from '@nuxt/utils'
import PostcssConfig from './postcss' import PostcssConfig from './postcss'
export default class StyleLoader { export default class StyleLoader {
constructor(options, nuxt, { isServer, perfLoader }) { constructor(buildContext, { isServer, perfLoader }) {
this.buildContext = buildContext
this.isServer = isServer this.isServer = isServer
this.perfLoader = perfLoader this.perfLoader = perfLoader
this.rootDir = options.rootDir
this.loaders = {
vueStyle: options.build.loaders.vueStyle,
css: options.build.loaders.css,
cssModules: options.build.loaders.cssModules
}
this.extractCSS = options.build.extractCSS
this.resources = options.build.styleResources
this.sourceMap = Boolean(options.build.cssSourceMap)
if (options.build.postcss) { if (buildContext.options.build.postcss) {
this.postcssConfig = new PostcssConfig(options, nuxt) this.postcssConfig = new PostcssConfig(buildContext)
} }
} }
get extractCSS() {
return this.buildContext.buildOptions.extractCSS
}
get exportOnlyLocals() { get exportOnlyLocals() {
return Boolean(this.isServer && this.extractCSS) return Boolean(this.isServer && this.extractCSS)
} }
@ -34,19 +30,20 @@ export default class StyleLoader {
} }
styleResource(ext) { styleResource(ext) {
const extResource = this.resources[ext] const { buildOptions: { styleResources }, options: { rootDir } } = this.buildContext
const extResource = styleResources[ext]
// style-resources-loader // style-resources-loader
// https://github.com/yenshih/style-resources-loader // https://github.com/yenshih/style-resources-loader
if (!extResource) { if (!extResource) {
return return
} }
const patterns = wrapArray(extResource).map(p => path.resolve(this.rootDir, p)) const patterns = wrapArray(extResource).map(p => path.resolve(rootDir, p))
return { return {
loader: 'style-resources-loader', loader: 'style-resources-loader',
options: Object.assign( options: Object.assign(
{ patterns }, { patterns },
this.resources.options || {} styleResources.options || {}
) )
} }
} }
@ -66,7 +63,7 @@ export default class StyleLoader {
return { return {
loader: 'postcss-loader', loader: 'postcss-loader',
options: Object.assign({ sourceMap: this.sourceMap }, config) options: Object.assign({ sourceMap: this.buildContext.buildOptions.cssSourceMap }, config)
} }
} }
@ -94,32 +91,34 @@ export default class StyleLoader {
styleLoader() { styleLoader() {
return this.extract() || { return this.extract() || {
loader: 'vue-style-loader', loader: 'vue-style-loader',
options: this.loaders.vueStyle options: this.buildContext.buildOptions.loaders.vueStyle
} }
} }
apply(ext, loaders = []) { apply(ext, loaders = []) {
const { css, cssModules } = this.buildContext.buildOptions.loaders
const customLoaders = [].concat( const customLoaders = [].concat(
this.postcss(), this.postcss(),
this.normalize(loaders), this.normalize(loaders),
this.styleResource(ext) this.styleResource(ext)
).filter(Boolean) ).filter(Boolean)
this.loaders.css.importLoaders = this.loaders.cssModules.importLoaders = customLoaders.length css.importLoaders = cssModules.importLoaders = customLoaders.length
return [ return [
// This matches <style module> // This matches <style module>
{ {
resourceQuery: /module/, resourceQuery: /module/,
use: this.perfLoader.css().concat( use: this.perfLoader.css().concat(
this.cssModules(this.loaders.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.perfLoader.css().concat(
this.css(this.loaders.css), this.css(css),
customLoaders customLoaders
) )
} }

View File

@ -46,7 +46,7 @@ describe('basic generate', () => {
}) })
test('Check builder', () => { test('Check builder', () => {
expect(builder.bundleBuilder.context.isStatic).toBe(true) expect(builder.bundleBuilder.buildContext.isStatic).toBe(true)
expect(builder.build).toHaveBeenCalledTimes(1) expect(builder.build).toHaveBeenCalledTimes(1)
}) })

View File

@ -15,16 +15,18 @@ describe('webpack configuration', () => {
]) ])
expect(PerfLoader.warmup).toHaveBeenCalledWith(css, ['css-loader']) expect(PerfLoader.warmup).toHaveBeenCalledWith(css, ['css-loader'])
const perfLoader = new PerfLoader({ const perfLoader = new PerfLoader(
name: 'test-perf', 'test-perf',
{
options: { options: {
dev: true, dev: true
build: { },
buildOptions: {
parallel: true, parallel: true,
cache: true cache: true
} }
} }
}) )
expect(perfLoader.workerPools).toMatchObject({ js, css }) expect(perfLoader.workerPools).toMatchObject({ js, css })
const loaders = perfLoader.use('js') const loaders = perfLoader.use('js')
const cacheDirectory = path.resolve('node_modules/.cache/cache-loader/test-perf') const cacheDirectory = path.resolve('node_modules/.cache/cache-loader/test-perf')