mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-27 01:22:49 +00:00
478 lines
20 KiB
JavaScript
478 lines
20 KiB
JavaScript
import { composeKeyWithSeparator, getManifestFileName } from "@module-federation/sdk";
|
|
import { assert, findChunk, getAssetsByChunk, getAssetsByChunkIDs, getFileNameWithOutExt, getSharedModules, getTypesMetaInfo } from "./utils.mjs";
|
|
import logger from "./logger.mjs";
|
|
import { ContainerManager, PKGJsonManager, RemoteManager, SharedManager, UNKNOWN_MODULE_NAME, utils } from "@module-federation/managers";
|
|
import { HOT_UPDATE_SUFFIX } from "./constants.mjs";
|
|
import { ModuleHandler, getExposeItem, getExposeName, getShareItem } from "./ModuleHandler.mjs";
|
|
|
|
;// CONCATENATED MODULE: external "@module-federation/sdk"
|
|
|
|
;// CONCATENATED MODULE: external "./utils.mjs"
|
|
|
|
;// CONCATENATED MODULE: external "./logger.mjs"
|
|
|
|
;// CONCATENATED MODULE: external "@module-federation/managers"
|
|
|
|
;// CONCATENATED MODULE: external "./constants.mjs"
|
|
|
|
;// CONCATENATED MODULE: external "./ModuleHandler.mjs"
|
|
|
|
;// CONCATENATED MODULE: ./src/StatsManager.ts
|
|
/* eslint-disable max-lines-per-function */ /* eslint-disable @typescript-eslint/member-ordering */ /* eslint-disable max-depth */
|
|
|
|
|
|
|
|
|
|
|
|
class StatsManager {
|
|
getBuildInfo(context, target) {
|
|
const rootPath = context || process.cwd();
|
|
const pkg = this._pkgJsonManager.readPKGJson(rootPath);
|
|
const statsBuildInfo = {
|
|
buildVersion: utils.getBuildVersion(rootPath),
|
|
buildName: utils.getBuildName() || pkg['name']
|
|
};
|
|
if (this._sharedManager.enableTreeShaking) {
|
|
statsBuildInfo.target = target ? Array.isArray(target) ? target : [
|
|
target
|
|
] : [];
|
|
statsBuildInfo.plugins = this._options.treeShakingSharedPlugins || [];
|
|
statsBuildInfo.excludePlugins = this._options.treeShakingSharedExcludePlugins || [];
|
|
}
|
|
return statsBuildInfo;
|
|
}
|
|
get fileName() {
|
|
return getManifestFileName(this._options.manifest).statsFileName;
|
|
}
|
|
setMetaDataPublicPath(metaData, compiler) {
|
|
if (this._options.getPublicPath) {
|
|
if ('publicPath' in metaData) {
|
|
// @ts-ignore
|
|
delete metaData.publicPath;
|
|
}
|
|
metaData.getPublicPath = this._options.getPublicPath;
|
|
} else {
|
|
metaData.publicPath = this.getPublicPath(compiler);
|
|
}
|
|
return metaData;
|
|
}
|
|
_getMetaData(compiler, compilation, extraOptions) {
|
|
var _this__options_library, _this__options;
|
|
const { context } = compiler.options;
|
|
const { _options: { name } } = this;
|
|
const buildInfo = this.getBuildInfo(context, compilation.options.target || '');
|
|
const type = this._pkgJsonManager.getExposeGarfishModuleType(context || process.cwd());
|
|
const getRemoteEntryName = ()=>{
|
|
if (!this._containerManager.enable) {
|
|
return '';
|
|
}
|
|
assert(name, 'name is required');
|
|
const remoteEntryPoint = compilation.entrypoints.get(name);
|
|
assert(remoteEntryPoint, 'Can not get remoteEntry entryPoint!');
|
|
const remoteEntryNameChunk = compilation.namedChunks.get(name);
|
|
assert(remoteEntryNameChunk, 'Can not get remoteEntry chunk!');
|
|
const files = Array.from(remoteEntryNameChunk.files).filter((f)=>!f.includes(HOT_UPDATE_SUFFIX) && !f.endsWith('.css'));
|
|
assert(files.length > 0, 'no files found for remoteEntry chunk');
|
|
assert(files.length === 1, `remoteEntry chunk should not have multiple files!, current files: ${files.join(',')}`);
|
|
const remoteEntryName = files[0];
|
|
return remoteEntryName;
|
|
};
|
|
const globalName = this._containerManager.globalEntryName;
|
|
assert(globalName, 'Can not get library.name, please ensure you have set library.name and the type is "string" !');
|
|
assert(this._pluginVersion, 'Can not get pluginVersion, please ensure you have set pluginVersion !');
|
|
const metaData = {
|
|
name: name,
|
|
type,
|
|
buildInfo,
|
|
remoteEntry: {
|
|
name: getRemoteEntryName(),
|
|
path: '',
|
|
// same as the types supported by runtime, currently only global/var/script is supported
|
|
type: ((_this__options = this._options) === null || _this__options === void 0 ? void 0 : (_this__options_library = _this__options.library) === null || _this__options_library === void 0 ? void 0 : _this__options_library.type) || 'global'
|
|
},
|
|
types: getTypesMetaInfo(this._options, compiler.context),
|
|
globalName: globalName,
|
|
pluginVersion: this._pluginVersion
|
|
};
|
|
return this.setMetaDataPublicPath(metaData, compiler);
|
|
}
|
|
_getFilteredModules(stats) {
|
|
const filteredModules = stats.modules.filter((module)=>{
|
|
if (!module || !module.name) {
|
|
return false;
|
|
}
|
|
const array = [
|
|
module.name.includes('container entry'),
|
|
module.name.includes('remote '),
|
|
module.name.includes('shared module '),
|
|
module.name.includes('provide module ')
|
|
];
|
|
return array.some((item)=>item);
|
|
});
|
|
return filteredModules;
|
|
}
|
|
_getModuleAssets(compilation, entryPointNames) {
|
|
const { chunks } = compilation;
|
|
const { exposeFileNameImportMap } = this._containerManager;
|
|
const assets = {};
|
|
chunks.forEach((chunk)=>{
|
|
if (typeof chunk.name !== 'string') return;
|
|
// Support split chunks caused by splitChunks.maxSize:
|
|
// A chunk named "__federation_expose_Foo" may be split into
|
|
// "__federation_expose_Foo-<hash>" chunks, so we match both exact
|
|
// and prefix+dash patterns.
|
|
const matchedKey = exposeFileNameImportMap[chunk.name] !== undefined ? chunk.name : Object.keys(exposeFileNameImportMap).find((key)=>chunk.name.startsWith(key + '-'));
|
|
if (!matchedKey) return;
|
|
// TODO: support multiple import
|
|
const exposeKey = exposeFileNameImportMap[matchedKey][0];
|
|
const assetKey = getFileNameWithOutExt(exposeKey);
|
|
const chunkAssets = getAssetsByChunk(chunk, entryPointNames);
|
|
if (!assets[assetKey]) {
|
|
assets[assetKey] = chunkAssets;
|
|
} else {
|
|
// Merge split chunk assets, deduplicating with Set
|
|
assets[assetKey] = {
|
|
js: {
|
|
sync: [
|
|
...new Set([
|
|
...assets[assetKey].js.sync,
|
|
...chunkAssets.js.sync
|
|
])
|
|
],
|
|
async: [
|
|
...new Set([
|
|
...assets[assetKey].js.async,
|
|
...chunkAssets.js.async
|
|
])
|
|
]
|
|
},
|
|
css: {
|
|
sync: [
|
|
...new Set([
|
|
...assets[assetKey].css.sync,
|
|
...chunkAssets.css.sync
|
|
])
|
|
],
|
|
async: [
|
|
...new Set([
|
|
...assets[assetKey].css.async,
|
|
...chunkAssets.css.async
|
|
])
|
|
]
|
|
}
|
|
};
|
|
}
|
|
});
|
|
return assets;
|
|
}
|
|
_getProvideSharedAssets(compilation, stats, entryPointNames) {
|
|
const sharedModules = stats.modules.filter((module)=>{
|
|
if (!module || !module.name) {
|
|
return false;
|
|
}
|
|
const array = [
|
|
module.name.includes('consume shared module ')
|
|
];
|
|
return array.some((item)=>item);
|
|
});
|
|
const manifestOverrideChunkIDMap = {};
|
|
const effectiveSharedModules = getSharedModules(stats, sharedModules);
|
|
effectiveSharedModules.forEach((item)=>{
|
|
const [sharedModuleName, sharedModule] = item;
|
|
if (!manifestOverrideChunkIDMap[sharedModuleName]) {
|
|
manifestOverrideChunkIDMap[sharedModuleName] = {
|
|
async: new Set(),
|
|
sync: new Set()
|
|
};
|
|
}
|
|
sharedModule.chunks.forEach((chunkID)=>{
|
|
const chunk = findChunk(chunkID, compilation.chunks);
|
|
manifestOverrideChunkIDMap[sharedModuleName].sync.add(chunkID);
|
|
if (!chunk) {
|
|
return;
|
|
}
|
|
[
|
|
...chunk.groupsIterable
|
|
].forEach((group)=>{
|
|
if (group.name && !entryPointNames.includes(group.name)) {
|
|
manifestOverrideChunkIDMap[sharedModuleName].sync.add(group.id);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
const assets = {
|
|
js: {
|
|
async: [],
|
|
sync: []
|
|
},
|
|
css: {
|
|
async: [],
|
|
sync: []
|
|
}
|
|
};
|
|
Object.keys(manifestOverrideChunkIDMap).forEach((override)=>{
|
|
const asyncAssets = getAssetsByChunkIDs(compilation, {
|
|
[override]: manifestOverrideChunkIDMap[override].async
|
|
});
|
|
const syncAssets = getAssetsByChunkIDs(compilation, {
|
|
[override]: manifestOverrideChunkIDMap[override].sync
|
|
});
|
|
assets[override] = {
|
|
js: {
|
|
async: asyncAssets[override].js,
|
|
sync: syncAssets[override].js
|
|
},
|
|
css: {
|
|
async: asyncAssets[override].css,
|
|
sync: syncAssets[override].css
|
|
}
|
|
};
|
|
});
|
|
return assets;
|
|
}
|
|
async _generateStats(compiler, compilation, extraOptions) {
|
|
try {
|
|
const { name, manifest: manifestOptions = {}, exposes = {} } = this._options;
|
|
const metaData = this._getMetaData(compiler, compilation, extraOptions);
|
|
const stats = {
|
|
id: name,
|
|
name: name,
|
|
metaData,
|
|
shared: [],
|
|
remotes: [],
|
|
exposes: []
|
|
};
|
|
if (typeof manifestOptions === 'object' && manifestOptions.disableAssetsAnalyze) {
|
|
const remotes = this._remoteManager.statsRemoteWithEmptyUsedIn;
|
|
stats.remotes = remotes;
|
|
stats.exposes = Object.keys(exposes).map((exposeKey)=>{
|
|
return getExposeItem({
|
|
exposeKey,
|
|
name: name,
|
|
file: {
|
|
import: exposes[exposeKey].import
|
|
}
|
|
});
|
|
});
|
|
stats.shared = Object.entries(this._sharedManager.normalizedOptions).reduce((sum, cur)=>{
|
|
const [pkgName, normalizedShareOptions] = cur;
|
|
sum.push(getShareItem({
|
|
pkgName,
|
|
normalizedShareOptions,
|
|
pkgVersion: normalizedShareOptions.version || UNKNOWN_MODULE_NAME,
|
|
hostName: name
|
|
}));
|
|
return sum;
|
|
}, []);
|
|
return stats;
|
|
}
|
|
const liveStats = compilation.getStats();
|
|
const statsOptions = {
|
|
all: false,
|
|
modules: true,
|
|
builtAt: true,
|
|
hash: true,
|
|
ids: true,
|
|
version: true,
|
|
entrypoints: true,
|
|
assets: false,
|
|
chunks: false,
|
|
reasons: true
|
|
};
|
|
if (this._bundler === 'webpack') {
|
|
statsOptions['cached'] = true;
|
|
}
|
|
statsOptions['cachedModules'] = true;
|
|
const webpackStats = liveStats.toJson(statsOptions);
|
|
const filteredModules = this._getFilteredModules(webpackStats);
|
|
const moduleHandler = new ModuleHandler(this._options, filteredModules, {
|
|
bundler: this._bundler
|
|
});
|
|
const { remotes, exposesMap, sharedMap } = moduleHandler.collect();
|
|
const entryPointNames = [
|
|
...compilation.entrypoints.values()
|
|
].map((e)=>e.name).filter((v)=>!!v);
|
|
await Promise.all([
|
|
new Promise((resolve)=>{
|
|
const sharedAssets = this._getProvideSharedAssets(compilation, webpackStats, entryPointNames);
|
|
Object.keys(sharedMap).forEach((sharedKey)=>{
|
|
const assets = sharedAssets[sharedKey];
|
|
if (assets) {
|
|
sharedMap[sharedKey].assets = assets;
|
|
}
|
|
});
|
|
resolve();
|
|
}),
|
|
new Promise((resolve)=>{
|
|
const moduleAssets = this._getModuleAssets(compilation, entryPointNames);
|
|
Object.keys(exposesMap).forEach((exposeKey)=>{
|
|
const assets = moduleAssets[exposeKey];
|
|
if (assets) {
|
|
exposesMap[exposeKey].assets = assets;
|
|
}
|
|
exposesMap[exposeKey].requires = Array.from(new Set(exposesMap[exposeKey].requires));
|
|
});
|
|
resolve();
|
|
})
|
|
]);
|
|
await Promise.all([
|
|
new Promise((resolve)=>{
|
|
const remoteMemo = new Set();
|
|
stats.remotes = remotes.map((remote)=>{
|
|
remoteMemo.add(remote.federationContainerName);
|
|
return {
|
|
...remote,
|
|
usedIn: Array.from(remote.usedIn.values())
|
|
};
|
|
});
|
|
const statsRemoteWithEmptyUsedIn = this._remoteManager.statsRemoteWithEmptyUsedIn;
|
|
statsRemoteWithEmptyUsedIn.forEach((remoteInfo)=>{
|
|
if (!remoteMemo.has(remoteInfo.federationContainerName)) {
|
|
stats.remotes.push(remoteInfo);
|
|
}
|
|
});
|
|
resolve();
|
|
}),
|
|
new Promise((resolve)=>{
|
|
stats.shared = Object.values(sharedMap).map((shared)=>({
|
|
...shared,
|
|
usedIn: Array.from(shared.usedIn)
|
|
}));
|
|
resolve();
|
|
})
|
|
]);
|
|
await new Promise((resolve)=>{
|
|
const sharedAssets = stats.shared.reduce((sum, shared)=>{
|
|
const { js, css } = shared.assets;
|
|
[
|
|
...js.sync,
|
|
...js.async,
|
|
...css.async,
|
|
css.sync
|
|
].forEach((asset)=>{
|
|
sum.add(asset);
|
|
});
|
|
return sum;
|
|
}, new Set());
|
|
const { fileExposeKeyMap } = this._containerManager;
|
|
stats.exposes = [];
|
|
Object.entries(fileExposeKeyMap).forEach(([exposeFileWithoutExt, exposeKeySet])=>{
|
|
const expose = exposesMap[exposeFileWithoutExt] || {
|
|
assets: {
|
|
js: {
|
|
sync: [],
|
|
async: []
|
|
},
|
|
css: {
|
|
sync: [],
|
|
async: []
|
|
}
|
|
}
|
|
};
|
|
exposeKeySet.forEach((exposeKey)=>{
|
|
const { js, css } = expose.assets;
|
|
const exposeModuleName = getExposeName(exposeKey);
|
|
stats.exposes.push({
|
|
...expose,
|
|
path: exposeKey,
|
|
id: composeKeyWithSeparator(this._options.name, exposeModuleName),
|
|
name: exposeModuleName,
|
|
assets: {
|
|
js: {
|
|
sync: js.sync.filter((asset)=>!sharedAssets.has(asset)),
|
|
async: js.async.filter((asset)=>!sharedAssets.has(asset))
|
|
},
|
|
css: {
|
|
sync: css.sync.filter((asset)=>!sharedAssets.has(asset)),
|
|
async: css.async.filter((asset)=>!sharedAssets.has(asset))
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
Object.values(exposesMap).map((expose)=>{
|
|
const { js, css } = expose.assets;
|
|
return {
|
|
...expose,
|
|
assets: {
|
|
js: {
|
|
sync: js.sync.filter((asset)=>!sharedAssets.has(asset)),
|
|
async: js.async.filter((asset)=>!sharedAssets.has(asset))
|
|
},
|
|
css: {
|
|
sync: css.sync.filter((asset)=>!sharedAssets.has(asset)),
|
|
async: css.async.filter((asset)=>!sharedAssets.has(asset))
|
|
}
|
|
}
|
|
};
|
|
});
|
|
resolve();
|
|
});
|
|
return stats;
|
|
} catch (err) {
|
|
throw err;
|
|
}
|
|
}
|
|
getPublicPath(compiler) {
|
|
if (this._publicPath) {
|
|
return this._publicPath;
|
|
}
|
|
const { output: { publicPath: originalPublicPath } } = compiler.options;
|
|
let publicPath = originalPublicPath;
|
|
this._publicPath = publicPath;
|
|
return publicPath;
|
|
}
|
|
init(options, { pluginVersion, bundler }) {
|
|
this._options = options;
|
|
this._pluginVersion = pluginVersion;
|
|
this._bundler = bundler;
|
|
this._containerManager = new ContainerManager();
|
|
this._containerManager.init(options);
|
|
this._remoteManager = new RemoteManager();
|
|
this._remoteManager.init(options);
|
|
this._sharedManager = new SharedManager();
|
|
this._sharedManager.init(options);
|
|
}
|
|
updateStats(stats, compiler) {
|
|
const { metaData } = stats;
|
|
if (!metaData.types) {
|
|
metaData.types = getTypesMetaInfo(this._options, compiler.context);
|
|
}
|
|
if (!metaData.pluginVersion) {
|
|
metaData.pluginVersion = this._pluginVersion;
|
|
}
|
|
this.setMetaDataPublicPath(metaData, compiler);
|
|
return stats;
|
|
}
|
|
async generateStats(compiler, compilation) {
|
|
try {
|
|
const stats = await this._generateStats(compiler, compilation);
|
|
return stats;
|
|
} catch (err) {
|
|
throw err;
|
|
}
|
|
}
|
|
validate(compiler) {
|
|
const { output: { publicPath } } = compiler.options;
|
|
if (typeof publicPath !== 'string') {
|
|
logger.warn(`Manifest will not generate, because publicPath can only be string, but got '${publicPath}'`);
|
|
return false;
|
|
} else if (publicPath === 'auto') {
|
|
logger.warn(`Manifest will use absolute path resolution via its host at runtime, reason: publicPath='${publicPath}'`);
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
constructor(){
|
|
this._options = {};
|
|
this._bundler = 'webpack';
|
|
this._containerManager = new ContainerManager();
|
|
this._remoteManager = new RemoteManager();
|
|
this._sharedManager = new SharedManager();
|
|
this._pkgJsonManager = new PKGJsonManager();
|
|
}
|
|
}
|
|
|
|
|
|
export { StatsManager };
|