diff --git a/src/base-app.js b/src/base-app.js index 5eaa9175..4c904649 100644 --- a/src/base-app.js +++ b/src/base-app.js @@ -12,10 +12,9 @@ import { ElementToken, RenderToken, SSRDeciderToken, - SSRBodyTemplateToken, RoutePrefixToken, } from './tokens'; -import {SSRDecider, SSRBodyTemplate} from './plugins/ssr'; +import {SSRDecider} from './plugins/ssr'; import RoutePrefixPlugin from './plugins/route-prefix'; import type {aliaser, cleanupFn, FusionPlugin, Token} from './types.js'; @@ -29,7 +28,6 @@ class FusionApp { el && this.register(ElementToken, el); render && this.register(RenderToken, render); this.register(SSRDeciderToken, SSRDecider); - this.register(SSRBodyTemplateToken, SSRBodyTemplate); this.register(RoutePrefixToken, RoutePrefixPlugin); } diff --git a/src/plugins/route-prefix.js b/src/plugins/route-prefix.js index a10122e1..8ba21f33 100644 --- a/src/plugins/route-prefix.js +++ b/src/plugins/route-prefix.js @@ -27,7 +27,7 @@ export default createPlugin({ if (ctx.element) { ctx.template.head.push( dangerouslySetHTML( - `` ) diff --git a/src/plugins/ssr.js b/src/plugins/ssr.js index 79967d35..3e3c65c3 100644 --- a/src/plugins/ssr.js +++ b/src/plugins/ssr.js @@ -5,6 +5,7 @@ * * @flow */ +/* eslint-env node*/ import {createPlugin} from '../create-plugin'; import {escape, consumeSanitizedHTML} from '../sanitization'; @@ -14,6 +15,11 @@ import type { SSRBodyTemplate as SSRBodyTemplateService, } from '../types.js'; +const isTest = Boolean(process.env.NODE_ENV === 'test' || process.env.JEST_ENV); + +// Flow workaround: https://github.com/facebook/flow/issues/285#issuecomment-382044301 +const {defineProperty} = Object; + const SSRDecider = createPlugin({ provides: () => { return ctx => { @@ -31,54 +37,6 @@ const SSRDecider = createPlugin({ }); export {SSRDecider}; -const SSRBodyTemplate = createPlugin({ - provides: () => { - return ctx => { - const {htmlAttrs, bodyAttrs, title, head, body} = ctx.template; - const safeAttrs = Object.keys(htmlAttrs) - .map(attrKey => { - return ` ${escape(attrKey)}="${escape(htmlAttrs[attrKey])}"`; - }) - .join(''); - - const safeBodyAttrs = Object.keys(bodyAttrs) - .map(attrKey => { - return ` ${escape(attrKey)}="${escape(bodyAttrs[attrKey])}"`; - }) - .join(''); - - const safeTitle = escape(title); - // $FlowFixMe - const safeHead = head.map(consumeSanitizedHTML).join(''); - // $FlowFixMe - const safeBody = body.map(consumeSanitizedHTML).join(''); - - const preloadHintLinks = getPreloadHintLinks(ctx); - const coreGlobals = getCoreGlobals(ctx); - const chunkScripts = getChunkScripts(ctx); - const bundleSplittingBootstrap = [ - preloadHintLinks, - coreGlobals, - chunkScripts, - ].join(''); - - return [ - '', - ``, - ``, - ``, - `${safeTitle}`, - `${bundleSplittingBootstrap}${safeHead}`, - ``, - `${ctx.rendered}${safeBody}`, - '', - ].join(''); - }; - }, -}); - -export {SSRBodyTemplate}; - export default function createSSRPlugin({ element, ssrDecider, @@ -86,7 +44,7 @@ export default function createSSRPlugin({ }: { element: any, ssrDecider: SSRDeciderService, - ssrBodyTemplate: SSRBodyTemplateService, + ssrBodyTemplate?: SSRBodyTemplateService, }) { return async function ssrPlugin(ctx: Context, next: () => Promise) { if (!ssrDecider(ctx)) return next(); @@ -111,10 +69,86 @@ export default function createSSRPlugin({ return; } - ctx.body = ssrBodyTemplate(ctx); + if (ssrBodyTemplate) { + ctx.body = ssrBodyTemplate(ctx); + } else { + let legacyBody = legacySSRBodyTemplate(ctx); + + if (!isTest) { + if (__DEV__) { + // eslint-disable-next-line no-console + console.warn([ + 'Warning: no SSRBodyTemplate token was registered.', + 'Upgrading fusion-cli will probably resolve this warning.', + ]); + } + ctx.body = legacyBody; + } else { + defineProperty(ctx, 'body', { + get: () => { + // eslint-disable-next-line no-console + console.warn([ + 'In the next major version of fusion-core,', + 'if no SSRBodyTemplate token is registered,', + 'ctx.body will not be set during SSR.', + 'This means simulation tests should assert against ctx.rendered instead of ctx.body', + ]); + return legacyBody; + }, + set: newBody => { + legacyBody = newBody; + }, + writeable: true, + enumerable: true, + configurable: true, + }); + } + } }; } +function legacySSRBodyTemplate(ctx) { + const {htmlAttrs, bodyAttrs, title, head, body} = ctx.template; + const safeAttrs = Object.keys(htmlAttrs) + .map(attrKey => { + return ` ${escape(attrKey)}="${escape(htmlAttrs[attrKey])}"`; + }) + .join(''); + + const safeBodyAttrs = Object.keys(bodyAttrs) + .map(attrKey => { + return ` ${escape(attrKey)}="${escape(bodyAttrs[attrKey])}"`; + }) + .join(''); + + const safeTitle = escape(title); + // $FlowFixMe + const safeHead = head.map(consumeSanitizedHTML).join(''); + // $FlowFixMe + const safeBody = body.map(consumeSanitizedHTML).join(''); + + const preloadHintLinks = getPreloadHintLinks(ctx); + const coreGlobals = getCoreGlobals(ctx); + const chunkScripts = getChunkScripts(ctx); + const bundleSplittingBootstrap = [ + preloadHintLinks, + coreGlobals, + chunkScripts, + ].join(''); + + return [ + '', + ``, + ``, + ``, + `${safeTitle}`, + `${bundleSplittingBootstrap}${safeHead}`, + ``, + `${ctx.rendered}${safeBody}`, + '', + ].join(''); +} + function getCoreGlobals(ctx) { const {webpackPublicPath, nonce} = ctx; diff --git a/src/server-app.js b/src/server-app.js index 48dd76b2..1e26d825 100644 --- a/src/server-app.js +++ b/src/server-app.js @@ -33,7 +33,7 @@ export default function(): typeof BaseApp { { element: ElementToken, ssrDecider: SSRDeciderToken, - ssrBodyTemplate: SSRBodyTemplateToken, + ssrBodyTemplate: SSRBodyTemplateToken.optional, }, ssrPlugin );