Skip to content

Commit 75182a0

Browse files
authored
Add library bundler (#9489)
1 parent aeaba41 commit 75182a0

File tree

12 files changed

+706
-87
lines changed

12 files changed

+706
-87
lines changed
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@parcel/bundler-library",
3+
"version": "2.11.0",
4+
"license": "MIT",
5+
"publishConfig": {
6+
"access": "public"
7+
},
8+
"funding": {
9+
"type": "opencollective",
10+
"url": "https://opencollective.com/parcel"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "https://github.com/parcel-bundler/parcel.git"
15+
},
16+
"main": "lib/LibraryBundler.js",
17+
"source": "src/LibraryBundler.js",
18+
"engines": {
19+
"node": ">= 12.0.0",
20+
"parcel": "^2.12.0"
21+
},
22+
"dependencies": {
23+
"@parcel/plugin": "2.12.0",
24+
"nullthrows": "^1.1.1"
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// @flow strict-local
2+
import {Bundler} from '@parcel/plugin';
3+
import nullthrows from 'nullthrows';
4+
5+
// This bundler plugin is designed specifically for library builds. It outputs a bundle for
6+
// each input asset, which ensures that the library can be effectively tree shaken and code
7+
// split by an application bundler.
8+
export default (new Bundler({
9+
bundle({bundleGraph}) {
10+
// Collect dependencies from the graph.
11+
// We do not want to mutate the graph while traversing, so this must be done first.
12+
let dependencies = [];
13+
bundleGraph.traverse((node, context) => {
14+
if (node.type === 'dependency') {
15+
let dependency = node.value;
16+
if (bundleGraph.isDependencySkipped(dependency)) {
17+
return;
18+
}
19+
dependencies.push([
20+
dependency,
21+
nullthrows(dependency.target ?? context),
22+
]);
23+
if (dependency.target) {
24+
return dependency.target;
25+
}
26+
}
27+
});
28+
29+
// Create bundles for each asset.
30+
let bundles = new Map();
31+
for (let [dependency, target] of dependencies) {
32+
let assets = bundleGraph.getDependencyAssets(dependency);
33+
if (assets.length === 0) {
34+
continue;
35+
}
36+
37+
let parentAsset = bundleGraph.getAssetWithDependency(dependency);
38+
let parentBundle;
39+
if (parentAsset) {
40+
let parentKey = getBundleKey(parentAsset, target);
41+
parentBundle = bundles.get(parentKey);
42+
}
43+
let bundleGroup;
44+
45+
// Create a separate bundle group/bundle for each asset.
46+
for (let asset of assets) {
47+
let key = getBundleKey(asset, target);
48+
let bundle = bundles.get(key);
49+
if (!bundle) {
50+
bundleGroup ??= bundleGraph.createBundleGroup(dependency, target);
51+
bundle = bundleGraph.createBundle({
52+
entryAsset: asset,
53+
needsStableName: dependency.isEntry,
54+
target,
55+
bundleBehavior: dependency.bundleBehavior ?? asset.bundleBehavior,
56+
});
57+
bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
58+
bundles.set(key, bundle);
59+
}
60+
61+
if (!bundle.hasAsset(asset)) {
62+
bundleGraph.addAssetToBundle(asset, bundle);
63+
}
64+
65+
// Reference the parent bundle so we create dependencies between them.
66+
if (parentBundle) {
67+
bundleGraph.createBundleReference(parentBundle, bundle);
68+
bundleGraph.createAssetReference(dependency, asset, bundle);
69+
}
70+
}
71+
}
72+
},
73+
optimize() {},
74+
}): Bundler);
75+
76+
function getBundleKey(asset, target) {
77+
// Group by type and file path so CSS generated by macros is combined together by parent JS file.
78+
// Also group by environment/target to ensure bundles cannot be shared between packages.
79+
return `${asset.type}:${asset.filePath}:${asset.env.id}:${target.distDir}`;
80+
}

packages/core/core/src/BundleGraph.js

+25-16
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,24 @@ export default class BundleGraph {
816816
getReferencedBundle(dependency: Dependency, fromBundle: Bundle): ?Bundle {
817817
let dependencyNodeId = this._graph.getNodeIdByContentKey(dependency.id);
818818

819+
// Find an attached bundle via a reference edge (e.g. from createAssetReference).
820+
let bundleNodes = this._graph
821+
.getNodeIdsConnectedFrom(
822+
dependencyNodeId,
823+
bundleGraphEdgeTypes.references,
824+
)
825+
.map(id => nullthrows(this._graph.getNode(id)))
826+
.filter(node => node.type === 'bundle');
827+
828+
if (bundleNodes.length) {
829+
let bundleNode =
830+
bundleNodes.find(
831+
b => b.type === 'bundle' && b.value.type === fromBundle.type,
832+
) || bundleNodes[0];
833+
invariant(bundleNode.type === 'bundle');
834+
return bundleNode.value;
835+
}
836+
819837
// If this dependency is async, there will be a bundle group attached to it.
820838
let node = this._graph
821839
.getNodeIdsConnectedFrom(dependencyNodeId)
@@ -831,20 +849,6 @@ export default class BundleGraph {
831849
return mainEntryId != null && node.value.entryAssetId === mainEntryId;
832850
});
833851
}
834-
835-
// Otherwise, find an attached bundle via a reference edge (e.g. from createAssetReference).
836-
let bundleNode = this._graph
837-
.getNodeIdsConnectedFrom(
838-
dependencyNodeId,
839-
bundleGraphEdgeTypes.references,
840-
)
841-
.map(id => nullthrows(this._graph.getNode(id)))
842-
.find(node => node.type === 'bundle');
843-
844-
if (bundleNode) {
845-
invariant(bundleNode.type === 'bundle');
846-
return bundleNode.value;
847-
}
848852
}
849853

850854
removeAssetGraphFromBundle(asset: Asset, bundle: Bundle) {
@@ -1142,19 +1146,24 @@ export default class BundleGraph {
11421146

11431147
// If a resolution still hasn't been found, return the first referenced asset.
11441148
if (resolved == null) {
1149+
let potential = [];
11451150
this._graph.traverse(
11461151
(nodeId, _, traversal) => {
11471152
let node = nullthrows(this._graph.getNode(nodeId));
11481153
if (node.type === 'asset') {
1149-
resolved = node.value;
1150-
traversal.stop();
1154+
potential.push(node.value);
11511155
} else if (node.id !== dep.id) {
11521156
traversal.skipChildren();
11531157
}
11541158
},
11551159
this._graph.getNodeIdByContentKey(dep.id),
11561160
bundleGraphEdgeTypes.references,
11571161
);
1162+
1163+
if (bundle) {
1164+
resolved = potential.find(a => a.type === bundle.type);
1165+
}
1166+
resolved ||= potential[0];
11581167
}
11591168

11601169
return resolved;
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
exports.test = true;
2+
exports['foo-bar'] = true;

packages/core/integration-tests/test/integration/sync-entry-shared/yarn.lock

Whitespace-only changes.

0 commit comments

Comments
 (0)