@atlaspack/core 2.17.3 → 2.18.0

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/lib/AssetGraph.js +17 -6
  3. package/lib/Atlaspack.js +3 -1
  4. package/lib/BundleGraph.js +6 -5
  5. package/lib/Dependency.js +6 -2
  6. package/lib/Environment.js +4 -3
  7. package/lib/EnvironmentManager.js +137 -0
  8. package/lib/InternalConfig.js +3 -2
  9. package/lib/PackagerRunner.js +52 -15
  10. package/lib/RequestTracker.js +191 -89
  11. package/lib/UncommittedAsset.js +20 -2
  12. package/lib/applyRuntimes.js +2 -1
  13. package/lib/assetUtils.js +2 -1
  14. package/lib/atlaspack-v3/worker/worker.js +8 -0
  15. package/lib/public/Asset.js +3 -2
  16. package/lib/public/Bundle.js +2 -1
  17. package/lib/public/BundleGraph.js +21 -5
  18. package/lib/public/Config.js +98 -3
  19. package/lib/public/Dependency.js +2 -1
  20. package/lib/public/MutableBundleGraph.js +2 -1
  21. package/lib/public/Target.js +2 -1
  22. package/lib/requests/AssetGraphRequest.js +13 -1
  23. package/lib/requests/AssetRequest.js +2 -1
  24. package/lib/requests/BundleGraphRequest.js +13 -1
  25. package/lib/requests/ConfigRequest.js +27 -4
  26. package/lib/requests/TargetRequest.js +18 -16
  27. package/lib/requests/WriteBundleRequest.js +15 -3
  28. package/lib/requests/WriteBundlesRequest.js +1 -0
  29. package/lib/resolveOptions.js +4 -2
  30. package/package.json +13 -13
  31. package/src/AssetGraph.js +12 -6
  32. package/src/Atlaspack.js +5 -4
  33. package/src/BundleGraph.js +13 -8
  34. package/src/Dependency.js +13 -5
  35. package/src/Environment.js +8 -5
  36. package/src/EnvironmentManager.js +145 -0
  37. package/src/InternalConfig.js +6 -5
  38. package/src/PackagerRunner.js +72 -20
  39. package/src/RequestTracker.js +330 -146
  40. package/src/UncommittedAsset.js +23 -3
  41. package/src/applyRuntimes.js +6 -1
  42. package/src/assetUtils.js +4 -3
  43. package/src/atlaspack-v3/worker/compat/plugin-config.js +9 -5
  44. package/src/atlaspack-v3/worker/worker.js +7 -0
  45. package/src/public/Asset.js +9 -2
  46. package/src/public/Bundle.js +2 -1
  47. package/src/public/BundleGraph.js +22 -5
  48. package/src/public/Config.js +129 -14
  49. package/src/public/Dependency.js +2 -1
  50. package/src/public/MutableBundleGraph.js +2 -1
  51. package/src/public/Target.js +2 -1
  52. package/src/requests/AssetGraphRequest.js +13 -3
  53. package/src/requests/AssetRequest.js +2 -1
  54. package/src/requests/BundleGraphRequest.js +13 -3
  55. package/src/requests/ConfigRequest.js +33 -9
  56. package/src/requests/TargetRequest.js +19 -25
  57. package/src/requests/WriteBundleRequest.js +14 -8
  58. package/src/requests/WriteBundlesRequest.js +1 -0
  59. package/src/resolveOptions.js +4 -2
  60. package/src/types.js +9 -7
  61. package/test/Environment.test.js +43 -34
  62. package/test/EnvironmentManager.test.js +192 -0
  63. package/test/PublicEnvironment.test.js +10 -7
  64. package/test/RequestTracker.test.js +115 -3
  65. package/test/public/Config.test.js +108 -0
  66. package/test/requests/ConfigRequest.test.js +187 -3
  67. package/test/test-utils.js +4 -9
@@ -30,7 +30,13 @@ import {ATLASPACK_VERSION} from './constants';
30
30
  import {createAsset, createAssetIdFromOptions} from './assetUtils';
31
31
  import {BundleBehaviorNames} from './types';
32
32
  import {invalidateOnFileCreateToInternal, createInvalidations} from './utils';
33
- import {type ProjectPath, fromProjectPath} from './projectPath';
33
+ import {
34
+ type ProjectPath,
35
+ fromProjectPath,
36
+ fromProjectPathRelative,
37
+ } from './projectPath';
38
+ import {getFeatureFlag} from '@atlaspack/feature-flags';
39
+ import {fromEnvironmentId} from './EnvironmentManager';
34
40
 
35
41
  type UncommittedAssetOptions = {|
36
42
  value: Asset,
@@ -153,7 +159,9 @@ export default class UncommittedAsset {
153
159
  size = content.length;
154
160
  }
155
161
 
162
+ // Maybe we should just store this in a file instead of LMDB
156
163
  await this.options.cache.setBlob(contentKey, content);
164
+
157
165
  return {size, hash};
158
166
  }
159
167
 
@@ -214,6 +222,9 @@ export default class UncommittedAsset {
214
222
  this.clearAST();
215
223
  }
216
224
 
225
+ /**
226
+ * @deprecated This has been broken on any cache other than FSCache for a long time.
227
+ */
217
228
  setStream(stream: Readable) {
218
229
  this.content = stream;
219
230
  this.clearAST();
@@ -296,6 +307,11 @@ export default class UncommittedAsset {
296
307
  }
297
308
 
298
309
  getCacheKey(key: string): string {
310
+ if (getFeatureFlag('cachePerformanceImprovements')) {
311
+ const filePath = fromProjectPathRelative(this.value.filePath);
312
+ return `Asset/${ATLASPACK_VERSION}/${filePath}/${this.value.id}/${key}`;
313
+ }
314
+
299
315
  return hashString(ATLASPACK_VERSION + key + this.value.id);
300
316
  }
301
317
 
@@ -306,7 +322,11 @@ export default class UncommittedAsset {
306
322
  ...rest,
307
323
  // $FlowFixMe "convert" the $ReadOnlyMaps to the interal mutable one
308
324
  symbols,
309
- env: mergeEnvironments(this.options.projectRoot, this.value.env, env),
325
+ env: mergeEnvironments(
326
+ this.options.projectRoot,
327
+ fromEnvironmentId(this.value.env),
328
+ env,
329
+ ),
310
330
  sourceAssetId: this.value.id,
311
331
  sourcePath: fromProjectPath(
312
332
  this.options.projectRoot,
@@ -371,7 +391,7 @@ export default class UncommittedAsset {
371
391
  isSource: this.value.isSource,
372
392
  env: mergeEnvironments(
373
393
  this.options.projectRoot,
374
- this.value.env,
394
+ fromEnvironmentId(this.value.env),
375
395
  result.env,
376
396
  ),
377
397
  dependencies:
@@ -34,6 +34,7 @@ import {createDevDependency, runDevDepRequest} from './requests/DevDepRequest';
34
34
  import {toProjectPath, fromProjectPathRelative} from './projectPath';
35
35
  import {tracer, PluginTracer} from '@atlaspack/profiler';
36
36
  import {DefaultMap} from '@atlaspack/utils';
37
+ import {fromEnvironmentId} from './EnvironmentManager';
37
38
 
38
39
  type RuntimeConnection = {|
39
40
  bundle: InternalBundle,
@@ -159,7 +160,11 @@ export default async function applyRuntimes<TResult: RequestResult>({
159
160
  let assetGroup = {
160
161
  code,
161
162
  filePath: toProjectPath(options.projectRoot, sourceName),
162
- env: mergeEnvironments(options.projectRoot, bundle.env, env),
163
+ env: mergeEnvironments(
164
+ options.projectRoot,
165
+ fromEnvironmentId(bundle.env),
166
+ env,
167
+ ),
163
168
  // Runtime assets should be considered source, as they should be
164
169
  // e.g. compiled to run in the target environment
165
170
  isSource: true,
package/src/assetUtils.js CHANGED
@@ -16,7 +16,6 @@ import type {
16
16
  Asset,
17
17
  RequestInvalidation,
18
18
  Dependency,
19
- Environment,
20
19
  AtlaspackOptions,
21
20
  } from './types';
22
21
 
@@ -40,6 +39,8 @@ import {hashString, createAssetId as createAssetIdRust} from '@atlaspack/rust';
40
39
  import {BundleBehavior as BundleBehaviorMap} from './types';
41
40
  import {PluginTracer} from '@atlaspack/profiler';
42
41
  import {identifierRegistry} from './IdentifierRegistry';
42
+ import type {EnvironmentRef} from './EnvironmentManager';
43
+ import {toEnvironmentId} from './EnvironmentManager';
43
44
 
44
45
  export type AssetOptions = {|
45
46
  id?: string,
@@ -56,7 +57,7 @@ export type AssetOptions = {|
56
57
  bundleBehavior?: ?BundleBehavior,
57
58
  isBundleSplittable?: ?boolean,
58
59
  isSource: boolean,
59
- env: Environment,
60
+ env: EnvironmentRef,
60
61
  meta?: Meta,
61
62
  outputHash?: ?string,
62
63
  pipeline?: ?string,
@@ -71,7 +72,7 @@ export type AssetOptions = {|
71
72
 
72
73
  export function createAssetIdFromOptions(options: AssetOptions): string {
73
74
  const data = {
74
- environmentId: options.env.id,
75
+ environmentId: toEnvironmentId(options.env),
75
76
  filePath: options.filePath,
76
77
  code: options.code,
77
78
  pipeline: options.pipeline,
@@ -100,11 +100,15 @@ export class PluginConfig implements IPluginConfig {
100
100
  // eslint-disable-next-line no-unused-vars
101
101
  filePaths: Array<FilePath>,
102
102
  // eslint-disable-next-line no-unused-vars
103
- options?: {|
104
- packageKey?: string,
105
- parse?: boolean,
106
- exclude?: boolean,
107
- |},
103
+ options?:
104
+ | {|
105
+ packageKey?: string,
106
+ parse?: boolean,
107
+ exclude?: boolean,
108
+ |}
109
+ | {|
110
+ readTracking?: boolean,
111
+ |},
108
112
  ): Promise<?ConfigResultWithFilePath<T>> {
109
113
  return this.#inner.getConfigFrom(searchPath, filePaths, options);
110
114
  }
@@ -141,6 +141,13 @@ export class AtlaspackWorker {
141
141
  };
142
142
  }
143
143
 
144
+ if (result.isExcluded) {
145
+ return {
146
+ invalidations: [],
147
+ resolution: {type: 'excluded'},
148
+ };
149
+ }
150
+
144
151
  return {
145
152
  invalidations: [],
146
153
  resolution: {
@@ -36,6 +36,7 @@ import {
36
36
  BundleBehaviorNames,
37
37
  } from '../types';
38
38
  import {toInternalSourceLocation} from '../utils';
39
+ import {fromEnvironmentId} from '../EnvironmentManager';
39
40
 
40
41
  const inspect = Symbol.for('nodejs.util.inspect.custom');
41
42
 
@@ -101,7 +102,10 @@ class BaseAsset {
101
102
  }
102
103
 
103
104
  get env(): IEnvironment {
104
- return new Environment(this.#asset.value.env, this.#asset.options);
105
+ return new Environment(
106
+ fromEnvironmentId(this.#asset.value.env),
107
+ this.#asset.options,
108
+ );
105
109
  }
106
110
 
107
111
  get fs(): FileSystem {
@@ -210,7 +214,10 @@ export class Asset extends BaseAsset implements IAsset {
210
214
  }
211
215
 
212
216
  get env(): IEnvironment {
213
- this.#env ??= new Environment(this.#asset.value.env, this.#asset.options);
217
+ this.#env ??= new Environment(
218
+ fromEnvironmentId(this.#asset.value.env),
219
+ this.#asset.options,
220
+ );
214
221
  return this.#env;
215
222
  }
216
223
 
@@ -34,6 +34,7 @@ import {
34
34
  import Target from './Target';
35
35
  import {BundleBehaviorNames} from '../types';
36
36
  import {fromProjectPath} from '../projectPath';
37
+ import {fromEnvironmentId} from '../EnvironmentManager';
37
38
 
38
39
  const inspect = Symbol.for('nodejs.util.inspect.custom');
39
40
 
@@ -123,7 +124,7 @@ export class Bundle implements IBundle {
123
124
  }
124
125
 
125
126
  get env(): IEnvironment {
126
- return new Environment(this.#bundle.env, this.#options);
127
+ return new Environment(fromEnvironmentId(this.#bundle.env), this.#options);
127
128
  }
128
129
 
129
130
  get needsStableName(): ?boolean {
@@ -31,6 +31,7 @@ import Dependency, {
31
31
  import {targetToInternalTarget} from './Target';
32
32
  import {fromInternalSourceLocation} from '../utils';
33
33
  import BundleGroup, {bundleGroupToInternalBundleGroup} from './BundleGroup';
34
+ import {getFeatureFlag} from '@atlaspack/feature-flags';
34
35
 
35
36
  // Friendly access for other modules within this package that need access
36
37
  // to the internal bundle.
@@ -489,11 +490,27 @@ export default class BundleGraph<TBundle: IBundle>
489
490
  for (let bundle of bundles) {
490
491
  const conditions = bundleConditions.get(bundle.id) ?? new Map();
491
492
 
492
- conditions.set(cond.key, {
493
- bundle,
494
- ifTrueBundles,
495
- ifFalseBundles,
496
- });
493
+ const currentCondition = conditions.get(cond.key);
494
+
495
+ if (getFeatureFlag('conditionalBundlingReporterSameConditionFix')) {
496
+ conditions.set(cond.key, {
497
+ bundle,
498
+ ifTrueBundles: [
499
+ ...(currentCondition?.ifTrueBundles ?? []),
500
+ ...ifTrueBundles,
501
+ ],
502
+ ifFalseBundles: [
503
+ ...(currentCondition?.ifFalseBundles ?? []),
504
+ ...ifFalseBundles,
505
+ ],
506
+ });
507
+ } else {
508
+ conditions.set(cond.key, {
509
+ bundle,
510
+ ifTrueBundles,
511
+ ifFalseBundles,
512
+ });
513
+ }
497
514
 
498
515
  bundleConditions.set(bundle.id, conditions);
499
516
  }
@@ -20,12 +20,90 @@ import {
20
20
  } from '@atlaspack/utils';
21
21
  import Environment from './Environment';
22
22
  import {fromProjectPath, toProjectPath} from '../projectPath';
23
+ import {getFeatureFlag} from '@atlaspack/feature-flags';
24
+ import {fromEnvironmentId} from '../EnvironmentManager';
23
25
 
24
26
  const internalConfigToConfig: DefaultWeakMap<
25
27
  AtlaspackOptions,
26
28
  WeakMap<Config, PublicConfig>,
27
29
  > = new DefaultWeakMap(() => new WeakMap());
28
30
 
31
+ /**
32
+ * Implements read tracking over an object.
33
+ *
34
+ * Calling this function with a non-trivial object like a class instance will fail to work.
35
+ *
36
+ * We track reads to fields that resolve to:
37
+ *
38
+ * - primitive values
39
+ * - arrays
40
+ *
41
+ * That is, reading a nested field `a.b.c` will make a single call to `onRead` with the path
42
+ * `['a', 'b', 'c']`.
43
+ *
44
+ * In case the value is null or an array, we will track the read as well.
45
+ *
46
+ * Iterating over `Object.keys(obj.field)` will register a read for the `['field']` path.
47
+ * Other reads work normally.
48
+ *
49
+ * @example
50
+ *
51
+ * const usedPaths = new Set();
52
+ * const onRead = (path) => {
53
+ * usedPaths.add(path);
54
+ * };
55
+ *
56
+ * const config = makeConfigProxy(onRead, {a: {b: {c: 'd'}}})
57
+ * console.log(config.a.b.c);
58
+ * console.log(Array.from(usedPaths));
59
+ * // We get a single read for the path
60
+ * // ['a', 'b', 'c']
61
+ *
62
+ */
63
+ export function makeConfigProxy<T>(
64
+ onRead: (path: string[]) => void,
65
+ config: T,
66
+ ): T {
67
+ const reportedPaths = new Set();
68
+ const reportPath = (path) => {
69
+ if (reportedPaths.has(path)) {
70
+ return;
71
+ }
72
+ reportedPaths.add(path);
73
+ onRead(path);
74
+ };
75
+
76
+ const makeProxy = (target, path) => {
77
+ return new Proxy(target, {
78
+ ownKeys(target) {
79
+ reportPath(path);
80
+
81
+ // $FlowFixMe
82
+ return Object.getOwnPropertyNames(target);
83
+ },
84
+ get(target, prop) {
85
+ // $FlowFixMe
86
+ const value = target[prop];
87
+
88
+ if (
89
+ typeof value === 'object' &&
90
+ value != null &&
91
+ !Array.isArray(value)
92
+ ) {
93
+ return makeProxy(value, [...path, prop]);
94
+ }
95
+
96
+ reportPath([...path, prop]);
97
+
98
+ return value;
99
+ },
100
+ });
101
+ };
102
+
103
+ // $FlowFixMe
104
+ return makeProxy(config, []);
105
+ }
106
+
29
107
  export default class PublicConfig implements IConfig {
30
108
  #config /*: Config */;
31
109
  #pkg /*: ?PackageJSON */;
@@ -45,7 +123,7 @@ export default class PublicConfig implements IConfig {
45
123
  }
46
124
 
47
125
  get env(): Environment {
48
- return new Environment(this.#config.env, this.#options);
126
+ return new Environment(fromEnvironmentId(this.#config.env), this.#options);
49
127
  }
50
128
 
51
129
  get searchPath(): FilePath {
@@ -75,7 +153,7 @@ export default class PublicConfig implements IConfig {
75
153
  );
76
154
  }
77
155
 
78
- invalidateOnConfigKeyChange(filePath: FilePath, configKey: string) {
156
+ invalidateOnConfigKeyChange(filePath: FilePath, configKey: string[]) {
79
157
  this.#config.invalidateOnConfigKeyChange.push({
80
158
  filePath: toProjectPath(this.#options.projectRoot, filePath),
81
159
  configKey,
@@ -132,11 +210,22 @@ export default class PublicConfig implements IConfig {
132
210
  async getConfigFrom<T>(
133
211
  searchPath: FilePath,
134
212
  fileNames: Array<string>,
135
- options: ?{|
136
- packageKey?: string,
137
- parse?: boolean,
138
- exclude?: boolean,
139
- |},
213
+ options:
214
+ | ?{|
215
+ /**
216
+ * @deprecated Use `configKey` instead.
217
+ */
218
+ packageKey?: string,
219
+ parse?: boolean,
220
+ exclude?: boolean,
221
+ |}
222
+ | ?{|
223
+ /**
224
+ * If specified, this function will return a proxy object that will track reads to
225
+ * config fields and only register invalidations for when those keys change.
226
+ */
227
+ readTracking?: boolean,
228
+ |},
140
229
  ): Promise<?ConfigResultWithFilePath<T>> {
141
230
  let packageKey = options?.packageKey;
142
231
  if (packageKey != null) {
@@ -146,7 +235,7 @@ export default class PublicConfig implements IConfig {
146
235
 
147
236
  if (pkg && pkg.contents[packageKey]) {
148
237
  // Invalidate only when the package key changes
149
- this.invalidateOnConfigKeyChange(pkg.filePath, packageKey);
238
+ this.invalidateOnConfigKeyChange(pkg.filePath, [packageKey]);
150
239
 
151
240
  return {
152
241
  contents: pkg.contents[packageKey],
@@ -155,6 +244,26 @@ export default class PublicConfig implements IConfig {
155
244
  }
156
245
  }
157
246
 
247
+ const readTracking = options?.readTracking;
248
+ if (readTracking === true) {
249
+ for (let fileName of fileNames) {
250
+ const config = await this.getConfigFrom(searchPath, [fileName], {
251
+ exclude: true,
252
+ });
253
+
254
+ if (config != null) {
255
+ return Promise.resolve({
256
+ contents: makeConfigProxy((keyPath) => {
257
+ this.invalidateOnConfigKeyChange(config.filePath, keyPath);
258
+ }, config.contents),
259
+ filePath: config.filePath,
260
+ });
261
+ }
262
+ }
263
+
264
+ // fall through so that file above invalidations are registered
265
+ }
266
+
158
267
  if (fileNames.length === 0) {
159
268
  return null;
160
269
  }
@@ -234,11 +343,15 @@ export default class PublicConfig implements IConfig {
234
343
 
235
344
  getConfig<T>(
236
345
  filePaths: Array<FilePath>,
237
- options: ?{|
238
- packageKey?: string,
239
- parse?: boolean,
240
- exclude?: boolean,
241
- |},
346
+ options:
347
+ | ?{|
348
+ packageKey?: string,
349
+ parse?: boolean,
350
+ exclude?: boolean,
351
+ |}
352
+ | {|
353
+ readTracking?: boolean,
354
+ |},
242
355
  ): Promise<?ConfigResultWithFilePath<T>> {
243
356
  return this.getConfigFrom(this.searchPath, filePaths, options);
244
357
  }
@@ -248,7 +361,9 @@ export default class PublicConfig implements IConfig {
248
361
  return this.#pkg;
249
362
  }
250
363
 
251
- let pkgConfig = await this.getConfig<PackageJSON>(['package.json']);
364
+ let pkgConfig = await this.getConfig<PackageJSON>(['package.json'], {
365
+ readTracking: getFeatureFlag('granularTsConfigInvalidation'),
366
+ });
252
367
  if (!pkgConfig) {
253
368
  return null;
254
369
  }
@@ -27,6 +27,7 @@ import {
27
27
  } from '../types';
28
28
  import {fromProjectPath} from '../projectPath';
29
29
  import {fromInternalSourceLocation} from '../utils';
30
+ import {fromEnvironmentId} from '../EnvironmentManager';
30
31
 
31
32
  const SpecifierTypeNames = Object.keys(SpecifierTypeMap);
32
33
  const PriorityNames = Object.keys(Priority);
@@ -112,7 +113,7 @@ export default class Dependency implements IDependency {
112
113
  }
113
114
 
114
115
  get env(): IEnvironment {
115
- return new Environment(this.#dep.env, this.#options);
116
+ return new Environment(fromEnvironmentId(this.#dep.env), this.#options);
116
117
  }
117
118
 
118
119
  get packageConditions(): ?Array<string> {
@@ -32,6 +32,7 @@ import {BundleBehavior} from '../types';
32
32
  import BundleGroup, {bundleGroupToInternalBundleGroup} from './BundleGroup';
33
33
  import type {ProjectPath} from '../projectPath';
34
34
  import {identifierRegistry} from '../IdentifierRegistry';
35
+ import {toEnvironmentRef} from '../EnvironmentManager';
35
36
 
36
37
  function createBundleId(data: {|
37
38
  entryAssetId: string | null,
@@ -236,7 +237,7 @@ export default class MutableBundleGraph
236
237
  : bundleId.slice(-8),
237
238
  type: opts.entryAsset ? opts.entryAsset.type : opts.type,
238
239
  env: opts.env
239
- ? environmentToInternalEnvironment(opts.env)
240
+ ? toEnvironmentRef(environmentToInternalEnvironment(opts.env))
240
241
  : nullthrows(entryAsset).env,
241
242
  entryAssetIds: entryAsset ? [entryAsset.id] : [],
242
243
  mainEntryId: entryAsset?.id,
@@ -11,6 +11,7 @@ import nullthrows from 'nullthrows';
11
11
  import Environment from './Environment';
12
12
  import {fromProjectPath} from '../projectPath';
13
13
  import {fromInternalSourceLocation} from '../utils';
14
+ import {fromEnvironmentId} from '../EnvironmentManager';
14
15
 
15
16
  const inspect = Symbol.for('nodejs.util.inspect.custom');
16
17
 
@@ -46,7 +47,7 @@ export default class Target implements ITarget {
46
47
  }
47
48
 
48
49
  get env(): IEnvironment {
49
- return new Environment(this.#target.env, this.#options);
50
+ return new Environment(fromEnvironmentId(this.#target.env), this.#options);
50
51
  }
51
52
 
52
53
  get name(): string {
@@ -20,6 +20,7 @@ import logger from '@atlaspack/logger';
20
20
 
21
21
  import invariant from 'assert';
22
22
  import nullthrows from 'nullthrows';
23
+ import {getFeatureFlag} from '@atlaspack/feature-flags';
23
24
  import {PromiseQueue, setEqual} from '@atlaspack/utils';
24
25
  import {hashString} from '@atlaspack/rust';
25
26
  import ThrowableDiagnostic from '@atlaspack/diagnostic';
@@ -161,12 +162,21 @@ export class AssetGraphBuilder {
161
162
  this.shouldBuildLazily = shouldBuildLazily ?? false;
162
163
  this.lazyIncludes = lazyIncludes ?? [];
163
164
  this.lazyExcludes = lazyExcludes ?? [];
164
- this.cacheKey =
165
- hashString(
165
+ if (getFeatureFlag('cachePerformanceImprovements')) {
166
+ const key = hashString(
166
167
  `${ATLASPACK_VERSION}${name}${JSON.stringify(entries) ?? ''}${
167
168
  options.mode
168
169
  }${options.shouldBuildLazily ? 'lazy' : 'eager'}`,
169
- ) + '-AssetGraph';
170
+ );
171
+ this.cacheKey = `AssetGraph/${ATLASPACK_VERSION}/${options.mode}/${key}`;
172
+ } else {
173
+ this.cacheKey =
174
+ hashString(
175
+ `${ATLASPACK_VERSION}${name}${JSON.stringify(entries) ?? ''}${
176
+ options.mode
177
+ }${options.shouldBuildLazily ? 'lazy' : 'eager'}`,
178
+ ) + '-AssetGraph';
179
+ }
170
180
 
171
181
  this.isSingleChangeRebuild =
172
182
  api
@@ -21,6 +21,7 @@ import {fromProjectPath, fromProjectPathRelative} from '../projectPath';
21
21
  import {report} from '../ReporterRunner';
22
22
  import {requestTypes} from '../RequestTracker';
23
23
  import type {DevDepRequestResult} from './DevDepRequest';
24
+ import {toEnvironmentId} from '../EnvironmentManager';
24
25
 
25
26
  type RunInput<TResult> = {|
26
27
  input: AssetRequestInput,
@@ -51,7 +52,7 @@ function getId(input: AssetRequestInput) {
51
52
  return hashString(
52
53
  type +
53
54
  fromProjectPathRelative(input.filePath) +
54
- input.env.id +
55
+ toEnvironmentId(input.env) +
55
56
  String(input.isSource) +
56
57
  String(input.sideEffects) +
57
58
  (input.code ?? '') +
@@ -20,6 +20,7 @@ import invariant from 'assert';
20
20
  import assert from 'assert';
21
21
  import nullthrows from 'nullthrows';
22
22
  import {PluginLogger} from '@atlaspack/logger';
23
+ import {getFeatureFlag} from '@atlaspack/feature-flags';
23
24
  import ThrowableDiagnostic, {errorToDiagnostic} from '@atlaspack/diagnostic';
24
25
  import AssetGraph from '../AssetGraph';
25
26
  import BundleGraph from '../public/BundleGraph';
@@ -282,12 +283,21 @@ class BundlerRunner {
282
283
  this.pluginOptions = new PluginOptions(
283
284
  optionsProxy(this.options, api.invalidateOnOptionChange),
284
285
  );
285
- this.cacheKey =
286
- hashString(
286
+ if (getFeatureFlag('cachePerformanceImprovements')) {
287
+ const key = hashString(
287
288
  `${ATLASPACK_VERSION}:BundleGraph:${
288
289
  JSON.stringify(options.entries) ?? ''
289
290
  }${options.mode}${options.shouldBuildLazily ? 'lazy' : 'eager'}`,
290
- ) + '-BundleGraph';
291
+ );
292
+ this.cacheKey = `BundleGraph/${ATLASPACK_VERSION}/${options.mode}/${key}`;
293
+ } else {
294
+ this.cacheKey =
295
+ hashString(
296
+ `${ATLASPACK_VERSION}:BundleGraph:${
297
+ JSON.stringify(options.entries) ?? ''
298
+ }${options.mode}${options.shouldBuildLazily ? 'lazy' : 'eager'}`,
299
+ ) + '-BundleGraph';
300
+ }
291
301
  }
292
302
 
293
303
  async loadConfigs() {
@@ -63,7 +63,7 @@ export type ConfigRequest = {
63
63
  invalidateOnFileChange: Set<ProjectPath>,
64
64
  invalidateOnConfigKeyChange: Array<{|
65
65
  filePath: ProjectPath,
66
- configKey: string,
66
+ configKey: string[],
67
67
  |}>,
68
68
  invalidateOnFileCreate: Array<InternalFileCreateInvalidation>,
69
69
  invalidateOnEnvChange: Set<string>,
@@ -108,34 +108,58 @@ export async function loadPluginConfig<T: PluginWithLoadConfig>(
108
108
  }
109
109
  }
110
110
 
111
+ /**
112
+ * Return value at a given key path within an object.
113
+ *
114
+ * @example
115
+ * const obj = { a: { b: { c: 'd' } } };
116
+ * getValueAtPath(obj, ['a', 'b', 'c']); // 'd'
117
+ * getValueAtPath(obj, ['a', 'b', 'd']); // undefined
118
+ * getValueAtPath(obj, ['a', 'b']); // { c: 'd' }
119
+ * getValueAtPath(obj, ['a', 'b', 'c', 'd']); // undefined
120
+ */
121
+ export function getValueAtPath(obj: Object, key: string[]): any {
122
+ let current = obj;
123
+ for (let part of key) {
124
+ if (current == null) {
125
+ return undefined;
126
+ }
127
+ current = current[part];
128
+ }
129
+ return current;
130
+ }
131
+
111
132
  const configKeyCache = createBuildCache();
112
133
 
113
134
  export async function getConfigKeyContentHash(
114
135
  filePath: ProjectPath,
115
- configKey: string,
136
+ configKey: string[],
116
137
  options: AtlaspackOptions,
117
138
  ): Async<string> {
118
- let cacheKey = `${fromProjectPathRelative(filePath)}:${configKey}`;
139
+ let cacheKey = `${fromProjectPathRelative(filePath)}:${JSON.stringify(
140
+ configKey,
141
+ )}`;
119
142
  let cachedValue = configKeyCache.get(cacheKey);
120
143
 
121
144
  if (cachedValue) {
122
145
  return cachedValue;
123
146
  }
124
147
 
125
- let conf = await readConfig(
148
+ const conf = await readConfig(
126
149
  options.inputFS,
127
150
  fromProjectPath(options.projectRoot, filePath),
128
151
  );
129
152
 
130
- if (conf == null || conf.config[configKey] == null) {
153
+ const value = getValueAtPath(conf?.config, configKey);
154
+ if (conf == null || value == null) {
131
155
  // This can occur when a config key has been removed entirely during `respondToFSEvents`
132
156
  return '';
133
157
  }
134
158
 
135
- let contentHash =
136
- typeof conf.config[configKey] === 'object'
137
- ? hashObject(conf.config[configKey])
138
- : hashString(JSON.stringify(conf.config[configKey]));
159
+ const contentHash =
160
+ typeof value === 'object'
161
+ ? hashObject(value)
162
+ : hashString(JSON.stringify(value));
139
163
 
140
164
  configKeyCache.set(cacheKey, contentHash);
141
165