fusion-cli/plugins/ssr-plugin.js (152 lines of code) (raw):
/** Copyright (c) 2018 Uber Technologies, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/* eslint-env node */
/* global __webpack_public_path__ */
import {
createPlugin,
escape,
consumeSanitizedHTML,
CriticalChunkIdsToken,
RoutePrefixToken,
getEnv,
} from 'fusion-core';
import {
chunks,
runtimeChunkIds,
initialChunkIds, // $FlowFixMe
} from '../build/loaders/chunk-manifest-loader.js!'; // eslint-disable-line
import modernBrowserVersions from '../build/modern-browser-versions.js';
/*::
import type {SSRBodyTemplateDepsType, SSRBodyTemplateType} from './types.js';
declare var __webpack_public_path__: string;
*/
/* eslint-disable-next-line */
const SSRBodyTemplate = createPlugin/*:: <SSRBodyTemplateDepsType,SSRBodyTemplateType> */(
{
deps: {
criticalChunkIds: CriticalChunkIdsToken.optional,
routePrefix: RoutePrefixToken.optional,
},
provides: ({criticalChunkIds, routePrefix}) => {
const {dangerouslyExposeSourceMaps} = getEnv();
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 coreGlobals = [
`<script nonce="${ctx.nonce}">`,
`window.performance && window.performance.mark && window.performance.mark('firstRenderStart');`,
routePrefix && `__ROUTE_PREFIX__ = ${JSON.stringify(routePrefix)};`,
`__FUSION_ASSET_PATH__ = ${JSON.stringify(__webpack_public_path__)};`, // consumed in src/entries/client-public-path.js
`__NONCE__ = ${JSON.stringify(ctx.nonce)}`, // consumed in src/entries/client-public-path.js
`</script>`,
]
.filter(Boolean)
.join('');
const tokenCriticalChunkIds = criticalChunkIds
? criticalChunkIds.from(ctx)
: new Set();
const allCriticalChunkIds = new Set([
...initialChunkIds,
// For now, take union of both ctx and token
...ctx.preloadChunks,
...tokenCriticalChunkIds,
// runtime chunk must be last script
...runtimeChunkIds,
]);
const legacyUrls = [];
const modernUrls = [];
for (let chunkId of allCriticalChunkIds) {
const url = chunks.get(chunkId);
if (url.includes('client-legacy')) {
legacyUrls.push(url);
} else {
modernUrls.push(url);
}
}
const isModernBrowser = checkModuleSupport(ctx.useragent.browser);
if (__DEV__) {
if (!isModernBrowser && legacyUrls.length === 0) {
return `<!DOCTYPE html>
<html>
<head>
</head>
<body style="padding:20vmin;font-family:sans-serif;font-size:16px;background:papayawhip">
<p>You are using a legacy browser but only the modern bundle has been built (legacy bundles are skipped by default when using <code style="display:inline">fusion dev</code>)
or when using using <code style="display:inline">fusion build</code> with the --modernBuildOnly flag.</p>
<p>Please use a modern browser, <pre><code style="display:inline">fusion dev --forceLegacyBuild</code></pre> or
<pre><code style="display:inline">fusion build</code></pre> with no --modernBuildOnly flag to build the legacy bundle.</p>
<p>For more information, see the docs on <a href="https://github.com/fusionjs/fusion-cli/blob/master/docs/progressively-enhanced-bundles.md">progressively enhanced bundles</a>.</p>
</body>
</html>`;
}
}
const criticalChunkUrls =
isModernBrowser || legacyUrls.length === 0 ? modernUrls : legacyUrls;
let criticalChunkScripts = [];
let preloadHints = [];
for (let url of criticalChunkUrls) {
if (!__DEV__ && dangerouslyExposeSourceMaps) {
// Use -with-map.js bundles
url = addWithMap(url);
}
const crossoriginAttr = process.env.CDN_URL
? ' crossorigin="anonymous"'
: '';
preloadHints.push(
`<link rel="preload" href="${url}" nonce="${ctx.nonce}"${crossoriginAttr} as="script"/>`
);
criticalChunkScripts.push(
`<script defer src="${url}" nonce="${ctx.nonce}"${crossoriginAttr}></script>`
);
}
return [
'<!doctype html>',
`<html${safeAttrs}>`,
`<head>`,
`<meta charset="utf-8" />`,
`<title>${safeTitle}</title>`,
`${preloadHints.join('')}${coreGlobals}${criticalChunkScripts.join(
''
)}${safeHead}`,
`</head>`,
`<body${safeBodyAttrs}>${ctx.rendered}${safeBody}</body>`,
'</html>',
].join('');
};
},
}
);
export {SSRBodyTemplate};
const embeddedBrowserVersions = {
ios_webkit: 605, // mobile safari v13
};
/*
Edge must get transpiled classes due to:
- https://github.com/Microsoft/ChakraCore/issues/5030
- https://github.com/Microsoft/ChakraCore/issues/4663
- https://github.com/babel/babel/issues/8019
Rather than transpile classes in the modern bundles, Edge should be forced on the slow path
Safari 10.1 and 11 have some ES6 bugs:
- https://github.com/mishoo/UglifyJS2/issues/1753
- https://github.com/mishoo/UglifyJS2/issues/2344
- https://github.com/terser-js/terser/issues/117
Rather than enable terser workarounds that reduces minification for compliant browsers,
Safari 10.1 and 11 should be treated as legacy.
*/
function checkModuleSupport({name, version}) {
if (typeof version !== 'string') {
return false;
}
if (name === 'Chrome' || name === 'Chrome Headless' || name === 'Chromium') {
if (majorVersion(version) >= modernBrowserVersions.chrome) return true;
} else if (name === 'Chrome WebView') {
if (majorVersion(version) >= modernBrowserVersions.android) return true;
} else if (name === 'WebKit') {
if (majorVersion(version) >= embeddedBrowserVersions.ios_webkit)
return true;
} else if (name === 'Safari') {
if (majorVersion(version) >= modernBrowserVersions.safari) return true;
} else if (name === 'Mobile Safari') {
if (majorVersion(version) >= modernBrowserVersions.ios) return true;
} else if (name === 'Firefox') {
if (majorVersion(version) >= modernBrowserVersions.firefox) return true;
}
return false;
}
function majorVersion(version) {
return parseInt(version.split('.')[0], 10);
}
function addWithMap(url) {
if (url.endsWith('-with-map.js')) {
return url;
} else {
return url.replace(/\.js$/, '-with-map.js');
}
}