feat(bundle): bundle the bit cli with esbuild - #10590
Draft
GiladShoham wants to merge 491 commits into
Draft
GiladShoham wants to merge 491 commits into
GiladShoham wants to merge 491 commits into
Conversation
bit-cli-app-env is itself an env, so teambit.envs/env; cli-bundler is a plain node module rather than an aspect, so node-babel-mocha.
…/@teambit/bit/bin/bit
a user's workspace resolves @teambit/<aspect> to a shim whose body is one dynamic require, so without declarations every import degraded to any - no type checking, no autocomplete. the .d.ts tree is copied verbatim from the package being shimmed rather than regenerated. that preserves type identity: declarations re-export their siblings and other @teambit/* packages, and those references resolve through the sibling shims, so every aspect sees the same Component and Workspace. rolling them up per package would break exactly that. the whole tree is copied, not just index.d.ts, because the re-exports are relative paths into it. the shim's exports map points 'types' at the copied declarations - inheriting the original, which points at .ts sources a shim does not have, would leave everything untyped. capsules always carry declarations; this repo needs 'bit compile --generate-types'. both verified, 107/107 shims with 1722 files. an external workspace type-checks against them under noImplicitAny, and a negative control confirms the types are enforced rather than silently any: ws.path resolves as string and cm.toArray() as [Component, string][], the Component coming from a sibling shim.
…k status docs.mdx explains each module and the traps: resolution is not path-joining, exports maps don't extension-probe, a missing main runtime is legitimate so a resolution bug looks like a normal build. plan gains 9e - the task runs green, what the first runs exposed, freshness confirmed correct, types verified in an external workspace, and what is still open.
the task wrote to <capsule>/app-bundle, so the build produced a prototype dir that something would
later have had to lift into the package. it now builds into the capsule itself, which *is* what gets
published, giving the 9b layout directly: package.json + bin/bit + dist/<aspect>/index.js locators +
dist/core-aspects/{bundle,node_modules}.
inPlace changes three things: never clean (the out dir holds the component's own sources and dist -
cleanOutDir would delete them), merge into the real package.json instead of writing the
@teambit/bit-bundle-externals stand-in, and skip .npmrc, which only exists for the prototype's local
npm install.
the merge prunes the dependency surface to the externals alone - 168 declared dependencies replaced
by 7. leaving them would make a consumer's install re-download the very tree the bundle replaces,
and would resolve a second copy of every core aspect next to the shims, so @teambit/workspace could
resolve to a published package rather than the bundle slice. dev/peer/optional deps go too;
identity fields (name, version, componentId, engines) are untouched.
also excludes dist/core-aspects from the .d.ts copy: in place, @teambit/bit's source dir is the
capsule, whose dist now contains the generated shims, so an unfiltered glob copied all 106 shims'
declarations into the bit shim (1158 files instead of 18).
verified: the capsule's sources and dist survive, and the built package runs --version, init, status
and list from a fresh workspace.
MochaMain.createTester() has no callers anywhere in the repo or in any resolved env - real consumers (node-babel-mocha, node-typescript-mocha) already import MochaTester directly from @teambit/defender.mocha-tester, which has no dependency on this aspect. Being an eager BitMain dependency, it forced mocha's require chain onto every bit invocation for no reason.
Pulls in the mocha core-aspect removal (750f930).
mocha is no longer needed - the @teambit/mocha core aspect that pulled it in unconditionally is gone (merged from remove-core-envs-from-manifest). Rebuild verified: externalsInstalled 11 -> 10, coreAspects 106 -> 105, zero require sites for mocha in bit.app.js. bundle-plan.md records the webpack/mocha externals research and the mocha removal end to end.
…s an install that re-keys them
pnpm keys a virtual-store directory by the package's peer-resolution hash, so
an install that changes the dependency set gives the same name@version a NEW
directory and deletes the one this process loaded its modules from. Node keeps
the loaded module objects, but not the files - so any require the loaded code
deferred past load time resolves against the deleted directory and throws
MODULE_NOT_FOUND.
This branch made that fatal: the envs that used to be core aspects are now
ordinary packages resolved out of the workspace's virtual store (on master
they resolve from bit's own installation, which no workspace install touches),
and @teambit/aspect defers require('./babel/babel-config') until getCompiler()
is called - which the install flow itself does right after the package-manager
run, when it compiles components and reloads envs. The install then dies with
"Cannot find module './babel/babel-config'", and since the env never loads,
bit create surfaces it as the misleading `template "react" was not found`
(8 of the 14 failed e2e shards in CI build 436404; the two unmasked ones fail
on bit install directly).
Replacing the in-memory instances instead does not work - verified by trying:
every reload path (reloadMovedEnvs, loading components as aspects) has to
consult the registered env to do its work, and consulting it is exactly what
throws. reloadMovedEnvs is additionally a no-op for these envs: it filters on
env.__path/env.id, which only plugin-loaded envs carry, never aspect-registered
ones.
So the fix follows the rule an OS applies to a running binary's deleted files:
what the process has loaded stays available for the process's lifetime. Before
the package-manager run, snapshot which virtual-store directories back entries
in require.cache; afterwards, restore any that vanished from their re-keyed
twin - same name@version, different peer hash - whose package content is
identical (same tarball; the peer set only affects the sibling dependency
symlinks, which are relative and stay valid). pnpmPruneModules learns to skip
directories backing require.cache entries so it does not re-delete a restored
one; a later command's process, which has nothing loaded from it, prunes it.
Reproduced and verified with deps-in-capsules.e2e.ts: the second install
re-keys a dozen loaded @teambit/*@1.0.1042 slots; without the fix the suite
fails on the babel-config require, with it all tests pass and the debug log
shows each removed slot restored.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ound" getTemplateWithIdOrEnvFallback swallowed every failure of the env fallback with a bare catch and re-threw the generator's own "template X was not found". Only "this aspect has no such template" is safe to replace that way; when the env itself fails to load (a failed capsule install, a missing module in its package), that failure is the actual cause, and hiding it sent the CI investigation of build 436404 in the wrong direction for 8 of 14 shards. Distinguish the two by matching the generator's own not-found error and let everything else propagate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…modules The preservation added in e792b9f keeps virtual-store directories the running process loaded modules from requireable when an install re-keys them, by scanning require.cache - which only CJS modules appear in. ESM modules live in node's ESM module map, which has no enumeration API, so an ESM env relocated by an install was still exposed to the same MODULE_NOT_FOUND on any import it deferred past load time. aspect-loader now records every file it loads through dynamic import() - the env-plugin loader and loadEsm(), the only two such call sites - in a Symbol.for-keyed global Set, and the preservation scans that set alongside require.cache. A global symbol rather than a shared import because the reader lives in the pnpm package manager, which aspect-loader must not depend on (the dependency runs the other way), and Symbol.for resolves to the same key even when a module is duplicated in node_modules; both sides document the contract and point at each other. Only ESM entry files are recorded, not their transitive static imports: those are fully loaded into memory and never re-read, while the entry's own package directory - where deferred imports and config-file reads point - is restored wholly. The realpath is recorded alongside the given spelling so a load reached through a node_modules symlink is attributed to the .pnpm directory that owns it. Verified: 55 specs across both components (4 new for the recorder, 2 new for the reader), deps-in-capsules.e2e.ts still green, npm run lint green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The restores ran under an unbounded Promise.all, one recursive fs.copy per removed directory, right after the engine has just saturated the disk - review flagged the burst as a hazard in constrained CI/container environments. The common case is zero removed directories and the checks stay cheap; when there are any, serial restore bounds the I/O with no meaningful cost (the deps-in-capsules repro restores 26 dirs and stays green). Also logs an aggregate removed/restored/duration line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…from-manifest The loaded-package preservation work this branch carried was upstreamed as #10595, so master and the branch both hold it and every conflict is between the branch's original and the reviewed version that landed. Master's version wins throughout: it is the same work with the realpath spellings, the patch-hash donor check, the slot-owner attribution and the prune early-return added on top. plugins.ts and aspect-loader's loadEsm merged cleanly into a double recordLoadedEsmFile - the branch recorded before the load, master after it, on the grounds that only a load that succeeded leaves something in memory worth keeping files for. Kept master's single post-load call in both. The branch-only aspect-loader changes (the versionless loaded-aspect lookup, the core-aspect manifest guard, the requested-id-preserving def dedup) are untouched - master never had them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s nested copy
An install that decides a hoisted copy satisfies what was nested deletes the
nested directory - and if this process loaded modules out of it, node keeps the
module objects but not the files, so any require the loaded code deferred past
load time throws MODULE_NOT_FOUND. `@teambit/aspect` defers
`require('./babel/babel-config')` until `getCompiler()`, which the install flow
itself calls right after the package-manager run, so the install dies with
`Cannot find module './babel/babel-config'`.
This is the same hazard #10595 fixed for the virtual store, reached by a
different route. That snapshot only scans `node_modules/.pnpm`, so a package
loaded from a root component's own node_modules was invisible to it and the
preservation was inert. root-components' hoisted-linker suite hit exactly this:
17 loaded directories under `.bit_roots/teambit.harmony_aspect/node_modules`
were removed by the second install.
`reloadMovedEnvs` does not cover it either - it skips any env without a
`__path`, which is only set for plugin-registered envs, and `@teambit/aspect`'s
AspectEnv is registered by its aspect provider. Reloading is no substitute
anyway: consulting the registered env is what triggers the deferred require.
So preserve these the same way: snapshot the package directories under the
workspace's node_modules that back loaded modules, and afterwards restore any
that vanished from a same-version copy found by walking the node_modules chain
up from where it used to be - what node itself would resolve now - bounded at
the workspace root. A donor must match on version, since the point is to keep
serving the files belonging to the modules already in memory. The loaded-files
plumbing shared with the virtual-store module moves to loaded-module-files.ts.
Verified with root-components.e2e.ts's hoisted-linker suite: 0 passing/1 failing
before (the before-all hook died on the babel-config require), 12 passing after,
with the debug log showing 17 of 17 removed directories restored.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of a nested capsule's A nested capsule install is rooted at the capsule, but it was handed the root links keyed by the capsules root - the directory one level *above* its root. That describes an importer outside the workspace, and the package manager then has to give the root project itself a directory dep path relative to itself. That path is empty, so it comes out as `<name>@file:`, which its own parser rejects: Failed to build lockfile from resolved dependency graph: Resolved dependency path "@teambit/react@file:(...)" keys no lockfile entry: Failed to parse suffix: Empty path after `file:` scheme Only the first nested capsule got the links (`index === 0`), so this needed that capsule to be a named package - which is exactly what an env capsule is. The mis-keying dates to #10453/#10457; it stayed latent while envs were core aspects and no env capsule was installed this way. Give the links the install they describe: one rooted at the capsules root. It runs before the capsules rather than alongside them, so they can resolve the core aspects it links while they install and nothing races it over the root's node_modules. When a cyclic group exists it is already rooted there and keeps owning them, unchanged. Verified against optional-dependencies.e2e.ts, whose "before all" runs `bit create react button --env teambit.react/react`: 0 passing/1 failing before, 10 passing after. The failure needs a cold capsule cache - once the env capsule is cached the install is rooted at the capsules root instead and the bug is skipped - so reproduce with `rm -rf ~/Library/Caches/Bit/capsules/*` first. On CI this cluster accounts for 12 failing e2e tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…as a loadable env again The suite asserts that an old-format env's dependency policy is missing after the first install and applied after the second. It skips setCustomEnv's install because it measures what each install does - which used to be fine, since the envs the fixture needs were core aspects that were always present. They are packages now, so with nothing installed the env cannot load at *any* point and the policy is never applied: the second-install and recurring-install assertions all fail. The chain fails one link at a time - @teambit/node first, then teambit.react/react, then teambit.harmony/aspect - so the fixture's own imports are only the first of what loading it needs. State the chain as a workspace policy rather than installing it up front. The first install then fetches it while the env is still unloadable - which is exactly what makes it an "old env" for the first-install assertions - and the second install finds it loadable. Installing the packages beforehand instead makes the env load on the first install and applies the policy there, which inverts the two first-install assertions. Not env.jsonc, though it would sidestep the chain: `calculateEnvManifest` reads the policy without loading the env, and `setOldNonLoadedEnvs` intersects with `envsWithoutManifest`, so a manifest would statically apply the deps on the first install and silence the warning - turning this suite into a duplicate of env-jsonc-policies.e2e.ts and dropping the only coverage of the legacy path. install.e2e.ts's old-envs suite: 2 passing/3 failing before, 5 passing after. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… compiler
The suite's last assertion is that the component compiles into its package
directory, but the component takes the default env - which on this branch is the
empty env, with no compiler - so no dist is ever written. The file landed on
master after this branch named an env explicitly in the e2e suites that assert a
dist, so it never got that treatment.
Use the workspace-local ts env rather than a published one: it installs nothing,
so what the global store does or does not serve is still exactly what the
assertions measure. `setBitdevNodeEnv` would install its env package and fails
to load under the global virtual store ("The requested module '@teambit/component'
does not provide an export named 'ComponentMap'"), which is a separate matter
from this suite's subject.
The "building an aspect" block still fails and is untouched: its TSCompiler
type-checks bit's own repo sources through the linked core aspects, against the
`@types/react@17` the pinned `@teambit/aspect@1.0.1042` env brings, so react 18
APIs (`startTransition`, `useSyncExternalStore`) come back as missing exports.
global-virtual-store.e2e.ts: 4 passing/2 failing before, 6 passing/1 failing after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The suite deletes the component's dist artifact from the remote and asserts on the error that follows, so the tag has to record one. It did not: the ts env this branch put here compiles in the workspace but emits nothing in a capsule build - its TSCompiler task runs and reports success while the capsule ends up with no dist directory, so builder records only the schema and package-tar artifacts and `artifacts.find(a => a.name === 'dist')` is undefined. A workspace compile is what made that look fine elsewhere: it copies the files it cannot compile into the dist, so a dist appears there regardless. A build does not copy, so nothing appears. Use the bitdev node env instead, the same env this file already relies on for the --loose build below, and install it after the re-import rather than importing it - it is a published package, not a scope component. build-cmd.e2e.ts "dist file is deleted from the remote": the before hook threw on `undefined.files` before, 2 passing after. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…'s so the bridge is the only variable The aspect built here uses the pinned legacy aspect env, which brings older typings than this repo does - @types/react@17 against its 19, @types/mime@1 against its 2. The type-paths bridge maps the program's `react` to whatever the workspace hoisted, which is correct for the workspace's own packages; but bit's own sources share that program, because a core aspect in the dev repo exposes `types: index.ts` rather than a built `.d.ts`, and they need the repo's typings. So the compile failed on `startTransition` and `useSyncExternalStore` missing from react, and `getType` missing from mime - none of which is about the reachability this suite measures. The mismatch is broad (13 of the 19 typings packages the workspace and the repo share differ), so pinning per package as errors appear would be endless. Take the versions from the running repo instead, at runtime, so a repo-side bump cannot silently reintroduce the skew. Filtering the errors by file was the other option and would have been wrong: with the bridge stubbed out to return no paths, all 47 resulting errors land in bit's own sources - the same files the version skew shows up in. Ignoring them would leave a suite that passes with the bridge fully disabled. That the guard still bites was verified both ways: bridge on with these versions pinned is 0 errors, bridge off with them pinned is still 47. global-virtual-store.e2e.ts: 6 passing/1 failing before, 7 passing after. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… an aspect `createAspect` configures the component with the aspect env, which used to be a core aspect and is now a package. This suite is the one createAspect caller that also configures the aspect in workspace.jsonc, so bit has to load it - and with the env's package absent the load fails, the component is reported with a "failed loading env" issue, and the snap in the before hook throws before the import under test ever runs. The other callers scaffold an aspect without the workspace using it, so nothing forces the env to load and they are unaffected. bit-import-on-lanes.e2e.ts: the before hook threw before, 19 passing after. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ests after fixing the oom issue in pnpm side
…undle Instead of copying the three markdown templates next to bit.app.js at build time, getDefaultRulesContent now requires them statically behind a BIT_IS_BUNDLE check so esbuild's new .md text loader inlines them as strings. Also fixes the pre-existing ignore-assets-plugin, which was silently stripping all .md files (including these) to empty modules. Template filenames are now exported from mcp-config-writer as a single source of truth the bundler plugin imports, instead of a duplicated list.
… has not landed e7cffe7 dropped e2e_test and e2e_test_bbit back to medium on the grounds that the OOM was fixed on the pnpm side. The run it produced (#437962) says otherwise: 15 failures, 12 of them a bare `Killed`, 11 of those on the `bit install` of an env package - @teambit/react.react-env@1.3.5, @teambit/env + @teambit/node. 2793 tests reported instead of 2948, the missing 155 being suites whose before-hooks were killed before they could run. The premise has not been met yet. pnpm/pnpm#13681 - the engine still peaking about 2x pnpm v10, 4.4GB for a 3.8k-package graph - is open, so the peak does not fit in medium's 4GB whichever way the fix lands. This branch is also pinned to @pnpm/napi 12.0.0-rc.1, behind the rc.2/rc.3 that any such fix would ship in, so it could not have picked one up. Restored with the reasoning in the file so the next attempt starts from what to re-measure: once #13681 closes and the engine is bumped here, drop this and watch for `Killed`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…aited on has not landed" This reverts commit 60b209a.
…undles - resolve an aspect's `artifacts/` from the running bit before bvm, so a bundled bit serves its own pre-bundles instead of a bvm install's - build the pre-bundles from core aspects only, so their `.hash` is not keyed to bit's own dev workspace (react arrives there as an env, versioned) - emit a shim file per non-main runtime; without one a bundled bit resolved zero preview aspects and hashed the empty string, so no artifact could ever match - copy each aspect's `artifacts/` into its shim - trim the ui-bundling externals: 31 -> 12 installed, 1.3 GB -> 322 MB Also fixes two resolution bugs that blocked the build tasks: `@apollo/client` is aliased to a single copy (it is a peer of @teambit/component, unresolvable from a capsule store, and carries React context), and use-cloud-scopes gets the `dist/esm.mjs` its exports map already declared.
…ndles §17 covers why both pre-bundle hashes missed on this branch (react left the core aspects, so bit's own workspace and a user's stopped agreeing), the alternatives weighed for what belongs in the artifact, and the resulting 1.3 GB -> 322 MB. Adds D12-D15, findings-log entries, and closes gap #1. Links #10596 for the 90 MB UI artifact. Also carries the §16/§18 text that was already in the working tree.
…er check Pre-phase baseline was captured on macOS and undercounted externals by ~8.7% vs. real CircleCI (Linux) numbers, eating most of the margin on the first run. Recalibrated from the actual CI job's report. Also adds total-pre/total-post checks measuring the entire out-dir, so anything landing anywhere in the shipped folder trips the guard, not just the named sub-paths.
Move the post-phase check + inject_ui_prebundle out of e2e_test_ui_prebundle into a new check_ui_prebundle_size job - build validation, not a test, so it shouldn't live in the e2e job. Persists the already-injected bundle so e2e_test_ui_prebundle attaches it directly instead of redoing the injection.
It builds the bundle, it doesn't set anything up. Updates the job key, every requires: reference, and current-state docs; historical findings log entries keep the old name since they describe what was true then.
React version compatibility check, process.cwd()-independent resolve root, correct nested node_modules handling, per-package version- collision handling, dependency-aware router-context safety, and no longer leaking the generated entry file's build paths into the shipped artifact - all in ui-vendor-dll.ts. Also: fail (not warn) on an unresolvable harmony runtime dep and vendor a missing browser-dist dependency in generate-shim-packages.ts; fail the size guard on a silently-missing artifact instead of reading it as 0 bytes; wire the size guard into both bvm publish CI jobs; fix a lint-staged glob that silently excluded root-level JS files.
## Proposed Changes The component-peer dependency e2e test gives `comp1` a custom TypeScript env but leaves `comp2` on the empty default env. With root components enabled, `comp1` references `comp2` as a TypeScript project even though the peer has no compiler to generate its tsconfig. Assign the same custom env to `comp2` so both projects have a compiler. This fixes the test fixture while preserving its peer-dependency and hidden-peer assertions; it does not change the external compiler's handling of non-TypeScript dependencies. Validation: `npm run lint` and `git diff --check` pass. Focused e2e runs were attempted under Node 22 with the local compiler reference-pruning patch disabled, but both original and corrected setups hit 18 unrelated local type errors mixing checkout types with an installed Bit version. A clean CI e2e result is still needed.
The previous commit's import { tmpdir } from 'os' broke the real
browser UI/preview pre-bundle build: ui-vendor-dll.ts is reachable
from a browser compilation via the @teambit/ui barrel, and os has no
browser resolve fallback the way fs/path do. Write the scratch entry
file under dirname(outputPath) instead - no new Node builtin needed,
and it sits outside the artifact glob rather than merely being
cleaned up in time.
Also: buildUiVendorDll's new sourceRoot default broke two existing
unit tests under real capsule execution - they used lodash.compact/
lodash.flatten as fixtures, which aren't actual dependencies of
@teambit/ui and only resolved by cwd happenstance. Switched to
p-map-series/chalk, real declared dependencies guaranteed present
wherever the component's own install is.
Also updates bundle-size-baseline.json's shims-dist check for the
expected size growth from vendoring @teambit/base-react.navigation.link.
p-map-series/chalk still failed to resolve via buildUiVendorDll's new sourceRoot default inside bit_pr's real build capsule, even though p-map-series is a genuine dependency of @teambit/ui. Revert to the original lodash.compact/lodash.flatten fixtures, explicitly resolved off process.cwd() (confirmed via CI history that this exact pairing passed before any of this sourceRoot work) - real BundleUiTask usage never passes a sourceRoot, so it's unaffected and still gets the correct default. Also replaces the resolveContextProviderMismatchUnsafePackages negative-case test's use of the live @teambit/preview component (whose dependency graph disagreed between CI and local) with a fully controlled fake package.
…om-manifest # Conflicts: # .bitmap # pnpm-lock.yaml # scopes/dependencies/pnpm/pnpm.package-manager.ts # scopes/workspace/install/install.main.runtime.ts
…om-manifest # Conflicts: # .bitmap # .circleci/config.yml # scopes/envs/envs/environments.main.runtime.ts
…nning bit's dists the dev install re-resolves with a manifest bbit never used; when the two disagree pnpm relinks the whole tree and replaces the workspace components' node_modules dirs, dists included. the install's own post-install compile then dies on a deferred require. also make the core-env e2e assertion follow DEFAULT_ENV instead of naming an env.
the install replaces the node_modules the process is loaded from, so every require it defers past that point throws MODULE_NOT_FOUND - the compiler aspect first, then the pager on the way out - long after the packages themselves have landed.
…alls building them inside an env root fails with node-gyp-build exit 127 since pnpm 12.4.1; nothing in the suite uses them.
…s unloaded their packages depend on this repo's components, whose dists the install just replaced, so the first pass falls back to tsc - whose eager requires pull UI-only packages into the node runtime and break the next bit.
the released bbit installed the current version while the dev binary wanted the pinned one; swapping a package this widely depended on re-keys peer hashes across the tree, so the dev install relinked all of node_modules and replaced the dists bit runs from. reverts the CI scaffolding that worked around the symptom.
… envs" the released bbit resolves core aspects from its own bundle and ignores the policy, so it still installed 1.0.1169 and the whole-tree relink was unchanged (+6152 either way).
…he container nothing in that describe looks at the preview; bundling a react-based env's preview is the tag's memory peak.
the tag is still OOM-killed with the preview task skipped, so the peak is elsewhere in the build - keep the task's coverage.
… relink Temporary, to be reverted. The dev-binary install in bit_pr / check_circular_dependencies relinks the whole tree (6152 packages) instead of the delta (1023 before the merge) and replaces the dists the running bit is loaded from. Everything else on this branch is unchanged, so this isolates the engine as the single variable.
…-install relink" This reverts commit 5db7dff.
… root A phantom require of a legacy core env resolves from the requiring package up to the workspace root, so a package already there satisfies it. Adding the pinned legacy version on top replaced the version the rest of the tree was resolved against: the package manager then re-linked every package whose peer resolution moved with it - the whole tree - re-materializing the workspace components' packages without the dists the running process is loaded from, which crashed the next deferred require with MODULE_NOT_FOUND. The bit repo installs the react env at the root, so it hit exactly this: the install relinked 6280 packages instead of the 1023 delta.
The previous commit guarded only the phantom-require path; the react env reaches the policy through the used-env path, so the downgrade still happened. Move the guard into addLegacyCoreEnvsToPolicy, which both paths funnel through, and make it version-aware: a root that already provides the package at the pinned version or later needs nothing added. These envs are dependencies of the bit package itself, so a bit that still ships them as core aspects installs its own (newer) version at the root. Pinning the older legacy version over it moved what the rest of the tree resolved its peers against and re-linked all 6280 packages, rewriting the dists the running process is loaded from.
Building them inside an env root fails with `node-gyp-build` exited with status 127 since pnpm 12.4.1, so an unrelated native addon decides whether a test can scaffold its fixture. Five suites hit it on the react env alone. Set neverBuiltDependencies once where every e2e workspace is created, and drop the two per-call --disallow-scripts workarounds it replaces.
…writes their packages
# Conflicts: # e2e/functionalities/peer-dependencies.e2e.ts # pnpm-lock.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bundles the CLI into a single 59 MB CJS file plus 10 externals that cannot be inlined, with a generated shim package per core aspect re-exporting its slice of the bundle.
npm run bundlebuilds it;--seaalso builds a node single executable.1.2 GB / 141k files → 159 MB / ~2.8k files.
bit --help~0.53s warm.The shims emit the same
dist/*.aspect.jsanddist/*.main.runtime.jsfilenames the aspect loader already discovers, so the runtime needed no changes. The one source fix is real and independent of bundling:hook-requirepatchedmodule.constructor.prototype.require, which under any bundler installs an enumerablerequireonObject.prototype.bit startnow works too: it serves the pre-built UI/preview bundles instead of running a bundler at all (shouldServeBundleUi/writePreviewEntryhash-match and serve from the shippedartifacts/, nopublic/written). Verified end to end from a freshbit init+bit createworkspace: UI shell, workspace/scope roots, and the component's own preview all served from the pre-bundle.@rspack/dev-serverand@rspack/coreitself (42 MB — was the single biggest external) are both fully excluded from the default build'spackage.json/node_modules, not just externalized — neither is reachable oncebit startserves the pre-bundle instead of rebuilding.Also adds a "UI vendor DLL" artifact, built alongside the existing UI/preview pre-bundle:
BundleUiTaskruns an additional rspackDllPluginpass producingvendor.js+ a portablevendor-manifest.json, covering React and every core-aspect package that ships browser (.ui.runtime/.preview.runtime) code. A newcreateUiVendorDllReference()lets a separate consuming project turn that manifest into realDllReferencePluginoptions, so it can intercept already-compiled core UI code instead of recompiling it from source — fixing the gap where a bundledbitcan't build a third-party UI root (e.g. an app likecommunity-cloud). Design doc:bundle-plan/26-ui-vendor-dll-design.md; plan + decision log:bundle-plan/27-ui-vendor-dll-plan.md.Also adds a CircleCI guard (
scripts/bundle-size-guard.mjs+scripts/bundle-size-baseline.json) that fails the build if any part of the bundle grows more than 10% past its baseline — a direct response to an SSR bundle regression (6 MB → 53 MB) caught and fixed on this branch by externalizing@rspack/coreinrspack.ssr.config.ts. Runs in two phases so the cheaper check fails fast:pre(no UI/preview build needed) inbuild_esbuild_bundle, right after the bundle is built and before the e2e fan-out starts;postine2e_test_ui_prebundle, right afterinject_ui_prebundle. Every check also has a whole-folder catch-all alongside the twelve named sub-paths.--update-baselinere-measures and rewrites the baseline file, the way to accept an intentional size increase later. Baseline is calibrated against a real CircleCI (Linux) run, not a local machine (native binaries for packages likeesbuild/@swc/corediffer by platform).Current size breakdown (measured on a real CircleCI Linux run):
bit.app.js(the CLI bundle)@teambit/*+ vendored harmony deps), before UI/preview injecteddist/barrels 0.12 MB + browser barrels (browser/, real compiled dist per shim, for third-party bundlers) 7.32 MB + vendored harmony runtime deps/locators/types the restartifacts/), injected after@rspack/coreexternalize fix) + UI vendor DLL (vendor.js+ manifest) 7.38 MB + preview 0.68 MBdist/core-aspects(bundle + all shims, before → after injection)node_modules)The UI pre-bundle shrink from the original ~82.7 MB two-build layout down to the single shared compilation comes from upstream #10628 (SSR fix + minifier) and #10629 (single rspack compilation shared by both UI roots instead of two separate builds), both merged into this branch, plus #10631's
bit startsanity e2e. Note the UI artifact no longer splits into separateui-bundle/workspace/ui-bundle/scopedirectories — it's one sharedui-bundle/public/bit/tree (withssr/andstatic/under it) plus the siblingui-vendor-dll/.esbuild's own
metafile.json(8.9 MB of build-analysis JSON, never read at runtime) is no longer written into the published package — still produced for localnpm run bundleiteration and CI's diagnostic capture.Producing the UI/preview pre-bundle locally needs a real
bit build --tasks BundleUI,PreBundlePreview; it's now cached under a gitignored.bundle-cache/(with a commit-hash + datemeta.json) sonode_moduleswipes don't force re-deriving it every time.Verified from an isolated dir: 40+ commands including
create,status,tag,export,import,watch,server,start, andbuild --unmodified(all 9 tasks, rspack included). UI vendor DLL additionally verified against a real rebuilt CLI bundle and against a genuinely separatepnpm installwith different peer dependencies, confirming realDllReferencePlugininterception (not just a clean build).npm run e2e-test:bundle/:searun the suite against the artifact; CircleCI builds it once inbuild_esbuild_bundleand shares it across the e2e nodes (gated to^bit-bundle.*branches).Full architecture, measurements, externals breakdown, script-vs-SEA analysis, the publishable package layout and open questions are in
bundle-plan.md.Draft: based on
remove-core-envs-from-manifest, so the diff includes that branch. Opened to get CircleCI running.ui-vendor-dll(#10690) has been fast-forward merged into this branch and closed.