@nf-beta/angular 0.0.1 → 0.0.2
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.
- package/LICENSE +8 -0
- package/README.md +458 -0
- package/builders.json +10 -0
- package/collection.json +27 -0
- package/generators.json +12 -0
- package/migration-collection.json +13 -0
- package/package.json +18 -8
- package/src/builders/build/builder.d.ts +6 -0
- package/src/builders/build/builder.d.ts.map +1 -0
- package/src/builders/build/builder.js +348 -0
- package/src/builders/build/federation-build-notifier.d.ts +70 -0
- package/src/builders/build/federation-build-notifier.d.ts.map +1 -0
- package/src/builders/build/federation-build-notifier.js +186 -0
- package/src/builders/build/schema.d.ts +21 -0
- package/src/builders/build/schema.json +84 -0
- package/src/config.d.ts +3 -0
- package/src/config.d.ts.map +1 -0
- package/src/config.js +2 -0
- package/src/generators/native-federation/files/src/index.ts__template__ +1 -0
- package/src/generators/native-federation/generator.d.ts +4 -0
- package/src/generators/native-federation/generator.d.ts.map +1 -0
- package/src/generators/native-federation/generator.js +43 -0
- package/src/generators/native-federation/schema.d.ts +5 -0
- package/src/generators/native-federation/schema.json +29 -0
- package/src/index.d.ts +2 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +1 -0
- package/src/patch-angular-build.d.ts +2 -0
- package/src/patch-angular-build.d.ts.map +1 -0
- package/src/patch-angular-build.js +5 -0
- package/src/plugin/dev-externals-mixin.d.ts +3 -0
- package/src/plugin/dev-externals-mixin.d.ts.map +1 -0
- package/src/plugin/dev-externals-mixin.js +29 -0
- package/src/plugin/externals-skip-list.d.ts +3 -0
- package/src/plugin/externals-skip-list.d.ts.map +1 -0
- package/src/plugin/externals-skip-list.js +4 -0
- package/src/plugin/index.d.ts +4 -0
- package/src/plugin/index.d.ts.map +1 -0
- package/src/plugin/index.js +75 -0
- package/src/schematics/appbuilder/schema.d.ts +3 -0
- package/src/schematics/appbuilder/schema.json +17 -0
- package/src/schematics/appbuilder/schematic.d.ts +5 -0
- package/src/schematics/appbuilder/schematic.d.ts.map +1 -0
- package/src/schematics/appbuilder/schematic.js +83 -0
- package/src/schematics/init/files/federation.config.js__tmpl__ +33 -0
- package/src/schematics/init/schema.d.ts +6 -0
- package/src/schematics/init/schema.json +34 -0
- package/src/schematics/init/schematic.d.ts +7 -0
- package/src/schematics/init/schematic.d.ts.map +1 -0
- package/src/schematics/init/schematic.js +422 -0
- package/src/schematics/remove/schema.d.ts +3 -0
- package/src/schematics/remove/schema.json +17 -0
- package/src/schematics/remove/schematic.d.ts +5 -0
- package/src/schematics/remove/schematic.d.ts.map +1 -0
- package/src/schematics/remove/schematic.js +109 -0
- package/src/schematics/update18/schema.json +7 -0
- package/src/schematics/update18/schematic.d.ts +3 -0
- package/src/schematics/update18/schematic.d.ts.map +1 -0
- package/src/schematics/update18/schematic.js +7 -0
- package/src/tools/fstart-as-data-url.d.ts +2 -0
- package/src/tools/fstart-as-data-url.d.ts.map +1 -0
- package/src/tools/fstart-as-data-url.js +1 -0
- package/src/utils/angular-esbuild-adapter.d.ts +10 -0
- package/src/utils/angular-esbuild-adapter.d.ts.map +1 -0
- package/src/utils/angular-esbuild-adapter.js +289 -0
- package/src/utils/angular-locales.d.ts +19 -0
- package/src/utils/angular-locales.d.ts.map +1 -0
- package/src/utils/angular-locales.js +18 -0
- package/src/utils/create-awaitable-compiler-plugin.d.ts +6 -0
- package/src/utils/create-awaitable-compiler-plugin.d.ts.map +1 -0
- package/src/utils/create-awaitable-compiler-plugin.js +29 -0
- package/src/utils/create-compiler-options.d.ts +5 -0
- package/src/utils/create-compiler-options.d.ts.map +1 -0
- package/src/utils/create-compiler-options.js +42 -0
- package/src/utils/event-source.d.ts +10 -0
- package/src/utils/event-source.d.ts.map +1 -0
- package/src/utils/event-source.js +10 -0
- package/src/utils/i18n.d.ts +23 -0
- package/src/utils/i18n.d.ts.map +1 -0
- package/src/utils/i18n.js +61 -0
- package/src/utils/mem-resuts.d.ts +29 -0
- package/src/utils/mem-resuts.d.ts.map +1 -0
- package/src/utils/mem-resuts.js +50 -0
- package/src/utils/patch-angular-build.d.ts +4 -0
- package/src/utils/patch-angular-build.d.ts.map +1 -0
- package/src/utils/patch-angular-build.js +29 -0
- package/src/utils/rebuild-events.d.ts +8 -0
- package/src/utils/rebuild-events.d.ts.map +1 -0
- package/src/utils/rebuild-events.js +4 -0
- package/src/utils/shared-mappings-plugin.d.ts +4 -0
- package/src/utils/shared-mappings-plugin.d.ts.map +1 -0
- package/src/utils/shared-mappings-plugin.js +28 -0
- package/src/utils/updateIndexHtml.d.ts +5 -0
- package/src/utils/updateIndexHtml.d.ts.map +1 -0
- package/src/utils/updateIndexHtml.js +34 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as mrmime from 'mrmime';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { buildApplication } from '@angular/build';
|
|
5
|
+
import { buildApplicationInternal, serveWithVite } from '@angular/build/private';
|
|
6
|
+
import { createBuilder, targetFromTargetString, } from '@angular-devkit/architect';
|
|
7
|
+
import { normalizeOptions } from '@angular-devkit/build-angular/src/builders/dev-server/options.js';
|
|
8
|
+
import { buildForFederation, getExternals, loadFederationConfig, logger, setBuildAdapter, setLogLevel, RebuildQueue, AbortedError, } from '@nf-beta/core/build';
|
|
9
|
+
import { createAngularBuildAdapter, setMemResultHandler, } from '../../utils/angular-esbuild-adapter.js';
|
|
10
|
+
import { existsSync, mkdirSync, rmSync } from 'fs';
|
|
11
|
+
import { fstart } from '../../tools/fstart-as-data-url.js';
|
|
12
|
+
import { EsBuildResult, MemResults, NgCliAssetResult } from '../../utils/mem-resuts.js';
|
|
13
|
+
import { getI18nConfig, translateFederationArtefacts } from '../../utils/i18n.js';
|
|
14
|
+
import { RebuildHubs } from '../../utils/rebuild-events.js';
|
|
15
|
+
import { createSharedMappingsPlugin } from '../../utils/shared-mappings-plugin.js';
|
|
16
|
+
import { updateScriptTags } from '../../utils/updateIndexHtml.js';
|
|
17
|
+
import { federationBuildNotifier } from './federation-build-notifier.js';
|
|
18
|
+
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
19
|
+
process.stderr.write = function (chunk, encodingOrCallback, callback) {
|
|
20
|
+
const str = typeof chunk === 'string' ? chunk : chunk.toString();
|
|
21
|
+
if (str.includes('vite:import-analysis') && str.includes('es-module-shims.js')) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (typeof encodingOrCallback !== 'string') {
|
|
25
|
+
return originalWrite(chunk, encodingOrCallback);
|
|
26
|
+
}
|
|
27
|
+
return originalWrite(chunk, encodingOrCallback, callback);
|
|
28
|
+
};
|
|
29
|
+
function _buildApplication(options, context, pluginsOrExtensions) {
|
|
30
|
+
let extensions;
|
|
31
|
+
if (pluginsOrExtensions && Array.isArray(pluginsOrExtensions)) {
|
|
32
|
+
extensions = {
|
|
33
|
+
codePlugins: pluginsOrExtensions,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
extensions = pluginsOrExtensions;
|
|
38
|
+
}
|
|
39
|
+
return buildApplicationInternal(options, context, extensions);
|
|
40
|
+
}
|
|
41
|
+
export async function* runBuilder(nfOptions, context) {
|
|
42
|
+
let target = targetFromTargetString(nfOptions.target);
|
|
43
|
+
let targetOptions = (await context.getTargetOptions(target));
|
|
44
|
+
let builder = await context.getBuilderNameForTarget(target);
|
|
45
|
+
if (builder === '@angular-devkit/build-angular:browser-esbuild') {
|
|
46
|
+
logger.info('.: NATIVE FEDERATION - UPDATE NEEDED :.');
|
|
47
|
+
logger.info('');
|
|
48
|
+
logger.info("Since version 17.1, Native Federation uses Angular's");
|
|
49
|
+
logger.info('Application-Builder and its Dev-Server.');
|
|
50
|
+
logger.info('');
|
|
51
|
+
logger.info('If you are sill on Angular 17.0.x, please update to');
|
|
52
|
+
logger.info('Angular 17.1.x or downgrade to Native Federation 17.0.x.');
|
|
53
|
+
logger.info('');
|
|
54
|
+
logger.info('For working with Native Federation 17.1.x (recommented), ');
|
|
55
|
+
logger.info('please update your project config, e.g. in angular.json');
|
|
56
|
+
logger.info('');
|
|
57
|
+
logger.info('This command performs the needed update for default configs:');
|
|
58
|
+
logger.info('');
|
|
59
|
+
logger.info('\tng g @angular-architects/native-federation:appbuilder');
|
|
60
|
+
logger.info('');
|
|
61
|
+
logger.info('You need to run it once per application to migrate');
|
|
62
|
+
logger.info('Please find more information here: https://shorturl.at/gADJW');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Explicitly defined as devServer or if the target contains "serve"
|
|
67
|
+
*/
|
|
68
|
+
const runServer = typeof nfOptions.devServer !== 'undefined'
|
|
69
|
+
? !!nfOptions.devServer
|
|
70
|
+
: target.target.includes('serve');
|
|
71
|
+
let options = (await context.validateOptions(runServer
|
|
72
|
+
? {
|
|
73
|
+
...targetOptions,
|
|
74
|
+
port: nfOptions.port || targetOptions['port'],
|
|
75
|
+
}
|
|
76
|
+
: targetOptions, builder));
|
|
77
|
+
let serverOptions = null;
|
|
78
|
+
const write = true;
|
|
79
|
+
const watch = nfOptions.watch;
|
|
80
|
+
if (options['buildTarget']) {
|
|
81
|
+
serverOptions = await normalizeOptions(context, context.target.project, options);
|
|
82
|
+
target = targetFromTargetString(options['buildTarget']);
|
|
83
|
+
targetOptions = (await context.getTargetOptions(target));
|
|
84
|
+
builder = await context.getBuilderNameForTarget(target);
|
|
85
|
+
options = (await context.validateOptions(targetOptions, builder));
|
|
86
|
+
}
|
|
87
|
+
options.watch = watch;
|
|
88
|
+
if (nfOptions.baseHref) {
|
|
89
|
+
options.baseHref = nfOptions.baseHref;
|
|
90
|
+
}
|
|
91
|
+
if (nfOptions.outputPath) {
|
|
92
|
+
options.outputPath = nfOptions.outputPath;
|
|
93
|
+
}
|
|
94
|
+
const rebuildEvents = new RebuildHubs();
|
|
95
|
+
const adapter = createAngularBuildAdapter(options, context, rebuildEvents);
|
|
96
|
+
setBuildAdapter(adapter);
|
|
97
|
+
setLogLevel(options.verbose ? 'verbose' : 'info');
|
|
98
|
+
if (!options.outputPath) {
|
|
99
|
+
options.outputPath = `dist/${context.target.project}`;
|
|
100
|
+
}
|
|
101
|
+
const outputPath = options.outputPath;
|
|
102
|
+
const outputOptions = {
|
|
103
|
+
browser: 'browser',
|
|
104
|
+
server: 'server',
|
|
105
|
+
media: 'media',
|
|
106
|
+
...(typeof outputPath === 'string' ? undefined : outputPath),
|
|
107
|
+
base: typeof outputPath === 'string' ? outputPath : outputPath.base,
|
|
108
|
+
};
|
|
109
|
+
const i18n = await getI18nConfig(context);
|
|
110
|
+
const localeFilter = getLocaleFilter(options, runServer);
|
|
111
|
+
const sourceLocaleSegment = typeof i18n?.sourceLocale === 'string'
|
|
112
|
+
? i18n.sourceLocale
|
|
113
|
+
: i18n?.sourceLocale?.subPath || i18n?.sourceLocale?.code || '';
|
|
114
|
+
const browserOutputPath = path.join(outputOptions.base, outputOptions.browser, options.localize ? sourceLocaleSegment : '');
|
|
115
|
+
const differentDevServerOutputPath = Array.isArray(localeFilter) && localeFilter.length === 1;
|
|
116
|
+
const devServerOutputPath = !differentDevServerOutputPath
|
|
117
|
+
? browserOutputPath
|
|
118
|
+
: path.join(outputOptions.base, outputOptions.browser, localeFilter[0]);
|
|
119
|
+
const entryPoint = path.join(path.dirname(options.tsConfig), 'src/main.ts');
|
|
120
|
+
const fedOptions = {
|
|
121
|
+
workspaceRoot: context.workspaceRoot,
|
|
122
|
+
outputPath: browserOutputPath,
|
|
123
|
+
federationConfig: inferConfigPath(options.tsConfig),
|
|
124
|
+
tsConfig: options.tsConfig,
|
|
125
|
+
verbose: options.verbose,
|
|
126
|
+
watch: false, // options.watch,
|
|
127
|
+
dev: !!nfOptions.dev,
|
|
128
|
+
entryPoint,
|
|
129
|
+
buildNotifications: nfOptions.buildNotifications,
|
|
130
|
+
cacheExternalArtifacts: nfOptions.cacheExternalArtifacts,
|
|
131
|
+
};
|
|
132
|
+
const activateSsr = nfOptions.ssr && !nfOptions.dev;
|
|
133
|
+
const start = process.hrtime();
|
|
134
|
+
const config = await loadFederationConfig(fedOptions);
|
|
135
|
+
logger.measure(start, 'To load the federation config.');
|
|
136
|
+
const externals = getExternals(config);
|
|
137
|
+
const plugins = [
|
|
138
|
+
createSharedMappingsPlugin(config.sharedMappings),
|
|
139
|
+
{
|
|
140
|
+
name: 'externals',
|
|
141
|
+
setup(build) {
|
|
142
|
+
if (!activateSsr && build.initialOptions.platform !== 'node') {
|
|
143
|
+
build.initialOptions.external = externals.filter(e => e !== 'tslib');
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
// SSR build fails when externals are provided via the plugin
|
|
149
|
+
if (activateSsr) {
|
|
150
|
+
options.externalDependencies = externals;
|
|
151
|
+
}
|
|
152
|
+
const isLocalDevelopment = runServer && nfOptions.dev;
|
|
153
|
+
// Initialize SSE reloader only for local development
|
|
154
|
+
if (isLocalDevelopment && nfOptions.buildNotifications?.enable) {
|
|
155
|
+
federationBuildNotifier.initialize(nfOptions.buildNotifications.endpoint);
|
|
156
|
+
}
|
|
157
|
+
const middleware = [
|
|
158
|
+
...(isLocalDevelopment
|
|
159
|
+
? [
|
|
160
|
+
federationBuildNotifier.createEventMiddleware(req => removeBaseHref(req, options.baseHref)),
|
|
161
|
+
]
|
|
162
|
+
: []),
|
|
163
|
+
(req, res, next) => {
|
|
164
|
+
const url = removeBaseHref(req, options.baseHref);
|
|
165
|
+
const fileName = path.join(fedOptions.workspaceRoot, devServerOutputPath, url);
|
|
166
|
+
const exists = fs.existsSync(fileName);
|
|
167
|
+
if (url !== '/' && url !== '' && exists) {
|
|
168
|
+
const lookup = mrmime.lookup;
|
|
169
|
+
const mimeType = lookup(path.extname(fileName)) || 'text/javascript';
|
|
170
|
+
const rawBody = fs.readFileSync(fileName, 'utf-8');
|
|
171
|
+
// TODO: Evaluate need for debug infos
|
|
172
|
+
// const body = addDebugInformation(url, rawBody);
|
|
173
|
+
const body = rawBody;
|
|
174
|
+
res.writeHead(200, {
|
|
175
|
+
'Content-Type': mimeType,
|
|
176
|
+
'Access-Control-Allow-Origin': '*',
|
|
177
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
|
|
178
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
179
|
+
});
|
|
180
|
+
res.end(body);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
next();
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
const memResults = new MemResults();
|
|
188
|
+
let first = true;
|
|
189
|
+
let lastResult;
|
|
190
|
+
if (existsSync(fedOptions.outputPath)) {
|
|
191
|
+
rmSync(fedOptions.outputPath, { recursive: true });
|
|
192
|
+
}
|
|
193
|
+
if (!existsSync(fedOptions.outputPath)) {
|
|
194
|
+
mkdirSync(fedOptions.outputPath, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
if (!write) {
|
|
197
|
+
// todo: Hardcoded disabled?
|
|
198
|
+
setMemResultHandler((outFiles, outDir) => {
|
|
199
|
+
const fullOutDir = outDir ? path.join(fedOptions.workspaceRoot, outDir) : undefined;
|
|
200
|
+
memResults.add(outFiles.map(f => new EsBuildResult(f, fullOutDir)));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
let federationResult;
|
|
204
|
+
try {
|
|
205
|
+
const start = process.hrtime();
|
|
206
|
+
federationResult = await buildForFederation(config, fedOptions, externals);
|
|
207
|
+
logger.measure(start, 'To build the artifacts.');
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
logger.error(e?.message ?? 'Building the artifacts failed');
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
if (activateSsr) {
|
|
214
|
+
writeFstartScript(fedOptions);
|
|
215
|
+
}
|
|
216
|
+
const hasLocales = i18n?.locales && Object.keys(i18n.locales).length > 0;
|
|
217
|
+
if (hasLocales && localeFilter) {
|
|
218
|
+
const start = process.hrtime();
|
|
219
|
+
translateFederationArtefacts(i18n, localeFilter, outputOptions.base, federationResult);
|
|
220
|
+
logger.measure(start, 'To translate the artifacts.');
|
|
221
|
+
}
|
|
222
|
+
options.deleteOutputPath = false;
|
|
223
|
+
const appBuilderName = '@angular/build:application';
|
|
224
|
+
const builderRun = runServer
|
|
225
|
+
? serveWithVite(serverOptions, appBuilderName, _buildApplication, context, nfOptions.skipHtmlTransform ? {} : { indexHtml: transformIndexHtml(nfOptions) }, {
|
|
226
|
+
buildPlugins: plugins,
|
|
227
|
+
middleware,
|
|
228
|
+
})
|
|
229
|
+
: buildApplication(options, context, {
|
|
230
|
+
codePlugins: plugins,
|
|
231
|
+
indexHtmlTransformer: transformIndexHtml(nfOptions),
|
|
232
|
+
});
|
|
233
|
+
const rebuildQueue = new RebuildQueue();
|
|
234
|
+
try {
|
|
235
|
+
for await (const output of builderRun) {
|
|
236
|
+
lastResult = output;
|
|
237
|
+
if (!write && output['outputFiles']) {
|
|
238
|
+
memResults.add(output['outputFiles'].map((file) => new EsBuildResult(file)));
|
|
239
|
+
}
|
|
240
|
+
if (!write && output['assetFiles']) {
|
|
241
|
+
memResults.add(output['assetFiles'].map((file) => new NgCliAssetResult(file)));
|
|
242
|
+
}
|
|
243
|
+
// if (write && !runServer && !nfOptions.skipHtmlTransform) {
|
|
244
|
+
// updateIndexHtml(fedOptions, nfOptions);
|
|
245
|
+
// }
|
|
246
|
+
// if (!runServer) {
|
|
247
|
+
// yield output;
|
|
248
|
+
// }
|
|
249
|
+
if (!first && (nfOptions.dev || watch)) {
|
|
250
|
+
rebuildQueue
|
|
251
|
+
.enqueue(async (signal) => {
|
|
252
|
+
if (signal?.aborted) {
|
|
253
|
+
throw new AbortedError('Build canceled before starting');
|
|
254
|
+
}
|
|
255
|
+
await new Promise((resolve, reject) => {
|
|
256
|
+
const timeout = setTimeout(resolve, Math.max(10, nfOptions.rebuildDelay));
|
|
257
|
+
if (signal) {
|
|
258
|
+
const abortHandler = () => {
|
|
259
|
+
clearTimeout(timeout);
|
|
260
|
+
reject(new AbortedError('[builder] During delay.'));
|
|
261
|
+
};
|
|
262
|
+
signal.addEventListener('abort', abortHandler, { once: true });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
if (signal?.aborted) {
|
|
266
|
+
throw new AbortedError('[builder] Before federation build.');
|
|
267
|
+
}
|
|
268
|
+
const start = process.hrtime();
|
|
269
|
+
federationResult = await buildForFederation(config, fedOptions, externals, {
|
|
270
|
+
skipMappingsAndExposed: false,
|
|
271
|
+
skipShared: true,
|
|
272
|
+
signal,
|
|
273
|
+
});
|
|
274
|
+
if (signal?.aborted) {
|
|
275
|
+
throw new AbortedError('[builder] After federation build.');
|
|
276
|
+
}
|
|
277
|
+
if (hasLocales && localeFilter) {
|
|
278
|
+
translateFederationArtefacts(i18n, localeFilter, outputOptions.base, federationResult);
|
|
279
|
+
}
|
|
280
|
+
if (signal?.aborted) {
|
|
281
|
+
throw new AbortedError('[builder] After federation translations.');
|
|
282
|
+
}
|
|
283
|
+
logger.info('Done!');
|
|
284
|
+
if (isLocalDevelopment) {
|
|
285
|
+
federationBuildNotifier.broadcastBuildCompletion();
|
|
286
|
+
}
|
|
287
|
+
logger.measure(start, 'To rebuild the federation artifacts.');
|
|
288
|
+
})
|
|
289
|
+
.catch(error => {
|
|
290
|
+
if (error instanceof AbortedError) {
|
|
291
|
+
logger.verbose('Rebuild was canceled. Cancellation point: ' + error?.message);
|
|
292
|
+
federationBuildNotifier.broadcastBuildCancellation();
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
logger.error('Federation rebuild failed!');
|
|
296
|
+
if (options.verbose)
|
|
297
|
+
console.error(error);
|
|
298
|
+
if (isLocalDevelopment) {
|
|
299
|
+
federationBuildNotifier.broadcastBuildError(error);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
first = false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
rebuildQueue.dispose();
|
|
309
|
+
if (isLocalDevelopment) {
|
|
310
|
+
federationBuildNotifier.stopEventServer();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
yield lastResult || { success: false };
|
|
314
|
+
}
|
|
315
|
+
function removeBaseHref(req, baseHref) {
|
|
316
|
+
let url = req.url ?? '';
|
|
317
|
+
if (baseHref && url.startsWith(baseHref)) {
|
|
318
|
+
url = url.substr(baseHref.length);
|
|
319
|
+
}
|
|
320
|
+
return url;
|
|
321
|
+
}
|
|
322
|
+
function writeFstartScript(fedOptions) {
|
|
323
|
+
const serverOutpath = path.join(fedOptions.outputPath, '../server');
|
|
324
|
+
const fstartPath = path.join(serverOutpath, 'fstart.mjs');
|
|
325
|
+
const buffer = Buffer.from(fstart, 'base64');
|
|
326
|
+
fs.mkdirSync(serverOutpath, { recursive: true });
|
|
327
|
+
fs.writeFileSync(fstartPath, buffer, 'utf-8');
|
|
328
|
+
}
|
|
329
|
+
function getLocaleFilter(options, runServer) {
|
|
330
|
+
let localize = options.localize || false;
|
|
331
|
+
if (runServer && Array.isArray(localize) && localize.length > 1) {
|
|
332
|
+
localize = false;
|
|
333
|
+
}
|
|
334
|
+
if (runServer && localize === true) {
|
|
335
|
+
localize = false;
|
|
336
|
+
}
|
|
337
|
+
return localize;
|
|
338
|
+
}
|
|
339
|
+
function inferConfigPath(tsConfig) {
|
|
340
|
+
const relProjectPath = path.dirname(tsConfig);
|
|
341
|
+
const relConfigPath = path.join(relProjectPath, 'federation.config.js');
|
|
342
|
+
return relConfigPath;
|
|
343
|
+
}
|
|
344
|
+
function transformIndexHtml(nfOptions) {
|
|
345
|
+
return (content) => Promise.resolve(updateScriptTags(content, nfOptions));
|
|
346
|
+
}
|
|
347
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
348
|
+
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
|
+
export 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,qBAAa,uBAAuB;IAClC,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 '@nf-beta/runtime';
|
|
2
|
+
import { logger } from '@nf-beta/core/build';
|
|
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
|
+
export 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();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { JsonObject } from '@angular-devkit/core';
|
|
2
|
+
import type { BuildNotificationOptions } from '@nf-beta/runtime';
|
|
3
|
+
import type { ESMSInitOptions } from 'es-module-shims';
|
|
4
|
+
|
|
5
|
+
export interface NfBuilderSchema extends JsonObject {
|
|
6
|
+
target: string;
|
|
7
|
+
dev: boolean;
|
|
8
|
+
port: number;
|
|
9
|
+
open: boolean;
|
|
10
|
+
rebuildDelay: number;
|
|
11
|
+
buildNotifications?: BuildNotificationOptions;
|
|
12
|
+
shell: string;
|
|
13
|
+
watch: boolean;
|
|
14
|
+
skipHtmlTransform: boolean;
|
|
15
|
+
esmsInitOptions: ESMSInitOptions;
|
|
16
|
+
baseHref?: string;
|
|
17
|
+
outputPath?: string;
|
|
18
|
+
ssr: boolean;
|
|
19
|
+
devServer?: boolean;
|
|
20
|
+
cacheExternalArtifacts?: boolean;
|
|
21
|
+
} // eslint-disable-line
|