module-federation-angular-adapter 0.2200.0

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.
Files changed (144) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +115 -0
  3. package/builders.json +15 -0
  4. package/collection.json +27 -0
  5. package/generators.json +12 -0
  6. package/migration-collection.json +6 -0
  7. package/package.json +69 -0
  8. package/src/builders/build/builder.d.ts +7 -0
  9. package/src/builders/build/builder.d.ts.map +1 -0
  10. package/src/builders/build/builder.js +413 -0
  11. package/src/builders/build/federation-build-notifier.d.ts +70 -0
  12. package/src/builders/build/federation-build-notifier.d.ts.map +1 -0
  13. package/src/builders/build/federation-build-notifier.js +186 -0
  14. package/src/builders/build/get-externals.d.ts +18 -0
  15. package/src/builders/build/get-externals.d.ts.map +1 -0
  16. package/src/builders/build/get-externals.js +19 -0
  17. package/src/builders/build/i18n.d.ts +21 -0
  18. package/src/builders/build/i18n.d.ts.map +1 -0
  19. package/src/builders/build/i18n.js +73 -0
  20. package/src/builders/build/schema.d.ts +35 -0
  21. package/src/builders/build/schema.json +93 -0
  22. package/src/builders/build/setup-builder-env-variables.d.ts +1 -0
  23. package/src/builders/build/setup-builder-env-variables.d.ts.map +1 -0
  24. package/src/builders/build/setup-builder-env-variables.js +9 -0
  25. package/src/builders/build/update-index-html.d.ts +5 -0
  26. package/src/builders/build/update-index-html.d.ts.map +1 -0
  27. package/src/builders/build/update-index-html.js +33 -0
  28. package/src/builders/remote/assets.d.ts +14 -0
  29. package/src/builders/remote/assets.d.ts.map +1 -0
  30. package/src/builders/remote/assets.js +49 -0
  31. package/src/builders/remote/builder.d.ts +13 -0
  32. package/src/builders/remote/builder.d.ts.map +1 -0
  33. package/src/builders/remote/builder.js +153 -0
  34. package/src/builders/remote/change-watcher.d.ts +10 -0
  35. package/src/builders/remote/change-watcher.d.ts.map +1 -0
  36. package/src/builders/remote/change-watcher.js +35 -0
  37. package/src/builders/remote/infer-config-path.d.ts +2 -0
  38. package/src/builders/remote/infer-config-path.d.ts.map +1 -0
  39. package/src/builders/remote/infer-config-path.js +10 -0
  40. package/src/builders/remote/resolve-ng-options.d.ts +24 -0
  41. package/src/builders/remote/resolve-ng-options.d.ts.map +1 -0
  42. package/src/builders/remote/resolve-ng-options.js +28 -0
  43. package/src/builders/remote/schema.d.ts +26 -0
  44. package/src/builders/remote/schema.json +116 -0
  45. package/src/config/angular-locales.d.ts +6 -0
  46. package/src/config/angular-locales.d.ts.map +1 -0
  47. package/src/config/angular-locales.js +23 -0
  48. package/src/config/angular-skip-list.d.ts +3 -0
  49. package/src/config/angular-skip-list.d.ts.map +1 -0
  50. package/src/config/angular-skip-list.js +23 -0
  51. package/src/config/with-module-federation.d.ts +72 -0
  52. package/src/config/with-module-federation.d.ts.map +1 -0
  53. package/src/config/with-module-federation.js +72 -0
  54. package/src/config.d.ts +4 -0
  55. package/src/config.d.ts.map +1 -0
  56. package/src/config.js +3 -0
  57. package/src/generators/native-federation/files/src/index.ts__template__ +1 -0
  58. package/src/generators/native-federation/generator.d.ts +4 -0
  59. package/src/generators/native-federation/generator.d.ts.map +1 -0
  60. package/src/generators/native-federation/generator.js +45 -0
  61. package/src/generators/native-federation/schema.d.ts +5 -0
  62. package/src/generators/native-federation/schema.json +29 -0
  63. package/src/index.d.ts +82 -0
  64. package/src/index.d.ts.map +1 -0
  65. package/src/index.js +132 -0
  66. package/src/internal.d.ts +2 -0
  67. package/src/internal.d.ts.map +1 -0
  68. package/src/internal.js +1 -0
  69. package/src/schematics/appbuilder/schema.d.ts +3 -0
  70. package/src/schematics/appbuilder/schema.json +17 -0
  71. package/src/schematics/appbuilder/schematic.d.ts +5 -0
  72. package/src/schematics/appbuilder/schematic.d.ts.map +1 -0
  73. package/src/schematics/appbuilder/schematic.js +80 -0
  74. package/src/schematics/init/files/federation.config.mjs__tmpl__ +35 -0
  75. package/src/schematics/init/schema.d.ts +5 -0
  76. package/src/schematics/init/schema.json +30 -0
  77. package/src/schematics/init/schematic.d.ts +6 -0
  78. package/src/schematics/init/schematic.d.ts.map +1 -0
  79. package/src/schematics/init/schematic.js +38 -0
  80. package/src/schematics/init/steps/add-dependencies.d.ts +3 -0
  81. package/src/schematics/init/steps/add-dependencies.d.ts.map +1 -0
  82. package/src/schematics/init/steps/add-dependencies.js +25 -0
  83. package/src/schematics/init/steps/generate-federation-config.d.ts +4 -0
  84. package/src/schematics/init/steps/generate-federation-config.d.ts.map +1 -0
  85. package/src/schematics/init/steps/generate-federation-config.js +15 -0
  86. package/src/schematics/init/steps/generate-remote-map.d.ts +2 -0
  87. package/src/schematics/init/steps/generate-remote-map.d.ts.map +1 -0
  88. package/src/schematics/init/steps/generate-remote-map.js +20 -0
  89. package/src/schematics/init/steps/make-main-async.d.ts +4 -0
  90. package/src/schematics/init/steps/make-main-async.d.ts.map +1 -0
  91. package/src/schematics/init/steps/make-main-async.js +35 -0
  92. package/src/schematics/init/steps/normalize-options.d.ts +20 -0
  93. package/src/schematics/init/steps/normalize-options.d.ts.map +1 -0
  94. package/src/schematics/init/steps/normalize-options.js +64 -0
  95. package/src/schematics/init/steps/update-package-json.d.ts +3 -0
  96. package/src/schematics/init/steps/update-package-json.d.ts.map +1 -0
  97. package/src/schematics/init/steps/update-package-json.js +19 -0
  98. package/src/schematics/init/steps/update-polyfills.d.ts +3 -0
  99. package/src/schematics/init/steps/update-polyfills.d.ts.map +1 -0
  100. package/src/schematics/init/steps/update-polyfills.js +21 -0
  101. package/src/schematics/init/steps/update-workspace-config.d.ts +4 -0
  102. package/src/schematics/init/steps/update-workspace-config.d.ts.map +1 -0
  103. package/src/schematics/init/steps/update-workspace-config.js +76 -0
  104. package/src/schematics/remove/schema.d.ts +3 -0
  105. package/src/schematics/remove/schema.json +17 -0
  106. package/src/schematics/remove/schematic.d.ts +5 -0
  107. package/src/schematics/remove/schematic.d.ts.map +1 -0
  108. package/src/schematics/remove/schematic.js +106 -0
  109. package/src/tools/esbuild/angular-bundler.d.ts +10 -0
  110. package/src/tools/esbuild/angular-bundler.d.ts.map +1 -0
  111. package/src/tools/esbuild/angular-bundler.js +134 -0
  112. package/src/tools/esbuild/create-awaitable-compiler-plugin.d.ts +6 -0
  113. package/src/tools/esbuild/create-awaitable-compiler-plugin.d.ts.map +1 -0
  114. package/src/tools/esbuild/create-awaitable-compiler-plugin.js +29 -0
  115. package/src/tools/esbuild/create-federation-tsconfig.d.ts +7 -0
  116. package/src/tools/esbuild/create-federation-tsconfig.d.ts.map +1 -0
  117. package/src/tools/esbuild/create-federation-tsconfig.js +46 -0
  118. package/src/tools/esbuild/shared-mappings-plugin.d.ts +4 -0
  119. package/src/tools/esbuild/shared-mappings-plugin.d.ts.map +1 -0
  120. package/src/tools/esbuild/shared-mappings-plugin.js +33 -0
  121. package/src/tools/mf/build-for-federation.d.ts +70 -0
  122. package/src/tools/mf/build-for-federation.d.ts.map +1 -0
  123. package/src/tools/mf/build-for-federation.js +75 -0
  124. package/src/tools/mf/federation-entry-points.d.ts +23 -0
  125. package/src/tools/mf/federation-entry-points.d.ts.map +1 -0
  126. package/src/tools/mf/federation-entry-points.js +13 -0
  127. package/src/tools/mf/federation-plugin.d.ts +11 -0
  128. package/src/tools/mf/federation-plugin.d.ts.map +1 -0
  129. package/src/tools/mf/federation-plugin.js +12 -0
  130. package/src/tools/mf/federation-side-build.d.ts +21 -0
  131. package/src/tools/mf/federation-side-build.d.ts.map +1 -0
  132. package/src/tools/mf/federation-side-build.js +27 -0
  133. package/src/tools/mf/to-plugin-config.d.ts +37 -0
  134. package/src/tools/mf/to-plugin-config.d.ts.map +1 -0
  135. package/src/tools/mf/to-plugin-config.js +33 -0
  136. package/src/utils/check-for-invalid-imports.d.ts +2 -0
  137. package/src/utils/check-for-invalid-imports.d.ts.map +1 -0
  138. package/src/utils/check-for-invalid-imports.js +29 -0
  139. package/src/utils/normalize-build-options.d.ts +22 -0
  140. package/src/utils/normalize-build-options.d.ts.map +1 -0
  141. package/src/utils/normalize-build-options.js +45 -0
  142. package/src/utils/normalize-context-options.d.ts +24 -0
  143. package/src/utils/normalize-context-options.d.ts.map +1 -0
  144. package/src/utils/normalize-context-options.js +18 -0
@@ -0,0 +1,413 @@
1
+ import "./setup-builder-env-variables.js";
2
+ import * as fs from "fs";
3
+ import * as mrmime from "mrmime";
4
+ import * as path from "path";
5
+ import { buildApplication, } from "@angular/build";
6
+ import { buildApplicationInternal, normalizeDevServerOptions, serveWithVite, SourceFileCache, } from "@angular/build/private";
7
+ import { createBuilder, targetFromTargetString, } from "@angular-devkit/architect";
8
+ import { createFederationCache, getExternals, normalizeFederationOptions, } from "@softarc/native-federation";
9
+ import { AbortedError, createNfWatcher, getDefaultCachePath, logger, RebuildQueue, setLogLevel, syncNfFileWatcher, } from "@softarc/native-federation/internal";
10
+ import { checkForInvalidImports } from "./../../utils/check-for-invalid-imports.js";
11
+ import { federationBuildNotifier } from "./federation-build-notifier.js";
12
+ import { createMfFederationBuilder, } from "../../tools/mf/build-for-federation.js";
13
+ import { getI18nConfig, translateFederationArtifacts } from "./i18n.js";
14
+ import { updateScriptTags } from "./update-index-html.js";
15
+ const originalWrite = process.stderr.write.bind(process.stderr);
16
+ process.stderr.write = function (chunk, encodingOrCallback, callback) {
17
+ const str = typeof chunk === "string" ? chunk : chunk.toString();
18
+ if (str.includes("vite:import-analysis") &&
19
+ str.includes("es-module-shims.js")) {
20
+ return true;
21
+ }
22
+ if (typeof encodingOrCallback !== "string") {
23
+ return originalWrite(chunk, encodingOrCallback);
24
+ }
25
+ return originalWrite(chunk, encodingOrCallback, callback);
26
+ };
27
+ const createInternalAngularBuilder = (externals, opts) => (options, context, pluginsOrExtensions) => {
28
+ let extensions;
29
+ if (pluginsOrExtensions && Array.isArray(pluginsOrExtensions)) {
30
+ extensions = {
31
+ codePlugins: pluginsOrExtensions,
32
+ };
33
+ }
34
+ else {
35
+ extensions = pluginsOrExtensions;
36
+ }
37
+ // serveWithVite fetches its own browserOptions independently, so ngBuilderOptions
38
+ // modifications don't reach here. Add NF externals to externalDependencies so
39
+ // Angular routes them to optimizeDeps.exclude, preventing Vite from trying to
40
+ // pre-bundle packages that include native .node binaries.
41
+ options.externalDependencies = [
42
+ ...(options.externalDependencies ?? []),
43
+ ...externals,
44
+ ];
45
+ if (opts?.instrumentForCoverage) {
46
+ options.instrumentForCoverage = opts.instrumentForCoverage;
47
+ }
48
+ // Todo: share cache with Angular builder: https://github.com/angular/angular-cli/pull/32527
49
+ // options.codeBundleCache = nfOptions.federationCache.bundlerCache;
50
+ return buildApplicationInternal(options, context, extensions);
51
+ };
52
+ export async function* runBuilder(nfBuilderOptions, context) {
53
+ let target = targetFromTargetString(nfBuilderOptions.target);
54
+ let targetOptions = (await context.getTargetOptions(target));
55
+ let builder = await context.getBuilderNameForTarget(target);
56
+ if (builder === "@angular-devkit/build-angular:browser-esbuild") {
57
+ logger.info(".: NATIVE FEDERATION - UPDATE NEEDED :.");
58
+ logger.info("");
59
+ logger.info("Since version 17.1, Native Federation uses Angular's");
60
+ logger.info("Application-Builder and its Dev-Server.");
61
+ logger.info("");
62
+ logger.info("If you are sill on Angular 17.0.x, please update to");
63
+ logger.info("Angular 17.1.x or downgrade to Native Federation 17.0.x.");
64
+ logger.info("");
65
+ logger.info("For working with Native Federation 17.1.x (recommented), ");
66
+ logger.info("please update your project config, e.g. in angular.json");
67
+ logger.info("");
68
+ logger.info("This command performs the needed update for default configs:");
69
+ logger.info("");
70
+ logger.info("\tng g @angular-architects/native-federation:appbuilder");
71
+ logger.info("");
72
+ logger.info("You need to run it once per application to migrate");
73
+ logger.info("Please find more information here: https://shorturl.at/gADJW");
74
+ return;
75
+ }
76
+ /**
77
+ * Explicitly defined as devServer or if the target contains "serve"
78
+ */
79
+ const runViteServer = typeof nfBuilderOptions.devServer !== "undefined"
80
+ ? !!nfBuilderOptions.devServer
81
+ : target.target.includes("serve");
82
+ let ngBuilderOptions = (await context.validateOptions(runViteServer
83
+ ? {
84
+ ...targetOptions,
85
+ port: nfBuilderOptions.port || targetOptions["port"],
86
+ }
87
+ : targetOptions, builder));
88
+ let serverOptions = null;
89
+ const watch = nfBuilderOptions.watch ?? ngBuilderOptions.watch ?? runViteServer;
90
+ ngBuilderOptions.watch = watch;
91
+ if (ngBuilderOptions["buildTarget"]) {
92
+ serverOptions = await normalizeDevServerOptions(context, context.target.project, ngBuilderOptions);
93
+ target = targetFromTargetString(ngBuilderOptions["buildTarget"]);
94
+ targetOptions = (await context.getTargetOptions(target));
95
+ builder = await context.getBuilderNameForTarget(target);
96
+ ngBuilderOptions = (await context.validateOptions(targetOptions, builder));
97
+ }
98
+ if (nfBuilderOptions.baseHref) {
99
+ ngBuilderOptions.baseHref = nfBuilderOptions.baseHref;
100
+ }
101
+ if (nfBuilderOptions.outputPath) {
102
+ ngBuilderOptions.outputPath = nfBuilderOptions.outputPath;
103
+ }
104
+ const federationTsConfig = !!nfBuilderOptions.tsConfig && nfBuilderOptions.tsConfig.length > 0
105
+ ? nfBuilderOptions.tsConfig
106
+ : ngBuilderOptions.tsConfig;
107
+ setLogLevel(ngBuilderOptions.verbose ? "verbose" : "info");
108
+ if (!ngBuilderOptions.outputPath) {
109
+ ngBuilderOptions.outputPath = `dist/${context.target.project}`;
110
+ }
111
+ const outputPath = ngBuilderOptions.outputPath;
112
+ const outputOptions = {
113
+ browser: "browser",
114
+ server: "server",
115
+ media: "media",
116
+ ...(typeof outputPath === "string" ? undefined : outputPath),
117
+ base: typeof outputPath === "string" ? outputPath : outputPath.base,
118
+ };
119
+ const i18n = await getI18nConfig(context);
120
+ const localeFilter = getLocaleFilter(ngBuilderOptions, runViteServer);
121
+ const sourceLocaleSegment = typeof i18n?.sourceLocale === "string"
122
+ ? i18n.sourceLocale
123
+ : i18n?.sourceLocale?.subPath || i18n?.sourceLocale?.code || "";
124
+ const browserOutputPath = path.join(outputOptions.base, outputOptions.browser, ngBuilderOptions.localize ? sourceLocaleSegment : "");
125
+ const differentDevServerOutputPath = Array.isArray(localeFilter) && localeFilter.length === 1;
126
+ const devServerOutputPath = !differentDevServerOutputPath
127
+ ? browserOutputPath
128
+ : path.join(outputOptions.base, outputOptions.browser, localeFilter[0]);
129
+ const entryPoints = nfBuilderOptions.entryPoints && nfBuilderOptions.entryPoints.length > 0
130
+ ? nfBuilderOptions.entryPoints
131
+ : [path.join(path.dirname(federationTsConfig), "src/main.ts")];
132
+ const cachePath = getDefaultCachePath(context.workspaceRoot);
133
+ const normalized = await normalizeFederationOptions({
134
+ projectName: nfBuilderOptions.projectName,
135
+ workspaceRoot: context.workspaceRoot,
136
+ outputPath: browserOutputPath,
137
+ federationConfig: inferConfigPath(federationTsConfig, context.workspaceRoot, nfBuilderOptions.federationConfigPath),
138
+ tsConfig: federationTsConfig,
139
+ verbose: ngBuilderOptions.verbose,
140
+ watch: ngBuilderOptions.watch,
141
+ dev: !!nfBuilderOptions.dev,
142
+ entryPoints,
143
+ buildNotifications: nfBuilderOptions.buildNotifications,
144
+ cacheExternalArtifacts: nfBuilderOptions.cacheExternalArtifacts !== false,
145
+ }, createFederationCache(cachePath, new SourceFileCache(cachePath)));
146
+ checkForInvalidImports(Object.values(normalized.config.sharedMappings), "shared mappings");
147
+ checkForInvalidImports(Object.keys(normalized.config.shared), "externals");
148
+ const start = process.hrtime();
149
+ logger.measure(start, "To load the federation config.");
150
+ const externals = getExternals(normalized.config);
151
+ // MF side builder (M2.1) — replaces NF's adapter + buildForFederation/rebuildForFederation.
152
+ const mfBuilder = await createMfFederationBuilder(normalized.config, normalized.options, externals, {
153
+ builderOptions: {
154
+ ...ngBuilderOptions,
155
+ plugins: nfBuilderOptions.plugins,
156
+ instrumentForCoverage: nfBuilderOptions.instrumentForCoverage,
157
+ },
158
+ context,
159
+ });
160
+ const plugins = [
161
+ {
162
+ name: "externals",
163
+ setup(build) {
164
+ if (build.initialOptions.platform !== "node") {
165
+ build.initialOptions.external = externals.filter((e) => e !== "tslib");
166
+ }
167
+ },
168
+ },
169
+ // Inject custom esbuild plugins
170
+ ...(Array.isArray(nfBuilderOptions.plugins)
171
+ ? nfBuilderOptions.plugins
172
+ : []),
173
+ ];
174
+ const isLocalDevelopment = runViteServer && nfBuilderOptions.dev;
175
+ // Initialize SSE reloader only for local development
176
+ if (isLocalDevelopment && nfBuilderOptions.buildNotifications?.enable) {
177
+ federationBuildNotifier.initialize(nfBuilderOptions.buildNotifications.endpoint);
178
+ }
179
+ const middleware = [
180
+ ...(isLocalDevelopment
181
+ ? [
182
+ federationBuildNotifier.createEventMiddleware((req) => removeBaseHref(req, ngBuilderOptions.baseHref)),
183
+ ]
184
+ : []),
185
+ (req, res, next) => {
186
+ const rawUrl = removeBaseHref(req, ngBuilderOptions.baseHref);
187
+ const url = new URL(rawUrl || "/", "http://localhost").pathname;
188
+ const fileName = path.join(normalized.options.workspaceRoot, devServerOutputPath, url);
189
+ const exists = fs.existsSync(fileName);
190
+ if (url !== "/" && url !== "" && exists) {
191
+ const lookup = mrmime.lookup;
192
+ const mimeType = lookup(path.extname(fileName)) || "text/javascript";
193
+ const rawBody = fs.readFileSync(fileName, "utf-8");
194
+ // TODO: Evaluate need for debug infos
195
+ // const body = addDebugInformation(url, rawBody);
196
+ const body = rawBody;
197
+ res.writeHead(200, {
198
+ "Content-Type": mimeType,
199
+ "Access-Control-Allow-Origin": "*",
200
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
201
+ "Access-Control-Allow-Headers": "Content-Type",
202
+ });
203
+ res.end(body);
204
+ }
205
+ else {
206
+ next();
207
+ }
208
+ },
209
+ ];
210
+ let first = true;
211
+ const nfWatcher = watch
212
+ ? createNfWatcher()
213
+ : undefined;
214
+ if (nfWatcher) {
215
+ nfWatcher.addPaths(path.dirname(path.resolve(context.workspaceRoot, federationTsConfig)));
216
+ }
217
+ if (fs.existsSync(normalized.options.outputPath)) {
218
+ fs.rmSync(normalized.options.outputPath, { recursive: true });
219
+ }
220
+ if (!fs.existsSync(normalized.options.outputPath)) {
221
+ fs.mkdirSync(normalized.options.outputPath, { recursive: true });
222
+ }
223
+ let federationResult;
224
+ try {
225
+ federationResult = await mfBuilder.build();
226
+ }
227
+ catch (e) {
228
+ logger.error(e?.message ?? "Building the artifacts failed");
229
+ process.exit(1);
230
+ }
231
+ // Dispose the finished federation context so its compiler-plugin onDispose resets
232
+ // Angular's shared TS compilation state before the app build (#47); watch reuses it.
233
+ if (!watch) {
234
+ await mfBuilder.dispose().catch(() => undefined);
235
+ }
236
+ if (nfWatcher) {
237
+ syncNfFileWatcher(nfWatcher, normalized.options.federationCache.bundlerCache);
238
+ }
239
+ const hasLocales = i18n?.locales && Object.keys(i18n.locales).length > 0;
240
+ if (hasLocales && localeFilter) {
241
+ const start = process.hrtime();
242
+ translateFederationArtifacts(i18n, localeFilter, outputOptions.base, toFederationInfoForI18n(federationResult));
243
+ logger.measure(start, "To translate the artifacts.");
244
+ }
245
+ ngBuilderOptions.deleteOutputPath = false;
246
+ const appBuilderName = "@angular/build:application";
247
+ const builderRun = runViteServer
248
+ ? serveWithVite(serverOptions, appBuilderName, createInternalAngularBuilder(externals, {
249
+ instrumentForCoverage: nfBuilderOptions.instrumentForCoverage,
250
+ }), context, nfBuilderOptions.skipHtmlTransform
251
+ ? {}
252
+ : { indexHtml: transformIndexHtml(nfBuilderOptions) }, {
253
+ buildPlugins: plugins,
254
+ middleware,
255
+ })
256
+ : buildApplication({
257
+ ...ngBuilderOptions,
258
+ ...(nfBuilderOptions.instrumentForCoverage
259
+ ? { instrumentForCoverage: nfBuilderOptions.instrumentForCoverage }
260
+ : {}),
261
+ }, context, {
262
+ codePlugins: plugins,
263
+ indexHtmlTransformer: transformIndexHtml(nfBuilderOptions),
264
+ });
265
+ const rebuildQueue = new RebuildQueue();
266
+ const builderIterator = builderRun[Symbol.asyncIterator]();
267
+ let ngBuildStatus = { success: false };
268
+ try {
269
+ let buildResult = await builderIterator.next();
270
+ while (!buildResult.done) {
271
+ if (buildResult.value)
272
+ ngBuildStatus = buildResult.value;
273
+ if (!ngBuildStatus.success) {
274
+ logger.warn("Skipping federation artifacts because Angular build failed.");
275
+ buildResult = await builderIterator.next();
276
+ }
277
+ else if (!first && watch) {
278
+ const nextOutputPromise = builderIterator.next();
279
+ const trackResult = await rebuildQueue.track(async (signal) => {
280
+ try {
281
+ if (signal?.aborted) {
282
+ throw new AbortedError("Build canceled before starting");
283
+ }
284
+ await new Promise((resolve, reject) => {
285
+ const timeout = setTimeout(resolve, Math.max(10, nfBuilderOptions.rebuildDelay));
286
+ if (signal) {
287
+ const abortHandler = () => {
288
+ clearTimeout(timeout);
289
+ reject(new AbortedError("[builder] During delay."));
290
+ };
291
+ signal.addEventListener("abort", abortHandler, {
292
+ once: true,
293
+ });
294
+ }
295
+ });
296
+ if (signal?.aborted) {
297
+ throw new AbortedError("[builder] Before federation build.");
298
+ }
299
+ // Invalidate only files that changed since the last rebuild, falling back to all
300
+ // source files when the buffer is empty (e.g. first watch rebuild). Unlike the
301
+ // remote builder, the buffer is cleared eagerly here because Angular's iterator —
302
+ // not this buffer — re-triggers the next rebuild.
303
+ const changedFiles = nfWatcher ? [...nfWatcher.get()] : [];
304
+ if (nfWatcher)
305
+ nfWatcher.clear();
306
+ federationResult = await mfBuilder.rebuild(changedFiles);
307
+ if (nfWatcher) {
308
+ syncNfFileWatcher(nfWatcher, normalized.options.federationCache.bundlerCache);
309
+ }
310
+ if (signal?.aborted) {
311
+ throw new AbortedError("[builder] After federation build.");
312
+ }
313
+ if (hasLocales && localeFilter) {
314
+ translateFederationArtifacts(i18n, localeFilter, outputOptions.base, toFederationInfoForI18n(federationResult));
315
+ }
316
+ if (signal?.aborted) {
317
+ throw new AbortedError("[builder] After federation translations.");
318
+ }
319
+ logger.info("Done!");
320
+ if (isLocalDevelopment) {
321
+ federationBuildNotifier.broadcastBuildCompletion();
322
+ }
323
+ return { success: true };
324
+ }
325
+ catch (error) {
326
+ if (error instanceof AbortedError) {
327
+ logger.verbose("Rebuild was canceled. Cancellation point: " + error?.message);
328
+ federationBuildNotifier.broadcastBuildCancellation();
329
+ return { success: false, cancelled: true };
330
+ }
331
+ logger.error("Federation rebuild failed!");
332
+ if (ngBuilderOptions.verbose)
333
+ console.error(error);
334
+ if (isLocalDevelopment) {
335
+ federationBuildNotifier.broadcastBuildError(error);
336
+ }
337
+ return { success: false };
338
+ }
339
+ }, nextOutputPromise);
340
+ // Same trackResult shape as the remote builder, plus the iterator pump:
341
+ // the 'interrupted' branch feeds Angular's next output back into the loop
342
+ // (the remote builder has no iterator, so it just loops on its watcher).
343
+ if (trackResult.type === "completed") {
344
+ if (!trackResult.result.cancelled) {
345
+ ngBuildStatus = { success: trackResult.result.success };
346
+ yield ngBuildStatus;
347
+ }
348
+ buildResult = await nextOutputPromise;
349
+ }
350
+ else {
351
+ buildResult = trackResult.value;
352
+ }
353
+ }
354
+ else {
355
+ buildResult = await builderIterator.next();
356
+ }
357
+ first = false;
358
+ }
359
+ }
360
+ finally {
361
+ rebuildQueue.dispose();
362
+ await mfBuilder.dispose();
363
+ await nfWatcher?.close();
364
+ if (isLocalDevelopment) {
365
+ federationBuildNotifier.stopEventServer();
366
+ }
367
+ }
368
+ yield ngBuildStatus;
369
+ }
370
+ /**
371
+ * Transitional bridge (M2.1): feed MF artifact files to NF's i18n translator,
372
+ * which reads `exposes[].outFileName`. The full MF i18n rework (per-locale
373
+ * `mf-manifest.json` + `remoteEntry.js`) is **M4.2**; until then this maps MF's
374
+ * written chunk basenames onto the minimal `FederationInfo` shape the translator
375
+ * consumes.
376
+ */
377
+ function toFederationInfoForI18n(mf) {
378
+ return {
379
+ name: mf.name,
380
+ shared: [],
381
+ exposes: mf.writtenFiles.map((f) => ({ outFileName: path.basename(f) })),
382
+ };
383
+ }
384
+ function removeBaseHref(req, baseHref) {
385
+ let url = req.url ?? "";
386
+ if (baseHref && url.startsWith(baseHref)) {
387
+ url = url.slice(baseHref.length);
388
+ }
389
+ return url;
390
+ }
391
+ function getLocaleFilter(options, runViteServer) {
392
+ let localize = options.localize || false;
393
+ if (runViteServer && Array.isArray(localize) && localize.length > 1) {
394
+ localize = false;
395
+ }
396
+ if (runViteServer && localize === true) {
397
+ localize = false;
398
+ }
399
+ return localize;
400
+ }
401
+ function inferConfigPath(tsConfig, workspaceRoot, federationConfigPath = "federation.config.mjs") {
402
+ const relProjectPath = path.dirname(tsConfig);
403
+ const mjsRelPath = path.join(relProjectPath, federationConfigPath);
404
+ if (fs.existsSync(path.resolve(workspaceRoot, mjsRelPath))) {
405
+ return mjsRelPath;
406
+ }
407
+ return path.join(relProjectPath, "federation.config.js");
408
+ }
409
+ function transformIndexHtml(nfOptions) {
410
+ return (content) => Promise.resolve(updateScriptTags(content, nfOptions));
411
+ }
412
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
413
+ export default createBuilder(runBuilder);
@@ -0,0 +1,70 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ type NextFunction = (error?: Error) => void;
3
+ type MiddlewareFunction = (req: IncomingMessage, res: ServerResponse, next: NextFunction) => void;
4
+ /**
5
+ * Manages Server-Sent Events for federation hot reload in local development
6
+ * Only active when running in development mode with dev server
7
+ */
8
+ declare class FederationBuildNotifier {
9
+ private connections;
10
+ private cleanupInterval;
11
+ private isActive;
12
+ private endpoint;
13
+ /**
14
+ * Initializes the SSE reloader for local development
15
+ */
16
+ initialize(endpoint: string): void;
17
+ /**
18
+ * Creates SSE middleware for federation events
19
+ */
20
+ createEventMiddleware(removeBaseHref: (req: IncomingMessage) => string): MiddlewareFunction;
21
+ /**
22
+ * Sets up a new SSE connection
23
+ */
24
+ private _setupSSEConnection;
25
+ /**
26
+ * Removes a connection from the pool
27
+ */
28
+ private _removeConnection;
29
+ /**
30
+ * Broadcasts an event to all connected clients
31
+ * Only works in local development mode
32
+ */
33
+ private _broadcastEvent;
34
+ /**
35
+ * Sends an event to a specific response stream
36
+ */
37
+ private _sendEvent;
38
+ /**
39
+ * Starts periodic cleanup of dead connections
40
+ */
41
+ private startCleanup;
42
+ /**
43
+ * Notifies about successful federation rebuild
44
+ */
45
+ broadcastBuildCompletion(): void;
46
+ /**
47
+ * Notifies about cancellation of a federation rebuild
48
+ */
49
+ broadcastBuildCancellation(): void;
50
+ /**
51
+ * Notifies about failed federation rebuild
52
+ */
53
+ broadcastBuildError(error: unknown): void;
54
+ /**
55
+ * Stops cleanup and closes all connections
56
+ * Should be called when development server stops
57
+ */
58
+ stopEventServer(): void;
59
+ /**
60
+ * Returns the number of active connections
61
+ */
62
+ get activeConnections(): number;
63
+ /**
64
+ * Returns whether the reloader is active
65
+ */
66
+ get isRunning(): boolean;
67
+ }
68
+ export declare const federationBuildNotifier: FederationBuildNotifier;
69
+ export {};
70
+ //# sourceMappingURL=federation-build-notifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-build-notifier.d.ts","sourceRoot":"","sources":["../../../../src/builders/build/federation-build-notifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAkB5D,KAAK,YAAY,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;AAC5C,KAAK,kBAAkB,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;AAElG;;;GAGG;AACH,cAAM,uBAAuB;IAC3B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAqB;IAErC;;OAEG;IACI,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYzC;;OAEG;IACI,qBAAqB,CAC1B,cAAc,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,GAC/C,kBAAkB;IAgBrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAyBvB;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;OAEG;IACH,OAAO,CAAC,YAAY;IAsBpB;;OAEG;IACI,wBAAwB,IAAI,IAAI;IAOvC;;OAEG;IACI,0BAA0B,IAAI,IAAI;IAOzC;;OAEG;IACI,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAQhD;;;OAGG;IACI,eAAe,IAAI,IAAI;IAuB9B;;OAEG;IACH,IAAW,iBAAiB,IAAI,MAAM,CAErC;IAED;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;CACF;AAGD,eAAO,MAAM,uBAAuB,yBAAgC,CAAC"}
@@ -0,0 +1,186 @@
1
+ import { BuildNotificationType } from '@softarc/native-federation';
2
+ import { logger } from '@softarc/native-federation/internal';
3
+ /**
4
+ * Manages Server-Sent Events for federation hot reload in local development
5
+ * Only active when running in development mode with dev server
6
+ */
7
+ class FederationBuildNotifier {
8
+ connections = [];
9
+ cleanupInterval = null;
10
+ isActive = false;
11
+ endpoint;
12
+ /**
13
+ * Initializes the SSE reloader for local development
14
+ */
15
+ initialize(endpoint) {
16
+ if (this.isActive) {
17
+ return;
18
+ }
19
+ this.endpoint = endpoint;
20
+ this.isActive = true;
21
+ this.startCleanup();
22
+ logger.info(`[Federation SSE] Local reloader initialized with endpoint ${this.endpoint}`);
23
+ }
24
+ /**
25
+ * Creates SSE middleware for federation events
26
+ */
27
+ createEventMiddleware(removeBaseHref) {
28
+ if (!this.isActive) {
29
+ return (_req, _res, next) => next();
30
+ }
31
+ return (req, res, next) => {
32
+ const url = removeBaseHref(req);
33
+ if (url !== this.endpoint) {
34
+ return next();
35
+ }
36
+ this._setupSSEConnection(req, res);
37
+ };
38
+ }
39
+ /**
40
+ * Sets up a new SSE connection
41
+ */
42
+ _setupSSEConnection(req, res) {
43
+ res.writeHead(200, {
44
+ 'Content-Type': 'text/event-stream',
45
+ 'Cache-Control': 'no-cache',
46
+ Connection: 'keep-alive',
47
+ 'Access-Control-Allow-Origin': '*',
48
+ 'Access-Control-Allow-Headers': 'Cache-Control',
49
+ });
50
+ // Send initial connection event
51
+ this._sendEvent(res, {
52
+ type: 'connected',
53
+ timestamp: Date.now(),
54
+ });
55
+ // Store connection
56
+ const connection = { response: res, request: req };
57
+ this.connections.push(connection);
58
+ // Handle disconnection
59
+ req.on('close', () => this._removeConnection(connection));
60
+ req.on('error', () => this._removeConnection(connection));
61
+ logger.info(`[Federation SSE] Client connected. Active connections: ${this.connections.length}`);
62
+ }
63
+ /**
64
+ * Removes a connection from the pool
65
+ */
66
+ _removeConnection(connection) {
67
+ const index = this.connections.indexOf(connection);
68
+ if (index !== -1) {
69
+ this.connections.splice(index, 1);
70
+ logger.info(`[Federation SSE] Client disconnected. Active connections: ${this.connections.length}`);
71
+ }
72
+ }
73
+ /**
74
+ * Broadcasts an event to all connected clients
75
+ * Only works in local development mode
76
+ */
77
+ _broadcastEvent(event) {
78
+ if (!this.isActive || this.connections.length === 0) {
79
+ return;
80
+ }
81
+ const deadConnections = [];
82
+ this.connections.forEach(connection => {
83
+ try {
84
+ this._sendEvent(connection.response, event);
85
+ }
86
+ catch {
87
+ deadConnections.push(connection);
88
+ }
89
+ });
90
+ // Remove dead connections
91
+ deadConnections.forEach(connection => this._removeConnection(connection));
92
+ if (this.connections.length > 0) {
93
+ logger.info(`[Federation SSE] Event '${event.type}' broadcast to ${this.connections.length} clients`);
94
+ }
95
+ }
96
+ /**
97
+ * Sends an event to a specific response stream
98
+ */
99
+ _sendEvent(res, event) {
100
+ const data = JSON.stringify(event);
101
+ res.write(`data: ${data}\n\n`);
102
+ }
103
+ /**
104
+ * Starts periodic cleanup of dead connections
105
+ */
106
+ startCleanup() {
107
+ if (this.cleanupInterval) {
108
+ return;
109
+ }
110
+ this.cleanupInterval = setInterval(() => {
111
+ const aliveBefore = this.connections.length;
112
+ this.connections = this.connections.filter(connection => !connection.response.destroyed &&
113
+ !connection.request.destroyed &&
114
+ connection.response.writable);
115
+ if (this.connections.length !== aliveBefore) {
116
+ logger.info(`[Federation SSE] Cleaned up ${aliveBefore - this.connections.length} dead connections`);
117
+ }
118
+ }, 30000); // Clean every 30 seconds
119
+ }
120
+ /**
121
+ * Notifies about successful federation rebuild
122
+ */
123
+ broadcastBuildCompletion() {
124
+ this._broadcastEvent({
125
+ type: BuildNotificationType.COMPLETED,
126
+ timestamp: Date.now(),
127
+ });
128
+ }
129
+ /**
130
+ * Notifies about cancellation of a federation rebuild
131
+ */
132
+ broadcastBuildCancellation() {
133
+ this._broadcastEvent({
134
+ type: BuildNotificationType.CANCELLED,
135
+ timestamp: Date.now(),
136
+ });
137
+ }
138
+ /**
139
+ * Notifies about failed federation rebuild
140
+ */
141
+ broadcastBuildError(error) {
142
+ this._broadcastEvent({
143
+ type: BuildNotificationType.ERROR,
144
+ timestamp: Date.now(),
145
+ error: error instanceof Error ? error.message : 'Unknown error',
146
+ });
147
+ }
148
+ /**
149
+ * Stops cleanup and closes all connections
150
+ * Should be called when development server stops
151
+ */
152
+ stopEventServer() {
153
+ if (!this.isActive) {
154
+ return;
155
+ }
156
+ if (this.cleanupInterval) {
157
+ clearInterval(this.cleanupInterval);
158
+ this.cleanupInterval = null;
159
+ }
160
+ this.connections.forEach(connection => {
161
+ try {
162
+ connection.response.end();
163
+ }
164
+ catch {
165
+ // Connection might already be closed
166
+ }
167
+ });
168
+ this.connections = [];
169
+ this.isActive = false;
170
+ logger.info('[Federation SSE] Local reloader disposed');
171
+ }
172
+ /**
173
+ * Returns the number of active connections
174
+ */
175
+ get activeConnections() {
176
+ return this.connections.length;
177
+ }
178
+ /**
179
+ * Returns whether the reloader is active
180
+ */
181
+ get isRunning() {
182
+ return this.isActive;
183
+ }
184
+ }
185
+ // Singleton instance for local development
186
+ export const federationBuildNotifier = new FederationBuildNotifier();