refactor(nuxt): cleanup renderer entry (#4853)

This commit is contained in:
pooya parsa 2022-05-06 17:34:21 +02:00 committed by GitHub
parent 31c67ad9b4
commit a58178c4fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 151 deletions

View File

@ -66,7 +66,7 @@
"unplugin": "^0.6.2", "unplugin": "^0.6.2",
"untyped": "^0.4.4", "untyped": "^0.4.4",
"vue": "^3.2.33", "vue": "^3.2.33",
"vue-bundle-renderer": "^0.3.7", "vue-bundle-renderer": "^0.3.8",
"vue-router": "^4.0.15" "vue-router": "^4.0.15"
}, },
"devDependencies": { "devDependencies": {

View File

@ -64,9 +64,8 @@ export async function initNitro (nuxt: Nuxt) {
] ]
}, },
alias: { alias: {
// TODO: #590
'vue/server-renderer': 'vue/server-renderer',
'vue/compiler-sfc': 'vue/compiler-sfc', 'vue/compiler-sfc': 'vue/compiler-sfc',
'vue/server-renderer': 'vue/server-renderer',
vue: await resolvePath(`vue/dist/vue.cjs${nuxt.options.dev ? '' : '.prod'}.js`), vue: await resolvePath(`vue/dist/vue.cjs${nuxt.options.dev ? '' : '.prod'}.js`),
// Vue 3 mocks // Vue 3 mocks
@ -77,9 +76,6 @@ export async function initNitro (nuxt: Nuxt) {
'@vue/compiler-ssr': 'unenv/runtime/mock/proxy', '@vue/compiler-ssr': 'unenv/runtime/mock/proxy',
'@vue/devtools-api': 'unenv/runtime/mock/proxy', '@vue/devtools-api': 'unenv/runtime/mock/proxy',
// Renderer
'#vue-renderer': resolve(distDir, 'core/runtime/nitro/vue3'),
// Paths // Paths
'#paths': resolve(distDir, 'core/runtime/nitro/paths'), '#paths': resolve(distDir, 'core/runtime/nitro/paths'),
@ -87,7 +83,7 @@ export async function initNitro (nuxt: Nuxt) {
...nuxt.options.alias ...nuxt.options.alias
}, },
replace: { replace: {
'process.env.NUXT_NO_SSR': nuxt.options.ssr === false ? true : undefined 'process.env.NUXT_NO_SSR': nuxt.options.ssr === false
}, },
rollupConfig: { rollupConfig: {
plugins: [] plugins: []

View File

@ -1,6 +1,10 @@
import { createRenderer } from 'vue-bundle-renderer' import { createRenderer } from 'vue-bundle-renderer'
import { eventHandler, useQuery } from 'h3' import type { SSRContext } from 'vue-bundle-renderer'
import { CompatibilityEvent, eventHandler, useQuery } from 'h3'
import devalue from '@nuxt/devalue' import devalue from '@nuxt/devalue'
import { RuntimeConfig } from '@nuxt/schema'
import { renderToString as _renderToString } from 'vue/server-renderer'
// @ts-ignore // @ts-ignore
import { useRuntimeConfig } from '#internal/nitro' import { useRuntimeConfig } from '#internal/nitro'
// @ts-ignore // @ts-ignore
@ -8,33 +12,70 @@ import { buildAssetsURL } from '#paths'
// @ts-ignore // @ts-ignore
import htmlTemplate from '#build/views/document.template.mjs' import htmlTemplate from '#build/views/document.template.mjs'
const STATIC_ASSETS_BASE = process.env.NUXT_STATIC_BASE + '/' + process.env.NUXT_STATIC_VERSION interface NuxtSSRContext extends SSRContext {
const NUXT_NO_SSR = process.env.NUXT_NO_SSR url: string
const PAYLOAD_JS = '/payload.js' noSSR: boolean
redirected: boolean
event: CompatibilityEvent
req: CompatibilityEvent['req']
res: CompatibilityEvent['res']
runtimeConfig: RuntimeConfig
error?: any
nuxt?: any
payload?: any
teleports?: { body?: string }
renderMeta?: () => Promise<any>
}
interface RenderResult {
html: any
renderResourceHints: () => string
renderStyles: () => string
renderScripts: () => string
meta?: Partial<{
htmlAttrs?: string,
bodyAttrs: string,
headAttrs: string,
headTags: string,
bodyScriptsPrepend : string,
bodyScripts : string
}>
}
// @ts-ignore // @ts-ignore
const getClientManifest = cachedImport(() => import('#build/dist/server/client.manifest.mjs')) const getClientManifest = () => import('#build/dist/server/client.manifest.mjs').then(r => r.default || r)
// @ts-ignore
const getSSRApp = !process.env.NUXT_NO_SSR && cachedImport(() => import('#build/dist/server/server.mjs'))
const getSSRRenderer = cachedResult(async () => { // @ts-ignore
const getServerEntry = () => process.env.NUXT_NO_SSR ? Promise.resolve(null) : import('#build/dist/server/server.mjs').then(r => r.default || r)
// -- SSR Renderer --
const getSSRRenderer = lazyCachedFunction(async () => {
// Load client manifest // Load client manifest
const clientManifest = await getClientManifest() const clientManifest = await getClientManifest()
if (!clientManifest) { throw new Error('client.manifest is not available') } if (!clientManifest) { throw new Error('client.manifest is not available') }
// Load server bundle // Load server bundle
const createSSRApp = await getSSRApp() const createSSRApp = await getServerEntry()
if (!createSSRApp) { throw new Error('Server bundle is not available') } if (!createSSRApp) { throw new Error('Server bundle is not available') }
// Create renderer // Create renderer
// @ts-ignore const renderToString = async (input, context) => {
const { renderToString } = await import('#vue-renderer') // Alias to vue2.ts or vue3.ts const html = await _renderToString(input, context)
return createRenderer((createSSRApp), { clientManifest, renderToString, publicPath: buildAssetsURL() }).renderToString return `<div id="__nuxt">${html}</div>`
}
return createRenderer(createSSRApp, {
clientManifest,
renderToString,
publicPath: buildAssetsURL()
})
}) })
const getSPARenderer = cachedResult(async () => { // -- SPA Renderer --
const getSPARenderer = lazyCachedFunction(async () => {
const clientManifest = await getClientManifest() const clientManifest = await getClientManifest()
return (ssrContext) => { const renderToString = (ssrContext: NuxtSSRContext) => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
ssrContext.nuxt = { ssrContext.payload = {
serverRendered: false, serverRendered: false,
config: { config: {
public: config.public, public: config.public,
@ -42,16 +83,14 @@ const getSPARenderer = cachedResult(async () => {
} }
} }
let entryFiles = Object.values(clientManifest).filter( let entryFiles = Object.values(clientManifest).filter((fileValue: any) => fileValue.isEntry)
(fileValue: any) => fileValue.isEntry
)
if ('all' in clientManifest && 'initial' in clientManifest) { if ('all' in clientManifest && 'initial' in clientManifest) {
// Upgrade legacy manifest (also see normalizeClientManifest in vue-bundle-renderer) // Upgrade legacy manifest (also see normalizeClientManifest in vue-bundle-renderer)
// https://github.com/nuxt-contrib/vue-bundle-renderer/issues/12 // https://github.com/nuxt-contrib/vue-bundle-renderer/issues/12
entryFiles = clientManifest.initial.map(file => ({ file })) entryFiles = clientManifest.initial.map(file => ({ file }))
} }
return { return Promise.resolve({
html: '<div id="__nuxt"></div>', html: '<div id="__nuxt"></div>',
renderResourceHints: () => '', renderResourceHints: () => '',
renderStyles: () => renderStyles: () =>
@ -67,36 +106,25 @@ const getSPARenderer = cachedResult(async () => {
return `<script ${isMJS ? 'type="module"' : ''} src="${buildAssetsURL(file)}"></script>` return `<script ${isMJS ? 'type="module"' : ''} src="${buildAssetsURL(file)}"></script>`
}) })
.join('') .join('')
} })
} }
})
function renderToString (ssrContext) { return { renderToString }
const getRenderer = (NUXT_NO_SSR || ssrContext.noSSR) ? getSPARenderer : getSSRRenderer })
return getRenderer().then(renderToString => renderToString(ssrContext))
}
export default eventHandler(async (event) => { export default eventHandler(async (event) => {
// Whether we're rendering an error page // Whether we're rendering an error page
const ssrError = event.req.url?.startsWith('/__nuxt_error') ? useQuery(event) : null const ssrError = event.req.url?.startsWith('/__nuxt_error') ? useQuery(event) : null
let url = ssrError?.url as string || event.req.url! const url = ssrError?.url as string || event.req.url!
// payload.json request detection
let isPayloadReq = false
if (url.startsWith(STATIC_ASSETS_BASE) && url.endsWith(PAYLOAD_JS)) {
isPayloadReq = true
url = url.slice(STATIC_ASSETS_BASE.length, url.length - PAYLOAD_JS.length) || '/'
}
// Initialize ssr context // Initialize ssr context
const ssrContext = { const ssrContext: NuxtSSRContext = {
url, url,
event, event,
req: event.req, req: event.req,
res: event.res, res: event.res,
runtimeConfig: useRuntimeConfig(), runtimeConfig: useRuntimeConfig(),
noSSR: event.req.headers['x-nuxt-no-ssr'], noSSR: !!event.req.headers['x-nuxt-no-ssr'],
error: ssrError, error: ssrError,
redirected: undefined, redirected: undefined,
nuxt: undefined, /* NuxtApp */ nuxt: undefined, /* NuxtApp */
@ -104,9 +132,10 @@ export default eventHandler(async (event) => {
} }
// Render app // Render app
const rendered = await renderToString(ssrContext).catch((e) => { const renderer = (process.env.NUXT_NO_SSR || ssrContext.noSSR) ? await getSPARenderer() : await getSSRRenderer()
const rendered = await renderer.renderToString(ssrContext).catch((e) => {
if (!ssrError) { throw e } if (!ssrError) { throw e }
}) }) as RenderResult
// If we error on rendering error page, we bail out and directly return to the error handler // If we error on rendering error page, we bail out and directly return to the error handler
if (!rendered) { return } if (!rendered) { return }
@ -115,76 +144,40 @@ export default eventHandler(async (event) => {
return return
} }
const error = ssrContext.error /* nuxt 3 */ || ssrContext.nuxt?.error
// Handle errors // Handle errors
if (error && !ssrError) { if (ssrContext.error && !ssrError) {
throw error throw ssrContext.error
} }
if (ssrContext.nuxt?.hooks) { if (ssrContext.nuxt?.hooks) {
await ssrContext.nuxt.hooks.callHook('app:rendered') await ssrContext.nuxt.hooks.callHook('app:rendered')
} }
// TODO: nuxt3 should not reuse `nuxt` property for different purpose! const html = await renderHTML(ssrContext.payload, rendered, ssrContext)
const payload = ssrContext.payload /* nuxt 3 */ || ssrContext.nuxt /* nuxt 2 */ event.res.setHeader('Content-Type', 'text/html;charset=UTF-8')
return html
if (process.env.NUXT_FULL_STATIC) {
payload.staticAssetsBase = STATIC_ASSETS_BASE
}
let data
if (isPayloadReq) {
data = renderPayload(payload, url)
event.res.setHeader('Content-Type', 'text/javascript;charset=UTF-8')
} else {
data = await renderHTML(payload, rendered, ssrContext)
event.res.setHeader('Content-Type', 'text/html;charset=UTF-8')
}
event.res.end(data, 'utf-8')
}) })
async function renderHTML (payload, rendered, ssrContext) { async function renderHTML (payload: any, rendered: RenderResult, ssrContext: NuxtSSRContext) {
const state = `<script>window.__NUXT__=${devalue(payload)}</script>` const state = `<script>window.__NUXT__=${devalue(payload)}</script>`
const html = rendered.html
if ('renderMeta' in ssrContext) { rendered.meta = rendered.meta || {}
rendered.meta = await ssrContext.renderMeta() if (ssrContext.renderMeta) {
Object.assign(rendered.meta, await ssrContext.renderMeta())
} }
const {
htmlAttrs = '',
bodyAttrs = '',
headAttrs = '',
headTags = '',
bodyScriptsPrepend = '',
bodyScripts = ''
} = rendered.meta || {}
return htmlTemplate({ return htmlTemplate({
HTML_ATTRS: htmlAttrs, HTML_ATTRS: (rendered.meta.htmlAttrs || ''),
HEAD_ATTRS: headAttrs, HEAD_ATTRS: (rendered.meta.headAttrs || ''),
HEAD: headTags + HEAD: (rendered.meta.headTags || '') +
rendered.renderResourceHints() + rendered.renderStyles() + (ssrContext.styles || ''), rendered.renderResourceHints() + rendered.renderStyles() + (ssrContext.styles || ''),
BODY_ATTRS: bodyAttrs, BODY_ATTRS: (rendered.meta.bodyAttrs || ''),
BODY_PREPEND: ssrContext.teleports?.body || '', BODY_PREPEND: (ssrContext.teleports?.body || ''),
APP: bodyScriptsPrepend + html + state + rendered.renderScripts() + bodyScripts APP: (rendered.meta.bodyScriptsPrepend || '') + rendered.html + state + rendered.renderScripts() + (rendered.meta.bodyScripts || '')
}) })
} }
function renderPayload (payload, url) { function lazyCachedFunction <T> (fn: () => Promise<T>): () => Promise<T> {
return `__NUXT_JSONP__("${url}", ${devalue(payload)})`
}
function _interopDefault (e) {
return e && typeof e === 'object' && 'default' in e ? e.default : e
}
function cachedImport <M> (importer: () => Promise<M>) {
return cachedResult(() => importer().then(_interopDefault)) as () => Promise<M>
}
function cachedResult <T> (fn: () => Promise<T>): () => Promise<T> {
let res: Promise<T> | null = null let res: Promise<T> | null = null
return () => { return () => {
if (res === null) { if (res === null) {

View File

@ -1,43 +0,0 @@
/**
* This template is only used for @nuxt/bridge
*
* TODO: Move to bridge once render functions was more reusable
*/
// @ts-ignore
import { createRenderer } from '#vue2-server-renderer'
const _renderer = createRenderer({})
// @ts-ignore
const __VUE_SSR_CONTEXT__ = globalThis.__VUE_SSR_CONTEXT__ = {}
export function renderToString (component, context) {
return new Promise((resolve, reject) => {
_renderer.renderToString(component, context, (err, result) => {
const styles = [__VUE_SSR_CONTEXT__, context].map(c => c && c._styles && c._styles.default).filter(Boolean)
if (!context._styles) { context._styles = {} }
context._styles.default = {
ids: [...styles.map(s => s.ids)],
css: styles.map(s => s.css).join(''),
media: styles.map(s => s.media).join('')
}
if (err) {
return reject(err)
}
return resolve(result)
})
})
}
// Basic renderer
// import _renderToString from 'vue-server-renderer/basic'
// export function renderToString (component, context) {
// return new Promise((resolve, reject) => {
// _renderToString(component, context, (err, result) => {
// if (err) {
// return reject(err)
// }
// return resolve(result)
// })
// })
// }

View File

@ -1,6 +0,0 @@
// @ts-ignore
import { renderToString as render } from 'vue/server-renderer'
export const renderToString: typeof render = (...args) => {
return render(...args).then(result => `<div id="__nuxt">${result}</div>`)
}

View File

@ -10089,7 +10089,7 @@ __metadata:
unplugin: ^0.6.2 unplugin: ^0.6.2
untyped: ^0.4.4 untyped: ^0.4.4
vue: ^3.2.33 vue: ^3.2.33
vue-bundle-renderer: ^0.3.7 vue-bundle-renderer: ^0.3.8
vue-meta: next vue-meta: next
vue-router: ^4.0.15 vue-router: ^4.0.15
bin: bin:
@ -13751,12 +13751,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vue-bundle-renderer@npm:^0.3.7": "vue-bundle-renderer@npm:^0.3.8":
version: 0.3.7 version: 0.3.8
resolution: "vue-bundle-renderer@npm:0.3.7" resolution: "vue-bundle-renderer@npm:0.3.8"
dependencies: dependencies:
bundle-runner: ^0.0.1 bundle-runner: ^0.0.1
checksum: 6ef21f019e2b415554dfc1c4ba554a39248a7d8107001de4a3637098787be06cf9784273ec997ee7e2a348949225d2ee534e5523ca8ec9c981e8d7e3965228d0 checksum: f1cc37913369786e60db6cffcda3f016cb073301db765128cddebdcf8367b50a7525e47b60924788def443f3183b92bcfc72ad248ce28d3fed19ef092ae8b662
languageName: node languageName: node
linkType: hard linkType: hard