Skip to content

feat(builders): keep prebuilt entry points out of the TypeScript program - #133

Closed
Aukevanoost wants to merge 1 commit into
mainfrom
issues/core-122
Closed

Aukevanoost wants to merge 1 commit into
mainfrom
issues/core-122

Conversation

@Aukevanoost

Copy link
Copy Markdown
Contributor

Adapter half of native-federation/native-federation-core#122sharedMappings
libraries being evaluated twice at runtime (NG0201).

Why

The duplication is structural in ngtsc: metadata derived from source never carries
bestGuessOwningModule, so a synthesized reference to a path-mapped library falls
through to RelativePathStrategy and emits a deep relative import, which bypasses the
bundler's external list. The fix on the core side is to let a mapping point at a
library's build output instead of its source:

// tsconfig.json
"@org/ui": ["./dist/ui"]   // instead of ["./libs/ui/src/public-api.ts"]

With the library's .d.ts outside the app's TypeScript program, AbsoluteModuleStrategy
succeeds and the import is emitted bare. Verified end-to-end: the second copy disappears
from the app bundle and the federated chunk becomes the only one.

What this changes

Such an entry point is already-compiled JS. esbuild loads it through the linker like any
dependency, but listing it in the federation tsconfig's files makes ngtsc reject it:

TS6504: File 'dist/ui/fesm2022/ui.mjs' is a JavaScript file. Did you mean to enable
the 'allowJs' option? The file is in the program because: Root file specified for compilation

Core now marks every entry point 'source' or 'package', so updateFederationTsConfig
filters on a decision core already made rather than inferring one here from a file
extension.

Compatibility

Not a breaking change. Exposes and source mappings are both 'source', so the emitted
files array is identical for every setup that exists today — only the new prebuilt shape
is filtered out. A host whose mappings are all prebuilt still falls back to the app's own
entry points, so the program is never empty.

Worth noting this is the only adapter change the feature needs. The generic esbuild adapter
already resolves .mjs/.js entry points and never constructs a TypeScript program, so it
works with prebuilt mappings untouched — this is Angular-specific because ngtsc is.

Blocked on

@softarc/native-federation with EntryPoint.kind. The ~4.5.0 pin means the core release
has to land first; draft until then.

Testing

pnpm test 201 passing (2 new), typecheck and lint clean. Also exercised against a real
Angular 22 workspace with one mapping repointed at dist.

A shared mapping may point at a library's build output rather than its source
(`"@org/ui": ["dist/ui"]`), which is what stops ngtsc emitting deep relative
imports into a mapped library and evaluating it twice. Such an entry point is
already-compiled JS: esbuild loads it through the linker like any dependency,
and listing it in the federation tsconfig's `files` only makes ngtsc reject it
as a non-TypeScript root file.

Core now marks every entry point `'source'` or `'package'`, so the decision is
one it already made rather than something inferred here from a file extension.
Nothing changes for existing setups: exposes and source mappings are both
`'source'`, and a host whose mappings are all prebuilt still falls back to the
app's own entry points to keep the program non-empty.

Requires @softarc/native-federation with EntryPoint.kind.
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