Skip to content

feat(bundler): honor angular.json externalDependencies (#146) - #174

Merged
lukekania merged 1 commit into
milestone/v0.11.0-builder-parityfrom
feat/bundler-external-deps
May 18, 2026
Merged

feat(bundler): honor angular.json externalDependencies (#146)#174
lukekania merged 1 commit into
milestone/v0.11.0-builder-parityfrom
feat/bundler-external-deps

Conversation

@lukekania

@lukekania lukekania commented May 18, 2026

Copy link
Copy Markdown
Owner

Closes #146

Summary

  • Honors angular.json's externalDependencies array. An import like import $ from 'jquery' survives as a bare ESM specifier when jquery is declared external — the runtime (browser import map, CDN loader, micro-frontend host) resolves it. The package is neither inlined nor BFS-walked by the resolver, matching @angular/build:application (esbuild --external: jquery).
  • Subpath matching: declaring jquery also externalises jquery/dist/jquery.slim etc., consistent with esbuild's package-name external semantics.
  • Per-configuration overrides replace the base list (matches ng build's merge order — same as how it handles e.g. budgets).

Where the change lives

  • crates/project-resolver/src/angular_json.rs: parse externalDependencies (base + per-configuration override) into ResolvedAngularProject.external_dependencies.
  • crates/npm-resolver/src/lib.rs: new resolve_npm_dependencies_with_externals skips externalised specifiers in both phase-1 entry resolution and the phase-2 transitive BFS. The original resolve_npm_dependencies is now a thin wrapper that passes an empty set.
  • crates/bundler/src/concat.rs + rewrite.rs: new BundleInput.external_specifiers field. is_local is updated to veto externals first (so even if a specifier leaks into bundled_specifiers, externals stay bare). Lazy-chunk routing keeps externals as direct bare imports instead of folding them through ./main.js.
  • crates/cli/src/main.rs: thread externals through every resolve_npm_dependencies call site; defensively strip externals from bundled_specifiers before bundling.
  • packages/builder/src/build/options.ts: drop the stale "externalDependencies is currently ignored" warning.

Tests

  • Unit tests for project-resolver parsing (base, empty default, configuration override).
  • Unit tests for npm-resolver skipping top-level + transitive externals, and matching subpaths.
  • Bundler tests that an external import survives verbatim even if bundled_specifiers contains the same name (regression guard), and that subpaths of external packages are also kept as bare specifiers.

End-to-end verification

Against test-ng-project:

  • Declared "externalDependencies": ["jquery"] in angular.json.
  • Added import $ from 'jquery'; and import { something } from 'jquery/dist/jquery.slim'; in src/main.ts.
  • Built with NGC_RS_BINARY=…/target/release/ngc-rs npx ng build.
  • dist/test-ng-project/main.js contains both imports verbatim; jquery is not installed in the fixture and the resolver silently skips it (the BFS never asks for it) — proving the second acceptance bullet.

Notes vs. issue body

  • The issue lists <script type="importmap"> emission as a separate follow-up. Left out of scope — implementation here only covers "leave the bare specifier".

Test plan

  • cargo build --release -p ngc-rs
  • cargo test -p ngc-bundler -p ngc-npm-resolver -p ngc-project-resolver
  • cargo clippy --workspace -- -D warnings
  • End-to-end: add externalDependencies to a fixture, build, grep dist/main.js for the bare import.

Adds support for `externalDependencies` in `angular.json` so projects
loading specific npm packages from a CDN (or expecting them as
`import` map entries) can exclude those packages from the bundle —
matching `@angular/build:application`.

- project-resolver: parse `externalDependencies` (base + per-config
  override) into `ResolvedAngularProject.external_dependencies`.
- npm-resolver: new `resolve_npm_dependencies_with_externals` skips
  externalised specifiers in phase 1 and the transitive BFS, so a
  package and all its modules stay out of `npm_resolution`.
- bundler: `BundleInput.external_specifiers` vetoes `is_local` —
  an import whose specifier matches an external entry (exact or
  `<name>/...` subpath) is emitted verbatim and never rewritten to a
  `__ns_*` namespace reference. Lazy-chunk routing keeps externals
  as bare specifiers instead of folding them through `./main.js`.
- cli: thread externals through every npm-resolver call site; strip
  externals from `bundled_specifiers` defensively before bundling.
- builder: drop the stale "currently ignored" warning.

Verified against `test-ng-project`: declaring `externalDependencies:
["jquery"]` and importing `$ from 'jquery'` (and the subpath
`jquery/dist/jquery.slim`) emits the imports verbatim in
`main.js`, jquery is not installed in the fixture so unresolved
externals are silently skipped by the resolver as intended.

Bumps workspace to 0.10.10.
@lukekania
lukekania force-pushed the feat/bundler-external-deps branch from 94b0efc to 68a6c85 Compare May 18, 2026 09:44
@lukekania
lukekania merged commit a09ab0e into milestone/v0.11.0-builder-parity May 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant