@loadable/server
Version:
Server utilities for loadable.
610 lines (504 loc) • 18.3 kB
JavaScript
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 };