@edynamix/mf-isolation 0.0.23 → 0.0.24
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/README.md +33 -0
- package/dist/angular-platform.d.ts +12 -0
- package/dist/angular-platform.d.ts.map +1 -0
- package/dist/angular-platform.js +56 -0
- package/dist/angular-platform.js.map +1 -0
- package/dist/vite.d.ts +10 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +24 -0
- package/dist/vite.js.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -178,6 +178,38 @@ Provide the runtime `MF_OVERLAY_ROOT` value through the existing platform/bootst
|
|
|
178
178
|
|
|
179
179
|
The Angular entry point requires `@angular/core`, `@angular/common`, and `@angular/cdk` 19.x.
|
|
180
180
|
|
|
181
|
+
## Sharing Angular between modules
|
|
182
|
+
|
|
183
|
+
Without sharing, every hosted module ships and starts its own copy of Angular. With `createMfAngularShared()` from `@edynamix/mf-isolation/vite`, modules on the same versions load Angular, the CDK, Material and RxJS once per page:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { federation } from '@module-federation/vite';
|
|
187
|
+
import { createMfAngularShared } from '@edynamix/mf-isolation/vite';
|
|
188
|
+
import packageJson from './package.json' with { type: 'json' };
|
|
189
|
+
|
|
190
|
+
federation({
|
|
191
|
+
name: 'autopoint',
|
|
192
|
+
filename: 'remoteEntry.js',
|
|
193
|
+
exposes: { './lifecycle': './src/lifecycle.ts' },
|
|
194
|
+
shared: createMfAngularShared(packageJson.dependencies),
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
It shares every `@angular/*` dependency, each with all its subpaths, plus `rxjs` and `rxjs/operators`, at the module's own version. The versions must be exact (`19.2.25`, not `^19.2.25`). A module on a different version keeps its own copy instead of mixing two Angular versions on one page.
|
|
199
|
+
|
|
200
|
+
A shared Angular means one Angular platform per page. A module bootstrapped with `createApplication()` reuses that platform already. A module that bootstraps an NgModule replaces `platformBrowserDynamic(providers)` with `createHostedAngularPlatform(providers)`:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { createHostedAngularPlatform } from '@edynamix/mf-isolation/angular-platform';
|
|
204
|
+
|
|
205
|
+
const platform = createHostedAngularPlatform([{ provide: APP_BASE_HREF, useValue: baseHref }]);
|
|
206
|
+
const moduleRef = await platform.bootstrapModule(AppModule);
|
|
207
|
+
// on unmount
|
|
208
|
+
platform.destroy();
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Its providers reach only the module's own NgModules, and `destroy()` destroys only them; the shared platform and the other modules keep running. Calling `platformBrowserDynamic(providers)` instead would hand the module another module's platform without its providers, and its `destroy()` would stop every module on the page. The entry point requires `@angular/platform-browser-dynamic` 19.x.
|
|
212
|
+
|
|
181
213
|
## Host-owned Zone.js
|
|
182
214
|
|
|
183
215
|
Standalone Angular applications load their own Zone.js runtime as usual:
|
|
@@ -207,5 +239,6 @@ export const mount = async (host: HTMLElement, context: IShellContext): Promise<
|
|
|
207
239
|
- `@edynamix/mf-isolation`
|
|
208
240
|
- `@edynamix/mf-isolation/angular`
|
|
209
241
|
- `@edynamix/mf-isolation/angular-runtime`
|
|
242
|
+
- `@edynamix/mf-isolation/angular-platform`
|
|
210
243
|
- `@edynamix/mf-isolation/host-contract`
|
|
211
244
|
- `@edynamix/mf-isolation/postcss`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PlatformRef, type StaticProvider } from '@angular/core';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces `platformBrowserDynamic(providers)` in a hosted module that bootstraps an NgModule.
|
|
4
|
+
*
|
|
5
|
+
* When the modules on a page share one Angular, the page has one Angular platform: a second
|
|
6
|
+
* `platformBrowserDynamic(providers)` call returns the first module's platform and drops its own providers, and a
|
|
7
|
+
* module's `platform.destroy()` would destroy every other module. This returns a platform for the module alone. The
|
|
8
|
+
* module's providers reach only its own NgModules, `destroy()` destroys only them, and the shared platform (created
|
|
9
|
+
* here, or reused from a module that started first, including one bootstrapped with `createApplication`) stays up.
|
|
10
|
+
*/
|
|
11
|
+
export declare const createHostedAngularPlatform: (providers?: StaticProvider[]) => PlatformRef;
|
|
12
|
+
//# sourceMappingURL=angular-platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angular-platform.d.ts","sourceRoot":"","sources":["../src/angular-platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,WAAW,EAAE,KAAK,cAAc,EAAa,MAAM,eAAe,CAAC;AAsDrH;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,GAAI,YAAW,cAAc,EAAO,KAAG,WAG9E,CAAC","sourcesContent":["import { getPlatform, Injector, type NgModuleRef, PlatformRef, type StaticProvider, type Type } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\n// PlatformRef takes its injector through an internal constructor, which the public typings leave out.\nconst PlatformRefWithInjector = PlatformRef as unknown as new (injector: Injector) => PlatformRef;\n\ntype TBootstrapModuleOptions = Parameters<PlatformRef['bootstrapModule']>[1];\n\n// One module's platform on top of the page's single Angular platform: its NgModules see the module's own providers,\n// and destroying it destroys only those NgModules, never the shared platform the other modules run on.\nclass HostedModulePlatformRef extends PlatformRefWithInjector {\n private readonly hostedModules = new Set<NgModuleRef<unknown>>();\n private readonly hostedDestroyListeners: Array<() => void> = [];\n private hostedDestroyed = false;\n\n override async bootstrapModule<M>(moduleType: Type<M>, compilerOptions?: TBootstrapModuleOptions): Promise<NgModuleRef<M>> {\n if (this.hostedDestroyed) {\n throw new Error('The hosted Angular module platform has been destroyed.');\n }\n\n const moduleRef = await super.bootstrapModule(moduleType, compilerOptions);\n if (this.hostedDestroyed) {\n moduleRef.destroy();\n throw new Error('The hosted Angular module platform was destroyed while the module was bootstrapping.');\n }\n\n this.hostedModules.add(moduleRef);\n moduleRef.onDestroy(() => this.hostedModules.delete(moduleRef));\n return moduleRef;\n }\n\n override onDestroy(callback: () => void): void {\n this.hostedDestroyListeners.push(callback);\n }\n\n override get destroyed(): boolean {\n return this.hostedDestroyed;\n }\n\n override destroy(): void {\n if (this.hostedDestroyed) {\n return;\n }\n\n this.hostedDestroyed = true;\n for (const moduleRef of [...this.hostedModules]) {\n moduleRef.destroy();\n }\n for (const listener of this.hostedDestroyListeners.splice(0)) {\n listener();\n }\n }\n}\n\n/**\n * Replaces `platformBrowserDynamic(providers)` in a hosted module that bootstraps an NgModule.\n *\n * When the modules on a page share one Angular, the page has one Angular platform: a second\n * `platformBrowserDynamic(providers)` call returns the first module's platform and drops its own providers, and a\n * module's `platform.destroy()` would destroy every other module. This returns a platform for the module alone. The\n * module's providers reach only its own NgModules, `destroy()` destroys only them, and the shared platform (created\n * here, or reused from a module that started first, including one bootstrapped with `createApplication`) stays up.\n */\nexport const createHostedAngularPlatform = (providers: StaticProvider[] = []): PlatformRef => {\n const sharedPlatform = getPlatform() ?? platformBrowserDynamic();\n return new HostedModulePlatformRef(Injector.create({ providers, parent: sharedPlatform.injector, name: 'Hosted Angular module' }));\n};\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getPlatform, Injector, PlatformRef } from '@angular/core';
|
|
2
|
+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
|
3
|
+
// PlatformRef takes its injector through an internal constructor, which the public typings leave out.
|
|
4
|
+
const PlatformRefWithInjector = PlatformRef;
|
|
5
|
+
// One module's platform on top of the page's single Angular platform: its NgModules see the module's own providers,
|
|
6
|
+
// and destroying it destroys only those NgModules, never the shared platform the other modules run on.
|
|
7
|
+
class HostedModulePlatformRef extends PlatformRefWithInjector {
|
|
8
|
+
hostedModules = new Set();
|
|
9
|
+
hostedDestroyListeners = [];
|
|
10
|
+
hostedDestroyed = false;
|
|
11
|
+
async bootstrapModule(moduleType, compilerOptions) {
|
|
12
|
+
if (this.hostedDestroyed) {
|
|
13
|
+
throw new Error('The hosted Angular module platform has been destroyed.');
|
|
14
|
+
}
|
|
15
|
+
const moduleRef = await super.bootstrapModule(moduleType, compilerOptions);
|
|
16
|
+
if (this.hostedDestroyed) {
|
|
17
|
+
moduleRef.destroy();
|
|
18
|
+
throw new Error('The hosted Angular module platform was destroyed while the module was bootstrapping.');
|
|
19
|
+
}
|
|
20
|
+
this.hostedModules.add(moduleRef);
|
|
21
|
+
moduleRef.onDestroy(() => this.hostedModules.delete(moduleRef));
|
|
22
|
+
return moduleRef;
|
|
23
|
+
}
|
|
24
|
+
onDestroy(callback) {
|
|
25
|
+
this.hostedDestroyListeners.push(callback);
|
|
26
|
+
}
|
|
27
|
+
get destroyed() {
|
|
28
|
+
return this.hostedDestroyed;
|
|
29
|
+
}
|
|
30
|
+
destroy() {
|
|
31
|
+
if (this.hostedDestroyed) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.hostedDestroyed = true;
|
|
35
|
+
for (const moduleRef of [...this.hostedModules]) {
|
|
36
|
+
moduleRef.destroy();
|
|
37
|
+
}
|
|
38
|
+
for (const listener of this.hostedDestroyListeners.splice(0)) {
|
|
39
|
+
listener();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Replaces `platformBrowserDynamic(providers)` in a hosted module that bootstraps an NgModule.
|
|
45
|
+
*
|
|
46
|
+
* When the modules on a page share one Angular, the page has one Angular platform: a second
|
|
47
|
+
* `platformBrowserDynamic(providers)` call returns the first module's platform and drops its own providers, and a
|
|
48
|
+
* module's `platform.destroy()` would destroy every other module. This returns a platform for the module alone. The
|
|
49
|
+
* module's providers reach only its own NgModules, `destroy()` destroys only them, and the shared platform (created
|
|
50
|
+
* here, or reused from a module that started first, including one bootstrapped with `createApplication`) stays up.
|
|
51
|
+
*/
|
|
52
|
+
export const createHostedAngularPlatform = (providers = []) => {
|
|
53
|
+
const sharedPlatform = getPlatform() ?? platformBrowserDynamic();
|
|
54
|
+
return new HostedModulePlatformRef(Injector.create({ providers, parent: sharedPlatform.injector, name: 'Hosted Angular module' }));
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=angular-platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angular-platform.js","sourceRoot":"","sources":["../src/angular-platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAoB,WAAW,EAAkC,MAAM,eAAe,CAAC;AACrH,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,sGAAsG;AACtG,MAAM,uBAAuB,GAAG,WAAiE,CAAC;AAIlG,oHAAoH;AACpH,uGAAuG;AACvG,MAAM,uBAAwB,SAAQ,uBAAuB;IAC1C,aAAa,GAAG,IAAI,GAAG,EAAwB,CAAC;IAChD,sBAAsB,GAAsB,EAAE,CAAC;IACxD,eAAe,GAAG,KAAK,CAAC;IAEvB,KAAK,CAAC,eAAe,CAAI,UAAmB,EAAE,eAAyC;QAC9F,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAC3E,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,SAAS,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,sFAAsF,CAAC,CAAC;QAC1G,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAClC,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QAChE,OAAO,SAAS,CAAC;IACnB,CAAC;IAEQ,SAAS,CAAC,QAAoB;QACrC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAED,IAAa,SAAS;QACpB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAEQ,OAAO;QACd,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,KAAK,MAAM,SAAS,IAAI,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAChD,SAAS,CAAC,OAAO,EAAE,CAAC;QACtB,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,QAAQ,EAAE,CAAC;QACb,CAAC;IACH,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,YAA8B,EAAE,EAAe,EAAE;IAC3F,MAAM,cAAc,GAAG,WAAW,EAAE,IAAI,sBAAsB,EAAE,CAAC;IACjE,OAAO,IAAI,uBAAuB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;AACrI,CAAC,CAAC","sourcesContent":["import { getPlatform, Injector, type NgModuleRef, PlatformRef, type StaticProvider, type Type } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\n// PlatformRef takes its injector through an internal constructor, which the public typings leave out.\nconst PlatformRefWithInjector = PlatformRef as unknown as new (injector: Injector) => PlatformRef;\n\ntype TBootstrapModuleOptions = Parameters<PlatformRef['bootstrapModule']>[1];\n\n// One module's platform on top of the page's single Angular platform: its NgModules see the module's own providers,\n// and destroying it destroys only those NgModules, never the shared platform the other modules run on.\nclass HostedModulePlatformRef extends PlatformRefWithInjector {\n private readonly hostedModules = new Set<NgModuleRef<unknown>>();\n private readonly hostedDestroyListeners: Array<() => void> = [];\n private hostedDestroyed = false;\n\n override async bootstrapModule<M>(moduleType: Type<M>, compilerOptions?: TBootstrapModuleOptions): Promise<NgModuleRef<M>> {\n if (this.hostedDestroyed) {\n throw new Error('The hosted Angular module platform has been destroyed.');\n }\n\n const moduleRef = await super.bootstrapModule(moduleType, compilerOptions);\n if (this.hostedDestroyed) {\n moduleRef.destroy();\n throw new Error('The hosted Angular module platform was destroyed while the module was bootstrapping.');\n }\n\n this.hostedModules.add(moduleRef);\n moduleRef.onDestroy(() => this.hostedModules.delete(moduleRef));\n return moduleRef;\n }\n\n override onDestroy(callback: () => void): void {\n this.hostedDestroyListeners.push(callback);\n }\n\n override get destroyed(): boolean {\n return this.hostedDestroyed;\n }\n\n override destroy(): void {\n if (this.hostedDestroyed) {\n return;\n }\n\n this.hostedDestroyed = true;\n for (const moduleRef of [...this.hostedModules]) {\n moduleRef.destroy();\n }\n for (const listener of this.hostedDestroyListeners.splice(0)) {\n listener();\n }\n }\n}\n\n/**\n * Replaces `platformBrowserDynamic(providers)` in a hosted module that bootstraps an NgModule.\n *\n * When the modules on a page share one Angular, the page has one Angular platform: a second\n * `platformBrowserDynamic(providers)` call returns the first module's platform and drops its own providers, and a\n * module's `platform.destroy()` would destroy every other module. This returns a platform for the module alone. The\n * module's providers reach only its own NgModules, `destroy()` destroys only them, and the shared platform (created\n * here, or reused from a module that started first, including one bootstrapped with `createApplication`) stays up.\n */\nexport const createHostedAngularPlatform = (providers: StaticProvider[] = []): PlatformRef => {\n const sharedPlatform = getPlatform() ?? platformBrowserDynamic();\n return new HostedModulePlatformRef(Injector.create({ providers, parent: sharedPlatform.injector, name: 'Hosted Angular module' }));\n};\n"]}
|
package/dist/vite.d.ts
CHANGED
|
@@ -4,6 +4,16 @@ export interface IMfNavigationPluginOptions {
|
|
|
4
4
|
fileName?: string;
|
|
5
5
|
navigation: IMfNavigationDefinition;
|
|
6
6
|
}
|
|
7
|
+
export interface IMfSharedDependency {
|
|
8
|
+
requiredVersion: string;
|
|
9
|
+
singleton: false;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The Module Federation `shared` configuration of a hosted Angular module: every `@angular/*` package and RxJS among
|
|
13
|
+
* the module's dependencies, at the module's own exact version. Modules on the same versions load these once per page
|
|
14
|
+
* and share them; a module on another version keeps its own copy instead of mixing two Angular versions.
|
|
15
|
+
*/
|
|
16
|
+
export declare const createMfAngularShared: (dependencies: Readonly<Record<string, string>>) => Record<string, IMfSharedDependency>;
|
|
7
17
|
export declare const MF_ASSET_MANIFEST_CONTRACT_VERSION: 1;
|
|
8
18
|
export interface IMfAssetManifest {
|
|
9
19
|
assets: readonly string[];
|
package/dist/vite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAsB,KAAK,uBAAuB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAQ/G,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,uBAAuB,CAAC;CACrC;AAED,eAAO,MAAM,kCAAkC,EAAG,CAAU,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,eAAe,EAAE,OAAO,kCAAkC,CAAC;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAsBD,eAAO,MAAM,0BAA0B,GAAI,YAAY,uBAAuB,KAAG,QAAQ,CAAC,qBAAqB,CAa9G,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,YAAY,uBAAuB,KAAG,MAEnF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,0BAA8C,0BAA0B,KAAG,MA4B7G,CAAC;AAmEF,eAAO,MAAM,wBAAwB,GAAI,UAAU,gBAAgB,KAAG,MAAkD,CAAC;AAEzH,eAAO,MAAM,qBAAqB,GAAI,wBAAkD,6BAA6B,KAAG,MAqCvH,CAAC","sourcesContent":["import { createHash } from 'node:crypto';\nimport type { Plugin } from 'vite';\nimport { defineMfNavigation, type IMfNavigationDefinition, type IMfNavigationManifest } from './navigation.js';\n\nconst DEFAULT_FILE_NAME = 'navigation.json';\nconst DEFAULT_ASSET_FILE_NAME = 'module-assets.json';\nconst FILE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.json$/;\nconst MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst CONTENT_HASHED_ASSET_PATTERN = /(?:^|\\/)[^/]+-[a-zA-Z0-9_-]{8,}\\.[^/]+$/;\n\nexport interface IMfNavigationPluginOptions {\n fileName?: string;\n navigation: IMfNavigationDefinition;\n}\n\nexport const MF_ASSET_MANIFEST_CONTRACT_VERSION = 1 as const;\n\nexport interface IMfAssetManifest {\n assets: readonly string[];\n contractVersion: typeof MF_ASSET_MANIFEST_CONTRACT_VERSION;\n moduleId: string;\n revision: string;\n}\n\nexport interface IMfAssetManifestPluginOptions {\n fileName?: string;\n moduleId: string;\n}\n\ninterface IMfOutputAsset {\n fileName: string;\n source: string | Uint8Array;\n type: 'asset';\n}\n\ninterface IMfOutputChunk {\n code: string;\n fileName: string;\n imports: readonly string[];\n modules: Readonly<Record<string, unknown>>;\n referencedFiles?: readonly string[];\n type: 'chunk';\n viteMetadata?: { importedAssets?: ReadonlySet<string>; importedCss?: ReadonlySet<string> };\n}\n\ntype TMfOutputBundle = Readonly<Record<string, IMfOutputAsset | IMfOutputChunk>>;\n\nconst serializeDefinition = (definition: Readonly<IMfNavigationDefinition>): string => JSON.stringify(definition);\n\nexport const createMfNavigationManifest = (navigation: IMfNavigationDefinition): Readonly<IMfNavigationManifest> => {\n const definition = defineMfNavigation(navigation);\n const revision = createHash('sha256').update(serializeDefinition(definition)).digest('hex');\n\n return Object.freeze({\n applicationId: definition.applicationId,\n contractVersion: definition.contractVersion,\n items: definition.items,\n label: definition.label,\n landingPath: definition.landingPath,\n moduleId: definition.moduleId,\n revision,\n });\n};\n\nexport const serializeMfNavigationManifest = (navigation: IMfNavigationDefinition): string => {\n return `${JSON.stringify(createMfNavigationManifest(navigation), null, 2)}\\n`;\n};\n\nexport const mfNavigationPlugin = ({ fileName = DEFAULT_FILE_NAME, navigation }: IMfNavigationPluginOptions): Plugin => {\n if (!FILE_NAME_PATTERN.test(fileName)) {\n throw new Error('Microfrontend navigation fileName must be a safe JSON file name.');\n }\n\n const source = serializeMfNavigationManifest(navigation);\n const publicPath = `/${fileName}`;\n\n return {\n name: 'edynamix-mf-navigation',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n const requestPath = request.url?.split('?', 1)[0];\n if (requestPath !== publicPath) {\n next();\n return;\n }\n\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(source);\n });\n },\n generateBundle() {\n this.emitFile({ fileName, source, type: 'asset' });\n },\n };\n};\n\nconst isLifecycleModule = (moduleId: string): boolean => /(?:^|\\/)src\\/lifecycle(?:-impl)?\\.[cm]?[jt]sx?$/.test(moduleId.replaceAll('\\\\', '/'));\n\nconst addChunkDependencies = (bundle: TMfOutputBundle, fileName: string, assets: Set<string>): void => {\n if (assets.has(fileName)) {\n return;\n }\n\n const output = bundle[fileName];\n if (output?.type !== 'chunk') {\n return;\n }\n\n assets.add(fileName);\n for (const importedFileName of output.imports) {\n addChunkDependencies(bundle, importedFileName, assets);\n }\n\n const metadata = output.viteMetadata;\n for (const referencedFileName of [...(metadata?.importedAssets ?? []), ...(metadata?.importedCss ?? []), ...(output.referencedFiles ?? [])]) {\n if (bundle[referencedFileName]) {\n assets.add(referencedFileName);\n }\n }\n};\n\nconst outputBytes = (output: IMfOutputAsset | IMfOutputChunk): string | Uint8Array => (output.type === 'chunk' ? output.code : output.source);\n\nconst createMfAssetManifest = (moduleId: string, bundle: TMfOutputBundle): Readonly<IMfAssetManifest> => {\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');\n }\n\n const lifecycleChunks = Object.values(bundle).filter(\n (output): output is IMfOutputChunk => output.type === 'chunk' && Object.keys(output.modules).some(isLifecycleModule),\n );\n if (lifecycleChunks.length === 0) {\n throw new Error(`Microfrontend ${moduleId} did not emit a lifecycle chunk.`);\n }\n\n const discoveredAssets = new Set<string>();\n for (const chunk of lifecycleChunks) {\n addChunkDependencies(bundle, chunk.fileName, discoveredAssets);\n }\n const assets = [...discoveredAssets].filter((fileName) => CONTENT_HASHED_ASSET_PATTERN.test(fileName)).sort();\n if (assets.length === 0) {\n throw new Error(`Microfrontend ${moduleId} did not emit content-hashed lifecycle assets.`);\n }\n\n const revisionHash = createHash('sha256');\n for (const fileName of assets) {\n const output = bundle[fileName];\n if (!output) {\n throw new Error(`Microfrontend ${moduleId} asset ${fileName} is missing from the output bundle.`);\n }\n revisionHash.update(fileName).update('\\0').update(outputBytes(output)).update('\\0');\n }\n\n return Object.freeze({\n assets: Object.freeze(assets),\n contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,\n moduleId,\n revision: revisionHash.digest('hex'),\n });\n};\n\nexport const serializeMfAssetManifest = (manifest: IMfAssetManifest): string => `${JSON.stringify(manifest, null, 2)}\\n`;\n\nexport const mfAssetManifestPlugin = ({ fileName = DEFAULT_ASSET_FILE_NAME, moduleId }: IMfAssetManifestPluginOptions): Plugin => {\n if (!FILE_NAME_PATTERN.test(fileName)) {\n throw new Error('Microfrontend asset fileName must be a safe JSON file name.');\n }\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');\n }\n\n const developmentManifest = serializeMfAssetManifest({\n assets: [],\n contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,\n moduleId,\n revision: 'development',\n });\n const publicPath = `/${fileName}`;\n\n return {\n name: 'edynamix-mf-assets',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n if (request.url?.split('?', 1)[0] !== publicPath) {\n next();\n return;\n }\n\n response.statusCode = 200;\n response.setHeader('Access-Control-Allow-Origin', '*');\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(developmentManifest);\n });\n },\n generateBundle(_options, bundle) {\n const source = serializeMfAssetManifest(createMfAssetManifest(moduleId, bundle as unknown as TMfOutputBundle));\n this.emitFile({ fileName, source, type: 'asset' });\n },\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAsB,KAAK,uBAAuB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAQ/G,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,uBAAuB,CAAC;CACrC;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,KAAK,CAAC;CAClB;AASD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,cAAc,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAYxH,CAAC;AAEF,eAAO,MAAM,kCAAkC,EAAG,CAAU,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,eAAe,EAAE,OAAO,kCAAkC,CAAC;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAsBD,eAAO,MAAM,0BAA0B,GAAI,YAAY,uBAAuB,KAAG,QAAQ,CAAC,qBAAqB,CAa9G,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,YAAY,uBAAuB,KAAG,MAEnF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,0BAA8C,0BAA0B,KAAG,MA4B7G,CAAC;AAmEF,eAAO,MAAM,wBAAwB,GAAI,UAAU,gBAAgB,KAAG,MAAkD,CAAC;AAEzH,eAAO,MAAM,qBAAqB,GAAI,wBAAkD,6BAA6B,KAAG,MAqCvH,CAAC","sourcesContent":["import { createHash } from 'node:crypto';\nimport type { Plugin } from 'vite';\nimport { defineMfNavigation, type IMfNavigationDefinition, type IMfNavigationManifest } from './navigation.js';\n\nconst DEFAULT_FILE_NAME = 'navigation.json';\nconst DEFAULT_ASSET_FILE_NAME = 'module-assets.json';\nconst FILE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.json$/;\nconst MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst CONTENT_HASHED_ASSET_PATTERN = /(?:^|\\/)[^/]+-[a-zA-Z0-9_-]{8,}\\.[^/]+$/;\n\nexport interface IMfNavigationPluginOptions {\n fileName?: string;\n navigation: IMfNavigationDefinition;\n}\n\nexport interface IMfSharedDependency {\n requiredVersion: string;\n singleton: false;\n}\n\n// Angular's packages are flat ESM entry points that import each other by package name, so a whole package, every\n// subpath included, is shared. RxJS subpaths reach its internals by relative path: only its two public entry points are\n// shared, each complete, so a page never mixes RxJS internals from two modules' builds.\nconst SHARED_ANGULAR_SCOPE = '@angular/';\nconst SHARED_RXJS_ENTRY_POINTS = ['rxjs', 'rxjs/operators'] as const;\nconst EXACT_VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/;\n\n/**\n * The Module Federation `shared` configuration of a hosted Angular module: every `@angular/*` package and RxJS among\n * the module's dependencies, at the module's own exact version. Modules on the same versions load these once per page\n * and share them; a module on another version keeps its own copy instead of mixing two Angular versions.\n */\nexport const createMfAngularShared = (dependencies: Readonly<Record<string, string>>): Record<string, IMfSharedDependency> => {\n const shared: Record<string, IMfSharedDependency> = {};\n for (const [name, version] of Object.entries(dependencies).sort(([left], [right]) => left.localeCompare(right))) {\n const sharedKeys = name.startsWith(SHARED_ANGULAR_SCOPE) ? [`${name}/`] : name === 'rxjs' ? SHARED_RXJS_ENTRY_POINTS : [];\n if (sharedKeys.length > 0 && !EXACT_VERSION_PATTERN.test(version)) {\n throw new Error(`Shared dependency ${name} must be pinned to an exact version, not \"${version}\".`);\n }\n for (const sharedKey of sharedKeys) {\n shared[sharedKey] = { requiredVersion: version, singleton: false };\n }\n }\n return shared;\n};\n\nexport const MF_ASSET_MANIFEST_CONTRACT_VERSION = 1 as const;\n\nexport interface IMfAssetManifest {\n assets: readonly string[];\n contractVersion: typeof MF_ASSET_MANIFEST_CONTRACT_VERSION;\n moduleId: string;\n revision: string;\n}\n\nexport interface IMfAssetManifestPluginOptions {\n fileName?: string;\n moduleId: string;\n}\n\ninterface IMfOutputAsset {\n fileName: string;\n source: string | Uint8Array;\n type: 'asset';\n}\n\ninterface IMfOutputChunk {\n code: string;\n fileName: string;\n imports: readonly string[];\n modules: Readonly<Record<string, unknown>>;\n referencedFiles?: readonly string[];\n type: 'chunk';\n viteMetadata?: { importedAssets?: ReadonlySet<string>; importedCss?: ReadonlySet<string> };\n}\n\ntype TMfOutputBundle = Readonly<Record<string, IMfOutputAsset | IMfOutputChunk>>;\n\nconst serializeDefinition = (definition: Readonly<IMfNavigationDefinition>): string => JSON.stringify(definition);\n\nexport const createMfNavigationManifest = (navigation: IMfNavigationDefinition): Readonly<IMfNavigationManifest> => {\n const definition = defineMfNavigation(navigation);\n const revision = createHash('sha256').update(serializeDefinition(definition)).digest('hex');\n\n return Object.freeze({\n applicationId: definition.applicationId,\n contractVersion: definition.contractVersion,\n items: definition.items,\n label: definition.label,\n landingPath: definition.landingPath,\n moduleId: definition.moduleId,\n revision,\n });\n};\n\nexport const serializeMfNavigationManifest = (navigation: IMfNavigationDefinition): string => {\n return `${JSON.stringify(createMfNavigationManifest(navigation), null, 2)}\\n`;\n};\n\nexport const mfNavigationPlugin = ({ fileName = DEFAULT_FILE_NAME, navigation }: IMfNavigationPluginOptions): Plugin => {\n if (!FILE_NAME_PATTERN.test(fileName)) {\n throw new Error('Microfrontend navigation fileName must be a safe JSON file name.');\n }\n\n const source = serializeMfNavigationManifest(navigation);\n const publicPath = `/${fileName}`;\n\n return {\n name: 'edynamix-mf-navigation',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n const requestPath = request.url?.split('?', 1)[0];\n if (requestPath !== publicPath) {\n next();\n return;\n }\n\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(source);\n });\n },\n generateBundle() {\n this.emitFile({ fileName, source, type: 'asset' });\n },\n };\n};\n\nconst isLifecycleModule = (moduleId: string): boolean => /(?:^|\\/)src\\/lifecycle(?:-impl)?\\.[cm]?[jt]sx?$/.test(moduleId.replaceAll('\\\\', '/'));\n\nconst addChunkDependencies = (bundle: TMfOutputBundle, fileName: string, assets: Set<string>): void => {\n if (assets.has(fileName)) {\n return;\n }\n\n const output = bundle[fileName];\n if (output?.type !== 'chunk') {\n return;\n }\n\n assets.add(fileName);\n for (const importedFileName of output.imports) {\n addChunkDependencies(bundle, importedFileName, assets);\n }\n\n const metadata = output.viteMetadata;\n for (const referencedFileName of [...(metadata?.importedAssets ?? []), ...(metadata?.importedCss ?? []), ...(output.referencedFiles ?? [])]) {\n if (bundle[referencedFileName]) {\n assets.add(referencedFileName);\n }\n }\n};\n\nconst outputBytes = (output: IMfOutputAsset | IMfOutputChunk): string | Uint8Array => (output.type === 'chunk' ? output.code : output.source);\n\nconst createMfAssetManifest = (moduleId: string, bundle: TMfOutputBundle): Readonly<IMfAssetManifest> => {\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');\n }\n\n const lifecycleChunks = Object.values(bundle).filter(\n (output): output is IMfOutputChunk => output.type === 'chunk' && Object.keys(output.modules).some(isLifecycleModule),\n );\n if (lifecycleChunks.length === 0) {\n throw new Error(`Microfrontend ${moduleId} did not emit a lifecycle chunk.`);\n }\n\n const discoveredAssets = new Set<string>();\n for (const chunk of lifecycleChunks) {\n addChunkDependencies(bundle, chunk.fileName, discoveredAssets);\n }\n const assets = [...discoveredAssets].filter((fileName) => CONTENT_HASHED_ASSET_PATTERN.test(fileName)).sort();\n if (assets.length === 0) {\n throw new Error(`Microfrontend ${moduleId} did not emit content-hashed lifecycle assets.`);\n }\n\n const revisionHash = createHash('sha256');\n for (const fileName of assets) {\n const output = bundle[fileName];\n if (!output) {\n throw new Error(`Microfrontend ${moduleId} asset ${fileName} is missing from the output bundle.`);\n }\n revisionHash.update(fileName).update('\\0').update(outputBytes(output)).update('\\0');\n }\n\n return Object.freeze({\n assets: Object.freeze(assets),\n contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,\n moduleId,\n revision: revisionHash.digest('hex'),\n });\n};\n\nexport const serializeMfAssetManifest = (manifest: IMfAssetManifest): string => `${JSON.stringify(manifest, null, 2)}\\n`;\n\nexport const mfAssetManifestPlugin = ({ fileName = DEFAULT_ASSET_FILE_NAME, moduleId }: IMfAssetManifestPluginOptions): Plugin => {\n if (!FILE_NAME_PATTERN.test(fileName)) {\n throw new Error('Microfrontend asset fileName must be a safe JSON file name.');\n }\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');\n }\n\n const developmentManifest = serializeMfAssetManifest({\n assets: [],\n contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,\n moduleId,\n revision: 'development',\n });\n const publicPath = `/${fileName}`;\n\n return {\n name: 'edynamix-mf-assets',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n if (request.url?.split('?', 1)[0] !== publicPath) {\n next();\n return;\n }\n\n response.statusCode = 200;\n response.setHeader('Access-Control-Allow-Origin', '*');\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(developmentManifest);\n });\n },\n generateBundle(_options, bundle) {\n const source = serializeMfAssetManifest(createMfAssetManifest(moduleId, bundle as unknown as TMfOutputBundle));\n this.emitFile({ fileName, source, type: 'asset' });\n },\n };\n};\n"]}
|
package/dist/vite.js
CHANGED
|
@@ -5,6 +5,30 @@ const DEFAULT_ASSET_FILE_NAME = 'module-assets.json';
|
|
|
5
5
|
const FILE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\.json$/;
|
|
6
6
|
const MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
7
7
|
const CONTENT_HASHED_ASSET_PATTERN = /(?:^|\/)[^/]+-[a-zA-Z0-9_-]{8,}\.[^/]+$/;
|
|
8
|
+
// Angular's packages are flat ESM entry points that import each other by package name, so a whole package, every
|
|
9
|
+
// subpath included, is shared. RxJS subpaths reach its internals by relative path: only its two public entry points are
|
|
10
|
+
// shared, each complete, so a page never mixes RxJS internals from two modules' builds.
|
|
11
|
+
const SHARED_ANGULAR_SCOPE = '@angular/';
|
|
12
|
+
const SHARED_RXJS_ENTRY_POINTS = ['rxjs', 'rxjs/operators'];
|
|
13
|
+
const EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
14
|
+
/**
|
|
15
|
+
* The Module Federation `shared` configuration of a hosted Angular module: every `@angular/*` package and RxJS among
|
|
16
|
+
* the module's dependencies, at the module's own exact version. Modules on the same versions load these once per page
|
|
17
|
+
* and share them; a module on another version keeps its own copy instead of mixing two Angular versions.
|
|
18
|
+
*/
|
|
19
|
+
export const createMfAngularShared = (dependencies) => {
|
|
20
|
+
const shared = {};
|
|
21
|
+
for (const [name, version] of Object.entries(dependencies).sort(([left], [right]) => left.localeCompare(right))) {
|
|
22
|
+
const sharedKeys = name.startsWith(SHARED_ANGULAR_SCOPE) ? [`${name}/`] : name === 'rxjs' ? SHARED_RXJS_ENTRY_POINTS : [];
|
|
23
|
+
if (sharedKeys.length > 0 && !EXACT_VERSION_PATTERN.test(version)) {
|
|
24
|
+
throw new Error(`Shared dependency ${name} must be pinned to an exact version, not "${version}".`);
|
|
25
|
+
}
|
|
26
|
+
for (const sharedKey of sharedKeys) {
|
|
27
|
+
shared[sharedKey] = { requiredVersion: version, singleton: false };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return shared;
|
|
31
|
+
};
|
|
8
32
|
export const MF_ASSET_MANIFEST_CONTRACT_VERSION = 1;
|
|
9
33
|
const serializeDefinition = (definition) => JSON.stringify(definition);
|
|
10
34
|
export const createMfNavigationManifest = (navigation) => {
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport type { Plugin } from 'vite';\nimport { defineMfNavigation, type IMfNavigationDefinition, type IMfNavigationManifest } from './navigation.js';\n\nconst DEFAULT_FILE_NAME = 'navigation.json';\nconst DEFAULT_ASSET_FILE_NAME = 'module-assets.json';\nconst FILE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.json$/;\nconst MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst CONTENT_HASHED_ASSET_PATTERN = /(?:^|\\/)[^/]+-[a-zA-Z0-9_-]{8,}\\.[^/]+$/;\n\nexport interface IMfNavigationPluginOptions {\n fileName?: string;\n navigation: IMfNavigationDefinition;\n}\n\nexport const MF_ASSET_MANIFEST_CONTRACT_VERSION = 1 as const;\n\nexport interface IMfAssetManifest {\n assets: readonly string[];\n contractVersion: typeof MF_ASSET_MANIFEST_CONTRACT_VERSION;\n moduleId: string;\n revision: string;\n}\n\nexport interface IMfAssetManifestPluginOptions {\n fileName?: string;\n moduleId: string;\n}\n\ninterface IMfOutputAsset {\n fileName: string;\n source: string | Uint8Array;\n type: 'asset';\n}\n\ninterface IMfOutputChunk {\n code: string;\n fileName: string;\n imports: readonly string[];\n modules: Readonly<Record<string, unknown>>;\n referencedFiles?: readonly string[];\n type: 'chunk';\n viteMetadata?: { importedAssets?: ReadonlySet<string>; importedCss?: ReadonlySet<string> };\n}\n\ntype TMfOutputBundle = Readonly<Record<string, IMfOutputAsset | IMfOutputChunk>>;\n\nconst serializeDefinition = (definition: Readonly<IMfNavigationDefinition>): string => JSON.stringify(definition);\n\nexport const createMfNavigationManifest = (navigation: IMfNavigationDefinition): Readonly<IMfNavigationManifest> => {\n const definition = defineMfNavigation(navigation);\n const revision = createHash('sha256').update(serializeDefinition(definition)).digest('hex');\n\n return Object.freeze({\n applicationId: definition.applicationId,\n contractVersion: definition.contractVersion,\n items: definition.items,\n label: definition.label,\n landingPath: definition.landingPath,\n moduleId: definition.moduleId,\n revision,\n });\n};\n\nexport const serializeMfNavigationManifest = (navigation: IMfNavigationDefinition): string => {\n return `${JSON.stringify(createMfNavigationManifest(navigation), null, 2)}\\n`;\n};\n\nexport const mfNavigationPlugin = ({ fileName = DEFAULT_FILE_NAME, navigation }: IMfNavigationPluginOptions): Plugin => {\n if (!FILE_NAME_PATTERN.test(fileName)) {\n throw new Error('Microfrontend navigation fileName must be a safe JSON file name.');\n }\n\n const source = serializeMfNavigationManifest(navigation);\n const publicPath = `/${fileName}`;\n\n return {\n name: 'edynamix-mf-navigation',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n const requestPath = request.url?.split('?', 1)[0];\n if (requestPath !== publicPath) {\n next();\n return;\n }\n\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(source);\n });\n },\n generateBundle() {\n this.emitFile({ fileName, source, type: 'asset' });\n },\n };\n};\n\nconst isLifecycleModule = (moduleId: string): boolean => /(?:^|\\/)src\\/lifecycle(?:-impl)?\\.[cm]?[jt]sx?$/.test(moduleId.replaceAll('\\\\', '/'));\n\nconst addChunkDependencies = (bundle: TMfOutputBundle, fileName: string, assets: Set<string>): void => {\n if (assets.has(fileName)) {\n return;\n }\n\n const output = bundle[fileName];\n if (output?.type !== 'chunk') {\n return;\n }\n\n assets.add(fileName);\n for (const importedFileName of output.imports) {\n addChunkDependencies(bundle, importedFileName, assets);\n }\n\n const metadata = output.viteMetadata;\n for (const referencedFileName of [...(metadata?.importedAssets ?? []), ...(metadata?.importedCss ?? []), ...(output.referencedFiles ?? [])]) {\n if (bundle[referencedFileName]) {\n assets.add(referencedFileName);\n }\n }\n};\n\nconst outputBytes = (output: IMfOutputAsset | IMfOutputChunk): string | Uint8Array => (output.type === 'chunk' ? output.code : output.source);\n\nconst createMfAssetManifest = (moduleId: string, bundle: TMfOutputBundle): Readonly<IMfAssetManifest> => {\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');\n }\n\n const lifecycleChunks = Object.values(bundle).filter(\n (output): output is IMfOutputChunk => output.type === 'chunk' && Object.keys(output.modules).some(isLifecycleModule),\n );\n if (lifecycleChunks.length === 0) {\n throw new Error(`Microfrontend ${moduleId} did not emit a lifecycle chunk.`);\n }\n\n const discoveredAssets = new Set<string>();\n for (const chunk of lifecycleChunks) {\n addChunkDependencies(bundle, chunk.fileName, discoveredAssets);\n }\n const assets = [...discoveredAssets].filter((fileName) => CONTENT_HASHED_ASSET_PATTERN.test(fileName)).sort();\n if (assets.length === 0) {\n throw new Error(`Microfrontend ${moduleId} did not emit content-hashed lifecycle assets.`);\n }\n\n const revisionHash = createHash('sha256');\n for (const fileName of assets) {\n const output = bundle[fileName];\n if (!output) {\n throw new Error(`Microfrontend ${moduleId} asset ${fileName} is missing from the output bundle.`);\n }\n revisionHash.update(fileName).update('\\0').update(outputBytes(output)).update('\\0');\n }\n\n return Object.freeze({\n assets: Object.freeze(assets),\n contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,\n moduleId,\n revision: revisionHash.digest('hex'),\n });\n};\n\nexport const serializeMfAssetManifest = (manifest: IMfAssetManifest): string => `${JSON.stringify(manifest, null, 2)}\\n`;\n\nexport const mfAssetManifestPlugin = ({ fileName = DEFAULT_ASSET_FILE_NAME, moduleId }: IMfAssetManifestPluginOptions): Plugin => {\n if (!FILE_NAME_PATTERN.test(fileName)) {\n throw new Error('Microfrontend asset fileName must be a safe JSON file name.');\n }\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');\n }\n\n const developmentManifest = serializeMfAssetManifest({\n assets: [],\n contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,\n moduleId,\n revision: 'development',\n });\n const publicPath = `/${fileName}`;\n\n return {\n name: 'edynamix-mf-assets',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n if (request.url?.split('?', 1)[0] !== publicPath) {\n next();\n return;\n }\n\n response.statusCode = 200;\n response.setHeader('Access-Control-Allow-Origin', '*');\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(developmentManifest);\n });\n },\n generateBundle(_options, bundle) {\n const source = serializeMfAssetManifest(createMfAssetManifest(moduleId, bundle as unknown as TMfOutputBundle));\n this.emitFile({ fileName, source, type: 'asset' });\n },\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport type { Plugin } from 'vite';\nimport { defineMfNavigation, type IMfNavigationDefinition, type IMfNavigationManifest } from './navigation.js';\n\nconst DEFAULT_FILE_NAME = 'navigation.json';\nconst DEFAULT_ASSET_FILE_NAME = 'module-assets.json';\nconst FILE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.json$/;\nconst MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\nconst CONTENT_HASHED_ASSET_PATTERN = /(?:^|\\/)[^/]+-[a-zA-Z0-9_-]{8,}\\.[^/]+$/;\n\nexport interface IMfNavigationPluginOptions {\n fileName?: string;\n navigation: IMfNavigationDefinition;\n}\n\nexport interface IMfSharedDependency {\n requiredVersion: string;\n singleton: false;\n}\n\n// Angular's packages are flat ESM entry points that import each other by package name, so a whole package, every\n// subpath included, is shared. RxJS subpaths reach its internals by relative path: only its two public entry points are\n// shared, each complete, so a page never mixes RxJS internals from two modules' builds.\nconst SHARED_ANGULAR_SCOPE = '@angular/';\nconst SHARED_RXJS_ENTRY_POINTS = ['rxjs', 'rxjs/operators'] as const;\nconst EXACT_VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/;\n\n/**\n * The Module Federation `shared` configuration of a hosted Angular module: every `@angular/*` package and RxJS among\n * the module's dependencies, at the module's own exact version. Modules on the same versions load these once per page\n * and share them; a module on another version keeps its own copy instead of mixing two Angular versions.\n */\nexport const createMfAngularShared = (dependencies: Readonly<Record<string, string>>): Record<string, IMfSharedDependency> => {\n const shared: Record<string, IMfSharedDependency> = {};\n for (const [name, version] of Object.entries(dependencies).sort(([left], [right]) => left.localeCompare(right))) {\n const sharedKeys = name.startsWith(SHARED_ANGULAR_SCOPE) ? [`${name}/`] : name === 'rxjs' ? SHARED_RXJS_ENTRY_POINTS : [];\n if (sharedKeys.length > 0 && !EXACT_VERSION_PATTERN.test(version)) {\n throw new Error(`Shared dependency ${name} must be pinned to an exact version, not \"${version}\".`);\n }\n for (const sharedKey of sharedKeys) {\n shared[sharedKey] = { requiredVersion: version, singleton: false };\n }\n }\n return shared;\n};\n\nexport const MF_ASSET_MANIFEST_CONTRACT_VERSION = 1 as const;\n\nexport interface IMfAssetManifest {\n assets: readonly string[];\n contractVersion: typeof MF_ASSET_MANIFEST_CONTRACT_VERSION;\n moduleId: string;\n revision: string;\n}\n\nexport interface IMfAssetManifestPluginOptions {\n fileName?: string;\n moduleId: string;\n}\n\ninterface IMfOutputAsset {\n fileName: string;\n source: string | Uint8Array;\n type: 'asset';\n}\n\ninterface IMfOutputChunk {\n code: string;\n fileName: string;\n imports: readonly string[];\n modules: Readonly<Record<string, unknown>>;\n referencedFiles?: readonly string[];\n type: 'chunk';\n viteMetadata?: { importedAssets?: ReadonlySet<string>; importedCss?: ReadonlySet<string> };\n}\n\ntype TMfOutputBundle = Readonly<Record<string, IMfOutputAsset | IMfOutputChunk>>;\n\nconst serializeDefinition = (definition: Readonly<IMfNavigationDefinition>): string => JSON.stringify(definition);\n\nexport const createMfNavigationManifest = (navigation: IMfNavigationDefinition): Readonly<IMfNavigationManifest> => {\n const definition = defineMfNavigation(navigation);\n const revision = createHash('sha256').update(serializeDefinition(definition)).digest('hex');\n\n return Object.freeze({\n applicationId: definition.applicationId,\n contractVersion: definition.contractVersion,\n items: definition.items,\n label: definition.label,\n landingPath: definition.landingPath,\n moduleId: definition.moduleId,\n revision,\n });\n};\n\nexport const serializeMfNavigationManifest = (navigation: IMfNavigationDefinition): string => {\n return `${JSON.stringify(createMfNavigationManifest(navigation), null, 2)}\\n`;\n};\n\nexport const mfNavigationPlugin = ({ fileName = DEFAULT_FILE_NAME, navigation }: IMfNavigationPluginOptions): Plugin => {\n if (!FILE_NAME_PATTERN.test(fileName)) {\n throw new Error('Microfrontend navigation fileName must be a safe JSON file name.');\n }\n\n const source = serializeMfNavigationManifest(navigation);\n const publicPath = `/${fileName}`;\n\n return {\n name: 'edynamix-mf-navigation',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n const requestPath = request.url?.split('?', 1)[0];\n if (requestPath !== publicPath) {\n next();\n return;\n }\n\n response.statusCode = 200;\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(source);\n });\n },\n generateBundle() {\n this.emitFile({ fileName, source, type: 'asset' });\n },\n };\n};\n\nconst isLifecycleModule = (moduleId: string): boolean => /(?:^|\\/)src\\/lifecycle(?:-impl)?\\.[cm]?[jt]sx?$/.test(moduleId.replaceAll('\\\\', '/'));\n\nconst addChunkDependencies = (bundle: TMfOutputBundle, fileName: string, assets: Set<string>): void => {\n if (assets.has(fileName)) {\n return;\n }\n\n const output = bundle[fileName];\n if (output?.type !== 'chunk') {\n return;\n }\n\n assets.add(fileName);\n for (const importedFileName of output.imports) {\n addChunkDependencies(bundle, importedFileName, assets);\n }\n\n const metadata = output.viteMetadata;\n for (const referencedFileName of [...(metadata?.importedAssets ?? []), ...(metadata?.importedCss ?? []), ...(output.referencedFiles ?? [])]) {\n if (bundle[referencedFileName]) {\n assets.add(referencedFileName);\n }\n }\n};\n\nconst outputBytes = (output: IMfOutputAsset | IMfOutputChunk): string | Uint8Array => (output.type === 'chunk' ? output.code : output.source);\n\nconst createMfAssetManifest = (moduleId: string, bundle: TMfOutputBundle): Readonly<IMfAssetManifest> => {\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');\n }\n\n const lifecycleChunks = Object.values(bundle).filter(\n (output): output is IMfOutputChunk => output.type === 'chunk' && Object.keys(output.modules).some(isLifecycleModule),\n );\n if (lifecycleChunks.length === 0) {\n throw new Error(`Microfrontend ${moduleId} did not emit a lifecycle chunk.`);\n }\n\n const discoveredAssets = new Set<string>();\n for (const chunk of lifecycleChunks) {\n addChunkDependencies(bundle, chunk.fileName, discoveredAssets);\n }\n const assets = [...discoveredAssets].filter((fileName) => CONTENT_HASHED_ASSET_PATTERN.test(fileName)).sort();\n if (assets.length === 0) {\n throw new Error(`Microfrontend ${moduleId} did not emit content-hashed lifecycle assets.`);\n }\n\n const revisionHash = createHash('sha256');\n for (const fileName of assets) {\n const output = bundle[fileName];\n if (!output) {\n throw new Error(`Microfrontend ${moduleId} asset ${fileName} is missing from the output bundle.`);\n }\n revisionHash.update(fileName).update('\\0').update(outputBytes(output)).update('\\0');\n }\n\n return Object.freeze({\n assets: Object.freeze(assets),\n contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,\n moduleId,\n revision: revisionHash.digest('hex'),\n });\n};\n\nexport const serializeMfAssetManifest = (manifest: IMfAssetManifest): string => `${JSON.stringify(manifest, null, 2)}\\n`;\n\nexport const mfAssetManifestPlugin = ({ fileName = DEFAULT_ASSET_FILE_NAME, moduleId }: IMfAssetManifestPluginOptions): Plugin => {\n if (!FILE_NAME_PATTERN.test(fileName)) {\n throw new Error('Microfrontend asset fileName must be a safe JSON file name.');\n }\n if (!MODULE_ID_PATTERN.test(moduleId)) {\n throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');\n }\n\n const developmentManifest = serializeMfAssetManifest({\n assets: [],\n contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,\n moduleId,\n revision: 'development',\n });\n const publicPath = `/${fileName}`;\n\n return {\n name: 'edynamix-mf-assets',\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n if (request.url?.split('?', 1)[0] !== publicPath) {\n next();\n return;\n }\n\n response.statusCode = 200;\n response.setHeader('Access-Control-Allow-Origin', '*');\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('Content-Type', 'application/json; charset=utf-8');\n response.end(developmentManifest);\n });\n },\n generateBundle(_options, bundle) {\n const source = serializeMfAssetManifest(createMfAssetManifest(moduleId, bundle as unknown as TMfOutputBundle));\n this.emitFile({ fileName, source, type: 'asset' });\n },\n };\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edynamix/mf-isolation",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "CSS and overlay isolation helpers for eDynamix microfrontends.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"types": "./dist/angular-runtime.d.ts",
|
|
30
30
|
"import": "./dist/angular-runtime.js"
|
|
31
31
|
},
|
|
32
|
+
"./angular-platform": {
|
|
33
|
+
"types": "./dist/angular-platform.d.ts",
|
|
34
|
+
"import": "./dist/angular-platform.js"
|
|
35
|
+
},
|
|
32
36
|
"./postcss": {
|
|
33
37
|
"types": "./dist/postcss.d.ts",
|
|
34
38
|
"import": "./dist/postcss.js"
|
|
@@ -100,6 +104,7 @@
|
|
|
100
104
|
"@angular/cdk": "^19.0.0",
|
|
101
105
|
"@angular/common": "^19.0.0",
|
|
102
106
|
"@angular/core": "^19.0.0",
|
|
107
|
+
"@angular/platform-browser-dynamic": "^19.0.0",
|
|
103
108
|
"postcss": "^8.5.16",
|
|
104
109
|
"vite": "^8.0.0"
|
|
105
110
|
},
|
|
@@ -113,6 +118,9 @@
|
|
|
113
118
|
"@angular/core": {
|
|
114
119
|
"optional": true
|
|
115
120
|
},
|
|
121
|
+
"@angular/platform-browser-dynamic": {
|
|
122
|
+
"optional": true
|
|
123
|
+
},
|
|
116
124
|
"postcss": {
|
|
117
125
|
"optional": true
|
|
118
126
|
},
|