UNPKG

@loadable/server

Version:
610 lines (504 loc) 18.3 kB
import React from 'react'; import { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } from '@loadable/component'; import _extends from '@babel/runtime/helpers/esm/extends'; import path from 'path'; import fs from 'fs'; import uniq from 'lodash/uniq.js'; import uniqBy from 'lodash/uniqBy.js'; import flatMap from 'lodash/flatMap.js'; /* eslint-disable no-underscore-dangle */ var invariant = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.invariant, Context = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Context, getRequiredChunkKey = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.getRequiredChunkKey; var ChunkExtractorManager = function ChunkExtractorManager(_ref) { var extractor = _ref.extractor, children = _ref.children; return React.createElement(Context.Provider, { value: extractor }, children); }; // Use __non_webpack_require__ to prevent Webpack from compiling it // when the server-side code is compiled with Webpack // eslint-disable-next-line camelcase, no-undef, global-require, import/no-dynamic-require, no-eval var getRequire = function getRequire() { return typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : eval('require'); }; var clearModuleCache = function clearModuleCache(moduleName) { var _getRequire = getRequire(), cache = _getRequire.cache; var m = cache[moduleName]; if (m) { // remove self from own parents if (m.parent && m.parent.children) { m.parent.children = m.parent.children.filter(function (x) { return x !== m; }); } // remove self from own children if (m.children) { m.children.forEach(function (child) { if (child.parent && child.parent === m) { child.parent = null; } }); } delete cache[moduleName]; } }; var smartRequire = function smartRequire(modulePath) { if (process.env.NODE_ENV !== 'production' && module.hot) { clearModuleCache(modulePath); } return getRequire()(modulePath); }; var joinURLPath = function joinURLPath(publicPath, filename) { if (publicPath.substr(-1) === '/') { return "" + publicPath + filename; } return publicPath + "/" + filename; }; var readJsonFileSync = function readJsonFileSync(inputFileSystem, jsonFilePath) { return JSON.parse(inputFileSystem.readFileSync(jsonFilePath)); }; var EXTENSION_SCRIPT_TYPES = { '.js': 'script', '.mjs': 'script', '.css': 'style' }; function extensionToScriptType(extension) { return EXTENSION_SCRIPT_TYPES[extension] || null; } /** * some files can be references with extra query arguments which have to be removed * @param name * @returns {*} */ function cleanFileName(name) { return name.split('?')[0]; } function getFileScriptType(fileName) { return extensionToScriptType(cleanFileName(path.extname(fileName)).toLowerCase()); } function isScriptFile(fileName) { return getFileScriptType(fileName) === 'script'; } function getAssets(chunks, getAsset) { return uniqBy(flatMap(chunks, function (chunk) { return getAsset(chunk); }), 'url'); } function handleExtraProps(asset, extraProps) { return typeof extraProps === 'function' ? extraProps(asset) : extraProps; } function extraPropsToString(asset, extraProps) { return Object.entries(handleExtraProps(asset, extraProps)).reduce(function (acc, _ref) { var key = _ref[0], value = _ref[1]; return acc + " " + key + "=\"" + value + "\""; }, ''); } function getSriHtmlAttributes(asset) { if (!asset.integrity) { return ''; } return " integrity=\"" + asset.integrity + "\""; } function assetToScriptTag(asset, extraProps) { return "<script async data-chunk=\"" + asset.chunk + "\" src=\"" + asset.url + "\"" + getSriHtmlAttributes(asset) + extraPropsToString(asset, extraProps) + "></script>"; } function assetToScriptElement(asset, extraProps) { return React.createElement("script", Object.assign({ key: asset.url, async: true, "data-chunk": asset.chunk, src: asset.url }, handleExtraProps(asset, extraProps))); } function assetToStyleString(asset, _ref2) { var inputFileSystem = _ref2.inputFileSystem; return new Promise(function (resolve, reject) { inputFileSystem.readFile(asset.path, 'utf8', function (err, data) { if (err) { reject(err); return; } resolve(data); }); }); } function assetToStyleTag(asset, extraProps) { return "<link data-chunk=\"" + asset.chunk + "\" rel=\"stylesheet\" href=\"" + asset.url + "\"" + getSriHtmlAttributes(asset) + extraPropsToString(asset, extraProps) + ">"; } function assetToStyleTagInline(asset, extraProps, _ref3) { var inputFileSystem = _ref3.inputFileSystem; return new Promise(function (resolve, reject) { inputFileSystem.readFile(asset.path, 'utf8', function (err, data) { if (err) { reject(err); return; } resolve("<style type=\"text/css\" data-chunk=\"" + asset.chunk + "\"" + extraPropsToString(asset, extraProps) + ">\n" + data + "\n</style>"); }); }); } function assetToStyleElement(asset, extraProps) { return React.createElement("link", Object.assign({ key: asset.url, "data-chunk": asset.chunk, rel: "stylesheet", href: asset.url }, handleExtraProps(asset, extraProps))); } function assetToStyleElementInline(asset, extraProps, _ref4) { var inputFileSystem = _ref4.inputFileSystem; return new Promise(function (resolve, reject) { inputFileSystem.readFile(asset.path, 'utf8', function (err, data) { if (err) { reject(err); return; } resolve(React.createElement("style", Object.assign({ key: asset.url, "data-chunk": asset.chunk, dangerouslySetInnerHTML: { __html: data } }, handleExtraProps(asset, extraProps)))); }); }); } var LINK_ASSET_HINTS = { mainAsset: 'data-chunk', childAsset: 'data-parent-chunk' }; function assetToLinkTag(asset, extraProps) { var hint = LINK_ASSET_HINTS[asset.type]; return "<link " + hint + "=\"" + asset.chunk + "\" rel=\"" + asset.linkType + "\" as=\"" + asset.scriptType + "\" href=\"" + asset.url + "\"" + getSriHtmlAttributes(asset) + extraPropsToString(asset, extraProps) + ">"; } function assetToLinkElement(asset, extraProps) { var _extends2; var hint = LINK_ASSET_HINTS[asset.type]; var props = _extends((_extends2 = { key: asset.url }, _extends2[hint] = asset.chunk, _extends2.rel = asset.linkType, _extends2.as = asset.scriptType, _extends2.href = asset.url, _extends2), handleExtraProps(asset, extraProps)); return React.createElement("link", props); } function joinTags(tags) { return tags.join('\n'); } var HOT_UPDATE_REGEXP = /\.hot-update\.js$/; function isValidChunkAsset(chunkAsset) { return chunkAsset.scriptType && !HOT_UPDATE_REGEXP.test(chunkAsset.filename); } function checkIfChunkIncludesJs(chunkInfo) { return chunkInfo.files.some(function (file) { return isScriptFile(file); }); } var ChunkExtractor = /*#__PURE__*/ function () { function ChunkExtractor(_temp) { var _ref5 = _temp === void 0 ? {} : _temp, statsFile = _ref5.statsFile, stats = _ref5.stats, _ref5$entrypoints = _ref5.entrypoints, entrypoints = _ref5$entrypoints === void 0 ? ['main'] : _ref5$entrypoints, _ref5$namespace = _ref5.namespace, namespace = _ref5$namespace === void 0 ? '' : _ref5$namespace, outputPath = _ref5.outputPath, publicPath = _ref5.publicPath, _ref5$inputFileSystem = _ref5.inputFileSystem, inputFileSystem = _ref5$inputFileSystem === void 0 ? fs : _ref5$inputFileSystem; this.namespace = namespace; this.stats = stats || readJsonFileSync(inputFileSystem, statsFile); this.publicPath = publicPath || this.stats.publicPath; this.outputPath = outputPath || this.stats.outputPath; this.statsFile = statsFile; this.entrypoints = Array.isArray(entrypoints) ? entrypoints : [entrypoints]; this.chunks = []; this.inputFileSystem = inputFileSystem; } var _proto = ChunkExtractor.prototype; _proto.resolvePublicUrl = function resolvePublicUrl(filename) { return joinURLPath(this.publicPath, filename); }; _proto.getChunkGroup = function getChunkGroup(chunk) { var chunkGroup = this.stats.namedChunkGroups[chunk]; invariant(chunkGroup, "cannot find " + chunk + " in stats"); return chunkGroup; }; _proto.getChunkInfo = function getChunkInfo(chunkId) { var chunkInfo = this.stats.chunks.find(function (chunk) { return chunk.id === chunkId; }); invariant(chunkInfo, "cannot find chunk (chunkId: " + chunkId + ") in stats"); return chunkInfo; }; _proto.createChunkAsset = function createChunkAsset(_ref6) { var filename = _ref6.filename, chunk = _ref6.chunk, type = _ref6.type, linkType = _ref6.linkType; var resolvedFilename = typeof filename === 'object' && filename.name ? filename.name : filename; var resolvedIntegrity = typeof filename === 'object' && filename.integrity ? filename.integrity : null; return { filename: resolvedFilename, integrity: resolvedIntegrity, scriptType: getFileScriptType(resolvedFilename), chunk: chunk, url: this.resolvePublicUrl(resolvedFilename), path: path.join(this.outputPath, resolvedFilename), type: type, linkType: linkType }; }; _proto.getChunkAssets = function getChunkAssets(chunks) { var _this = this; var one = function one(chunk) { var chunkGroup = _this.getChunkGroup(chunk); return chunkGroup.assets.map(function (filename) { return _this.createChunkAsset({ filename: filename, chunk: chunk, type: 'mainAsset', linkType: 'preload' }); }).filter(isValidChunkAsset); }; if (Array.isArray(chunks)) { return getAssets(chunks, one); } return one(chunks); }; _proto.getChunkChildAssets = function getChunkChildAssets(chunks, type) { var _this2 = this; var one = function one(chunk) { var chunkGroup = _this2.getChunkGroup(chunk); var assets = chunkGroup.childAssets[type] || []; return assets.map(function (filename) { return _this2.createChunkAsset({ filename: filename, chunk: chunk, type: 'childAsset', linkType: type }); }).filter(isValidChunkAsset); }; if (Array.isArray(chunks)) { return getAssets(chunks, one); } return one(chunks); }; _proto.getChunkDependencies = function getChunkDependencies(chunks) { var _this3 = this; var one = function one(chunk) { var chunkGroup = _this3.getChunkGroup(chunk); // ignore chunk that only contains css files. return chunkGroup.chunks.filter(function (chunkId) { var chunkInfo = _this3.getChunkInfo(chunkId); if (!chunkInfo) { return false; } return checkIfChunkIncludesJs(chunkInfo); }); }; if (Array.isArray(chunks)) { return uniq(flatMap(chunks, one)); } return one(chunks); }; _proto.getRequiredChunksScriptContent = function getRequiredChunksScriptContent() { return JSON.stringify(this.getChunkDependencies(this.chunks)); }; _proto.getRequiredChunksNamesScriptContent = function getRequiredChunksNamesScriptContent() { return JSON.stringify({ namedChunks: this.chunks }); }; _proto.getRequiredChunksScriptTag = function getRequiredChunksScriptTag(extraProps) { var id = getRequiredChunkKey(this.namespace); var props = "type=\"application/json\"" + extraPropsToString(null, extraProps); return ["<script id=\"" + id + "\" " + props + ">" + this.getRequiredChunksScriptContent() + "</script>", "<script id=\"" + id + "_ext\" " + props + ">" + this.getRequiredChunksNamesScriptContent() + "</script>"].join(''); }; _proto.getRequiredChunksScriptElements = function getRequiredChunksScriptElements(extraProps) { var id = getRequiredChunkKey(this.namespace); var props = _extends({ type: 'application/json' }, handleExtraProps(null, extraProps)); return [React.createElement("script", Object.assign({ id: id, key: id, dangerouslySetInnerHTML: { __html: this.getRequiredChunksScriptContent() } }, props)), React.createElement("script", Object.assign({ id: id + "_ext", key: id + "_ext", dangerouslySetInnerHTML: { __html: this.getRequiredChunksNamesScriptContent() } }, props))]; } // Public methods // ----------------- // Collect ; _proto.addChunk = function addChunk(chunk) { if (this.chunks.indexOf(chunk) !== -1) return; this.chunks.push(chunk); }; _proto.collectChunks = function collectChunks(app) { return React.createElement(ChunkExtractorManager, { extractor: this }, app); } // Utilities ; _proto.requireEntrypoint = function requireEntrypoint(entrypoint) { var entrypointPath = this.getEntrypointPath(this.entrypoint); this.getAllScriptAssetsPaths().forEach(function (assetPath) { smartRequire(assetPath); }); return smartRequire(entrypointPath); }; _proto.getEntrypointPath = function getEntrypointPath(entrypoint) { entrypoint = entrypoint || this.entrypoints[0]; var assets = this.getChunkAssets(entrypoint); var mainAsset = assets.find(function (asset) { return asset.scriptType === 'script'; }); invariant(mainAsset, 'asset not found'); return cleanFileName(mainAsset.path); }; _proto.getAllScriptAssetsPaths = function getAllScriptAssetsPaths() { var _this4 = this; return this.stats.assets.filter(function (_ref7) { var name = _ref7.name; return isScriptFile(name); }).map(function (_ref8) { var name = _ref8.name; return path.join(_this4.outputPath, cleanFileName(name)); }); } // Main assets ; _proto.getMainAssets = function getMainAssets(scriptType) { var chunks = [].concat(this.entrypoints, this.chunks); var assets = this.getChunkAssets(chunks); if (scriptType) { return assets.filter(function (asset) { return asset.scriptType === scriptType; }); } return assets; }; _proto.getScriptTags = function getScriptTags(extraProps) { if (extraProps === void 0) { extraProps = {}; } var requiredScriptTag = this.getRequiredChunksScriptTag(extraProps); var mainAssets = this.getMainAssets('script'); var assetsScriptTags = mainAssets.map(function (asset) { return assetToScriptTag(asset, extraProps); }); return joinTags([requiredScriptTag].concat(assetsScriptTags)); }; _proto.getScriptElements = function getScriptElements(extraProps) { if (extraProps === void 0) { extraProps = {}; } var requiredScriptElements = this.getRequiredChunksScriptElements(extraProps); var mainAssets = this.getMainAssets('script'); var assetsScriptElements = mainAssets.map(function (asset) { return assetToScriptElement(asset, extraProps); }); return [].concat(requiredScriptElements, assetsScriptElements); }; _proto.getCssString = function getCssString() { var _this5 = this; var mainAssets = this.getMainAssets('style'); var promises = mainAssets.map(function (asset) { return assetToStyleString(asset, _this5).then(function (data) { return data; }); }); return Promise.all(promises).then(function (results) { return joinTags(results); }); }; _proto.getStyleTags = function getStyleTags(extraProps) { if (extraProps === void 0) { extraProps = {}; } var mainAssets = this.getMainAssets('style'); return joinTags(mainAssets.map(function (asset) { return assetToStyleTag(asset, extraProps); })); }; _proto.getInlineStyleTags = function getInlineStyleTags(extraProps) { var _this6 = this; if (extraProps === void 0) { extraProps = {}; } var mainAssets = this.getMainAssets('style'); var promises = mainAssets.map(function (asset) { return assetToStyleTagInline(asset, extraProps, _this6).then(function (data) { return data; }); }); return Promise.all(promises).then(function (results) { return joinTags(results); }); }; _proto.getStyleElements = function getStyleElements(extraProps) { if (extraProps === void 0) { extraProps = {}; } var mainAssets = this.getMainAssets('style'); return mainAssets.map(function (asset) { return assetToStyleElement(asset, extraProps); }); }; _proto.getInlineStyleElements = function getInlineStyleElements(extraProps) { var _this7 = this; if (extraProps === void 0) { extraProps = {}; } var mainAssets = this.getMainAssets('style'); var promises = mainAssets.map(function (asset) { return assetToStyleElementInline(asset, extraProps, _this7).then(function (data) { return data; }); }); return Promise.all(promises).then(function (results) { return results; }); } // Pre assets ; _proto.getPreAssets = function getPreAssets() { var mainAssets = this.getMainAssets(); var chunks = [].concat(this.entrypoints, this.chunks); var preloadAssets = this.getChunkChildAssets(chunks, 'preload'); var prefetchAssets = this.getChunkChildAssets(chunks, 'prefetch'); return [].concat(mainAssets, preloadAssets, prefetchAssets).sort(function (a) { return a.scriptType === 'style' ? -1 : 0; }); }; _proto.getLinkTags = function getLinkTags(extraProps) { if (extraProps === void 0) { extraProps = {}; } var assets = this.getPreAssets(); var linkTags = assets.map(function (asset) { return assetToLinkTag(asset, extraProps); }); return joinTags(linkTags); }; _proto.getLinkElements = function getLinkElements(extraProps) { if (extraProps === void 0) { extraProps = {}; } var assets = this.getPreAssets(); return assets.map(function (asset) { return assetToLinkElement(asset, extraProps); }); }; return ChunkExtractor; }(); export { ChunkExtractor, ChunkExtractorManager };