feat: experimental dll support

`.cache/` should be git ignored in projects.
disabled by default. Can be enabled using `options.build.dll: true`
This commit is contained in:
Pooya Parsa 2017-08-21 15:46:35 +04:30
parent d1a6b2bc80
commit d7fbe47c31
7 changed files with 134 additions and 32 deletions

View File

@ -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()) {

View File

@ -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$/,

View File

@ -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(','))
}
}
// --------------------------------------

View File

@ -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
}

View File

@ -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 = {}

View File

@ -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,

View File

@ -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: {}
}
}