Merge pull request #2901 from nuxt/feat/webpack4

webpack 4 upgrade
This commit is contained in:
Sébastien Chopin 2018-03-14 15:20:57 +01:00 committed by GitHub
commit 3b7f23afb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1019 additions and 1428 deletions

View File

@ -1,8 +1,5 @@
#!/usr/bin/env node
// Show logs
process.env.DEBUG = process.env.DEBUG || 'nuxt:*'
const { join } = require('path')
const semver = require('semver')

View File

@ -69,7 +69,6 @@ if (options.mode !== 'spa') {
// -- Build for SSR app --
builder
.build()
.then(() => debug('Building done'))
.then(() => close())
.catch(Utils.fatalError)
} else {

View File

@ -1,7 +1,4 @@
module.exports = {
build: {
vendor: ['axios'] // Add axios in the vendor.bundle.js
},
loading: {
color: '#4FC08D',
failedColor: '#bf5050',

View File

@ -10,9 +10,6 @@ module.exports = {
{ hid: 'description', content: 'Auth Routes example' }
]
},
build: {
vendor: ['axios']
},
/*
** Add server middleware
** Nuxt.js uses `connect` module as server

View File

@ -3,10 +3,8 @@ module.exports = {
filenames: {
css: 'styles.[chunkhash].css', // default: common.[chunkhash].css
manifest: 'manifest.[hash].js', // default: manifest.[hash].js
vendor: 'vendor.[hash].js', // default: vendor.bundle.[hash].js
app: 'app.[chunkhash].js' // default: nuxt.bundle.[chunkhash].js
},
vendor: ['lodash'],
extend(config, { isDev }) {
if (isDev) {
config.devtool = 'eval-source-map'

View File

@ -3,7 +3,6 @@
<p><img src="~/assets/nuxt.png" /></p>
<p>This image is included as data:image/png;base64...</p>
<p>In the source code, the files generated are based on the build.filenames data.</p>
<p>If you look at the <a href="/_nuxt/vendor.js">vendor.js</a>, lodash has been included (cmd/ctrl + F "lodash").</p>
</div>
</template>

View File

@ -1,5 +1,2 @@
module.exports = {
build: {
vendor: ['axios']
}
}

View File

@ -1,8 +1,5 @@
module.exports = {
loading: { color: 'cyan' },
build: {
vendor: ['vue-i18n']
},
router: {
middleware: 'i18n'
},

View File

@ -1,7 +1,4 @@
module.exports = {
build: {
vendor: ['axios']
},
css: ['~/assets/main.css'],
layoutTransition: {
name: 'layout',

View File

@ -1,7 +1,4 @@
module.exports = {
build: {
vendor: ['axios', 'mini-toastr', 'vue-notifications']
},
plugins: [
// ssr: false to only include it on client-side
{ src: '~/plugins/vue-notifications.js', ssr: false }

View File

@ -1,6 +1,3 @@
module.exports = {
build: {
vendor: ['axios']
},
css: ['~/assets/main.css']
}

View File

@ -1,6 +1,3 @@
module.exports = {
build: {
vendor: ['axios']
},
css: ['~/assets/main.css']
}

View File

@ -19,8 +19,5 @@ module.exports = {
** Build configuration
*/
css: ['tachyons/css/tachyons.min.css', '~/assets/css/main.css'],
build: {
vendor: ['axios', 'vuex-class', 'nuxt-class-component']
},
modules: ['~/modules/typescript']
}

View File

@ -7,7 +7,6 @@ module.exports = {
]
},
build: {
vendor: ['axios', 'moment', 'chart.js', 'vue-chartjs'],
maxChunkSize: 300000
},
env: {

View File

@ -7,8 +7,5 @@ module.exports = {
link: [
{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css' }
]
},
build: {
vendor: ['axios']
}
}

View File

@ -7,9 +7,6 @@ module.exports = function () {
// close this server on 'close' event
this.nuxt.plugin('close', () => new Promise((resolve) => server.close(resolve)))
// Add `socket.io-client` in vendor
this.addVendor('socket.io-client')
// Add socket.io events
let messages = []
io.on('connection', (socket) => {

View File

@ -10,11 +10,8 @@ module.exports = {
{ rel: 'stylesheet', type: 'text/css', href: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' }
]
},
/*
** Add Vuetify into vendor.bundle.js
*/
build: {
vendor: ['vuetify'],
extractCSS: true,
extend(config, ctx) {
if (ctx.isServer) {

View File

@ -4,15 +4,17 @@ import Vue from 'vue'
import '<%= relativeToBuild(resolvePath(c.src || c)) %>'
<% }) %>
let layouts = {
<%
var layoutsKeys = Object.keys(layouts);
layoutsKeys.forEach(function (key, i) { %>
"_<%= key %>": () => import('<%= layouts[key] %>' /* webpackChunkName: "<%= wChunk('layouts/'+key) %>" */).then(m => m.default || m)<%= (i + 1) < layoutsKeys.length ? ',' : '' %>
<% }) %>
}
<%= Object.keys(layouts).map(key => {
if (splitPages) {
return `const _${hash(key)} = () => import('${layouts[key]}' /* webpackChunkName: "${wChunk('layouts/' + key)}" */).then(m => m.default || m)`
} else {
return `import _${hash(key)} from '${layouts[key]}'`
}
}).join('\n') %>
let resolvedLayouts = {}
const layouts = { <%= Object.keys(layouts).map(key => `"_${key}": _${hash(key)}`).join(',') %> }
<% if (splitPages) { %>let resolvedLayouts = {}<% } %>
export default {
head: <%= serialize(head).replace('head(', 'function(').replace('titleTemplate(', 'function(') %>,
@ -76,6 +78,7 @@ export default {
}
},
<% } %>
<% if (splitPages) { %>
setLayout (layout) {
if (!layout || !resolvedLayouts['_' + layout]) layout = 'default'
this.layoutName = layout
@ -90,17 +93,28 @@ export default {
return Promise.resolve(resolvedLayouts[_layout])
}
return layouts[_layout]()
.then((Component) => {
resolvedLayouts[_layout] = Component
delete layouts[_layout]
return resolvedLayouts[_layout]
})
.catch((e) => {
if (this.$nuxt) {
return this.$nuxt.error({ statusCode: 500, message: e.message })
}
})
.then((Component) => {
resolvedLayouts[_layout] = Component
delete layouts[_layout]
return resolvedLayouts[_layout]
})
.catch((e) => {
if (this.$nuxt) {
return this.$nuxt.error({ statusCode: 500, message: e.message })
}
})
}
<% } else { %>
setLayout(layout) {
if (!layout || !layouts['_' + layout]) layout = 'default'
this.layoutName = layout
this.layout = layouts['_' + layout]
return this.layout
},
loadLayout(layout) {
return Promise.resolve(layouts['_' + layout])
}
<% } %>
},
components: {
<%= (loading ? 'NuxtLoading' : '') %>

View File

@ -1,17 +1,14 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
<%
function recursiveRoutes(routes, tab, components) {
<% function recursiveRoutes(routes, tab, components) {
let res = ''
routes.forEach((route, i) => {
route._name = '_' + hash(route.component)
components.push({ _name: route._name, component: route.component, name: route.name, chunkName: route.chunkName })
res += tab + '{\n'
res += tab + '\tpath: ' + JSON.stringify(route.path) + ',\n'
res += tab + '\tcomponent: ' + route._name
res += tab + '\tcomponent: ' + (splitPages ? route._name : `() => ${route._name}.default || ${route._name}`)
res += (route.name) ? ',\n\t' + tab + 'name: ' + JSON.stringify(route.name) : ''
res += (route.children) ? ',\n\t' + tab + 'children: [\n' + recursiveRoutes(routes[i].children, tab + '\t\t', components) + '\n\t' + tab + ']' : ''
res += '\n' + tab + '}' + (i + 1 === routes.length ? '' : ',\n')
@ -20,8 +17,19 @@ function recursiveRoutes(routes, tab, components) {
}
const _components = []
const _routes = recursiveRoutes(router.routes, '\t\t', _components)
uniqBy(_components, '_name').forEach((route) => { %>const <%= route._name %> = () => import('<%= relativeToBuild(route.component) %>' /* webpackChunkName: "<%= wChunk(route.chunkName) %>" */).then(m => m.default || m)
<% }) %>
%><%= uniqBy(_components, '_name').map((route) => {
const path = relativeToBuild(route.component)
const chunkName = wChunk(route.chunkName)
const name = route._name
if (splitPages) {
return `const ${name} = () => import('${path}' /* webpackChunkName: "${chunkName}" */).then(m => m.default || m)`
} else {
return `import ${name} from '${path}'`
}
}).join('\n')%>
Vue.use(Router)
<% if (router.scrollBehavior) { %>
const scrollBehavior = <%= serialize(router.scrollBehavior).replace('scrollBehavior(', 'function(').replace('function function', 'function') %>

View File

@ -1,5 +1,4 @@
import Vue from 'vue'
import clone from 'clone'
import { stringify } from 'querystring'
import { omit } from 'lodash'
import middleware from './middleware'

View File

@ -2,6 +2,7 @@ const { promisify } = require('util')
const _ = require('lodash')
const chokidar = require('chokidar')
const { remove, readFile, writeFile, mkdirp, existsSync } = require('fs-extra')
const fs = require('fs')
const hash = require('hash-sum')
const webpack = require('webpack')
const serialize = require('serialize-javascript')
@ -11,20 +12,12 @@ const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const Debug = require('debug')
const Glob = require('glob')
const {
r,
wp,
wChunk,
createRoutes,
sequence,
relativeTo,
waitFor
} = require('../common/utils')
const { r, wp, wChunk, createRoutes, parallel, relativeTo, waitFor } = require('../common/utils')
const { Options } = require('../common')
const clientWebpackConfig = require('./webpack/client.config.js')
const serverWebpackConfig = require('./webpack/server.config.js')
const dllWebpackConfig = require('./webpack/dll.config.js')
const upath = require('upath')
const ORA = require('ora')
const debug = Debug('nuxt:build')
debug.color = 2 // Force green color
@ -44,6 +37,8 @@ module.exports = class Builder {
this.webpackHotMiddleware = null
this.filesWatcher = null
this.customFilesWatcher = null
this.spinner = new ORA()
this.spinner.color = 'green'
// Mute stats on dev
this.webpackStats = this.options.dev ? false : this.options.build.stats
@ -82,34 +77,6 @@ module.exports = class Builder {
)
}
vendor() {
return ['vue', 'vue-router', 'vue-meta', this.options.store && 'vuex']
.concat(
this.options.build.extractCSS && [
// https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/456
'vue-style-loader/lib/addStylesClient',
'css-loader/lib/css-base'
]
)
.concat(this.options.build.vendor)
.filter(v => v)
}
vendorEntries() {
// Used for dll
const vendor = this.vendor()
const vendorEntries = {}
vendor.forEach(v => {
try {
require.resolve(v)
vendorEntries[v] = [v]
} catch (e) {
// Ignore
}
})
return vendorEntries
}
forGenerate() {
this.isStatic = true
}
@ -128,6 +95,8 @@ module.exports = class Builder {
}
this._buildStatus = STATUS.BUILDING
this.spinner.start('Initializing builder')
// Wait for nuxt ready
await this.nuxt.ready()
@ -151,8 +120,9 @@ module.exports = class Builder {
}
}
this.spinner.start(`Generating nuxt files...`)
debug(`App root: ${this.options.srcDir}`)
debug(`Generating ${this.options.buildDir} files...`)
// Create .nuxt/, .nuxt/components and .nuxt/dist folders
await remove(r(this.options.buildDir))
@ -164,6 +134,8 @@ module.exports = class Builder {
// Generate routes and interpret the template files
await this.generateRoutesAndFiles()
this.spinner.start('Compiling...')
// Start webpack build
await this.webpackBuild()
@ -236,6 +208,7 @@ module.exports = class Builder {
.map(ext => ext.replace(/^\./, ''))
.join('|'),
messages: this.options.messages,
splitPages: this.options.build.splitPages,
uniqBy: _.uniqBy,
isDev: this.options.dev,
debug: this.options.debug,
@ -503,28 +476,22 @@ module.exports = class Builder {
}
})
// Make a dll plugin after compile to make nuxt dev builds faster
if (this.options.build.dll && this.options.dev) {
compilersOptions.push(dllWebpackConfig.call(this, clientConfig))
}
// Initialize shared FS and Cache
const sharedFS = this.options.dev && new MFS()
const sharedCache = {}
// Initialize compilers
this.compilers = compilersOptions.map(compilersOption => {
const compiler = webpack(compilersOption)
// In dev, write files in memory FS (except for DLL)
if (sharedFS && !compiler.name.includes('-dll')) {
// In dev, write files in memory FS
if (sharedFS) {
compiler.outputFileSystem = sharedFS
}
compiler.cache = sharedCache
return compiler
})
// Start Builds
await sequence(
await parallel(
this.compilers,
compiler =>
new Promise(async (resolve, reject) => {
@ -532,14 +499,14 @@ module.exports = class Builder {
await this.nuxt.callHook('build:compile', { name, compiler })
// Resolve only when compiler emit done event
compiler.plugin('done', async stats => {
compiler.hooks.done.tap('load-resources', async stats => {
await this.nuxt.callHook('build:compiled', {
name,
compiler,
stats
})
// Reload renderer if available
this.nuxt.renderer.loadResources(sharedFS || require('fs'))
this.nuxt.renderer.loadResources(sharedFS || fs)
// Resolve on next tick
process.nextTick(resolve)
})
@ -549,14 +516,6 @@ module.exports = class Builder {
if (compiler.options.name === 'client') {
return this.webpackDev(compiler)
}
// DLL build, should run only once
if (compiler.options.name.includes('-dll')) {
compiler.run((err, stats) => {
if (err) return reject(err)
debug('[DLL] updated')
})
return
}
// Server, build and watch for changes
this.compilersWatching.push(
compiler.watch(this.options.watchers.webpack, err => {
@ -574,13 +533,7 @@ module.exports = class Builder {
return reject(err)
}
// Show build stats for production
console.log(stats.toString(this.webpackStats)) // eslint-disable-line no-console
/* istanbul ignore if */
if (stats.hasErrors()) {
return reject(new Error('Webpack build exited with errors'))
}
resolve()
})
})
)

View File

@ -10,6 +10,7 @@ const _ = require('lodash')
const { resolve, join, dirname, sep } = require('path')
const { minify } = require('html-minifier')
const Chalk = require('chalk')
const ORA = require('ora')
const { printWarn } = require('../common/utils')
const {
@ -34,14 +35,23 @@ module.exports = class Generator {
this.distPath,
isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath
)
this.spinner = new ORA()
}
async generate({ build = true, init = true } = {}) {
this.spinner.start('Initializing generator...')
await this.initiate({ build, init })
this.spinner.start('Preparing routes for generate...')
const routes = await this.initRoutes()
this.spinner.start('Generating pages...')
const errors = await this.generateRoutes(routes)
await this.afterGenerate()
// Done hook
@ -128,7 +138,7 @@ module.exports = class Generator {
const color = isHandled ? 'yellow' : 'red'
let line =
Chalk.black[bgColor](' ROUTE ') + Chalk[color](` ${route}\n\n`)
Chalk.black[bgColor](' GENERATE ERR ') + Chalk[color](` ${route}\n\n`)
if (isHandled) {
line += Chalk.grey(JSON.stringify(error, undefined, 2) + '\n')
@ -280,7 +290,10 @@ module.exports = class Generator {
})
if (pageErrors.length) {
this.spinner.fail('Error generating ' + route)
Array.prototype.push.apply(errors, pageErrors)
} else {
this.spinner.succeed('Generated ' + route)
}
return true

View File

@ -1,11 +1,16 @@
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const TimeFixPlugin = require('time-fix-plugin')
const WarnFixPlugin = require('./plugins/warnfix')
const ProgressPlugin = require('./plugins/progress')
const webpack = require('webpack')
const { cloneDeep } = require('lodash')
const { join, resolve } = require('path')
const { isUrl, urlJoin } = require('../../common/utils')
const vueLoader = require('./vue-loader')
const styleLoader = require('./style-loader')
const TimeFixPlugin = require('./plugins/timefix')
const WarnFixPlugin = require('./plugins/warnfix')
/*
|--------------------------------------------------------------------------
@ -23,14 +28,22 @@ module.exports = function webpackBaseConfig({ name, isServer }) {
// Used by vue-loader so we can use in templates
// with <img src="~/assets/nuxt.png"/>
configAlias[this.options.dir.assets] = join(this.options.srcDir, this.options.dir.assets)
configAlias[this.options.dir.static] = join(this.options.srcDir, this.options.dir.static)
configAlias[this.options.dir.assets] = join(
this.options.srcDir,
this.options.dir.assets
)
configAlias[this.options.dir.static] = join(
this.options.srcDir,
this.options.dir.static
)
const config = {
name,
mode: this.options.dev ? 'development' : 'production',
entry: {
app: null
},
optimization: {},
output: {
path: resolve(this.options.buildDir, 'dist'),
filename: this.getFileName('app'),
@ -46,12 +59,15 @@ module.exports = function webpackBaseConfig({ name, isServer }) {
},
resolve: {
extensions: ['.js', '.json', '.vue', '.jsx'],
alias: Object.assign({
'~': join(this.options.srcDir),
'~~': join(this.options.rootDir),
'@': join(this.options.srcDir),
'@@': join(this.options.rootDir)
}, configAlias),
alias: Object.assign(
{
'~': join(this.options.srcDir),
'~~': join(this.options.rootDir),
'@': join(this.options.srcDir),
'@@': join(this.options.rootDir)
},
configAlias
),
modules: webpackModulesDir
},
resolveLoader: {
@ -113,6 +129,17 @@ module.exports = function webpackBaseConfig({ name, isServer }) {
plugins: this.options.build.plugins
}
// Build progress indicator
if (this.options.build.profile) {
config.plugins.push(new webpack.ProgressPlugin({ profile: true }))
} else {
config.plugins.push(new ProgressPlugin({
spinner: this.spinner,
name: isServer ? 'server' : 'client',
color: isServer ? 'orange' : 'green'
}))
}
// Add timefix-plugin before others plugins
if (this.options.dev) {
config.plugins.unshift(new TimeFixPlugin())

View File

@ -3,43 +3,21 @@ const webpack = require('webpack')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const HTMLPlugin = require('html-webpack-plugin')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const ProgressPlugin = require('webpack/lib/ProgressPlugin')
const StylishPlugin = require('webpack-stylish')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const { resolve } = require('path')
const { existsSync } = require('fs')
const Debug = require('debug')
const Chalk = require('chalk')
const { printWarn } = require('../../common/utils')
const base = require('./base.config.js')
const debug = Debug('nuxt:build')
debug.color = 2 // Force green color
/*
|--------------------------------------------------------------------------
| Webpack Client Config
|
| Generate public/dist/client-vendor-bundle.js
| Generate public/dist/client-bundle.js
|
| In production, will generate public/dist/style.css
|--------------------------------------------------------------------------
*/
module.exports = function webpackClientConfig() {
let config = base.call(this, { name: 'client', isServer: false })
// Entry points
config.entry.app = resolve(this.options.buildDir, 'client.js')
config.entry.vendor = this.vendor()
// Config devtool
config.devtool = this.options.dev ? 'cheap-source-map' : false
config.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]'
// Add CommonChunks plugin
commonChunksPlugin.call(this, config)
// Env object defined in nuxt.config.js
let env = {}
@ -78,22 +56,10 @@ module.exports = function webpackClientConfig() {
})
)
// Extract webpack runtime & manifest
config.plugins.push(
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
filename: this.getFileName('manifest')
})
)
// Define Env
config.plugins.push(
new webpack.DefinePlugin(
Object.assign(env, {
'process.env.NODE_ENV': JSON.stringify(
this.options.env.NODE_ENV || (this.options.dev ? 'development' : 'production')
),
'process.env.VUE_ENV': JSON.stringify('client'),
'process.mode': JSON.stringify(this.options.mode),
'process.browser': true,
@ -104,24 +70,6 @@ module.exports = function webpackClientConfig() {
)
)
// Build progress bar
if (this.options.build.profile) {
config.plugins.push(
new ProgressPlugin({
profile: true
})
)
} else {
config.plugins.push(
new ProgressBarPlugin({
complete: Chalk.green('█'),
incomplete: Chalk.white('█'),
format: ' :bar ' + Chalk.green.bold(':percent') + ' :msg',
clear: false
})
)
}
const shouldClearConsole =
this.options.build.stats !== false &&
this.options.build.stats !== 'errors-only'
@ -131,13 +79,19 @@ module.exports = function webpackClientConfig() {
new FriendlyErrorsWebpackPlugin({ clearConsole: shouldClearConsole })
)
// Optimization
config.optimization = {
splitChunks: {
chunks: 'all',
// TODO: remove spa after https://github.com/jantimon/html-webpack-plugin/issues/878 solved
name: this.options.dev || this.options.mode === 'spa'
}
}
// --------------------------------------
// Dev specific config
// --------------------------------------
if (this.options.dev) {
// https://webpack.js.org/plugins/named-modules-plugin
config.plugins.push(new webpack.NamedModulesPlugin())
// Add HMR support
config.entry.app = [
// https://github.com/glenjamin/webpack-hot-middleware#config
@ -146,26 +100,15 @@ module.exports = function webpackClientConfig() {
}/__webpack_hmr`.replace(/\/\//g, '/'),
config.entry.app
]
config.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
)
// DllReferencePlugin
if (this.options.build.dll) {
dllPlugin.call(this, config)
}
// HMR
config.plugins.push(new webpack.HotModuleReplacementPlugin())
}
// --------------------------------------
// Production specific config
// --------------------------------------
if (!this.options.dev) {
// Scope Hoisting
if (this.options.build.scopeHoisting === true) {
config.plugins.push(new webpack.optimize.ModuleConcatenationPlugin())
}
// Chunks size limit
// https://webpack.js.org/plugins/aggressive-splitting-plugin/
if (this.options.build.maxChunkSize) {
@ -177,32 +120,9 @@ module.exports = function webpackClientConfig() {
)
}
// https://webpack.js.org/plugins/hashed-module-ids-plugin
config.plugins.push(new webpack.HashedModuleIdsPlugin())
// Minify JS
// https://github.com/webpack-contrib/uglifyjs-webpack-plugin
if (this.options.build.uglify !== false) {
config.plugins.push(
new UglifyJSPlugin(
Object.assign(
{
// cache: true,
sourceMap: true,
parallel: true,
extractComments: {
filename: 'LICENSES'
},
uglifyOptions: {
output: {
comments: /^\**!|@preserve|@license|@cc_on/
}
}
},
this.options.build.uglify
)
)
)
// https://github.com/webpack-contrib/webpack-stylish
if (!this.options.dev) {
config.plugins.push(new StylishPlugin())
}
// Webpack Bundle Analyzer
@ -224,6 +144,7 @@ module.exports = function webpackClientConfig() {
isDev,
isClient: true
})
// Only overwrite config when something is returned for backwards compatibility
if (extendedConfig !== undefined) {
config = extendedConfig
@ -232,50 +153,3 @@ module.exports = function webpackClientConfig() {
return config
}
// --------------------------------------------------------------------------
// Adds Common Chunks Plugin
// --------------------------------------------------------------------------
function commonChunksPlugin(config) {
// Create explicit vendor chunk
config.plugins.unshift(
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: this.getFileName('vendor'),
minChunks(module, count) {
// A module is extracted into the vendor chunk when...
return (
// If it's inside node_modules
/node_modules/.test(module.context) &&
// Do not externalize if the request is a CSS file or a Vue file which can potentially emit CSS assets!
!/\.(css|less|scss|sass|styl|stylus|vue)$/.test(module.request)
)
}
})
)
}
// --------------------------------------------------------------------------
// Adds DLL plugin
// https://github.com/webpack/webpack/tree/master/examples/dll-user
// --------------------------------------------------------------------------
function dllPlugin(config) {
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
})
)
}
})
if (_dlls.length) {
debug('Using dll for ' + _dlls.join(','))
}
}

View File

@ -1,46 +0,0 @@
const webpack = require('webpack')
const { resolve } = require('path')
const ClientConfig = require('./client.config')
/*
|--------------------------------------------------------------------------
| Webpack Dll Config
| https://github.com/webpack/webpack/tree/master/examples/dll
|--------------------------------------------------------------------------
*/
module.exports = 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

@ -0,0 +1,83 @@
const webpack = require('webpack')
const chalk = require('chalk')
const _ = require('lodash')
const sharedState = {}
const BLOCK_CHAR = '█'
module.exports = class ProgressPlugin extends webpack.ProgressPlugin {
constructor(options) {
super(options)
this.handler = (percent, msg) => this.updateProgress(percent, msg)
this.options = options
if (!sharedState[options.name]) {
sharedState[options.name] = {
color: options.color
}
}
this.spinner = options.spinner
}
get state() {
return sharedState[this.options.name]
}
updateProgress(percent, msg) {
const progress = Math.floor(percent * 100)
this.state.progress = progress
this.state.msg = msg
// Process all states
let inProgress = false
const additional = []
const bars = Object.keys(sharedState).map(name => {
const state = sharedState[name]
if (state.progress < 100) {
inProgress = true
}
const blockChar = chalk.keyword(state.color)(BLOCK_CHAR)
additional.push(`${blockChar} ${name} (${state.progress}%) `)
return {
name,
color: state.color,
progress: state.progress,
blockChar: chalk.keyword(state.color)(BLOCK_CHAR)
}
})
if (!inProgress) {
this.spinner.succeed('Compiled ' + this.options.name)
return
}
// Generate progressbars
const width = 25
const progressbars = _.range(width).fill(chalk.white(BLOCK_CHAR))
_.sortBy(bars, 'progress').reverse().forEach(bar => {
const w = bar.progress * (width / 100)
for (let i = 0; i < w; i++) {
progressbars[i] = bar.blockChar
}
})
// Update spinner
this.spinner.start()
this.spinner.text = 'Compiling ' + progressbars.join('') + ' ' + additional.join(' ')
}
}

View File

@ -1,18 +0,0 @@
// Taken from https://github.com/egoist/poi/blob/3e93c88c520db2d20c25647415e6ae0d3de61145/packages/poi/lib/webpack/timefix-plugin.js#L1-L16
// Thanks to @egoist
module.exports = class TimeFixPlugin {
constructor(timefix = 11000) {
this.timefix = timefix
}
apply(compiler) {
compiler.plugin('watch-run', (watching, callback) => {
watching.startTime += this.timefix
callback()
})
compiler.plugin('done', stats => {
stats.startTime -= this.timefix
})
}
}

View File

@ -1,6 +1,6 @@
module.exports = class WarnFixPlugin {
apply(compiler) /* istanbul ignore next */ {
compiler.plugin('done', stats => {
compiler.hooks.done.tap('warnfix-plugin', stats => {
stats.compilation.warnings = stats.compilation.warnings.filter(warn => {
if (
warn.name === 'ModuleDependencyWarning' &&

View File

@ -46,9 +46,6 @@ module.exports = function webpackServerConfig() {
}),
new webpack.DefinePlugin(
Object.assign(env, {
'process.env.NODE_ENV': JSON.stringify(
this.options.env.NODE_ENV || (this.options.dev ? 'development' : 'production')
),
'process.env.VUE_ENV': JSON.stringify('server'),
'process.mode': JSON.stringify(this.options.mode),
'process.browser': false,

View File

@ -197,8 +197,7 @@ Options.defaults = {
build: {
analyze: false,
profile: process.argv.includes('--profile'),
dll: false,
scopeHoisting: false,
splitPages: true,
maxChunkSize: false,
extractCSS: false,
cssSourceMap: undefined,
@ -208,12 +207,10 @@ Options.defaults = {
filenames: {
css: '[name].[contenthash].css',
manifest: 'manifest.[hash].js',
vendor: 'vendor.[chunkhash].js',
app: '[name].[chunkhash].js',
chunk: '[name].[chunkhash].js'
},
styleResources: {},
vendor: [],
plugins: [],
babel: {
babelrc: false
@ -342,9 +339,7 @@ Options.defaults = {
}
},
watchers: {
webpack: {
ignored: /-dll/
},
webpack: {},
chokidar: {}
},
editor: undefined,

View File

@ -53,6 +53,31 @@ exports.waitFor = function waitFor(ms) {
return new Promise(resolve => setTimeout(resolve, ms || 0))
}
async function promiseFinally(fn, finalFn) {
let result
try {
if (typeof fn === 'function') {
result = await fn()
} else {
result = await fn
}
} finally {
finalFn()
}
return result
}
exports.promiseFinally = promiseFinally
exports.timeout = function timeout(fn, ms, msg) {
let timerId
const warpPromise = promiseFinally(fn, () => clearTimeout(timerId))
const timerPromise = new Promise((resolve, reject) => {
timerId = setTimeout(() => reject(new Error(msg)), ms)
})
return Promise.race([warpPromise, timerPromise])
}
exports.urlJoin = function urlJoin() {
return [].slice
.call(arguments)

View File

@ -1,6 +1,5 @@
const path = require('path')
const fs = require('fs')
const { uniq } = require('lodash')
const hash = require('hash-sum')
const { chainFn, sequence, printWarn } = require('../common/utils')
@ -23,12 +22,7 @@ module.exports = class ModuleContainer {
}
addVendor(vendor) {
/* istanbul ignore if */
if (typeof vendor !== 'string' && !Array.isArray(vendor)) {
throw new Error('Invalid vendor: ' + vendor)
}
this.options.build.vendor = uniq(this.options.build.vendor.concat(vendor))
printWarn('module: addVendor is no longer necessary')
}
addTemplate(template) {

View File

@ -11,7 +11,7 @@ const connect = require('connect')
const launchMiddleware = require('launch-editor-middleware')
const crypto = require('crypto')
const { setAnsiColors, isUrl, waitFor } = require('../common/utils')
const { setAnsiColors, isUrl, waitFor, timeout } = require('../common/utils')
const { Options } = require('../common')
const MetaRenderer = require('./meta')
@ -440,9 +440,9 @@ module.exports = class Renderer {
throw error
}
// Used by nuxt.js to say when the components are loaded and the app ready
await new Promise(resolve => {
await timeout(new Promise(resolve => {
window._onNuxtLoaded = () => resolve(window)
})
}), 20000, 'Components loading in renderAndGetWindow was not completed in 20s')
if (opts.virtualConsole !== false) {
// after window initialized successfully
options.virtualConsole.removeListener('jsdomError', jsdomErrHandler)

View File

@ -1,6 +1,6 @@
{
"name": "nuxt",
"version": "1.4.0",
"version": "2.0.0",
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
"contributors": [
{
@ -59,37 +59,37 @@
"dependencies": {
"@nuxtjs/youch": "^4.2.3",
"ansi-html": "^0.0.7",
"autoprefixer": "^7.2.5",
"autoprefixer": "^8.1.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-loader": "^7.1.4",
"babel-preset-vue-app": "^2.0.0",
"caniuse-lite": "^1.0.30000808",
"chalk": "^2.3.1",
"caniuse-lite": "^1.0.30000813",
"chalk": "^2.3.2",
"chokidar": "^2.0.1",
"clone": "^2.1.1",
"compression": "^1.7.1",
"connect": "^3.6.5",
"css-hot-loader": "^1.3.7",
"css-hot-loader": "^1.3.8",
"css-loader": "^0.28.9",
"debug": "^3.1.0",
"es6-promise": "^4.2.4",
"etag": "^1.8.1",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "^1.1.11",
"fresh": "^0.5.2",
"friendly-errors-webpack-plugin": "^1.6.1",
"fs-extra": "^5.0.0",
"glob": "^7.1.2",
"hash-sum": "^1.0.2",
"html-minifier": "^3.5.9",
"html-webpack-plugin": "^2.30.1",
"html-minifier": "^3.5.10",
"html-webpack-plugin": "^3.0.6",
"launch-editor": "^2.2.1",
"launch-editor-middleware": "^2.2.1",
"lodash": "^4.17.5",
"lru-cache": "^4.1.1",
"lru-cache": "^4.1.2",
"memory-fs": "^0.4.1",
"minimist": "^1.2.0",
"opencollective": "^1.0.3",
"ora": "^2.0.0",
"postcss": "^6.0.17",
"postcss-cssnext": "^3.1.0",
"postcss-import": "^11.1.0",
@ -97,41 +97,37 @@
"postcss-loader": "^2.1.0",
"postcss-url": "^7.3.0",
"pretty-error": "^2.1.1",
"progress-bar-webpack-plugin": "^1.10.0",
"semver": "^5.5.0",
"serialize-javascript": "^1.4.0",
"serve-static": "^1.13.2",
"server-destroy": "^1.0.1",
"source-map": "^0.7.0",
"style-resources-loader": "^1.0.0",
"uglifyjs-webpack-plugin": "^1.1.8",
"source-map": "^0.7.2",
"style-resources-loader": "^1.1.0",
"time-fix-plugin": "^2.0.0",
"upath": "^1.0.2",
"url-loader": "^0.6.2",
"vue": "^2.5.13",
"vue-loader": "13.7.0",
"vue-meta": "^1.4.3",
"url-loader": "^1.0.1",
"vue": "^2.5.15",
"vue-loader": "^14.2.1",
"vue-meta": "^1.4.1",
"vue-router": "^3.0.1",
"vue-server-renderer": "^2.5.13",
"vue-template-compiler": "^2.5.13",
"vue-server-renderer": "^2.5.15",
"vue-template-compiler": "^2.5.15",
"vuex": "^3.0.1",
"webpack": "^3.11.0",
"webpack-bundle-analyzer": "^2.10.0",
"webpack-dev-middleware": "^2.0.5",
"webpack-hot-middleware": "^2.21.0",
"webpack-node-externals": "^1.6.0"
"webpack": "^4.1.1",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-middleware": "^3.0.1",
"webpack-hot-middleware": "^2.21.2",
"webpack-node-externals": "^1.6.0",
"webpack-stylish": "^0.1.6"
},
"devDependencies": {
"ava": "^0.25.0",
"babel-eslint": "^8.2.1",
"babel-plugin-array-includes": "^2.0.3",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-istanbul": "^4.1.5",
"codecov": "^3.0.0",
"copy-webpack-plugin": "^4.4.1",
"cross-env": "^5.1.3",
"eslint": "^4.17.0",
"eslint-config-standard": "^11.0.0-beta.0",
"eslint-config-standard-jsx": "^4.0.2",
"eslint": "^4.18.2",
"eslint-config-standard": "^11.0.0",
"eslint-config-standard-jsx": "^5.0.0",
"eslint-plugin-html": "^4.0.2",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^6.0.0",
@ -139,15 +135,13 @@
"eslint-plugin-react": "^7.6.1",
"eslint-plugin-standard": "^3.0.1",
"express": "^4.16.2",
"finalhandler": "^1.1.0",
"finalhandler": "^1.1.1",
"jsdom": "^11.6.2",
"json-loader": "^0.5.7",
"nyc": "^11.4.1",
"nyc": "^11.6.0",
"puppeteer": "^1.0.0",
"request": "^2.83.0",
"request-promise-native": "^1.0.5",
"sinon": "^4.3.0",
"uglify-js": "^3.3.10"
"sinon": "^4.3.0"
},
"collective": {
"type": "opencollective",

View File

@ -1,6 +1,6 @@
import { promisify } from 'util'
import test from 'ava'
import { resolve, sep } from 'path'
import { resolve } from 'path'
import rp from 'request-promise-native'
import { exec, spawn } from 'child_process'
import { Utils } from '..'
@ -14,10 +14,9 @@ const url = route => 'http://localhost:' + port + route
const nuxtBin = resolve(__dirname, '..', 'bin', 'nuxt')
test.serial('nuxt build', async t => {
const { stdout, stderr } = await execify(`node ${nuxtBin} build ${rootDir}`)
const { stdout } = await execify(`node ${nuxtBin} build ${rootDir}`)
t.true(stdout.includes('server-bundle.json'))
t.true(stderr.includes('Building done'))
t.true(stdout.includes('Compiled successfully'))
})
test.serial('nuxt build -> error config', async t => {
@ -91,12 +90,7 @@ test.serial('nuxt start', async t => {
})
test.serial('nuxt generate', async t => {
const { stdout, stderr } = await execify(`node ${nuxtBin} generate ${rootDir}`)
const { stdout } = await execify(`node ${nuxtBin} generate ${rootDir}`)
t.true(stdout.includes('server-bundle.json'))
t.true(stderr.includes('Destination folder cleaned'))
t.true(stderr.includes('Static & build files copied'))
t.true(stderr.includes(`Generate file: ${sep}users${sep}1${sep}index.html`))
t.true(stdout.includes('Generate errors summary:'))
t.true(stderr.includes('Generate done'))
t.true(stdout.includes('vue-ssr-client-manifest.json'))
})

View File

@ -1,53 +0,0 @@
import { promisify } from 'util'
import test from 'ava'
import { resolve } from 'path'
import fs from 'fs'
import { Nuxt, Builder } from '..'
import { interceptLog, release } from './helpers/console'
const readFile = promisify(fs.readFile)
const rootDir = resolve(__dirname, 'fixtures/dll')
const dllDir = resolve(rootDir, '.cache/client-dll')
const checkCache = lib => {
return async t => {
const manifest = await readFile(
resolve(dllDir, `./${lib}-manifest.json`),
'utf-8'
)
t.truthy(JSON.parse(manifest).name)
t.true(fs.existsSync(resolve(dllDir, `./${JSON.parse(manifest).name}.js`)))
}
}
let nuxt
test.serial('Init Nuxt.js', async t => {
let config = require(resolve(rootDir, 'nuxt.config.js'))
config.rootDir = rootDir
config.dev = true
const logSpy = await interceptLog(async () => {
nuxt = new Nuxt(config)
await new Builder(nuxt).build()
})
t.true(logSpy.calledWithMatch('DONE'))
})
test('Check vue cache', checkCache('vue'))
test('Check vue-meta cache', checkCache('vue-meta'))
test('Check vue-router cache', checkCache('vue-router'))
test('Build with DllReferencePlugin', async t => {
const logSpy = await interceptLog()
await new Builder(nuxt).build()
release()
t.true(logSpy.withArgs('Using dll for 3 libs').calledOnce)
})
// Close server and ask nuxt to stop listening to file changes
test.after.always('Closing nuxt.js', t => {
nuxt.close()
})

View File

@ -1,15 +0,0 @@
module.exports = {
build: {
stats: false,
dll: true,
extend(config, options) {
if (options.isClient) {
const dlls = config.plugins.filter(
plugin => plugin.constructor.name === 'DllReferencePlugin'
)
console.log('Using dll for ' + dlls.length + ' libs') // eslint-disable-line no-console
}
return config
}
}
}

View File

View File

@ -1,9 +1,8 @@
const path = require('path')
module.exports = function basicModule(options, resolve) {
// Add vendor
// Add vendor (deprecated)
this.addVendor('lodash')
this.addVendor(['lodash', 'lodash'])
// Add a plugin
this.addPlugin(path.resolve(__dirname, 'reverse.js'))

View File

@ -30,6 +30,7 @@ export function release() {
}
context = null
delete console.spiedInTest // eslint-disable-line no-console
}
export async function intercept(levels, msg, cb) {
@ -39,6 +40,7 @@ export async function intercept(levels, msg, cb) {
)
}
context = {}
console.spiedInTest = true // eslint-disable-line no-console
if (cb === undefined && typeof msg === 'function') {
cb = msg

View File

@ -29,13 +29,6 @@ test.serial('Init Nuxt.js', async t => {
t.true(buildSpies.log.calledWithMatch('OPEN'))
})
test.serial('Vendor', async t => {
t.true(
nuxt.options.build.vendor.indexOf('lodash') !== -1,
'lodash added to config'
)
})
test.serial('Plugin', async t => {
t.true(
normalize(nuxt.options.plugins[0].src).includes(

View File

@ -28,6 +28,33 @@ test('waitFor', async t => {
await Utils.waitFor()
})
test('timeout (promise)', async t => {
const result = await Utils.timeout(Promise.resolve('time not run out'), 100)
t.is(result, 'time not run out')
})
test('timeout (async function)', async t => {
const result = await Utils.timeout(async () => {
await Utils.waitFor(10)
return 'time not run out'
}, 100)
t.is(result, 'time not run out')
})
test('timeout (timeout in 100ms)', async t => {
const timeout = Utils.timeout(Utils.waitFor(200), 100, 'timeout test 100ms')
const { message } = await t.throws(timeout)
t.is(message, 'timeout test 100ms')
})
test('timeout (async timeout in 100ms)', async t => {
const timeout = Utils.timeout(async () => {
await Utils.waitFor(500)
}, 100, 'timeout test 100ms')
const { message } = await t.throws(timeout)
t.is(message, 'timeout test 100ms')
})
test('urlJoin', t => {
t.is(Utils.urlJoin('test', '/about'), 'test/about')
})

View File

@ -43,7 +43,7 @@ test('/ (global styles inlined)', async t => {
t.true(html.includes('.global-css-selector'))
})
test('/ (preload fonts)', async t => {
test.skip('/ (preload fonts)', async t => {
const { html } = await nuxt.renderRoute('/')
t.true(
html.includes(
@ -59,7 +59,7 @@ test('/ (custom app.html)', async t => {
test('/ (custom build.publicPath)', async t => {
const { html } = await nuxt.renderRoute('/')
t.true(html.includes('src="/test/orion/vendor.'))
t.true(html.includes('<script src="/test/orion/'))
})
test('/ (custom postcss.config.js)', async t => {
@ -176,7 +176,7 @@ test('Check stats.json generated by build.analyze', t => {
__dirname,
'fixtures/with-config/.nuxt/dist/stats.json'
))
t.is(stats.assets.length, 37)
t.is(stats.assets.length, 34)
})
test('Check /test/test.txt with custom serve-static options', async t => {

1622
yarn.lock

File diff suppressed because it is too large Load Diff