diff --git a/lib/builder/builder.mjs b/lib/builder/builder.mjs index 13eb6e8228..3eb9331df4 100644 --- a/lib/builder/builder.mjs +++ b/lib/builder/builder.mjs @@ -18,6 +18,7 @@ import upath from 'upath' import { r, wp, wChunk, createRoutes, parallel, relativeTo, waitFor, createSpinner } from '../common/utils' import Options from '../common/options' +import PerfLoader from './webpack/utils/perf-loader' import ClientWebpackConfig from './webpack/client' import ServerWebpackConfig from './webpack/server' @@ -39,6 +40,9 @@ export default class Builder { this.webpackHotMiddleware = null this.filesWatcher = null this.customFilesWatcher = null + this.perfLoader = null + + // Shared spinner this.spinner = createSpinner({ minimal: this.options.minimalCLI }) this.spinner.enabled = !this.options.test @@ -419,7 +423,8 @@ export default class Builder { } async webpackBuild() { - debug('Building files...') + this.perfLoader = new PerfLoader(this) + const compilersOptions = [] // Client @@ -449,7 +454,7 @@ export default class Builder { } }) - // Initialize compilers + // Configure compilers this.compilers = compilersOptions.map(compilersOption => { const compiler = webpack(compilersOption) @@ -461,6 +466,13 @@ export default class Builder { return compiler }) + // Warmup perfLoader before build + if (this.options.build.parallel) { + this.spinner.start('Warming up worker pools') + this.perfLoader.warmup() + this.spinner.succeed('Worker pools ready') + } + // Start Builds await parallel(this.compilers, compiler => { return this.webpackCompile(compiler) diff --git a/lib/builder/webpack/base.mjs b/lib/builder/webpack/base.mjs index d8221094ea..53be886567 100644 --- a/lib/builder/webpack/base.mjs +++ b/lib/builder/webpack/base.mjs @@ -99,34 +99,6 @@ export default class WebpackBaseConfig { } } - perfLoaders(_baseLoaders) { - if (this.options.dev) { - return _baseLoaders - } - - const loaders = [] - - if (this.options.build.cache) { - // https://github.com/webpack-contrib/cache-loader - loaders.push({ - loader: 'cache-loader', - options: { - cacheDirectory: path.resolve('node_modules/.cache/cache-loader') - } - }) - } - - if (this.options.build.parallel) { - // https://github.com/webpack-contrib/thread-loader - loaders.push({ - loader: 'thread-loader', - options: {} - }) - } - - return loaders.concat(_baseLoaders) - } - rules() { const styleLoader = new StyleLoader( this.options, @@ -134,6 +106,8 @@ export default class WebpackBaseConfig { { isServer: this.isServer } ) + const perfLoader = this.builder.perfLoader + return [ { test: /\.vue$/, @@ -143,56 +117,62 @@ export default class WebpackBaseConfig { { test: /\.jsx?$/, exclude: /node_modules/, - use: this.perfLoaders({ + use: perfLoader.pool('js', { loader: 'babel-loader', options: this.getBabelOptions() }) }, { test: /\.css$/, - use: styleLoader.apply('css') + use: perfLoader.pool('css', styleLoader.apply('css')) }, { test: /\.less$/, - use: styleLoader.apply('less', 'less-loader') + use: perfLoader.pool('css', styleLoader.apply('less', 'less-loader')) }, { test: /\.sass$/, - use: styleLoader.apply('sass', { + use: perfLoader.pool('css', styleLoader.apply('sass', { loader: 'sass-loader', options: { indentedSyntax: true } - }) + })) }, { test: /\.scss$/, - use: styleLoader.apply('scss', 'sass-loader') + use: perfLoader.pool('css', styleLoader.apply('scss', 'sass-loader')) }, { test: /\.styl(us)?$/, - use: styleLoader.apply('stylus', 'stylus-loader') + use: perfLoader.pool('css', styleLoader.apply('stylus', 'stylus-loader')) }, { test: /\.(png|jpe?g|gif|svg)$/, - loader: 'url-loader', - options: { - limit: 1000, // 1KO - name: 'img/[name].[hash:7].[ext]' - } + use: perfLoader.pool('assets', { + loader: 'url-loader', + options: { + limit: 1000, // 1KO + name: 'img/[name].[hash:7].[ext]' + } + }) }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, - loader: 'url-loader', - options: { - limit: 1000, // 1 KO - name: 'fonts/[name].[hash:7].[ext]' - } + use: perfLoader.pool('assets', { + loader: 'url-loader', + options: { + limit: 1000, // 1 KO + name: 'fonts/[name].[hash:7].[ext]' + } + }) }, { test: /\.(webm|mp4)$/, - loader: 'file-loader', - options: { - name: 'videos/[name].[hash:7].[ext]' - } + use: perfLoader.pool('assets', { + loader: 'file-loader', + options: { + name: 'videos/[name].[hash:7].[ext]' + } + }) } ] } diff --git a/lib/builder/webpack/client.mjs b/lib/builder/webpack/client.mjs index 4d415102e4..68d9d98187 100644 --- a/lib/builder/webpack/client.mjs +++ b/lib/builder/webpack/client.mjs @@ -112,7 +112,7 @@ export default class WebpackClientConfig extends WebpackBaseConfig { config.optimization.minimizer = [ new UglifyJsWebpackPlugin({ parallel: true, - cache: true, + cache: this.options.build.cache, sourceMap: false }) ] diff --git a/lib/builder/webpack/plugins/progress.mjs b/lib/builder/webpack/plugins/progress.mjs index a8e540f2f3..85304cc0d5 100644 --- a/lib/builder/webpack/plugins/progress.mjs +++ b/lib/builder/webpack/plugins/progress.mjs @@ -44,7 +44,8 @@ export default class ProgressPlugin extends webpack.ProgressPlugin { const progress = Math.floor(percent * 100) this.state.progress = progress - this.state.msg = (msg && msg.length) ? msg : 'wait' + this.state.msg = (msg && msg.length) ? msg : (progress === 100 ? 'done' : 'still building') + this.state.details = details // Process all states let inProgress = false @@ -72,7 +73,9 @@ export default class ProgressPlugin extends webpack.ProgressPlugin { if (!inProgress) { logUpdate.clear() } else { - logUpdate('\n' + lines.join('\n') + '\n') + const title = chalk.bgRedBright.black('BUILDING') + + logUpdate('\n' + title + '\n\n' + lines.join('\n')) } } diff --git a/lib/builder/webpack/utils/perf-loader.mjs b/lib/builder/webpack/utils/perf-loader.mjs new file mode 100644 index 0000000000..ed7ba6c3df --- /dev/null +++ b/lib/builder/webpack/utils/perf-loader.mjs @@ -0,0 +1,60 @@ +import path from 'path' + +import threadLoader from 'thread-loader' + +// https://github.com/webpack-contrib/thread-loader +// https://github.com/webpack-contrib/cache-loader + +export default class PerfLoader { + constructor(builder) { + this.builder = builder + this.options = builder.options + + this.workerPools = { + js: { + name: 'js', + poolTimeout: this.options.dev ? Infinity : 2000 + + }, + css: { + name: 'css', + poolTimeout: this.options.dev ? Infinity : 2000 + } + } + } + + warmup() { + if (!this.options.build.parallel) { + return + } + + threadLoader.warmup(this.workerPools.js, ['babel-loader', 'babel-preset-env']) + threadLoader.warmup(this.workerPools.css, ['css-loader']) + } + + pool(poolName, _loaders) { + const loaders = [].concat(_loaders) + + if (this.options.build.parallel) { + const pool = this.workerPools[poolName] + + if (pool) { + loaders.unshift({ + loader: 'thread-loader', + options: pool + }) + } + } + + if (this.options.build.cache) { + loaders.unshift({ + loader: 'cache-loader', + options: { + cacheDirectory: path.resolve('node_modules/.cache/cache-loader') + } + }) + } + + return loaders + } +} diff --git a/lib/common/nuxt.config.js b/lib/common/nuxt.config.js index 038513b1dc..4b96810264 100644 --- a/lib/common/nuxt.config.js +++ b/lib/common/nuxt.config.js @@ -37,8 +37,8 @@ export default { extractCSS: false, cssSourceMap: undefined, ssr: undefined, - parallel: os.cpus().length > 1, - cache: true, + parallel: false, + cache: false, publicPath: '/_nuxt/', filenames: { app: '[name].[chunkhash].js', diff --git a/lib/common/options.mjs b/lib/common/options.mjs index 794b6d647f..d1730d1102 100644 --- a/lib/common/options.mjs +++ b/lib/common/options.mjs @@ -143,7 +143,7 @@ Options.from = function (_options) { // babel cacheDirectory if (options.build.babel.cacheDirectory === undefined) { - options.build.babel.cacheDirectory = options.dev + options.build.babel.cacheDirectory = options.dev && options.build.cache } // Resource hints