@edynamix/mf-isolation 0.0.13 → 0.0.15
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/dist/angular-runtime.d.ts +10 -0
- package/dist/angular-runtime.d.ts.map +1 -1
- package/dist/angular-runtime.js +31 -0
- package/dist/angular-runtime.js.map +1 -1
- package/dist/vite.d.ts +13 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +92 -0
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,4 +5,14 @@
|
|
|
5
5
|
* importing an Angular bootstrap module.
|
|
6
6
|
*/
|
|
7
7
|
export declare const assertHostZoneRuntime: () => void;
|
|
8
|
+
type TAngularLifecycleResult = void | Promise<void>;
|
|
9
|
+
export interface IAngularLifecycleImplementation<TShellContext> {
|
|
10
|
+
mount: (host: HTMLElement, shellContext: TShellContext) => TAngularLifecycleResult;
|
|
11
|
+
unmount: (host: HTMLElement) => TAngularLifecycleResult;
|
|
12
|
+
}
|
|
13
|
+
export interface IHostedAngularLifecycle<TShellContext> extends IAngularLifecycleImplementation<TShellContext> {
|
|
14
|
+
preload: () => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare const createHostedAngularLifecycle: <TShellContext>(importImplementation: () => Promise<IAngularLifecycleImplementation<TShellContext>>) => IHostedAngularLifecycle<TShellContext>;
|
|
17
|
+
export {};
|
|
8
18
|
//# sourceMappingURL=angular-runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"angular-runtime.d.ts","sourceRoot":"","sources":["../src/angular-runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAO,IAMxC,CAAC","sourcesContent":["/**\n * Verifies the host-owned Zone.js runtime before Angular is loaded.\n *\n * This check is intentionally framework-free and must run before dynamically\n * importing an Angular bootstrap module.\n */\nexport const assertHostZoneRuntime = (): void => {\n const zoneRuntime = (globalThis as typeof globalThis & { Zone?: unknown }).Zone;\n\n if (typeof zoneRuntime !== 'function') {\n throw new Error('Host Zone.js runtime is missing. Expected globalThis.Zone to be a function before loading Angular.');\n }\n};\n"]}
|
|
1
|
+
{"version":3,"file":"angular-runtime.d.ts","sourceRoot":"","sources":["../src/angular-runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAO,IAMxC,CAAC;AAEF,KAAK,uBAAuB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpD,MAAM,WAAW,+BAA+B,CAAC,aAAa;IAC5D,KAAK,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,KAAK,uBAAuB,CAAC;IACnF,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,uBAAuB,CAAC;CACzD;AAED,MAAM,WAAW,uBAAuB,CAAC,aAAa,CAAE,SAAQ,+BAA+B,CAAC,aAAa,CAAC;IAC5G,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,eAAO,MAAM,4BAA4B,GAAI,aAAa,EACxD,sBAAsB,MAAM,OAAO,CAAC,+BAA+B,CAAC,aAAa,CAAC,CAAC,KAClF,uBAAuB,CAAC,aAAa,CAiCvC,CAAC","sourcesContent":["/**\n * Verifies the host-owned Zone.js runtime before Angular is loaded.\n *\n * This check is intentionally framework-free and must run before dynamically\n * importing an Angular bootstrap module.\n */\nexport const assertHostZoneRuntime = (): void => {\n const zoneRuntime = (globalThis as typeof globalThis & { Zone?: unknown }).Zone;\n\n if (typeof zoneRuntime !== 'function') {\n throw new Error('Host Zone.js runtime is missing. Expected globalThis.Zone to be a function before loading Angular.');\n }\n};\n\ntype TAngularLifecycleResult = void | Promise<void>;\n\nexport interface IAngularLifecycleImplementation<TShellContext> {\n mount: (host: HTMLElement, shellContext: TShellContext) => TAngularLifecycleResult;\n unmount: (host: HTMLElement) => TAngularLifecycleResult;\n}\n\nexport interface IHostedAngularLifecycle<TShellContext> extends IAngularLifecycleImplementation<TShellContext> {\n preload: () => Promise<void>;\n}\n\nexport const createHostedAngularLifecycle = <TShellContext>(\n importImplementation: () => Promise<IAngularLifecycleImplementation<TShellContext>>,\n): IHostedAngularLifecycle<TShellContext> => {\n let implementation: Promise<IAngularLifecycleImplementation<TShellContext>> | undefined;\n\n const loadImplementation = (): Promise<IAngularLifecycleImplementation<TShellContext>> => {\n if (!implementation) {\n assertHostZoneRuntime();\n const pending = importImplementation();\n const cached = pending.catch((error: unknown) => {\n if (implementation === cached) {\n implementation = undefined;\n }\n throw error;\n });\n implementation = cached;\n }\n\n return implementation;\n };\n\n return {\n preload: async () => {\n await loadImplementation();\n },\n mount: async (host, shellContext) => {\n await (await loadImplementation()).mount(host, shellContext);\n },\n unmount: async (host) => {\n if (!implementation) {\n return;\n }\n await (await implementation).unmount(host);\n },\n };\n};\n"]}
|
package/dist/angular-runtime.js
CHANGED
|
@@ -10,4 +10,35 @@ export const assertHostZoneRuntime = () => {
|
|
|
10
10
|
throw new Error('Host Zone.js runtime is missing. Expected globalThis.Zone to be a function before loading Angular.');
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
|
+
export const createHostedAngularLifecycle = (importImplementation) => {
|
|
14
|
+
let implementation;
|
|
15
|
+
const loadImplementation = () => {
|
|
16
|
+
if (!implementation) {
|
|
17
|
+
assertHostZoneRuntime();
|
|
18
|
+
const pending = importImplementation();
|
|
19
|
+
const cached = pending.catch((error) => {
|
|
20
|
+
if (implementation === cached) {
|
|
21
|
+
implementation = undefined;
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
24
|
+
});
|
|
25
|
+
implementation = cached;
|
|
26
|
+
}
|
|
27
|
+
return implementation;
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
preload: async () => {
|
|
31
|
+
await loadImplementation();
|
|
32
|
+
},
|
|
33
|
+
mount: async (host, shellContext) => {
|
|
34
|
+
await (await loadImplementation()).mount(host, shellContext);
|
|
35
|
+
},
|
|
36
|
+
unmount: async (host) => {
|
|
37
|
+
if (!implementation) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await (await implementation).unmount(host);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
13
44
|
//# sourceMappingURL=angular-runtime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"angular-runtime.js","sourceRoot":"","sources":["../src/angular-runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAS,EAAE;IAC9C,MAAM,WAAW,GAAI,UAAqD,CAAC,IAAI,CAAC;IAEhF,IAAI,OAAO,WAAW,KAAK,UAAU,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;IACxH,CAAC;AACH,CAAC,CAAC","sourcesContent":["/**\n * Verifies the host-owned Zone.js runtime before Angular is loaded.\n *\n * This check is intentionally framework-free and must run before dynamically\n * importing an Angular bootstrap module.\n */\nexport const assertHostZoneRuntime = (): void => {\n const zoneRuntime = (globalThis as typeof globalThis & { Zone?: unknown }).Zone;\n\n if (typeof zoneRuntime !== 'function') {\n throw new Error('Host Zone.js runtime is missing. Expected globalThis.Zone to be a function before loading Angular.');\n }\n};\n"]}
|
|
1
|
+
{"version":3,"file":"angular-runtime.js","sourceRoot":"","sources":["../src/angular-runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAS,EAAE;IAC9C,MAAM,WAAW,GAAI,UAAqD,CAAC,IAAI,CAAC;IAEhF,IAAI,OAAO,WAAW,KAAK,UAAU,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;IACxH,CAAC;AACH,CAAC,CAAC;AAaF,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,oBAAmF,EAC3C,EAAE;IAC1C,IAAI,cAAmF,CAAC;IAExF,MAAM,kBAAkB,GAAG,GAA4D,EAAE;QACvF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,qBAAqB,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,oBAAoB,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAC9C,IAAI,cAAc,KAAK,MAAM,EAAE,CAAC;oBAC9B,cAAc,GAAG,SAAS,CAAC;gBAC7B,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC,CAAC,CAAC;YACH,cAAc,GAAG,MAAM,CAAC;QAC1B,CAAC;QAED,OAAO,cAAc,CAAC;IACxB,CAAC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,kBAAkB,EAAE,CAAC;QAC7B,CAAC;QACD,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE;YAClC,MAAM,CAAC,MAAM,kBAAkB,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACtB,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,OAAO;YACT,CAAC;YACD,MAAM,CAAC,MAAM,cAAc,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/**\n * Verifies the host-owned Zone.js runtime before Angular is loaded.\n *\n * This check is intentionally framework-free and must run before dynamically\n * importing an Angular bootstrap module.\n */\nexport const assertHostZoneRuntime = (): void => {\n const zoneRuntime = (globalThis as typeof globalThis & { Zone?: unknown }).Zone;\n\n if (typeof zoneRuntime !== 'function') {\n throw new Error('Host Zone.js runtime is missing. Expected globalThis.Zone to be a function before loading Angular.');\n }\n};\n\ntype TAngularLifecycleResult = void | Promise<void>;\n\nexport interface IAngularLifecycleImplementation<TShellContext> {\n mount: (host: HTMLElement, shellContext: TShellContext) => TAngularLifecycleResult;\n unmount: (host: HTMLElement) => TAngularLifecycleResult;\n}\n\nexport interface IHostedAngularLifecycle<TShellContext> extends IAngularLifecycleImplementation<TShellContext> {\n preload: () => Promise<void>;\n}\n\nexport const createHostedAngularLifecycle = <TShellContext>(\n importImplementation: () => Promise<IAngularLifecycleImplementation<TShellContext>>,\n): IHostedAngularLifecycle<TShellContext> => {\n let implementation: Promise<IAngularLifecycleImplementation<TShellContext>> | undefined;\n\n const loadImplementation = (): Promise<IAngularLifecycleImplementation<TShellContext>> => {\n if (!implementation) {\n assertHostZoneRuntime();\n const pending = importImplementation();\n const cached = pending.catch((error: unknown) => {\n if (implementation === cached) {\n implementation = undefined;\n }\n throw error;\n });\n implementation = cached;\n }\n\n return implementation;\n };\n\n return {\n preload: async () => {\n await loadImplementation();\n },\n mount: async (host, shellContext) => {\n await (await loadImplementation()).mount(host, shellContext);\n },\n unmount: async (host) => {\n if (!implementation) {\n return;\n }\n await (await implementation).unmount(host);\n },\n };\n};\n"]}
|
package/dist/vite.d.ts
CHANGED
|
@@ -4,7 +4,20 @@ export interface IMfNavigationPluginOptions {
|
|
|
4
4
|
fileName?: string;
|
|
5
5
|
navigation: IMfNavigationDefinition;
|
|
6
6
|
}
|
|
7
|
+
export declare const MF_ASSET_MANIFEST_CONTRACT_VERSION: 1;
|
|
8
|
+
export interface IMfAssetManifest {
|
|
9
|
+
assets: readonly string[];
|
|
10
|
+
contractVersion: typeof MF_ASSET_MANIFEST_CONTRACT_VERSION;
|
|
11
|
+
moduleId: string;
|
|
12
|
+
revision: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IMfAssetManifestPluginOptions {
|
|
15
|
+
fileName?: string;
|
|
16
|
+
moduleId: string;
|
|
17
|
+
}
|
|
7
18
|
export declare const createMfNavigationManifest: (navigation: IMfNavigationDefinition) => Readonly<IMfNavigationManifest>;
|
|
8
19
|
export declare const serializeMfNavigationManifest: (navigation: IMfNavigationDefinition) => string;
|
|
9
20
|
export declare const mfNavigationPlugin: ({ fileName, navigation }: IMfNavigationPluginOptions) => Plugin;
|
|
21
|
+
export declare const serializeMfAssetManifest: (manifest: IMfAssetManifest) => string;
|
|
22
|
+
export declare const mfAssetManifestPlugin: ({ fileName, moduleId }: IMfAssetManifestPluginOptions) => Plugin;
|
|
10
23
|
//# sourceMappingURL=vite.d.ts.map
|
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;
|
|
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"]}
|
package/dist/vite.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { defineMfNavigation } from './navigation.js';
|
|
3
3
|
const DEFAULT_FILE_NAME = 'navigation.json';
|
|
4
|
+
const DEFAULT_ASSET_FILE_NAME = 'module-assets.json';
|
|
4
5
|
const FILE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\.json$/;
|
|
6
|
+
const MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
7
|
+
const CONTENT_HASHED_ASSET_PATTERN = /(?:^|\/)[^/]+-[a-zA-Z0-9_-]{8,}\.[^/]+$/;
|
|
8
|
+
export const MF_ASSET_MANIFEST_CONTRACT_VERSION = 1;
|
|
5
9
|
const serializeDefinition = (definition) => JSON.stringify(definition);
|
|
6
10
|
export const createMfNavigationManifest = (navigation) => {
|
|
7
11
|
const definition = defineMfNavigation(navigation);
|
|
@@ -45,4 +49,92 @@ export const mfNavigationPlugin = ({ fileName = DEFAULT_FILE_NAME, navigation })
|
|
|
45
49
|
},
|
|
46
50
|
};
|
|
47
51
|
};
|
|
52
|
+
const isLifecycleModule = (moduleId) => /(?:^|\/)src\/lifecycle(?:-impl)?\.[cm]?[jt]sx?$/.test(moduleId.replaceAll('\\', '/'));
|
|
53
|
+
const addChunkDependencies = (bundle, fileName, assets) => {
|
|
54
|
+
if (assets.has(fileName)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const output = bundle[fileName];
|
|
58
|
+
if (output?.type !== 'chunk') {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
assets.add(fileName);
|
|
62
|
+
for (const importedFileName of output.imports) {
|
|
63
|
+
addChunkDependencies(bundle, importedFileName, assets);
|
|
64
|
+
}
|
|
65
|
+
const metadata = output.viteMetadata;
|
|
66
|
+
for (const referencedFileName of [...(metadata?.importedAssets ?? []), ...(metadata?.importedCss ?? []), ...output.referencedFiles]) {
|
|
67
|
+
if (bundle[referencedFileName]) {
|
|
68
|
+
assets.add(referencedFileName);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const outputBytes = (output) => (output.type === 'chunk' ? output.code : output.source);
|
|
73
|
+
const createMfAssetManifest = (moduleId, bundle) => {
|
|
74
|
+
if (!MODULE_ID_PATTERN.test(moduleId)) {
|
|
75
|
+
throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');
|
|
76
|
+
}
|
|
77
|
+
const lifecycleChunks = Object.values(bundle).filter((output) => output.type === 'chunk' && Object.keys(output.modules).some(isLifecycleModule));
|
|
78
|
+
if (lifecycleChunks.length === 0) {
|
|
79
|
+
throw new Error(`Microfrontend ${moduleId} did not emit a lifecycle chunk.`);
|
|
80
|
+
}
|
|
81
|
+
const discoveredAssets = new Set();
|
|
82
|
+
for (const chunk of lifecycleChunks) {
|
|
83
|
+
addChunkDependencies(bundle, chunk.fileName, discoveredAssets);
|
|
84
|
+
}
|
|
85
|
+
const assets = [...discoveredAssets].filter((fileName) => CONTENT_HASHED_ASSET_PATTERN.test(fileName)).sort();
|
|
86
|
+
if (assets.length === 0) {
|
|
87
|
+
throw new Error(`Microfrontend ${moduleId} did not emit content-hashed lifecycle assets.`);
|
|
88
|
+
}
|
|
89
|
+
const revisionHash = createHash('sha256');
|
|
90
|
+
for (const fileName of assets) {
|
|
91
|
+
const output = bundle[fileName];
|
|
92
|
+
if (!output) {
|
|
93
|
+
throw new Error(`Microfrontend ${moduleId} asset ${fileName} is missing from the output bundle.`);
|
|
94
|
+
}
|
|
95
|
+
revisionHash.update(fileName).update('\0').update(outputBytes(output)).update('\0');
|
|
96
|
+
}
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
assets: Object.freeze(assets),
|
|
99
|
+
contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,
|
|
100
|
+
moduleId,
|
|
101
|
+
revision: revisionHash.digest('hex'),
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
export const serializeMfAssetManifest = (manifest) => `${JSON.stringify(manifest, null, 2)}\n`;
|
|
105
|
+
export const mfAssetManifestPlugin = ({ fileName = DEFAULT_ASSET_FILE_NAME, moduleId }) => {
|
|
106
|
+
if (!FILE_NAME_PATTERN.test(fileName)) {
|
|
107
|
+
throw new Error('Microfrontend asset fileName must be a safe JSON file name.');
|
|
108
|
+
}
|
|
109
|
+
if (!MODULE_ID_PATTERN.test(moduleId)) {
|
|
110
|
+
throw new Error('Microfrontend asset moduleId must use lowercase letters, numbers, and hyphens.');
|
|
111
|
+
}
|
|
112
|
+
const developmentManifest = serializeMfAssetManifest({
|
|
113
|
+
assets: [],
|
|
114
|
+
contractVersion: MF_ASSET_MANIFEST_CONTRACT_VERSION,
|
|
115
|
+
moduleId,
|
|
116
|
+
revision: 'development',
|
|
117
|
+
});
|
|
118
|
+
const publicPath = `/${fileName}`;
|
|
119
|
+
return {
|
|
120
|
+
name: 'edynamix-mf-assets',
|
|
121
|
+
configureServer(server) {
|
|
122
|
+
server.middlewares.use((request, response, next) => {
|
|
123
|
+
if (request.url?.split('?', 1)[0] !== publicPath) {
|
|
124
|
+
next();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
response.statusCode = 200;
|
|
128
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
129
|
+
response.setHeader('Cache-Control', 'no-cache');
|
|
130
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
131
|
+
response.end(developmentManifest);
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
generateBundle(_options, bundle) {
|
|
135
|
+
const source = serializeMfAssetManifest(createMfAssetManifest(moduleId, bundle));
|
|
136
|
+
this.emitFile({ fileName, source, type: 'asset' });
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
};
|
|
48
140
|
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAA4D,MAAM,iBAAiB,CAAC;AAE/G,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAC5C,MAAM,iBAAiB,GAAG,oCAAoC,CAAC;AAO/D,MAAM,mBAAmB,GAAG,CAAC,UAA6C,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;AAElH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,UAAmC,EAAmC,EAAE;IACjH,MAAM,UAAU,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE5F,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,aAAa,EAAE,UAAU,CAAC,aAAa;QACvC,eAAe,EAAE,UAAU,CAAC,eAAe;QAC3C,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,QAAQ;KACT,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,UAAmC,EAAU,EAAE;IAC3F,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAChF,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EAAE,QAAQ,GAAG,iBAAiB,EAAE,UAAU,EAA8B,EAAU,EAAE;IACrH,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;IACtF,CAAC;IAED,MAAM,MAAM,GAAG,6BAA6B,CAAC,UAAU,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,IAAI,QAAQ,EAAE,CAAC;IAElC,OAAO;QACL,IAAI,EAAE,wBAAwB;QAC9B,eAAe,CAAC,MAAM;YACpB,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBACjD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAClD,IAAI,WAAW,KAAK,UAAU,EAAE,CAAC;oBAC/B,IAAI,EAAE,CAAC;oBACP,OAAO;gBACT,CAAC;gBAED,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;gBAChD,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,iCAAiC,CAAC,CAAC;gBACtE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC,CAAC,CAAC;QACL,CAAC;QACD,cAAc;YACZ,IAAI,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACrD,CAAC;KACF,CAAC;AACJ,CAAC,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 FILE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\\.json$/;\n\nexport interface IMfNavigationPluginOptions {\n fileName?: string;\n navigation: IMfNavigationDefinition;\n}\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"]}
|
|
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"]}
|