mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat: support ssr: false
(#351)
Co-Authored-By: Daniel Roe <daniel@roe.dev>
This commit is contained in:
parent
9c7085da58
commit
be255772b2
@ -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>
|
||||
|
||||
</html>
|
||||
|
@ -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
14
packages/nitro/index.d.ts
vendored
Normal 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
|
||||
}
|
@ -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": {
|
||||
|
@ -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')
|
||||
if (serverConfig) {
|
||||
serverConfig.devtool = false
|
||||
}
|
||||
})
|
||||
|
||||
// Nitro client plugin
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
if (ctx.nuxt.options.ssr) {
|
||||
await buildServer(ctx)
|
||||
}
|
||||
}
|
||||
|
@ -85,9 +85,9 @@ class WebpackBundler {
|
||||
this.getWebpackConfig('client')
|
||||
]
|
||||
|
||||
// if (options.build.ssr) {
|
||||
if (options.ssr) {
|
||||
webpackConfigs.push(this.getWebpackConfig('server'))
|
||||
// }
|
||||
}
|
||||
|
||||
await this.nuxt.callHook('webpack:config', webpackConfigs)
|
||||
|
||||
|
8
test/fixtures/compat/nuxt.config.ts
vendored
8
test/fixtures/compat/nuxt.config.ts
vendored
@ -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 }
|
||||
|
10
yarn.lock
10
yarn.lock
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user