UNPKG

fusion-cli

Version:
268 lines (241 loc) • 7.97 kB
/** 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'); } }