diff --git a/.gitignore b/.gitignore
index 306525d111..8bc7941666 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
# dependencies
-yarn.lock
node_modules
+examples/**/*/yarn.lock
# logs
*.log
diff --git a/examples/async-data/pages/index.vue b/examples/async-data/pages/index.vue
index 387a5725e5..0dd6050846 100644
--- a/examples/async-data/pages/index.vue
+++ b/examples/async-data/pages/index.vue
@@ -9,7 +9,7 @@
+
+
diff --git a/examples/dynamic-layouts/layouts/mobile.vue b/examples/dynamic-layouts/layouts/mobile.vue
new file mode 100644
index 0000000000..db8735a00e
--- /dev/null
+++ b/examples/dynamic-layouts/layouts/mobile.vue
@@ -0,0 +1,37 @@
+
+ Hi from {{ name }}Welcome!
+
<%= ansiHTML(encodeHtml(err.stack)) %>+
{{ stack }}diff --git a/lib/webpack/base.config.js b/lib/webpack/base.config.js index 017f7e99ae..88e68ce6f5 100644 --- a/lib/webpack/base.config.js +++ b/lib/webpack/base.config.js @@ -3,7 +3,7 @@ import vueLoaderConfig from './vue-loader.config' import { defaults } from 'lodash' import { join } from 'path' -import { urlJoin } from '../utils' +import { isUrl, urlJoin } from '../utils' /* |-------------------------------------------------------------------------- @@ -16,14 +16,16 @@ import { urlJoin } from '../utils' export default function ({ isClient, isServer }) { const nodeModulesDir = join(__dirname, '..', 'node_modules') let config = { - devtool: 'source-map', + devtool: (this.dev ? 'cheap-module-eval-source-map' : false), entry: { vendor: ['vue', 'vue-router', 'vue-meta'] }, output: { - publicPath: urlJoin(this.options.router.base, '/_nuxt/') + publicPath: (isUrl(this.options.build.publicPath) ? this.options.build.publicPath : urlJoin(this.options.router.base, this.options.build.publicPath)) }, performance: { + maxEntrypointSize: 300000, + maxAssetSize: 300000, hints: (this.dev ? false : 'warning') }, resolve: { @@ -64,16 +66,13 @@ export default function ({ isClient, isServer }) { loader: 'babel-loader', exclude: /node_modules/, query: defaults(this.options.build.babel, { - plugins: [ - 'transform-async-to-generator', - 'transform-runtime' - ], - presets: [ - ['es2015', { modules: false }], - 'stage-2' - ], + presets: ['vue-app'], cacheDirectory: !!this.dev }) + }, + { + test: /\.css$/, + loader: 'vue-style-loader!css-loader' } ] }, diff --git a/lib/webpack/client.config.js b/lib/webpack/client.config.js index 8cee34438e..7e40a22d29 100644 --- a/lib/webpack/client.config.js +++ b/lib/webpack/client.config.js @@ -2,7 +2,10 @@ import { each } from 'lodash' import webpack from 'webpack' -import ExtractTextPlugin from 'extract-text-webpack-plugin' +import HTMLPlugin from 'html-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 { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' import base from './base.config.js' @@ -40,38 +43,55 @@ export default function () { }) // Webpack plugins config.plugins = (config.plugins || []).concat([ - // strip comments in Vue code + // Strip comments in Vue code new webpack.DefinePlugin(Object.assign(env, { 'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'), 'process.BROWSER_BUILD': true, - 'process.SERVER_BUILD': false + 'process.SERVER_BUILD': false, + 'process.browser': true, + 'process.server': true })), // Extract vendor chunks for better caching new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: this.options.build.filenames.vendor + }), + // Generate output HTML + new HTMLPlugin({ + template: this.options.appTemplatePath + }), + // Add defer to scripts + new ScriptExtHtmlWebpackPlugin({ + defaultAttribute: 'defer' }) ]) + if (!this.dev && this.options.performance.prefetch === true) { + // Add prefetch code-splitted routes + config.plugins.push( + new PreloadWebpackPlugin({ + rel: 'prefetch' + }) + ) + } // client bundle progress bar config.plugins.push( new ProgressBarPlugin() ) - + // Add friendly error plugin + if (this.dev) { + config.plugins.push(new FriendlyErrorsWebpackPlugin()) + } // Production client build if (!this.dev) { config.plugins.push( - // Use ExtractTextPlugin to extract CSS into a single file - new ExtractTextPlugin({ - filename: this.options.build.filenames.css, - allChunks: true - }), // This is needed in webpack 2 for minifying CSS new webpack.LoaderOptionsPlugin({ minimize: true }), // Minify JS new webpack.optimize.UglifyJsPlugin({ + sourceMap: true, compress: { warnings: false } diff --git a/lib/webpack/server.config.js b/lib/webpack/server.config.js index e8f1323f67..3a811d3cf2 100644 --- a/lib/webpack/server.config.js +++ b/lib/webpack/server.config.js @@ -1,6 +1,7 @@ 'use strict' import webpack from 'webpack' +import VueSSRPlugin from 'vue-ssr-webpack-plugin' import base from './base.config.js' import { each, uniq } from 'lodash' import { existsSync, readFileSync } from 'fs' @@ -22,7 +23,7 @@ export default function () { config = Object.assign(config, { target: 'node', - devtool: false, + devtool: 'source-map', entry: resolve(this.dir, '.nuxt', 'server.js'), output: Object.assign({}, config.output, { path: resolve(this.dir, '.nuxt', 'dist'), @@ -30,14 +31,26 @@ export default function () { libraryTarget: 'commonjs2' }), plugins: (config.plugins || []).concat([ + new VueSSRPlugin({ + filename: 'server-bundle.json' + }), new webpack.DefinePlugin(Object.assign(env, { 'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'), - 'process.BROWSER_BUILD': false, - 'process.SERVER_BUILD': true + 'process.BROWSER_BUILD': false, // deprecated + 'process.SERVER_BUILD': true, // deprecated + 'process.browser': false, + 'process.server': true })) ]) }) - + // This is needed in webpack 2 for minifying CSS + if (!this.dev) { + config.plugins.push( + new webpack.LoaderOptionsPlugin({ + minimize: true + }) + ) + } // Externals const nuxtPackageJson = require('../../package.json') const projectPackageJsonPath = resolve(this.dir, 'package.json') @@ -48,6 +61,7 @@ export default function () { config.externals = config.externals.concat(Object.keys(projectPackageJson.dependencies || {})) } catch (e) {} } + config.externals = config.externals.concat(this.options.build.vendor) config.externals = uniq(config.externals) // Extend config diff --git a/lib/webpack/vue-loader.config.js b/lib/webpack/vue-loader.config.js index b045d1a29c..7b431505ac 100644 --- a/lib/webpack/vue-loader.config.js +++ b/lib/webpack/vue-loader.config.js @@ -4,19 +4,14 @@ import { defaults } from 'lodash' export default function ({ isClient }) { let babelOptions = JSON.stringify(defaults(this.options.build.babel, { - plugins: [ - 'transform-async-to-generator', - 'transform-runtime' - ], - presets: [ - ['es2015', { modules: false }], - 'stage-2' - ] + presets: ['vue-app'], + cacheDirectory: !!this.dev })) let config = { postcss: this.options.build.postcss, loaders: { 'js': 'babel-loader?' + babelOptions, + 'css': 'vue-style-loader!css-loader', 'less': 'vue-style-loader!css-loader!less-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax', 'scss': 'vue-style-loader!css-loader!sass-loader', @@ -25,17 +20,6 @@ export default function ({ isClient }) { }, preserveWhitespace: false } - - if (!this.dev && isClient) { - // Use ExtractTextPlugin to extract CSS into a single file - const ExtractTextPlugin = require('extract-text-webpack-plugin') - config.loaders.css = ExtractTextPlugin.extract({ loader: 'css-loader' }) - config.loaders.scss = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader', fallbackLoader: 'vue-style-loader' }) - config.loaders.sass = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader?indentedSyntax', fallbackLoader: 'vue-style-loader' }) - config.loaders.stylus = ExtractTextPlugin.extract({ loader: 'css-loader!stylus-loader', fallbackLoader: 'vue-style-loader' }) - config.loaders.less = ExtractTextPlugin.extract({ loader: 'css-loader!less-loader', fallbackLoader: 'vue-style-loader' }) - } - // Return the config return config } diff --git a/package.json b/package.json index fb55baa258..1dcd8a050a 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "nuxt": "./bin/nuxt" }, "scripts": { - "test": "nyc ava --serial test/", + "test": "nyc ava --verbose --serial test/", "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", "lint": "eslint --ext .js,.vue bin lib pages test/*.js --ignore-pattern lib/app", "build": "webpack", @@ -52,63 +52,68 @@ }, "dependencies": { "ansi-html": "^0.0.7", - "autoprefixer": "^6.7.2", - "babel-core": "^6.22.1", - "babel-loader": "^6.2.10", - "babel-plugin-array-includes": "^2.0.3", - "babel-plugin-transform-async-to-generator": "^6.22.0", - "babel-plugin-transform-runtime": "^6.22.0", - "babel-preset-es2015": "^6.22.0", - "babel-preset-stage-2": "^6.22.0", - "chalk": "^1.1.3", + "autoprefixer": "^6.7.7", + "babel-core": "^6.24.0", + "babel-loader": "^6.4.1", + "babel-preset-vue-app": "^0.5.1", "chokidar": "^1.6.1", "co": "^4.6.0", - "css-loader": "^0.26.1", - "debug": "^2.6.1", - "extract-text-webpack-plugin": "2.0.0-beta.4", - "file-loader": "^0.10.0", - "fs-extra": "^2.0.0", + "compression": "^1.6.2", + "css-loader": "^0.27.3", + "debug": "^2.6.3", + "file-loader": "^0.10.1", + "friendly-errors-webpack-plugin": "^1.6.1", + "fs-extra": "^2.1.2", "glob": "^7.1.1", "hash-sum": "^1.0.2", - "html-minifier": "^3.3.1", + "html-minifier": "^3.4.2", + "html-webpack-plugin": "^2.28.0", "lodash": "^4.17.4", "lru-cache": "^4.0.2", "memory-fs": "^0.4.1", - "path-to-regexp": "^1.7.0", "pify": "^2.3.0", - "post-compile-webpack-plugin": "^0.1.1", + "preload-webpack-plugin": "^1.2.1", "progress-bar-webpack-plugin": "^1.9.3", + "script-ext-html-webpack-plugin": "^1.7.1", "serialize-javascript": "^1.3.0", - "serve-static": "^1.11.2", - "url-loader": "^0.5.7", - "vue": "^2.1.10", - "vue-loader": "^10.3.0", - "vue-meta": "^0.5.3", - "vue-router": "^2.2.0", - "vue-server-renderer": "^2.1.10", + "serve-static": "^1.12.1", + "url-loader": "^0.5.8", + "vue": "^2.2.5", + "vue-loader": "^11.3.3", + "vue-meta": "^0.5.5", + "vue-router": "^2.3.0", + "vue-server-renderer": "^2.2.5", "vue-ssr-html-stream": "^2.2.0", - "vue-template-compiler": "^2.1.10", - "vuex": "^2.1.2", - "webpack": "^2.2.1", - "webpack-bundle-analyzer": "^2.2.3", - "webpack-dev-middleware": "^1.10.0", - "webpack-hot-middleware": "^2.16.1" + "vue-ssr-webpack-plugin": "^1.0.2", + "vue-template-compiler": "^2.2.5", + "vuex": "^2.2.1", + "webpack": "^2.3.1", + "webpack-bundle-analyzer": "^2.3.1", + "webpack-dev-middleware": "^1.10.1", + "webpack-hot-middleware": "^2.17.1" }, "devDependencies": { - "ava": "^0.18.1", - "babel-eslint": "^7.1.1", - "codecov": "^1.0.1", + "ava": "^0.18.2", + "babel-eslint": "^7.2.1", + "babel-plugin-array-includes": "^2.0.3", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-preset-es2015": "^6.24.0", + "babel-preset-stage-2": "^6.22.0", + "codecov": "^2.1.0", "copy-webpack-plugin": "^4.0.1", - "eslint": "^3.15.0", - "eslint-config-standard": "^6.2.1", - "eslint-plugin-html": "^2.0.0", - "eslint-plugin-promise": "^3.4.1", - "eslint-plugin-standard": "^2.0.1", - "finalhandler": "^0.5.1", - "jsdom": "^9.10.0", + "eslint": "^3.18.0", + "eslint-config-standard": "^8.0.0-beta.2", + "eslint-plugin-html": "^2.0.1", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-node": "^4.2.1", + "eslint-plugin-promise": "^3.5.0", + "eslint-plugin-standard": "^2.1.1", + "finalhandler": "^1.0.1", + "jsdom": "^9.12.0", "json-loader": "^0.5.4", - "nyc": "^10.1.2", - "request": "^2.79.0", + "nyc": "^10.2.0-candidate.0", + "request": "^2.81.0", "request-promise-native": "^1.0.3", "webpack-node-externals": "^1.5.4" } diff --git a/test/basic.fail.generate.test.js b/test/basic.fail.generate.test.js index 034a6f3365..bdb3552f98 100644 --- a/test/basic.fail.generate.test.js +++ b/test/basic.fail.generate.test.js @@ -1,42 +1,16 @@ import test from 'ava' import { resolve } from 'path' -test('Fail to generate without routeParams', t => { - const Nuxt = require('../') - const options = { - rootDir: resolve(__dirname, 'fixtures/basic'), - dev: false - // no generate.routeParams - } - const nuxt = new Nuxt(options) - return new Promise((resolve) => { - var oldExit = process.exit - var oldCE = console.error // eslint-disable-line no-console - var _log = '' - console.error = (s) => { _log += s } // eslint-disable-line no-console - process.exit = (code) => { - process.exit = oldExit - console.error = oldCE // eslint-disable-line no-console - t.is(code, 1) - t.true(_log.includes('Could not generate the dynamic route /users/:id')) - resolve() - } - nuxt.generate() - }) -}) - -test('Fail with routeParams which throw an error', t => { +test('Fail with routes() which throw an error', t => { const Nuxt = require('../') const options = { rootDir: resolve(__dirname, 'fixtures/basic'), dev: false, generate: { - routeParams: { - '/users/:id': function () { - return new Promise((resolve, reject) => { - reject('Not today!') - }) - } + routes: function () { + return new Promise((resolve, reject) => { + reject(new Error('Not today!')) + }) } } } @@ -50,12 +24,12 @@ test('Fail with routeParams which throw an error', t => { process.exit = oldExit console.error = oldCE // eslint-disable-line no-console t.is(code, 1) - t.true(_log.includes('Could not resolve routeParams[/users/:id]')) + t.true(_log.includes('Could not resolve routes')) resolve() } nuxt.generate() .catch((e) => { - t.true(e === 'Not today!') + t.true(e.message === 'Not today!') }) }) }) diff --git a/test/fixtures/basic/nuxt.config.js b/test/fixtures/basic/nuxt.config.js index 1793846e1f..19c0433cab 100644 --- a/test/fixtures/basic/nuxt.config.js +++ b/test/fixtures/basic/nuxt.config.js @@ -1,11 +1,9 @@ module.exports = { generate: { - routeParams: { - '/users/:id': [ - { id: 1 }, - { id: 2 }, - { id: 3 } - ] - } + routes: [ + '/users/1', + '/users/2', + '/users/3' + ] } } diff --git a/test/fixtures/basic/pages/async-data.vue b/test/fixtures/basic/pages/async-data.vue index 6eec4d9466..f999fb6871 100755 --- a/test/fixtures/basic/pages/async-data.vue +++ b/test/fixtures/basic/pages/async-data.vue @@ -4,7 +4,7 @@