@atlaspack/core 2.12.1-canary.3432 → 2.12.1-canary.3434

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Atlaspack.js CHANGED
@@ -151,6 +151,11 @@ class Atlaspack {
151
151
  if (this.#initialized) {
152
152
  return;
153
153
  }
154
+ const featureFlags = {
155
+ ..._featureFlags().DEFAULT_FEATURE_FLAGS,
156
+ ...this.#initialOptions.featureFlags
157
+ };
158
+ (0, _featureFlags().setFeatureFlags)(featureFlags);
154
159
  await _sourceMap().init;
155
160
  await (_rust().init === null || _rust().init === void 0 ? void 0 : (0, _rust().init)());
156
161
  try {
@@ -162,7 +167,10 @@ class Atlaspack {
162
167
  // Fallthrough
163
168
  _logger().default.warn(e);
164
169
  }
165
- let resolvedOptions = await (0, _resolveOptions.default)(this.#initialOptions);
170
+ let resolvedOptions = await (0, _resolveOptions.default)({
171
+ ...this.#initialOptions,
172
+ featureFlags
173
+ });
166
174
  this.#resolvedOptions = resolvedOptions;
167
175
  let rustAtlaspack;
168
176
  if (resolvedOptions.featureFlags.atlaspackV3) {
@@ -192,7 +200,6 @@ class Atlaspack {
192
200
  }
193
201
  });
194
202
  }
195
- (0, _featureFlags().setFeatureFlags)(resolvedOptions.featureFlags);
196
203
  let {
197
204
  config
198
205
  } = await (0, _AtlaspackConfigRequest.loadAtlaspackConfig)(resolvedOptions);
@@ -44,6 +44,13 @@ var _utils2 = require("./utils");
44
44
  var _Environment = require("./public/Environment");
45
45
  var _projectPath = require("./projectPath");
46
46
  var _constants = require("./constants");
47
+ function _featureFlags() {
48
+ const data = require("@atlaspack/feature-flags");
49
+ _featureFlags = function () {
50
+ return data;
51
+ };
52
+ return data;
53
+ }
47
54
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
48
55
  const bundleGraphEdgeTypes = exports.bundleGraphEdgeTypes = {
49
56
  // A lack of an edge type indicates to follow the edge while traversing
@@ -107,16 +114,19 @@ class BundleGraph {
107
114
  /** The internal core Graph structure */
108
115
 
109
116
  _bundlePublicIds /*: Set<string> */ = new Set();
117
+ _conditions /*: Map<string, Condition> */ = new Map();
110
118
  constructor({
111
119
  graph,
112
120
  publicIdByAssetId,
113
121
  assetPublicIds,
114
- bundleContentHashes
122
+ bundleContentHashes,
123
+ conditions
115
124
  }) {
116
125
  this._graph = graph;
117
126
  this._assetPublicIds = assetPublicIds;
118
127
  this._publicIdByAssetId = publicIdByAssetId;
119
128
  this._bundleContentHashes = bundleContentHashes;
129
+ this._conditions = conditions;
120
130
  }
121
131
 
122
132
  /**
@@ -128,6 +138,8 @@ class BundleGraph {
128
138
  let assetGroupIds = new Map();
129
139
  let dependencies = new Map();
130
140
  let assetGraphNodeIdToBundleGraphNodeId = new Map();
141
+ let conditions = new Map();
142
+ let placeholderToDependency = new Map();
131
143
  let assetGraphRootNode = assetGraph.rootNodeId != null ? assetGraph.getNode(assetGraph.rootNodeId) : null;
132
144
  (0, _assert().default)(assetGraphRootNode != null && assetGraphRootNode.type === 'root');
133
145
  assetGraph.dfsFast(nodeId => {
@@ -146,6 +158,17 @@ class BundleGraph {
146
158
  }
147
159
  } else if (node != null && node.type === 'asset_group') {
148
160
  assetGroupIds.set(nodeId, assetGraph.getNodeIdsConnectedFrom(nodeId));
161
+ } else if ((0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node != null && node.type === 'dependency') {
162
+ var _dep$meta;
163
+ // The dependency placeholders in the `importCond` calls that will be in the transformed
164
+ // code need to be mapped to the "real" dependencies, so we need access to a map of placeholders
165
+ // to dependencies
166
+ const dep = node.value;
167
+ // $FlowFixMe[incompatible-type] Meta is untyped
168
+ const placeholder = (_dep$meta = dep.meta) === null || _dep$meta === void 0 ? void 0 : _dep$meta.placeholder;
169
+ if (placeholder != null) {
170
+ placeholderToDependency.set(placeholder, dep);
171
+ }
149
172
  }
150
173
  });
151
174
  let walkVisited = new Set();
@@ -153,6 +176,34 @@ class BundleGraph {
153
176
  if (walkVisited.has(nodeId)) return;
154
177
  walkVisited.add(nodeId);
155
178
  let node = (0, _nullthrows().default)(assetGraph.getNode(nodeId));
179
+ if ((0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.type === 'asset') {
180
+ const asset = node.value;
181
+ if (Array.isArray(asset.meta.conditions)) {
182
+ // $FlowFixMe
183
+ for (const condition of asset.meta.conditions) {
184
+ // Resolve the placeholders that were attached to the asset in JSTransformer to dependencies,
185
+ // as well as create a public id for the condition.
186
+ const {
187
+ key,
188
+ ifTruePlaceholder,
189
+ ifFalsePlaceholder
190
+ } = condition;
191
+ const condHash = (0, _rust().hashString)(`${key}:${ifTruePlaceholder}:${ifFalsePlaceholder}`);
192
+ const condPublicId = (0, _utils2.getPublicId)(condHash, v => conditions.has(v));
193
+ if (conditions.has(condHash)) {
194
+ throw new Error('Unexpected duplicate asset');
195
+ } else {
196
+ conditions.set(condHash, {
197
+ publicId: condPublicId,
198
+ assets: new Set([asset]),
199
+ key,
200
+ ifTrueDependency: (0, _nullthrows().default)(placeholderToDependency.get(ifTruePlaceholder), 'ifTruePlaceholder was undefined'),
201
+ ifFalseDependency: (0, _nullthrows().default)(placeholderToDependency.get(ifFalsePlaceholder), 'ifFalsePlaceholder was undefined')
202
+ });
203
+ }
204
+ }
205
+ }
206
+ }
156
207
  if (node.type === 'dependency' && node.value.symbols != null && node.value.env.shouldScopeHoist &&
157
208
  // Disable in dev mode because this feature is at odds with safeToIncrementallyBundle
158
209
  isProduction) {
@@ -338,7 +389,8 @@ class BundleGraph {
338
389
  graph,
339
390
  assetPublicIds,
340
391
  bundleContentHashes: new Map(),
341
- publicIdByAssetId
392
+ publicIdByAssetId,
393
+ conditions
342
394
  });
343
395
  }
344
396
  serialize() {
@@ -347,7 +399,8 @@ class BundleGraph {
347
399
  graph: this._graph.serialize(),
348
400
  assetPublicIds: this._assetPublicIds,
349
401
  bundleContentHashes: this._bundleContentHashes,
350
- publicIdByAssetId: this._publicIdByAssetId
402
+ publicIdByAssetId: this._publicIdByAssetId,
403
+ conditions: this._conditions
351
404
  };
352
405
  }
353
406
  static deserialize(serialized) {
@@ -355,7 +408,8 @@ class BundleGraph {
355
408
  graph: _graph().ContentGraph.deserialize(serialized.graph),
356
409
  assetPublicIds: serialized.assetPublicIds,
357
410
  bundleContentHashes: serialized.bundleContentHashes,
358
- publicIdByAssetId: serialized.publicIdByAssetId
411
+ publicIdByAssetId: serialized.publicIdByAssetId,
412
+ conditions: serialized.conditions
359
413
  });
360
414
  }
361
415
  createBundle(opts) {
@@ -768,7 +822,7 @@ class BundleGraph {
768
822
  return true;
769
823
  }
770
824
  let assetNodeId = (0, _nullthrows().default)(this._graph.getNodeIdByContentKey(asset.id));
771
- if (this._graph.getNodeIdsConnectedTo(assetNodeId, bundleGraphEdgeTypes.references).map(id => this._graph.getNode(id)).some(node => (node === null || node === void 0 ? void 0 : node.type) === 'dependency' && node.value.priority === _types.Priority.lazy && node.value.specifierType !== _types.SpecifierType.url)) {
825
+ if (this._graph.getNodeIdsConnectedTo(assetNodeId, bundleGraphEdgeTypes.references).map(id => this._graph.getNode(id)).some(node => (node === null || node === void 0 ? void 0 : node.type) === 'dependency' && (node.value.priority === _types.Priority.lazy || node.value.priority === _types.Priority.conditional) && node.value.specifierType !== _types.SpecifierType.url)) {
772
826
  // If this asset is referenced by any async dependency, it's referenced.
773
827
  return true;
774
828
  }
@@ -108,7 +108,7 @@ function propagateSymbols({
108
108
  namespaceReexportedSymbols.add('*');
109
109
  } else {
110
110
  for (let incomingDep of incomingDeps) {
111
- if (incomingDep.value.symbols == null) {
111
+ if (incomingDep.value.symbols == null || incomingDep.value.priority === _types.Priority.conditional) {
112
112
  if (incomingDep.value.sourceAssetId == null) {
113
113
  // The root dependency on non-library builds
114
114
  isEntry = true;
@@ -180,5 +180,6 @@ napi().registerWorker(_worker_threads().workerData.tx_worker, new AtlaspackWorke
180
180
  const PriorityMap = {
181
181
  sync: 0,
182
182
  parallel: 1,
183
- lazy: 2
183
+ lazy: 2,
184
+ conditional: 3
184
185
  };
@@ -189,5 +189,73 @@ class BundleGraph {
189
189
  getEntryRoot(target) {
190
190
  return this.#graph.getEntryRoot(this.#options.projectRoot, (0, _Target.targetToInternalTarget)(target));
191
191
  }
192
+
193
+ // Given a set of dependencies, return any conditions where those dependencies are either
194
+ // the true or false dependency for those conditions. This is currently used to work out which
195
+ // conditions belong to a bundle in packaging.
196
+ getConditionsForDependencies(deps) {
197
+ const conditions = new Set();
198
+ const depIds = deps.map(dep => dep.id);
199
+ for (const condition of this.#graph._conditions.values()) {
200
+ if (depIds.includes(condition.ifTrueDependency.id) || depIds.includes(condition.ifFalseDependency.id)) {
201
+ const [trueAsset, falseAsset] = [condition.ifTrueDependency, condition.ifFalseDependency].map(dep => {
202
+ const resolved = (0, _nullthrows().default)(this.#graph.resolveAsyncDependency(dep));
203
+ if (resolved.type === 'asset') {
204
+ return resolved.value;
205
+ } else {
206
+ return this.#graph.getAssetById(resolved.value.entryAssetId);
207
+ }
208
+ });
209
+ conditions.add({
210
+ publicId: condition.publicId,
211
+ key: condition.key,
212
+ ifTrueDependency: (0, _nullthrows().default)(deps.find(dep => dep.id === condition.ifTrueDependency.id), 'ifTrueDependency was null'),
213
+ ifFalseDependency: (0, _nullthrows().default)(deps.find(dep => dep.id === condition.ifFalseDependency.id), 'ifFalseDependency was null'),
214
+ ifTrueAssetId: this.#graph.getAssetPublicId(trueAsset),
215
+ ifFalseAssetId: this.#graph.getAssetPublicId(falseAsset)
216
+ });
217
+ }
218
+ }
219
+ return conditions;
220
+ }
221
+
222
+ // This is used to generate information for building a manifest that can
223
+ // be used by a webserver to understand which conditions are used by which bundles,
224
+ // and which bundles those conditions require depending on what they evaluate to.
225
+ getConditionalBundleMapping() {
226
+ let bundleConditions = new Map();
227
+
228
+ // Convert the internal references in conditions to public API references
229
+ for (const cond of this.#graph._conditions.values()) {
230
+ let assets = Array.from(cond.assets).map(asset => (0, _nullthrows().default)(this.getAssetById(asset.id)));
231
+ let bundles = new Set();
232
+ let ifTrueBundles = [];
233
+ let ifFalseBundles = [];
234
+ for (const asset of assets) {
235
+ const bundlesWithAsset = this.getBundlesWithAsset(asset);
236
+ for (const bundle of bundlesWithAsset) {
237
+ bundles.add(bundle);
238
+ }
239
+ const assetDeps = this.getDependencies(asset);
240
+ const depToBundles = dep => {
241
+ const publicDep = (0, _nullthrows().default)(assetDeps.find(assetDep => dep.id === assetDep.id));
242
+ const resolved = (0, _nullthrows().default)(this.resolveAsyncDependency(publicDep));
243
+ (0, _assert().default)(resolved.type === 'bundle_group');
244
+ return this.getBundlesInBundleGroup(resolved.value);
245
+ };
246
+ ifTrueBundles.push(...depToBundles(cond.ifTrueDependency));
247
+ ifFalseBundles.push(...depToBundles(cond.ifFalseDependency));
248
+ }
249
+ for (let bundle of bundles) {
250
+ const conditions = bundleConditions.get(bundle) ?? new Map();
251
+ conditions.set(cond.key, {
252
+ ifTrueBundles,
253
+ ifFalseBundles
254
+ });
255
+ bundleConditions.set(bundle, conditions);
256
+ }
257
+ }
258
+ return bundleConditions;
259
+ }
192
260
  }
193
261
  exports.default = BundleGraph;
@@ -197,6 +197,8 @@ async function resolveOptions(initialOptions) {
197
197
  outputFormat: initialOptions === null || initialOptions === void 0 || (_initialOptions$defau8 = initialOptions.defaultTargetOptions) === null || _initialOptions$defau8 === void 0 ? void 0 : _initialOptions$defau8.outputFormat,
198
198
  isLibrary: initialOptions === null || initialOptions === void 0 || (_initialOptions$defau9 = initialOptions.defaultTargetOptions) === null || _initialOptions$defau9 === void 0 ? void 0 : _initialOptions$defau9.isLibrary
199
199
  },
200
+ // unused, feature-flags are set above this to allow this function to use
201
+ // feature-flags
200
202
  featureFlags: {
201
203
  ..._featureFlags().DEFAULT_FEATURE_FLAGS,
202
204
  ...(initialOptions === null || initialOptions === void 0 ? void 0 : initialOptions.featureFlags)
package/lib/types.js CHANGED
@@ -13,7 +13,8 @@ const SpecifierType = exports.SpecifierType = {
13
13
  const Priority = exports.Priority = {
14
14
  sync: 0,
15
15
  parallel: 1,
16
- lazy: 2
16
+ lazy: 2,
17
+ conditional: 3
17
18
  };
18
19
 
19
20
  // Must match package_json.rs in node-resolver-rs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaspack/core",
3
- "version": "2.12.1-canary.3432+415929ab6",
3
+ "version": "2.12.1-canary.3434+c61bf3cde",
4
4
  "license": "(MIT OR Apache-2.0)",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -20,20 +20,20 @@
20
20
  "check-ts": "tsc --noEmit index.d.ts"
21
21
  },
22
22
  "dependencies": {
23
- "@atlaspack/cache": "2.12.1-canary.3432+415929ab6",
24
- "@atlaspack/diagnostic": "2.12.1-canary.3432+415929ab6",
25
- "@atlaspack/events": "2.12.1-canary.3432+415929ab6",
26
- "@atlaspack/feature-flags": "2.12.1-canary.3432+415929ab6",
27
- "@atlaspack/fs": "2.12.1-canary.3432+415929ab6",
28
- "@atlaspack/graph": "3.2.1-canary.3432+415929ab6",
29
- "@atlaspack/logger": "2.12.1-canary.3432+415929ab6",
30
- "@atlaspack/package-manager": "2.12.1-canary.3432+415929ab6",
31
- "@atlaspack/plugin": "2.12.1-canary.3432+415929ab6",
32
- "@atlaspack/profiler": "2.12.1-canary.3432+415929ab6",
33
- "@atlaspack/rust": "2.12.1-canary.3432+415929ab6",
34
- "@atlaspack/types": "2.12.1-canary.3432+415929ab6",
35
- "@atlaspack/utils": "2.12.1-canary.3432+415929ab6",
36
- "@atlaspack/workers": "2.12.1-canary.3432+415929ab6",
23
+ "@atlaspack/cache": "2.12.1-canary.3434+c61bf3cde",
24
+ "@atlaspack/diagnostic": "2.12.1-canary.3434+c61bf3cde",
25
+ "@atlaspack/events": "2.12.1-canary.3434+c61bf3cde",
26
+ "@atlaspack/feature-flags": "2.12.1-canary.3434+c61bf3cde",
27
+ "@atlaspack/fs": "2.12.1-canary.3434+c61bf3cde",
28
+ "@atlaspack/graph": "3.2.1-canary.3434+c61bf3cde",
29
+ "@atlaspack/logger": "2.12.1-canary.3434+c61bf3cde",
30
+ "@atlaspack/package-manager": "2.12.1-canary.3434+c61bf3cde",
31
+ "@atlaspack/plugin": "2.12.1-canary.3434+c61bf3cde",
32
+ "@atlaspack/profiler": "2.12.1-canary.3434+c61bf3cde",
33
+ "@atlaspack/rust": "2.12.1-canary.3434+c61bf3cde",
34
+ "@atlaspack/types": "2.12.1-canary.3434+c61bf3cde",
35
+ "@atlaspack/utils": "2.12.1-canary.3434+c61bf3cde",
36
+ "@atlaspack/workers": "2.12.1-canary.3434+c61bf3cde",
37
37
  "@mischnic/json-sourcemap": "^0.1.0",
38
38
  "@parcel/source-map": "^2.1.1",
39
39
  "base-x": "^3.0.8",
@@ -57,5 +57,5 @@
57
57
  "browser": {
58
58
  "./src/serializerCore.js": "./src/serializerCore.browser.js"
59
59
  },
60
- "gitHead": "415929ab63f5314913c27d3dfdb965f2e76bedf2"
60
+ "gitHead": "c61bf3cde53d1cdb5ae13541c644a6e3ef5521e1"
61
61
  }
package/src/Atlaspack.js CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  fromProjectPathRelative,
58
58
  } from './projectPath';
59
59
  import {tracer} from '@atlaspack/profiler';
60
- import {setFeatureFlags} from '@atlaspack/feature-flags';
60
+ import {setFeatureFlags, DEFAULT_FEATURE_FLAGS} from '@atlaspack/feature-flags';
61
61
  import {AtlaspackV3, toFileSystemV3} from './atlaspack-v3';
62
62
 
63
63
  registerCoreWithSerializer();
@@ -105,6 +105,12 @@ export default class Atlaspack {
105
105
  return;
106
106
  }
107
107
 
108
+ const featureFlags = {
109
+ ...DEFAULT_FEATURE_FLAGS,
110
+ ...this.#initialOptions.featureFlags,
111
+ };
112
+ setFeatureFlags(featureFlags);
113
+
108
114
  await initSourcemaps;
109
115
  await initRust?.();
110
116
  try {
@@ -117,9 +123,10 @@ export default class Atlaspack {
117
123
  logger.warn(e);
118
124
  }
119
125
 
120
- let resolvedOptions: AtlaspackOptions = await resolveOptions(
121
- this.#initialOptions,
122
- );
126
+ let resolvedOptions: AtlaspackOptions = await resolveOptions({
127
+ ...this.#initialOptions,
128
+ featureFlags,
129
+ });
123
130
  this.#resolvedOptions = resolvedOptions;
124
131
 
125
132
  let rustAtlaspack: AtlaspackV3;
@@ -152,8 +159,6 @@ export default class Atlaspack {
152
159
  });
153
160
  }
154
161
 
155
- setFeatureFlags(resolvedOptions.featureFlags);
156
-
157
162
  let {config} = await loadAtlaspackConfig(resolvedOptions);
158
163
  this.#config = new AtlaspackConfig(config, resolvedOptions);
159
164
 
@@ -6,6 +6,7 @@ import type {
6
6
  Symbol,
7
7
  TraversalActions,
8
8
  BundleBehavior as IBundleBehavior,
9
+ ConditionMeta,
9
10
  } from '@atlaspack/types';
10
11
  import type {
11
12
  ContentKey,
@@ -26,6 +27,7 @@ import type {
26
27
  Environment,
27
28
  InternalSourceLocation,
28
29
  Target,
30
+ Condition,
29
31
  } from './types';
30
32
  import type AssetGraph from './AssetGraph';
31
33
  import type {ProjectPath} from './projectPath';
@@ -46,6 +48,7 @@ import {getBundleGroupId, getPublicId} from './utils';
46
48
  import {ISOLATED_ENVS} from './public/Environment';
47
49
  import {fromProjectPath, fromProjectPathRelative} from './projectPath';
48
50
  import {HASH_REF_PREFIX} from './constants';
51
+ import {getFeatureFlag} from '@atlaspack/feature-flags';
49
52
 
50
53
  export const bundleGraphEdgeTypes = {
51
54
  // A lack of an edge type indicates to follow the edge while traversing
@@ -91,6 +94,7 @@ type BundleGraphOpts = {|
91
94
  bundleContentHashes: Map<string, string>,
92
95
  assetPublicIds: Set<string>,
93
96
  publicIdByAssetId: Map<string, string>,
97
+ conditions: Map<string, Condition>,
94
98
  |};
95
99
 
96
100
  type SerializedBundleGraph = {|
@@ -99,6 +103,7 @@ type SerializedBundleGraph = {|
99
103
  bundleContentHashes: Map<string, string>,
100
104
  assetPublicIds: Set<string>,
101
105
  publicIdByAssetId: Map<string, string>,
106
+ conditions: Map<string, Condition>,
102
107
  |};
103
108
 
104
109
  function makeReadOnlySet<T>(set: Set<T>): $ReadOnlySet<T> {
@@ -139,22 +144,26 @@ export default class BundleGraph {
139
144
  /** The internal core Graph structure */
140
145
  _graph: ContentGraph<BundleGraphNode, BundleGraphEdgeType>;
141
146
  _bundlePublicIds /*: Set<string> */ = new Set<string>();
147
+ _conditions /*: Map<string, Condition> */ = new Map<string, Condition>();
142
148
 
143
149
  constructor({
144
150
  graph,
145
151
  publicIdByAssetId,
146
152
  assetPublicIds,
147
153
  bundleContentHashes,
154
+ conditions,
148
155
  }: {|
149
156
  graph: ContentGraph<BundleGraphNode, BundleGraphEdgeType>,
150
157
  publicIdByAssetId: Map<string, string>,
151
158
  assetPublicIds: Set<string>,
152
159
  bundleContentHashes: Map<string, string>,
160
+ conditions: Map<string, Condition>,
153
161
  |}) {
154
162
  this._graph = graph;
155
163
  this._assetPublicIds = assetPublicIds;
156
164
  this._publicIdByAssetId = publicIdByAssetId;
157
165
  this._bundleContentHashes = bundleContentHashes;
166
+ this._conditions = conditions;
158
167
  }
159
168
 
160
169
  /**
@@ -171,6 +180,9 @@ export default class BundleGraph {
171
180
  let assetGroupIds = new Map();
172
181
  let dependencies = new Map();
173
182
  let assetGraphNodeIdToBundleGraphNodeId = new Map<NodeId, NodeId>();
183
+ let conditions = new Map<string, Condition>();
184
+
185
+ let placeholderToDependency = new Map<string, Dependency>();
174
186
 
175
187
  let assetGraphRootNode =
176
188
  assetGraph.rootNodeId != null
@@ -195,6 +207,20 @@ export default class BundleGraph {
195
207
  }
196
208
  } else if (node != null && node.type === 'asset_group') {
197
209
  assetGroupIds.set(nodeId, assetGraph.getNodeIdsConnectedFrom(nodeId));
210
+ } else if (
211
+ getFeatureFlag('conditionalBundlingApi') &&
212
+ node != null &&
213
+ node.type === 'dependency'
214
+ ) {
215
+ // The dependency placeholders in the `importCond` calls that will be in the transformed
216
+ // code need to be mapped to the "real" dependencies, so we need access to a map of placeholders
217
+ // to dependencies
218
+ const dep = node.value;
219
+ // $FlowFixMe[incompatible-type] Meta is untyped
220
+ const placeholder: string | void = dep.meta?.placeholder;
221
+ if (placeholder != null) {
222
+ placeholderToDependency.set(placeholder, dep);
223
+ }
198
224
  }
199
225
  });
200
226
 
@@ -204,6 +230,51 @@ export default class BundleGraph {
204
230
  walkVisited.add(nodeId);
205
231
 
206
232
  let node = nullthrows(assetGraph.getNode(nodeId));
233
+
234
+ if (getFeatureFlag('conditionalBundlingApi') && node.type === 'asset') {
235
+ const asset = node.value;
236
+ if (Array.isArray(asset.meta.conditions)) {
237
+ // $FlowFixMe
238
+ for (const condition of (asset.meta.conditions: ConditionMeta[])) {
239
+ // Resolve the placeholders that were attached to the asset in JSTransformer to dependencies,
240
+ // as well as create a public id for the condition.
241
+ const {
242
+ key,
243
+ ifTruePlaceholder,
244
+ ifFalsePlaceholder,
245
+ }: {
246
+ key: string,
247
+ ifTruePlaceholder: string,
248
+ ifFalsePlaceholder: string,
249
+ ...
250
+ } = condition;
251
+
252
+ const condHash = hashString(
253
+ `${key}:${ifTruePlaceholder}:${ifFalsePlaceholder}`,
254
+ );
255
+ const condPublicId = getPublicId(condHash, v => conditions.has(v));
256
+
257
+ if (conditions.has(condHash)) {
258
+ throw new Error('Unexpected duplicate asset');
259
+ } else {
260
+ conditions.set(condHash, {
261
+ publicId: condPublicId,
262
+ assets: new Set([asset]),
263
+ key,
264
+ ifTrueDependency: nullthrows(
265
+ placeholderToDependency.get(ifTruePlaceholder),
266
+ 'ifTruePlaceholder was undefined',
267
+ ),
268
+ ifFalseDependency: nullthrows(
269
+ placeholderToDependency.get(ifFalsePlaceholder),
270
+ 'ifFalsePlaceholder was undefined',
271
+ ),
272
+ });
273
+ }
274
+ }
275
+ }
276
+ }
277
+
207
278
  if (
208
279
  node.type === 'dependency' &&
209
280
  node.value.symbols != null &&
@@ -440,11 +511,13 @@ export default class BundleGraph {
440
511
  );
441
512
  }
442
513
  }
514
+
443
515
  return new BundleGraph({
444
516
  graph,
445
517
  assetPublicIds,
446
518
  bundleContentHashes: new Map(),
447
519
  publicIdByAssetId,
520
+ conditions,
448
521
  });
449
522
  }
450
523
 
@@ -455,6 +528,7 @@ export default class BundleGraph {
455
528
  assetPublicIds: this._assetPublicIds,
456
529
  bundleContentHashes: this._bundleContentHashes,
457
530
  publicIdByAssetId: this._publicIdByAssetId,
531
+ conditions: this._conditions,
458
532
  };
459
533
  }
460
534
 
@@ -464,6 +538,7 @@ export default class BundleGraph {
464
538
  assetPublicIds: serialized.assetPublicIds,
465
539
  bundleContentHashes: serialized.bundleContentHashes,
466
540
  publicIdByAssetId: serialized.publicIdByAssetId,
541
+ conditions: serialized.conditions,
467
542
  });
468
543
  }
469
544
 
@@ -1215,7 +1290,8 @@ export default class BundleGraph {
1215
1290
  .some(
1216
1291
  node =>
1217
1292
  node?.type === 'dependency' &&
1218
- node.value.priority === Priority.lazy &&
1293
+ (node.value.priority === Priority.lazy ||
1294
+ node.value.priority === Priority.conditional) &&
1219
1295
  node.value.specifierType !== SpecifierType.url,
1220
1296
  )
1221
1297
  ) {
@@ -16,7 +16,7 @@ import nullthrows from 'nullthrows';
16
16
  import {setEqual} from '@atlaspack/utils';
17
17
  import logger from '@atlaspack/logger';
18
18
  import {md, convertSourceLocationToHighlight} from '@atlaspack/diagnostic';
19
- import {BundleBehavior} from './types';
19
+ import {BundleBehavior, Priority} from './types';
20
20
  import {fromProjectPathRelative, fromProjectPath} from './projectPath';
21
21
 
22
22
  export function propagateSymbols({
@@ -101,7 +101,10 @@ export function propagateSymbols({
101
101
  namespaceReexportedSymbols.add('*');
102
102
  } else {
103
103
  for (let incomingDep of incomingDeps) {
104
- if (incomingDep.value.symbols == null) {
104
+ if (
105
+ incomingDep.value.symbols == null ||
106
+ incomingDep.value.priority === Priority.conditional
107
+ ) {
105
108
  if (incomingDep.value.sourceAssetId == null) {
106
109
  // The root dependency on non-library builds
107
110
  isEntry = true;
@@ -217,4 +217,5 @@ const PriorityMap = {
217
217
  sync: 0,
218
218
  parallel: 1,
219
219
  lazy: 2,
220
+ conditional: 3,
220
221
  };
@@ -332,4 +332,110 @@ export default class BundleGraph<TBundle: IBundle>
332
332
  targetToInternalTarget(target),
333
333
  );
334
334
  }
335
+
336
+ // Given a set of dependencies, return any conditions where those dependencies are either
337
+ // the true or false dependency for those conditions. This is currently used to work out which
338
+ // conditions belong to a bundle in packaging.
339
+ getConditionsForDependencies(deps: Array<IDependency>): Set<{|
340
+ publicId: string,
341
+ key: string,
342
+ ifTrueDependency: IDependency,
343
+ ifFalseDependency: IDependency,
344
+ ifTrueAssetId: string,
345
+ ifFalseAssetId: string,
346
+ |}> {
347
+ const conditions = new Set();
348
+ const depIds = deps.map(dep => dep.id);
349
+ for (const condition of this.#graph._conditions.values()) {
350
+ if (
351
+ depIds.includes(condition.ifTrueDependency.id) ||
352
+ depIds.includes(condition.ifFalseDependency.id)
353
+ ) {
354
+ const [trueAsset, falseAsset] = [
355
+ condition.ifTrueDependency,
356
+ condition.ifFalseDependency,
357
+ ].map(dep => {
358
+ const resolved = nullthrows(this.#graph.resolveAsyncDependency(dep));
359
+ if (resolved.type === 'asset') {
360
+ return resolved.value;
361
+ } else {
362
+ return this.#graph.getAssetById(resolved.value.entryAssetId);
363
+ }
364
+ });
365
+
366
+ conditions.add({
367
+ publicId: condition.publicId,
368
+ key: condition.key,
369
+ ifTrueDependency: nullthrows(
370
+ deps.find(dep => dep.id === condition.ifTrueDependency.id),
371
+ 'ifTrueDependency was null',
372
+ ),
373
+ ifFalseDependency: nullthrows(
374
+ deps.find(dep => dep.id === condition.ifFalseDependency.id),
375
+ 'ifFalseDependency was null',
376
+ ),
377
+ ifTrueAssetId: this.#graph.getAssetPublicId(trueAsset),
378
+ ifFalseAssetId: this.#graph.getAssetPublicId(falseAsset),
379
+ });
380
+ }
381
+ }
382
+
383
+ return conditions;
384
+ }
385
+
386
+ // This is used to generate information for building a manifest that can
387
+ // be used by a webserver to understand which conditions are used by which bundles,
388
+ // and which bundles those conditions require depending on what they evaluate to.
389
+ getConditionalBundleMapping(): Map<
390
+ TBundle,
391
+ Map<
392
+ string,
393
+ {|
394
+ ifTrueBundles: Array<TBundle>,
395
+ ifFalseBundles: Array<TBundle>,
396
+ |},
397
+ >,
398
+ > {
399
+ let bundleConditions = new Map();
400
+
401
+ // Convert the internal references in conditions to public API references
402
+ for (const cond of this.#graph._conditions.values()) {
403
+ let assets = Array.from(cond.assets).map(asset =>
404
+ nullthrows(this.getAssetById(asset.id)),
405
+ );
406
+ let bundles = new Set<TBundle>();
407
+ let ifTrueBundles = [];
408
+ let ifFalseBundles = [];
409
+ for (const asset of assets) {
410
+ const bundlesWithAsset = this.getBundlesWithAsset(asset);
411
+ for (const bundle of bundlesWithAsset) {
412
+ bundles.add(bundle);
413
+ }
414
+ const assetDeps = this.getDependencies(asset);
415
+ const depToBundles = dep => {
416
+ const publicDep = nullthrows(
417
+ assetDeps.find(assetDep => dep.id === assetDep.id),
418
+ );
419
+ const resolved = nullthrows(this.resolveAsyncDependency(publicDep));
420
+ invariant(resolved.type === 'bundle_group');
421
+ return this.getBundlesInBundleGroup(resolved.value);
422
+ };
423
+ ifTrueBundles.push(...depToBundles(cond.ifTrueDependency));
424
+ ifFalseBundles.push(...depToBundles(cond.ifFalseDependency));
425
+ }
426
+
427
+ for (let bundle of bundles) {
428
+ const conditions = bundleConditions.get(bundle) ?? new Map();
429
+
430
+ conditions.set(cond.key, {
431
+ ifTrueBundles,
432
+ ifFalseBundles,
433
+ });
434
+
435
+ bundleConditions.set(bundle, conditions);
436
+ }
437
+ }
438
+
439
+ return bundleConditions;
440
+ }
335
441
  }
@@ -231,6 +231,8 @@ export default async function resolveOptions(
231
231
  outputFormat: initialOptions?.defaultTargetOptions?.outputFormat,
232
232
  isLibrary: initialOptions?.defaultTargetOptions?.isLibrary,
233
233
  },
234
+ // unused, feature-flags are set above this to allow this function to use
235
+ // feature-flags
234
236
  featureFlags: {...DEFAULT_FEATURE_FLAGS, ...initialOptions?.featureFlags},
235
237
  parcelVersion: ATLASPACK_VERSION,
236
238
  };
package/src/types.js CHANGED
@@ -115,6 +115,7 @@ export const Priority = {
115
115
  sync: 0,
116
116
  parallel: 1,
117
117
  lazy: 2,
118
+ conditional: 3,
118
119
  };
119
120
 
120
121
  // Must match package_json.rs in node-resolver-rs.
@@ -542,6 +543,7 @@ export type Bundle = {|
542
543
  displayName: ?string,
543
544
  pipeline: ?string,
544
545
  manualSharedBundle?: ?string,
546
+ conditions?: Map<string, string>,
545
547
  |};
546
548
 
547
549
  export type BundleNode = {|
@@ -580,3 +582,11 @@ export type ValidationOpts = {|
580
582
  |};
581
583
 
582
584
  export type ReportFn = (event: ReporterEvent) => void | Promise<void>;
585
+
586
+ export type Condition = {|
587
+ publicId: string,
588
+ assets: Set<Asset>,
589
+ key: string,
590
+ ifTrueDependency: Dependency,
591
+ ifFalseDependency: Dependency,
592
+ |};
@@ -42,6 +42,7 @@ describe('Public Bundle', () => {
42
42
  assetPublicIds: new Set(),
43
43
  publicIdByAssetId: new Map(),
44
44
  bundleContentHashes: new Map(),
45
+ conditions: new Map(),
45
46
  });
46
47
  });
47
48