feat: support ssr: false (#351)

Co-Authored-By: Daniel Roe <daniel@roe.dev>
This commit is contained in:
pooya parsa 2021-07-21 22:05:22 +02:00 committed by GitHub
parent 9c7085da58
commit be255772b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 139 additions and 52 deletions

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
<% if (nuxt.options.vite && nuxt.options.dev) { %><script type="module" src="/@vite/client"></script>
<script type="module" src="/__app/entry"></script><% } %>
</body>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>

View File

@ -33,7 +33,8 @@ if (process.client) {
}
entry = async function initApp () {
const app = createSSRApp(App)
const isSSR = Boolean(window.__NUXT__?.serverRendered)
const app = isSSR ? createSSRApp(App) : createApp(App)
const nuxt = createNuxt({ app })

14
packages/nitro/index.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
declare module '#build/dist/server/client.manifest.mjs' {
type ClientManifest = any // TODO: export from vue-bundle-renderer
const clientManifest: ClientManifest
export default clientManifest
}
declare module '#build/dist/server/server.mjs' {
const _default: any
export default _default
}
declare module '#nitro-renderer' {
export const renderToString: Function
}

View File

@ -67,7 +67,7 @@
"unstorage": "^0.2.3",
"upath": "^2.0.1",
"vue": "3.1.5",
"vue-bundle-renderer": "^0.2.5",
"vue-bundle-renderer": "^0.2.9",
"vue-server-renderer": "^2.6.14"
},
"devDependencies": {

View File

@ -50,7 +50,9 @@ export default function nuxt2CompatModule () {
// Disable server sourceMap, esbuild will generate for it.
nuxt.hook('webpack:config', (webpackConfigs) => {
const serverConfig = webpackConfigs.find(config => config.name === 'server')
serverConfig.devtool = false
if (serverConfig) {
serverConfig.devtool = false
}
})
// Nitro client plugin

View File

@ -40,6 +40,7 @@ export interface NitroContext {
_nuxt: {
majorVersion: number
dev: boolean
ssr: boolean
rootDir: string
srcDir: string
buildDir: string
@ -99,6 +100,7 @@ export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): N
_nuxt: {
majorVersion: nuxtOptions._majorVersion || 2,
dev: nuxtOptions.dev,
ssr: nuxtOptions.ssr,
rootDir: nuxtOptions.rootDir,
srcDir: nuxtOptions.srcDir,
buildDir: nuxtOptions.buildDir,

View File

@ -134,6 +134,7 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
'global.': 'globalThis.',
'process.server': 'true',
'process.client': 'false',
'process.env.NUXT_NO_SSR': JSON.stringify(!nitroContext._nuxt.ssr),
'process.env.ROUTER_BASE': JSON.stringify(nitroContext._nuxt.routerBase),
'process.env.PUBLIC_PATH': JSON.stringify(nitroContext._nuxt.publicPath),
'process.env.NUXT_STATIC_BASE': JSON.stringify(nitroContext._nuxt.staticAssets.base),

View File

@ -4,27 +4,47 @@ import { runtimeConfig } from './config'
// @ts-ignore
import htmlTemplate from '#build/views/document.template.mjs'
function _interopDefault (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e }
const STATIC_ASSETS_BASE = process.env.NUXT_STATIC_BASE + '/' + process.env.NUXT_STATIC_VERSION
const NUXT_NO_SSR = process.env.NUXT_NO_SSR
const PAYLOAD_JS = '/payload.js'
let _renderer
async function loadRenderer () {
if (_renderer) {
return _renderer
}
// @ts-ignore
const getClientManifest = cachedImport(() => import('#build/dist/server/client.manifest.mjs'))
const getSSRApp = cachedImport(() => import('#build/dist/server/server.mjs'))
const getSSRRenderer = cachedResult(async () => {
// Load client manifest
const clientManifest = await getClientManifest()
if (!clientManifest) { throw new Error('client.manifest is missing') }
// Load server bundle
const createSSRApp = await getSSRApp()
if (!createSSRApp) { throw new Error('Server bundle is missing') }
// Create renderer
const { renderToString } = await import('#nitro-renderer')
// @ts-ignore
const createApp = await import('#build/dist/server/server.mjs')
// @ts-ignore
const clientManifest = await import('#build/dist/server/client.manifest.mjs')
_renderer = createRenderer(_interopDefault(createApp), {
clientManifest: _interopDefault(clientManifest),
renderToString
return createRenderer((createSSRApp), { clientManifest, renderToString }).renderToString
})
const getSPARenderer = cachedResult(async () => {
const clientManifest = await getClientManifest()
return (ssrContext) => {
ssrContext.nuxt = {}
return {
html: '<div id="__nuxt"></div>',
renderResourceHints: () => '',
renderStyles: () => '',
renderScripts: () => clientManifest.initial.map((s) => {
const isMJS = !s.endsWith('.js')
return `<script ${isMJS ? 'type="module"' : ''} src="${clientManifest.publicPath}${s}"></script>`
}).join('')
}
}
})
function renderToString (ssrContext) {
const getRenderer = (NUXT_NO_SSR || ssrContext.noSSR) ? getSPARenderer : getSSRRenderer
return getRenderer().then(renderToString => renderToString(ssrContext)).catch((err) => {
console.warn('Server Side Rendering Error:', err)
return getSPARenderer().then(renderToString => renderToString(ssrContext))
})
return _renderer
}
export async function renderMiddleware (req, res) {
@ -37,15 +57,18 @@ export async function renderMiddleware (req, res) {
url = url.substr(STATIC_ASSETS_BASE.length, url.length - STATIC_ASSETS_BASE.length - PAYLOAD_JS.length)
}
// Initialize ssr context
const ssrContext = {
url,
req,
res,
runtimeConfig,
noSSR: req.spa || req.headers['x-nuxt-no-ssr'],
...(req.context || {})
}
const renderer = await loadRenderer()
const rendered = await renderer.renderToString(ssrContext)
// Render app
const rendered = await renderToString(ssrContext)
// Handle errors
if (ssrContext.error) {
@ -107,3 +130,24 @@ async function renderHTML (payload, rendered, ssrContext) {
function renderPayload (payload, url) {
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).catch((err) => {
if (err.code === 'ERR_MODULE_NOT_FOUND') { return null }
throw err
}))
}
function cachedResult <T> (fn: () => Promise<T>): () => Promise<T> {
let res = null
return () => {
if (res === null) {
res = fn().catch((err) => { res = null; throw err })
}
return res
}
}

View File

@ -70,6 +70,11 @@ export function createDevServer (nitroContext: NitroContext) {
const proxy = createProxy()
app.use((req, res) => {
if (workerAddress) {
// Workaround to pass legacy req.spa to proxy
// @ts-ignore
if (req.spa) {
req.headers['x-nuxt-no-ssr'] = 'true'
}
proxy.web(req, res, { target: workerAddress }, (_err: unknown) => {
// console.error('[proxy]', err)
})

View File

@ -1,4 +1,6 @@
import * as vite from 'vite'
import { resolve } from 'upath'
import { mkdirp, writeFile } from 'fs-extra'
import vitePlugin from '@vitejs/plugin-vue'
import { cacheDirPlugin } from './plugins/cache-dir'
import { replace } from './plugins/replace'
@ -30,15 +32,25 @@ export async function buildClient (ctx: ViteBuildContext) {
await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false })
const clientManifest = {
publicPath: ctx.nuxt.options.build.publicPath,
all: [],
initial: [ctx.nuxt.options.dev && '@vite/client', 'entry.mjs'].filter(Boolean),
async: [],
modules: {}
}
const serverDist = resolve(ctx.nuxt.options.buildDir, 'dist/server')
await mkdirp(serverDist)
await writeFile(resolve(serverDist, 'client.manifest.json'), JSON.stringify(clientManifest, null, 2), 'utf8')
await writeFile(resolve(serverDist, 'client.manifest.mjs'), 'export default ' + JSON.stringify(clientManifest, null, 2), 'utf8')
const viteServer = await vite.createServer(clientConfig)
await ctx.nuxt.callHook('vite:serverCreated', viteServer)
const viteMiddleware = (req, res, next) => {
// Workaround: vite devmiddleware modifies req.url
const originalURL = req.url
if (req.url === '/_nuxt/client.js') {
return res.end('')
}
viteServer.middlewares.handle(req, res, (err) => {
req.url = originalURL
next(err)

View File

@ -1,7 +1,6 @@
import { resolve } from 'upath'
import * as vite from 'vite'
import vuePlugin from '@vitejs/plugin-vue'
import { mkdirp, writeFile } from 'fs-extra'
import consola from 'consola'
import { ViteBuildContext, ViteOptions } from './vite'
import { wpfs } from './utils/wpfs'
@ -52,12 +51,6 @@ export async function buildServer (ctx: ViteBuildContext) {
await ctx.nuxt.callHook('vite:extendConfig', serverConfig, { isClient: false, isServer: true })
const serverDist = resolve(ctx.nuxt.options.buildDir, 'dist/server')
await mkdirp(serverDist)
await writeFile(resolve(serverDist, 'client.manifest.json'), 'false', 'utf8')
await writeFile(resolve(serverDist, 'client.manifest.mjs'), 'export default false', 'utf8')
const onBuild = () => ctx.nuxt.callHook('build:resources', wpfs)
if (!ctx.nuxt.options.ssr) {

View File

@ -37,8 +37,9 @@ export async function bundle (nuxt: Nuxt) {
...nuxt.options.alias,
'#app': nuxt.options.appDir,
'#build': nuxt.options.buildDir,
'/__app': nuxt.options.appDir,
'/__build': nuxt.options.buildDir,
'/build': nuxt.options.buildDir,
'/app': nuxt.options.appDir,
'/entry.mjs': resolve(nuxt.options.appDir, 'entry'),
'~': nuxt.options.srcDir,
'@': nuxt.options.srcDir,
'web-streams-polyfill/ponyfill/es2018': 'unenv/runtime/mock/empty',
@ -46,6 +47,7 @@ export async function bundle (nuxt: Nuxt) {
'abort-controller': 'unenv/runtime/mock/empty'
}
},
base: nuxt.options.build.publicPath,
vue: {},
css: {},
optimizeDeps: {
@ -67,7 +69,7 @@ export async function bundle (nuxt: Nuxt) {
],
server: {
fs: {
strict: true,
strict: false,
allow: [
nuxt.options.buildDir,
nuxt.options.appDir,
@ -85,11 +87,13 @@ export async function bundle (nuxt: Nuxt) {
nuxt.hook('vite:serverCreated', (server: vite.ViteDevServer) => {
const start = Date.now()
warmupViteServer(server, ['/__app/entry']).then(() => {
warmupViteServer(server, ['/app/entry.mjs']).then(() => {
consola.info(`Vite warmed up in ${Date.now() - start}ms`)
}).catch(consola.error)
})
await buildClient(ctx)
await buildServer(ctx)
if (ctx.nuxt.options.ssr) {
await buildServer(ctx)
}
}

View File

@ -85,9 +85,9 @@ class WebpackBundler {
this.getWebpackConfig('client')
]
// if (options.build.ssr) {
webpackConfigs.push(this.getWebpackConfig('server'))
// }
if (options.ssr) {
webpackConfigs.push(this.getWebpackConfig('server'))
}
await this.nuxt.callHook('webpack:config', webpackConfigs)

View File

@ -7,6 +7,14 @@ export default defineNuxtConfig({
buildModules: [
'@nuxt/nitro/compat'
],
serverMiddleware: [
{
handle (req, _res, next) {
req.spa = req.url.includes('?spa')
next()
}
}
],
buildDir: process.env.NITRO_BUILD_DIR,
nitro: {
output: { dir: process.env.NITRO_OUTPUT_DIR }

View File

@ -1549,7 +1549,7 @@ __metadata:
unstorage: ^0.2.3
upath: ^2.0.1
vue: 3.1.5
vue-bundle-renderer: ^0.2.5
vue-bundle-renderer: ^0.2.9
vue-server-renderer: ^2.6.14
languageName: unknown
linkType: soft
@ -12056,12 +12056,12 @@ fsevents@~2.3.2:
languageName: node
linkType: hard
"vue-bundle-renderer@npm:^0.2.5":
version: 0.2.5
resolution: "vue-bundle-renderer@npm:0.2.5"
"vue-bundle-renderer@npm:^0.2.9":
version: 0.2.9
resolution: "vue-bundle-renderer@npm:0.2.9"
dependencies:
bundle-runner: ^0.0.1
checksum: 9848b493aec6dda72296cf885e3f270610e8481bc1773035f4827915d84f560c49c9e819a2c8ecbc89b70499ec188453772720ad9d58977aa92585878f028adf
checksum: 82f1d06f7e839016707159f211c1feca4ca7ee123864545d8c2f81078ba80c84f3199bf52f66e4117bc4355c4ab91e704476cedf5f29979a5872639d7e3ae8e6
languageName: node
linkType: hard