diff --git a/packages/nuxt3/src/config/config/_app.ts b/packages/nuxt3/src/config/config/_app.ts index 569b9d4d64..903ff8cf48 100644 --- a/packages/nuxt3/src/config/config/_app.ts +++ b/packages/nuxt3/src/config/config/_app.ts @@ -5,7 +5,7 @@ type Plugin = string | { mode?: 'all' | 'client' | 'server', src: string, ssr?: interface AppOptions { css: string[] - head: MetaInfo + head: MetaInfo | (() => MetaInfo) ErrorPage: null | string extendPlugins: null | ((plugins: Plugin[]) => Plugin[]) features: { diff --git a/packages/nuxt3/src/config/config/_common.ts b/packages/nuxt3/src/config/config/_common.ts index 9099578eb7..2f036c9852 100644 --- a/packages/nuxt3/src/config/config/_common.ts +++ b/packages/nuxt3/src/config/config/_common.ts @@ -108,7 +108,7 @@ interface CommonConfiguration { ignore: Array // TODO: remove in Nuxt 3 mode: Mode - modern?: boolean + modern?: boolean | 'client' | 'server' modules: NuxtModule[] privateRuntimeConfig: Record | ((env: NodeJS.ProcessEnv) => Record) publicRuntimeConfig: Record | ((env: NodeJS.ProcessEnv) => Record) diff --git a/packages/nuxt3/src/config/config/render.ts b/packages/nuxt3/src/config/config/render.ts index 0fbddcc388..822fdfafc9 100644 --- a/packages/nuxt3/src/config/config/render.ts +++ b/packages/nuxt3/src/config/config/render.ts @@ -27,7 +27,7 @@ type CspPolicyName = 'child-src' | 'connect-src' | 'default-src' | 'font-src' | interface RenderOptions { bundleRenderer: { - shouldPrefetch: () => boolean + shouldPrefetch: (fileWithoutQuery: string, asType: string) => boolean shouldPreload: (fileWithoutQuery: string, asType: string) => boolean runInNewContext?: boolean } @@ -63,6 +63,7 @@ interface RenderOptions { preloadFiles: PreloadFile[] ) => string[]) } + injectScripts?: boolean resourceHints: boolean ssr?: boolean ssrLog?: boolean | 'collapsed' diff --git a/packages/nuxt3/src/server/server.ts b/packages/nuxt3/src/server/server.ts index f44d5f6972..7c11e51d7b 100644 --- a/packages/nuxt3/src/server/server.ts +++ b/packages/nuxt3/src/server/server.ts @@ -21,6 +21,8 @@ import createTimingMiddleware from './middleware/timing' interface Manifest { assetsMapping: Record publicPath: string + initial: Array + async: Array } export default class Server { @@ -37,6 +39,7 @@ export default class Server { renderer: VueRenderer resources: { clientManifest?: Manifest + loadingHTML?: string modernManifest?: Manifest serverManifest?: Manifest ssrTemplate?: TemplateExecutor diff --git a/packages/nuxt3/src/utils/modern.ts b/packages/nuxt3/src/utils/modern.ts index 12cdc18d75..86eb05f518 100644 --- a/packages/nuxt3/src/utils/modern.ts +++ b/packages/nuxt3/src/utils/modern.ts @@ -65,7 +65,7 @@ export const isModernBrowser = (ua: string) => { ) } -export const isModernRequest = (req: NuxtRequest, modernMode = false) => { +export const isModernRequest = (req: NuxtRequest, modernMode: boolean | string = false) => { if (modernMode === false) { return false } diff --git a/packages/nuxt3/src/vue-renderer/renderer.ts b/packages/nuxt3/src/vue-renderer/renderer.ts index cb6d1fcf44..64a61b6584 100644 --- a/packages/nuxt3/src/vue-renderer/renderer.ts +++ b/packages/nuxt3/src/vue-renderer/renderer.ts @@ -2,19 +2,37 @@ import path from 'path' import fs from 'fs-extra' import consola from 'consola' import template from 'lodash/template' -import { TARGETS, isModernRequest, waitFor } from 'src/utils' +import { Target, TARGETS, isModernRequest, waitFor } from 'src/utils' import ServerContext from 'src/server/context' import SPARenderer from './renderers/spa' import SSRRenderer from './renderers/ssr' import ModernRenderer from './renderers/modern' +declare module 'fs-extra' { + export function exists(path: string): Promise; +} + +export interface RenderContext { + target?: Target + spa?: boolean + modern?: boolean + req?: any + res?: any + runtimeConfig?: { + private: ServerContext['options']['privateRuntimeConfig'], + public: ServerContext['options']['publicRuntimeConfig'] + } + url?: string +} + export default class VueRenderer { __closed?: boolean _state?: 'created' | 'loading' | 'ready' | 'error' _error?: null _readyPromise?: Promise distPath: string + options: ServerContext['options'] serverContext: ServerContext renderer: { ssr: any @@ -252,7 +270,7 @@ export default class VueRenderer { return renderer.render(renderContext) } - async renderRoute (url, renderContext = {}, _retried = 0) { + async renderRoute (url, renderContext : RenderContext = {}, _retried = 0) { /* istanbul ignore if */ if (!this.isReady) { // Fall-back to loading-screen if enabled diff --git a/packages/nuxt3/src/vue-renderer/renderers/base.ts b/packages/nuxt3/src/vue-renderer/renderers/base.ts index dfb369fac0..5bafdf6f0f 100644 --- a/packages/nuxt3/src/vue-renderer/renderers/base.ts +++ b/packages/nuxt3/src/vue-renderer/renderers/base.ts @@ -1,4 +1,5 @@ -import ServerContext from 'nuxt/server/context' +import ServerContext from 'src/server/context' +import { RenderContext } from '../renderer' export default class BaseRenderer { serverContext: ServerContext @@ -18,7 +19,7 @@ export default class BaseRenderer { return templateFn(opts) } - render (renderContext) { + render (_renderContext: RenderContext) { throw new Error('`render()` needs to be implemented') } } diff --git a/packages/nuxt3/src/vue-renderer/renderers/spa.ts b/packages/nuxt3/src/vue-renderer/renderers/spa.ts index ee5d8c5fea..d3f51903d9 100644 --- a/packages/nuxt3/src/vue-renderer/renderers/spa.ts +++ b/packages/nuxt3/src/vue-renderer/renderers/spa.ts @@ -37,7 +37,7 @@ export default class SPARenderer extends BaseRenderer { const modernMode = this.options.modern const modern = (modernMode && this.options.target === TARGETS.static) || isModernRequest(req, modernMode) const cacheKey = `${modern ? 'modern:' : 'legacy:'}${url}` - let meta = this.cache.get(cacheKey) + let meta : Record = this.cache.get(cacheKey) if (meta) { // Return a copy of the content, so that future @@ -127,7 +127,7 @@ export default class SPARenderer extends BaseRenderer { .map(file => ({ ...file, modern })) meta.resourceHints += meta.preloadFiles - .map(({ file, extension, fileWithoutQuery, asType, modern }) => { + .map(({ file, extension, asType, modern }) => { let extra = '' if (asType === 'font') { extra = ` type="font/${extension}"${cors ? '' : ' crossorigin'}` diff --git a/packages/nuxt3/src/vue-renderer/renderers/ssr.ts b/packages/nuxt3/src/vue-renderer/renderers/ssr.ts index 10c8313e13..04652f3348 100644 --- a/packages/nuxt3/src/vue-renderer/renderers/ssr.ts +++ b/packages/nuxt3/src/vue-renderer/renderers/ssr.ts @@ -11,6 +11,8 @@ import ServerContext from 'src/server/context' import BaseRenderer from './base' export default class SSRRenderer extends BaseRenderer { + vueRenderer: typeof import('@vue/server-renderer') + constructor (serverContext: ServerContext) { super(serverContext) this.createRenderer() @@ -170,9 +172,14 @@ export default class SSRRenderer extends BaseRenderer { } const { csp } = this.options.render - // Only add the hash if 'unsafe-inline' rule isn't present to avoid conflicts (#5387) - const containsUnsafeInlineScriptSrc = csp.policies && csp.policies['script-src'] && csp.policies['script-src'].includes('\'unsafe-inline\'') - const shouldHashCspScriptSrc = csp && (csp.unsafeInlineCompatibility || !containsUnsafeInlineScriptSrc) + let shouldHashCspScriptSrc = false + if (typeof csp === 'object') { + const { policies, unsafeInlineCompatibility } = csp + shouldHashCspScriptSrc = unsafeInlineCompatibility || + // Only add the hash if 'unsafe-inline' rule isn't present to avoid conflicts (#5387) + !(policies && policies['script-src'] && policies['script-src'].includes('\'unsafe-inline\'')) + } + const inlineScripts = [] if (renderContext.staticAssetsBase) { @@ -228,7 +235,7 @@ export default class SSRRenderer extends BaseRenderer { // Calculate CSP hashes const cspScriptSrcHashes = [] - if (csp) { + if (typeof csp === 'object') { if (shouldHashCspScriptSrc) { for (const script of inlineScripts) { const hash = crypto.createHash(csp.hashAlgorithm)