Merge branch 'dev' of github.com:nuxt/nuxt.js into dev

This commit is contained in:
Sebastien Chopin 2017-07-11 12:45:01 +02:00
commit eedd1137b6
10 changed files with 152 additions and 71 deletions

1
lib/app/empty.js Normal file
View File

@ -0,0 +1 @@
// This file is intentially left empty for noop aliases

View File

@ -9,40 +9,16 @@ import Nuxt from './components/nuxt.vue'
import App from '<%= appPath %>' import App from '<%= appPath %>'
import { getContext } from './utils' import { getContext } from './utils'
<% if (store) { %>import { createStore } from './store.js'<% } %> <% if (store) { %>import { createStore } from './store.js'<% } %>
<% plugins.forEach(plugin => { %>import <%= plugin.name %> from '<%= plugin.name %>'
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 %>
<% }) %> <% }) %>
<% } %>
<% 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: <nuxt-child> // Component: <nuxt-child>
Vue.component(NuxtChild.name, NuxtChild) Vue.component(NuxtChild.name, NuxtChild)
// Component: <nuxt-link> // Component: <nuxt-link>
Vue.component(NuxtLink.name, NuxtLink) Vue.component(NuxtLink.name, NuxtLink)
// Component: <nuxt>
// Component: <nuxt>`
Vue.component(Nuxt.name, Nuxt) Vue.component(Nuxt.name, Nuxt)
// vue-meta configuration // vue-meta configuration
@ -132,24 +108,17 @@ async function createApp (ssrContext) {
route: router.currentRoute, route: router.currentRoute,
next, next,
error: app._nuxt.error.bind(app), error: app._nuxt.error.bind(app),
<% if(store) { %> store,<% } %> <% if(store) { %>store,<% } %>
req: ssrContext ? ssrContext.req : undefined, req: ssrContext ? ssrContext.req : undefined,
res: ssrContext ? ssrContext.res : undefined, res: ssrContext ? ssrContext.res : undefined,
}, app) }, app)
<% if (plugins.filter(p => p.ssr).length) { %>
<% plugins.filter(p => p.ssr).forEach(plugin => { %> <% 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 (plugins.filter(p => !p.ssr).length) { %>
if (process.browser) { if (process.browser) { <% plugins.filter(p => !p.ssr).forEach(plugin => { %>
<% 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) }<% } %>
<% }) %>
}
<% } %>
return { return {
app, app,

View File

@ -2,6 +2,15 @@ import Vue from 'vue'
const noopData = () => ({}) 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 = {}) { export function applyAsyncData (Component, asyncData = {}) {
const ComponentData = Component.options.data || noopData const ComponentData = Component.options.data || noopData
// Prevent calling this method for each request on SSR context // Prevent calling this method for each request on SSR context

View File

@ -49,6 +49,14 @@ export default class Builder extends Tapable {
this._buildStatus = STATUS.INITIAL 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 () { async build () {
// Avoid calling build() method multiple times when dev:true // Avoid calling build() method multiple times when dev:true
/* istanbul ignore if */ /* istanbul ignore if */
@ -119,6 +127,7 @@ export default class Builder extends Tapable {
'router.js', 'router.js',
'server.js', 'server.js',
'utils.js', 'utils.js',
'empty.js',
'components/nuxt-error.vue', 'components/nuxt-error.vue',
'components/nuxt-loading.vue', 'components/nuxt-loading.vue',
'components/nuxt-child.js', 'components/nuxt-child.js',
@ -137,11 +146,7 @@ export default class Builder extends Tapable {
middleware: fs.existsSync(join(this.options.srcDir, 'middleware')), middleware: fs.existsSync(join(this.options.srcDir, 'middleware')),
store: this.options.store, store: this.options.store,
css: this.options.css, css: this.options.css,
plugins: this.options.plugins.map((p, i) => { plugins: this.plugins,
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}` }
}),
appPath: './App.vue', appPath: './App.vue',
layouts: Object.assign({}, this.options.layouts), layouts: Object.assign({}, this.options.layouts),
loading: typeof this.options.loading === 'string' ? this.relativeToBuild(this.options.srcDir, this.options.loading) : this.options.loading, loading: typeof this.options.loading === 'string' ? this.relativeToBuild(this.options.srcDir, this.options.loading) : this.options.loading,
@ -264,15 +269,33 @@ export default class Builder extends Tapable {
async webpackBuild () { async webpackBuild () {
debug('Building files...') debug('Building files...')
let compilersOptions = [] const compilersOptions = []
// Client // Client
let clientConfig = clientWebpackConfig.call(this) const clientConfig = clientWebpackConfig.call(this)
compilersOptions.push(clientConfig) compilersOptions.push(clientConfig)
// Server // Server
let serverConfig = serverWebpackConfig.call(this) 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 => {
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 // Simulate webpack multi compiler interface
// Separate compilers are simpler, safer and faster // Separate compilers are simpler, safer and faster

View File

@ -60,11 +60,23 @@ export default function webpackClientConfig () {
config.plugins = [] 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( config.plugins.push(
new HTMLPlugin({ new HTMLPlugin({
filename: 'index.spa.html',
template: this.options.appTemplatePath, template: this.options.appTemplatePath,
inject: this.options.ssr === false, inject: true,
chunksSortMode: 'dependency' chunksSortMode: 'dependency'
}) })
) )

View File

@ -46,16 +46,57 @@ export default function Options (_options) {
options.store = true 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 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 = { export const defaultOptions = {
dev: (process.env.NODE_ENV !== 'production'), mode: 'universal',
dev: process.env.NODE_ENV !== 'production',
buildDir: '.nuxt', buildDir: '.nuxt',
nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist
build: { build: {
analyze: false, analyze: false,
extractCSS: false, extractCSS: false,
ssr: undefined,
publicPath: '/_nuxt/', publicPath: '/_nuxt/',
filenames: { filenames: {
css: 'common.[chunkhash].css', css: 'common.[chunkhash].css',
@ -132,10 +173,10 @@ export const defaultOptions = {
scrollBehavior: null, scrollBehavior: null,
fallback: false fallback: false
}, },
ssr: true,
render: { render: {
bundleRenderer: {}, bundleRenderer: {},
resourceHints: true, resourceHints: true,
ssr: undefined,
http2: { http2: {
push: false push: false
}, },

View File

@ -42,7 +42,8 @@ export default class Renderer extends Tapable {
this.resources = { this.resources = {
clientManifest: null, clientManifest: null,
serverBundle: null, serverBundle: null,
appTemplate: null, ssrTemplate: null,
spaTemplate: null,
errorTemplate: parseTemplate('<pre>{{ stack }}</pre>') // Will be loaded on ready errorTemplate: parseTemplate('<pre>{{ stack }}</pre>') // Will be loaded on ready
} }
@ -121,7 +122,18 @@ export default class Renderer extends Tapable {
} }
get noSSR () { 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 () { createRenderer () {
@ -297,7 +309,7 @@ export default class Renderer extends Tapable {
async renderRoute (url, context = {}) { async renderRoute (url, context = {}) {
/* istanbul ignore if */ /* istanbul ignore if */
if (!(this.noSSR || this.bundleRenderer) || !this.resources.appTemplate) { if (!this.isReady) {
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(() => resolve(this.renderRoute(url, context)), 1000) setTimeout(() => resolve(this.renderRoute(url, context)), 1000)
}) })
@ -315,7 +327,7 @@ export default class Renderer extends Tapable {
let APP = '<div id="__nuxt"></div>' let APP = '<div id="__nuxt"></div>'
let HEAD = '' let HEAD = ''
let html = this.resources.appTemplate({ let html = this.resources.spaTemplate({
HTML_ATTRS: '', HTML_ATTRS: '',
BODY_ATTRS: '', BODY_ATTRS: '',
HEAD, HEAD,
@ -340,15 +352,19 @@ export default class Renderer extends Tapable {
} }
let resourceHints = '' let resourceHints = ''
if (this.options.render.resourceHints) {
resourceHints = context.renderResourceHints()
HEAD += resourceHints
}
HEAD += context.renderStyles()
APP += `<script type="text/javascript">window.__NUXT__=${serialize(context.nuxt, { isJSON: true })};</script>`
APP += context.renderScripts()
let html = this.resources.appTemplate({ if (!this.staticSSR) {
if (this.options.render.resourceHints) {
resourceHints = context.renderResourceHints()
HEAD += resourceHints
}
APP += `<script type="text/javascript">window.__NUXT__=${serialize(context.nuxt, { isJSON: true })};</script>`
APP += context.renderScripts()
}
HEAD += context.renderStyles()
let html = this.resources.ssrTemplate({
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(),
HEAD, HEAD,
@ -422,8 +438,13 @@ const resourceMap = [
transform: JSON.parse transform: JSON.parse
}, },
{ {
key: 'appTemplate', key: 'ssrTemplate',
fileName: 'index.html', fileName: 'index.ssr.html',
transform: parseTemplate
},
{
key: 'spaTemplate',
fileName: 'index.spa.html',
transform: parseTemplate transform: parseTemplate
} }
] ]

View File

@ -7,3 +7,5 @@ function $reverseStr (str) {
} }
Vue.prototype.$reverseStr = $reverseStr Vue.prototype.$reverseStr = $reverseStr
export default undefined

View File

@ -3,18 +3,21 @@ import Router from 'vue-router'
Vue.use(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 () { export function createRouter () {
return new Router({ return new Router({
mode: 'history', mode: 'history',
routes: [ routes: [
{ {
path: '/', path: '/',
component: require('~/views/index.vue').default, component: indexPage,
name: 'index' name: 'index'
}, },
{ {
path: '/about', path: '/about',
component: require('~/views/about.vue').default, component: aboutPage,
name: 'about' name: 'about'
} }
] ]

View File

@ -90,7 +90,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)
}) })
test('Check /test.txt with custom serve-static options', async t => { test('Check /test.txt with custom serve-static options', async t => {