@esmx/rspack
Version:
A high-performance Rspack integration for Esmx microfrontend framework, providing Module Linking and SSR capabilities.
180 lines (163 loc) • 5.85 kB
text/typescript
import type { Compilation, Compiler, StatsCompilation } from '@rspack/core';
import upath from 'upath';
import type {
ManifestJson,
ManifestJsonChunks,
ManifestJsonExports,
ParsedModuleLinkPluginOptions
} from './types';
export const RSPACK_PLUGIN_NAME = 'rspack-module-link-plugin';
export class ManifestPlugin {
constructor(private opts: ParsedModuleLinkPluginOptions) {}
apply(compiler: Compiler) {
const opts = this.opts;
const { Compilation } = compiler.rspack;
compiler.hooks.thisCompilation.tap(
RSPACK_PLUGIN_NAME,
(compilation) => {
let manifestJson: ManifestJson = {
name: opts.name,
exports: {},
scopes: opts.scopes,
files: [],
chunks: {}
};
compilation.hooks.processAssets.tap(
{
name: RSPACK_PLUGIN_NAME,
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
},
(assets) => {
const stats = compilation.getStats().toJson({
hash: true,
entrypoints: true
});
const exports = getExports(opts, stats);
const resources = Object.keys(assets)
.map(transFileName)
.filter((file) => !file.includes('hot-update'));
manifestJson = {
name: opts.name,
exports: exports,
scopes: opts.scopes,
files: resources,
chunks: getChunks(opts, compilation)
};
const { RawSource } = compiler.rspack.sources;
compilation.emitAsset(
'manifest.json',
new RawSource(JSON.stringify(manifestJson, null, 4))
);
}
);
if (opts.injectChunkName) {
compilation.hooks.processAssets.tap(
{
name: RSPACK_PLUGIN_NAME,
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
},
(assets) => {
const { RawSource } = compiler.rspack.sources;
for (const [key, value] of Object.entries(
manifestJson.chunks
)) {
const asset = assets[value.js];
if (!asset) {
return;
}
const source = new RawSource(
`import.meta.chunkName = import.meta.chunkName ?? ${JSON.stringify(key)};\n${asset.source()}`
);
compilation.updateAsset(value.js, source);
}
}
);
}
}
);
}
}
function transFileName(fileName: string): string {
return fileName.replace(/^.\//, '');
}
export function getExports(
opts: ParsedModuleLinkPluginOptions,
stats: StatsCompilation
): ManifestJsonExports {
const entrypoints = stats.entrypoints || {};
const exports: ManifestJsonExports = {};
for (const [key, value] of Object.entries(entrypoints)) {
const asset = value.assets?.find((item) => {
return (
item.name.endsWith(opts.ext) &&
item.name.startsWith(key) &&
!item.name.includes('hot-update')
);
});
if (!asset) continue;
if (key in opts.exports) {
exports[key] = {
...opts.exports[key],
file: asset.name
};
}
}
return exports;
}
function getChunks(
opts: ParsedModuleLinkPluginOptions,
compilation: Compilation
) {
const stats = compilation.getStats().toJson({
all: false,
chunks: true,
modules: true,
chunkModules: true,
ids: true
});
const buildChunks: ManifestJsonChunks = {};
if (!stats.chunks) return buildChunks;
for (const chunk of stats.chunks) {
const module = chunk.modules
?.sort((a, b) => {
return (a.index ?? -1) - (b?.index ?? -1);
})
?.find((module) => {
return module.moduleType?.includes('javascript/');
});
if (!module?.nameForCondition) continue;
const js = chunk.files?.find((file) => file.endsWith(opts.ext));
if (!js) continue;
const root = compilation.options.context ?? process.cwd();
const name = generateIdentifier({
root,
name: opts.name,
filePath: module.nameForCondition
});
const css = chunk.files?.filter((file) => file.endsWith('.css')) ?? [];
const resources = chunk.auxiliaryFiles ?? [];
buildChunks[name] = {
name,
js,
css,
resources
};
}
return buildChunks;
}
export function generateIdentifier({
root,
name,
filePath
}: {
root: string;
name: string;
filePath: string;
}) {
const unixFilePath = upath.toUnix(filePath);
if (!root) {
return `${name}@${unixFilePath}`;
}
const file = upath.relative(upath.toUnix(root), unixFilePath);
return `${name}@${file}`;
}