mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-29 09:02:03 +00:00
refactor(nuxt): cleanup renderer entry (#4853)
This commit is contained in:
parent
31c67ad9b4
commit
a58178c4fd
@ -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": {
|
||||||
|
@ -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: []
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// }
|
|
@ -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>`)
|
|
||||||
}
|
|
10
yarn.lock
10
yarn.lock
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user