Skip to content

Commit 2929716

Browse files
authored
feat(vue-renderer): improvements (#4722)
1 parent eac6d02 commit 2929716

File tree

6 files changed

+122
-59
lines changed

6 files changed

+122
-59
lines changed

packages/core/src/nuxt.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export default class Nuxt extends Hookable {
2626

2727
// Deprecated hooks
2828
this._deprecatedHooks = {
29-
'render:context': 'render:routeContext', // #3773
29+
'render:context': 'render:routeContext',
30+
'render:routeContext': 'vue-renderer:afterRender',
3031
'showReady': 'webpack:done' // Workaround to deprecate showReady
3132
}
3233

packages/core/test/nuxt.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ describe('core: nuxt', () => {
4141

4242
expect(nuxt._deprecatedHooks).toEqual({
4343
'render:context': 'render:routeContext',
44+
'render:routeContext': 'vue-renderer:afterRender',
4445
'showReady': 'webpack:done'
4546
})
4647

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

60+
// TODO: Remove in next major release
5961
test('should call hook webpack:done in showReady', () => {
6062
const nuxt = new Nuxt()
6163
nuxt.callHook = jest.fn()

packages/vue-app/template/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ async function createApp(ssrContext) {
123123
payload: ssrContext ? ssrContext.payload : undefined,
124124
req: ssrContext ? ssrContext.req : undefined,
125125
res: ssrContext ? ssrContext.res : undefined,
126-
beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined
126+
beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined,
127+
ssrContext
127128
})
128129

129130
<% if (plugins.length) { %>

packages/vue-app/template/utils.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,15 @@ export async function setContext(app, context) {
132132
env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
133133
}
134134
// Only set once
135-
if (context.req) app.context.req = context.req
136-
if (context.res) app.context.res = context.res
135+
if (context.req) {
136+
app.context.req = context.req
137+
}
138+
if (context.res) {
139+
app.context.res = context.res
140+
}
141+
if (context.ssrContext) {
142+
app.context.ssrContext = context.ssrContext
143+
}
137144
app.context.redirect = (status, path, query) => {
138145
if (!status) {
139146
return

packages/vue-renderer/src/renderer.js

Lines changed: 106 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ export default class VueRenderer {
3030
spaTemplate: undefined,
3131
errorTemplate: this.parseTemplate('Nuxt.js Internal Server Error')
3232
})
33+
34+
// Keep time of last shown messages
35+
this._lastWaitingForResource = new Date()
3336
}
3437

3538
get assetsMapping() {
@@ -110,7 +113,6 @@ export default class VueRenderer {
110113

111114
async ready() {
112115
// -- Development mode --
113-
114116
if (this.context.options.dev) {
115117
this.context.nuxt.hook('build:resources', mfs => this.loadResources(mfs, true))
116118
return
@@ -121,7 +123,7 @@ export default class VueRenderer {
121123
// Try once to load SSR resources from fs
122124
await this.loadResources(fs)
123125

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

299-
async renderRoute(url, context = {}, retries = 5) {
300-
/* istanbul ignore if */
301-
if (!this.isReady) {
302-
if (this.context.options.dev && retries > 0) {
303-
consola.info('Waiting for server resources...')
304-
await waitFor(1000)
305-
return this.renderRoute(url, context, retries - 1)
306-
} else {
307-
throw new Error('Server resources are not available!')
308-
}
301+
async renderSPA(context) {
302+
const content = await this.renderer.spa.render(context)
303+
304+
const APP =
305+
`<div id="${this.context.globals.id}">${this.context.resources.loadingHTML}</div>` +
306+
content.BODY_SCRIPTS
307+
308+
// Prepare template params
309+
const templateParams = {
310+
...content,
311+
APP,
312+
ENV: this.context.options.env
309313
}
310314

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

314-
// Add url and isSever to the context
315-
context.url = url
318+
// Render with SPA template
319+
const html = this.renderTemplate(false, templateParams)
316320

317-
// Basic response if SSR is disabled or SPA data provided
318-
const { req, res } = context
319-
const spa = context.spa || (res && res.spa)
320-
const ENV = this.context.options.env
321-
322-
if (!this.SSR || spa) {
323-
const {
324-
HTML_ATTRS,
325-
HEAD_ATTRS,
326-
BODY_ATTRS,
327-
HEAD,
328-
BODY_SCRIPTS,
329-
getPreloadFiles
330-
} = await this.renderer.spa.render(context)
331-
const APP =
332-
`<div id="${this.context.globals.id}">${this.context.resources.loadingHTML}</div>` + BODY_SCRIPTS
333-
334-
const html = this.renderTemplate(false, {
335-
HTML_ATTRS,
336-
HEAD_ATTRS,
337-
BODY_ATTRS,
338-
HEAD,
339-
APP,
340-
ENV
321+
return {
322+
html,
323+
getPreloadFiles: this.getPreloadFiles.bind(this, {
324+
getPreloadFiles: content.getPreloadFiles
341325
})
342-
343-
return { html, getPreloadFiles: this.getPreloadFiles.bind(this, { getPreloadFiles }) }
344326
}
327+
}
345328

346-
let APP
329+
async renderSSR(context) {
347330
// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
348-
if (req && req.modernMode) {
349-
APP = await this.renderer.modern.renderToString(context)
350-
} else {
351-
APP = await this.renderer.ssr.renderToString(context)
352-
}
331+
const renderer = context.modern ? this.renderer.modern : this.renderer.ssr
332+
333+
// Call ssr:context hook to extend context from modules
334+
await this.context.nuxt.callHook('vue-renderer:ssr:prepareContext', context)
353335

336+
// Call Vue renderer renderToString
337+
let APP = await renderer.renderToString(context)
338+
339+
// Call ssr:context hook
340+
await this.context.nuxt.callHook('vue-renderer:ssr:context', context)
341+
// TODO: Remove in next major release
342+
await this.context.nuxt.callHook('render:routeContext', context.nuxt)
343+
344+
// Fallback to empty response
354345
if (!context.nuxt.serverRendered) {
355346
APP = `<div id="${this.context.globals.id}"></div>`
356347
}
348+
349+
// Inject head meta
357350
const m = context.meta.inject()
358351
let HEAD =
359352
m.title.text() +
@@ -362,18 +355,25 @@ export default class VueRenderer {
362355
m.style.text() +
363356
m.script.text() +
364357
m.noscript.text()
358+
359+
// Add <base href=""> meta if router base specified
365360
if (this.context.options._routerBaseSpecified) {
366361
HEAD += `<base href="${this.context.options.router.base}">`
367362
}
368363

364+
// Inject resource hints
369365
if (this.context.options.render.resourceHints) {
370366
HEAD += this.renderResourceHints(context)
371367
}
372368

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

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

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

385-
APP += `<script>${serializedSession}</script>`
385+
// Call ssr:csp hook
386+
await this.context.nuxt.callHook('vue-renderer:ssr:csp', cspScriptSrcHashes)
387+
388+
// Prepend scripts
386389
APP += this.renderScripts(context)
387390
APP += m.script.text({ body: true })
388391
APP += m.noscript.text({ body: true })
389392

390-
HEAD += context.renderStyles()
391-
392-
const html = this.renderTemplate(true, {
393+
// Template params
394+
const templateParams = {
393395
HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
394396
HEAD_ATTRS: m.headAttrs.text(),
395397
BODY_ATTRS: m.bodyAttrs.text(),
396398
HEAD,
397399
APP,
398-
ENV
399-
})
400+
ENV: this.context.options.env
401+
}
402+
403+
// Call ssr:templateParams hook
404+
await this.context.nuxt.callHook('vue-renderer:ssr:templateParams', templateParams)
405+
406+
// Render with SSR template
407+
const html = this.renderTemplate(true, templateParams)
400408

401409
return {
402410
html,
@@ -407,6 +415,49 @@ export default class VueRenderer {
407415
}
408416
}
409417

418+
async renderRoute(url, context = {}, retries = 5) {
419+
/* istanbul ignore if */
420+
if (!this.isReady) {
421+
if (!this.context.options.dev || retries <= 0) {
422+
throw new Error('Server resources are not available!')
423+
}
424+
425+
const now = new Date()
426+
if (now - this._lastWaitingForResource > 3000) {
427+
consola.info('Waiting for server resources...')
428+
this._lastWaitingForResource = now
429+
}
430+
await waitFor(1000)
431+
432+
return this.renderRoute(url, context, retries - 1)
433+
}
434+
435+
// Log rendered url
436+
consola.debug(`Rendering url ${url}`)
437+
438+
// Add url to the context
439+
context.url = url
440+
441+
// context.spa
442+
if (context.spa === undefined) {
443+
// TODO: Remove reading from context.res in Nuxt3
444+
context.spa = !this.SSR || context.spa || (context.req && context.req.spa) || (context.res && context.res.spa)
445+
}
446+
447+
// context.modern
448+
if (context.modern === undefined) {
449+
context.modern = context.req ? (context.req.modernMode || context.req.modern) : false
450+
}
451+
452+
// Call context hook
453+
await this.context.nuxt.callHook('vue-renderer:context', context)
454+
455+
// Render SPA or SSR
456+
return context.spa
457+
? this.renderSPA(context)
458+
: this.renderSSR(context)
459+
}
460+
410461
get resourceMap() {
411462
return {
412463
clientManifest: {

test/fixtures/module/modules/hooks/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default function () {
1212
})
1313

1414
// Get data before data sent to client
15+
// TODO: Remove in next major release
1516
this.nuxt.hook('render:context', (data) => {
1617
this.nuxt.__render_context = data
1718
})

0 commit comments

Comments
 (0)