refactor: webpack build config

This commit is contained in:
Clark Du 2018-03-22 22:06:54 +08:00 committed by Pooya Parsa
parent 46f7a0bc70
commit 818e982eca
4 changed files with 393 additions and 358 deletions

View File

@ -18,8 +18,8 @@ import upath from 'upath'
import { r, wp, wChunk, createRoutes, parallel, relativeTo, waitFor, createSpinner } from '../common/utils' import { r, wp, wChunk, createRoutes, parallel, relativeTo, waitFor, createSpinner } from '../common/utils'
import Options from '../common/options' import Options from '../common/options'
import clientWebpackConfig from './webpack/client.config' import ClientWebpackConfig from './webpack/client.config'
import serverWebpackConfig from './webpack/server.config' import ServerWebpackConfig from './webpack/server.config'
const debug = Debug('nuxt:build') const debug = Debug('nuxt:build')
debug.color = 2 // Force green color debug.color = 2 // Force green color
@ -147,39 +147,6 @@ export default class Builder {
return this return this
} }
getBabelOptions({ isServer }) {
const options = _.clone(this.options.build.babel)
if (typeof options.presets === 'function') {
options.presets = options.presets({ isServer })
}
if (!options.babelrc && !options.presets) {
options.presets = [
[
this.nuxt.resolvePath('babel-preset-vue-app'),
{
targets: isServer ? { node: '8.0.0' } : { ie: 9, uglify: true }
}
]
]
}
return options
}
getFileName(name) {
let fileName = this.options.build.filenames[name]
// Don't use hashes when watching
// https://github.com/webpack/webpack/issues/1914#issuecomment-174171709
if (this.options.dev) {
fileName = fileName.replace(/\[(chunkhash|contenthash|hash)\]\./g, '')
}
return fileName
}
async generateRoutesAndFiles() { async generateRoutesAndFiles() {
this.spinner.start(`Generating nuxt files...`) this.spinner.start(`Generating nuxt files...`)
@ -452,13 +419,13 @@ export default class Builder {
const compilersOptions = [] const compilersOptions = []
// Client // Client
const clientConfig = clientWebpackConfig.call(this) const clientConfig = new ClientWebpackConfig(this).config()
compilersOptions.push(clientConfig) compilersOptions.push(clientConfig)
// Server // Server
let serverConfig = null let serverConfig = null
if (this.options.build.ssr) { if (this.options.build.ssr) {
serverConfig = serverWebpackConfig.call(this) serverConfig = new ServerWebpackConfig(this).config()
compilersOptions.push(serverConfig) compilersOptions.push(serverConfig)
} }

View File

@ -9,7 +9,7 @@ import VueLoader from 'vue-loader'
import { isUrl, urlJoin } from '../../common/utils' import { isUrl, urlJoin } from '../../common/utils'
import customLoaders from './loaders' import customLoaders from './loaders'
import styleLoaderWrapper from './style-loader' import createStyleLoader from './style-loader'
import WarnFixPlugin from './plugins/warnfix' import WarnFixPlugin from './plugins/warnfix'
import ProgressPlugin from './plugins/progress' import ProgressPlugin from './plugins/progress'
import StatsPlugin from './plugins/stats' import StatsPlugin from './plugins/stats'
@ -22,60 +22,96 @@ import StatsPlugin from './plugins/stats'
| webpack config files | webpack config files
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
export default function webpackBaseConfig({ name, isServer }) { export default class WebpackBaseConfig {
// Prioritize nested node_modules in webpack search path (#2558) constructor(builder, options) {
const webpackModulesDir = ['node_modules'].concat(this.options.modulesDir) this.name = options.name
this.isServer = options.isServer
this.builder = builder
this.isStatic = builder.isStatic
this.options = builder.options
this.spinner = builder.spinner
}
const configAlias = {} getBabelOptions() {
const styleLoader = styleLoaderWrapper({ isServer }) const options = _.clone(this.options.build.babel)
// Used by vue-loader so we can use in templates if (typeof options.presets === 'function') {
// with <img src="~/assets/nuxt.png"/> options.presets = options.presets({ isServer: this.isServer })
configAlias[this.options.dir.assets] = path.join( }
this.options.srcDir,
this.options.dir.assets
)
configAlias[this.options.dir.static] = path.join(
this.options.srcDir,
this.options.dir.static
)
const config = { if (!options.babelrc && !options.presets) {
name, options.presets = [
mode: this.options.dev ? 'development' : 'production', [
optimization: {}, this.builder.nuxt.resolvePath('babel-preset-vue-app'),
output: { {
targets: this.isServer ? { node: '8.0.0' } : { ie: 9, uglify: true }
}
]
]
}
return options
}
getFileName(name) {
let fileName = this.options.build.filenames[name]
// Don't use hashes when watching
// https://github.com/webpack/webpack/issues/1914#issuecomment-174171709
if (this.options.dev) {
fileName = fileName.replace(/\[(chunkhash|contenthash|hash)\]\./g, '')
}
return fileName
}
env() {
const env = {
'process.mode': JSON.stringify(this.options.mode),
'process.static': this.isStatic
}
_.each(this.options.env, (value, key) => {
env['process.env.' + key] =
['boolean', 'number'].indexOf(typeof value) !== -1
? value
: JSON.stringify(value)
})
return env
}
output() {
return {
path: path.resolve(this.options.buildDir, 'dist'), path: path.resolve(this.options.buildDir, 'dist'),
filename: this.getFileName('app'), filename: this.getFileName('app'),
chunkFilename: this.getFileName('chunk'), chunkFilename: this.getFileName('chunk'),
publicPath: isUrl(this.options.build.publicPath) publicPath: isUrl(this.options.build.publicPath)
? this.options.build.publicPath ? this.options.build.publicPath
: urlJoin(this.options.router.base, this.options.build.publicPath) : urlJoin(this.options.router.base, this.options.build.publicPath)
}, }
performance: { }
maxEntrypointSize: 1000 * 1024,
hints: this.options.dev ? false : 'warning' alias() {
}, return {
resolve: {
extensions: ['.js', '.json', '.vue', '.jsx'],
alias: Object.assign(
{
'~': path.join(this.options.srcDir), '~': path.join(this.options.srcDir),
'~~': path.join(this.options.rootDir), '~~': path.join(this.options.rootDir),
'@': path.join(this.options.srcDir), '@': path.join(this.options.srcDir),
'@@': path.join(this.options.rootDir) '@@': path.join(this.options.rootDir),
}, [this.options.dir.assets]: path.join(
configAlias this.options.srcDir,
this.options.dir.assets
), ),
modules: webpackModulesDir [this.options.dir.static]: path.join(
}, this.options.srcDir,
resolveLoader: { this.options.dir.static
alias: customLoaders, )
modules: webpackModulesDir }
}, }
module: {
noParse: /es6-promise\.js$/, // Avoid webpack shimming process rules() {
rules: [ const styleLoader = createStyleLoader({
isServer: this.isServer
})
return [
{ {
test: /\.vue$/, test: /\.vue$/,
loader: 'vue-loader', loader: 'vue-loader',
@ -85,21 +121,21 @@ export default function webpackBaseConfig({ name, isServer }) {
test: /\.jsx?$/, test: /\.jsx?$/,
loader: 'babel-loader', loader: 'babel-loader',
exclude: /node_modules/, exclude: /node_modules/,
options: this.getBabelOptions({ isServer }) options: this.getBabelOptions()
}, },
{ test: /\.css$/, use: styleLoader.call(this, 'css') }, { test: /\.css$/, use: styleLoader.call(this.builder, 'css') },
{ test: /\.less$/, use: styleLoader.call(this, 'less', 'less-loader') }, { test: /\.less$/, use: styleLoader.call(this.builder, 'less', 'less-loader') },
{ {
test: /\.sass$/, test: /\.sass$/,
use: styleLoader.call(this, 'sass', { use: styleLoader.call(this.builder, 'sass', {
loader: 'sass-loader', loader: 'sass-loader',
options: { indentedSyntax: true } options: { indentedSyntax: true }
}) })
}, },
{ test: /\.scss$/, use: styleLoader.call(this, 'scss', 'sass-loader') }, { test: /\.scss$/, use: styleLoader.call(this.builder, 'scss', 'sass-loader') },
{ {
test: /\.styl(us)?$/, test: /\.styl(us)?$/,
use: styleLoader.call(this, 'stylus', 'stylus-loader') use: styleLoader.call(this.builder, 'stylus', 'stylus-loader')
}, },
{ {
test: /\.(png|jpe?g|gif|svg)$/, test: /\.(png|jpe?g|gif|svg)$/,
@ -125,46 +161,79 @@ export default function webpackBaseConfig({ name, isServer }) {
} }
} }
] ]
},
plugins: [
new VueLoader.VueLoaderPlugin()
].concat(this.options.build.plugins || [])
} }
plugins() {
const plugins = [ new VueLoader.VueLoaderPlugin() ]
Array.prototype.push.apply(plugins, this.options.build.plugins || [])
// Add timefix-plugin before others plugins // Add timefix-plugin before others plugins
if (this.options.dev) { if (this.options.dev) {
config.plugins.unshift(new TimeFixPlugin()) plugins.unshift(new TimeFixPlugin())
} }
// Hide warnings about plugins without a default export (#1179) // Hide warnings about plugins without a default export (#1179)
config.plugins.push(new WarnFixPlugin()) plugins.push(new WarnFixPlugin())
if (!this.options.test) { if (!this.options.test) {
// Build progress indicator // Build progress indicator
if (this.options.build.profile) { if (this.options.build.profile) {
config.plugins.push(new webpack.ProgressPlugin({ profile: true })) plugins.push(new webpack.ProgressPlugin({ profile: true }))
} else { } else {
if (!(this.options.minimalCLI)) { if (!(this.options.minimalCLI)) {
config.plugins.push(new ProgressPlugin({ plugins.push(new ProgressPlugin({
spinner: this.spinner, spinner: this.spinner,
name: isServer ? 'server' : 'client', name: this.isServer ? 'server' : 'client',
color: isServer ? 'green' : 'darkgreen' color: this.isServer ? 'green' : 'darkgreen'
})) }))
} }
} }
// Add stats plugin // Add stats plugin
config.plugins.push(new StatsPlugin(this.options.build.stats)) plugins.push(new StatsPlugin(this.options.build.stats))
// Add friendly error plugin // Add friendly error plugin
config.plugins.push( plugins.push(
new FriendlyErrorsWebpackPlugin({ new FriendlyErrorsWebpackPlugin({
clearConsole: true, clearConsole: true,
logLevel: 'WARNING' logLevel: 'WARNING'
}) })
) )
} }
return plugins
}
config() {
// Prioritize nested node_modules in webpack search path (#2558)
const webpackModulesDir = ['node_modules'].concat(this.options.modulesDir)
const config = {
name: this.name,
mode: this.options.dev ? 'development' : 'production',
optimization: {},
output: this.output(),
performance: {
maxEntrypointSize: 1000 * 1024,
hints: this.options.dev ? false : 'warning'
},
resolve: {
extensions: ['.js', '.json', '.vue', '.jsx'],
alias: this.alias(),
modules: webpackModulesDir
},
resolveLoader: {
alias: customLoaders,
modules: webpackModulesDir
},
module: {
noParse: /es6-promise\.js$/, // Avoid webpack shimming process
rules: this.rules()
},
plugins: this.plugins()
}
// Clone deep avoid leaking config between Client and Server // Clone deep avoid leaking config between Client and Server
return _.cloneDeep(config) return _.cloneDeep(config)
} }
}

View File

@ -1,6 +1,5 @@
import path from 'path' import path from 'path'
import _ from 'lodash'
import webpack from 'webpack' import webpack from 'webpack'
import HTMLPlugin from 'html-webpack-plugin' import HTMLPlugin from 'html-webpack-plugin'
@ -8,7 +7,7 @@ import BundleAnalyzer from 'webpack-bundle-analyzer'
import MiniCssExtractPlugin from 'mini-css-extract-plugin' import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import Debug from 'debug' import Debug from 'debug'
import base from './base.config' import BaseConfig from './base.config'
// import VueSSRClientPlugin from 'vue-server-renderer/client-plugin' // import VueSSRClientPlugin from 'vue-server-renderer/client-plugin'
import VueSSRClientPlugin from './plugins/vue/client' import VueSSRClientPlugin from './plugins/vue/client'
@ -21,34 +20,26 @@ debug.color = 2 // Force green color
| Webpack Client Config | Webpack Client Config
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
export default function webpackClientConfig() { export default class WebpackClientConfig extends BaseConfig {
let config = base.call(this, { name: 'client', isServer: false }) constructor(builder) {
super(builder, { name: 'client', isServer: false })
}
// Entry points env() {
config.entry = path.resolve(this.options.buildDir, 'client.js') return Object.assign(super.env(), {
'process.env.VUE_ENV': JSON.stringify('client'),
// Env object defined in nuxt.config.js 'process.browser': true,
let env = {} 'process.client': true,
_.each(this.options.env, (value, key) => { 'process.server': false
env['process.env.' + key] =
['boolean', 'number'].indexOf(typeof value) !== -1
? value
: JSON.stringify(value)
}) })
}
// Generate output HTML for SPA plugins() {
config.plugins.push( const plugins = super.plugins()
new HTMLPlugin({
filename: 'index.spa.html',
template: 'lodash!' + this.options.appTemplatePath,
inject: true,
chunksSortMode: 'dependency'
})
)
// Generate output HTML for SSR // Generate output HTML for SSR
if (this.options.build.ssr) { if (this.options.build.ssr) {
config.plugins.push( plugins.push(
new HTMLPlugin({ new HTMLPlugin({
filename: 'index.ssr.html', filename: 'index.ssr.html',
template: 'lodash!' + this.options.appTemplatePath, template: 'lodash!' + this.options.appTemplatePath,
@ -57,26 +48,66 @@ export default function webpackClientConfig() {
) )
} }
// Generate vue-ssr-client-manifest plugins.push(
config.plugins.push( new HTMLPlugin({
filename: 'index.spa.html',
template: 'lodash!' + this.options.appTemplatePath,
inject: true,
chunksSortMode: 'dependency'
}),
new VueSSRClientPlugin({ new VueSSRClientPlugin({
filename: 'vue-ssr-client-manifest.json' filename: 'vue-ssr-client-manifest.json'
}) }),
new webpack.DefinePlugin(this.env())
) )
// Define Env if (this.options.dev) {
config.plugins.push( // --------------------------------------
new webpack.DefinePlugin( // Dev specific config
Object.assign(env, { // --------------------------------------
'process.env.VUE_ENV': JSON.stringify('client'),
'process.mode': JSON.stringify(this.options.mode), // HMR
'process.browser': true, plugins.push(new webpack.HotModuleReplacementPlugin())
'process.client': true, } else {
'process.server': false, // --------------------------------------
'process.static': this.isStatic // Production specific config
// --------------------------------------
// Chunks size limit
// https://webpack.js.org/plugins/aggressive-splitting-plugin/
if (this.options.build.maxChunkSize) {
plugins.push(
new webpack.optimize.AggressiveSplittingPlugin({
minSize: this.options.build.maxChunkSize,
maxSize: this.options.build.maxChunkSize
}) })
) )
}
// Webpack Bundle Analyzer
if (this.options.build.analyze) {
plugins.push(
new BundleAnalyzer.BundleAnalyzerPlugin(Object.assign({}, this.options.build.analyze))
) )
}
}
// CSS extraction
const extractCSS = this.options.build.extractCSS
if (extractCSS) {
plugins.push(new MiniCssExtractPlugin(Object.assign({
filename: this.getFileName('css')
}, typeof extractCSS === 'object' ? extractCSS : {})))
}
return plugins
}
config() {
let config = super.config()
// Entry points
config.entry = path.resolve(this.options.buildDir, 'client.js')
// -- Optimization -- // -- Optimization --
config.optimization = this.options.build.optimization config.optimization = this.options.build.optimization
@ -107,38 +138,12 @@ export default function webpackClientConfig() {
}/__webpack_hmr`.replace(/\/\//g, '/'), }/__webpack_hmr`.replace(/\/\//g, '/'),
config.entry config.entry
] ]
// HMR
config.plugins.push(new webpack.HotModuleReplacementPlugin())
}
// --------------------------------------
// Production specific config
// --------------------------------------
if (!this.options.dev) {
// Chunks size limit
// https://webpack.js.org/plugins/aggressive-splitting-plugin/
if (this.options.build.maxChunkSize) {
config.plugins.push(
new webpack.optimize.AggressiveSplittingPlugin({
minSize: this.options.build.maxChunkSize,
maxSize: this.options.build.maxChunkSize
})
)
}
// Webpack Bundle Analyzer
if (this.options.build.analyze) {
config.plugins.push(
new BundleAnalyzer.BundleAnalyzerPlugin(Object.assign({}, this.options.build.analyze))
)
}
} }
// Extend config // Extend config
if (typeof this.options.build.extend === 'function') { if (typeof this.options.build.extend === 'function') {
const isDev = this.options.dev const isDev = this.options.dev
const extendedConfig = this.options.build.extend.call(this, config, { const extendedConfig = this.options.build.extend.call(this.builder, config, {
isDev, isDev,
isClient: true isClient: true
}) })
@ -149,13 +154,6 @@ export default function webpackClientConfig() {
} }
} }
// CSS extraction
const extractCSS = this.options.build.extractCSS
if (extractCSS) {
config.plugins.push(new MiniCssExtractPlugin(Object.assign({
filename: this.getFileName('css')
}, typeof extractCSS === 'object' ? extractCSS : {})))
}
return config return config
} }
}

View File

@ -3,9 +3,8 @@ import fs from 'fs'
import webpack from 'webpack' import webpack from 'webpack'
import nodeExternals from 'webpack-node-externals' import nodeExternals from 'webpack-node-externals'
import _ from 'lodash'
import base from './base.config' import BaseConfig from './base.config'
// import VueSSRServerPlugin from 'vue-server-renderer/server-plugin' // import VueSSRServerPlugin from 'vue-server-renderer/server-plugin'
import VueSSRServerPlugin from './plugins/vue/server' import VueSSRServerPlugin from './plugins/vue/server'
@ -15,22 +14,38 @@ import VueSSRServerPlugin from './plugins/vue/server'
| Webpack Server Config | Webpack Server Config
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
export default function webpackServerConfig() { export default class WebpackServerConfig extends BaseConfig {
let config = base.call(this, { name: 'server', isServer: true }) constructor(builder) {
super(builder, { name: 'server', isServer: true })
}
// Env object defined in nuxt.config.js env() {
let env = {} return Object.assign(super.env(), {
_.each(this.options.env, (value, key) => { 'process.env.VUE_ENV': JSON.stringify('server'),
env['process.env.' + key] = 'process.browser': false,
['boolean', 'number'].indexOf(typeof value) !== -1 'process.client': false,
? value 'process.server': true
: JSON.stringify(value)
}) })
}
plugins() {
const plugins = super.plugins()
plugins.push(
new VueSSRServerPlugin({
filename: 'server-bundle.json'
}),
new webpack.DefinePlugin(this.env())
)
return plugins
}
config() {
let config = super.config()
// Config devtool // Config devtool
config.devtool = this.options.dev ? 'cheap-source-map' : false config.devtool = this.options.dev ? 'cheap-source-map' : false
config = Object.assign(config, { Object.assign(config, {
target: 'node', target: 'node',
node: false, node: false,
entry: path.resolve(this.options.buildDir, 'server.js'), entry: path.resolve(this.options.buildDir, 'server.js'),
@ -42,22 +57,7 @@ export default function webpackServerConfig() {
hints: false, hints: false,
maxAssetSize: Infinity maxAssetSize: Infinity
}, },
externals: [], externals: []
plugins: (config.plugins || []).concat([
new VueSSRServerPlugin({
filename: 'server-bundle.json'
}),
new webpack.DefinePlugin(
Object.assign(env, {
'process.env.VUE_ENV': JSON.stringify('server'),
'process.mode': JSON.stringify(this.options.mode),
'process.browser': false,
'process.client': false,
'process.server': true,
'process.static': this.isStatic
})
)
])
}) })
// https://webpack.js.org/configuration/externals/#externals // https://webpack.js.org/configuration/externals/#externals
@ -77,7 +77,7 @@ export default function webpackServerConfig() {
// Extend config // Extend config
if (typeof this.options.build.extend === 'function') { if (typeof this.options.build.extend === 'function') {
const isDev = this.options.dev const isDev = this.options.dev
const extendedConfig = this.options.build.extend.call(this, config, { const extendedConfig = this.options.build.extend.call(this.builder, config, {
isDev, isDev,
isServer: true isServer: true
}) })
@ -89,3 +89,4 @@ export default function webpackServerConfig() {
return config return config
} }
}