Merge pull request #633 from pi0/2.3.0

️ Upgrade to Vue 2.3.x
This commit is contained in:
Sébastien Chopin 2017-05-05 10:57:05 +02:00 committed by GitHub
commit ded7de0e72
22 changed files with 575 additions and 416 deletions

View File

@ -2,8 +2,6 @@ language: node_js
node_js: node_js:
- "7.2" - "7.2"
- "6.9" - "6.9"
- "5.12"
- "4.7"
before_install: before_install:
- if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi - if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
install: install:

View File

@ -1,6 +1,7 @@
module.exports = { module.exports = {
build: { build: {
filenames: { filenames: {
css: 'styles.[chunkhash].css', // default: common.[chunkhash].css
manifest: 'manifest.[hash].js', // default: manifest.[hash].js manifest: 'manifest.[hash].js', // default: manifest.[hash].js
vendor: 'vendor.[hash].js', // default: vendor.bundle.[hash].js vendor: 'vendor.[hash].js', // default: vendor.bundle.[hash].js
app: 'app.[chunkhash].js' // default: nuxt.bundle.[chunkhash].js app: 'app.[chunkhash].js' // default: nuxt.bundle.[chunkhash].js

View File

@ -2,13 +2,15 @@
import Vue from 'vue' import Vue from 'vue'
import middleware from './middleware' import middleware from './middleware'
import { app, router<%= (store ? ', store' : '') %>, NuxtError } from './index' import { createApp, NuxtError } from './index'
import { applyAsyncData, getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, promiseSeries, promisify, getLocation, compile } from './utils' import { applyAsyncData, getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, promiseSeries, promisify, getLocation, compile } from './utils'
const noopData = () => { return {} } const noopData = () => { return {} }
const noopFetch = () => {} const noopFetch = () => {}
let _lastPaths = [] let _lastPaths = []
let _lastComponentsFiles = [] let _lastComponentsFiles = []
const { app, router<%= (store ? ', store' : '') %> } = createApp()
function mapTransitions(Components, to, from) { function mapTransitions(Components, to, from) {
return Components.map((Component) => { return Components.map((Component) => {
let transition = Component.options.transition let transition = Component.options.transition

View File

@ -2,8 +2,8 @@
import Vue from 'vue' import Vue from 'vue'
import Meta from 'vue-meta' import Meta from 'vue-meta'
import router from './router.js' import { createRouter } from './router.js'
<% if (store) { %>import store from './store.js'<% } %> <% if (store) { %>import { createStore } from './store.js'<% } %>
import NuxtChild from './components/nuxt-child.js' import NuxtChild from './components/nuxt-child.js'
import NuxtLink from './components/nuxt-link.js' import NuxtLink from './components/nuxt-link.js'
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>' import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
@ -25,86 +25,97 @@ Vue.use(Meta, {
tagIDKeyName: 'hid' // the property name that vue-meta uses to determine whether to overwrite or append a tag tagIDKeyName: 'hid' // the property name that vue-meta uses to determine whether to overwrite or append a tag
}) })
if (process.browser) {
<% if (store) { %>
// Replace store state before calling plugins
if (window.__NUXT__ && window.__NUXT__.state) {
store.replaceState(window.__NUXT__.state)
}
<% } %>
// window.onNuxtReady(() => console.log('Ready')) hook
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
window._nuxtReadyCbs = []
window.onNuxtReady = function (cb) {
window._nuxtReadyCbs.push(cb)
}
}
// root instance
// here we inject the router and store to all child components,
// making them available everywhere as `this.$router` and `this.$store`.
const defaultTransition = <%= const defaultTransition = <%=
serialize(transition) serialize(transition)
.replace('beforeEnter(', 'function(').replace('enter(', 'function(').replace('afterEnter(', 'function(') .replace('beforeEnter(', 'function(').replace('enter(', 'function(').replace('afterEnter(', 'function(')
.replace('enterCancelled(', 'function(').replace('beforeLeave(', 'function(').replace('leave(', 'function(') .replace('enterCancelled(', 'function(').replace('beforeLeave(', 'function(').replace('leave(', 'function(')
.replace('afterLeave(', 'function(').replace('leaveCancelled(', 'function(') .replace('afterLeave(', 'function(').replace('leaveCancelled(', 'function(')
%> %>
let app = {
router, export { NuxtError }
<%= (store ? 'store,' : '') %>
_nuxt: { export function createApp (ssrContext) {
defaultTransition: defaultTransition, <% if (store) { %>
transitions: [ defaultTransition ], const store = createStore()
setTransitions (transitions) { <% } %>
if (!Array.isArray(transitions)) { const router = createRouter()
transitions = [ transitions ]
} if (process.browser) {
transitions = transitions.map((transition) => { <% if (store) { %>
if (!transition) { // Replace store state before calling plugins
transition = defaultTransition if (window.__NUXT__ && window.__NUXT__.state) {
} else if (typeof transition === 'string') { store.replaceState(window.__NUXT__.state)
transition = Object.assign({}, defaultTransition, { name: transition })
} else {
transition = Object.assign({}, defaultTransition, transition)
}
return transition
})
this.$options._nuxt.transitions = transitions
return transitions
},
err: null,
dateErr: null,
error (err) {
err = err || null
if (typeof err === 'string') {
err = { statusCode: 500, message: err }
}
this.$options._nuxt.dateErr = Date.now()
this.$options._nuxt.err = err;
return err
} }
}, <% } %>
...App // window.onNuxtReady(() => console.log('Ready')) hook
} // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
window._nuxtReadyCbs = []
window.onNuxtReady = function (cb) {
window._nuxtReadyCbs.push(cb)
}
}
// root instance
// here we inject the router and store to all child components,
// making them available everywhere as `this.$router` and `this.$store`.
let app = {
router,
<%= (store ? 'store,' : '') %>
ssrContext,
_nuxt: {
defaultTransition: defaultTransition,
transitions: [ defaultTransition ],
setTransitions (transitions) {
if (!Array.isArray(transitions)) {
transitions = [ transitions ]
}
transitions = transitions.map((transition) => {
if (!transition) {
transition = defaultTransition
} else if (typeof transition === 'string') {
transition = Object.assign({}, defaultTransition, { name: transition })
} else {
transition = Object.assign({}, defaultTransition, transition)
}
return transition
})
this.$options._nuxt.transitions = transitions
return transitions
},
err: null,
dateErr: null,
error (err) {
err = err || null
if (typeof err === 'string') {
err = { statusCode: 500, message: err }
}
this.$options._nuxt.dateErr = Date.now()
this.$options._nuxt.err = err;
return err
}
},
...App
}
// Includes & Inject external plugins // Includes & Inject external plugins
<% plugins.forEach(function (plugin) { <% plugins.forEach(function (plugin) {
if (plugin.ssr) { %> if (plugin.ssr) { %>
<%= (plugin.injectAs ? 'let ' + plugin.injectAs + ' = ' : '') %>require('<%= plugin.src %>')
<% if (plugin.injectAs) { %>
<%= plugin.injectAs + ' = ' + plugin.injectAs + '.default || ' + plugin.injectAs %>
app['<%= plugin.injectAs %>'] = <%= plugin.injectAs %>
<% }
} else { %>
if (process.browser) {
<%= (plugin.injectAs ? 'let ' + plugin.injectAs + ' = ' : '') %>require('<%= plugin.src %>') <%= (plugin.injectAs ? 'let ' + plugin.injectAs + ' = ' : '') %>require('<%= plugin.src %>')
<% if (plugin.injectAs) { %> <% if (plugin.injectAs) { %>
<%= plugin.injectAs + ' = ' + plugin.injectAs + '.default || ' + plugin.injectAs %> <%= plugin.injectAs + ' = ' + plugin.injectAs + '.default || ' + plugin.injectAs %>
app['<%= plugin.injectAs %>'] = <%= plugin.injectAs %> app['<%= plugin.injectAs %>'] = <%= plugin.injectAs %>
<% } %> <% }
} } else { %>
<% } if (process.browser) {
}) %> <%= (plugin.injectAs ? 'let ' + plugin.injectAs + ' = ' : '') %>require('<%= plugin.src %>')
<% if (plugin.injectAs) { %>
<%= plugin.injectAs + ' = ' + plugin.injectAs + '.default || ' + plugin.injectAs %>
app['<%= plugin.injectAs %>'] = <%= plugin.injectAs %>
<% } %>
}
<% }
}) %>
export { app, router<%= (store ? ', store' : '') %>, NuxtError } return { app, router<%= (store ? ', store' : '') %> }
}

View File

@ -53,12 +53,14 @@ const scrollBehavior = (to, from, savedPosition) => {
} }
<% } %> <% } %>
export default new Router({ export function createRouter () {
mode: '<%= router.mode %>', return new Router({
base: '<%= router.base %>', mode: '<%= router.mode %>',
linkActiveClass: '<%= router.linkActiveClass %>', base: '<%= router.base %>',
scrollBehavior, linkActiveClass: '<%= router.linkActiveClass %>',
routes: [ scrollBehavior,
<%= _routes %> routes: [
] <%= _routes %>
}) ]
})
}

View File

@ -7,11 +7,10 @@ import Vue from 'vue'
import { stringify } from 'querystring' import { stringify } from 'querystring'
import { omit } from 'lodash' import { omit } from 'lodash'
import middleware from './middleware' import middleware from './middleware'
import { app, router<%= (store ? ', store' : '') %>, NuxtError } from './index' import { createApp, NuxtError } from './index'
import { applyAsyncData, getMatchedComponents, getContext, promiseSeries, promisify, urlJoin } from './utils' import { applyAsyncData, getMatchedComponents, getContext, promiseSeries, promisify, urlJoin } from './utils'
const isDev = <%= isDev %> const isDev = <%= isDev %>
const _app = new Vue(app)
// This exported function will be called by `bundleRenderer`. // This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the // This is where we perform data-prefetching to determine the
@ -19,6 +18,8 @@ const _app = new Vue(app)
// Since data fetching is async, this function is expected to // Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance. // return a Promise that resolves to the app instance.
export default context => { export default context => {
const { app, router<%= (store ? ', store' : '') %> } = createApp(context)
const _app = new Vue(app)
// Add store to the context // Add store to the context
<%= (store ? 'context.store = store' : '') %> <%= (store ? 'context.store = store' : '') %>
// Nuxt object // Nuxt object

View File

@ -30,8 +30,11 @@ let storeData = {}
if (filenames.indexOf('./index.js') !== -1) { if (filenames.indexOf('./index.js') !== -1) {
let mainModule = getModule('./index.js') let mainModule = getModule('./index.js')
if (mainModule.commit) { if (mainModule.commit) {
store = mainModule console.error('[nuxt.js] store/index.js should export raw store options instead of an instance.')
} else { } else {
if (mainModule.state && typeof mainModule.state !== 'function') {
console.error('[nuxt.js] store state should be a function.')
}
storeData = mainModule storeData = mainModule
} }
} }
@ -49,8 +52,13 @@ if (store == null) {
name = namePath.pop() name = namePath.pop()
module[name] = getModule(filename) module[name] = getModule(filename)
module[name].namespaced = true module[name].namespaced = true
if (typeof module[name].state !== 'function') {
console.error('[nuxt.js] store module state should be a function.')
}
} }
store = new Vuex.Store(storeData)
} }
export default store export function createStore () {
return new Vuex.Store(storeData)
}

View File

@ -47,6 +47,7 @@ const defaults = {
analyze: false, analyze: false,
publicPath: '/_nuxt/', publicPath: '/_nuxt/',
filenames: { filenames: {
css: 'common.[chunkhash].css',
manifest: 'manifest.[hash].js', manifest: 'manifest.[hash].js',
vendor: 'vendor.bundle.[hash].js', vendor: 'vendor.bundle.[hash].js',
app: 'nuxt.bundle.[chunkhash].js' app: 'nuxt.bundle.[chunkhash].js'
@ -101,9 +102,11 @@ export function options () {
} }
const serverConfig = getWebpackServerConfig.call(this) const serverConfig = getWebpackServerConfig.call(this)
const bundlePath = join(serverConfig.output.path, 'server-bundle.json') const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
if (fs.existsSync(bundlePath)) { const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
if (fs.existsSync(bundlePath) && fs.existsSync(manifestPath)) {
const bundle = fs.readFileSync(bundlePath, 'utf8') const bundle = fs.readFileSync(bundlePath, 'utf8')
createRenderer.call(this, JSON.parse(bundle)) const manifest = fs.readFileSync(manifestPath, 'utf8')
createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
addAppTemplate.call(this) addAppTemplate.call(this)
} }
} }
@ -142,10 +145,8 @@ function * buildFiles () {
watchPages.call(this) watchPages.call(this)
} else { } else {
debug('Building files...') debug('Building files...')
yield [ yield webpackRunClient.call(this)
webpackRunClient.call(this), yield webpackRunServer.call(this)
webpackRunServer.call(this)
]
addAppTemplate.call(this) addAppTemplate.call(this)
} }
} }
@ -386,6 +387,7 @@ function createWebpackMiddleware () {
}) })
) )
const clientCompiler = webpack(clientConfig) const clientCompiler = webpack(clientConfig)
this.clientCompiler = clientCompiler
// Add the middleware to the instance context // Add the middleware to the instance context
this.webpackDevMiddleware = pify(require('webpack-dev-middleware')(clientCompiler, { this.webpackDevMiddleware = pify(require('webpack-dev-middleware')(clientCompiler, {
publicPath: clientConfig.output.publicPath, publicPath: clientConfig.output.publicPath,
@ -406,20 +408,35 @@ function createWebpackMiddleware () {
interpolate: /{{([\s\S]+?)}}/g interpolate: /{{([\s\S]+?)}}/g
}) })
} }
this.watchHandler()
}) })
} }
function webpackWatchAndUpdate () { function webpackWatchAndUpdate () {
const MFS = require('memory-fs') // <- dependencies of webpack const MFS = require('memory-fs') // <- dependencies of webpack
const mfs = new MFS() const serverFS = new MFS()
const clientFS = this.clientCompiler.outputFileSystem
const serverConfig = getWebpackServerConfig.call(this) const serverConfig = getWebpackServerConfig.call(this)
const serverCompiler = webpack(serverConfig) const serverCompiler = webpack(serverConfig)
const outputPath = join(serverConfig.output.path, 'server-bundle.json') const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
serverCompiler.outputFileSystem = mfs const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
this.webpackServerWatcher = serverCompiler.watch(this.options.watchers.webpack, (err) => { serverCompiler.outputFileSystem = serverFS
const watchHandler = (err) => {
if (err) throw err if (err) throw err
createRenderer.call(this, JSON.parse(mfs.readFileSync(outputPath, 'utf-8'))) const bundleExists = serverFS.existsSync(bundlePath)
}) const manifestExists = clientFS.existsSync(manifestPath)
if (!bundleExists) {
debug('Waiting for server bundle...')
} else if (!manifestExists) {
debug('Waiting for client manifest...')
} else {
const bundle = serverFS.readFileSync(bundlePath, 'utf8')
const manifest = clientFS.readFileSync(manifestPath, 'utf8')
createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
}
}
this.watchHandler = watchHandler
this.webpackServerWatcher = serverCompiler.watch(this.options.watchers.webpack, watchHandler)
} }
function webpackRunClient () { function webpackRunClient () {
@ -444,16 +461,18 @@ function webpackRunServer () {
console.log('[nuxt:build:server]\n', stats.toString(webpackStats)) // eslint-disable-line no-console console.log('[nuxt:build:server]\n', stats.toString(webpackStats)) // eslint-disable-line no-console
if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors')) if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
const bundlePath = join(serverConfig.output.path, 'server-bundle.json') const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
readFile(bundlePath, 'utf8') readFile(bundlePath, 'utf8')
.then((bundle) => { .then(bundle => readFile(manifestPath, 'utf8')
createRenderer.call(this, JSON.parse(bundle)) .then(manifest => {
resolve() createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
}) resolve()
}))
}) })
}) })
} }
function createRenderer (bundle) { function createRenderer (bundle, manifest) {
// Create bundle renderer to give a fresh context for every request // Create bundle renderer to give a fresh context for every request
let cacheConfig = false let cacheConfig = false
if (this.options.cache) { if (this.options.cache) {
@ -463,9 +482,11 @@ function createRenderer (bundle) {
maxAge: 1000 * 60 * 15 maxAge: 1000 * 60 * 15
})) }))
} }
this.renderer = createBundleRenderer(bundle, { this.renderer = createBundleRenderer(bundle, Object.assign({
cache: cacheConfig cache: cacheConfig,
}) clientManifest: manifest,
runInNewContext: false
}, this.options.ssr))
this.renderToString = pify(this.renderer.renderToString) this.renderToString = pify(this.renderer.renderToString)
this.renderToStream = this.renderer.renderToStream this.renderToStream = this.renderer.renderToStream
} }

View File

@ -43,8 +43,7 @@ class Nuxt {
performance: { performance: {
gzip: { gzip: {
threshold: 0 threshold: 0
}, }
prefetch: true
}, },
watchers: { watchers: {
webpack: {}, webpack: {},

View File

@ -93,7 +93,7 @@ export function renderRoute (url, context = {}) {
// Add url and isSever to the context // Add url and isSever to the context
context.url = url context.url = url
context.isServer = true context.isServer = true
// Call rendertoSting from the bundleRenderer and generate the HTML (will update the context as well) // Call renderToSting from the bundleRenderer and generate the HTML (will update the context as well)
const self = this const self = this
return co(function * () { return co(function * () {
let APP = yield self.renderToString(context) let APP = yield self.renderToString(context)
@ -105,8 +105,9 @@ export function renderRoute (url, context = {}) {
if (self.options.router.base !== '/') { if (self.options.router.base !== '/') {
HEAD += `<base href="${self.options.router.base}">` HEAD += `<base href="${self.options.router.base}">`
} }
HEAD += context.styles HEAD += context.renderResourceHints() + context.renderStyles()
APP += `<script type="text/javascript">window.__NUXT__=${serialize(context.nuxt, { isJSON: true })}</script>` APP += `<script type="text/javascript">window.__NUXT__=${serialize(context.nuxt, { isJSON: true })}</script>`
APP += context.renderScripts()
const html = self.appTemplate({ const html = self.appTemplate({
HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(), HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
BODY_ATTRS: m.bodyAttrs.text(), BODY_ATTRS: m.bodyAttrs.text(),
@ -127,7 +128,8 @@ export function renderAndGetWindow (url, opts = {}) {
/* istanbul ignore if */ /* istanbul ignore if */
if (!jsdom) { if (!jsdom) {
try { try {
jsdom = require('jsdom') // https://github.com/tmpvar/jsdom/blob/master/lib/old-api.md
jsdom = require('jsdom/lib/old-api')
} catch (e) { } catch (e) {
console.error('Fail when calling nuxt.renderAndGetWindow(url)') // eslint-disable-line no-console console.error('Fail when calling nuxt.renderAndGetWindow(url)') // eslint-disable-line no-console
console.error('jsdom module is not installed') // eslint-disable-line no-console console.error('jsdom module is not installed') // eslint-disable-line no-console
@ -136,6 +138,7 @@ export function renderAndGetWindow (url, opts = {}) {
} }
} }
let virtualConsole = jsdom.createVirtualConsole().sendTo(console) let virtualConsole = jsdom.createVirtualConsole().sendTo(console)
// let virtualConsole = new jsdom.VirtualConsole().sendTo(console)
if (opts.virtualConsole === false) { if (opts.virtualConsole === false) {
virtualConsole = undefined virtualConsole = undefined
} }

View File

@ -4,6 +4,8 @@ import vueLoaderConfig from './vue-loader.config'
import { defaults } from 'lodash' import { defaults } from 'lodash'
import { join } from 'path' import { join } from 'path'
import { isUrl, urlJoin } from '../utils' import { isUrl, urlJoin } from '../utils'
import { styleLoader, extractStyles } from './helpers'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -71,15 +73,21 @@ export default function ({ isClient, isServer }) {
cacheDirectory: !!this.dev cacheDirectory: !!this.dev
}) })
}, },
{ test: /\.css$/, loader: 'vue-style-loader!css-loader' }, { test: /\.css$/, use: styleLoader.call(this, 'css') },
{ test: /\.less$/, loader: 'vue-style-loader!css-loader!less-loader' }, { test: /\.less$/, use: styleLoader.call(this, 'less', 'less-loader') },
{ test: /\.sass$/, loader: 'vue-style-loader!css-loader!sass-loader?indentedSyntax' }, { test: /\.sass$/, use: styleLoader.call(this, 'sass', 'sass-loader?indentedSyntax') },
{ test: /\.scss$/, loader: 'vue-style-loader!css-loader!sass-loader' }, { test: /\.scss$/, use: styleLoader.call(this, 'sass', 'sass-loader') },
{ test: /\.styl(us)?$/, loader: 'vue-style-loader!css-loader!stylus-loader' } { test: /\.styl(us)?$/, use: styleLoader.call(this, 'stylus', 'stylus-loader') }
] ]
}, },
plugins: this.options.build.plugins plugins: this.options.build.plugins
} }
// CSS extraction
if (extractStyles.call(this)) {
config.plugins.push(
new ExtractTextPlugin({filename: this.options.build.filenames.css})
)
}
// Add nuxt build loaders (can be configured in nuxt.config.js) // Add nuxt build loaders (can be configured in nuxt.config.js)
config.module.rules = config.module.rules.concat(this.options.build.loaders) config.module.rules = config.module.rules.concat(this.options.build.loaders)
// Return config // Return config

View File

@ -2,14 +2,15 @@
import { each, defaults } from 'lodash' import { each, defaults } from 'lodash'
import webpack from 'webpack' import webpack from 'webpack'
import VueSSRClientPlugin from 'vue-server-renderer/client-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import HTMLPlugin from 'html-webpack-plugin' import HTMLPlugin from 'html-webpack-plugin'
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin' import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
import ScriptExtHtmlWebpackPlugin from 'script-ext-html-webpack-plugin'
import PreloadWebpackPlugin from 'preload-webpack-plugin'
import ProgressBarPlugin from 'progress-bar-webpack-plugin' import ProgressBarPlugin from 'progress-bar-webpack-plugin'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
import OfflinePlugin from 'offline-plugin' import OfflinePlugin from 'offline-plugin'
import base from './base.config.js' import base from './base.config.js'
import { extractStyles } from './helpers'
import { resolve } from 'path' import { resolve } from 'path'
/* /*
@ -56,31 +57,33 @@ export default function () {
// Extract vendor chunks for better caching // Extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', name: 'vendor',
filename: this.options.build.filenames.vendor filename: this.options.build.filenames.vendor,
minChunks (module) {
// 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
!/\.css$/.test(module.request)
)
}
}), }),
// Extract manifest // Extract webpack runtime & manifest
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'manifest', name: 'manifest',
minChunks: Infinity,
filename: this.options.build.filenames.manifest filename: this.options.build.filenames.manifest
}), }),
// Generate output HTML // Generate output HTML
new HTMLPlugin({ new HTMLPlugin({
template: this.options.appTemplatePath template: this.options.appTemplatePath,
inject: false // <- Resources will be injected using vue server renderer
}), }),
// Add defer to scripts // Generate client manifest json
new ScriptExtHtmlWebpackPlugin({ new VueSSRClientPlugin({
defaultAttribute: 'defer' filename: 'client-manifest.json'
}) })
]) ])
if (!this.dev && this.options.performance.prefetch === true) {
// Add prefetch code-splitted routes
config.plugins.push(
new PreloadWebpackPlugin({
rel: 'prefetch'
})
)
}
// client bundle progress bar // client bundle progress bar
config.plugins.push( config.plugins.push(
new ProgressBarPlugin() new ProgressBarPlugin()

15
lib/webpack/helpers.js Executable file
View File

@ -0,0 +1,15 @@
import ExtractTextPlugin from 'extract-text-webpack-plugin'
export function extractStyles(ext) {
return !this.dev && !!this.options.build.extractCSS && this.options.build.extractCSS[ext] !== false
}
export function styleLoader(ext, loader = []) {
if (!extractStyles.call(this, ext)) {
return ['vue-style-loader', 'css-loader'].concat(loader)
}
return ExtractTextPlugin.extract({
use: ['css-loader?minimize'].concat(loader),
fallback: 'vue-style-loader'
})
}

View File

@ -1,7 +1,7 @@
'use strict' 'use strict'
import webpack from 'webpack' import webpack from 'webpack'
import VueSSRPlugin from 'vue-ssr-webpack-plugin' import VueSSRServerPlugin from 'vue-server-renderer/server-plugin'
import base from './base.config.js' import base from './base.config.js'
import { each, uniq } from 'lodash' import { each, uniq } from 'lodash'
import { existsSync, readFileSync } from 'fs' import { existsSync, readFileSync } from 'fs'
@ -34,7 +34,7 @@ export default function () {
hints: false hints: false
}, },
plugins: (config.plugins || []).concat([ plugins: (config.plugins || []).concat([
new VueSSRPlugin({ new VueSSRServerPlugin({
filename: 'server-bundle.json' filename: 'server-bundle.json'
}), }),
new webpack.DefinePlugin(Object.assign(env, { new webpack.DefinePlugin(Object.assign(env, {

View File

@ -1,6 +1,7 @@
'use strict' 'use strict'
import { defaults } from 'lodash' import { defaults } from 'lodash'
import { extractStyles, styleLoader } from './helpers'
export default function ({ isClient }) { export default function ({ isClient }) {
let babelOptions = JSON.stringify(defaults(this.options.build.babel, { let babelOptions = JSON.stringify(defaults(this.options.build.babel, {
@ -8,18 +9,21 @@ export default function ({ isClient }) {
babelrc: false, babelrc: false,
cacheDirectory: !!this.dev cacheDirectory: !!this.dev
})) }))
// https://github.com/vuejs/vue-loader/blob/master/docs/en/configurations
let config = { let config = {
postcss: this.options.build.postcss, postcss: this.options.build.postcss,
loaders: { loaders: {
'js': 'babel-loader?' + babelOptions, 'js': 'babel-loader?' + babelOptions,
'css': 'vue-style-loader!css-loader', 'css': styleLoader.call(this, 'css'),
'less': 'vue-style-loader!css-loader!less-loader', 'less': styleLoader.call(this, 'less', 'less-loader'),
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax', 'sass': styleLoader.call(this, 'sass', 'sass-loader?indentedSyntax'),
'scss': 'vue-style-loader!css-loader!sass-loader', 'scss': styleLoader.call(this, 'sass', 'scss-loader'),
'stylus': 'vue-style-loader!css-loader!stylus-loader', 'stylus': styleLoader.call(this, 'stylus', 'stylus-loader'),
'styl': 'vue-style-loader!css-loader!stylus-loader' 'styl': styleLoader.call(this, 'stylus', 'stylus-loader')
}, },
preserveWhitespace: false preserveWhitespace: false,
extractCSS: extractStyles.call(this, 'vue')
} }
// Return the config // Return the config
return config return config

View File

@ -54,17 +54,18 @@
"ansi-html": "^0.0.7", "ansi-html": "^0.0.7",
"autoprefixer": "^6.7.7", "autoprefixer": "^6.7.7",
"babel-core": "^6.24.1", "babel-core": "^6.24.1",
"babel-loader": "^6.4.1", "babel-loader": "^7.0.0",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"babel-preset-vue-app": "^1.2.0", "babel-preset-vue-app": "^1.2.0",
"chokidar": "^1.6.1", "chokidar": "^1.6.1",
"co": "^4.6.0", "co": "^4.6.0",
"compression": "^1.6.2", "compression": "^1.6.2",
"css-loader": "^0.28.0", "css-loader": "^0.28.1",
"debug": "^2.6.3", "debug": "^2.6.6",
"extract-text-webpack-plugin": "^2.1.0",
"file-loader": "^0.11.1", "file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.6.1", "friendly-errors-webpack-plugin": "^1.6.1",
"fs-extra": "^2.1.2", "fs-extra": "^3.0.0",
"glob": "^7.1.1", "glob": "^7.1.1",
"hash-sum": "^1.0.2", "hash-sum": "^1.0.2",
"html-minifier": "^3.4.3", "html-minifier": "^3.4.3",
@ -79,25 +80,24 @@
"progress-bar-webpack-plugin": "^1.9.3", "progress-bar-webpack-plugin": "^1.9.3",
"script-ext-html-webpack-plugin": "^1.7.1", "script-ext-html-webpack-plugin": "^1.7.1",
"serialize-javascript": "^1.3.0", "serialize-javascript": "^1.3.0",
"serve-static": "^1.12.1", "serve-static": "^1.12.2",
"url-loader": "^0.5.8", "url-loader": "^0.5.8",
"vue": "~2.2.6", "vue": "~2.3.1",
"vue-loader": "^11.3.4", "vue-loader": "^12.0.2",
"vue-meta": "^0.5.6", "vue-meta": "^0.5.6",
"vue-router": "^2.4.0", "vue-router": "^2.5.3",
"vue-server-renderer": "~2.2.6", "vue-server-renderer": "~2.3.1",
"vue-ssr-html-stream": "^2.2.0", "vue-ssr-html-stream": "^2.2.0",
"vue-ssr-webpack-plugin": "^3.0.0", "vue-template-compiler": "~2.3.1",
"vue-template-compiler": "~2.2.6", "vuex": "^2.3.1",
"vuex": "^2.3.0",
"webpack": "^2.4.1", "webpack": "^2.4.1",
"webpack-bundle-analyzer": "^2.4.0", "webpack-bundle-analyzer": "^2.4.0",
"webpack-dev-middleware": "^1.10.1", "webpack-dev-middleware": "^1.10.2",
"webpack-hot-middleware": "^2.18.0" "webpack-hot-middleware": "^2.18.0"
}, },
"devDependencies": { "devDependencies": {
"ava": "^0.19.1", "ava": "^0.19.1",
"babel-eslint": "^7.2.2", "babel-eslint": "^7.2.3",
"babel-plugin-array-includes": "^2.0.3", "babel-plugin-array-includes": "^2.0.3",
"babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",
@ -111,10 +111,10 @@
"eslint-plugin-node": "^4.2.2", "eslint-plugin-node": "^4.2.2",
"eslint-plugin-promise": "^3.5.0", "eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1", "eslint-plugin-standard": "^3.0.1",
"finalhandler": "^1.0.1", "finalhandler": "^1.0.2",
"jsdom": "^9.12.0", "jsdom": "^10.1.0",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"nyc": "^10.2.0", "nyc": "^10.3.0",
"request": "^2.81.0", "request": "^2.81.0",
"request-promise-native": "^1.0.3", "request-promise-native": "^1.0.3",
"webpack-node-externals": "^1.5.4" "webpack-node-externals": "^1.5.4"

View File

@ -17,12 +17,11 @@ test.before('Init Nuxt.js', async t => {
test('Check .nuxt/router.js', t => { test('Check .nuxt/router.js', t => {
return readFile(resolve(__dirname, './fixtures/dynamic-routes/.nuxt/router.js'), 'utf-8') return readFile(resolve(__dirname, './fixtures/dynamic-routes/.nuxt/router.js'), 'utf-8')
.then((routerFile) => { .then((routerFile) => {
routerFile = routerFile.slice( routerFile = routerFile
routerFile.indexOf('routes: ['), .slice(routerFile.indexOf('routes: ['))
-3 .replace('routes: [', '[')
) .replace(/ _[0-9A-z]+,/g, ' "",')
.replace('routes: [', '[') routerFile = routerFile.substr(routerFile.indexOf('['), routerFile.lastIndexOf(']') + 1)
.replace(/ _[0-9A-z]+,/g, ' "",')
let routes = eval('( ' + routerFile + ')') // eslint-disable-line no-eval let routes = eval('( ' + routerFile + ')') // eslint-disable-line no-eval
// pages/index.vue // pages/index.vue
t.is(routes[0].path, '/') t.is(routes[0].path, '/')

View File

@ -37,7 +37,7 @@ test('/ with renderAndGetWindow()', async t => {
await nuxt.renderAndGetWindow(url('/')) await nuxt.renderAndGetWindow(url('/'))
} catch (e) { } catch (e) {
t.true(e.message.includes('Could not load the nuxt app')) t.true(e.message.includes('Could not load the nuxt app'))
t.true(e.body.includes('not_defined is not defined')) t.true(e.body.includes('render function or template not defined in component'))
} }
}) })

View File

@ -1,5 +1,7 @@
export const state = { export const state = () => {
baz: 'Vuex Nested Modules' return {
baz: 'Vuex Nested Modules'
}
} }
export const getters = { export const getters = {

View File

@ -3,7 +3,7 @@ import Vuex from 'vuex'
Vue.use(Vuex) Vue.use(Vuex)
const store = new Vuex.Store({ const store = () => new Vuex.Store({
state: { state: {
counter: 0 counter: 0
}, },

View File

@ -82,7 +82,7 @@ test('/test/about-bis (added with extendRoutes)', async t => {
test('Check stats.json generated by build.analyze', t => { test('Check stats.json generated by build.analyze', t => {
const stats = require(resolve(__dirname, 'fixtures/with-config/.nuxt/dist/stats.json')) const stats = require(resolve(__dirname, 'fixtures/with-config/.nuxt/dist/stats.json'))
t.is(stats.assets.length, 26) t.is(stats.assets.length, 27)
}) })
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes

560
yarn.lock

File diff suppressed because it is too large Load Diff