@teambit/workspace 1.0.1192 → 1.0.1193

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.
@@ -1,7 +1,7 @@
1
1
  # Component Loading Redesign
2
2
 
3
- **Status:** Phase 1 shipped; Phase 2 in progress
4
- **Last updated:** 2026-06-15 (code references are against `master` @ `59855b104`; line numbers will drift)
3
+ **Status:** Phase 1 shipped; Phase 2 re-scoped; Phase 3's premise disproved; object-cache fix shipped (see [Status](#status))
4
+ **Last updated:** 2026-09-24 (code references are against `master` @ `59855b104`, §4.5 @ `9307c9c6a`; line numbers will drift)
5
5
 
6
6
  This document is the source of truth for a multi-phase effort to simplify Bit's component-loading
7
7
  mechanism: fewer caches, a staged (lazy) loading pipeline, a single env/aspect load planner, and a
@@ -67,7 +67,9 @@ Problems:
67
67
 
68
68
  - The workspace cache key embeds serialized `loadOpts`
69
69
  (`createComponentCacheKey`, `workspace-component-loader.ts:990`), so the same component loaded
70
- with different options is cached as separate opaque blobs.
70
+ with different options is cached under separate keys. _(Measured 2026-09-22: the keys point to
71
+ the same object, so this costs no memory — the real issue is that a key describes the request,
72
+ not what was loaded. See §4.3 item 2.)_
71
73
  - Invalidation requires three coordinated calls (`workspace.ts:829-841`:
72
74
  `componentLoader.clearCache` + legacy `clearComponentsCache` + `componentStatusLoader.clearCache`).
73
75
  - Cache hits are silent — a stale cache is indistinguishable from a fresh load in the logs.
@@ -204,14 +206,30 @@ earlier ones teach us).
204
206
  ### Phase 2 — Quick perf wins on existing seams
205
207
 
206
208
  - [x] Benchmark harness committed + baseline recorded (see §4) — **gate for the rest of the phase**
207
- - [ ] Lazy file contents in `ModelComponent.toConsumerComponent`
209
+ - [x] ~~Forward `loadOpts` in the batch `executeLoadSlot` path~~ — **implemented, measured, rejected.**
210
+ Halved `on-load` aggregate self-time with **no wall change** and **+67MB RSS** (§4.3). Not merged.
211
+ - [ ] ~~Default `loadDocs: false, loadCompositions: false` for non-UI flows~~ — **blocked**: `status`
212
+ already sets them; they can't take effect until the cache stops keying on them (Phase 3)
213
+ - [ ] Lazy file contents in `ModelComponent.toConsumerComponent` (targets `graph`, and the RSS trend)
208
214
  - [ ] `bit deps usage`: ids + stored deps instead of full load
209
215
  - [ ] IDE metadata endpoint (`api-for-ide.ts`): S0-S2-level data only
210
216
  - [ ] `bit remove` / forking: drop full-component loads where only ids/paths are used
211
- - [ ] Default `loadDocs: false, loadCompositions: false` for non-UI flows
217
+
218
+ **Explicitly dropped from this phase:** narrowing the deps-cache freshness fs scan (PR #10445,
219
+ closed 2026-09-22). It cut warm `status` fs syscalls 37% (74.3k → 46.4k) but moved wall ~0.3s —
220
+ the syscalls are `stat`/`lstat`/`readdir`, which overlap with CPU on a warm SSD — in exchange for
221
+ narrowing the invalidation signal of the highest-blast-radius cache in the loader. Bad trade. The
222
+ useful output of that work is the §4.1/§4.2 profiling and the node_modules invalidation constraints
223
+ recorded in the deps-cache notes.
212
224
 
213
225
  ### Phase 3 — Cache consolidation
214
226
 
227
+ > **Reassessed 2026-09-22 — not a perf lever.** Its promotion (§4.3) rested on the component cache
228
+ > holding duplicate copies; measured, it holds exactly one object per id (§4.3 item 2, §4.5). What
229
+ > remains is simplification and a correctness hazard (keys describe the request, not the content),
230
+ > worth doing only alongside partial-loading work that needs it. The 500-component cap on these
231
+ > caches is the one open perf question here (§4.5).
232
+
215
233
  - [ ] Introduce `ComponentCacheManager` (unused, with tests)
216
234
  - [ ] Migrate the workspace loader's four caches onto it
217
235
  - [ ] Migrate scope loader + legacy ComponentLoader caches; single `invalidate(id, reason)` event
@@ -250,18 +268,52 @@ The `Peak RSS` column is the worst case across the four commands (`bit graph`).
250
268
  median seconds. Per-command peak RSS for the baseline: status 1237MB, list 425MB, show 253MB,
251
269
  graph 2013MB.
252
270
 
253
- | Milestone | `bit status` | `bit list` | `bit show <comp>` | `bit graph --json` | Peak RSS |
254
- | ---------------------- | ------------ | ---------- | ----------------- | ------------------ | -------- |
255
- | Baseline (pre-Phase 2) | 11.24s | 1.59s | 1.73s | 20.49s | 2013MB |
256
- | After Phase 2 | — | — | — | — | — |
257
- | After Phase 3 | — | — | — | — | — |
258
- | After Phase 4 | — | — | — | — | — |
259
- | After Phase 5 | — | — | — | — | — |
271
+ | Milestone | `bit status` | `bit list` | `bit show <comp>` | `bit graph --json` | Peak RSS |
272
+ | ------------------------ | ------------ | ---------- | ----------------- | ------------------ | -------- |
273
+ | Baseline (pre-Phase 2) | 11.24s | 1.59s | 1.73s | 20.49s | 2013MB |
274
+ | Re-baseline (2026-09-22) | 9.43s | 1.72s | 1.23s | 18.59s | 2352MB |
275
+ | 2026-10-01 (§4.5-4.7) | 6.80s | 1.57s | 1.19s | 19.63s | 2495MB |
276
+ | After Phase 2 | — | — | — | — | — |
277
+ | After Phase 3 | — | — | — | — | — |
278
+ | After Phase 4 | — | — | — | — | — |
279
+ | After Phase 5 | — | — | — | — | — |
260
280
 
261
281
  Baseline measured 2026-06-15 on darwin (Apple silicon), `bit` @ 1.13.222, node v22.20.0,
262
282
  `bit show teambit.workspace/workspace`. The two clear hotspots are `bit status` (11s) and
263
283
  `bit graph` (20s, 2GB).
264
284
 
285
+ **Re-baseline 2026-09-22** — same machine/method, `bit` @ 2.2.57, node v22.20.0. Note the workspace
286
+ grew from ~313 to **331 components** (+5.8%), so per-component deltas are better than the raw wall
287
+ numbers suggest. Per-command peak RSS: status 1239MB, list 549MB, show 260MB, graph 2352MB.
288
+
289
+ | metric | baseline | re-baseline | raw Δ | per-component Δ |
290
+ | ----------------- | -------- | ----------- | ------ | --------------- |
291
+ | `bit status` wall | 11.24s | 9.43s | −16.1% | −20.6% |
292
+ | `bit graph` wall | 20.49s | 18.59s | −9.3% | −14.2% |
293
+ | `bit show` wall | 1.73s | 1.23s | −28.9% | n/a (1 comp) |
294
+ | `bit list` wall | 1.59s | 1.72s | +8.2% | +2.3% (flat) |
295
+ | `bit graph` RSS | 2013MB | 2352MB | +16.8% | +10.4% |
296
+ | `bit list` RSS | 425MB | 549MB | +29.2% | +22.2% |
297
+
298
+ Two things to read from this:
299
+
300
+ - **The wall-time gains came from outside this effort.** No Phase-2 item shipped, yet `status` and
301
+ `graph` improved materially — from targeted fixes landed independently (batching/deduping
302
+ dependency imports when building graphs from fs, dropping a per-invocation hard-link-directory
303
+ load, unifying component load paths). The redesign plan is not what moved these numbers.
304
+ - **Memory is trending the wrong way.** `graph` peak RSS is up 339MB and `list` up 124MB, beyond
305
+ what the +5.8% component growth explains. No phase currently targets memory; §2.1 lazy file
306
+ contents is the closest lever.
307
+
308
+ **2026-10-01**, after #10723 and §4.6-4.7, same machine/method, ~333 components. Per-command peak
309
+ RSS: status 927MB, list 546MB, show 249MB, graph 2495MB. Against the September re-baseline:
310
+
311
+ - `status` 9.43s → 6.80s (**−28%**) and 1239MB → 927MB (**−25%**).
312
+ - `list` −9%, `show` −3%.
313
+ - `graph` is **+6% wall and +6% RSS** (18.59s → 19.63s, 2352MB → 2495MB). The machine's load rose
314
+ during its runs (it went last), so some of this may be noise, but nothing here targeted it. `graph`
315
+ is now the outlier on both axes; its dominant cost is still file-content reads (§4.1).
316
+
265
317
  ### 4.1 Profiling findings (where the warm load time actually goes)
266
318
 
267
319
  Beyond wall-time, an opt-in aggregate profiler (`BIT_LOAD_PROFILE=1 bit <cmd>`, in
@@ -303,18 +355,258 @@ Numbers below are aggregate self-time across ~313 components at ~6× concurrency
303
355
  `deps usage`/IDE/`remove`/forking; `loadDocs/loadCompositions: false` trims `status`'s slot work
304
356
  (~1.7s). The big `status` number is deferred to the staged-loading phase.
305
357
 
358
+ ### 4.2 Re-profile 2026-09-22 (`bit status`, 331 components, 9.43s wall)
359
+
360
+ `BIT_LOAD_PROFILE=1 bit status`, aggregate self-time 68.1s (wall was 9.43s — the sum exceeds wall
361
+ because concurrently-interleaved spans each count their own `await` time).
362
+
363
+ > **Do not read these as wall-time budgets.** §4.3 shows, by direct experiment, that cutting a row
364
+ > here in half changed wall by nothing. Use this table to find _where to look_, never to size a win.
365
+
366
+ | stage | self-time | calls | share of self-time |
367
+ | -------------------------------------------- | --------- | ----- | ------------------ |
368
+ | `legacy-load-deps` | 34.3s | 331 | 50% |
369
+ | `on-load` (docs, compositions, schema, pkg…) | 13.7s | 2317 | 20% |
370
+ | `dependency-resolution` | 6.7s | 331 | 10% |
371
+ | `workspace.get` | 5.5s | 3349 | 8% |
372
+ | `execute-load-slot` (own) | 4.5s | 331 | 7% |
373
+ | `consumer-fs-load` | ~0s self | 7 | — |
374
+
375
+ Observations:
376
+
377
+ - `legacy-load-deps` is **50% of all load self-time** — still dependency-object materialization on
378
+ cache _hit_, not resolution. Per-component it improved (137ms → 104ms, −24%) but it did not change
379
+ shape. Same picture as June, three months apart.
380
+ - `on-load` is **20%** across **2317 calls** (331 components × ~7 handlers). `bit status` passes
381
+ `loadDocs:false`/`loadCompositions:false`, but they are **ineffective on the batch path**:
382
+ `workspace-component-loader.ts:479-480` calls `executeLoadSlot(component)` without forwarding
383
+ `loadOpts` (the single-component path at :1083/:1092 does forward them).
384
+ - `workspace.get` shows **3349 calls for 331 components (~10×)** — explained and tested in §4.4.
385
+
386
+ ### 4.3 ⚠️ Aggregate self-time does not predict wall-time — validate before building
387
+
388
+ The `on-load` row above was tested directly, and the result invalidates the metric this section is
389
+ built on. Forwarding `loadOpts` on the batch path (the "one-line fix") was implemented and measured:
390
+
391
+ | metric | without fix | with fix | result |
392
+ | ------------------------------ | ----------- | ----------- | ---------------------- |
393
+ | `on-load` aggregate self-time | 13.7s | 6.6s | **−52%** |
394
+ | total load self-time | 68.1s | 53.5s | **−21%** |
395
+ | **`bit status` wall (median)** | **9.05s** | **8.98s** | **−0.07s — noise** |
396
+ | **peak RSS** | **~1228MB** | **~1295MB** | **+67MB — consistent** |
397
+
398
+ Wall is the median of 9 runs per variant, **interleaved** (A/B/A/B/A/B, recompiling between) to
399
+ cancel drift. Drift matters: the same unchanged code measured 9.43s and 8.99s twenty minutes apart,
400
+ a larger gap than the effect being tested. RSS is 4 runs per variant with zero overlap between the
401
+ two sets.
402
+
403
+ So halving the aggregate self-time of a stage worth "~1.9s of wall" by the divide-by-concurrency
404
+ estimate produced **no measurable wall change**, and cost memory. Two lessons:
405
+
406
+ 1. **Aggregate self-time is a hypothesis generator, not a size estimate.** Spans are summed across
407
+ concurrently-interleaved async work on one JS thread, so most of what is counted is `await` time
408
+ overlapping other work, not critical-path CPU. Dividing by observed concurrency does **not**
409
+ recover wall-time. This is the third time the metric has misled this effort (the earlier "39s"
410
+ correction, the deps-cache fs-scan of #10445, and now this). **Do not size or prioritize work
411
+ from §4.1/§4.2 numbers — A/B the wall-clock first.**
412
+ 2. ~~Forwarding `loadOpts` costs memory because the cache key embeds it.~~ **Retracted (§4.5).**
413
+ Instrumenting the cache showed every id does get two keys, but **both point to the same object**
414
+ (with or without the fix) — the partial-key entry is later mutated in place into the full
415
+ component. So there is no second copy; the +67MB has some other cause and is unexplained. What
416
+ _is_ true is subtler: the key records what the caller _asked for_, not what was loaded — `getMany`
417
+ stores every result under the full key (:203) regardless of its options and ignores
418
+ `storeInCache`. With the forwarding fix, docs-less components would have been served to later
419
+ full `get`s. That's a correctness hazard for partial loading, not a memory one.
420
+
421
+ **Method for the rest of this effort:** every perf claim lands with an interleaved wall-clock A/B on
422
+ `scripts/bench-component-loading.js` and a peak-RSS comparison. No merges justified by profiler
423
+ self-time alone.
424
+
425
+ ### 4.4 The `workspace.get` 10× call count: explained, tested, and not worth a cache
426
+
427
+ Attributing every `workspace.get` call by caller (temporary stack-tally instrumentation, since
428
+ removed) on a warm `bit status`:
429
+
430
+ | caller | calls | note |
431
+ | ---------------------------------------------- | ----- | ------------------------------- |
432
+ | `envs` → `getEnvComponentByEnvId` (`opts={}`) | 2520 | resolving the **env** component |
433
+ | the batch load path (one per component) | 331 | expected |
434
+ | `component-dependency-factory` | 329 | ~one per component |
435
+ | `envs` (with `loadExtensions/executeLoadSlot`) | 162 | a second envs path |
436
+ | misc | 7 | |
437
+
438
+ **80% of all `workspace.get` calls are the envs aspect re-resolving the env component** — and this
439
+ workspace has **4 distinct envs**. `getEnvComponentByEnvId`
440
+ (`scopes/envs/envs/environments.main.runtime.ts`) does `host.get(envId)` on every call, and its
441
+ callers (dependency-resolver, dev-files, preview, and the env-descriptor path) each run per
442
+ component.
443
+
444
+ Memoizing it by `envId` was implemented and measured:
445
+
446
+ | metric | without memo | with memo | result |
447
+ | ----------------------------- | ------------ | --------- | -------------------------- |
448
+ | `workspace.get` calls | 3349 | 835 | **−75%** |
449
+ | `workspace.get` self-time | 5.48s | 5.09s | −0.39s — **nearly free** |
450
+ | `bit status` wall (median, 9) | 9.59s | 9.45s | −0.14s (−1.5%), consistent |
451
+ | peak RSS | ~1234MB | ~1246MB | +12MB, within noise |
452
+
453
+ **Conclusion: real but small, and the wrong fix.** Removing 75% of the calls moved
454
+ `workspace.get` self-time by only 0.39s, which proves those 2514 calls were already cheap cache
455
+ hits — the alarming 10× count was a **third red herring**, consistent with §4.3. The 1.5% wall gain
456
+ is genuine but would be bought by adding a 12th ad-hoc cache, with its own invalidation surface, to
457
+ a system whose stated core problem (§1.2) is _~11 uncoordinated caches_. That is the anti-pattern
458
+ this effort exists to remove.
459
+
460
+ **Where it belongs instead:** this is direct evidence for the standalone **`EnvResolver`** of §2.4 —
461
+ env identity is asked for constantly and should be resolvable from S0-S2 data with one owner and one
462
+ cache, rather than by loading a full env Component through the general loader 2682 times. Fold it
463
+ into Phase 5, and do not bolt on a point-memo before then.
464
+
465
+ ### 4.5 A real CPU profile: a third of `status` is the object layer, and it was thrashing
466
+
467
+ The span profiler (§4.1-4.4) misled three times, so `bit status` was profiled with V8 instead
468
+ (`node --cpu-prof`). V8 samples measure on-thread time, which _does_ sum to wall-time (9.17s
469
+ sampled: 7.7s JS/native, 0.83s GC, 0.46s idle). **Use this, not `BIT_LOAD_PROFILE`, to decide
470
+ where to work.**
471
+
472
+ The dominant cost is not in the component loader at all: **`@teambit/objects` is 1.7s self / 3.1s
473
+ inclusive** — about a third of wall. Mostly `parseObject` (1.1s, Version/ModelComponent/Source
474
+ JSON parsing). Counting parses per hash showed **half of them were repeats**: status touches ~4,700
475
+ distinct objects, and the `Repository` LRU was capped at **3,000 objects**, so it evicted and
476
+ re-parsed ~4,000. Caching everything costs ~124MB inflated (22MB on disk).
477
+
478
+ The count cap had been lowered twice for OOM reasons (10K → 5K → 3K), and objects range from bytes
479
+ to ~1.7MB, so raising the count would repeat a known mistake. Fixed instead by **bounding the cache
480
+ by approximate inflated bytes** (256MB default) — [#10723](https://github.com/teambit/bit/pull/10723) (merged):
481
+
482
+ | interleaved A/B | old (3,000 objects) | byte budget | result |
483
+ | --------------------------------- | ------------------- | ----------- | --------------------- |
484
+ | `bit status` wall (median, 8) | 8.64s | 7.37s | **−1.27s (−15%)** |
485
+ | `bit status` wall range | 8.50-8.91s | 7.36-7.48s | no overlap, 8/8 pairs |
486
+ | `bit status` peak RSS | ~1305MB | ~1130MB | **~−170MB** |
487
+ | `bit graph --json` wall (3 pairs) | 20.91s | 19.94s | −0.97s (−5%) |
488
+ | `bit list` / `bit show` | — | — | neutral |
489
+
490
+ It wins on memory too, but not because the old cache retained duplicates: only ~78 duplicate copies
491
+ (2.6MB) were live at the end of the old run. The win is **churn**: each of the ~4,000 re-parses
492
+ inflated and parsed a fresh copy that became garbage, so the old policy did 8,702 parses vs 4,715.
493
+ Peak `arrayBuffers` fell ~214→~103MB, peak `heapUsed` ~840→~768MB, and GC time ~820→~635ms. This is
494
+ the first change in this effort that moved wall-time by more than noise — and none of the phases
495
+ pointed at it.
496
+
497
+ **The cache is essential, but a byte budget alone has a cliff.** With the cache disabled (`max 1`),
498
+ `status` takes 22.4s with 31,213 parses. A workspace whose working set exceeds the budget falls back
499
+ to master's thrashing, and was slightly _worse_ than master at 3×. So #10723 also added a **weak layer** (`LiveObjects`, `objects/live-objects.ts`): a
500
+ hash → `WeakRef` map with a `FinalizationRegistry`. On an LRU miss, it returns the object if
501
+ something else (e.g. a loaded component) still references it; it never keeps anything alive by
502
+ itself. Objects too big for the LRU (>100KB compressed) are tracked there too. Alone it's
503
+ insufficient (9.5s vs 7.7s: most objects are unreferenced between uses); on top of the LRU it's a no-op while
504
+ the budget fits and removes the cliff when it doesn't. Simulated larger workspaces (both limits
505
+ shrunk by the same factor, `status`, median of 2, builds alternated):
506
+
507
+ | simulated size | master | byte budget | byte budget + weak |
508
+ | -------------- | -------------- | -------------- | ------------------ |
509
+ | this repo | 8.9s / 1317MB | 7.7s / 1140MB | 7.8s / 1110MB |
510
+ | 3× | 10.5s / 1629MB | 11.2s / 1683MB | **8.7s / 1200MB** |
511
+ | 5× | 12.1s / 1934MB | 12.1s / 1874MB | **9.2s / 1303MB** |
512
+
513
+ Correctness notes for anyone touching it: a `ModelComponent`'s hash is name-based, so one hash maps
514
+ to different content over time — every path that writes, removes, or clears the LRU must update the
515
+ weak map too (`_writeOne` only after the write succeeds; `removeFromCache`; `clearObjectsFromCache`).
516
+
517
+ Other hotspots from the same profile, since acted on (§4.6, §4.7):
518
+
519
+ - `ModelComponent.versionsIncludeOrphaned`, a getter that spreads `{...versions, ...orphanedVersions}`
520
+ on **every access** (~375ms) → single-tag lookups no longer use it ([#10739](https://github.com/teambit/bit/pull/10739)).
521
+ - `getTagOfRefIfExists`, a linear scan over all tags (~150ms self) → no longer copies; still linear (§4.7).
522
+ - `Version.calculateHash` from `isComponentModified` (~285ms) → [#10741](https://github.com/teambit/bit/pull/10741).
523
+ - `status` parses ~3,300 `Source` objects (file contents from the model). The "lazy file contents"
524
+ item of Phase 2 would remove these parses entirely. Still open.
525
+ - The **component** caches are capped at **500**, and this workspace has 339 → measured in §4.6: the
526
+ cliff was real, and steep.
527
+
528
+ ### 4.6 The component-cache cliff: eviction freed nothing and forced rebuilds
529
+
530
+ A larger workspace was simulated by shrinking the component caches' limit
531
+ (`BIT_CONFIG_CACHE_MAX_COMPONENTS=N`, which bounds the 8 caches sized by `cache.max.components`).
532
+ On master, warm `bit status`:
533
+
534
+ | limit | wall | peak RSS |
535
+ | ------------- | ---------- | -------- |
536
+ | default (500) | ~8.7s | ~1.1GB |
537
+ | 200 | 16.1-17.0s | 1.47GB |
538
+ | 50 | 29-30.8s | 1.65GB |
539
+
540
+ So a workspace a few times this size would take 2-3× longer. Isolating each cache (all but one
541
+ unbounded) put the cliff on the three **component** caches: the workspace loader's, the scope
542
+ loader's, and the legacy `ComponentLoader`'s.
543
+
544
+ The cause is a scan pattern that an LRU can't serve. `status` requests every component several times
545
+ over, and between passes it keeps all of them alive in its own list. Evicting a component therefore
546
+ frees no memory (the status list still holds it); it only makes the next request rebuild it from
547
+ scratch. Hence higher RSS at lower limits, not lower.
548
+
549
+ Fixed with the same idea as §4.5, applied to the component caches:
550
+ [#10736](https://github.com/teambit/bit/pull/10736) adds a `weak: true` option to `createInMemoryCache`
551
+ (`WeakValues`: a `WeakRef` map beside the LRU, cleaned by a `FinalizationRegistry`). On a miss it
552
+ returns the evicted value if it is still alive and puts it back in the LRU. `keys()`, `delete` and
553
+ `deleteAll` include the weak entries, so key-based invalidation (`clearComponentsCache` iterates
554
+ `keys()`) still clears them. It never keeps anything alive on its own.
555
+
556
+ | limit | master | with #10735 + #10736 |
557
+ | ----- | ------------------- | -------------------- |
558
+ | 200 | 16.1-17.0s / 1.47GB | 8.7s / 1.1GB |
559
+ | 50 | 29-30.8s / 1.65GB | 8.9s / 1.1GB |
560
+
561
+ At limit 50 the output is identical to the default.
562
+
563
+ **The weak layer exposed a latent bug, fixed separately in [#10735](https://github.com/teambit/bit/pull/10735).**
564
+ With it on the legacy cache, `status` reported 23 "failed loading env" issues. `buildLoadGroups`
565
+ passed each component's extension data to the next step **through** the bounded
566
+ `componentsExtensionsCache`. Entries evicted in between were missing, so their envs were left out of
567
+ the env-first load group, and components were loaded before their env was registered. On master this
568
+ was hidden by accidental reloads later in the command, the same rebuilds that made it slow.
569
+ `populateScopeAndExtensionsCache` now returns the data as a map, and the cache is only a cache again.
570
+ Performance-neutral on its own.
571
+
572
+ ### 4.7 Follow-ups from the CPU profile: copies nobody reads
573
+
574
+ With the caches fixed, the profile was flat enough that the next items were plain wasted work:
575
+
576
+ | PR | change | measured |
577
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
578
+ | [#10739](https://github.com/teambit/bit/pull/10739) | single-tag lookups (`getRef`, `getTagOfRefIfExists`, `sources.get/exists`) read `versions`/`orphanedVersions` instead of copying both | `status` 7.6 → 6.8s (5 interleaved rounds) |
579
+ | [#10741](https://github.com/teambit/bit/pull/10741) | `Version.id()` (the hash) builds only its 8 fields, not all of `toObject()`; `toConfigArray` clones only the config; ids not cloned in `toConsumerComponent` | `status` 8.5 → 7.65s, peak RSS 1.1GB → 985MB (5 rounds) |
580
+ | [#10742](https://github.com/teambit/bit/pull/10742) | `.bitmap` lookups index ids by name; the `IfExist` variants no longer throw and catch | time in those lookups ~140ms → ~10ms (CPU profile) |
581
+
582
+ `Version.id()` is a content hash, so its output must not change: the old and new implementations were
583
+ compared on all 15,398 version objects in this repo's scope, with identical results. Output of
584
+ `bit status` was identical in every A/B.
585
+
586
+ Checked and not worth acting on:
587
+
588
+ - **Object parsing (~590ms).** After #10723, each of the ~4,700 objects `status` touches is parsed
589
+ once (15 repeats, all `Source`). Real work, no longer churn.
590
+ - **Module loading.** `bin/bit.js` already enables Node's compile cache; the cost only shows up
591
+ right after a recompile.
592
+
593
+ Still open: `getHeadRegardlessOfLaneAsTagOrHash` scans all tags to find the head's tag (~145ms,
594
+ several callers per component). The head is usually the newest tag, so searching from the end would
595
+ find it at once. `extensions.clone()` in `toConsumerComponent` (~175ms) is needed, since it keeps a
596
+ loaded component from mutating the cached `Version`.
597
+
306
598
  ---
307
599
 
308
600
  ## Status
309
601
 
310
- | Phase | State | OpenSpec change | PRs |
311
- | ----------------------- | ----------- | ------------------------------ | --------------------------------------------------- |
312
- | 1 — Observability | done | `component-load-observability` | [#10418](https://github.com/teambit/bit/pull/10418) |
313
- | 2 — Quick perf wins | in progress | — | — |
314
- | 3 — Cache consolidation | not started | — | — |
315
- | 4 — Staged pipeline | not started | — | — |
316
- | 5 — Env planner | not started | — | — |
317
- | 6 — Legacy inversion | not started | — | — |
602
+ | Phase | State | OpenSpec change | PRs |
603
+ | ----------------------- | ----------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
604
+ | 1 — Observability | done | `component-load-observability` | [#10418](https://github.com/teambit/bit/pull/10418) |
605
+ | 2 — Quick perf wins | re-scoped, 1/7 done; profile-driven fixes merged (§4.5-4.7) | — | [#10445](https://github.com/teambit/bit/pull/10445) (closed, not merged), [#10723](https://github.com/teambit/bit/pull/10723), [#10735](https://github.com/teambit/bit/pull/10735), [#10736](https://github.com/teambit/bit/pull/10736), [#10739](https://github.com/teambit/bit/pull/10739), [#10741](https://github.com/teambit/bit/pull/10741), [#10742](https://github.com/teambit/bit/pull/10742) |
606
+ | 3 — Cache consolidation | not started — premise disproved, demoted (§4.5) | — | — |
607
+ | 4 — Staged pipeline | not started | — | — |
608
+ | 5 — Env planner | not started | — | — |
609
+ | 6 — Legacy inversion | not started | — | — |
318
610
 
319
611
  **Log:**
320
612
 
@@ -340,3 +632,58 @@ Numbers below are aggregate self-time across ~313 components at ~6× concurrency
340
632
  file-content reads (`consumer-fs-load`, 5.8s) → the target for lazy file contents. (Correction: an
341
633
  earlier "39s" figure was aggregate-concurrent self-time, not wall; warm wall is ~13s.) Direction
342
634
  for the next Phase-2 PR intentionally left open.
635
+ - 2026-09-22 — **Phase 2 reassessed after a 3-month gap.** Re-baselined (§4) and re-profiled (§4.2).
636
+ Findings:
637
+ 1. `status` −16% / `graph` −9% / `show` −29% since June, **with no Phase-2 item shipped** — the
638
+ gains came from targeted fixes landed outside this effort. Peak RSS regressed (`graph` +339MB,
639
+ `list` +124MB); nothing currently targets memory.
640
+ 2. The structural conclusion is confirmed a second time: `legacy-load-deps` is now **50% of all
641
+ load self-time**, still materialization-on-cache-hit. Phase 4 is the lever; Phase 2 was never
642
+ going to move it. Phase 4 promoted to the next high-value target.
643
+ 3. Found the concrete cheap win: `bit status` already sets `loadDocs:false`/`loadCompositions:false`
644
+ but the batch path drops them (`workspace-component-loader.ts:479-480` doesn't forward
645
+ `loadOpts`), leaving ~1.9s of `on-load` work on the table across 2317 handler calls.
646
+ 4. New unexplained signal: `workspace.get` is called **3349× for 331 components (~10×)**.
647
+ 5. **The cheap win was implemented and rejected on measurement** (§4.3). Forwarding `loadOpts`
648
+ halved `on-load` self-time but moved wall by −0.07s (noise) and cost +67MB RSS, because the
649
+ component cache keys on `loadDocs`/`loadCompositions` and so stores a second copy. This
650
+ invalidated the prioritization metric: **aggregate self-time does not predict wall-time**, for
651
+ the third time in this effort. Phase 4 is therefore _not_ promoted on the strength of the
652
+ `legacy-load-deps` 50% figure; **Phase 3 (cache consolidation) is promoted instead**, since the
653
+ loadOpts-in-cache-key problem blocks every partial-loading idea in Phases 2 and 4. All further
654
+ perf claims require an interleaved wall-clock A/B plus peak-RSS comparison.
655
+ 6. **The `workspace.get` 10× count was chased down and also rejected** (§4.4). 80% of the 3349
656
+ calls are the envs aspect re-resolving one of only 4 distinct envs. Memoizing removes 75% of
657
+ the calls for −0.14s (−1.5%) wall — real, but it buys that by adding a 12th ad-hoc cache to the
658
+ ~11 the effort exists to consolidate. Recorded as evidence for the §2.4 `EnvResolver` (Phase 5)
659
+ instead. Net result of this session: three candidate quick wins investigated, **all three
660
+ rejected on measurement**, and the prioritization metric itself invalidated.
661
+ PR #10445 closed unmerged (see §3 for the rationale). The abandoned May branch
662
+ `refactor/component-loading-v2-take-3` (`UnifiedComponentLoader` behind `BIT_LOADER=new`) predates
663
+ this document and is superseded by the phase plan — not to be resumed.
664
+ - 2026-09-22 — **Phase 3 started, and its premise disproved in the first hour.** Instrumenting the
665
+ component cache found one object per id (two keys, same object), so consolidating it saves no
666
+ memory; §4.3's explanation of the +67MB was wrong and is retracted. Switched tools: a V8 CPU
667
+ profile (`node --cpu-prof`) — unlike span self-time, it sums to wall — showed a third of warm
668
+ `status` in `@teambit/objects`, half of whose parses were repeats caused by the 3,000-object LRU
669
+ cap. Bounding that cache by bytes instead ([#10723](https://github.com/teambit/bit/pull/10723)):
670
+ `status` **−15% wall, ~−170MB RSS**, `graph` −5%, others neutral (§4.5). First change in this
671
+ effort to move wall-time beyond noise. Phase 3 demoted; next candidates are the remaining §4.5
672
+ hotspots, each gated on its own A/B.
673
+ - 2026-09-24 — **#10723 merged** (`9307c9c6a`). Beyond the byte budget, it added a weak layer over the
674
+ objects LRU after simulation showed the budget alone regresses to master (slightly worse at 3×)
675
+ once a workspace outgrows it; with the weak layer, 3×/5× stay 17-24% faster and 26-33% leaner
676
+ than master (§4.5). Also recorded why RSS dropped: fewer throwaway copies from re-parsing
677
+ (8,702 → 4,715 parses), not retained duplicates.
678
+ - 2026-09-28 — **Component-cache cliff measured and removed** (§4.6). Simulating a larger workspace
679
+ (`BIT_CONFIG_CACHE_MAX_COMPONENTS`) took warm `status` from ~8.7s to 16-17s at a limit of 200 and
680
+ 29-31s at 50. The three component caches thrash because `status` re-requests every component while
681
+ holding them all. [#10736](https://github.com/teambit/bit/pull/10736) adds a weak layer to them
682
+ (8.7s / 8.9s at 200 / 50, identical output). It exposed an env-ordering bug that eviction caused
683
+ and accidental reloads hid, fixed first in [#10735](https://github.com/teambit/bit/pull/10735).
684
+ - 2026-09-30 — **Profile follow-ups** (§4.7): [#10739](https://github.com/teambit/bit/pull/10739)
685
+ (−0.85s), [#10741](https://github.com/teambit/bit/pull/10741) (−0.85s, −115MB RSS),
686
+ [#10742](https://github.com/teambit/bit/pull/10742) (lookups 140 → 10ms). All merged. Next: the
687
+ head-to-tag scan (§4.7).
688
+ - 2026-10-01 — Benchmarked current master (§4): `status` 6.80s / 927MB, **−28% wall and −25% RSS**
689
+ since the September re-baseline. `graph` did not improve (19.63s, 2495MB) and is now the outlier.
@@ -1,7 +1,7 @@
1
1
  # Component Loading Redesign
2
2
 
3
- **Status:** Phase 1 shipped; Phase 2 in progress
4
- **Last updated:** 2026-06-15 (code references are against `master` @ `59855b104`; line numbers will drift)
3
+ **Status:** Phase 1 shipped; Phase 2 re-scoped; Phase 3's premise disproved; object-cache fix shipped (see [Status](#status))
4
+ **Last updated:** 2026-09-24 (code references are against `master` @ `59855b104`, §4.5 @ `9307c9c6a`; line numbers will drift)
5
5
 
6
6
  This document is the source of truth for a multi-phase effort to simplify Bit's component-loading
7
7
  mechanism: fewer caches, a staged (lazy) loading pipeline, a single env/aspect load planner, and a
@@ -67,7 +67,9 @@ Problems:
67
67
 
68
68
  - The workspace cache key embeds serialized `loadOpts`
69
69
  (`createComponentCacheKey`, `workspace-component-loader.ts:990`), so the same component loaded
70
- with different options is cached as separate opaque blobs.
70
+ with different options is cached under separate keys. _(Measured 2026-09-22: the keys point to
71
+ the same object, so this costs no memory — the real issue is that a key describes the request,
72
+ not what was loaded. See §4.3 item 2.)_
71
73
  - Invalidation requires three coordinated calls (`workspace.ts:829-841`:
72
74
  `componentLoader.clearCache` + legacy `clearComponentsCache` + `componentStatusLoader.clearCache`).
73
75
  - Cache hits are silent — a stale cache is indistinguishable from a fresh load in the logs.
@@ -204,14 +206,30 @@ earlier ones teach us).
204
206
  ### Phase 2 — Quick perf wins on existing seams
205
207
 
206
208
  - [x] Benchmark harness committed + baseline recorded (see §4) — **gate for the rest of the phase**
207
- - [ ] Lazy file contents in `ModelComponent.toConsumerComponent`
209
+ - [x] ~~Forward `loadOpts` in the batch `executeLoadSlot` path~~ — **implemented, measured, rejected.**
210
+ Halved `on-load` aggregate self-time with **no wall change** and **+67MB RSS** (§4.3). Not merged.
211
+ - [ ] ~~Default `loadDocs: false, loadCompositions: false` for non-UI flows~~ — **blocked**: `status`
212
+ already sets them; they can't take effect until the cache stops keying on them (Phase 3)
213
+ - [ ] Lazy file contents in `ModelComponent.toConsumerComponent` (targets `graph`, and the RSS trend)
208
214
  - [ ] `bit deps usage`: ids + stored deps instead of full load
209
215
  - [ ] IDE metadata endpoint (`api-for-ide.ts`): S0-S2-level data only
210
216
  - [ ] `bit remove` / forking: drop full-component loads where only ids/paths are used
211
- - [ ] Default `loadDocs: false, loadCompositions: false` for non-UI flows
217
+
218
+ **Explicitly dropped from this phase:** narrowing the deps-cache freshness fs scan (PR #10445,
219
+ closed 2026-09-22). It cut warm `status` fs syscalls 37% (74.3k → 46.4k) but moved wall ~0.3s —
220
+ the syscalls are `stat`/`lstat`/`readdir`, which overlap with CPU on a warm SSD — in exchange for
221
+ narrowing the invalidation signal of the highest-blast-radius cache in the loader. Bad trade. The
222
+ useful output of that work is the §4.1/§4.2 profiling and the node_modules invalidation constraints
223
+ recorded in the deps-cache notes.
212
224
 
213
225
  ### Phase 3 — Cache consolidation
214
226
 
227
+ > **Reassessed 2026-09-22 — not a perf lever.** Its promotion (§4.3) rested on the component cache
228
+ > holding duplicate copies; measured, it holds exactly one object per id (§4.3 item 2, §4.5). What
229
+ > remains is simplification and a correctness hazard (keys describe the request, not the content),
230
+ > worth doing only alongside partial-loading work that needs it. The 500-component cap on these
231
+ > caches is the one open perf question here (§4.5).
232
+
215
233
  - [ ] Introduce `ComponentCacheManager` (unused, with tests)
216
234
  - [ ] Migrate the workspace loader's four caches onto it
217
235
  - [ ] Migrate scope loader + legacy ComponentLoader caches; single `invalidate(id, reason)` event
@@ -250,18 +268,52 @@ The `Peak RSS` column is the worst case across the four commands (`bit graph`).
250
268
  median seconds. Per-command peak RSS for the baseline: status 1237MB, list 425MB, show 253MB,
251
269
  graph 2013MB.
252
270
 
253
- | Milestone | `bit status` | `bit list` | `bit show <comp>` | `bit graph --json` | Peak RSS |
254
- | ---------------------- | ------------ | ---------- | ----------------- | ------------------ | -------- |
255
- | Baseline (pre-Phase 2) | 11.24s | 1.59s | 1.73s | 20.49s | 2013MB |
256
- | After Phase 2 | — | — | — | — | — |
257
- | After Phase 3 | — | — | — | — | — |
258
- | After Phase 4 | — | — | — | — | — |
259
- | After Phase 5 | — | — | — | — | — |
271
+ | Milestone | `bit status` | `bit list` | `bit show <comp>` | `bit graph --json` | Peak RSS |
272
+ | ------------------------ | ------------ | ---------- | ----------------- | ------------------ | -------- |
273
+ | Baseline (pre-Phase 2) | 11.24s | 1.59s | 1.73s | 20.49s | 2013MB |
274
+ | Re-baseline (2026-09-22) | 9.43s | 1.72s | 1.23s | 18.59s | 2352MB |
275
+ | 2026-10-01 (§4.5-4.7) | 6.80s | 1.57s | 1.19s | 19.63s | 2495MB |
276
+ | After Phase 2 | — | — | — | — | — |
277
+ | After Phase 3 | — | — | — | — | — |
278
+ | After Phase 4 | — | — | — | — | — |
279
+ | After Phase 5 | — | — | — | — | — |
260
280
 
261
281
  Baseline measured 2026-06-15 on darwin (Apple silicon), `bit` @ 1.13.222, node v22.20.0,
262
282
  `bit show teambit.workspace/workspace`. The two clear hotspots are `bit status` (11s) and
263
283
  `bit graph` (20s, 2GB).
264
284
 
285
+ **Re-baseline 2026-09-22** — same machine/method, `bit` @ 2.2.57, node v22.20.0. Note the workspace
286
+ grew from ~313 to **331 components** (+5.8%), so per-component deltas are better than the raw wall
287
+ numbers suggest. Per-command peak RSS: status 1239MB, list 549MB, show 260MB, graph 2352MB.
288
+
289
+ | metric | baseline | re-baseline | raw Δ | per-component Δ |
290
+ | ----------------- | -------- | ----------- | ------ | --------------- |
291
+ | `bit status` wall | 11.24s | 9.43s | −16.1% | −20.6% |
292
+ | `bit graph` wall | 20.49s | 18.59s | −9.3% | −14.2% |
293
+ | `bit show` wall | 1.73s | 1.23s | −28.9% | n/a (1 comp) |
294
+ | `bit list` wall | 1.59s | 1.72s | +8.2% | +2.3% (flat) |
295
+ | `bit graph` RSS | 2013MB | 2352MB | +16.8% | +10.4% |
296
+ | `bit list` RSS | 425MB | 549MB | +29.2% | +22.2% |
297
+
298
+ Two things to read from this:
299
+
300
+ - **The wall-time gains came from outside this effort.** No Phase-2 item shipped, yet `status` and
301
+ `graph` improved materially — from targeted fixes landed independently (batching/deduping
302
+ dependency imports when building graphs from fs, dropping a per-invocation hard-link-directory
303
+ load, unifying component load paths). The redesign plan is not what moved these numbers.
304
+ - **Memory is trending the wrong way.** `graph` peak RSS is up 339MB and `list` up 124MB, beyond
305
+ what the +5.8% component growth explains. No phase currently targets memory; §2.1 lazy file
306
+ contents is the closest lever.
307
+
308
+ **2026-10-01**, after #10723 and §4.6-4.7, same machine/method, ~333 components. Per-command peak
309
+ RSS: status 927MB, list 546MB, show 249MB, graph 2495MB. Against the September re-baseline:
310
+
311
+ - `status` 9.43s → 6.80s (**−28%**) and 1239MB → 927MB (**−25%**).
312
+ - `list` −9%, `show` −3%.
313
+ - `graph` is **+6% wall and +6% RSS** (18.59s → 19.63s, 2352MB → 2495MB). The machine's load rose
314
+ during its runs (it went last), so some of this may be noise, but nothing here targeted it. `graph`
315
+ is now the outlier on both axes; its dominant cost is still file-content reads (§4.1).
316
+
265
317
  ### 4.1 Profiling findings (where the warm load time actually goes)
266
318
 
267
319
  Beyond wall-time, an opt-in aggregate profiler (`BIT_LOAD_PROFILE=1 bit <cmd>`, in
@@ -303,18 +355,258 @@ Numbers below are aggregate self-time across ~313 components at ~6× concurrency
303
355
  `deps usage`/IDE/`remove`/forking; `loadDocs/loadCompositions: false` trims `status`'s slot work
304
356
  (~1.7s). The big `status` number is deferred to the staged-loading phase.
305
357
 
358
+ ### 4.2 Re-profile 2026-09-22 (`bit status`, 331 components, 9.43s wall)
359
+
360
+ `BIT_LOAD_PROFILE=1 bit status`, aggregate self-time 68.1s (wall was 9.43s — the sum exceeds wall
361
+ because concurrently-interleaved spans each count their own `await` time).
362
+
363
+ > **Do not read these as wall-time budgets.** §4.3 shows, by direct experiment, that cutting a row
364
+ > here in half changed wall by nothing. Use this table to find _where to look_, never to size a win.
365
+
366
+ | stage | self-time | calls | share of self-time |
367
+ | -------------------------------------------- | --------- | ----- | ------------------ |
368
+ | `legacy-load-deps` | 34.3s | 331 | 50% |
369
+ | `on-load` (docs, compositions, schema, pkg…) | 13.7s | 2317 | 20% |
370
+ | `dependency-resolution` | 6.7s | 331 | 10% |
371
+ | `workspace.get` | 5.5s | 3349 | 8% |
372
+ | `execute-load-slot` (own) | 4.5s | 331 | 7% |
373
+ | `consumer-fs-load` | ~0s self | 7 | — |
374
+
375
+ Observations:
376
+
377
+ - `legacy-load-deps` is **50% of all load self-time** — still dependency-object materialization on
378
+ cache _hit_, not resolution. Per-component it improved (137ms → 104ms, −24%) but it did not change
379
+ shape. Same picture as June, three months apart.
380
+ - `on-load` is **20%** across **2317 calls** (331 components × ~7 handlers). `bit status` passes
381
+ `loadDocs:false`/`loadCompositions:false`, but they are **ineffective on the batch path**:
382
+ `workspace-component-loader.ts:479-480` calls `executeLoadSlot(component)` without forwarding
383
+ `loadOpts` (the single-component path at :1083/:1092 does forward them).
384
+ - `workspace.get` shows **3349 calls for 331 components (~10×)** — explained and tested in §4.4.
385
+
386
+ ### 4.3 ⚠️ Aggregate self-time does not predict wall-time — validate before building
387
+
388
+ The `on-load` row above was tested directly, and the result invalidates the metric this section is
389
+ built on. Forwarding `loadOpts` on the batch path (the "one-line fix") was implemented and measured:
390
+
391
+ | metric | without fix | with fix | result |
392
+ | ------------------------------ | ----------- | ----------- | ---------------------- |
393
+ | `on-load` aggregate self-time | 13.7s | 6.6s | **−52%** |
394
+ | total load self-time | 68.1s | 53.5s | **−21%** |
395
+ | **`bit status` wall (median)** | **9.05s** | **8.98s** | **−0.07s — noise** |
396
+ | **peak RSS** | **~1228MB** | **~1295MB** | **+67MB — consistent** |
397
+
398
+ Wall is the median of 9 runs per variant, **interleaved** (A/B/A/B/A/B, recompiling between) to
399
+ cancel drift. Drift matters: the same unchanged code measured 9.43s and 8.99s twenty minutes apart,
400
+ a larger gap than the effect being tested. RSS is 4 runs per variant with zero overlap between the
401
+ two sets.
402
+
403
+ So halving the aggregate self-time of a stage worth "~1.9s of wall" by the divide-by-concurrency
404
+ estimate produced **no measurable wall change**, and cost memory. Two lessons:
405
+
406
+ 1. **Aggregate self-time is a hypothesis generator, not a size estimate.** Spans are summed across
407
+ concurrently-interleaved async work on one JS thread, so most of what is counted is `await` time
408
+ overlapping other work, not critical-path CPU. Dividing by observed concurrency does **not**
409
+ recover wall-time. This is the third time the metric has misled this effort (the earlier "39s"
410
+ correction, the deps-cache fs-scan of #10445, and now this). **Do not size or prioritize work
411
+ from §4.1/§4.2 numbers — A/B the wall-clock first.**
412
+ 2. ~~Forwarding `loadOpts` costs memory because the cache key embeds it.~~ **Retracted (§4.5).**
413
+ Instrumenting the cache showed every id does get two keys, but **both point to the same object**
414
+ (with or without the fix) — the partial-key entry is later mutated in place into the full
415
+ component. So there is no second copy; the +67MB has some other cause and is unexplained. What
416
+ _is_ true is subtler: the key records what the caller _asked for_, not what was loaded — `getMany`
417
+ stores every result under the full key (:203) regardless of its options and ignores
418
+ `storeInCache`. With the forwarding fix, docs-less components would have been served to later
419
+ full `get`s. That's a correctness hazard for partial loading, not a memory one.
420
+
421
+ **Method for the rest of this effort:** every perf claim lands with an interleaved wall-clock A/B on
422
+ `scripts/bench-component-loading.js` and a peak-RSS comparison. No merges justified by profiler
423
+ self-time alone.
424
+
425
+ ### 4.4 The `workspace.get` 10× call count: explained, tested, and not worth a cache
426
+
427
+ Attributing every `workspace.get` call by caller (temporary stack-tally instrumentation, since
428
+ removed) on a warm `bit status`:
429
+
430
+ | caller | calls | note |
431
+ | ---------------------------------------------- | ----- | ------------------------------- |
432
+ | `envs` → `getEnvComponentByEnvId` (`opts={}`) | 2520 | resolving the **env** component |
433
+ | the batch load path (one per component) | 331 | expected |
434
+ | `component-dependency-factory` | 329 | ~one per component |
435
+ | `envs` (with `loadExtensions/executeLoadSlot`) | 162 | a second envs path |
436
+ | misc | 7 | |
437
+
438
+ **80% of all `workspace.get` calls are the envs aspect re-resolving the env component** — and this
439
+ workspace has **4 distinct envs**. `getEnvComponentByEnvId`
440
+ (`scopes/envs/envs/environments.main.runtime.ts`) does `host.get(envId)` on every call, and its
441
+ callers (dependency-resolver, dev-files, preview, and the env-descriptor path) each run per
442
+ component.
443
+
444
+ Memoizing it by `envId` was implemented and measured:
445
+
446
+ | metric | without memo | with memo | result |
447
+ | ----------------------------- | ------------ | --------- | -------------------------- |
448
+ | `workspace.get` calls | 3349 | 835 | **−75%** |
449
+ | `workspace.get` self-time | 5.48s | 5.09s | −0.39s — **nearly free** |
450
+ | `bit status` wall (median, 9) | 9.59s | 9.45s | −0.14s (−1.5%), consistent |
451
+ | peak RSS | ~1234MB | ~1246MB | +12MB, within noise |
452
+
453
+ **Conclusion: real but small, and the wrong fix.** Removing 75% of the calls moved
454
+ `workspace.get` self-time by only 0.39s, which proves those 2514 calls were already cheap cache
455
+ hits — the alarming 10× count was a **third red herring**, consistent with §4.3. The 1.5% wall gain
456
+ is genuine but would be bought by adding a 12th ad-hoc cache, with its own invalidation surface, to
457
+ a system whose stated core problem (§1.2) is _~11 uncoordinated caches_. That is the anti-pattern
458
+ this effort exists to remove.
459
+
460
+ **Where it belongs instead:** this is direct evidence for the standalone **`EnvResolver`** of §2.4 —
461
+ env identity is asked for constantly and should be resolvable from S0-S2 data with one owner and one
462
+ cache, rather than by loading a full env Component through the general loader 2682 times. Fold it
463
+ into Phase 5, and do not bolt on a point-memo before then.
464
+
465
+ ### 4.5 A real CPU profile: a third of `status` is the object layer, and it was thrashing
466
+
467
+ The span profiler (§4.1-4.4) misled three times, so `bit status` was profiled with V8 instead
468
+ (`node --cpu-prof`). V8 samples measure on-thread time, which _does_ sum to wall-time (9.17s
469
+ sampled: 7.7s JS/native, 0.83s GC, 0.46s idle). **Use this, not `BIT_LOAD_PROFILE`, to decide
470
+ where to work.**
471
+
472
+ The dominant cost is not in the component loader at all: **`@teambit/objects` is 1.7s self / 3.1s
473
+ inclusive** — about a third of wall. Mostly `parseObject` (1.1s, Version/ModelComponent/Source
474
+ JSON parsing). Counting parses per hash showed **half of them were repeats**: status touches ~4,700
475
+ distinct objects, and the `Repository` LRU was capped at **3,000 objects**, so it evicted and
476
+ re-parsed ~4,000. Caching everything costs ~124MB inflated (22MB on disk).
477
+
478
+ The count cap had been lowered twice for OOM reasons (10K → 5K → 3K), and objects range from bytes
479
+ to ~1.7MB, so raising the count would repeat a known mistake. Fixed instead by **bounding the cache
480
+ by approximate inflated bytes** (256MB default) — [#10723](https://github.com/teambit/bit/pull/10723) (merged):
481
+
482
+ | interleaved A/B | old (3,000 objects) | byte budget | result |
483
+ | --------------------------------- | ------------------- | ----------- | --------------------- |
484
+ | `bit status` wall (median, 8) | 8.64s | 7.37s | **−1.27s (−15%)** |
485
+ | `bit status` wall range | 8.50-8.91s | 7.36-7.48s | no overlap, 8/8 pairs |
486
+ | `bit status` peak RSS | ~1305MB | ~1130MB | **~−170MB** |
487
+ | `bit graph --json` wall (3 pairs) | 20.91s | 19.94s | −0.97s (−5%) |
488
+ | `bit list` / `bit show` | — | — | neutral |
489
+
490
+ It wins on memory too, but not because the old cache retained duplicates: only ~78 duplicate copies
491
+ (2.6MB) were live at the end of the old run. The win is **churn**: each of the ~4,000 re-parses
492
+ inflated and parsed a fresh copy that became garbage, so the old policy did 8,702 parses vs 4,715.
493
+ Peak `arrayBuffers` fell ~214→~103MB, peak `heapUsed` ~840→~768MB, and GC time ~820→~635ms. This is
494
+ the first change in this effort that moved wall-time by more than noise — and none of the phases
495
+ pointed at it.
496
+
497
+ **The cache is essential, but a byte budget alone has a cliff.** With the cache disabled (`max 1`),
498
+ `status` takes 22.4s with 31,213 parses. A workspace whose working set exceeds the budget falls back
499
+ to master's thrashing, and was slightly _worse_ than master at 3×. So #10723 also added a **weak layer** (`LiveObjects`, `objects/live-objects.ts`): a
500
+ hash → `WeakRef` map with a `FinalizationRegistry`. On an LRU miss, it returns the object if
501
+ something else (e.g. a loaded component) still references it; it never keeps anything alive by
502
+ itself. Objects too big for the LRU (>100KB compressed) are tracked there too. Alone it's
503
+ insufficient (9.5s vs 7.7s: most objects are unreferenced between uses); on top of the LRU it's a no-op while
504
+ the budget fits and removes the cliff when it doesn't. Simulated larger workspaces (both limits
505
+ shrunk by the same factor, `status`, median of 2, builds alternated):
506
+
507
+ | simulated size | master | byte budget | byte budget + weak |
508
+ | -------------- | -------------- | -------------- | ------------------ |
509
+ | this repo | 8.9s / 1317MB | 7.7s / 1140MB | 7.8s / 1110MB |
510
+ | 3× | 10.5s / 1629MB | 11.2s / 1683MB | **8.7s / 1200MB** |
511
+ | 5× | 12.1s / 1934MB | 12.1s / 1874MB | **9.2s / 1303MB** |
512
+
513
+ Correctness notes for anyone touching it: a `ModelComponent`'s hash is name-based, so one hash maps
514
+ to different content over time — every path that writes, removes, or clears the LRU must update the
515
+ weak map too (`_writeOne` only after the write succeeds; `removeFromCache`; `clearObjectsFromCache`).
516
+
517
+ Other hotspots from the same profile, since acted on (§4.6, §4.7):
518
+
519
+ - `ModelComponent.versionsIncludeOrphaned`, a getter that spreads `{...versions, ...orphanedVersions}`
520
+ on **every access** (~375ms) → single-tag lookups no longer use it ([#10739](https://github.com/teambit/bit/pull/10739)).
521
+ - `getTagOfRefIfExists`, a linear scan over all tags (~150ms self) → no longer copies; still linear (§4.7).
522
+ - `Version.calculateHash` from `isComponentModified` (~285ms) → [#10741](https://github.com/teambit/bit/pull/10741).
523
+ - `status` parses ~3,300 `Source` objects (file contents from the model). The "lazy file contents"
524
+ item of Phase 2 would remove these parses entirely. Still open.
525
+ - The **component** caches are capped at **500**, and this workspace has 339 → measured in §4.6: the
526
+ cliff was real, and steep.
527
+
528
+ ### 4.6 The component-cache cliff: eviction freed nothing and forced rebuilds
529
+
530
+ A larger workspace was simulated by shrinking the component caches' limit
531
+ (`BIT_CONFIG_CACHE_MAX_COMPONENTS=N`, which bounds the 8 caches sized by `cache.max.components`).
532
+ On master, warm `bit status`:
533
+
534
+ | limit | wall | peak RSS |
535
+ | ------------- | ---------- | -------- |
536
+ | default (500) | ~8.7s | ~1.1GB |
537
+ | 200 | 16.1-17.0s | 1.47GB |
538
+ | 50 | 29-30.8s | 1.65GB |
539
+
540
+ So a workspace a few times this size would take 2-3× longer. Isolating each cache (all but one
541
+ unbounded) put the cliff on the three **component** caches: the workspace loader's, the scope
542
+ loader's, and the legacy `ComponentLoader`'s.
543
+
544
+ The cause is a scan pattern that an LRU can't serve. `status` requests every component several times
545
+ over, and between passes it keeps all of them alive in its own list. Evicting a component therefore
546
+ frees no memory (the status list still holds it); it only makes the next request rebuild it from
547
+ scratch. Hence higher RSS at lower limits, not lower.
548
+
549
+ Fixed with the same idea as §4.5, applied to the component caches:
550
+ [#10736](https://github.com/teambit/bit/pull/10736) adds a `weak: true` option to `createInMemoryCache`
551
+ (`WeakValues`: a `WeakRef` map beside the LRU, cleaned by a `FinalizationRegistry`). On a miss it
552
+ returns the evicted value if it is still alive and puts it back in the LRU. `keys()`, `delete` and
553
+ `deleteAll` include the weak entries, so key-based invalidation (`clearComponentsCache` iterates
554
+ `keys()`) still clears them. It never keeps anything alive on its own.
555
+
556
+ | limit | master | with #10735 + #10736 |
557
+ | ----- | ------------------- | -------------------- |
558
+ | 200 | 16.1-17.0s / 1.47GB | 8.7s / 1.1GB |
559
+ | 50 | 29-30.8s / 1.65GB | 8.9s / 1.1GB |
560
+
561
+ At limit 50 the output is identical to the default.
562
+
563
+ **The weak layer exposed a latent bug, fixed separately in [#10735](https://github.com/teambit/bit/pull/10735).**
564
+ With it on the legacy cache, `status` reported 23 "failed loading env" issues. `buildLoadGroups`
565
+ passed each component's extension data to the next step **through** the bounded
566
+ `componentsExtensionsCache`. Entries evicted in between were missing, so their envs were left out of
567
+ the env-first load group, and components were loaded before their env was registered. On master this
568
+ was hidden by accidental reloads later in the command, the same rebuilds that made it slow.
569
+ `populateScopeAndExtensionsCache` now returns the data as a map, and the cache is only a cache again.
570
+ Performance-neutral on its own.
571
+
572
+ ### 4.7 Follow-ups from the CPU profile: copies nobody reads
573
+
574
+ With the caches fixed, the profile was flat enough that the next items were plain wasted work:
575
+
576
+ | PR | change | measured |
577
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
578
+ | [#10739](https://github.com/teambit/bit/pull/10739) | single-tag lookups (`getRef`, `getTagOfRefIfExists`, `sources.get/exists`) read `versions`/`orphanedVersions` instead of copying both | `status` 7.6 → 6.8s (5 interleaved rounds) |
579
+ | [#10741](https://github.com/teambit/bit/pull/10741) | `Version.id()` (the hash) builds only its 8 fields, not all of `toObject()`; `toConfigArray` clones only the config; ids not cloned in `toConsumerComponent` | `status` 8.5 → 7.65s, peak RSS 1.1GB → 985MB (5 rounds) |
580
+ | [#10742](https://github.com/teambit/bit/pull/10742) | `.bitmap` lookups index ids by name; the `IfExist` variants no longer throw and catch | time in those lookups ~140ms → ~10ms (CPU profile) |
581
+
582
+ `Version.id()` is a content hash, so its output must not change: the old and new implementations were
583
+ compared on all 15,398 version objects in this repo's scope, with identical results. Output of
584
+ `bit status` was identical in every A/B.
585
+
586
+ Checked and not worth acting on:
587
+
588
+ - **Object parsing (~590ms).** After #10723, each of the ~4,700 objects `status` touches is parsed
589
+ once (15 repeats, all `Source`). Real work, no longer churn.
590
+ - **Module loading.** `bin/bit.js` already enables Node's compile cache; the cost only shows up
591
+ right after a recompile.
592
+
593
+ Still open: `getHeadRegardlessOfLaneAsTagOrHash` scans all tags to find the head's tag (~145ms,
594
+ several callers per component). The head is usually the newest tag, so searching from the end would
595
+ find it at once. `extensions.clone()` in `toConsumerComponent` (~175ms) is needed, since it keeps a
596
+ loaded component from mutating the cached `Version`.
597
+
306
598
  ---
307
599
 
308
600
  ## Status
309
601
 
310
- | Phase | State | OpenSpec change | PRs |
311
- | ----------------------- | ----------- | ------------------------------ | --------------------------------------------------- |
312
- | 1 — Observability | done | `component-load-observability` | [#10418](https://github.com/teambit/bit/pull/10418) |
313
- | 2 — Quick perf wins | in progress | — | — |
314
- | 3 — Cache consolidation | not started | — | — |
315
- | 4 — Staged pipeline | not started | — | — |
316
- | 5 — Env planner | not started | — | — |
317
- | 6 — Legacy inversion | not started | — | — |
602
+ | Phase | State | OpenSpec change | PRs |
603
+ | ----------------------- | ----------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
604
+ | 1 — Observability | done | `component-load-observability` | [#10418](https://github.com/teambit/bit/pull/10418) |
605
+ | 2 — Quick perf wins | re-scoped, 1/7 done; profile-driven fixes merged (§4.5-4.7) | — | [#10445](https://github.com/teambit/bit/pull/10445) (closed, not merged), [#10723](https://github.com/teambit/bit/pull/10723), [#10735](https://github.com/teambit/bit/pull/10735), [#10736](https://github.com/teambit/bit/pull/10736), [#10739](https://github.com/teambit/bit/pull/10739), [#10741](https://github.com/teambit/bit/pull/10741), [#10742](https://github.com/teambit/bit/pull/10742) |
606
+ | 3 — Cache consolidation | not started — premise disproved, demoted (§4.5) | — | — |
607
+ | 4 — Staged pipeline | not started | — | — |
608
+ | 5 — Env planner | not started | — | — |
609
+ | 6 — Legacy inversion | not started | — | — |
318
610
 
319
611
  **Log:**
320
612
 
@@ -340,3 +632,58 @@ Numbers below are aggregate self-time across ~313 components at ~6× concurrency
340
632
  file-content reads (`consumer-fs-load`, 5.8s) → the target for lazy file contents. (Correction: an
341
633
  earlier "39s" figure was aggregate-concurrent self-time, not wall; warm wall is ~13s.) Direction
342
634
  for the next Phase-2 PR intentionally left open.
635
+ - 2026-09-22 — **Phase 2 reassessed after a 3-month gap.** Re-baselined (§4) and re-profiled (§4.2).
636
+ Findings:
637
+ 1. `status` −16% / `graph` −9% / `show` −29% since June, **with no Phase-2 item shipped** — the
638
+ gains came from targeted fixes landed outside this effort. Peak RSS regressed (`graph` +339MB,
639
+ `list` +124MB); nothing currently targets memory.
640
+ 2. The structural conclusion is confirmed a second time: `legacy-load-deps` is now **50% of all
641
+ load self-time**, still materialization-on-cache-hit. Phase 4 is the lever; Phase 2 was never
642
+ going to move it. Phase 4 promoted to the next high-value target.
643
+ 3. Found the concrete cheap win: `bit status` already sets `loadDocs:false`/`loadCompositions:false`
644
+ but the batch path drops them (`workspace-component-loader.ts:479-480` doesn't forward
645
+ `loadOpts`), leaving ~1.9s of `on-load` work on the table across 2317 handler calls.
646
+ 4. New unexplained signal: `workspace.get` is called **3349× for 331 components (~10×)**.
647
+ 5. **The cheap win was implemented and rejected on measurement** (§4.3). Forwarding `loadOpts`
648
+ halved `on-load` self-time but moved wall by −0.07s (noise) and cost +67MB RSS, because the
649
+ component cache keys on `loadDocs`/`loadCompositions` and so stores a second copy. This
650
+ invalidated the prioritization metric: **aggregate self-time does not predict wall-time**, for
651
+ the third time in this effort. Phase 4 is therefore _not_ promoted on the strength of the
652
+ `legacy-load-deps` 50% figure; **Phase 3 (cache consolidation) is promoted instead**, since the
653
+ loadOpts-in-cache-key problem blocks every partial-loading idea in Phases 2 and 4. All further
654
+ perf claims require an interleaved wall-clock A/B plus peak-RSS comparison.
655
+ 6. **The `workspace.get` 10× count was chased down and also rejected** (§4.4). 80% of the 3349
656
+ calls are the envs aspect re-resolving one of only 4 distinct envs. Memoizing removes 75% of
657
+ the calls for −0.14s (−1.5%) wall — real, but it buys that by adding a 12th ad-hoc cache to the
658
+ ~11 the effort exists to consolidate. Recorded as evidence for the §2.4 `EnvResolver` (Phase 5)
659
+ instead. Net result of this session: three candidate quick wins investigated, **all three
660
+ rejected on measurement**, and the prioritization metric itself invalidated.
661
+ PR #10445 closed unmerged (see §3 for the rationale). The abandoned May branch
662
+ `refactor/component-loading-v2-take-3` (`UnifiedComponentLoader` behind `BIT_LOADER=new`) predates
663
+ this document and is superseded by the phase plan — not to be resumed.
664
+ - 2026-09-22 — **Phase 3 started, and its premise disproved in the first hour.** Instrumenting the
665
+ component cache found one object per id (two keys, same object), so consolidating it saves no
666
+ memory; §4.3's explanation of the +67MB was wrong and is retracted. Switched tools: a V8 CPU
667
+ profile (`node --cpu-prof`) — unlike span self-time, it sums to wall — showed a third of warm
668
+ `status` in `@teambit/objects`, half of whose parses were repeats caused by the 3,000-object LRU
669
+ cap. Bounding that cache by bytes instead ([#10723](https://github.com/teambit/bit/pull/10723)):
670
+ `status` **−15% wall, ~−170MB RSS**, `graph` −5%, others neutral (§4.5). First change in this
671
+ effort to move wall-time beyond noise. Phase 3 demoted; next candidates are the remaining §4.5
672
+ hotspots, each gated on its own A/B.
673
+ - 2026-09-24 — **#10723 merged** (`9307c9c6a`). Beyond the byte budget, it added a weak layer over the
674
+ objects LRU after simulation showed the budget alone regresses to master (slightly worse at 3×)
675
+ once a workspace outgrows it; with the weak layer, 3×/5× stay 17-24% faster and 26-33% leaner
676
+ than master (§4.5). Also recorded why RSS dropped: fewer throwaway copies from re-parsing
677
+ (8,702 → 4,715 parses), not retained duplicates.
678
+ - 2026-09-28 — **Component-cache cliff measured and removed** (§4.6). Simulating a larger workspace
679
+ (`BIT_CONFIG_CACHE_MAX_COMPONENTS`) took warm `status` from ~8.7s to 16-17s at a limit of 200 and
680
+ 29-31s at 50. The three component caches thrash because `status` re-requests every component while
681
+ holding them all. [#10736](https://github.com/teambit/bit/pull/10736) adds a weak layer to them
682
+ (8.7s / 8.9s at 200 / 50, identical output). It exposed an env-ordering bug that eviction caused
683
+ and accidental reloads hid, fixed first in [#10735](https://github.com/teambit/bit/pull/10735).
684
+ - 2026-09-30 — **Profile follow-ups** (§4.7): [#10739](https://github.com/teambit/bit/pull/10739)
685
+ (−0.85s), [#10741](https://github.com/teambit/bit/pull/10741) (−0.85s, −115MB RSS),
686
+ [#10742](https://github.com/teambit/bit/pull/10742) (lookups 140 → 10ms). All merged. Next: the
687
+ head-to-tag scan (§4.7).
688
+ - 2026-10-01 — Benchmarked current master (§4): `status` 6.80s / 927MB, **−28% wall and −25% RSS**
689
+ since the September re-baseline. `graph` did not improve (19.63s, 2495MB) and is now the outlier.
@@ -1,5 +1,5 @@
1
- import * as compositions_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad/teambit.workspace_workspace@1.0.1192/dist/workspace.composition.js';
2
- import * as overview_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad/teambit.workspace_workspace@1.0.1192/dist/workspace.docs.mdx';
1
+ import * as compositions_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad/teambit.workspace_workspace@1.0.1193/dist/workspace.composition.js';
2
+ import * as overview_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad/teambit.workspace_workspace@1.0.1193/dist/workspace.docs.mdx';
3
3
 
4
4
  export const compositions = [compositions_0];
5
5
  export const overview = [overview_0];
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@teambit/workspace",
3
- "version": "1.0.1192",
3
+ "version": "1.0.1193",
4
4
  "homepage": "https://bit.cloud/teambit/workspace/workspace",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.workspace",
8
8
  "name": "workspace",
9
- "version": "1.0.1192"
9
+ "version": "1.0.1193"
10
10
  },
11
11
  "dependencies": {
12
12
  "lodash": "4.17.21",
@@ -30,26 +30,42 @@
30
30
  "reset-css": "5.0.1",
31
31
  "@teambit/component-id": "1.2.4",
32
32
  "@teambit/harmony": "0.4.12",
33
+ "@teambit/legacy.extension-data": "0.0.175",
34
+ "@teambit/legacy.scope": "0.0.173",
33
35
  "@teambit/component-version": "1.0.4",
36
+ "@teambit/legacy.consumer-component": "0.0.174",
37
+ "@teambit/legacy.consumer": "0.0.173",
34
38
  "@teambit/bit-error": "0.0.404",
35
39
  "@teambit/lane-id": "0.0.312",
40
+ "@teambit/legacy.bit-map": "0.0.230",
36
41
  "@teambit/toolbox.fs.last-modified": "0.0.21",
37
42
  "@teambit/toolbox.path.path": "0.0.23",
38
43
  "@teambit/graph.cleargraph": "0.0.11",
39
44
  "@teambit/logger": "0.0.1494",
45
+ "@teambit/cli": "0.0.1404",
46
+ "@teambit/config-store": "0.0.285",
40
47
  "@teambit/legacy.constants": "0.0.46",
41
48
  "@teambit/component.ui.component-status-resolver": "0.0.510",
42
49
  "@teambit/harmony.modules.load-trace": "0.0.15",
43
50
  "@teambit/harmony.modules.resolved-component": "0.0.528",
44
51
  "@teambit/legacy.utils": "0.0.52",
52
+ "@teambit/scope.remotes": "0.0.173",
53
+ "@teambit/config": "0.0.1579",
45
54
  "@teambit/harmony.modules.requireable-component": "0.0.528",
46
55
  "@teambit/harmony.modules.runtimes": "0.0.1",
47
56
  "@teambit/toolbox.modules.module-resolver": "0.0.27",
57
+ "@teambit/workspace.modules.node-modules-linker": "0.0.406",
58
+ "@teambit/global-config": "0.0.1408",
59
+ "@teambit/legacy.consumer-config": "0.0.173",
60
+ "@teambit/variants": "0.0.1672",
48
61
  "@teambit/component-issues": "0.0.188",
62
+ "@teambit/component.sources": "0.0.225",
49
63
  "@teambit/dependencies.modules.packages-excluder": "1.0.8",
50
64
  "@teambit/git.modules.git-executable": "0.0.47",
51
65
  "@teambit/harmony.modules.in-memory-cache": "0.0.51",
52
66
  "@teambit/legacy-bit-id": "1.1.3",
67
+ "@teambit/legacy.component-list": "0.0.227",
68
+ "@teambit/legacy.scope-api": "0.0.228",
53
69
  "@teambit/toolbox.path.is-path-inside": "0.0.516",
54
70
  "@teambit/workspace.modules.match-pattern": "0.0.536",
55
71
  "@teambit/component.ui.component-drawer": "0.0.510",
@@ -86,39 +102,23 @@
86
102
  "@teambit/workspace.hooks.use-url-change-broadcaster": "0.0.6",
87
103
  "@teambit/workspace.ui.preserve-workspace-mode": "0.0.7",
88
104
  "@teambit/evangelist.elements.icon": "1.0.5",
89
- "@teambit/component": "1.0.1192",
90
- "@teambit/dependency-resolver": "1.0.1192",
91
- "@teambit/envs": "1.0.1192",
92
- "@teambit/legacy.extension-data": "0.0.175",
93
- "@teambit/legacy.scope": "0.0.173",
94
- "@teambit/legacy.consumer-component": "0.0.174",
95
- "@teambit/legacy.consumer": "0.0.173",
96
- "@teambit/legacy.bit-map": "0.0.230",
97
- "@teambit/objects": "0.0.699",
98
- "@teambit/scope": "1.0.1192",
99
- "@teambit/graph": "1.0.1192",
100
- "@teambit/cli": "0.0.1404",
101
- "@teambit/config-store": "0.0.285",
102
- "@teambit/isolator": "1.0.1192",
103
- "@teambit/component-tree": "1.0.1192",
104
- "@teambit/watcher": "1.0.1192",
105
- "@teambit/scope.remotes": "0.0.173",
106
- "@teambit/aspect-loader": "1.0.1192",
107
- "@teambit/config": "0.0.1579",
108
- "@teambit/workspace.modules.node-modules-linker": "0.0.406",
109
- "@teambit/graphql": "1.0.1192",
110
- "@teambit/bundler": "1.0.1192",
111
- "@teambit/global-config": "0.0.1408",
112
- "@teambit/legacy.consumer-config": "0.0.173",
113
- "@teambit/ui": "1.0.1192",
114
- "@teambit/variants": "0.0.1672",
115
- "@teambit/component.sources": "0.0.225",
116
- "@teambit/legacy.component-list": "0.0.227",
117
- "@teambit/legacy.scope-api": "0.0.228",
118
- "@teambit/command-bar": "1.0.1192",
119
- "@teambit/sidebar": "1.0.1192",
120
- "@teambit/pubsub": "1.0.1192",
121
- "@teambit/deprecation": "1.0.1192"
105
+ "@teambit/component": "1.0.1193",
106
+ "@teambit/dependency-resolver": "1.0.1193",
107
+ "@teambit/envs": "1.0.1193",
108
+ "@teambit/objects": "0.0.700",
109
+ "@teambit/scope": "1.0.1193",
110
+ "@teambit/graph": "1.0.1193",
111
+ "@teambit/isolator": "1.0.1193",
112
+ "@teambit/component-tree": "1.0.1193",
113
+ "@teambit/watcher": "1.0.1193",
114
+ "@teambit/aspect-loader": "1.0.1193",
115
+ "@teambit/graphql": "1.0.1193",
116
+ "@teambit/bundler": "1.0.1193",
117
+ "@teambit/ui": "1.0.1193",
118
+ "@teambit/command-bar": "1.0.1193",
119
+ "@teambit/sidebar": "1.0.1193",
120
+ "@teambit/pubsub": "1.0.1193",
121
+ "@teambit/deprecation": "1.0.1193"
122
122
  },
123
123
  "devDependencies": {
124
124
  "@types/lodash": "4.14.165",