From 3ef0d15f6b3ae714f0f9c22cad1b73c69144949b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 11 Jul 2017 03:23:06 +0430 Subject: [PATCH 1/4] perf: use es6 import for plugins --- lib/app/empty.js | 1 + lib/app/index.js | 51 +++++++++--------------------------------- lib/app/utils.js | 9 ++++++++ lib/builder/builder.js | 37 +++++++++++++++++++++++------- 4 files changed, 49 insertions(+), 49 deletions(-) create mode 100644 lib/app/empty.js diff --git a/lib/app/empty.js b/lib/app/empty.js new file mode 100644 index 0000000000..d96e1063fb --- /dev/null +++ b/lib/app/empty.js @@ -0,0 +1 @@ +// This file is intentially left empty for noop aliases diff --git a/lib/app/index.js b/lib/app/index.js index 09a0cf44a1..6a42150fb2 100644 --- a/lib/app/index.js +++ b/lib/app/index.js @@ -9,40 +9,16 @@ import Nuxt from './components/nuxt.vue' import App from '<%= appPath %>' import { getContext } from './utils' <% if (store) { %>import { createStore } from './store.js'<% } %> - -if (process.browser) { - // 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) - } -} - -<% if (plugins.filter(p => p.ssr).length) { %> -// Require plugins -<% plugins.filter(p => p.ssr).forEach(plugin => { %> -let <%= plugin.name %> = require('<%= relativeToBuild(plugin.src) %>') -<%= plugin.name %> = <%= plugin.name %>.default || <%= plugin.name %> +<% plugins.forEach(plugin => { %>import <%= plugin.name %> from '<%= plugin.name %>' <% }) %> -<% } %> - -<% if (plugins.filter(p => !p.ssr).length) { %> -// Require browser-only plugins -if (process.browser) { - <% plugins.filter(p => !p.ssr).forEach(plugin => { %> - let <%= plugin.name %> = require('<%= relativeToBuild(plugin.src) %>') - <%= plugin.name %> = <%= plugin.name %>.default || <%= plugin.name %> - <% }) %> -} -<% } %> - // Component: Vue.component(NuxtChild.name, NuxtChild) + // Component: Vue.component(NuxtLink.name, NuxtLink) -// Component: + +// Component: ` Vue.component(Nuxt.name, Nuxt) // vue-meta configuration @@ -132,24 +108,17 @@ async function createApp (ssrContext) { route: router.currentRoute, next, error: app._nuxt.error.bind(app), - <% if(store) { %> store,<% } %> + <% if(store) { %>store,<% } %> req: ssrContext ? ssrContext.req : undefined, res: ssrContext ? ssrContext.res : undefined, }, app) - <% if (plugins.filter(p => p.ssr).length) { %> <% plugins.filter(p => p.ssr).forEach(plugin => { %> - if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx) - <% }) %> - <% } %> - + if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx)<% }) %> <% if (plugins.filter(p => !p.ssr).length) { %> - if (process.browser) { - <% plugins.filter(p => !p.ssr).forEach(plugin => { %> - if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx) - <% }) %> - } - <% } %> + if (process.browser) { <% plugins.filter(p => !p.ssr).forEach(plugin => { %> + if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx)<% }) %> + }<% } %> return { app, @@ -158,4 +127,4 @@ async function createApp (ssrContext) { } } -export { createApp, NuxtError } +export { createApp, NuxtError } \ No newline at end of file diff --git a/lib/app/utils.js b/lib/app/utils.js index 0529c9b9bc..6771b61686 100644 --- a/lib/app/utils.js +++ b/lib/app/utils.js @@ -2,6 +2,15 @@ import Vue from 'vue' const noopData = () => ({}) +// window.onNuxtReady(() => console.log('Ready')) hook +// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading) +if (process.browser) { + window._nuxtReadyCbs = [] + window.onNuxtReady = function (cb) { + window._nuxtReadyCbs.push(cb) + } +} + export function applyAsyncData (Component, asyncData = {}) { const ComponentData = Component.options.data || noopData // Prevent calling this method for each request on SSR context diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 08352af0bd..f6681dd340 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -49,6 +49,14 @@ export default class Builder extends Tapable { this._buildStatus = STATUS.INITIAL } + get plugins () { + return this.options.plugins.map((p, i) => { + if (typeof p === 'string') p = { src: p } + p.src = r(this.options.srcDir, p.src) + return { src: p.src, ssr: (p.ssr !== false), name: `plugin${i}` } + }) + } + async build () { // Avoid calling build() method multiple times when dev:true /* istanbul ignore if */ @@ -119,6 +127,7 @@ export default class Builder extends Tapable { 'router.js', 'server.js', 'utils.js', + 'empty.js', 'components/nuxt-error.vue', 'components/nuxt-loading.vue', 'components/nuxt-child.js', @@ -137,11 +146,7 @@ export default class Builder extends Tapable { middleware: fs.existsSync(join(this.options.srcDir, 'middleware')), store: this.options.store, css: this.options.css, - plugins: this.options.plugins.map((p, i) => { - if (typeof p === 'string') p = { src: p } - p.src = r(this.options.srcDir, p.src) - return { src: p.src, ssr: (p.ssr !== false), name: `plugin${i}` } - }), + plugins: this.plugins, appPath: './App.vue', layouts: Object.assign({}, this.options.layouts), loading: typeof this.options.loading === 'string' ? this.relativeToBuild(this.options.srcDir, this.options.loading) : this.options.loading, @@ -264,16 +269,32 @@ export default class Builder extends Tapable { async webpackBuild () { debug('Building files...') - let compilersOptions = [] + const compilersOptions = [] // Client - let clientConfig = clientWebpackConfig.call(this) + const clientConfig = clientWebpackConfig.call(this) compilersOptions.push(clientConfig) // Server - let serverConfig = serverWebpackConfig.call(this) + const serverConfig = serverWebpackConfig.call(this) compilersOptions.push(serverConfig) + // Alias plugins to their real path + this.plugins.forEach(p => { + const src = this.relativeToBuild(p.src) + + // Client config + if (!clientConfig.resolve.alias[p.name]) { + clientConfig.resolve.alias[p.name] = src + } + + // Server config + if (!serverConfig.resolve.alias[p.name]) { + // Alias to noop for ssr:false plugins + serverConfig.resolve.alias[p.name] = p.ssr ? src : './empty.js' + } + }) + // Simulate webpack multi compiler interface // Separate compilers are simpler, safer and faster this.compiler = { compilers: [] } From a3be3cfe1bca415846d3e9abdafd3dad906376c6 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 11 Jul 2017 04:54:39 +0430 Subject: [PATCH 2/4] feat: mode option --- lib/builder/builder.js | 4 ++- lib/builder/webpack/client.config.js | 16 +++++++-- lib/core/options.js | 45 +++++++++++++++++++++++-- lib/core/renderer.js | 49 ++++++++++++++++++++-------- 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/lib/builder/builder.js b/lib/builder/builder.js index f6681dd340..34e7b24dc6 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -277,7 +277,9 @@ export default class Builder extends Tapable { // Server const serverConfig = serverWebpackConfig.call(this) - compilersOptions.push(serverConfig) + if (this.options.build.ssr) { + compilersOptions.push(serverConfig) + } // Alias plugins to their real path this.plugins.forEach(p => { diff --git a/lib/builder/webpack/client.config.js b/lib/builder/webpack/client.config.js index 9e9834fdfd..953e51a753 100644 --- a/lib/builder/webpack/client.config.js +++ b/lib/builder/webpack/client.config.js @@ -60,11 +60,23 @@ export default function webpackClientConfig () { config.plugins = [] } - // Generate output HTML + // Generate output HTML for SSR + if (this.options.build.ssr) { + config.plugins.push( + new HTMLPlugin({ + filename: 'index.ssr.html', + template: this.options.appTemplatePath, + inject: false // Resources will be injected using bundleRenderer + }) + ) + } + + // Generate output HTML for SPA config.plugins.push( new HTMLPlugin({ + filename: 'index.spa.html', template: this.options.appTemplatePath, - inject: this.options.ssr === false, + inject: true, chunksSortMode: 'dependency' }) ) diff --git a/lib/core/options.js b/lib/core/options.js index 2887927622..da54f205f3 100755 --- a/lib/core/options.js +++ b/lib/core/options.js @@ -46,16 +46,57 @@ export default function Options (_options) { options.store = true } + // Resolve mode + let mode = options.mode + if (typeof mode === 'function') { + mode = mode() + } + if (typeof mode === 'string') { + mode = Modes[mode] + } + + // Apply mode + _.defaultsDeep(options, mode) + return options } +const Modes = { + universal: { + build: { + ssr: true + }, + render: { + ssr: true + } + }, + spa: { + build: { + ssr: false + }, + render: { + ssr: false + } + }, + static: { + build: { + ssr: true + }, + render: { + ssr: 'static' + } + } +} + export const defaultOptions = { - dev: (process.env.NODE_ENV !== 'production'), + mode: 'universal', + dev: process.env.NODE_ENV !== 'production', buildDir: '.nuxt', nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist build: { analyze: false, extractCSS: false, + ssr: undefined, publicPath: '/_nuxt/', filenames: { css: 'common.[chunkhash].css', @@ -133,10 +174,10 @@ export const defaultOptions = { scrollBehavior: null, fallback: false }, - ssr: true, render: { bundleRenderer: {}, resourceHints: true, + ssr: undefined, http2: { push: false }, diff --git a/lib/core/renderer.js b/lib/core/renderer.js index 07625b340c..8de1e42d87 100644 --- a/lib/core/renderer.js +++ b/lib/core/renderer.js @@ -42,7 +42,8 @@ export default class Renderer extends Tapable { this.resources = { clientManifest: null, serverBundle: null, - appTemplate: null, + ssrTemplate: null, + spaTemplate: null, errorTemplate: parseTemplate('
{{ stack }}
') // Will be loaded on ready } @@ -121,7 +122,18 @@ export default class Renderer extends Tapable { } get noSSR () { - return this.options.ssr === false + return this.options.render.ssr === false + } + + get staticSSR () { + return this.options.render.ssr === 'static' + } + + get isReady () { + if (this.noSSR) { + return this.resources.spaTemplate + } + return this.bundleRenderer && this.resources.ssrTemplate } createRenderer () { @@ -297,7 +309,7 @@ export default class Renderer extends Tapable { async renderRoute (url, context = {}) { /* istanbul ignore if */ - if (!(this.noSSR || this.bundleRenderer) || !this.resources.appTemplate) { + if (!this.isReady) { return new Promise(resolve => { setTimeout(() => resolve(this.renderRoute(url, context)), 1000) }) @@ -315,7 +327,7 @@ export default class Renderer extends Tapable { let APP = '
' let HEAD = '' - let html = this.resources.appTemplate({ + let html = this.resources.spaTemplate({ HTML_ATTRS: '', BODY_ATTRS: '', HEAD, @@ -340,15 +352,19 @@ export default class Renderer extends Tapable { } let resourceHints = '' - if (this.options.render.resourceHints) { - resourceHints = context.renderResourceHints() - HEAD += resourceHints - } - HEAD += context.renderStyles() - APP += `` - APP += context.renderScripts() - let html = this.resources.appTemplate({ + if (!this.staticSSR) { + if (this.options.render.resourceHints) { + resourceHints = context.renderResourceHints() + HEAD += resourceHints + } + APP += `` + APP += context.renderScripts() + } + + HEAD += context.renderStyles() + + let html = this.resources.ssrTemplate({ HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(), BODY_ATTRS: m.bodyAttrs.text(), HEAD, @@ -422,8 +438,13 @@ const resourceMap = [ transform: JSON.parse }, { - key: 'appTemplate', - fileName: 'index.html', + key: 'ssrTemplate', + fileName: 'index.ssr.html', + transform: parseTemplate + }, + { + key: 'spaTemplate', + fileName: 'index.spa.html', transform: parseTemplate } ] From 107d36c8512cd6b72d4c3fd7d6aad84bb62c071e Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 11 Jul 2017 13:33:29 +0430 Subject: [PATCH 3/4] tests: update module fixture --- test/fixtures/module/modules/basic/reverse.js | 2 ++ test/fixtures/module/router.js | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/fixtures/module/modules/basic/reverse.js b/test/fixtures/module/modules/basic/reverse.js index 39e01fba3b..5955969033 100755 --- a/test/fixtures/module/modules/basic/reverse.js +++ b/test/fixtures/module/modules/basic/reverse.js @@ -7,3 +7,5 @@ function $reverseStr (str) { } Vue.prototype.$reverseStr = $reverseStr + +export default undefined diff --git a/test/fixtures/module/router.js b/test/fixtures/module/router.js index 6fe636aeab..4856380eca 100644 --- a/test/fixtures/module/router.js +++ b/test/fixtures/module/router.js @@ -3,18 +3,21 @@ import Router from 'vue-router' Vue.use(Router) +const indexPage = () => import('~/views/index.vue').then(m => m.default || m) +const aboutPage = () => import('~/views/about.vue').then(m => m.default || m) + export function createRouter () { return new Router({ mode: 'history', routes: [ { path: '/', - component: require('~/views/index.vue').default, + component: indexPage, name: 'index' }, { path: '/about', - component: require('~/views/about.vue').default, + component: aboutPage, name: 'about' } ] From 75d933aac6501dfd777c605a8de08665b32b1946 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 11 Jul 2017 13:40:46 +0430 Subject: [PATCH 4/4] tests: update with-config Two index files are generated now so increase assets.length by 1 --- test/with-config.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/with-config.test.js b/test/with-config.test.js index 852ed60625..83b6747a8e 100644 --- a/test/with-config.test.js +++ b/test/with-config.test.js @@ -90,7 +90,7 @@ test('/test/about-bis (added with extendRoutes)', async t => { test('Check stats.json generated by build.analyze', t => { const stats = require(resolve(__dirname, 'fixtures/with-config/.nuxt/dist/stats.json')) - t.is(stats.assets.length, 26) + t.is(stats.assets.length, 27) }) test('Check /test.txt with custom serve-static options', async t => {