diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 98636b3fb3..715f10afda 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -10,11 +10,12 @@ import Tapable from 'tappable' import MFS from 'memory-fs' import webpackDevMiddleware from 'webpack-dev-middleware' import webpackHotMiddleware from 'webpack-hot-middleware' -import { r, wp, wChunk, createRoutes, parallel, relativeTo, isPureObject } from 'utils' +import { r, wp, wChunk, createRoutes, sequence, relativeTo, isPureObject } from 'utils' import Debug from 'debug' import Glob from 'glob' import clientWebpackConfig from './webpack/client.config.js' import serverWebpackConfig from './webpack/server.config.js' +import dllWebpackConfig from './webpack/dll.config.js' import vueLoaderConfig from './webpack/vue-loader.config' import styleLoader from './webpack/style-loader' @@ -66,6 +67,29 @@ export default class Builder extends Tapable { }) } + vendor () { + return [ + 'vue', + 'vue-router', + 'vue-meta', + 'core-js', + 'regenerator-runtime', + 'es6-promise', + 'babel-runtime', + 'vuex' + ].concat(this.options.build.vendor).filter(v => v) + } + + vendorEntries () { + // Used for dll + const vendor = this.vendor() + const vendorEntries = {} + vendor.forEach(v => { + vendorEntries[v] = [ v ] + }) + return vendorEntries + } + forGenerate () { this.isStatic = true } @@ -361,6 +385,11 @@ export default class Builder extends Tapable { } }) + // Make a dll plugin after compile to make next dev builds faster + if (this.options.build.dll && this.options.dev) { + compilersOptions.push(dllWebpackConfig.call(this, clientConfig)) + } + // Simulate webpack multi compiler interface // Separate compilers are simpler, safer and faster this.compiler = { compilers: [] } @@ -377,7 +406,7 @@ export default class Builder extends Tapable { // Initialize compilers compilersOptions.forEach(compilersOption => { const compiler = webpack(compilersOption) - if (sharedFS) { + if (sharedFS && !compiler.name.includes('-dll')) { compiler.outputFileSystem = sharedFS } compiler.cache = sharedCache @@ -398,6 +427,9 @@ export default class Builder extends Tapable { if (stats.hasErrors()) { return } + + // console.log(stats.toString({ chunks: true })) + // Reload renderer if available if (this.nuxt.renderer) { this.nuxt.renderer.loadResources(sharedFS || fs) @@ -414,12 +446,21 @@ export default class Builder extends Tapable { await this.applyPluginsAsync('compile', { builder: this, compiler: this.compiler }) // Start Builds - await parallel(this.compiler.compilers, compiler => new Promise((resolve, reject) => { + await sequence(this.compiler.compilers, compiler => new Promise((resolve, reject) => { if (this.options.dev) { // --- Dev Build --- if (compiler.options.name === 'client') { // Client watch is started by dev-middleware resolve() + } else if (compiler.options.name.includes('-dll')) { + // DLL builds should run once + compiler.run((err, stats) => { + if (err) { + return reject(err) + } + debug('[DLL] updated') + resolve() + }) } else { // Build and watch for changes compiler.watch(this.options.watchers.webpack, (err) => { @@ -440,7 +481,7 @@ export default class Builder extends Tapable { if (err) return console.error(err) // eslint-disable-line no-console // Show build stats for production - console.log(stats.toString(this.webpackStats))// eslint-disable-line no-console + console.log(stats.toString(this.webpackStats)) // eslint-disable-line no-console /* istanbul ignore if */ if (stats.hasErrors()) { diff --git a/lib/builder/webpack/base.config.js b/lib/builder/webpack/base.config.js index 6bbb62c70c..25fc2f1b75 100644 --- a/lib/builder/webpack/base.config.js +++ b/lib/builder/webpack/base.config.js @@ -12,10 +12,11 @@ import { isUrl, urlJoin } from 'utils' | webpack config files |-------------------------------------------------------------------------- */ -export default function webpackBaseConfig ({ isClient, isServer }) { +export default function webpackBaseConfig (name) { const nodeModulesDir = join(__dirname, '..', 'node_modules') const config = { + name, devtool: this.options.dev ? 'cheap-module-source-map' : 'nosources-source-map', entry: { app: null @@ -63,7 +64,7 @@ export default function webpackBaseConfig ({ isClient, isServer }) { { test: /\.vue$/, loader: 'vue-loader', - options: this.vueLoader({ isClient, isServer }) + options: this.vueLoader() }, { test: /\.js$/, diff --git a/lib/builder/webpack/client.config.js b/lib/builder/webpack/client.config.js index 14c8d5559b..c0da2c38ec 100644 --- a/lib/builder/webpack/client.config.js +++ b/lib/builder/webpack/client.config.js @@ -7,8 +7,13 @@ import ProgressBarPlugin from 'progress-bar-webpack-plugin' import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' import MinifyPlugin from 'babel-minify-webpack-plugin' import { resolve } from 'path' +import { existsSync } from 'fs' +import Debug from 'debug' import base from './base.config.js' +const debug = Debug('nuxt:build') +debug.color = 2 // Force green color + /* |-------------------------------------------------------------------------- | Webpack Client Config @@ -20,29 +25,16 @@ import base from './base.config.js' |-------------------------------------------------------------------------- */ export default function webpackClientConfig () { - let config = base.call(this, { isClient: true }) - - config.name = 'client' + let config = base.call(this, 'client') // App entry config.entry.app = resolve(this.options.buildDir, 'client.js') - // Add vendors - // This vendors should explicitly extracted - // Even if not used in 50% of the chunks! - const vendor = [ - 'vue', - 'vue-router', - 'vue-meta', - 'core-js', - 'regenerator-runtime', - 'es6-promise', - 'babel-runtime', - 'vuex' - ].concat(this.options.build.vendor).filter(v => v) - // Extract vendor chunks for better caching const _this = this + const totalPages = _this.routes ? _this.routes.length : 0 + const vendor = this.vendor() + config.plugins.push( new webpack.optimize.CommonsChunkPlugin({ name: 'common', @@ -56,13 +48,11 @@ export default function webpackClientConfig () { } // Extract all explicit vendor modules + // Vendor should explicitly extracted even if not used in 50% of the chunks! if (module.context && vendor.some(v => module.context.includes(v))) { return true } - // Total pages - const totalPages = _this.routes ? _this.routes.length : 0 - // A module is extracted into the vendor chunk when... return ( // If it's inside node_modules @@ -157,6 +147,27 @@ export default function webpackClientConfig () { new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ) + + // DllReferencePlugin + // https://github.com/webpack/webpack/tree/master/examples/dll-user + if (this.options.build.dll) { + const _dlls = [] + const vendorEntries = this.vendorEntries() + const dllDir = resolve(this.options.cacheDir, config.name + '-dll') + Object.keys(vendorEntries).forEach(v => { + const dllManifestFile = resolve(dllDir, v + '-manifest.json') + if (existsSync(dllManifestFile)) { + _dlls.push(v) + config.plugins.push( + new webpack.DllReferencePlugin({ + // context: this.options.rootDir, + manifest: dllManifestFile // Using full path to allow finding .js dll file + }) + ) + } + }) + debug('[DLL] ' + _dlls.join(',')) + } } // -------------------------------------- diff --git a/lib/builder/webpack/dll.config.js b/lib/builder/webpack/dll.config.js new file mode 100644 index 0000000000..8b030f6766 --- /dev/null +++ b/lib/builder/webpack/dll.config.js @@ -0,0 +1,46 @@ +import webpack from 'webpack' +import { resolve } from 'path' +import ClientConfig from './client.config' + +/* +|-------------------------------------------------------------------------- +| Webpack Dll Config +| https://github.com/webpack/webpack/tree/master/examples/dll +|-------------------------------------------------------------------------- +*/ +export default function webpackDllConfig (_refConfig) { + const refConfig = _refConfig || new ClientConfig() + + const name = refConfig.name + '-dll' + const dllDir = resolve(this.options.cacheDir, name) + + let config = { + name, + entry: this.vendorEntries(), + // context: this.options.rootDir, + resolve: refConfig.resolve, + target: refConfig.target, + resolveLoader: refConfig.resolveLoader, + module: refConfig.module, + plugins: [] + } + + config.output = { + path: dllDir, + filename: '[name]_[hash].js', + library: '[name]_[hash]' + } + + config.plugins.push( + new webpack.DllPlugin({ + // The path to the manifest file which maps between + // modules included in a bundle and the internal IDs + // within that bundle + path: resolve(dllDir, '[name]-manifest.json'), + + name: '[name]_[hash]' + }) + ) + + return config +} diff --git a/lib/builder/webpack/server.config.js b/lib/builder/webpack/server.config.js index 5fda27a7db..e9169d4084 100644 --- a/lib/builder/webpack/server.config.js +++ b/lib/builder/webpack/server.config.js @@ -12,9 +12,7 @@ import base from './base.config.js' |-------------------------------------------------------------------------- */ export default function webpackServerConfig () { - let config = base.call(this, { isServer: true }) - - config.name = 'server' + let config = base.call(this, 'server') // env object defined in nuxt.config.js let env = {} diff --git a/lib/builder/webpack/vue-loader.config.js b/lib/builder/webpack/vue-loader.config.js index f52ff46cec..e440ef97c9 100644 --- a/lib/builder/webpack/vue-loader.config.js +++ b/lib/builder/webpack/vue-loader.config.js @@ -1,4 +1,4 @@ -export default function vueLoader ({ isClient }) { +export default function vueLoader () { // https://vue-loader.vuejs.org/en const config = { postcss: this.options.build.postcss, diff --git a/lib/common/options.js b/lib/common/options.js index fa0f1bd063..0fc0beba43 100755 --- a/lib/common/options.js +++ b/lib/common/options.js @@ -33,7 +33,8 @@ Options.from = function (_options) { options.rootDir = hasValue(options.rootDir) ? options.rootDir : process.cwd() options.srcDir = hasValue(options.srcDir) ? resolve(options.rootDir, options.srcDir) : options.rootDir options.modulesDir = resolve(options.rootDir, hasValue(options.modulesDir) ? options.modulesDir : 'node_modules') - options.buildDir = join(options.rootDir, options.buildDir) + options.buildDir = resolve(options.rootDir, options.buildDir) + options.cacheDir = resolve(options.rootDir, options.cacheDir) // If app.html is defined, set the template path to the user template options.appTemplatePath = resolve(options.buildDir, 'views/app.template.html') @@ -162,9 +163,11 @@ Options.defaults = { dev: process.env.NODE_ENV !== 'production', debug: undefined, // Will be equal to dev if not provided buildDir: '.nuxt', + cacheDir: '.cache', nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist build: { analyze: false, + dll: false, extractCSS: false, cssSourceMap: undefined, ssr: undefined, @@ -266,7 +269,9 @@ Options.defaults = { } }, watchers: { - webpack: {}, + webpack: { + ignored: /-dll/ + }, chokidar: {} } }