1412 lines
63 KiB
JavaScript
1412 lines
63 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.default = void 0;
|
|
function _graph() {
|
|
const data = require("@parcel/graph");
|
|
_graph = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _assert() {
|
|
const data = _interopRequireDefault(require("assert"));
|
|
_assert = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _plugin() {
|
|
const data = require("@parcel/plugin");
|
|
_plugin = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _utils() {
|
|
const data = require("@parcel/utils");
|
|
_utils = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _nullthrows() {
|
|
const data = _interopRequireDefault(require("nullthrows"));
|
|
_nullthrows = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _path() {
|
|
const data = _interopRequireDefault(require("path"));
|
|
_path = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _diagnostic() {
|
|
const data = require("@parcel/diagnostic");
|
|
_diagnostic = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
// Default options by http version.
|
|
const HTTP_OPTIONS = {
|
|
'1': {
|
|
minBundles: 1,
|
|
manualSharedBundles: [],
|
|
minBundleSize: 30000,
|
|
maxParallelRequests: 6,
|
|
disableSharedBundles: false
|
|
},
|
|
'2': {
|
|
minBundles: 1,
|
|
manualSharedBundles: [],
|
|
minBundleSize: 20000,
|
|
maxParallelRequests: 25,
|
|
disableSharedBundles: false
|
|
}
|
|
};
|
|
|
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
|
|
const dependencyPriorityEdges = {
|
|
sync: 1,
|
|
parallel: 2,
|
|
lazy: 3
|
|
};
|
|
|
|
// IdealGraph is the structure we will pass to decorate,
|
|
// which mutates the assetGraph into the bundleGraph we would
|
|
// expect from default bundler
|
|
/**
|
|
*
|
|
* The Bundler works by creating an IdealGraph, which contains a BundleGraph that models bundles
|
|
* connected to other bundles by what references them, and thus models BundleGroups.
|
|
*
|
|
* First, we enter `bundle({bundleGraph, config})`. Here, "bundleGraph" is actually just the
|
|
* assetGraph turned into a type `MutableBundleGraph`, which will then be mutated in decorate,
|
|
* and turned into what we expect the bundleGraph to be as per the old (default) bundler structure
|
|
* & what the rest of Parcel expects a BundleGraph to be.
|
|
*
|
|
* `bundle({bundleGraph, config})` First gets a Mapping of target to entries, In most cases there is
|
|
* only one target, and one or more entries. (Targets are pertinent in monorepos or projects where you
|
|
* will have two or more distDirs, or output folders.) Then calls create IdealGraph and Decorate per target.
|
|
*
|
|
*/
|
|
var _default = exports.default = new (_plugin().Bundler)({
|
|
loadConfig({
|
|
config,
|
|
options,
|
|
logger
|
|
}) {
|
|
return loadBundlerConfig(config, options, logger);
|
|
},
|
|
bundle({
|
|
bundleGraph,
|
|
config,
|
|
logger
|
|
}) {
|
|
let targetMap = getEntryByTarget(bundleGraph); // Organize entries by target output folder/ distDir
|
|
let graphs = [];
|
|
for (let entries of targetMap.values()) {
|
|
// Create separate bundleGraphs per distDir
|
|
graphs.push(createIdealGraph(bundleGraph, config, entries, logger));
|
|
}
|
|
for (let g of graphs) {
|
|
decorateLegacyGraph(g, bundleGraph); //mutate original graph
|
|
}
|
|
},
|
|
|
|
optimize() {}
|
|
});
|
|
function decorateLegacyGraph(idealGraph, bundleGraph) {
|
|
let idealBundleToLegacyBundle = new Map();
|
|
let {
|
|
bundleGraph: idealBundleGraph,
|
|
dependencyBundleGraph,
|
|
bundleGroupBundleIds,
|
|
manualAssetToBundle
|
|
} = idealGraph;
|
|
let entryBundleToBundleGroup = new Map();
|
|
// Step Create Bundles: Create bundle groups, bundles, and shared bundles and add assets to them
|
|
for (let [bundleNodeId, idealBundle] of idealBundleGraph.nodes.entries()) {
|
|
if (!idealBundle || idealBundle === 'root') continue;
|
|
let entryAsset = idealBundle.mainEntryAsset;
|
|
let bundleGroups = [];
|
|
let bundleGroup;
|
|
let bundle;
|
|
if (bundleGroupBundleIds.has(bundleNodeId)) {
|
|
(0, _assert().default)(idealBundle.manualSharedBundle == null, 'Unstable Manual Shared Bundle feature is processing a manualSharedBundle as a BundleGroup');
|
|
let dependencies = dependencyBundleGraph.getNodeIdsConnectedTo(dependencyBundleGraph.getNodeIdByContentKey(String(bundleNodeId)), _graph().ALL_EDGE_TYPES).map(nodeId => {
|
|
let dependency = (0, _nullthrows().default)(dependencyBundleGraph.getNode(nodeId));
|
|
(0, _assert().default)(dependency.type === 'dependency');
|
|
return dependency.value;
|
|
});
|
|
(0, _assert().default)(entryAsset != null, 'Processing a bundleGroup with no entry asset');
|
|
for (let dependency of dependencies) {
|
|
bundleGroup = bundleGraph.createBundleGroup(dependency, idealBundle.target);
|
|
bundleGroups.push(bundleGroup);
|
|
}
|
|
(0, _assert().default)(bundleGroup);
|
|
entryBundleToBundleGroup.set(bundleNodeId, bundleGroup);
|
|
bundle = (0, _nullthrows().default)(bundleGraph.createBundle({
|
|
entryAsset: (0, _nullthrows().default)(entryAsset),
|
|
needsStableName: idealBundle.needsStableName,
|
|
bundleBehavior: idealBundle.bundleBehavior,
|
|
target: idealBundle.target,
|
|
manualSharedBundle: idealBundle.manualSharedBundle
|
|
}));
|
|
bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
|
|
} else if (idealBundle.sourceBundles.size > 0 && !idealBundle.mainEntryAsset) {
|
|
let uniqueKey = idealBundle.uniqueKey != null ? idealBundle.uniqueKey : [...idealBundle.assets].map(asset => asset.id).join(',');
|
|
bundle = (0, _nullthrows().default)(bundleGraph.createBundle({
|
|
uniqueKey,
|
|
needsStableName: idealBundle.needsStableName,
|
|
bundleBehavior: idealBundle.bundleBehavior,
|
|
type: idealBundle.type,
|
|
target: idealBundle.target,
|
|
env: idealBundle.env,
|
|
manualSharedBundle: idealBundle.manualSharedBundle
|
|
}));
|
|
} else if (idealBundle.uniqueKey != null) {
|
|
bundle = (0, _nullthrows().default)(bundleGraph.createBundle({
|
|
uniqueKey: idealBundle.uniqueKey,
|
|
needsStableName: idealBundle.needsStableName,
|
|
bundleBehavior: idealBundle.bundleBehavior,
|
|
type: idealBundle.type,
|
|
target: idealBundle.target,
|
|
env: idealBundle.env,
|
|
manualSharedBundle: idealBundle.manualSharedBundle
|
|
}));
|
|
} else {
|
|
(0, _assert().default)(entryAsset != null);
|
|
bundle = (0, _nullthrows().default)(bundleGraph.createBundle({
|
|
entryAsset,
|
|
needsStableName: idealBundle.needsStableName,
|
|
bundleBehavior: idealBundle.bundleBehavior,
|
|
target: idealBundle.target,
|
|
manualSharedBundle: idealBundle.manualSharedBundle
|
|
}));
|
|
}
|
|
idealBundleToLegacyBundle.set(idealBundle, bundle);
|
|
for (let asset of idealBundle.assets) {
|
|
bundleGraph.addAssetToBundle(asset, bundle);
|
|
}
|
|
}
|
|
// Step Internalization: Internalize dependencies for bundles
|
|
for (let idealBundle of idealBundleGraph.nodes) {
|
|
if (!idealBundle || idealBundle === 'root') continue;
|
|
let bundle = (0, _nullthrows().default)(idealBundleToLegacyBundle.get(idealBundle));
|
|
if (idealBundle.internalizedAssets) {
|
|
idealBundle.internalizedAssets.forEach(internalized => {
|
|
let incomingDeps = bundleGraph.getIncomingDependencies(idealGraph.assets[internalized]);
|
|
for (let incomingDep of incomingDeps) {
|
|
if (incomingDep.priority === 'lazy' && incomingDep.specifierType !== 'url' && bundle.hasDependency(incomingDep)) {
|
|
bundleGraph.internalizeAsyncDependency(bundle, incomingDep);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
// Unstable Manual Shared Bundles
|
|
// NOTE: This only works under the assumption that manual shared bundles would have
|
|
// always already been loaded before the bundle that requires internalization.
|
|
for (let manualSharedAsset of manualAssetToBundle.keys()) {
|
|
let incomingDeps = bundleGraph.getIncomingDependencies(manualSharedAsset);
|
|
for (let incomingDep of incomingDeps) {
|
|
if (incomingDep.priority === 'lazy' && incomingDep.specifierType !== 'url') {
|
|
let bundles = bundleGraph.getBundlesWithDependency(incomingDep);
|
|
for (let bundle of bundles) {
|
|
bundleGraph.internalizeAsyncDependency(bundle, incomingDep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step Add to BundleGroups: Add bundles to their bundle groups
|
|
idealBundleGraph.traverse((nodeId, _, actions) => {
|
|
let node = idealBundleGraph.getNode(nodeId);
|
|
if (node === 'root') {
|
|
return;
|
|
}
|
|
actions.skipChildren();
|
|
let outboundNodeIds = idealBundleGraph.getNodeIdsConnectedFrom(nodeId);
|
|
let entryBundle = (0, _nullthrows().default)(idealBundleGraph.getNode(nodeId));
|
|
(0, _assert().default)(entryBundle !== 'root');
|
|
let legacyEntryBundle = (0, _nullthrows().default)(idealBundleToLegacyBundle.get(entryBundle));
|
|
for (let id of outboundNodeIds) {
|
|
let siblingBundle = (0, _nullthrows().default)(idealBundleGraph.getNode(id));
|
|
(0, _assert().default)(siblingBundle !== 'root');
|
|
let legacySiblingBundle = (0, _nullthrows().default)(idealBundleToLegacyBundle.get(siblingBundle));
|
|
bundleGraph.createBundleReference(legacyEntryBundle, legacySiblingBundle);
|
|
}
|
|
});
|
|
|
|
// Step References: Add references to all bundles
|
|
for (let [asset, references] of idealGraph.assetReference) {
|
|
for (let [dependency, bundle] of references) {
|
|
let legacyBundle = (0, _nullthrows().default)(idealBundleToLegacyBundle.get(bundle));
|
|
bundleGraph.createAssetReference(dependency, asset, legacyBundle);
|
|
}
|
|
}
|
|
for (let {
|
|
from,
|
|
to
|
|
} of idealBundleGraph.getAllEdges()) {
|
|
let sourceBundle = (0, _nullthrows().default)(idealBundleGraph.getNode(from));
|
|
if (sourceBundle === 'root') {
|
|
continue;
|
|
}
|
|
(0, _assert().default)(sourceBundle !== 'root');
|
|
let legacySourceBundle = (0, _nullthrows().default)(idealBundleToLegacyBundle.get(sourceBundle));
|
|
let targetBundle = (0, _nullthrows().default)(idealBundleGraph.getNode(to));
|
|
if (targetBundle === 'root') {
|
|
continue;
|
|
}
|
|
(0, _assert().default)(targetBundle !== 'root');
|
|
let legacyTargetBundle = (0, _nullthrows().default)(idealBundleToLegacyBundle.get(targetBundle));
|
|
bundleGraph.createBundleReference(legacySourceBundle, legacyTargetBundle);
|
|
}
|
|
}
|
|
function createIdealGraph(assetGraph, config, entries, logger) {
|
|
// Asset to the bundle and group it's an entry of
|
|
let bundleRoots = new Map();
|
|
let bundles = new Map();
|
|
let dependencyBundleGraph = new (_graph().ContentGraph)();
|
|
let assetReference = new (_utils().DefaultMap)(() => []);
|
|
|
|
// A Graph of Bundles and a root node (dummy string), which models only Bundles, and connections to their
|
|
// referencing Bundle. There are no actual BundleGroup nodes, just bundles that take on that role.
|
|
let bundleGraph = new (_graph().Graph)();
|
|
let stack = [];
|
|
let bundleRootEdgeTypes = {
|
|
parallel: 1,
|
|
lazy: 2
|
|
};
|
|
// Graph that models bundleRoots, with parallel & async deps only to inform reachability
|
|
let bundleRootGraph = new (_graph().Graph)();
|
|
let assetToBundleRootNodeId = new Map();
|
|
let bundleGroupBundleIds = new Set();
|
|
let bundleGraphRootNodeId = (0, _nullthrows().default)(bundleGraph.addNode('root'));
|
|
bundleGraph.setRootNodeId(bundleGraphRootNodeId);
|
|
// Step Create Entry Bundles
|
|
for (let [asset, dependency] of entries) {
|
|
let bundle = createBundle({
|
|
asset,
|
|
target: (0, _nullthrows().default)(dependency.target),
|
|
needsStableName: dependency.isEntry
|
|
});
|
|
let nodeId = bundleGraph.addNode(bundle);
|
|
bundles.set(asset.id, nodeId);
|
|
bundleRoots.set(asset, [nodeId, nodeId]);
|
|
bundleGraph.addEdge(bundleGraphRootNodeId, nodeId);
|
|
dependencyBundleGraph.addEdge(dependencyBundleGraph.addNodeByContentKeyIfNeeded(dependency.id, {
|
|
value: dependency,
|
|
type: 'dependency'
|
|
}), dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(nodeId), {
|
|
value: bundle,
|
|
type: 'bundle'
|
|
}), dependencyPriorityEdges[dependency.priority]);
|
|
bundleGroupBundleIds.add(nodeId);
|
|
}
|
|
let assets = [];
|
|
let assetToIndex = new Map();
|
|
//Manual is a map of the user-given name to the bundle node Id that corresponds to ALL the assets that match any glob in that user-specified array
|
|
let manualSharedMap = new Map();
|
|
// May need a map to be able to look up NON- bundle root assets which need special case instructions
|
|
// Use this when placing assets into bundles, to avoid duplication
|
|
let manualAssetToBundle = new Map();
|
|
let {
|
|
manualAssetToConfig,
|
|
constantModuleToMSB
|
|
} = function makeManualAssetToConfigLookup() {
|
|
let manualAssetToConfig = new Map();
|
|
let constantModuleToMSB = new (_utils().DefaultMap)(() => []);
|
|
if (config.manualSharedBundles.length === 0) {
|
|
return {
|
|
manualAssetToConfig,
|
|
constantModuleToMSB
|
|
};
|
|
}
|
|
let parentsToConfig = new (_utils().DefaultMap)(() => []);
|
|
for (let c of config.manualSharedBundles) {
|
|
if (c.root != null) {
|
|
parentsToConfig.get(_path().default.join(config.projectRoot, c.root)).push(c);
|
|
}
|
|
}
|
|
let numParentsToFind = parentsToConfig.size;
|
|
let configToParentAsset = new Map();
|
|
assetGraph.traverse((node, _, actions) => {
|
|
if (node.type === 'asset' && parentsToConfig.has(node.value.filePath)) {
|
|
for (let c of parentsToConfig.get(node.value.filePath)) {
|
|
configToParentAsset.set(c, node.value);
|
|
}
|
|
numParentsToFind--;
|
|
if (numParentsToFind === 0) {
|
|
// If we've found all parents we can stop traversal
|
|
actions.stop();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Process in reverse order so earlier configs take precedence
|
|
for (let c of config.manualSharedBundles.reverse()) {
|
|
if (c.root != null && !configToParentAsset.has(c)) {
|
|
logger.warn({
|
|
origin: '@parcel/bundler-default',
|
|
message: `Manual shared bundle "${c.name}" skipped, no root asset found`
|
|
});
|
|
continue;
|
|
}
|
|
let parentAsset = configToParentAsset.get(c);
|
|
let assetRegexes = c.assets.map(glob => (0, _utils().globToRegex)(glob));
|
|
assetGraph.traverse((node, _, actions) => {
|
|
if (node.type === 'asset' && (!Array.isArray(c.types) || c.types.includes(node.value.type))) {
|
|
// +1 accounts for leading slash
|
|
let projectRelativePath = node.value.filePath.slice(config.projectRoot.length + 1);
|
|
if (!assetRegexes.some(regex => regex.test(projectRelativePath))) {
|
|
return;
|
|
}
|
|
|
|
// We track all matching MSB's for constant modules as they are never duplicated
|
|
// and need to be assigned to all matching bundles
|
|
if (node.value.meta.isConstantModule === true) {
|
|
constantModuleToMSB.get(node.value).push(c);
|
|
}
|
|
manualAssetToConfig.set(node.value, c);
|
|
return;
|
|
}
|
|
if (node.type === 'dependency' && node.value.priority === 'lazy' && parentAsset) {
|
|
// Don't walk past the bundle group assets
|
|
actions.skipChildren();
|
|
}
|
|
}, parentAsset);
|
|
}
|
|
return {
|
|
manualAssetToConfig,
|
|
constantModuleToMSB
|
|
};
|
|
}();
|
|
let manualBundleToInternalizedAsset = new (_utils().DefaultMap)(() => []);
|
|
|
|
/**
|
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
* for asset type changes, parallel, inline, and async or lazy dependencies,
|
|
* adding only that asset to each bundle, not its entire subgraph.
|
|
*/
|
|
assetGraph.traverse({
|
|
enter(node, context, actions) {
|
|
if (node.type === 'asset') {
|
|
if ((context === null || context === void 0 ? void 0 : context.type) === 'dependency' && context !== null && context !== void 0 && context.value.isEntry && !entries.has(node.value)) {
|
|
// Skip whole subtrees of other targets by skipping those entries
|
|
actions.skipChildren();
|
|
return node;
|
|
}
|
|
assetToIndex.set(node.value, assets.length);
|
|
assets.push(node.value);
|
|
let bundleIdTuple = bundleRoots.get(node.value);
|
|
if (bundleIdTuple && bundleIdTuple[0] === bundleIdTuple[1]) {
|
|
// Push to the stack (only) when a new bundle is created
|
|
stack.push([node.value, bundleIdTuple[0]]);
|
|
} else if (bundleIdTuple) {
|
|
// Otherwise, push on the last bundle that marks the start of a BundleGroup
|
|
stack.push([node.value, stack[stack.length - 1][1]]);
|
|
}
|
|
} else if (node.type === 'dependency') {
|
|
if (context == null) {
|
|
return node;
|
|
}
|
|
let dependency = node.value;
|
|
(0, _assert().default)((context === null || context === void 0 ? void 0 : context.type) === 'asset');
|
|
let assets = assetGraph.getDependencyAssets(dependency);
|
|
if (assets.length === 0) {
|
|
return node;
|
|
}
|
|
for (let childAsset of assets) {
|
|
let bundleId = bundles.get(childAsset.id);
|
|
let bundle;
|
|
|
|
// MSB Step 1: Match glob on filepath and type for any asset
|
|
let manualSharedBundleKey;
|
|
let manualSharedObject = manualAssetToConfig.get(childAsset);
|
|
if (manualSharedObject) {
|
|
// MSB Step 2: Generate a key for which to look up this manual bundle with
|
|
manualSharedBundleKey = manualSharedObject.name + ',' + childAsset.type;
|
|
}
|
|
if (
|
|
// MSB Step 3: If a bundle for these globs already exsits, use it
|
|
manualSharedBundleKey != null && manualSharedMap.has(manualSharedBundleKey)) {
|
|
bundleId = (0, _nullthrows().default)(manualSharedMap.get(manualSharedBundleKey));
|
|
}
|
|
if (dependency.priority === 'lazy' || childAsset.bundleBehavior === 'isolated' // An isolated Dependency, or Bundle must contain all assets it needs to load.
|
|
) {
|
|
if (bundleId == null) {
|
|
var _dependency$bundleBeh;
|
|
let firstBundleGroup = (0, _nullthrows().default)(bundleGraph.getNode(stack[0][1]));
|
|
(0, _assert().default)(firstBundleGroup !== 'root');
|
|
bundle = createBundle({
|
|
asset: childAsset,
|
|
target: firstBundleGroup.target,
|
|
needsStableName: dependency.bundleBehavior === 'inline' || childAsset.bundleBehavior === 'inline' ? false : dependency.isEntry || dependency.needsStableName,
|
|
bundleBehavior: (_dependency$bundleBeh = dependency.bundleBehavior) !== null && _dependency$bundleBeh !== void 0 ? _dependency$bundleBeh : childAsset.bundleBehavior
|
|
});
|
|
bundleId = bundleGraph.addNode(bundle);
|
|
bundles.set(childAsset.id, bundleId);
|
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
bundleGroupBundleIds.add(bundleId);
|
|
bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
|
|
if (manualSharedObject) {
|
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
// since MSBs should not have main entry assets
|
|
manualBundleToInternalizedAsset.get(bundleId).push(childAsset);
|
|
}
|
|
} else {
|
|
bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
|
|
(0, _assert().default)(bundle !== 'root');
|
|
if (
|
|
// If this dependency requests isolated, but the bundle is not,
|
|
// make the bundle isolated for all uses.
|
|
dependency.bundleBehavior === 'isolated' && bundle.bundleBehavior == null) {
|
|
bundle.bundleBehavior = dependency.bundleBehavior;
|
|
}
|
|
}
|
|
dependencyBundleGraph.addEdge(dependencyBundleGraph.addNodeByContentKeyIfNeeded(dependency.id, {
|
|
value: dependency,
|
|
type: 'dependency'
|
|
}), dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(bundleId), {
|
|
value: bundle,
|
|
type: 'bundle'
|
|
}), dependencyPriorityEdges[dependency.priority]);
|
|
} else if (dependency.priority === 'parallel' || childAsset.bundleBehavior === 'inline') {
|
|
// The referencing bundleRoot is the root of a Bundle that first brings in another bundle (essentially the FIRST parent of a bundle, this may or may not be a bundleGroup)
|
|
let [referencingBundleRoot, bundleGroupNodeId] = (0, _nullthrows().default)(stack[stack.length - 1]);
|
|
let bundleGroup = (0, _nullthrows().default)(bundleGraph.getNode(bundleGroupNodeId));
|
|
(0, _assert().default)(bundleGroup !== 'root');
|
|
let referencingBundleId = (0, _nullthrows().default)(bundleRoots.get(referencingBundleRoot))[0];
|
|
let referencingBundle = (0, _nullthrows().default)(bundleGraph.getNode(referencingBundleId));
|
|
(0, _assert().default)(referencingBundle !== 'root');
|
|
if (bundleId == null) {
|
|
var _dependency$bundleBeh2;
|
|
bundle = createBundle({
|
|
// Bundles created from type changes shouldn't have an entry asset.
|
|
asset: childAsset,
|
|
type: childAsset.type,
|
|
env: childAsset.env,
|
|
bundleBehavior: (_dependency$bundleBeh2 = dependency.bundleBehavior) !== null && _dependency$bundleBeh2 !== void 0 ? _dependency$bundleBeh2 : childAsset.bundleBehavior,
|
|
target: referencingBundle.target,
|
|
needsStableName: childAsset.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inline' || dependency.priority === 'parallel' && !dependency.needsStableName ? false : referencingBundle.needsStableName
|
|
});
|
|
bundleId = bundleGraph.addNode(bundle);
|
|
} else {
|
|
bundle = bundleGraph.getNode(bundleId);
|
|
(0, _assert().default)(bundle != null && bundle !== 'root');
|
|
if (
|
|
// If this dependency requests isolated, but the bundle is not,
|
|
// make the bundle isolated for all uses.
|
|
dependency.bundleBehavior === 'isolated' && bundle.bundleBehavior == null) {
|
|
bundle.bundleBehavior = dependency.bundleBehavior;
|
|
}
|
|
}
|
|
bundles.set(childAsset.id, bundleId);
|
|
|
|
// A bundle can belong to multiple bundlegroups, all the bundle groups of it's
|
|
// ancestors, and all async and entry bundles before it are "bundle groups"
|
|
// TODO: We may need to track bundles to all bundleGroups it belongs to in the future.
|
|
bundleRoots.set(childAsset, [bundleId, bundleGroupNodeId]);
|
|
bundleGraph.addEdge(referencingBundleId, bundleId);
|
|
if (bundleId != bundleGroupNodeId) {
|
|
dependencyBundleGraph.addEdge(dependencyBundleGraph.addNodeByContentKeyIfNeeded(dependency.id, {
|
|
value: dependency,
|
|
type: 'dependency'
|
|
}), dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(bundleId), {
|
|
value: bundle,
|
|
type: 'bundle'
|
|
}), dependencyPriorityEdges.parallel);
|
|
}
|
|
assetReference.get(childAsset).push([dependency, bundle]);
|
|
} else {
|
|
bundleId = null;
|
|
}
|
|
if (manualSharedObject && bundleId != null) {
|
|
// MSB Step 5: At this point we've either created or found an existing MSB bundle
|
|
// add the asset if it doesn't already have it and set key
|
|
|
|
(0, _assert().default)(bundle !== 'root' && bundle != null && bundleId != null);
|
|
manualAssetToBundle.set(childAsset, bundleId);
|
|
if (!bundle.assets.has(childAsset)) {
|
|
// Add asset to bundle
|
|
bundle.assets.add(childAsset);
|
|
bundle.size += childAsset.stats.size;
|
|
}
|
|
bundles.set(childAsset.id, bundleId);
|
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
(0, _assert().default)(manualSharedBundleKey != null);
|
|
// Ensure we set key to BundleId so the next glob match uses the appropriate bundle
|
|
if (!manualSharedMap.has(manualSharedBundleKey)) {
|
|
manualSharedMap.set(manualSharedBundleKey, bundleId);
|
|
}
|
|
bundle.manualSharedBundle = manualSharedObject.name;
|
|
bundle.uniqueKey = manualSharedObject.name + childAsset.type;
|
|
}
|
|
}
|
|
}
|
|
return node;
|
|
},
|
|
exit(node) {
|
|
var _stack;
|
|
if (((_stack = stack[stack.length - 1]) === null || _stack === void 0 ? void 0 : _stack[0]) === node.value) {
|
|
stack.pop();
|
|
}
|
|
}
|
|
}, null, {
|
|
skipUnusedDependencies: true
|
|
});
|
|
|
|
// Strip MSBs of entries
|
|
for (let [nodeId, internalizedAssets] of manualBundleToInternalizedAsset.entries()) {
|
|
let bundle = bundleGraph.getNode(nodeId);
|
|
(0, _assert().default)(bundle != null && bundle !== 'root');
|
|
if (!bundle.internalizedAssets) {
|
|
bundle.internalizedAssets = new (_graph().BitSet)(assets.length);
|
|
}
|
|
for (let asset of internalizedAssets) {
|
|
bundle.internalizedAssets.add((0, _nullthrows().default)(assetToIndex.get(asset)));
|
|
}
|
|
bundle.mainEntryAsset = null;
|
|
bundleGroupBundleIds.delete(nodeId); // manual bundles can now act as shared, non-bundle group, should they be non-bundleRoots as well?
|
|
}
|
|
|
|
/**
|
|
* Step Determine Reachability: Determine reachability for every asset from each bundleRoot.
|
|
* This is later used to determine which bundles to place each asset in. We build up two
|
|
* structures, one traversal each. ReachableRoots to store sync relationships,
|
|
* and bundleRootGraph to store the minimal availability through `parallel` and `async` relationships.
|
|
* The two graphs, are used to build up ancestorAssets, a structure which holds all availability by
|
|
* all means for each asset.
|
|
*/
|
|
let rootNodeId = bundleRootGraph.addNode(-1);
|
|
bundleRootGraph.setRootNodeId(rootNodeId);
|
|
for (let [root] of bundleRoots) {
|
|
let nodeId = bundleRootGraph.addNode((0, _nullthrows().default)(assetToIndex.get(root)));
|
|
assetToBundleRootNodeId.set(root, nodeId);
|
|
if (entries.has(root)) {
|
|
bundleRootGraph.addEdge(rootNodeId, nodeId);
|
|
}
|
|
}
|
|
|
|
// reachableRoots is an array of bit sets for each asset. Each bit set
|
|
// indicates which bundle roots are reachable from that asset synchronously.
|
|
let reachableRoots = [];
|
|
for (let i = 0; i < assets.length; i++) {
|
|
reachableRoots.push(new (_graph().BitSet)(bundleRootGraph.nodes.length));
|
|
}
|
|
|
|
// reachableAssets is the inverse mapping of reachableRoots. For each bundle root,
|
|
// it contains a bit set that indicates which assets are reachable from it.
|
|
let reachableAssets = [];
|
|
|
|
// ancestorAssets maps bundle roots to the set of all assets available to it at runtime,
|
|
// including in earlier parallel bundles. These are intersected through all paths to
|
|
// the bundle to ensure that the available assets are always present no matter in which
|
|
// order the bundles are loaded.
|
|
let ancestorAssets = [];
|
|
let inlineConstantDeps = new (_utils().DefaultMap)(() => new Set());
|
|
for (let [bundleRootId, assetId] of bundleRootGraph.nodes.entries()) {
|
|
let reachable = new (_graph().BitSet)(assets.length);
|
|
reachableAssets.push(reachable);
|
|
ancestorAssets.push(null);
|
|
if (bundleRootId == rootNodeId || assetId == null) continue;
|
|
// Add sync relationships to ReachableRoots
|
|
let root = assets[assetId];
|
|
assetGraph.traverse((node, _, actions) => {
|
|
if (node.value === root) {
|
|
return;
|
|
}
|
|
if (node.type === 'dependency') {
|
|
let dependency = node.value;
|
|
if (dependency.priority !== 'sync' && dependencyBundleGraph.hasContentKey(dependency.id)) {
|
|
let assets = assetGraph.getDependencyAssets(dependency);
|
|
if (assets.length === 0) {
|
|
return;
|
|
}
|
|
(0, _assert().default)(assets.length === 1);
|
|
let bundleRoot = assets[0];
|
|
let bundle = (0, _nullthrows().default)(bundleGraph.getNode((0, _nullthrows().default)(bundles.get(bundleRoot.id))));
|
|
if (bundle !== 'root' && bundle.bundleBehavior == null && !bundle.env.isIsolated() && bundle.env.context === root.env.context) {
|
|
bundleRootGraph.addEdge(bundleRootId, (0, _nullthrows().default)(assetToBundleRootNodeId.get(bundleRoot)), dependency.priority === 'parallel' ? bundleRootEdgeTypes.parallel : bundleRootEdgeTypes.lazy);
|
|
}
|
|
}
|
|
if (dependency.priority !== 'sync') {
|
|
actions.skipChildren();
|
|
}
|
|
return;
|
|
}
|
|
//asset node type
|
|
let asset = node.value;
|
|
if (asset.bundleBehavior != null) {
|
|
actions.skipChildren();
|
|
return;
|
|
}
|
|
let assetIndex = (0, _nullthrows().default)(assetToIndex.get(node.value));
|
|
reachable.add(assetIndex);
|
|
reachableRoots[assetIndex].add(bundleRootId);
|
|
if (asset.meta.isConstantModule === true) {
|
|
let parents = assetGraph.getIncomingDependencies(asset).map(dep => (0, _nullthrows().default)(assetGraph.getAssetWithDependency(dep)));
|
|
for (let parent of parents) {
|
|
inlineConstantDeps.get(parent).add(asset);
|
|
}
|
|
}
|
|
return;
|
|
}, root, {
|
|
skipUnusedDependencies: true
|
|
});
|
|
}
|
|
for (let entry of entries.keys()) {
|
|
// Initialize an empty set of ancestors available to entries
|
|
let entryId = (0, _nullthrows().default)(assetToBundleRootNodeId.get(entry));
|
|
ancestorAssets[entryId] = new (_graph().BitSet)(assets.length);
|
|
}
|
|
|
|
// Step Determine Availability
|
|
// Visit nodes in a topological order, visiting parent nodes before child nodes.
|
|
|
|
// This allows us to construct an understanding of which assets will already be
|
|
// loaded and available when a bundle runs, by pushing available assets downwards and
|
|
// computing the intersection of assets available through all possible paths to a bundle.
|
|
// We call this structure ancestorAssets, a Map that tracks a bundleRoot,
|
|
// to all assets available to it (meaning they will exist guaranteed when the bundleRoot is loaded)
|
|
// The topological sort ensures all parents are visited before the node we want to process.
|
|
for (let nodeId of bundleRootGraph.topoSort(_graph().ALL_EDGE_TYPES)) {
|
|
if (nodeId === rootNodeId) continue;
|
|
const bundleRoot = assets[(0, _nullthrows().default)(bundleRootGraph.getNode(nodeId))];
|
|
let bundleGroupId = (0, _nullthrows().default)(bundleRoots.get(bundleRoot))[1];
|
|
|
|
// At a BundleRoot, we access it's available assets (via ancestorAssets),
|
|
// and add to that all assets within the bundles in that BundleGroup.
|
|
|
|
// This set is available to all bundles in a particular bundleGroup because
|
|
// bundleGroups are just bundles loaded at the same time. However it is
|
|
// not true that a bundle's available assets = all assets of all the bundleGroups
|
|
// it belongs to. It's the intersection of those sets.
|
|
let available;
|
|
if (bundleRoot.bundleBehavior === 'isolated') {
|
|
available = new (_graph().BitSet)(assets.length);
|
|
} else {
|
|
available = (0, _nullthrows().default)(ancestorAssets[nodeId]).clone();
|
|
for (let bundleIdInGroup of [bundleGroupId, ...bundleGraph.getNodeIdsConnectedFrom(bundleGroupId)]) {
|
|
let bundleInGroup = (0, _nullthrows().default)(bundleGraph.getNode(bundleIdInGroup));
|
|
(0, _assert().default)(bundleInGroup !== 'root');
|
|
if (bundleInGroup.bundleBehavior != null) {
|
|
continue;
|
|
}
|
|
for (let bundleRoot of bundleInGroup.assets) {
|
|
// Assets directly connected to current bundleRoot
|
|
available.add((0, _nullthrows().default)(assetToIndex.get(bundleRoot)));
|
|
available.union(reachableAssets[(0, _nullthrows().default)(assetToBundleRootNodeId.get(bundleRoot))]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that we have bundleGroup availability, we will propagate that down to all the children
|
|
// of this bundleGroup. For a child, we also must maintain parallel availability. If it has
|
|
// parallel siblings that come before it, those, too, are available to it. Add those parallel
|
|
// available assets to the set of available assets for this child as well.
|
|
let children = bundleRootGraph.getNodeIdsConnectedFrom(nodeId, _graph().ALL_EDGE_TYPES);
|
|
let parallelAvailability = new (_graph().BitSet)(assets.length);
|
|
for (let childId of children) {
|
|
let assetId = (0, _nullthrows().default)(bundleRootGraph.getNode(childId));
|
|
let child = assets[assetId];
|
|
let bundleBehavior = getBundleFromBundleRoot(child).bundleBehavior;
|
|
if (bundleBehavior != null) {
|
|
continue;
|
|
}
|
|
let isParallel = bundleRootGraph.hasEdge(nodeId, childId, bundleRootEdgeTypes.parallel);
|
|
|
|
// Most of the time, a child will have many parent bundleGroups,
|
|
// so the next time we peek at a child from another parent, we will
|
|
// intersect the availability built there with the previously computed
|
|
// availability. this ensures no matter which bundleGroup loads a particular bundle,
|
|
// it will only assume availability of assets it has under any circumstance
|
|
const childAvailableAssets = ancestorAssets[childId];
|
|
let currentChildAvailable = isParallel ? _graph().BitSet.union(parallelAvailability, available) : available;
|
|
if (childAvailableAssets != null) {
|
|
childAvailableAssets.intersect(currentChildAvailable);
|
|
} else {
|
|
ancestorAssets[childId] = currentChildAvailable.clone();
|
|
}
|
|
if (isParallel) {
|
|
parallelAvailability.union(reachableAssets[childId]);
|
|
parallelAvailability.add(assetId); //The next sibling should have older sibling available via parallel
|
|
}
|
|
}
|
|
}
|
|
// Step Internalize async bundles - internalize Async bundles if and only if,
|
|
// the bundle is synchronously available elsewhere.
|
|
// We can query sync assets available via reachableRoots. If the parent has
|
|
// the bundleRoot by reachableRoots AND ancestorAssets, internalize it.
|
|
for (let [id, bundleRootId] of bundleRootGraph.nodes.entries()) {
|
|
if (bundleRootId == null || id === rootNodeId) continue;
|
|
let bundleRoot = assets[bundleRootId];
|
|
if (manualAssetToConfig.has(bundleRoot)) {
|
|
// We internalize for MSBs later, we should never delete MSBs
|
|
continue;
|
|
}
|
|
let parentRoots = bundleRootGraph.getNodeIdsConnectedTo(id, _graph().ALL_EDGE_TYPES);
|
|
let canDelete = getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated';
|
|
if (parentRoots.length === 0) continue;
|
|
for (let parentId of parentRoots) {
|
|
var _ancestorAssets$paren;
|
|
if (parentId === rootNodeId) {
|
|
// connected to root.
|
|
canDelete = false;
|
|
continue;
|
|
}
|
|
if (reachableAssets[parentId].has(bundleRootId) || (_ancestorAssets$paren = ancestorAssets[parentId]) !== null && _ancestorAssets$paren !== void 0 && _ancestorAssets$paren.has(bundleRootId)) {
|
|
let parentAssetId = (0, _nullthrows().default)(bundleRootGraph.getNode(parentId));
|
|
let parent = assets[parentAssetId];
|
|
let parentBundle = bundleGraph.getNode((0, _nullthrows().default)(bundles.get(parent.id)));
|
|
(0, _assert().default)(parentBundle != null && parentBundle !== 'root');
|
|
if (!parentBundle.internalizedAssets) {
|
|
parentBundle.internalizedAssets = new (_graph().BitSet)(assets.length);
|
|
}
|
|
parentBundle.internalizedAssets.add(bundleRootId);
|
|
} else {
|
|
canDelete = false;
|
|
}
|
|
}
|
|
if (canDelete) {
|
|
deleteBundle(bundleRoot);
|
|
}
|
|
}
|
|
function assignInlineConstants(parentAsset, bundle) {
|
|
for (let inlineConstant of inlineConstantDeps.get(parentAsset)) {
|
|
if (!bundle.assets.has(inlineConstant)) {
|
|
bundle.assets.add(inlineConstant);
|
|
bundle.size += inlineConstant.stats.size;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step Insert Or Share: Place all assets into bundles or create shared bundles. Each asset
|
|
// is placed into a single bundle based on the bundle entries it is reachable from.
|
|
// This creates a maximally code split bundle graph with no duplication.
|
|
let reachable = new (_graph().BitSet)(assets.length);
|
|
let reachableNonEntries = new (_graph().BitSet)(assets.length);
|
|
let reachableIntersection = new (_graph().BitSet)(assets.length);
|
|
for (let i = 0; i < assets.length; i++) {
|
|
let asset = assets[i];
|
|
let manualSharedObject = manualAssetToConfig.get(asset);
|
|
if (bundleRoots.has(asset) && inlineConstantDeps.get(asset).size > 0) {
|
|
let entryBundleId = (0, _nullthrows().default)(bundleRoots.get(asset))[0];
|
|
let entryBundle = (0, _nullthrows().default)(bundleGraph.getNode(entryBundleId));
|
|
(0, _assert().default)(entryBundle !== 'root');
|
|
assignInlineConstants(asset, entryBundle);
|
|
}
|
|
if (asset.meta.isConstantModule === true) {
|
|
// Ignore constant modules as they are placed with their direct parents
|
|
continue;
|
|
}
|
|
|
|
// Unreliable bundleRoot assets which need to pulled in by shared bundles or other means.
|
|
// Filter out entries, since they can't have shared bundles.
|
|
// Neither can non-splittable, isolated, or needing of stable name bundles.
|
|
// Reserve those filtered out bundles since we add the asset back into them.
|
|
reachableNonEntries.clear();
|
|
reachableRoots[i].forEach(nodeId => {
|
|
var _ancestorAssets$nodeI;
|
|
let assetId = bundleRootGraph.getNode(nodeId);
|
|
if (assetId == null) return; // deleted
|
|
let a = assets[assetId];
|
|
if (entries.has(a) || !a.isBundleSplittable || bundleRoots.get(a) && (getBundleFromBundleRoot(a).needsStableName || getBundleFromBundleRoot(a).bundleBehavior === 'isolated')) {
|
|
// Add asset to non-splittable bundles.
|
|
addAssetToBundleRoot(asset, a);
|
|
} else if (!((_ancestorAssets$nodeI = ancestorAssets[nodeId]) !== null && _ancestorAssets$nodeI !== void 0 && _ancestorAssets$nodeI.has(i))) {
|
|
// Filter out bundles from this asset's reachable array if
|
|
// bundle does not contain the asset in its ancestry
|
|
reachableNonEntries.add(assetId);
|
|
}
|
|
});
|
|
reachable.bits.set(reachableNonEntries.bits);
|
|
|
|
// If we encounter a "manual" asset, draw an edge from reachable to its MSB
|
|
if (manualSharedObject && !reachable.empty()) {
|
|
let bundle;
|
|
let bundleId;
|
|
let manualSharedBundleKey = manualSharedObject.name + ',' + asset.type;
|
|
let sourceBundles = [];
|
|
reachable.forEach(id => {
|
|
sourceBundles.push((0, _nullthrows().default)(bundleRoots.get(assets[id]))[0]);
|
|
});
|
|
if (!manualSharedMap.has(manualSharedBundleKey)) {
|
|
let firstSourceBundle = (0, _nullthrows().default)(bundleGraph.getNode(sourceBundles[0]));
|
|
(0, _assert().default)(firstSourceBundle !== 'root');
|
|
bundle = createBundle({
|
|
uniqueKey: manualSharedBundleKey,
|
|
target: firstSourceBundle.target,
|
|
type: asset.type,
|
|
env: firstSourceBundle.env,
|
|
manualSharedBundle: manualSharedObject === null || manualSharedObject === void 0 ? void 0 : manualSharedObject.name
|
|
});
|
|
bundle.sourceBundles = new Set(sourceBundles);
|
|
bundle.assets.add(asset);
|
|
bundleId = bundleGraph.addNode(bundle);
|
|
manualSharedMap.set(manualSharedBundleKey, bundleId);
|
|
} else {
|
|
bundleId = (0, _nullthrows().default)(manualSharedMap.get(manualSharedBundleKey));
|
|
bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
|
|
(0, _assert().default)(bundle != null && bundle !== 'root', 'We tried to use the root incorrectly');
|
|
if (!bundle.assets.has(asset)) {
|
|
bundle.assets.add(asset);
|
|
bundle.size += asset.stats.size;
|
|
}
|
|
for (let s of sourceBundles) {
|
|
if (s != bundleId) {
|
|
bundle.sourceBundles.add(s);
|
|
}
|
|
}
|
|
}
|
|
for (let sourceBundleId of sourceBundles) {
|
|
if (bundleId !== sourceBundleId) {
|
|
bundleGraph.addEdge(sourceBundleId, bundleId);
|
|
}
|
|
}
|
|
dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(bundleId), {
|
|
value: bundle,
|
|
type: 'bundle'
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Finally, filter out bundleRoots (bundles) from this assets
|
|
// reachable if they are subgraphs, and reuse that subgraph bundle
|
|
// by drawing an edge. Essentially, if two bundles within an asset's
|
|
// reachable array, have an ancestor-subgraph relationship, draw that edge.
|
|
// This allows for us to reuse a bundle instead of making a shared bundle if
|
|
// a bundle represents the exact set of assets a set of bundles would share
|
|
|
|
// if a bundle b is a subgraph of another bundle f, reuse it, drawing an edge between the two
|
|
if (config.disableSharedBundles === false) {
|
|
reachableNonEntries.forEach(candidateId => {
|
|
let candidateSourceBundleRoot = assets[candidateId];
|
|
let candidateSourceBundleId = (0, _nullthrows().default)(bundleRoots.get(candidateSourceBundleRoot))[0];
|
|
if (candidateSourceBundleRoot.env.isIsolated()) {
|
|
return;
|
|
}
|
|
let reuseableBundleId = bundles.get(asset.id);
|
|
if (reuseableBundleId != null) {
|
|
reachable.delete(candidateId);
|
|
bundleGraph.addEdge(candidateSourceBundleId, reuseableBundleId);
|
|
let reusableBundle = bundleGraph.getNode(reuseableBundleId);
|
|
(0, _assert().default)(reusableBundle !== 'root' && reusableBundle != null);
|
|
reusableBundle.sourceBundles.add(candidateSourceBundleId);
|
|
} else {
|
|
// Asset is not a bundleRoot, but if its ancestor bundle (in the asset's reachable) can be
|
|
// reused as a subgraph of another bundleRoot in its reachable, reuse it
|
|
reachableIntersection.bits.set(reachableNonEntries.bits);
|
|
reachableIntersection.intersect(reachableAssets[(0, _nullthrows().default)(assetToBundleRootNodeId.get(candidateSourceBundleRoot))]);
|
|
reachableIntersection.forEach(otherCandidateId => {
|
|
let otherReuseCandidate = assets[otherCandidateId];
|
|
if (candidateSourceBundleRoot === otherReuseCandidate) return;
|
|
let reusableBundleId = (0, _nullthrows().default)(bundles.get(otherReuseCandidate.id));
|
|
reachable.delete(candidateId);
|
|
bundleGraph.addEdge((0, _nullthrows().default)(bundles.get(candidateSourceBundleRoot.id)), reusableBundleId);
|
|
let reusableBundle = bundleGraph.getNode(reusableBundleId);
|
|
(0, _assert().default)(reusableBundle !== 'root' && reusableBundle != null);
|
|
reusableBundle.sourceBundles.add(candidateSourceBundleId);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
let reachableArray = [];
|
|
reachable.forEach(id => {
|
|
reachableArray.push(assets[id]);
|
|
});
|
|
|
|
// Create shared bundles for splittable bundles.
|
|
if (config.disableSharedBundles === false && reachableArray.length > config.minBundles) {
|
|
let sourceBundles = reachableArray.map(a => (0, _nullthrows().default)(bundleRoots.get(a))[0]);
|
|
let key = reachableArray.map(a => a.id).join(',') + '.' + asset.type;
|
|
let bundleId = bundles.get(key);
|
|
let bundle;
|
|
if (bundleId == null) {
|
|
let firstSourceBundle = (0, _nullthrows().default)(bundleGraph.getNode(sourceBundles[0]));
|
|
(0, _assert().default)(firstSourceBundle !== 'root');
|
|
bundle = createBundle({
|
|
target: firstSourceBundle.target,
|
|
type: asset.type,
|
|
env: firstSourceBundle.env
|
|
});
|
|
bundle.sourceBundles = new Set(sourceBundles);
|
|
let sharedInternalizedAssets = firstSourceBundle.internalizedAssets ? firstSourceBundle.internalizedAssets.clone() : new (_graph().BitSet)(assets.length);
|
|
for (let p of sourceBundles) {
|
|
let parentBundle = (0, _nullthrows().default)(bundleGraph.getNode(p));
|
|
(0, _assert().default)(parentBundle !== 'root');
|
|
if (parentBundle === firstSourceBundle) continue;
|
|
if (parentBundle.internalizedAssets) {
|
|
sharedInternalizedAssets.intersect(parentBundle.internalizedAssets);
|
|
} else {
|
|
sharedInternalizedAssets.clear();
|
|
}
|
|
}
|
|
bundle.internalizedAssets = sharedInternalizedAssets;
|
|
bundleId = bundleGraph.addNode(bundle);
|
|
bundles.set(key, bundleId);
|
|
} else {
|
|
bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
|
|
(0, _assert().default)(bundle !== 'root');
|
|
}
|
|
bundle.assets.add(asset);
|
|
bundle.size += asset.stats.size;
|
|
assignInlineConstants(asset, bundle);
|
|
for (let sourceBundleId of sourceBundles) {
|
|
if (bundleId !== sourceBundleId) {
|
|
bundleGraph.addEdge(sourceBundleId, bundleId);
|
|
}
|
|
}
|
|
dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(bundleId), {
|
|
value: bundle,
|
|
type: 'bundle'
|
|
});
|
|
} else if (config.disableSharedBundles === true || reachableArray.length <= config.minBundles) {
|
|
for (let root of reachableArray) {
|
|
addAssetToBundleRoot(asset, root);
|
|
}
|
|
}
|
|
}
|
|
let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
|
|
// Step split manual shared bundles for those that have the "split" property set
|
|
let remainderMap = new (_utils().DefaultMap)(() => []);
|
|
for (let id of manualSharedMap.values()) {
|
|
let manualBundle = bundleGraph.getNode(id);
|
|
(0, _assert().default)(manualBundle !== 'root' && manualBundle != null);
|
|
if (manualBundle.sourceBundles.size > 0) {
|
|
var _manualAssetToConfig$;
|
|
let firstSourceBundle = (0, _nullthrows().default)(bundleGraph.getNode([...manualBundle.sourceBundles][0]));
|
|
(0, _assert().default)(firstSourceBundle !== 'root');
|
|
let firstAsset = [...manualBundle.assets][0];
|
|
let manualSharedObject = manualAssetToConfig.get(firstAsset);
|
|
(0, _assert().default)(manualSharedObject != null);
|
|
let modNum = (_manualAssetToConfig$ = manualAssetToConfig.get(firstAsset)) === null || _manualAssetToConfig$ === void 0 ? void 0 : _manualAssetToConfig$.split;
|
|
if (modNum != null) {
|
|
for (let a of [...manualBundle.assets]) {
|
|
let numRep = getBigIntFromContentKey(a.id);
|
|
// $FlowFixMe Flow doesn't know about BigInt
|
|
let r = Number(numRep % BigInt(modNum));
|
|
remainderMap.get(r).push(a);
|
|
}
|
|
for (let i = 1; i < [...remainderMap.keys()].length; i++) {
|
|
let bundle = createBundle({
|
|
uniqueKey: manualSharedObject.name + firstSourceBundle.type + i,
|
|
target: firstSourceBundle.target,
|
|
type: firstSourceBundle.type,
|
|
env: firstSourceBundle.env,
|
|
manualSharedBundle: manualSharedObject.name
|
|
});
|
|
bundle.sourceBundles = manualBundle.sourceBundles;
|
|
bundle.internalizedAssets = manualBundle.internalizedAssets;
|
|
let bundleId = bundleGraph.addNode(bundle);
|
|
manualSharedBundleIds.add(bundleId);
|
|
for (let sourceBundleId of manualBundle.sourceBundles) {
|
|
if (bundleId !== sourceBundleId) {
|
|
bundleGraph.addEdge(sourceBundleId, bundleId);
|
|
}
|
|
}
|
|
for (let sp of remainderMap.get(i)) {
|
|
bundle.assets.add(sp);
|
|
bundle.size += sp.stats.size;
|
|
manualBundle.assets.delete(sp);
|
|
manualBundle.size -= sp.stats.size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step insert constant modules into manual shared bundles.
|
|
// We have to do this separately as they're the only case where a single asset can
|
|
// match multiple MSB's
|
|
for (let [asset, msbs] of constantModuleToMSB.entries()) {
|
|
for (let manualSharedObject of msbs) {
|
|
let bundleId = manualSharedMap.get(manualSharedObject.name + ',js');
|
|
if (bundleId == null) continue;
|
|
let bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
|
|
(0, _assert().default)(bundle != null && bundle !== 'root', 'We tried to use the root incorrectly');
|
|
if (!bundle.assets.has(asset)) {
|
|
bundle.assets.add(asset);
|
|
bundle.size += asset.stats.size;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
// their source bundles, and remove the bundle.
|
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
for (let [bundleNodeId, bundle] of bundleGraph.nodes.entries()) {
|
|
if (!bundle || bundle === 'root') continue;
|
|
if (bundle.sourceBundles.size > 0 && bundle.mainEntryAsset == null && bundle.size < config.minBundleSize && !manualSharedBundleIds.has(bundleNodeId)) {
|
|
removeBundle(bundleGraph, bundleNodeId, assetReference);
|
|
}
|
|
}
|
|
let modifiedSourceBundles = new Set();
|
|
|
|
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
if (config.disableSharedBundles === false) {
|
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
// Find shared bundles in this bundle group.
|
|
let bundleId = bundleGroupId;
|
|
|
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
let bundleIdsInGroup = getBundlesForBundleGroup(bundleId); //get all bundlegrups this bundle is an ancestor of
|
|
|
|
// Filter out inline assests as they should not contribute to PRL
|
|
let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
|
|
let bundle = (0, _nullthrows().default)(bundleGraph.getNode(b));
|
|
(0, _assert().default)(bundle !== 'root');
|
|
return count + (bundle.bundleBehavior !== 'inline');
|
|
}, 0);
|
|
if (numBundlesContributingToPRL > config.maxParallelRequests) {
|
|
let sharedBundleIdsInBundleGroup = bundleIdsInGroup.filter(b => {
|
|
let bundle = (0, _nullthrows().default)(bundleGraph.getNode(b));
|
|
// shared bundles must have source bundles, we could have a bundle
|
|
// connected to another bundle that isnt a shared bundle, so check
|
|
return bundle !== 'root' && bundle.sourceBundles.size > 0 && bundleId != b && !manualSharedBundleIds.has(b);
|
|
});
|
|
|
|
// Sort the bundles so the smallest ones are removed first.
|
|
let sharedBundlesInGroup = sharedBundleIdsInBundleGroup.map(id => ({
|
|
id,
|
|
bundle: (0, _nullthrows().default)(bundleGraph.getNode(id))
|
|
})).map(({
|
|
id,
|
|
bundle
|
|
}) => {
|
|
// For Flow
|
|
(0, _assert().default)(bundle !== 'root');
|
|
return {
|
|
id,
|
|
bundle
|
|
};
|
|
}).sort((a, b) => b.bundle.size - a.bundle.size);
|
|
|
|
// Remove bundles until the bundle group is within the parallel request limit.
|
|
while (sharedBundlesInGroup.length > 0 && numBundlesContributingToPRL > config.maxParallelRequests) {
|
|
let bundleTuple = sharedBundlesInGroup.pop();
|
|
let bundleToRemove = bundleTuple.bundle;
|
|
let bundleIdToRemove = bundleTuple.id;
|
|
//TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
|
|
// but total # bundles still exceeds limit due to non shared bundles
|
|
|
|
// Add all assets in the shared bundle into the source bundles that are within this bundle group.
|
|
let sourceBundles = [...bundleToRemove.sourceBundles].filter(b => bundleIdsInGroup.includes(b));
|
|
for (let sourceBundleId of sourceBundles) {
|
|
let sourceBundle = (0, _nullthrows().default)(bundleGraph.getNode(sourceBundleId));
|
|
(0, _assert().default)(sourceBundle !== 'root');
|
|
modifiedSourceBundles.add(sourceBundle);
|
|
bundleToRemove.sourceBundles.delete(sourceBundleId);
|
|
for (let asset of bundleToRemove.assets) {
|
|
addAssetToBundleRoot(asset, (0, _nullthrows().default)(sourceBundle.mainEntryAsset));
|
|
}
|
|
//This case is specific to reused bundles, which can have shared bundles attached to it
|
|
for (let childId of bundleGraph.getNodeIdsConnectedFrom(bundleIdToRemove)) {
|
|
let child = bundleGraph.getNode(childId);
|
|
(0, _assert().default)(child !== 'root' && child != null);
|
|
child.sourceBundles.add(sourceBundleId);
|
|
bundleGraph.addEdge(sourceBundleId, childId);
|
|
}
|
|
// needs to add test case where shared bundle is removed from ONE bundlegroup but not from the whole graph!
|
|
// Remove the edge from this bundle group to the shared bundle.
|
|
// If there is now only a single bundle group that contains this bundle,
|
|
// merge it into the remaining source bundles. If it is orphaned entirely, remove it.
|
|
let incomingNodeCount = bundleGraph.getNodeIdsConnectedTo(bundleIdToRemove).length;
|
|
if (incomingNodeCount <= 2 &&
|
|
//Never fully remove reused bundles
|
|
bundleToRemove.mainEntryAsset == null) {
|
|
// If one bundle group removes a shared bundle, but the other *can* keep it, still remove because that shared bundle is pointless (only one source bundle)
|
|
removeBundle(bundleGraph, bundleIdToRemove, assetReference);
|
|
// Stop iterating through bundleToRemove's sourceBundles as the bundle has been removed.
|
|
break;
|
|
} else {
|
|
bundleGraph.removeEdge(sourceBundleId, bundleIdToRemove);
|
|
}
|
|
}
|
|
numBundlesContributingToPRL--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function getBigIntFromContentKey(contentKey) {
|
|
let b = Buffer.alloc(64);
|
|
b.write(contentKey);
|
|
// $FlowFixMe Flow doesn't have BigInt types in this version
|
|
return b.readBigInt64BE();
|
|
}
|
|
// Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
|
|
if (modifiedSourceBundles.size > 0) {
|
|
let assetOrderMap = new Map(assets.map((a, index) => [a, index]));
|
|
for (let bundle of modifiedSourceBundles) {
|
|
bundle.assets = new Set([...bundle.assets].sort((a, b) => {
|
|
let aIndex = (0, _nullthrows().default)(assetOrderMap.get(a));
|
|
let bIndex = (0, _nullthrows().default)(assetOrderMap.get(b));
|
|
return aIndex - bIndex;
|
|
}));
|
|
}
|
|
}
|
|
function deleteBundle(bundleRoot) {
|
|
bundleGraph.removeNode((0, _nullthrows().default)(bundles.get(bundleRoot.id)));
|
|
bundleRoots.delete(bundleRoot);
|
|
bundles.delete(bundleRoot.id);
|
|
let bundleRootId = assetToBundleRootNodeId.get(bundleRoot);
|
|
if (bundleRootId != null && bundleRootGraph.hasNode(bundleRootId)) {
|
|
bundleRootGraph.removeNode(bundleRootId);
|
|
}
|
|
}
|
|
function getBundlesForBundleGroup(bundleGroupId) {
|
|
let bundlesInABundleGroup = [];
|
|
bundleGraph.traverse(nodeId => {
|
|
bundlesInABundleGroup.push(nodeId);
|
|
}, bundleGroupId);
|
|
return bundlesInABundleGroup;
|
|
}
|
|
function getBundleFromBundleRoot(bundleRoot) {
|
|
let bundle = bundleGraph.getNode((0, _nullthrows().default)(bundleRoots.get(bundleRoot))[0]);
|
|
(0, _assert().default)(bundle !== 'root' && bundle != null);
|
|
return bundle;
|
|
}
|
|
function addAssetToBundleRoot(asset, bundleRoot) {
|
|
let [bundleId, bundleGroupId] = (0, _nullthrows().default)(bundleRoots.get(bundleRoot));
|
|
let bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
|
|
(0, _assert().default)(bundle !== 'root');
|
|
if (asset.type !== bundle.type) {
|
|
let bundleGroup = (0, _nullthrows().default)(bundleGraph.getNode(bundleGroupId));
|
|
(0, _assert().default)(bundleGroup !== 'root');
|
|
let key = (0, _nullthrows().default)(bundleGroup.mainEntryAsset).id + '.' + asset.type;
|
|
let typeChangeBundleId = bundles.get(key);
|
|
if (typeChangeBundleId == null) {
|
|
let typeChangeBundle = createBundle({
|
|
uniqueKey: key,
|
|
needsStableName: bundle.needsStableName,
|
|
bundleBehavior: bundle.bundleBehavior,
|
|
type: asset.type,
|
|
target: bundle.target,
|
|
env: bundle.env
|
|
});
|
|
typeChangeBundleId = bundleGraph.addNode(typeChangeBundle);
|
|
bundleGraph.addEdge(bundleId, typeChangeBundleId);
|
|
bundles.set(key, typeChangeBundleId);
|
|
bundle = typeChangeBundle;
|
|
} else {
|
|
bundle = (0, _nullthrows().default)(bundleGraph.getNode(typeChangeBundleId));
|
|
(0, _assert().default)(bundle !== 'root');
|
|
}
|
|
}
|
|
bundle.assets.add(asset);
|
|
bundle.size += asset.stats.size;
|
|
assignInlineConstants(asset, bundle);
|
|
}
|
|
function removeBundle(bundleGraph, bundleId, assetReference) {
|
|
let bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
|
|
(0, _assert().default)(bundle !== 'root');
|
|
for (let asset of bundle.assets) {
|
|
assetReference.set(asset, assetReference.get(asset).filter(t => !t.includes(bundle)));
|
|
for (let sourceBundleId of bundle.sourceBundles) {
|
|
let sourceBundle = (0, _nullthrows().default)(bundleGraph.getNode(sourceBundleId));
|
|
(0, _assert().default)(sourceBundle !== 'root');
|
|
addAssetToBundleRoot(asset, (0, _nullthrows().default)(sourceBundle.mainEntryAsset));
|
|
}
|
|
}
|
|
bundleGraph.removeNode(bundleId);
|
|
}
|
|
return {
|
|
assets,
|
|
bundleGraph,
|
|
dependencyBundleGraph,
|
|
bundleGroupBundleIds,
|
|
assetReference,
|
|
manualAssetToBundle
|
|
};
|
|
}
|
|
const CONFIG_SCHEMA = {
|
|
type: 'object',
|
|
properties: {
|
|
http: {
|
|
type: 'number',
|
|
enum: Object.keys(HTTP_OPTIONS).map(k => Number(k))
|
|
},
|
|
manualSharedBundles: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
name: {
|
|
type: 'string'
|
|
},
|
|
assets: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string'
|
|
}
|
|
},
|
|
types: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string'
|
|
}
|
|
},
|
|
root: {
|
|
type: 'string'
|
|
},
|
|
split: {
|
|
type: 'number'
|
|
}
|
|
},
|
|
required: ['name', 'assets'],
|
|
additionalProperties: false
|
|
}
|
|
},
|
|
minBundles: {
|
|
type: 'number'
|
|
},
|
|
minBundleSize: {
|
|
type: 'number'
|
|
},
|
|
maxParallelRequests: {
|
|
type: 'number'
|
|
},
|
|
disableSharedBundles: {
|
|
type: 'boolean'
|
|
}
|
|
},
|
|
additionalProperties: false
|
|
};
|
|
function createBundle(opts) {
|
|
var _opts$type, _opts$env, _opts$bundleBehavior;
|
|
if (opts.asset == null) {
|
|
return {
|
|
uniqueKey: opts.uniqueKey,
|
|
assets: new Set(),
|
|
mainEntryAsset: null,
|
|
size: 0,
|
|
sourceBundles: new Set(),
|
|
target: opts.target,
|
|
type: (0, _nullthrows().default)(opts.type),
|
|
env: (0, _nullthrows().default)(opts.env),
|
|
needsStableName: Boolean(opts.needsStableName),
|
|
bundleBehavior: opts.bundleBehavior,
|
|
manualSharedBundle: opts.manualSharedBundle
|
|
};
|
|
}
|
|
let asset = (0, _nullthrows().default)(opts.asset);
|
|
return {
|
|
uniqueKey: opts.uniqueKey,
|
|
assets: new Set([asset]),
|
|
mainEntryAsset: asset,
|
|
size: asset.stats.size,
|
|
sourceBundles: new Set(),
|
|
target: opts.target,
|
|
type: (_opts$type = opts.type) !== null && _opts$type !== void 0 ? _opts$type : asset.type,
|
|
env: (_opts$env = opts.env) !== null && _opts$env !== void 0 ? _opts$env : asset.env,
|
|
needsStableName: Boolean(opts.needsStableName),
|
|
bundleBehavior: (_opts$bundleBehavior = opts.bundleBehavior) !== null && _opts$bundleBehavior !== void 0 ? _opts$bundleBehavior : asset.bundleBehavior,
|
|
manualSharedBundle: opts.manualSharedBundle
|
|
};
|
|
}
|
|
function resolveModeConfig(config, mode) {
|
|
let generalConfig = {};
|
|
let modeConfig = {};
|
|
for (const key of Object.keys(config)) {
|
|
if (key === 'development' || key === 'production') {
|
|
if (key === mode) {
|
|
modeConfig = config[key];
|
|
}
|
|
} else {
|
|
generalConfig[key] = config[key];
|
|
}
|
|
}
|
|
|
|
// $FlowFixMe Not sure how to convince flow here...
|
|
return {
|
|
...generalConfig,
|
|
...modeConfig
|
|
};
|
|
}
|
|
async function loadBundlerConfig(config, options, logger) {
|
|
var _modeConfig$http, _modeConfig$minBundle, _modeConfig$minBundle2, _modeConfig$maxParall, _modeConfig$disableSh, _modeConfig$manualSha;
|
|
let conf = await config.getConfig([], {
|
|
packageKey: '@parcel/bundler-default'
|
|
});
|
|
if (!conf) {
|
|
const modDefault = {
|
|
...HTTP_OPTIONS['2'],
|
|
projectRoot: options.projectRoot
|
|
};
|
|
return modDefault;
|
|
}
|
|
(0, _assert().default)((conf === null || conf === void 0 ? void 0 : conf.contents) != null);
|
|
let modeConfig = resolveModeConfig(conf.contents, options.mode);
|
|
|
|
// minBundles will be ignored if shared bundles are disabled
|
|
if (modeConfig.minBundles != null && modeConfig.disableSharedBundles === true) {
|
|
logger.warn({
|
|
origin: '@parcel/bundler-default',
|
|
message: `The value of "${modeConfig.minBundles}" set for minBundles will not be used as shared bundles have been disabled`
|
|
});
|
|
}
|
|
|
|
// minBundleSize will be ignored if shared bundles are disabled
|
|
if (modeConfig.minBundleSize != null && modeConfig.disableSharedBundles === true) {
|
|
logger.warn({
|
|
origin: '@parcel/bundler-default',
|
|
message: `The value of "${modeConfig.minBundleSize}" set for minBundleSize will not be used as shared bundles have been disabled`
|
|
});
|
|
}
|
|
|
|
// maxParallelRequests will be ignored if shared bundles are disabled
|
|
if (modeConfig.maxParallelRequests != null && modeConfig.disableSharedBundles === true) {
|
|
logger.warn({
|
|
origin: '@parcel/bundler-default',
|
|
message: `The value of "${modeConfig.maxParallelRequests}" set for maxParallelRequests will not be used as shared bundles have been disabled`
|
|
});
|
|
}
|
|
if (modeConfig.manualSharedBundles) {
|
|
let nameArray = modeConfig.manualSharedBundles.map(a => a.name);
|
|
let nameSet = new Set(nameArray);
|
|
(0, _assert().default)(nameSet.size == nameArray.length, 'The name field must be unique for property manualSharedBundles');
|
|
}
|
|
_utils().validateSchema.diagnostic(CONFIG_SCHEMA, {
|
|
data: modeConfig,
|
|
source: await options.inputFS.readFile(conf.filePath, 'utf8'),
|
|
filePath: conf.filePath,
|
|
prependKey: `/${(0, _diagnostic().encodeJSONKeyComponent)('@parcel/bundler-default')}`
|
|
}, '@parcel/bundler-default', 'Invalid config for @parcel/bundler-default');
|
|
let http = (_modeConfig$http = modeConfig.http) !== null && _modeConfig$http !== void 0 ? _modeConfig$http : 2;
|
|
let defaults = HTTP_OPTIONS[http];
|
|
return {
|
|
minBundles: (_modeConfig$minBundle = modeConfig.minBundles) !== null && _modeConfig$minBundle !== void 0 ? _modeConfig$minBundle : defaults.minBundles,
|
|
minBundleSize: (_modeConfig$minBundle2 = modeConfig.minBundleSize) !== null && _modeConfig$minBundle2 !== void 0 ? _modeConfig$minBundle2 : defaults.minBundleSize,
|
|
maxParallelRequests: (_modeConfig$maxParall = modeConfig.maxParallelRequests) !== null && _modeConfig$maxParall !== void 0 ? _modeConfig$maxParall : defaults.maxParallelRequests,
|
|
projectRoot: options.projectRoot,
|
|
disableSharedBundles: (_modeConfig$disableSh = modeConfig.disableSharedBundles) !== null && _modeConfig$disableSh !== void 0 ? _modeConfig$disableSh : defaults.disableSharedBundles,
|
|
manualSharedBundles: (_modeConfig$manualSha = modeConfig.manualSharedBundles) !== null && _modeConfig$manualSha !== void 0 ? _modeConfig$manualSha : defaults.manualSharedBundles
|
|
};
|
|
}
|
|
function getEntryByTarget(bundleGraph) {
|
|
// Find entries from assetGraph per target
|
|
let targets = new (_utils().DefaultMap)(() => new Map());
|
|
bundleGraph.traverse({
|
|
enter(node, context, actions) {
|
|
if (node.type !== 'asset') {
|
|
return node;
|
|
}
|
|
(0, _assert().default)(context != null && context.type === 'dependency' && context.value.isEntry && context.value.target != null);
|
|
targets.get(context.value.target.distDir).set(node.value, context.value);
|
|
actions.skipChildren();
|
|
return node;
|
|
}
|
|
});
|
|
return targets;
|
|
} |