fusion-cli
Version:
268 lines (241 loc) • 7.97 kB
JavaScript
/** 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 '__SECRET_CHUNK_MANIFEST_LOADER__!'; // eslint-disable-line
import modernBrowserVersions from '../build/modern-browser-versions.js';
/*::
import type {
SSRBodyTemplateDepsType,
SSRBodyTemplateType,
SSRShellTemplateDepsType,
SSRShellTemplateType,
} from './types.js';
declare var __webpack_public_path__: string;
*/
function getSSRTemplateContents(
ctx /*: any */,
criticalChunkIds /*: any */,
routePrefix /*: any */,
emitScripts /*: boolean */
) /*: {
start: string,
end: string,
scripts: Array<string>
} */ {
const {dangerouslyExposeSourceMaps} = getEnv();
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, // Set in fusion-react
...tokenCriticalChunkIds, // Same as initial
// 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) {
const warningMessage = `<!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>`;
return {
start: warningMessage,
end: '',
scripts: [],
};
}
}
const criticalChunkUrls =
isModernBrowser || legacyUrls.length === 0 ? modernUrls : legacyUrls;
let criticalChunkScripts = [];
let preloadHints = [];
if (emitScripts) {
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>`
);
}
}
const start = [
'<!doctype html>',
`<html${safeAttrs}>`,
`<head>`,
`<meta charset="utf-8" />`,
`<title>${safeTitle}</title>`,
`${preloadHints.join('')}${coreGlobals}${criticalChunkScripts.join(
''
)}${safeHead}`,
`</head>`,
`<body${safeBodyAttrs}>`,
].join('');
const end = [`${safeBody}</body>`, '</html>'].join('');
return {
start,
end,
scripts: criticalChunkUrls,
};
}
const SSRBodyTemplate =
createPlugin/*:: <SSRBodyTemplateDepsType,SSRBodyTemplateType> */(
{
deps: {
criticalChunkIds: CriticalChunkIdsToken.optional,
routePrefix: RoutePrefixToken.optional,
},
provides: ({criticalChunkIds, routePrefix}) => {
return (ctx) => {
const template = getSSRTemplateContents(
ctx,
criticalChunkIds,
routePrefix,
true
);
return [template.start, ctx.rendered, template.end].join('');
};
},
}
);
const getSSRShellTemplate = (useModuleScripts /*: boolean */) =>
createPlugin/*:: <SSRShellTemplateDepsType,SSRShellTemplateType> */(
{
deps: {
criticalChunkIds: CriticalChunkIdsToken.optional,
routePrefix: RoutePrefixToken.optional,
},
provides: ({criticalChunkIds, routePrefix}) => {
return (ctx) => {
const shell = getSSRTemplateContents(
ctx,
criticalChunkIds,
routePrefix,
false
);
return {
start: shell.start,
end: shell.end,
scripts: shell.scripts,
useModuleScripts,
};
};
},
}
);
export {SSRBodyTemplate, getSSRShellTemplate};
const embeddedBrowserVersions = {
ios_webkit: 605, // mobile safari v13
};
/*
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;
} else if (name === 'Edge') {
if (majorVersion(version) >= modernBrowserVersions.edge) 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');
}
}