@atlaspack/core 2.32.0 → 2.32.1

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.
@@ -22,6 +22,7 @@ import type {
22
22
  BundleNode,
23
23
  Dependency,
24
24
  DependencyNode,
25
+ Environment,
25
26
  InternalSourceLocation,
26
27
  Target,
27
28
  Condition,
@@ -75,7 +76,7 @@ export const bundleGraphEdgeTypes = {
75
76
  internal_async: 5,
76
77
  // This type is used to mark an edge between a bundle and a conditional bundle.
77
78
  // This allows efficient discovery of conditional bundles in packaging
78
- conditional: 5,
79
+ conditional: 6,
79
80
  } as const;
80
81
 
81
82
  export type BundleGraphEdgeType =
@@ -574,11 +575,13 @@ export default class BundleGraph {
574
575
 
575
576
  /**
576
577
  * Serialize the bundle graph for efficient transfer to native Rust code.
577
- * Returns a JSON string of nodes and an array of edges.
578
+ * Returns a JSON string of nodes, an array of edges, and a map of asset IDs to public IDs.
578
579
  */
579
580
  serializeForNative(): {
580
581
  nodesJson: string;
581
582
  edges: [number, number, BundleGraphEdgeType][];
583
+ publicIdByAssetId: Record<string, string>;
584
+ environmentsJson: string;
582
585
  } {
583
586
  const start = performance.now();
584
587
 
@@ -593,18 +596,62 @@ export default class BundleGraph {
593
596
  next = edgeIterator.next();
594
597
  }
595
598
 
599
+ // Extract and deduplicate environments
600
+ const environmentMap = new Map<string, Environment>();
601
+ const extractEnvironment = (envRef: EnvironmentRef): string => {
602
+ const env = fromEnvironmentId(envRef);
603
+ const envId = env.id;
604
+ if (!environmentMap.has(envId)) {
605
+ environmentMap.set(envId, env);
606
+ }
607
+ return envId;
608
+ };
609
+
610
+ // Replace env objects with env IDs in nodes
611
+ const processedNodes = nodes.map((node) => {
612
+ const processedNode = {...node};
613
+ if (node.type === 'asset' && node.value?.env) {
614
+ processedNode.value = {
615
+ ...node.value,
616
+ env: extractEnvironment(node.value.env),
617
+ };
618
+ } else if (node.type === 'dependency' && node.value?.env) {
619
+ processedNode.value = {
620
+ ...node.value,
621
+ env: extractEnvironment(node.value.env),
622
+ };
623
+ } else if (node.type === 'bundle' && node.value?.env) {
624
+ processedNode.value = {
625
+ ...node.value,
626
+ env: extractEnvironment(node.value.env),
627
+ };
628
+ }
629
+ return processedNode;
630
+ });
631
+
596
632
  // Optimize nodes by omitting null/undefined values to reduce JSON size
597
- const optimizedNodes = nodes.map((node) => this._omitNulls(node));
633
+ const optimizedNodes = processedNodes.map((node) => this._omitNulls(node));
598
634
  const nodesJson = JSON.stringify(optimizedNodes);
599
635
 
636
+ // Serialize environments as array
637
+ const environments = Array.from(environmentMap.values());
638
+ const environmentsJson = JSON.stringify(environments);
639
+
640
+ // Convert Map to plain object for serialization
641
+ const publicIdByAssetId: Record<string, string> = {};
642
+ for (const [assetId, publicId] of this._publicIdByAssetId) {
643
+ publicIdByAssetId[assetId] = publicId;
644
+ }
645
+
600
646
  const duration = performance.now() - start;
601
- const sizeMB = (nodesJson.length / (1024 * 1024)).toFixed(2);
647
+ const nodesSizeMB = (nodesJson.length / (1024 * 1024)).toFixed(2);
648
+ const envsSizeMB = (environmentsJson.length / (1024 * 1024)).toFixed(2);
602
649
  logger.verbose({
603
650
  origin: '@atlaspack/core',
604
- message: `serializeForNative: ${duration.toFixed(1)}ms, ${sizeMB}MB JSON, ${nodes.length} nodes, ${edges.length} edges`,
651
+ message: `serializeForNative: ${duration.toFixed(1)}ms, ${nodesSizeMB}MB nodes, ${envsSizeMB}MB envs (${environmentMap.size} unique), ${nodes.length} nodes, ${edges.length} edges`,
605
652
  });
606
653
 
607
- return {nodesJson, edges};
654
+ return {nodesJson, edges, publicIdByAssetId, environmentsJson};
608
655
  }
609
656
 
610
657
  /**
@@ -1773,8 +1820,36 @@ export default class BundleGraph {
1773
1820
  }
1774
1821
 
1775
1822
  /**
1776
- * TODO: Document why this works like this & why visitor order matters
1777
- * on these use-cases.
1823
+ * Performs a depth-first traversal of all assets and dependencies contained
1824
+ * within a bundle. Only visits nodes that are directly contained in the bundle
1825
+ * (connected via a `contains` edge).
1826
+ *
1827
+ * Entry Asset Ordering:
1828
+ * The traversal guarantees that entry assets are visited in the exact order they
1829
+ * appear in `bundle.entryAssetIds`. This ordering is critical for several reasons:
1830
+ *
1831
+ * 1. **Code Execution Order in Packagers**: Packagers (ScopeHoistingPackager,
1832
+ * DevPackager) use this traversal to concatenate assets into the final bundle.
1833
+ * The traversal order determines the execution order of code in the output.
1834
+ * Entry assets must be processed in their defined order to ensure correct
1835
+ * initialization sequences.
1836
+ *
1837
+ * 2. **Runtime Injection**: Runtime assets (HMR, bundle manifests) are prepended
1838
+ * to `entryAssetIds` via `unshift()` in `applyRuntimes.ts`. By honoring the
1839
+ * array order, runtimes are guaranteed to be visited (and thus output) before
1840
+ * application entry points, ensuring the runtime infrastructure is available
1841
+ * when application code executes.
1842
+ *
1843
+ * 3. **Deterministic Builds**: Consistent traversal order ensures reproducible
1844
+ * bundle output, which is essential for caching and build verification.
1845
+ *
1846
+ * The sorting only applies at the first traversal level (direct children of the
1847
+ * start node). Subsequent levels follow standard DFS order based on the graph's
1848
+ * edge structure.
1849
+ *
1850
+ * @param bundle - The bundle to traverse
1851
+ * @param visit - Visitor callback receiving asset or dependency nodes
1852
+ * @param startAsset - Optional asset to start traversal from (defaults to bundle root)
1778
1853
  */
1779
1854
  traverseBundle<TContext>(
1780
1855
  bundle: Bundle,
@@ -11,11 +11,11 @@ import {
11
11
  CacheStats,
12
12
  } from '@atlaspack/rust';
13
13
  import {NapiWorkerPool} from './NapiWorkerPool';
14
- import ThrowableDiagnostic from '@atlaspack/diagnostic';
14
+ import ThrowableDiagnostic, {Diagnostic} from '@atlaspack/diagnostic';
15
15
  import type {Event} from '@parcel/watcher';
16
16
  import type {NapiWorkerPool as INapiWorkerPool} from '@atlaspack/types';
17
- import invariant from 'assert';
18
17
  import type BundleGraph from '../BundleGraph';
18
+ import {RunPackagerRunnerResult} from '../PackagerRunner';
19
19
 
20
20
  export type AtlaspackV3Options = {
21
21
  fs?: AtlaspackNapiOptions['fs'];
@@ -102,17 +102,24 @@ export class AtlaspackV3 {
102
102
  }
103
103
 
104
104
  loadBundleGraph(bundleGraph: BundleGraph): Promise<void> {
105
- const {nodesJson, edges} = bundleGraph.serializeForNative();
105
+ const {nodesJson, edges, publicIdByAssetId, environmentsJson} =
106
+ bundleGraph.serializeForNative();
106
107
 
107
108
  return atlaspackNapiLoadBundleGraph(
108
109
  this._atlaspack_napi,
109
110
  nodesJson,
110
111
  edges,
112
+ publicIdByAssetId,
113
+ environmentsJson,
111
114
  ) as Promise<void>;
112
115
  }
113
116
 
114
- package(): Promise<any> {
115
- return atlaspackNapiPackage(this._atlaspack_napi) as Promise<any>;
117
+ package(
118
+ bundleId: string,
119
+ ): Promise<[RunPackagerRunnerResult, Diagnostic | null]> {
120
+ return atlaspackNapiPackage(this._atlaspack_napi, bundleId) as Promise<
121
+ [RunPackagerRunnerResult, Diagnostic | null]
122
+ >;
116
123
  }
117
124
 
118
125
  async respondToFsEvents(events: Array<Event>): Promise<boolean> {
@@ -15,6 +15,8 @@ import {getDevDepRequests, runDevDepRequest} from './DevDepRequest';
15
15
  import createAtlaspackConfigRequest from './AtlaspackConfigRequest';
16
16
  import {fromEnvironmentId} from '../EnvironmentManager';
17
17
  import {getFeatureFlag} from '@atlaspack/feature-flags';
18
+ import logger from '@atlaspack/logger';
19
+ import ThrowableDiagnostic, {Diagnostic} from '@atlaspack/diagnostic';
18
20
 
19
21
  type PackageRequestInput = {
20
22
  bundleGraph: BundleGraph;
@@ -61,6 +63,7 @@ async function run({input, api, farm, rustAtlaspack}: RunInput<BundleInfo>) {
61
63
  ),
62
64
  );
63
65
 
66
+ let packagingResult: RunPackagerRunnerResult;
64
67
  if (
65
68
  getFeatureFlag('nativePackager') &&
66
69
  getFeatureFlag('nativePackagerSSRDev') &&
@@ -69,11 +72,20 @@ async function run({input, api, farm, rustAtlaspack}: RunInput<BundleInfo>) {
69
72
  bundle.type === 'js'
70
73
  ) {
71
74
  // Once this actually does something, the code below will be in an `else` block (i.e. we'll only run one or the other)
72
- await rustAtlaspack.package();
73
- }
74
-
75
- let {devDepRequests, configRequests, bundleInfo, invalidations} =
76
- (await runPackage({
75
+ let result = await rustAtlaspack.package(bundle.id);
76
+ let error: Diagnostic | null = null;
77
+ [packagingResult, error] = result;
78
+ if (error) {
79
+ throw new ThrowableDiagnostic({
80
+ diagnostic: error,
81
+ });
82
+ }
83
+ logger.verbose({
84
+ message: JSON.stringify(packagingResult, null, 2),
85
+ origin: '@atlaspack/core',
86
+ });
87
+ } else {
88
+ packagingResult = (await runPackage({
77
89
  bundle,
78
90
  bundleGraphReference,
79
91
  optionsRef,
@@ -82,7 +94,10 @@ async function run({input, api, farm, rustAtlaspack}: RunInput<BundleInfo>) {
82
94
  invalidDevDeps,
83
95
  previousInvalidations: api.getInvalidations(),
84
96
  })) as RunPackagerRunnerResult;
97
+ }
85
98
 
99
+ let {devDepRequests, configRequests, bundleInfo, invalidations} =
100
+ packagingResult;
86
101
  for (let devDepRequest of devDepRequests) {
87
102
  await runDevDepRequest(api, devDepRequest);
88
103
  }