fix(schematics): repair init's bootstrap.ts handling, add --webcomponent - #135
Merged
Merged
Conversation
…xistence init decided what to do with main.ts by checking whether bootstrap.ts was there. The two can disagree, and both ways they disagree were broken. When bootstrap.ts had gone missing from an already-initialised project -- renamed, deleted, or the build entry repointed between runs -- the guard fell through and copied main.ts into it. main.ts was the federation stub by then, so bootstrap.ts called initFederation a second time and imported itself, and the original bootstrap code went with the overwrite. When bootstrap.ts existed for any other reason the guard returned early, and only this step stopped: polyfills, angular.json, tsconfig.federation.json, package.json and federation.config.mjs were all still written. init reported success on a workspace whose entry point never called initFederation. main.ts is now always rewritten, and its own content decides what goes into bootstrap.ts: the unfederated source when there is some to move, a regenerated bootstrapApplication scaffold when main.ts is already the stub. An existing bootstrap.ts is never overwritten -- it is kept, with a warning that main.ts's previous contents were dropped. Generating that scaffold needs the root component's symbol and not just its file, so the app.component.ts/app.ts probe moves out of schematic.ts into resolveAppComponent and now reads the exported class name: Angular >=20 scaffolds `App` in app/app.ts, earlier versions `AppComponent` in app/app.component.ts. A missing build entry point throws with the offending path and the angular.json key it came from, rather than a bare PathDoesNotExistException out of tree.overwrite. The step had no spec, which is why this survived. It has one now, with fixtures taken verbatim from `ng new` on Angular 22.
Remotes that are consumed as custom elements rather than as lazy Angular routes need a different bootstrap: createApplication instead of bootstrapApplication, and a customElements.define for the root component. Until now that had to be written by hand after every init, and the tag name and injector wiring are easy to get subtly wrong. `--webcomponent` generates that bootstrap.ts instead of moving main.ts into it. The element is registered as mfe-<project> -- custom element names must contain a hyphen, which a one-word project name does not -- and the generated line keeps the `// your componentname` marker, since the tag is part of the remote's contract with its host and is meant to be edited. @angular/elements is added at whatever range the workspace already has for @angular/core. It ships in lockstep with the framework, so a floating range would resolve a major that does not match, and a workspace without @angular/core to match against is an error rather than a guess. An existing bootstrap.ts is still never overwritten; the flag warns that it was a no-op rather than discarding the file.
…ut it Deriving bootstrap.ts from main.ts's content made main.ts the file that gets rewritten, and three paths rewrote it over contents that were not recoverable afterwards. A re-run regenerated the stub from scratch, so anything the user had changed about the initFederation call -- a shimMode: false for #70, an adjusted hostRemoteEntry, remotes added by hand -- was reverted without a word. main.ts is now left as it is once it already calls initFederation; only a missing bootstrap.ts is regenerated. A host bakes its remote map into main.ts, so a host re-run says that the map was not refreshed rather than leaving it to be discovered. When bootstrap.ts was already taken and main.ts was not federated, main.ts's contents were dropped with a console.warn as the only trace. There is no second place to move them to, so that is now a SchematicsException naming both files, thrown before anything is written. --webcomponent replaces main.ts instead of moving it, which the feature cannot avoid, but it did so in silence: a remote calling registerLocaleData or initialising Sentry ahead of bootstrapApplication lost it. It warns now, which is what the bootstrap.ts-is-taken path already did. resolveAppComponent returned null whenever it could not read a class name, even though it had found the file. schematic.ts only wants the path, and fell back to the update-this.ts placeholder -- a federation.config.mjs exposing a file that is not there, for an app.ts using a default export or a separate export statement. className is now nullable and only resolveAppRefs, which needs the symbol, rejects. @angular/elements and its install task were queued from the flag alone, before makeMainAsync had decided the flag was a no-op because bootstrap.ts already existed. addDependencies moves into the chain behind makeMainAsync and takes the outcome, so the dependency follows the bootstrap that was actually written. --webcomponent against a host or dynamic-host is rejected up front: the generated bootstrap only registers an element and never bootstraps the shell. The generated createApplication chain also had no .catch, unlike the bootstrapApplication one, so a throwing provider was an unhandled rejection on a blank page.
initFederation's parameter type came from @softarc/native-federation -- the
build-time entry that exports buildForFederation and the esbuild adapters --
while the initFederation it forwards to lives in
@softarc/native-federation-orchestrator.
The two descriptors are not the same. The build-time one carries an optional
`main` that nothing in the orchestrator bundle reads, so
initFederation({ mfe1: { url, main } }) type-checked and then ignored `main`
at runtime. The emitted index.d.ts also kept the import, pulling the build
entry's type graph into every consumer compilation.
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.
Five commits. The schematic work is the reason for the PR;
548fb0ewas already on the branch, and the last two are review fixes.548fb0echore: bad re-export of initFederationinitFederation's public signature tookRecord<string, string> | stringwhile core's takesFederationManifest | string. Narrower than what it forwards to, so a valid manifest was a type error at the adapter boundary.2992edcfix: derive bootstrap.ts from main.ts, not from its own existencemakeMainAsynckeyed offtree.exists('bootstrap.ts')as a proxy for "already initialised". The two can disagree, and both directions were broken:main.tsinto it.main.tswas the stub by then, so the newbootstrap.tscalledinitFederationagain and didimport('./bootstrap')on itself. The real bootstrap code went with the overwrite — no copy anywhere.return, and only this step stopped. Polyfills,angular.json,tsconfig.federation.json,package.jsonandfederation.config.mjswere all still written, sincemakeMainAsyncruns last in thechain([...]). init reported success on a workspace whose entry point never calledinitFederation. Trace was oneconsole.info.What goes into
bootstrap.tsnow follows frommain.ts's own content. Generating the scaffold needs the root component's symbol, not just its file, so theapp.component.ts/app.tsprobe moved out ofschematic.tsintoresolveAppComponent, which now also reads the exported class name — Angular >=20 scaffoldsAppinapp/app.ts, earlier versionsAppComponentinapp/app.component.ts.schematic.tsreuses it for exposes-seeding so the two can't drift.A missing build entry point now throws naming the path and the
angular.jsonkey, instead of a barePathDoesNotExistExceptionout oftree.overwrite.The step had no spec, which is why this survived. It has one now, fixtures verbatim from
ng newon Angular 22.a2c33ebfeat:--webcomponenton initGenerates a custom-element
bootstrap.tsinstead of movingmain.tsinto it:Tag is
mfe-<project>— custom element names need a hyphen, which a one-word project name lacks. The// your componentnamemarker stays: the tag is part of the remote's contract with its host and is meant to be edited.@angular/elementsis added at whatever range the workspace has for@angular/core— it ships in lockstep with the framework, so a floating range resolves a mismatched major. No@angular/coreto match against is an error, not a guess. Note this lands in the rootpackage.json, so in a monorepo one remote's flag makes it available workspace-wide;package.jsonhas no per-project scoping.The flag is only accepted for
--type remote, and an existingbootstrap.tsis never clobbered.d8c9cc6fix: stop init from overwriting main.ts with nowhere to put itReview fix. Deriving
bootstrap.tsfrommain.ts's content mademain.tsthe file that gets rewritten, and three paths rewrote it over contents that were not recoverable afterwards.initFederationcall —shimMode: falsefor Shim mode corrupts every method namedimport(e.g. DevExtreme Diagram); allow opting into native import maps #70, an adjustedhostRemoteEntry/cacheTag, remotes added by hand — was reverted without a word.main.tsis now left alone once it already callsinitFederation; only a missingbootstrap.tsis regenerated. A host bakes its remote map intomain.ts, so a host re-run now says the map was not refreshed rather than leaving it to be discovered.bootstrap.tstaken +main.tsnot federated destroyedmain.ts. Its contents were dropped with aconsole.warnas the only trace, and there is no second place to move them to. Now aSchematicsExceptionnaming both files, thrown before anything is written.--webcomponentdiscardedmain.tssilently. It replacesmain.tsrather than moving it, which the feature cannot avoid, but a remote callingregisterLocaleDataor initialising Sentry ahead ofbootstrapApplicationlost it without a word. It warns now, matching the row above.Resulting behaviour:
bootstrap.tsgetsmain.ts's content;main.tsbecomes the stubmain.ts's contents have nowhere to gobootstrap.tsregenerated from the root component;main.tsuntouchedWith
--webcomponentthe first row generates the element bootstrap instead of movingmain.ts, and warns thatmain.tswas not kept.Three smaller fixes from the same review:
resolveAppComponentreturnednullwhenever it could not read a class name, even though it had found the file.schematic.tsonly wants the path and fell back to theupdate-this.tsplaceholder — afederation.config.mjsexposing a file that is not there, for anapp.tsusing a default export or a separateexport { App }.classNameis nullable now and onlyresolveAppRefs, which needs the symbol, rejects.@angular/elementsand itsNodePackageInstallTaskwere queued from the flag alone, beforemakeMainAsynchad decided the flag was a no-op becausebootstrap.tsalready existed.addDependenciesmoves into thechainbehindmakeMainAsyncand takes its outcome.--webcomponentagainst ahost/dynamic-hostis rejected up front: the generated bootstrap only registers an element and never bootstraps the shell.createApplicationchain had no.catch, unlike thebootstrapApplicationone, so a throwing provider was an unhandled rejection on a blank page.722e88afix: take FederationManifest from the orchestrator, not the build entryReview fix.
initFederation's parameter type came from@softarc/native-federation— the build-time entry that exportsbuildForFederationand the esbuild adapters — while theinitFederationit forwards to lives in@softarc/native-federation-orchestrator.The two descriptors differ: the build-time one carries an optional
mainthat nothing in the orchestrator bundle reads, soinitFederation({ mfe1: { url, main } })type-checked and then ignoredmainat runtime. The emittedindex.d.tsalso kept the import, pulling the build entry's type graph into every consumer compilation.Verification
227 tests, typecheck clean, eslint 0 errors.
2992edcwas checked green standalone (208 tests) so the fix is cherry-pickable without the feature.Beyond unit tests, the built
distwas run as a real schematic againstng newon Angular 22 (@angular/core^22.1.0), confirming: plain init moves main.ts; deletingbootstrap.tsand re-running regenerates a correct scaffold instead of the self-import;--webcomponentemits the element registration withmfe-test-app;@angular/elementsresolves to^22.1.0matching core. Both generated variants type-check under Angular's stricttsconfig.app.json.Note
The branch name is from
548fb0eand doesn't describe the schematic work; the two are unrelated beyond sharing a branch.