Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vue-renderer): improvements #4722

Merged
merged 25 commits into from
Feb 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/core/src/nuxt.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export default class Nuxt extends Hookable {

// Deprecated hooks
pi0 marked this conversation as resolved.
Show resolved Hide resolved
this._deprecatedHooks = {
'render:context': 'render:routeContext', // #3773
'render:context': 'render:routeContext',
'render:routeContext': 'vue-renderer:afterRender',
'showReady': 'webpack:done' // Workaround to deprecate showReady
}

Expand Down
2 changes: 2 additions & 0 deletions packages/core/test/nuxt.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe('core: nuxt', () => {

expect(nuxt._deprecatedHooks).toEqual({
'render:context': 'render:routeContext',
'render:routeContext': 'vue-renderer:afterRender',
'showReady': 'webpack:done'
})

Expand All @@ -56,6 +57,7 @@ describe('core: nuxt', () => {
expect(nuxt.ready).toBeCalledTimes(1)
})

// TODO: Remove in next major release
test('should call hook webpack:done in showReady', () => {
const nuxt = new Nuxt()
nuxt.callHook = jest.fn()
Expand Down
3 changes: 2 additions & 1 deletion packages/vue-app/template/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ async function createApp(ssrContext) {
payload: ssrContext ? ssrContext.payload : undefined,
req: ssrContext ? ssrContext.req : undefined,
res: ssrContext ? ssrContext.res : undefined,
beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined
beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined,
TheAlexLichter marked this conversation as resolved.
Show resolved Hide resolved
ssrContext
})

<% if (plugins.length) { %>
Expand Down
11 changes: 9 additions & 2 deletions packages/vue-app/template/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,15 @@ export async function setContext(app, context) {
env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
}
// Only set once
if (context.req) app.context.req = context.req
if (context.res) app.context.res = context.res
if (context.req) {
app.context.req = context.req
}
if (context.res) {
app.context.res = context.res
}
if (context.ssrContext) {
app.context.ssrContext = context.ssrContext
}
app.context.redirect = (status, path, query) => {
if (!status) {
return
Expand Down
161 changes: 106 additions & 55 deletions packages/vue-renderer/src/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export default class VueRenderer {
spaTemplate: undefined,
errorTemplate: this.parseTemplate('Nuxt.js Internal Server Error')
})

// Keep time of last shown messages
this._lastWaitingForResource = new Date()
}

get assetsMapping() {
Expand Down Expand Up @@ -110,7 +113,6 @@ export default class VueRenderer {

async ready() {
// -- Development mode --

if (this.context.options.dev) {
this.context.nuxt.hook('build:resources', mfs => this.loadResources(mfs, true))
return
Expand All @@ -121,7 +123,7 @@ export default class VueRenderer {
// Try once to load SSR resources from fs
await this.loadResources(fs)

// Without using`nuxt start` (Programatic, Tests and Generate)
// Without using `nuxt start` (Programatic, Tests and Generate)
if (!this.context.options._start) {
this.context.nuxt.hook('build:resources', () => this.loadResources(fs))
}
Expand Down Expand Up @@ -296,64 +298,55 @@ export default class VueRenderer {
return fn(opts)
}

async renderRoute(url, context = {}, retries = 5) {
/* istanbul ignore if */
if (!this.isReady) {
if (this.context.options.dev && retries > 0) {
consola.info('Waiting for server resources...')
await waitFor(1000)
return this.renderRoute(url, context, retries - 1)
} else {
throw new Error('Server resources are not available!')
}
async renderSPA(context) {
const content = await this.renderer.spa.render(context)

const APP =
`<div id="${this.context.globals.id}">${this.context.resources.loadingHTML}</div>` +
content.BODY_SCRIPTS

// Prepare template params
const templateParams = {
...content,
APP,
ENV: this.context.options.env
}

// Log rendered url
consola.debug(`Rendering url ${url}`)
// Call spa:templateParams hook
this.context.nuxt.callHook('vue-renderer:spa:templateParams', templateParams)

// Add url and isSever to the context
context.url = url
// Render with SPA template
const html = this.renderTemplate(false, templateParams)

// Basic response if SSR is disabled or SPA data provided
const { req, res } = context
const spa = context.spa || (res && res.spa)
const ENV = this.context.options.env

if (!this.SSR || spa) {
const {
HTML_ATTRS,
HEAD_ATTRS,
BODY_ATTRS,
HEAD,
BODY_SCRIPTS,
getPreloadFiles
} = await this.renderer.spa.render(context)
const APP =
`<div id="${this.context.globals.id}">${this.context.resources.loadingHTML}</div>` + BODY_SCRIPTS

const html = this.renderTemplate(false, {
HTML_ATTRS,
HEAD_ATTRS,
BODY_ATTRS,
HEAD,
APP,
ENV
return {
html,
getPreloadFiles: this.getPreloadFiles.bind(this, {
getPreloadFiles: content.getPreloadFiles
})

return { html, getPreloadFiles: this.getPreloadFiles.bind(this, { getPreloadFiles }) }
}
}

let APP
async renderSSR(context) {
// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
if (req && req.modernMode) {
APP = await this.renderer.modern.renderToString(context)
} else {
APP = await this.renderer.ssr.renderToString(context)
}
const renderer = context.modern ? this.renderer.modern : this.renderer.ssr

// Call ssr:context hook to extend context from modules
await this.context.nuxt.callHook('vue-renderer:ssr:prepareContext', context)

// Call Vue renderer renderToString
let APP = await renderer.renderToString(context)

// Call ssr:context hook
await this.context.nuxt.callHook('vue-renderer:ssr:context', context)
// TODO: Remove in next major release
await this.context.nuxt.callHook('render:routeContext', context.nuxt)

// Fallback to empty response
if (!context.nuxt.serverRendered) {
APP = `<div id="${this.context.globals.id}"></div>`
}

// Inject head meta
const m = context.meta.inject()
let HEAD =
m.title.text() +
Expand All @@ -362,18 +355,25 @@ export default class VueRenderer {
m.style.text() +
m.script.text() +
m.noscript.text()

// Add <base href=""> meta if router base specified
if (this.context.options._routerBaseSpecified) {
HEAD += `<base href="${this.context.options.router.base}">`
}

// Inject resource hints
if (this.context.options.render.resourceHints) {
HEAD += this.renderResourceHints(context)
}

await this.context.nuxt.callHook('render:routeContext', context.nuxt)
// Inject styles
HEAD += context.renderStyles()

// Serialize state
const serializedSession = `window.${this.context.globals.context}=${devalue(context.nuxt)};`
APP += `<script>${serializedSession}</script>`

// Calculate CSP hashes
const cspScriptSrcHashes = []
if (this.context.options.render.csp) {
const { hashAlgorithm } = this.context.options.render.csp
Expand All @@ -382,21 +382,29 @@ export default class VueRenderer {
cspScriptSrcHashes.push(`'${hashAlgorithm}-${hash.digest('base64')}'`)
}

APP += `<script>${serializedSession}</script>`
// Call ssr:csp hook
await this.context.nuxt.callHook('vue-renderer:ssr:csp', cspScriptSrcHashes)

// Prepend scripts
APP += this.renderScripts(context)
APP += m.script.text({ body: true })
APP += m.noscript.text({ body: true })

HEAD += context.renderStyles()

const html = this.renderTemplate(true, {
// Template params
const templateParams = {
HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
HEAD_ATTRS: m.headAttrs.text(),
BODY_ATTRS: m.bodyAttrs.text(),
HEAD,
APP,
ENV
})
ENV: this.context.options.env
}

// Call ssr:templateParams hook
await this.context.nuxt.callHook('vue-renderer:ssr:templateParams', templateParams)

// Render with SSR template
const html = this.renderTemplate(true, templateParams)

return {
html,
Expand All @@ -407,6 +415,49 @@ export default class VueRenderer {
}
}

async renderRoute(url, context = {}, retries = 5) {
/* istanbul ignore if */
if (!this.isReady) {
if (!this.context.options.dev || retries <= 0) {
throw new Error('Server resources are not available!')
}

const now = new Date()
if (now - this._lastWaitingForResource > 3000) {
consola.info('Waiting for server resources...')
this._lastWaitingForResource = now
}
await waitFor(1000)

return this.renderRoute(url, context, retries - 1)
}

// Log rendered url
consola.debug(`Rendering url ${url}`)

// Add url to the context
context.url = url

// context.spa
if (context.spa === undefined) {
// TODO: Remove reading from context.res in Nuxt3
TheAlexLichter marked this conversation as resolved.
Show resolved Hide resolved
context.spa = !this.SSR || context.spa || (context.req && context.req.spa) || (context.res && context.res.spa)
}

// context.modern
if (context.modern === undefined) {
context.modern = context.req ? (context.req.modernMode || context.req.modern) : false
}

// Call context hook
await this.context.nuxt.callHook('vue-renderer:context', context)

// Render SPA or SSR
return context.spa
? this.renderSPA(context)
: this.renderSSR(context)
}

get resourceMap() {
return {
clientManifest: {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/module/modules/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function () {
})

// Get data before data sent to client
// TODO: Remove in next major release
this.nuxt.hook('render:context', (data) => {
this.nuxt.__render_context = data
})
Expand Down