diff --git a/packages/bundlers/default/src/DefaultBundler.js b/packages/bundlers/default/src/DefaultBundler.js index 792530525da..82dccc5f75c 100644 --- a/packages/bundlers/default/src/DefaultBundler.js +++ b/packages/bundlers/default/src/DefaultBundler.js @@ -11,44 +11,64 @@ import type { MutableBundleGraph, PluginOptions, Target, + BuildMode, } from '@parcel/types'; import type {NodeId} from '@parcel/graph'; import type {SchemaEntity} from '@parcel/utils'; -import {ContentGraph, Graph, BitSet} from '@parcel/graph'; +import {ContentGraph, Graph, BitSet, ALL_EDGE_TYPES} from '@parcel/graph'; import invariant from 'assert'; -import {ALL_EDGE_TYPES} from '@parcel/graph'; import {Bundler} from '@parcel/plugin'; +import {setEqual, validateSchema, DefaultMap, globToRegex} from '@parcel/utils'; import logger from '@parcel/logger'; -import {setEqual, validateSchema, DefaultMap} from '@parcel/utils'; import nullthrows from 'nullthrows'; +import path from 'path'; import {encodeJSONKeyComponent} from '@parcel/diagnostic'; -type BundlerConfig = {| +type Glob = string; + +type ManualSharedBundles = Array<{| + name: string, + assets: Array, + types?: Array, + parent?: string, + split?: number, +|}>; + +type BaseBundlerConfig = {| http?: number, minBundles?: number, minBundleSize?: number, maxParallelRequests?: number, disableSharedBundles?: boolean, + unstable_manualSharedBundles?: ManualSharedBundles, |}; +type BundlerConfig = {| + [mode: BuildMode]: BaseBundlerConfig, +|} & BaseBundlerConfig; + type ResolvedBundlerConfig = {| minBundles: number, minBundleSize: number, maxParallelRequests: number, + projectRoot: string, disableSharedBundles: boolean, + manualSharedBundles: ManualSharedBundles, |}; // 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, @@ -69,6 +89,7 @@ export type Bundle = {| target: Target, env: Environment, type: string, + manualSharedBundle: ?string, // for naming purposes |}; const dependencyPriorityEdges = { @@ -97,6 +118,7 @@ type IdealGraph = {| bundleGraph: Graph, bundleGroupBundleIds: Set, assetReference: DefaultMap>, + manualAssetToBundle: Map, |}; /** @@ -143,16 +165,22 @@ function decorateLegacyGraph( bundleGraph: idealBundleGraph, dependencyBundleGraph, bundleGroupBundleIds, + manualAssetToBundle, } = idealGraph; let entryBundleToBundleGroup: Map = 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)) { + invariant( + idealBundle.manualSharedBundle == null, + 'Unstable Manual Shared Bundle feature is processing a manualSharedBundle as a BundleGroup', + ); let dependencies = dependencyBundleGraph .getNodeIdsConnectedTo( dependencyBundleGraph.getNodeIdByContentKey(String(bundleNodeId)), @@ -163,11 +191,16 @@ function decorateLegacyGraph( invariant(dependency.type === 'dependency'); return dependency.value; }); + invariant( + entryAsset != null, + 'Processing a bundleGroup with no entry asset', + ); for (let dependency of dependencies) { bundleGroup = bundleGraph.createBundleGroup( dependency, idealBundle.target, ); + bundleGroups.push(bundleGroup); } invariant(bundleGroup); entryBundleToBundleGroup.set(bundleNodeId, bundleGroup); @@ -178,6 +211,7 @@ function decorateLegacyGraph( needsStableName: idealBundle.needsStableName, bundleBehavior: idealBundle.bundleBehavior, target: idealBundle.target, + manualSharedBundle: idealBundle.manualSharedBundle, }), ); @@ -196,6 +230,7 @@ function decorateLegacyGraph( type: idealBundle.type, target: idealBundle.target, env: idealBundle.env, + manualSharedBundle: idealBundle.manualSharedBundle, }), ); } else if (idealBundle.uniqueKey != null) { @@ -207,6 +242,7 @@ function decorateLegacyGraph( type: idealBundle.type, target: idealBundle.target, env: idealBundle.env, + manualSharedBundle: idealBundle.manualSharedBundle, }), ); } else { @@ -217,6 +253,7 @@ function decorateLegacyGraph( needsStableName: idealBundle.needsStableName, bundleBehavior: idealBundle.bundleBehavior, target: idealBundle.target, + manualSharedBundle: idealBundle.manualSharedBundle, }), ); } @@ -248,6 +285,23 @@ function decorateLegacyGraph( }); } } + // 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) => { @@ -369,6 +423,97 @@ function createIdealGraph( let assetToIndex = new Map(); let typeChangeIds = new Set(); + function makeManualAssetToConfigLookup() { + let manualAssetToConfig = new Map(); + let constantModuleToMSB = new DefaultMap(() => []); + + if (config.manualSharedBundles.length === 0) { + return {manualAssetToConfig, constantModuleToMSB}; + } + + let parentsToConfig = new DefaultMap(() => []); + + for (let c of config.manualSharedBundles) { + if (c.parent != null) { + parentsToConfig.get(path.join(config.projectRoot, c.parent)).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()) { + invariant( + c.parent == null || configToParentAsset.has(c), + 'Invalid manual shared bundle. Could not find parent asset.', + ); + + let parentAsset = configToParentAsset.get(c); + let assetRegexes = c.assets.map(glob => 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}; + } + + //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: Map = 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: Map = new Map(); + let {manualAssetToConfig, constantModuleToMSB} = + makeManualAssetToConfigLookup(); + let manualBundleToInternalizedAsset: Map< + NodeId, + Array, + > = new DefaultMap(() => []); + /** * Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles * for asset type changes, parallel, inline, and async or lazy dependencies, @@ -403,6 +548,7 @@ function createIdealGraph( return node; } let dependency = node.value; + invariant(context?.type === 'asset'); let parentAsset = context.value; @@ -412,12 +558,31 @@ function createIdealGraph( } for (let childAsset of assets) { + // 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 ( dependency.priority === 'lazy' || childAsset.bundleBehavior === 'isolated' // An isolated Dependency, or Bundle must contain all assets it needs to load. ) { let bundleId = bundles.get(childAsset.id); let bundle; + + if ( + // MSB Step 3: If a bundle for these globs already exsits, use it + manualSharedBundleKey != null && + manualSharedMap.has(manualSharedBundleKey) + ) { + bundleId = nullthrows( + manualSharedMap.get(manualSharedBundleKey), + ); + } if (bundleId == null) { let firstBundleGroup = nullthrows( bundleGraph.getNode(stack[0][1]), @@ -439,6 +604,22 @@ function createIdealGraph( bundleRoots.set(childAsset, [bundleId, bundleId]); bundleGroupBundleIds.add(bundleId); bundleGraph.addEdge(bundleGraphRootNodeId, bundleId); + if (manualSharedObject) { + // MSB Step 4: If this is the first instance of a match, set it appropriately + //If this was an existing glob we must add the asset + manualAssetToBundle.set(childAsset, bundleId); // Add asset to bundle + + invariant(bundle !== 'root' && bundle !== null); + bundle.size = childAsset.stats.size; + invariant(manualSharedBundleKey != null); + if (!manualSharedMap.has(manualSharedBundleKey)) { + manualSharedMap.set(manualSharedBundleKey, bundleId); + } + nullthrows( + manualBundleToInternalizedAsset.get(bundleId), + ).push(childAsset); + bundle.manualSharedBundle = manualSharedObject.name; + } } else { bundle = nullthrows(bundleGraph.getNode(bundleId)); invariant(bundle !== 'root'); @@ -451,6 +632,32 @@ function createIdealGraph( ) { bundle.bundleBehavior = dependency.bundleBehavior; } + if (manualSharedObject) { + // MSB Step 5: If a bundle for this asset already exists and we have a glob match + // simply add the asset if it doesn't already have it + // If this was an existing glob we must add the asset + manualAssetToBundle.set(childAsset, bundleId); // Add asset to bundle + + invariant(bundle !== 'root' && bundle !== null); + + if (!bundle.assets.has(childAsset)) { + bundle.assets.add(childAsset); + bundle.size += childAsset.stats.size; + } + + bundles.set(childAsset.id, bundleId); + bundleRoots.set(childAsset, [bundleId, bundleId]); + + nullthrows( + manualBundleToInternalizedAsset.get(bundleId), + ).push(childAsset); + + invariant(manualSharedBundleKey != null); + if (!manualSharedMap.has(manualSharedBundleKey)) { + manualSharedMap.set(manualSharedBundleKey, bundleId); + } + bundle.manualSharedBundle = manualSharedObject.name; + } } dependencyBundleGraph.addEdge( @@ -512,6 +719,17 @@ function createIdealGraph( ) { bundleId = bundleGroupNodeId; } + if ( + // MSB Step 3 alt: If a bundle for these globs already exsits, use it + manualSharedBundleKey != null && + manualSharedMap.has(manualSharedBundleKey) + ) { + // If theres an existing bundle for this glob, add the asset + bundleId = nullthrows( + manualSharedMap.get(manualSharedBundleKey), + ); + bundle = nullthrows(bundleGraph.getNode(bundleId)); + } if (bundleId == null) { bundle = createBundle({ // Bundles created from type changes shouldn't have an entry asset. @@ -535,6 +753,20 @@ function createIdealGraph( if (parentAsset.type !== childAsset.type) { typeChangeIds.add(bundleId); } + if (manualSharedObject) { + //If this was an existing glob we must add the asset + manualAssetToBundle.set(childAsset, bundleId); // Add asset to bundle + + invariant(bundle !== 'root' && bundle !== null); + bundle.assets.add(childAsset); + bundle.size += childAsset.stats.size; + bundles.set(childAsset.id, bundleId); // bundleRoots.set(childAsset, [bundleId, bundleId]); + invariant(manualSharedBundleKey != null); + if (!manualSharedMap.has(manualSharedBundleKey)) { + manualSharedMap.set(manualSharedBundleKey, bundleId); + } + bundle.manualSharedBundle = manualSharedObject.name; + } } else { bundle = bundleGraph.getNode(bundleId); invariant(bundle != null && bundle !== 'root'); @@ -548,6 +780,22 @@ function createIdealGraph( bundle.bundleBehavior = dependency.bundleBehavior; } } + // GLOB MATCHING - If the bundle did exist, added the new asset, if the bundle was jsut created, add the key to map + if (manualSharedObject) { + // If this was an existing glob we must add the asset + manualAssetToBundle.set(childAsset, bundleId); // Add asset to bundle + + invariant(bundle !== 'root' && bundle !== null); + bundle.assets.add(childAsset); + bundle.size += childAsset.stats.size; + bundles.set(childAsset.id, bundleId); + + invariant(manualSharedBundleKey != null); + if (!manualSharedMap.has(manualSharedBundleKey)) { + manualSharedMap.set(manualSharedBundleKey, bundleId); + } + bundle.manualSharedBundle = manualSharedObject.name; + } bundles.set(childAsset.id, bundleId); @@ -594,6 +842,24 @@ function createIdealGraph( {skipUnusedDependencies: true}, ); + // Strip MSBs of entries + for (let [ + nodeId, + internalizedAssets, + ] of manualBundleToInternalizedAsset.entries()) { + let bundle = bundleGraph.getNode(nodeId); + invariant(bundle != null && bundle !== 'root'); + + if (!bundle.internalizedAssets) { + bundle.internalizedAssets = new BitSet(assets.length); + } + for (let asset of internalizedAssets) { + bundle.internalizedAssets.add(nullthrows(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 Merge Type Change Bundles: Clean up type change bundles within the exact same bundlegroups for (let [nodeIdA, a] of bundleGraph.nodes.entries()) { //if bundle b bundlegroups ==== bundle a bundlegroups then combine type changes @@ -641,7 +907,6 @@ function createIdealGraph( * 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); @@ -676,7 +941,6 @@ function createIdealGraph( ancestorAssets.push(null); if (bundleRootId == rootNodeId || assetId == null) continue; - // Add sync relationships to ReachableRoots let root = assets[assetId]; assetGraph.traverse( @@ -840,6 +1104,12 @@ function createIdealGraph( 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, ALL_EDGE_TYPES); let canDelete = getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated'; @@ -873,7 +1143,6 @@ function createIdealGraph( deleteBundle(bundleRoot); } } - // 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. @@ -882,6 +1151,7 @@ function createIdealGraph( let reachableIntersection = new BitSet(assets.length); for (let i = 0; i < assets.length; i++) { let asset = assets[i]; + let manualSharedObject = manualAssetToConfig.get(asset); if (asset.meta.isConstantModule === true) { // Add assets to non-splittable bundles. @@ -928,6 +1198,67 @@ function createIdealGraph( } }); + 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(nullthrows(bundleRoots.get(assets[id]))[0]); + }); + + if (!manualSharedMap.has(manualSharedBundleKey)) { + let firstSourceBundle = nullthrows( + bundleGraph.getNode(sourceBundles[0]), + ); + invariant(firstSourceBundle !== 'root'); + + bundle = createBundle({ + target: firstSourceBundle.target, + type: firstSourceBundle.type, + env: firstSourceBundle.env, + manualSharedBundle: manualSharedObject?.name, + }); + bundle.sourceBundles = new Set(sourceBundles); + bundle.assets.add(asset); + bundleId = bundleGraph.addNode(bundle); + manualSharedMap.set(manualSharedBundleKey, bundleId); + } else { + bundleId = nullthrows(manualSharedMap.get(manualSharedBundleKey)); + bundle = nullthrows(bundleGraph.getNode(bundleId)); + invariant( + 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 @@ -936,7 +1267,6 @@ function createIdealGraph( // 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 - reachable.bits.set(reachableNonEntries.bits); if (config.disableSharedBundles === false) { reachableNonEntries.forEach(candidateId => { let candidateSourceBundleRoot = assets[candidateId]; @@ -963,6 +1293,7 @@ function createIdealGraph( nullthrows(assetToBundleRootNodeId.get(candidateSourceBundleRoot)) ], ); + reachableIntersection.forEach(otherCandidateId => { let otherReuseCandidate = assets[otherCandidateId]; if (candidateSourceBundleRoot === otherReuseCandidate) return; @@ -1058,6 +1389,79 @@ function createIdealGraph( } } } + + let manualSharedBundleIds = new Set([...manualSharedMap.values()]); + // Step split manual shared bundles for those that have the "split" property set + let remainderMap = new DefaultMap(() => []); + for (let [manualName, id] of manualSharedMap) { + let manualBundle = bundleGraph.getNode(id); + invariant(manualBundle !== 'root' && manualBundle != null); + + if (manualBundle.sourceBundles.size > 0) { + let firstSourceBundle = nullthrows( + bundleGraph.getNode([...manualBundle.sourceBundles][0]), + ); + invariant(firstSourceBundle !== 'root'); + let firstAsset = [...manualBundle.assets][0]; + let manualSharedObject = manualAssetToConfig.get(firstAsset); + invariant(manualSharedObject != null); + let modNum = manualAssetToConfig.get(firstAsset)?.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({ + 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 = nullthrows(bundleGraph.getNode(bundleId)); + invariant( + 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 @@ -1066,7 +1470,8 @@ function createIdealGraph( if ( bundle.sourceBundles.size > 0 && bundle.mainEntryAsset == null && - bundle.size < config.minBundleSize + bundle.size < config.minBundleSize && + !manualSharedBundleIds.has(bundleNodeId) ) { removeBundle(bundleGraph, bundleNodeId, assetReference); } @@ -1096,7 +1501,10 @@ function createIdealGraph( // 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 + bundle !== 'root' && + bundle.sourceBundles.size > 0 && + bundleId != b && + !manualSharedBundleIds.has(b) ); }); @@ -1173,6 +1581,12 @@ function createIdealGraph( } } + 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])); @@ -1297,6 +1711,7 @@ function createIdealGraph( dependencyBundleGraph, bundleGroupBundleIds, assetReference, + manualAssetToBundle, }; } @@ -1307,6 +1722,37 @@ const CONFIG_SCHEMA: SchemaEntity = { type: 'number', enum: Object.keys(HTTP_OPTIONS).map(k => Number(k)), }, + unstable_manualSharedBundles: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + assets: { + type: 'array', + items: { + type: 'string', + }, + }, + types: { + type: 'array', + items: { + type: 'string', + }, + }, + parent: { + type: 'string', + }, + split: { + type: 'number', + }, + }, + required: ['name', 'assets'], + additionalProperties: false, + }, + }, minBundles: { type: 'number', }, @@ -1331,6 +1777,7 @@ function createBundle(opts: {| type?: string, needsStableName?: boolean, bundleBehavior?: ?BundleBehavior, + manualSharedBundle?: ?string, |}): Bundle { if (opts.asset == null) { return { @@ -1344,6 +1791,7 @@ function createBundle(opts: {| env: nullthrows(opts.env), needsStableName: Boolean(opts.needsStableName), bundleBehavior: opts.bundleBehavior, + manualSharedBundle: opts.manualSharedBundle, }; } @@ -1359,6 +1807,7 @@ function createBundle(opts: {| env: opts.env ?? asset.env, needsStableName: Boolean(opts.needsStableName), bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior, + manualSharedBundle: opts.manualSharedBundle, }; } @@ -1385,6 +1834,30 @@ function removeBundle( bundleGraph.removeNode(bundleId); } +function resolveModeConfig( + config: BundlerConfig, + mode: BuildMode, +): BaseBundlerConfig { + 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: Config, options: PluginOptions, @@ -1394,48 +1867,54 @@ async function loadBundlerConfig( }); if (!conf) { - return HTTP_OPTIONS['2']; + const modDefault = { + ...HTTP_OPTIONS['2'], + projectRoot: options.projectRoot, + }; + return modDefault; } invariant(conf?.contents != null); + let modeConfig = resolveModeConfig(conf.contents, options.mode); + // minBundles will be ignored if shared bundles are disabled if ( - conf.contents.minBundles != null && - conf.contents.disableSharedBundles === true + modeConfig.minBundles != null && + modeConfig.disableSharedBundles === true ) { logger.warn({ origin: '@parcel/bundler-default', - message: `The value of "${conf.contents.minBundles}" set for minBundles will not be used as shared bundles have been disabled`, + 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 ( - conf.contents.minBundleSize != null && - conf.contents.disableSharedBundles === true + modeConfig.minBundleSize != null && + modeConfig.disableSharedBundles === true ) { logger.warn({ origin: '@parcel/bundler-default', - message: `The value of "${conf.contents.minBundleSize}" set for minBundleSize will not be used as shared bundles have been disabled`, + 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 ( - conf.contents.maxParallelRequests != null && - conf.contents.disableSharedBundles === true + modeConfig.maxParallelRequests != null && + modeConfig.disableSharedBundles === true ) { logger.warn({ origin: '@parcel/bundler-default', - message: `The value of "${conf.contents.maxParallelRequests}" set for maxParallelRequests will not be used as shared bundles have been disabled`, + message: `The value of "${modeConfig.maxParallelRequests}" set for maxParallelRequests will not be used as shared bundles have been disabled`, }); } validateSchema.diagnostic( CONFIG_SCHEMA, { - data: conf?.contents, + data: modeConfig, source: await options.inputFS.readFile(conf.filePath, 'utf8'), filePath: conf.filePath, prependKey: `/${encodeJSONKeyComponent('@parcel/bundler-default')}`, @@ -1444,16 +1923,19 @@ async function loadBundlerConfig( 'Invalid config for @parcel/bundler-default', ); - let http = conf.contents.http ?? 2; + let http = modeConfig.http ?? 2; let defaults = HTTP_OPTIONS[http]; return { - minBundles: conf.contents.minBundles ?? defaults.minBundles, - minBundleSize: conf.contents.minBundleSize ?? defaults.minBundleSize, + minBundles: modeConfig.minBundles ?? defaults.minBundles, + minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize, maxParallelRequests: - conf.contents.maxParallelRequests ?? defaults.maxParallelRequests, + modeConfig.maxParallelRequests ?? defaults.maxParallelRequests, + projectRoot: options.projectRoot, disableSharedBundles: - conf.contents.disableSharedBundles ?? defaults.disableSharedBundles, + modeConfig.disableSharedBundles ?? defaults.disableSharedBundles, + manualSharedBundles: + modeConfig.unstable_manualSharedBundles ?? defaults.manualSharedBundles, }; } diff --git a/packages/core/core/src/public/Bundle.js b/packages/core/core/src/public/Bundle.js index c138b67c737..9e9b29dac91 100644 --- a/packages/core/core/src/public/Bundle.js +++ b/packages/core/core/src/public/Bundle.js @@ -132,6 +132,10 @@ export class Bundle implements IBundle { return this.#bundle.isSplittable; } + get manualSharedBundle(): ?string { + return this.#bundle.manualSharedBundle; + } + get target(): ITarget { return new Target(this.#bundle.target, this.#options); } diff --git a/packages/core/core/src/public/MutableBundleGraph.js b/packages/core/core/src/public/MutableBundleGraph.js index 16a60c5f868..7144aad779c 100644 --- a/packages/core/core/src/public/MutableBundleGraph.js +++ b/packages/core/core/src/public/MutableBundleGraph.js @@ -9,7 +9,11 @@ import type { MutableBundleGraph as IMutableBundleGraph, Target, } from '@parcel/types'; -import type {ParcelOptions, BundleGroup as InternalBundleGroup} from '../types'; +import type { + ParcelOptions, + BundleGroup as InternalBundleGroup, + BundleNode, +} from '../types'; import invariant from 'assert'; import nullthrows from 'nullthrows'; @@ -204,7 +208,7 @@ export default class MutableBundleGraph isPlaceholder = entryAssetNode.requested === false; } - let bundleNode = { + let bundleNode: BundleNode = { type: 'bundle', id: bundleId, value: { @@ -232,6 +236,7 @@ export default class MutableBundleGraph name: null, displayName: null, publicId, + manualSharedBundle: opts.manualSharedBundle, }, }; diff --git a/packages/core/core/src/types.js b/packages/core/core/src/types.js index a9b2d3c56f3..d68eb410b95 100644 --- a/packages/core/core/src/types.js +++ b/packages/core/core/src/types.js @@ -518,6 +518,7 @@ export type Bundle = {| name: ?string, displayName: ?string, pipeline: ?string, + manualSharedBundle?: ?string, |}; export type BundleNode = {| diff --git a/packages/core/graph/src/BitSet.js b/packages/core/graph/src/BitSet.js index 21cb6e66a33..e7135ef2597 100644 --- a/packages/core/graph/src/BitSet.js +++ b/packages/core/graph/src/BitSet.js @@ -55,6 +55,16 @@ export class BitSet { return Boolean(this.bits[i] & (1 << b)); } + empty(): boolean { + for (let k = 0; k < this.bits.length; k++) { + if (this.bits[k] !== 0) { + return false; + } + } + + return true; + } + clear() { this.bits.fill(0); } diff --git a/packages/core/graph/test/BitSet.test.js b/packages/core/graph/test/BitSet.test.js index 26134710a93..47d77875fee 100644 --- a/packages/core/graph/test/BitSet.test.js +++ b/packages/core/graph/test/BitSet.test.js @@ -55,6 +55,18 @@ describe('BitSet', () => { assertValues(set1, [1, 5]); }); + it('empty should check if there are no values set', () => { + let set1 = new BitSet(5); + + assert(set1.empty()); + + set1.add(3); + assert(!set1.empty()); + + set1.delete(3); + assert(set1.empty()); + }); + it('should intersect with another BitSet', () => { let set1 = new BitSet(5); set1.add(1); diff --git a/packages/core/integration-tests/test/bundler.js b/packages/core/integration-tests/test/bundler.js index 1c1f11518c1..e73d6956a83 100644 --- a/packages/core/integration-tests/test/bundler.js +++ b/packages/core/integration-tests/test/bundler.js @@ -1,20 +1,61 @@ import path from 'path'; import assert from 'assert'; import Logger from '@parcel/logger'; -import {bundle, assertBundles, findAsset} from '@parcel/test-utils'; +import { + bundle, + assertBundles, + findAsset, + overlayFS, + fsFixture, + run, +} from '@parcel/test-utils'; describe('bundler', function () { it('should not create shared bundles when a bundle is being reused and disableSharedBundles is enabled', async function () { + await fsFixture(overlayFS, __dirname)` + disable-shared-bundle-single-source + a.js: + import foo from './foo'; + + export default 5; + b.js: + export default 4; + bar.js: + import a from './a'; + import b from './b'; + + export default 3; + foo.js: + import a from './a'; + import b from './b'; + + export default 2; + index.js: + import('./foo'); + import('./bar'); + + export default 1; + + package.json: + { + "@parcel/bundler-default": { + "minBundles": 0, + "minBundleSize": 200, + "maxParallelRequests": 100, + "disableSharedBundles": true + } + } + + yarn.lock:`; + let b = await bundle( - path.join( - __dirname, - 'integration/disable-shared-bundle-single-source/index.js', - ), + path.join(__dirname, 'disable-shared-bundle-single-source/index.js'), { mode: 'production', defaultTargetOptions: { shouldScopeHoist: false, }, + inputFS: overlayFS, }, ); @@ -40,20 +81,50 @@ describe('bundler', function () { }); it('should not create shared bundles and should warn when disableSharedBundles is set to true with maxParallelRequests set', async function () { + await fsFixture(overlayFS, __dirname)` + disable-shared-bundles-true-parallel + a.js: + export default 5; + b.js: + export default 4; + bar.js: + import a from './a'; + import b from './b'; + + export default 3; + foo.js: + import a from './a'; + import b from './b'; + + export default 2; + index.js: + import('./foo'); + import('./bar'); + + export default 1; + + package.json: + { + "@parcel/bundler-default": { + "maxParallelRequests": 100, + "disableSharedBundles": true + } + } + + yarn.lock:`; + let messages = []; let loggerDisposable = Logger.onLog(message => { messages.push(message); }); let b = await bundle( - path.join( - __dirname, - 'integration/disable-shared-bundles-true-parallel/index.js', - ), + path.join(__dirname, 'disable-shared-bundles-true-parallel/index.js'), { mode: 'production', defaultTargetOptions: { shouldScopeHoist: false, }, + inputFS: overlayFS, }, ); loggerDisposable.dispose(); @@ -93,6 +164,38 @@ describe('bundler', function () { }); it('should not create shared bundles and should warn when disableSharedBundles is set to true with minBundleSize set', async function () { + await fsFixture(overlayFS, __dirname)` + disable-shared-bundles-true-min-bundleSize + a.js: + export default 5; + b.js: + export default 4; + bar.js: + import a from './a'; + import b from './b'; + + export default 3; + foo.js: + import a from './a'; + import b from './b'; + + export default 2; + index.js: + import('./foo'); + import('./bar'); + + export default 1; + + package.json: + { + "@parcel/bundler-default": { + "minBundleSize": 200, + "disableSharedBundles": true + } + } + + yarn.lock:`; + let messages = []; let loggerDisposable = Logger.onLog(message => { messages.push(message); @@ -100,13 +203,14 @@ describe('bundler', function () { let b = await bundle( path.join( __dirname, - 'integration/disable-shared-bundles-true-min-bundleSize/index.js', + 'disable-shared-bundles-true-min-bundleSize/index.js', ), { mode: 'production', defaultTargetOptions: { shouldScopeHoist: false, }, + inputFS: overlayFS, }, ); loggerDisposable.dispose(); @@ -146,20 +250,50 @@ describe('bundler', function () { }); it('should not create shared bundles and should warn when disableSharedBundles is set to true with minBundles set', async function () { + await fsFixture(overlayFS, __dirname)` + disable-shared-bundles-true-min-bundles + a.js: + export default 5; + b.js: + export default 4; + bar.js: + import a from './a'; + import b from './b'; + + export default 3; + foo.js: + import a from './a'; + import b from './b'; + + export default 2; + index.js: + import('./foo'); + import('./bar'); + + export default 1; + + package.json: + { + "@parcel/bundler-default": { + "minBundles": 0, + "disableSharedBundles": true + } + } + + yarn.lock:`; + let messages = []; let loggerDisposable = Logger.onLog(message => { messages.push(message); }); let b = await bundle( - path.join( - __dirname, - 'integration/disable-shared-bundles-true-min-bundles/index.js', - ), + path.join(__dirname, 'disable-shared-bundles-true-min-bundles/index.js'), { mode: 'production', defaultTargetOptions: { shouldScopeHoist: false, }, + inputFS: overlayFS, }, ); loggerDisposable.dispose(); @@ -199,6 +333,40 @@ describe('bundler', function () { }); it('should not create shared bundles and should warn when disableSharedBundles is set to true with minBundles, minBundleSize and maxParallelRequests set', async function () { + await fsFixture(overlayFS, __dirname)` + disable-shared-bundles-true-min-bundles-parallel + a.js: + export default 5; + b.js: + export default 4; + bar.js: + import a from './a'; + import b from './b'; + + export default 3; + foo.js: + import a from './a'; + import b from './b'; + + export default 2; + index.js: + import('./foo'); + import('./bar'); + + export default 1; + + package.json: + { + "@parcel/bundler-default": { + "minBundles": 0, + "minBundleSize": 200, + "maxParallelRequests": 100, + "disableSharedBundles": true + } + } + + yarn.lock:`; + let messages = []; let loggerDisposable = Logger.onLog(message => { messages.push(message); @@ -206,13 +374,14 @@ describe('bundler', function () { let b = await bundle( path.join( __dirname, - 'integration/disable-shared-bundles-true-min-bundles-parallel/index.js', + 'disable-shared-bundles-true-min-bundles-parallel/index.js', ), { mode: 'production', defaultTargetOptions: { shouldScopeHoist: false, }, + inputFS: overlayFS, }, ); loggerDisposable.dispose(); @@ -274,17 +443,52 @@ describe('bundler', function () { }); it('should create shared bundles and should not throw a warning when disableSharedBundles is set to false', async function () { + await fsFixture(overlayFS, __dirname)` + disable-shared-bundles-false + a.js: + export default 5; + b.js: + export default 4; + bar.js: + import a from './a'; + import b from './b'; + + export default 3; + foo.js: + import a from './a'; + import b from './b'; + + export default 2; + index.js: + import('./foo'); + import('./bar'); + + export default 1; + + package.json: + { + "@parcel/bundler-default": { + "minBundles": 0, + "minBundleSize": 200, + "maxParallelRequests": 100, + "disableSharedBundles": false + } + } + + yarn.lock:`; + let messages = []; let loggerDisposable = Logger.onLog(message => { messages.push(message); }); let b = await bundle( - path.join(__dirname, 'integration/disable-shared-bundles-false/index.js'), + path.join(__dirname, 'disable-shared-bundles-false/index.js'), { mode: 'production', defaultTargetOptions: { shouldScopeHoist: false, }, + inputFS: overlayFS, }, ); loggerDisposable.dispose(); @@ -315,16 +519,43 @@ describe('bundler', function () { }); it('should not count inline assests towards parallel request limit', async function () { + await fsFixture(overlayFS, __dirname)` + inlined-assests + buzz.js: + export default 7; + inline-module.js: + import('./buzz'); + + export default 10; + local.html: + + + + + + + + package.json: + { + "@parcel/bundler-default": { + "minBundles": 1, + "minBundleSize": 200, + "maxParallelRequests": 2 + } + } + + yarn.lock:`; + // Shared bundle should not be removed in this case - let b = await bundle( - path.join(__dirname, 'integration/inlined-assests/local.html'), - { - mode: 'production', - defaultTargetOptions: { - shouldScopeHoist: false, - }, + let b = await bundle(path.join(__dirname, 'inlined-assests/local.html'), { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, }, - ); + inputFS: overlayFS, + }); assertBundles(b, [ { @@ -702,4 +933,618 @@ describe('bundler', function () { }, ]); }); + + it('should respect mode specific config', async function () { + await fsFixture(overlayFS, __dirname)` + mode-specific-bundler-config + a.js: + import foo from './foo'; + + export default 5; + b.js: + export default 4; + bar.js: + import a from './a'; + import b from './b'; + + export default 3; + foo.js: + import a from './a'; + import b from './b'; + + export default 2; + index.js: + import('./foo'); + import('./bar'); + + export default 1; + + package.json: + { + "@parcel/bundler-default": { + "minBundles": 0, + "minBundleSize": 200, + "production": { + "maxParallelRequests": 100, + "disableSharedBundles": true + } + } + } + + yarn.lock:`; + + let b = await bundle( + path.join(__dirname, 'mode-specific-bundler-config/index.js'), + { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, + }, + inputFS: overlayFS, + }, + ); + + assertBundles(b, [ + { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-url.js', + 'cacheLoader.js', + 'esmodule-helpers.js', + 'js-loader.js', + 'bundle-manifest.js', + ], + }, + { + assets: ['foo.js', 'a.js', 'b.js'], + }, + { + assets: ['a.js', 'b.js', 'foo.js', 'bar.js'], + }, + ]); + }); + + describe('manual shared bundles', () => { + const dir = path.join(__dirname, 'manual-bundle'); + + beforeEach(() => { + overlayFS.mkdirp(dir); + }); + + afterEach(() => { + overlayFS.rimraf(dir); + }); + + it('should support manual shared bundles via glob config option for different types', async function () { + await fsFixture(overlayFS, dir)` + yarn.lock: + // Required for config loading + package.json: + { + "@parcel/bundler-default": { + "minBundleSize": 0, + "unstable_manualSharedBundles": [{ + "name": "vendor", + "assets": ["vendor*.*"] + }] + } + } + + index.html: + + + index.js: + import './vendor.css'; + import './vendor.js'; + import('./async'); + + async.js: + import './vendor-async.css'; + import './vendor-async.js'; + + vendor.js: + export default 'vendor.js'; + + vendor-async.js: + export default 'vendor-async.js'; + + vendor.css: + body { + background: blue; + } + + vendor-async.css: + body { + color: blue; + } + `; + + let b = await bundle(path.join(dir, 'index.html'), { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, + sourceMaps: false, + shouldOptimize: false, + }, + inputFS: overlayFS, + }); + + assertBundles(b, [ + { + assets: ['index.html'], + }, + { + assets: [ + 'bundle-manifest.js', + 'bundle-url.js', + 'cacheLoader.js', + 'css-loader.js', + 'esmodule-helpers.js', + 'index.js', + 'js-loader.js', + ], + }, + { + assets: ['async.js'], + }, + { + // Vendor MSB for CSS + assets: ['vendor.css', 'vendor-async.css'], + }, + { + // Vendor MSB for JS + assets: ['vendor.js', 'vendor-async.js'], + }, + ]); + }); + + it('should respect Asset.isBundleSplittable', async function () { + await fsFixture(overlayFS, dir)` + yarn.lock: + // Required for config loading + package.json: + { + "@parcel/bundler-default": { + "unstable_manualSharedBundles": [{ + "name": "manual-inline", + "assets": ["shared.js"] + }] + } + } + + .parcelrc: + { + "extends": "@parcel/config-default", + "transformers": { + "*.js": ["./transformer.js", "..."], + } + } + + transformer.js: + import { Transformer } from '@parcel/plugin'; + + export default new Transformer({ + transform({asset}) { + if (asset.filePath.endsWith('.html')) { + asset.isBundleSplittable = false; + } + + return [asset]; + } + }); + + index.html: + + + + index.js: + import shared from './shared.js'; + sideEffectNoop(shared); + + shared.js: + export default 'shared'; + `; + + let b = await bundle(path.join(dir, 'index.html'), { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, + sourceMaps: false, + shouldOptimize: false, + }, + inputFS: overlayFS, + }); + + assertBundles(b, [ + { + assets: ['index.html'], + }, + { + // Inline script bundle + assets: ['index.html', 'esmodule-helpers.js', 'shared.js'], + }, + { + assets: ['esmodule-helpers.js', 'index.js'], + }, + { + // MSB for JS + assets: ['shared.js'], + }, + ]); + + run(b); + }); + + it('should support manual shared bundles via glob config option for configured types', async function () { + await fsFixture(overlayFS, dir)` + yarn.lock: + // Required for config loading + package.json: + { + "@parcel/bundler-default": { + "minBundleSize": 0, + "unstable_manualSharedBundles": [{ + "name": "vendor", + "assets": ["vendor*.*"], + "types": ["js"] + }] + } + } + + index.html: + + + index.js: + import './vendor.css'; + import './vendor.js'; + import('./async'); + + async.js: + import './vendor-async.css'; + import './vendor-async.js'; + + vendor.js: + export default 'vendor.js'; + + vendor-async.js: + export default 'vendor-async.js'; + + vendor.css: + body { + background: blue; + } + + vendor-async.css: + body { + color: blue; + } + `; + + let b = await bundle(path.join(dir, 'index.html'), { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, + sourceMaps: false, + }, + inputFS: overlayFS, + }); + + assertBundles(b, [ + { + assets: ['index.html'], + }, + { + assets: [ + 'bundle-manifest.js', + 'bundle-url.js', + 'cacheLoader.js', + 'css-loader.js', + 'esmodule-helpers.js', + 'index.js', + 'js-loader.js', + ], + }, + { + assets: ['async.js'], + }, + { + assets: ['vendor.css'], + }, + { + assets: ['vendor-async.css'], + }, + { + // Vendor MSB for JS + assets: ['vendor.js', 'vendor-async.js'], + }, + ]); + }); + + it('should support manual shared bundles via parent config option', async function () { + await fsFixture(overlayFS, dir)` + yarn.lock: + // Required for config loading + package.json: + { + "@parcel/bundler-default": { + "minBundleSize": 0, + "unstable_manualSharedBundles": [{ + "name": "vendor", + "parent": "math/math.js", + "assets": ["math/!(divide).js"] + }] + } + } + + index.html: + + + index.js: + import {add, subtract, divide} from './math/math'; + sideEffectNoop(divide(subtract(add(1, 2), 3), 4)); + + math + math.js: + export * from './add'; + export * from './subtract'; + export * from './divide'; + + add.js: + export const add = (a, b) => a + b; + + subtract.js: + export const subtract = (a, b) => a - b; + + divide.js: + export const divide = (a, b) => a / b; + `; + + let b = await bundle(path.join(dir, 'index.html'), { + defaultTargetOptions: { + shouldScopeHoist: false, + sourceMaps: false, + }, + inputFS: overlayFS, + }); + //assert that a,b,c are in one bundle, causeing foo and bar to overfetch, due to MSB config + assertBundles(b, [ + { + assets: ['index.html'], + }, + { + assets: ['esmodule-helpers.js', 'index.js', 'divide.js'], + }, + { + // Manual shared bundle + assets: ['math.js', 'add.js', 'subtract.js'], + }, + ]); + }); + + it('should support manual shared bundles with constants module', async function () { + await fsFixture(overlayFS, dir)` + yarn.lock: + // Required for config loading + package.json: + { + "@parcel/transformer-js" : { + "unstable_inlineConstants": true + }, + "@parcel/bundler-default": { + "minBundleSize": 0, + "unstable_manualSharedBundles": [{ + "name": "vendor", + "assets": ["vendor*.*"], + "types": ["js"] + }] + }, + "sideEffects": ["index.js"] + } + + vendor-constants.js: + export const a = 'hello'; + + index.html: + + + index.js: + import {a} from './vendor-constants.js'; + import('./async').then((res) => sideEffectNoop(res)); + sideEffectNoop(a); + + async.js: + import v from './vendor-async.js'; + export default 'async' + v; + + vendor-async.js: + import {a} from './vendor-constants.js'; + export default 'vendor-async.js' + a; + `; + + let b = await bundle(path.join(dir, 'index.html'), { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: true, + sourceMaps: false, + shouldOptimize: false, + }, + inputFS: overlayFS, + }); + + assertBundles(b, [ + { + assets: ['index.html'], + }, + { + assets: [ + 'bundle-manifest.js', + 'esm-js-loader.js', + 'index.js', + 'vendor-constants.js', + ], + }, + { + assets: ['async.js', 'vendor-constants.js'], + }, + { + // Vendor MSB for JS + assets: ['vendor-async.js', 'vendor-constants.js'], + }, + ]); + }); + + it('should support manual shared bundles with internalized assets', async function () { + await fsFixture(overlayFS, dir)` + yarn.lock: + // Required for config loading + package.json: + { + "@parcel/transformer-js" : { + "unstable_inlineConstants": true + }, + "@parcel/bundler-default": { + "minBundleSize": 0, + "unstable_manualSharedBundles": [{ + "name": "vendor", + "parent": "manual.js", + "assets": ["**/*"], + "types": ["js"] + }] + } + } + + index.html: + + + index.js: + import a from './manual.js'; + + manual.js: + import v from './vendor-async.js'; + import n from './vendor'; + export default 'async' + v; + + vendor.js: + export const n = () => import('./vendor-async'); + + vendor-async.js: + export default 'vendor-async.js'; + `; + + let b = await bundle(path.join(dir, 'index.html'), { + mode: 'production', + defaultTargetOptions: { + shouldScopeHoist: false, + sourceMaps: false, + shouldOptimize: false, + }, + inputFS: overlayFS, + }); + + assertBundles(b, [ + { + assets: ['index.html'], + }, + { + assets: ['esmodule-helpers.js', 'index.js'], + }, + { + // Vendor MSB for JS + assets: ['manual.js', 'vendor.js', 'vendor-async.js'], + }, + ]); + + await run(b); + }); + + it('should support consistently splitting manual shared bundles', async function () { + await fsFixture(overlayFS, dir)` + yarn.lock: + // Required for config loading + package.json: + { + "@parcel/bundler-default": { + "minBundleSize": 0, + "unstable_manualSharedBundles": [{ + "name": "vendor", + "parent": "vendor.js", + "assets": ["**/*"], + "split": 3 + }] + } + } + + index.html: + + + index.js: + import * as vendor from './vendor'; + sideEffectNoop(vendor); + + vendor.js: + export * from './a'; + export * from './b'; + export * from './c'; + export * from './d'; + export * from './e'; + export * from './f'; + export * from './g'; + export * from './h'; + export * from './i'; + export * from './j'; + + a.js: + export const a = 'a'; + b.js: + export const b = 'b'; + c.js: + export const c = 'c'; + d.js: + export const d = 'd'; + e.js: + export const e = 'e'; + f.js: + export const f = 'f'; + g.js: + export const g = 'g'; + h.js: + export const h = 'h'; + i.js: + export const i = 'i'; + j.js: + export const j = 'j'; + `; + + let b = await bundle(path.join(dir, 'index.html'), { + defaultTargetOptions: { + shouldScopeHoist: false, + shouldOptimize: false, + sourceMaps: false, + }, + inputFS: overlayFS, + }); + + assertBundles(b, [ + { + assets: ['index.html'], + }, + { + assets: ['a.js', 'i.js'], + }, + { + assets: ['vendor.js', 'b.js', 'j.js'], + }, + { + assets: ['c.js', 'd.js', 'e.js', 'f.js', 'g.js', 'h.js'], + }, + { + assets: ['esmodule-helpers.js', 'index.js'], + }, + ]); + }); + }); }); diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/a.js b/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/a.js deleted file mode 100644 index c53064a98bf..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/a.js +++ /dev/null @@ -1,3 +0,0 @@ -import foo from './foo'; - -export default 5; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/b.js b/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/b.js deleted file mode 100644 index 04514102e60..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/b.js +++ /dev/null @@ -1 +0,0 @@ -export default 4; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/bar.js b/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/bar.js deleted file mode 100644 index f08fcacacf1..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/bar.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 3; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/foo.js b/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/foo.js deleted file mode 100644 index 288a2e69559..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/foo.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 2; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/index.js b/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/index.js deleted file mode 100644 index 1059a9a2bf6..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import('./foo'); -import('./bar'); - -export default 1; diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/package.json b/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/package.json deleted file mode 100644 index 078980a0a9f..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@parcel/bundler-default": { - "minBundles": 0, - "minBundleSize": 200, - "maxParallelRequests": 100, - "disableSharedBundles": true - } -} diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/yarn.lock b/packages/core/integration-tests/test/integration/disable-shared-bundle-single-source/yarn.lock deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/a.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-false/a.js deleted file mode 100644 index 8d144a54b3e..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/a.js +++ /dev/null @@ -1 +0,0 @@ -export default 5; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/b.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-false/b.js deleted file mode 100644 index 04514102e60..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/b.js +++ /dev/null @@ -1 +0,0 @@ -export default 4; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/bar.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-false/bar.js deleted file mode 100644 index f08fcacacf1..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/bar.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 3; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/foo.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-false/foo.js deleted file mode 100644 index 288a2e69559..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/foo.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 2; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/index.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-false/index.js deleted file mode 100644 index 1059a9a2bf6..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import('./foo'); -import('./bar'); - -export default 1; diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/package.json b/packages/core/integration-tests/test/integration/disable-shared-bundles-false/package.json deleted file mode 100644 index 777d3fbf681..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@parcel/bundler-default": { - "minBundles": 0, - "minBundleSize": 200, - "maxParallelRequests": 100, - "disableSharedBundles": false - } -} diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-false/yarn.lock b/packages/core/integration-tests/test/integration/disable-shared-bundles-false/yarn.lock deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/a.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/a.js deleted file mode 100644 index 8d144a54b3e..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/a.js +++ /dev/null @@ -1 +0,0 @@ -export default 5; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/b.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/b.js deleted file mode 100644 index 04514102e60..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/b.js +++ /dev/null @@ -1 +0,0 @@ -export default 4; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/bar.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/bar.js deleted file mode 100644 index f08fcacacf1..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/bar.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 3; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/foo.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/foo.js deleted file mode 100644 index 288a2e69559..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/foo.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 2; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/index.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/index.js deleted file mode 100644 index 1059a9a2bf6..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import('./foo'); -import('./bar'); - -export default 1; diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/package.json b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/package.json deleted file mode 100644 index b5915811843..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "@parcel/bundler-default": { - "minBundleSize": 200, - "disableSharedBundles": true - } -} diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/yarn.lock b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundleSize/yarn.lock deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/a.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/a.js deleted file mode 100644 index 8d144a54b3e..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/a.js +++ /dev/null @@ -1 +0,0 @@ -export default 5; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/b.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/b.js deleted file mode 100644 index 04514102e60..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/b.js +++ /dev/null @@ -1 +0,0 @@ -export default 4; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/bar.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/bar.js deleted file mode 100644 index f08fcacacf1..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/bar.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 3; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/foo.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/foo.js deleted file mode 100644 index 288a2e69559..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/foo.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 2; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/index.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/index.js deleted file mode 100644 index 1059a9a2bf6..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import('./foo'); -import('./bar'); - -export default 1; diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/package.json b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/package.json deleted file mode 100644 index 078980a0a9f..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@parcel/bundler-default": { - "minBundles": 0, - "minBundleSize": 200, - "maxParallelRequests": 100, - "disableSharedBundles": true - } -} diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/yarn.lock b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles-parallel/yarn.lock deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/a.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/a.js deleted file mode 100644 index 8d144a54b3e..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/a.js +++ /dev/null @@ -1 +0,0 @@ -export default 5; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/b.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/b.js deleted file mode 100644 index 04514102e60..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/b.js +++ /dev/null @@ -1 +0,0 @@ -export default 4; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/bar.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/bar.js deleted file mode 100644 index f08fcacacf1..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/bar.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 3; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/foo.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/foo.js deleted file mode 100644 index 288a2e69559..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/foo.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 2; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/index.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/index.js deleted file mode 100644 index 1059a9a2bf6..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import('./foo'); -import('./bar'); - -export default 1; diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/package.json b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/package.json deleted file mode 100644 index 585c70677ae..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "@parcel/bundler-default": { - "minBundles": 0, - "disableSharedBundles": true - } -} diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/yarn.lock b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-min-bundles/yarn.lock deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/a.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/a.js deleted file mode 100644 index 8d144a54b3e..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/a.js +++ /dev/null @@ -1 +0,0 @@ -export default 5; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/b.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/b.js deleted file mode 100644 index 04514102e60..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/b.js +++ /dev/null @@ -1 +0,0 @@ -export default 4; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/bar.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/bar.js deleted file mode 100644 index f08fcacacf1..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/bar.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 3; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/foo.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/foo.js deleted file mode 100644 index 288a2e69559..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/foo.js +++ /dev/null @@ -1,4 +0,0 @@ -import a from './a'; -import b from './b'; - -export default 2; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/index.js b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/index.js deleted file mode 100644 index 1059a9a2bf6..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import('./foo'); -import('./bar'); - -export default 1; diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/package.json b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/package.json deleted file mode 100644 index 1ddb759de0f..00000000000 --- a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "@parcel/bundler-default": { - "maxParallelRequests": 100, - "disableSharedBundles": true - } -} diff --git a/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/yarn.lock b/packages/core/integration-tests/test/integration/disable-shared-bundles-true-parallel/yarn.lock deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/core/integration-tests/test/integration/inlined-assests/buzz.js b/packages/core/integration-tests/test/integration/inlined-assests/buzz.js deleted file mode 100644 index 6be7d8e2100..00000000000 --- a/packages/core/integration-tests/test/integration/inlined-assests/buzz.js +++ /dev/null @@ -1 +0,0 @@ -export default 7; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/inlined-assests/inline-module.js b/packages/core/integration-tests/test/integration/inlined-assests/inline-module.js deleted file mode 100644 index 6bfb7fe7f23..00000000000 --- a/packages/core/integration-tests/test/integration/inlined-assests/inline-module.js +++ /dev/null @@ -1,3 +0,0 @@ -import('./buzz'); - -export default 10; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/inlined-assests/local.html b/packages/core/integration-tests/test/integration/inlined-assests/local.html deleted file mode 100644 index 222116f3ec3..00000000000 --- a/packages/core/integration-tests/test/integration/inlined-assests/local.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/packages/core/integration-tests/test/integration/inlined-assests/package.json b/packages/core/integration-tests/test/integration/inlined-assests/package.json deleted file mode 100644 index 07afabac875..00000000000 --- a/packages/core/integration-tests/test/integration/inlined-assests/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "@parcel/bundler-default": { - "minBundles": 1, - "minBundleSize": 200, - "maxParallelRequests": 2 - } -} diff --git a/packages/core/integration-tests/test/integration/inlined-assests/yarn.lock b/packages/core/integration-tests/test/integration/inlined-assests/yarn.lock deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/core/types/index.js b/packages/core/types/index.js index 78c7dca5a25..52610e3d943 100644 --- a/packages/core/types/index.js +++ b/packages/core/types/index.js @@ -1230,6 +1230,8 @@ export type CreateBundleOpts = * - isolated: The bundle will be isolated from its parents. Shared assets will be duplicated. */ +bundleBehavior?: ?BundleBehavior, + /** Name of the manual shared bundle config that caused this bundle to be created */ + +manualSharedBundle?: ?string, |} // If an entryAsset is not provided, a bundle id, type, and environment must // be provided. @@ -1263,6 +1265,8 @@ export type CreateBundleOpts = +isSplittable?: ?boolean, /** The bundle's pipeline, to be used for optimization. Usually based on the pipeline of the entry asset. */ +pipeline?: ?string, + /** Name of the manual shared bundle config that caused this bundle to be created */ + +manualSharedBundle?: ?string, |}; /**