@softarc/native-federation 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/core/build-for-federation.js +16 -2
- package/src/lib/core/bundle-exposed-and-mappings.js +15 -2
- package/src/lib/core/bundle-shared.d.ts +2 -1
- package/src/lib/core/bundle-shared.js +30 -3
- package/src/lib/core/federation-cache.d.ts +3 -2
- package/src/lib/core/federation-cache.js +6 -1
- package/src/lib/core/rebuild-for-federation.js +7 -1
- package/src/lib/core/write-import-map.d.ts +2 -2
- package/src/lib/core/write-import-map.js +12 -1
- package/src/lib/domain/core/federation-cache.contract.d.ts +2 -1
- package/src/lib/domain/core/federation-info.contract.d.ts +3 -0
- package/src/lib/domain/core/federation-options.contract.d.ts +1 -0
- package/src/lib/domain/core/index.d.ts +1 -1
- package/src/lib/utils/cache-persistence.d.ts +11 -13
- package/src/lib/utils/hash-file.d.ts +2 -0
- package/src/lib/utils/hash-file.js +5 -0
package/package.json
CHANGED
|
@@ -87,8 +87,14 @@ export async function buildForFederation(config, fedOptions, externals, signal)
|
|
|
87
87
|
if (artifactInfo?.chunks) {
|
|
88
88
|
federationInfo.chunks = { ...(federationInfo.chunks ?? {}), ...artifactInfo?.chunks };
|
|
89
89
|
}
|
|
90
|
+
if (fedOptions.integrity) {
|
|
91
|
+
federationInfo.integrity = {
|
|
92
|
+
...(fedOptions.federationCache.integrity ?? {}),
|
|
93
|
+
...(artifactInfo?.integrity ?? {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
90
96
|
writeFederationInfo(federationInfo, fedOptions);
|
|
91
|
-
writeImportMap(fedOptions.federationCache, fedOptions);
|
|
97
|
+
writeImportMap(fedOptions.federationCache, fedOptions, federationInfo.integrity);
|
|
92
98
|
return federationInfo;
|
|
93
99
|
}
|
|
94
100
|
function inferPackageFromSecondary(secondary) {
|
|
@@ -123,7 +129,15 @@ async function bundleSeparatePackages(separateBrowser, externals, config, fedOpt
|
|
|
123
129
|
if (r.chunks) {
|
|
124
130
|
chunks = { ...(acc.chunks ?? {}), ...r.chunks };
|
|
125
131
|
}
|
|
126
|
-
|
|
132
|
+
let integrity = acc.integrity;
|
|
133
|
+
if (r.integrity) {
|
|
134
|
+
integrity = { ...(acc.integrity ?? {}), ...r.integrity };
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
externals: [...acc.externals, ...r.externals],
|
|
138
|
+
chunks,
|
|
139
|
+
integrity,
|
|
140
|
+
};
|
|
127
141
|
}, { externals: [] });
|
|
128
142
|
}
|
|
129
143
|
function splitShared(shared) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { integrityForFile } from '../utils/hash-file.js';
|
|
3
4
|
import { createBuildResultMap, popFromResultMap } from '../utils/build-result-map.js';
|
|
4
5
|
import { logger } from '../utils/logger.js';
|
|
5
6
|
import { normalize } from '../utils/normalize.js';
|
|
@@ -100,14 +101,26 @@ export async function bundleExposedAndMappings(config, fedOptions, externals, mo
|
|
|
100
101
|
.forEach(f => popFromResultMap(resultMap, f));
|
|
101
102
|
// Process remaining chunks and lazy loaded internal modules
|
|
102
103
|
let exportedChunks = undefined;
|
|
104
|
+
const chunkPaths = [];
|
|
103
105
|
if (config.chunks && config.features.denseChunking) {
|
|
104
106
|
for (const entryFile of entryFiles)
|
|
105
107
|
rewriteChunkImports(entryFile);
|
|
108
|
+
chunkPaths.push(...Object.values(resultMap));
|
|
106
109
|
exportedChunks = {
|
|
107
|
-
['mapping-or-exposed']:
|
|
110
|
+
['mapping-or-exposed']: chunkPaths.map(chunk => path.basename(chunk)),
|
|
108
111
|
};
|
|
109
112
|
}
|
|
110
|
-
|
|
113
|
+
// Must run after rewriteChunkImports so SRI matches the final on-disk bytes.
|
|
114
|
+
let integrity;
|
|
115
|
+
if (fedOptions.integrity) {
|
|
116
|
+
integrity = {};
|
|
117
|
+
for (const filePath of [...entryFiles, ...chunkPaths]) {
|
|
118
|
+
if (!fs.existsSync(filePath))
|
|
119
|
+
continue;
|
|
120
|
+
integrity[path.basename(filePath)] = integrityForFile(filePath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { mappings: sharedResult, exposes: exposedResult, chunks: exportedChunks, integrity };
|
|
111
124
|
}
|
|
112
125
|
export function describeExposed(config, options) {
|
|
113
126
|
const result = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { NormalizedFederationConfig } from '../domain/config/federation-config.contract.js';
|
|
2
|
-
import type { SharedInfo } from '../domain/core/federation-info.contract.js';
|
|
2
|
+
import type { IntegrityMap, SharedInfo } from '../domain/core/federation-info.contract.js';
|
|
3
3
|
import { type NormalizedFederationOptions } from '../domain/core/federation-options.contract.js';
|
|
4
4
|
import type { NormalizedExternalConfig } from '../domain/config/external-config.contract.js';
|
|
5
5
|
export declare function bundleShared(sharedBundles: Record<string, NormalizedExternalConfig>, config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, externals: string[], buildOptions: {
|
|
@@ -9,4 +9,5 @@ export declare function bundleShared(sharedBundles: Record<string, NormalizedExt
|
|
|
9
9
|
}): Promise<{
|
|
10
10
|
externals: SharedInfo[];
|
|
11
11
|
chunks?: Record<string, string[]>;
|
|
12
|
+
integrity?: IntegrityMap;
|
|
12
13
|
}>;
|
|
@@ -9,6 +9,7 @@ import { toChunkImport } from '../domain/core/chunk.js';
|
|
|
9
9
|
import { cacheEntry, getChecksum, getFilename } from '../utils/cache-persistence.js';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { getBuildAdapter } from './build-adapter.js';
|
|
12
|
+
import { integrityForFile } from '../utils/hash-file.js';
|
|
12
13
|
export async function bundleShared(sharedBundles, config, fedOptions, externals, buildOptions) {
|
|
13
14
|
const checksum = getChecksum(sharedBundles, fedOptions.dev ? '1' : '0');
|
|
14
15
|
const folder = fedOptions.packageJson
|
|
@@ -20,7 +21,15 @@ export async function bundleShared(sharedBundles, config, fedOptions, externals,
|
|
|
20
21
|
if (cacheMetadata) {
|
|
21
22
|
logger.debug(`Checksum of ${buildOptions.bundleName} matched, Skipped artifact bundling`);
|
|
22
23
|
bundleCache.copyFiles(path.join(fedOptions.workspaceRoot, fedOptions.outputPath));
|
|
23
|
-
|
|
24
|
+
let integrity = cacheMetadata.integrity;
|
|
25
|
+
if (fedOptions.integrity && !integrity) {
|
|
26
|
+
integrity = computeIntegrityForFiles(cacheMetadata.files, fedOptions.federationCache.cachePath);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
externals: cacheMetadata.externals,
|
|
30
|
+
chunks: cacheMetadata.chunks,
|
|
31
|
+
integrity,
|
|
32
|
+
};
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
35
|
bundleCache.clear();
|
|
@@ -111,14 +120,32 @@ export async function bundleShared(sharedBundles, config, fedOptions, externals,
|
|
|
111
120
|
else {
|
|
112
121
|
addChunksToResult(chunks, result);
|
|
113
122
|
}
|
|
123
|
+
const persistedFiles = bundleResult.map(r => r.fileName.split(path.sep).pop() ?? r.fileName);
|
|
124
|
+
// Must run after rewriteImports so SRI matches the bytes copied to dist.
|
|
125
|
+
const integrity = fedOptions.integrity
|
|
126
|
+
? computeIntegrityForFiles(persistedFiles, fedOptions.federationCache.cachePath)
|
|
127
|
+
: undefined;
|
|
114
128
|
bundleCache.persist({
|
|
115
129
|
checksum,
|
|
116
130
|
externals: result,
|
|
117
|
-
files:
|
|
131
|
+
files: persistedFiles,
|
|
118
132
|
chunks: exportedChunks,
|
|
133
|
+
integrity,
|
|
119
134
|
});
|
|
120
135
|
bundleCache.copyFiles(path.join(fedOptions.workspaceRoot, fedOptions.outputPath));
|
|
121
|
-
return { externals: result, chunks: exportedChunks };
|
|
136
|
+
return { externals: result, chunks: exportedChunks, integrity };
|
|
137
|
+
}
|
|
138
|
+
function computeIntegrityForFiles(files, baseDir) {
|
|
139
|
+
const integrity = {};
|
|
140
|
+
for (const file of files) {
|
|
141
|
+
if (file.endsWith('.map'))
|
|
142
|
+
continue;
|
|
143
|
+
const fullPath = path.join(baseDir, file);
|
|
144
|
+
if (!fs.existsSync(fullPath))
|
|
145
|
+
continue;
|
|
146
|
+
integrity[path.basename(file)] = integrityForFile(fullPath);
|
|
147
|
+
}
|
|
148
|
+
return integrity;
|
|
122
149
|
}
|
|
123
150
|
function rewriteImports(cachedFiles, cachePath) {
|
|
124
151
|
const newSourceFiles = cachedFiles.filter(cf => isSourceFile(cf));
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { FederationCache } from '../domain/core/federation-cache.contract.js';
|
|
2
|
-
import type { ChunkInfo, SharedInfo } from '../domain/core/federation-info.contract.js';
|
|
2
|
+
import type { ChunkInfo, IntegrityMap, SharedInfo } from '../domain/core/federation-info.contract.js';
|
|
3
3
|
export declare function createFederationCache(cachePath: string): FederationCache<undefined>;
|
|
4
4
|
export declare function createFederationCache<TBundlerCache>(cachePath: string, bundlerCache: TBundlerCache): FederationCache<TBundlerCache>;
|
|
5
|
-
export declare function addExternalsToCache(cache: FederationCache, { externals, chunks }: {
|
|
5
|
+
export declare function addExternalsToCache(cache: FederationCache, { externals, chunks, integrity, }: {
|
|
6
6
|
externals: SharedInfo[];
|
|
7
7
|
chunks?: ChunkInfo;
|
|
8
|
+
integrity?: IntegrityMap;
|
|
8
9
|
}): void;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
export function createFederationCache(cachePath, bundlerCache) {
|
|
2
2
|
return { externals: [], cachePath, bundlerCache };
|
|
3
3
|
}
|
|
4
|
-
export function addExternalsToCache(cache, { externals, chunks }) {
|
|
4
|
+
export function addExternalsToCache(cache, { externals, chunks, integrity, }) {
|
|
5
5
|
cache.externals.push(...externals);
|
|
6
6
|
if (chunks) {
|
|
7
7
|
if (!cache.chunks)
|
|
8
8
|
cache.chunks = {};
|
|
9
9
|
cache.chunks = { ...cache.chunks, ...chunks };
|
|
10
10
|
}
|
|
11
|
+
if (integrity) {
|
|
12
|
+
if (!cache.integrity)
|
|
13
|
+
cache.integrity = {};
|
|
14
|
+
cache.integrity = { ...cache.integrity, ...integrity };
|
|
15
|
+
}
|
|
11
16
|
}
|
|
@@ -31,7 +31,13 @@ export async function rebuildForFederation(config, fedOptions, externals, modifi
|
|
|
31
31
|
if (artifactInfo?.chunks) {
|
|
32
32
|
federationInfo.chunks = { ...(federationInfo.chunks ?? {}), ...artifactInfo?.chunks };
|
|
33
33
|
}
|
|
34
|
+
if (fedOptions.integrity) {
|
|
35
|
+
federationInfo.integrity = {
|
|
36
|
+
...(federationCache.integrity ?? {}),
|
|
37
|
+
...(artifactInfo?.integrity ?? {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
34
40
|
writeFederationInfo(federationInfo, fedOptions);
|
|
35
|
-
writeImportMap(federationCache, fedOptions);
|
|
41
|
+
writeImportMap(federationCache, fedOptions, federationInfo.integrity);
|
|
36
42
|
return federationInfo;
|
|
37
43
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ChunkInfo, SharedInfo } from '../domain/core/federation-info.contract.js';
|
|
1
|
+
import type { ChunkInfo, IntegrityMap, SharedInfo } from '../domain/core/federation-info.contract.js';
|
|
2
2
|
import type { FederationOptions } from '../domain/core/federation-options.contract.js';
|
|
3
3
|
export declare function writeImportMap(sharedInfo: {
|
|
4
4
|
externals: SharedInfo[];
|
|
5
5
|
chunks?: ChunkInfo;
|
|
6
|
-
}, fedOption: FederationOptions): void;
|
|
6
|
+
}, fedOption: FederationOptions, fileIntegrity?: IntegrityMap): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import { toChunkImport } from '../domain/core/chunk.js';
|
|
4
|
-
export function writeImportMap(sharedInfo, fedOption) {
|
|
4
|
+
export function writeImportMap(sharedInfo, fedOption, fileIntegrity) {
|
|
5
5
|
const imports = sharedInfo.externals.reduce((acc, cur) => {
|
|
6
6
|
return {
|
|
7
7
|
...acc,
|
|
@@ -17,6 +17,17 @@ export function writeImportMap(sharedInfo, fedOption) {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
const importMap = { imports };
|
|
20
|
+
if (fileIntegrity) {
|
|
21
|
+
const integrity = {};
|
|
22
|
+
for (const url of Object.values(imports)) {
|
|
23
|
+
const sri = fileIntegrity[url];
|
|
24
|
+
if (sri)
|
|
25
|
+
integrity[url] = sri;
|
|
26
|
+
}
|
|
27
|
+
if (Object.keys(integrity).length > 0) {
|
|
28
|
+
importMap.integrity = integrity;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
20
31
|
const importMapPath = path.join(fedOption.workspaceRoot, fedOption.outputPath, 'importmap.json');
|
|
21
32
|
fs.writeFileSync(importMapPath, JSON.stringify(importMap, null, 2));
|
|
22
33
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { ChunkInfo, SharedInfo } from './federation-info.contract.js';
|
|
1
|
+
import type { ChunkInfo, IntegrityMap, SharedInfo } from './federation-info.contract.js';
|
|
2
2
|
export type FederationCache<TBundlerCache = unknown> = {
|
|
3
3
|
externals: SharedInfo[];
|
|
4
4
|
chunks?: ChunkInfo;
|
|
5
|
+
integrity?: IntegrityMap;
|
|
5
6
|
bundlerCache: TBundlerCache;
|
|
6
7
|
cachePath: string;
|
|
7
8
|
};
|
|
@@ -3,6 +3,7 @@ export interface FederationInfo {
|
|
|
3
3
|
exposes: ExposesInfo[];
|
|
4
4
|
shared: SharedInfo[];
|
|
5
5
|
chunks?: Record<string, string[]>;
|
|
6
|
+
integrity?: IntegrityMap;
|
|
6
7
|
buildNotificationsEndpoint?: string;
|
|
7
8
|
}
|
|
8
9
|
export type SharedInfo = {
|
|
@@ -19,6 +20,7 @@ export type SharedInfo = {
|
|
|
19
20
|
};
|
|
20
21
|
};
|
|
21
22
|
export type ChunkInfo = Record<string, string[]>;
|
|
23
|
+
export type IntegrityMap = Record<string, string>;
|
|
22
24
|
export interface ExposesInfo {
|
|
23
25
|
key: string;
|
|
24
26
|
outFileName: string;
|
|
@@ -30,4 +32,5 @@ export interface ArtifactInfo {
|
|
|
30
32
|
mappings: SharedInfo[];
|
|
31
33
|
exposes: ExposesInfo[];
|
|
32
34
|
chunks?: ChunkInfo;
|
|
35
|
+
integrity?: IntegrityMap;
|
|
33
36
|
}
|
|
@@ -13,6 +13,7 @@ export interface FederationOptions {
|
|
|
13
13
|
packageJson?: string;
|
|
14
14
|
entryPoints?: string[];
|
|
15
15
|
buildNotifications?: BuildNotificationOptions;
|
|
16
|
+
integrity?: boolean;
|
|
16
17
|
}
|
|
17
18
|
export interface NormalizedFederationOptions<TBundlerCache = unknown> extends FederationOptions {
|
|
18
19
|
federationCache: FederationCache<TBundlerCache>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { SharedInfo, FederationInfo, ExposesInfo, ArtifactInfo, ChunkInfo, } from './federation-info.contract.js';
|
|
1
|
+
export type { SharedInfo, FederationInfo, ExposesInfo, ArtifactInfo, ChunkInfo, IntegrityMap, } from './federation-info.contract.js';
|
|
2
2
|
export { type BuildNotificationOptions, BuildNotificationType, } from './build-notification-options.contract.js';
|
|
3
3
|
export type { FederationOptions, NormalizedFederationOptions, } from './federation-options.contract.js';
|
|
4
4
|
export type { EntryPoint, NFBuildAdapterOptions, NFBuildAdapter, NFBuildAdapterResult, NFBuildAdapterContext, } from './build-adapter.contract.js';
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import type { NormalizedExternalConfig } from '../domain/config/external-config.contract.js';
|
|
2
|
-
import type { ChunkInfo, SharedInfo } from '../domain/core/federation-info.contract.js';
|
|
2
|
+
import type { ChunkInfo, IntegrityMap, SharedInfo } from '../domain/core/federation-info.contract.js';
|
|
3
3
|
export declare const getDefaultCachePath: (workspaceRoot: string) => string;
|
|
4
4
|
export declare const getFilename: (title: string, dev?: boolean) => string;
|
|
5
5
|
export declare const getChecksum: (shared: Record<string, NormalizedExternalConfig>, dev: "1" | "0") => string;
|
|
6
|
+
type CacheMetadata = {
|
|
7
|
+
checksum: string;
|
|
8
|
+
externals: SharedInfo[];
|
|
9
|
+
chunks?: ChunkInfo;
|
|
10
|
+
integrity?: IntegrityMap;
|
|
11
|
+
files: string[];
|
|
12
|
+
};
|
|
6
13
|
export declare const cacheEntry: (pathToCache: string, fileName: string) => {
|
|
7
|
-
getMetadata: (checksum: string) =>
|
|
8
|
-
|
|
9
|
-
externals: SharedInfo[];
|
|
10
|
-
chunks?: ChunkInfo;
|
|
11
|
-
files: string[];
|
|
12
|
-
} | undefined;
|
|
13
|
-
persist: (payload: {
|
|
14
|
-
checksum: string;
|
|
15
|
-
externals: SharedInfo[];
|
|
16
|
-
chunks?: ChunkInfo;
|
|
17
|
-
files: string[];
|
|
18
|
-
}) => void;
|
|
14
|
+
getMetadata: (checksum: string) => CacheMetadata | undefined;
|
|
15
|
+
persist: (payload: CacheMetadata) => void;
|
|
19
16
|
copyFiles: (fullOutputPath: string) => void;
|
|
20
17
|
clear: () => void;
|
|
21
18
|
};
|
|
19
|
+
export {};
|
|
@@ -6,3 +6,8 @@ export function hashFile(fileName) {
|
|
|
6
6
|
hashSum.update(fileBuffer);
|
|
7
7
|
return hashSum.digest('hex');
|
|
8
8
|
}
|
|
9
|
+
export function integrityForFile(fileName, algorithm = 'sha384') {
|
|
10
|
+
const fileBuffer = fs.readFileSync(fileName);
|
|
11
|
+
const hash = crypto.createHash(algorithm).update(fileBuffer).digest('base64');
|
|
12
|
+
return `${algorithm}-${hash}`;
|
|
13
|
+
}
|