@esmx/rspack 3.0.0-rc.117 → 3.0.0-rc.118
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 +15 -1
- package/README.zh-CN.md +18 -4
- package/dist/module-link/config.d.ts +1 -1
- package/dist/module-link/config.mjs +7 -3
- package/dist/module-link/config1.d.ts +1 -1
- package/dist/module-link/index.d.ts +2 -2
- package/dist/module-link/index.mjs +2 -2
- package/dist/module-link/manifest-plugin.d.ts +22 -2
- package/dist/module-link/manifest-plugin.mjs +26 -16
- package/dist/module-link/manifest-plugin.test.d.ts +1 -0
- package/dist/module-link/manifest-plugin.test.mjs +195 -0
- package/dist/module-link/parse.mjs +2 -1
- package/dist/module-link/types.d.ts +10 -0
- package/dist/rspack/app.d.ts +1 -1
- package/dist/rspack/app.mjs +51 -6
- package/dist/rspack/chain-config.d.ts +3 -3
- package/dist/rspack/chain-config.mjs +67 -28
- package/dist/rspack-html/index.mjs +2 -9
- package/package.json +14 -9
- package/src/module-link/config.ts +13 -4
- package/src/module-link/config1.ts +1 -1
- package/src/module-link/index.ts +6 -5
- package/src/module-link/manifest-plugin.test.ts +227 -0
- package/src/module-link/manifest-plugin.ts +58 -21
- package/src/module-link/parse.ts +2 -1
- package/src/module-link/types.ts +10 -0
- package/src/rspack/app.ts +87 -8
- package/src/rspack/chain-config.ts +102 -31
- package/src/rspack-html/index.ts +6 -11
- package/dist/module-link/config2.d.ts +0 -3
- package/dist/module-link/config2.mjs +0 -17
- package/src/module-link/config2.ts +0 -28
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { buildPkgWrapper } from "@esmx/pkg-wrapper";
|
|
1
3
|
import { rspack } from "@rspack/core";
|
|
2
|
-
import RspackChain from "rspack-chain";
|
|
4
|
+
import { RspackChain } from "rspack-chain";
|
|
5
|
+
import RspackVirtualModulePlugin from "rspack-plugin-virtual-module";
|
|
3
6
|
import nodeExternals from "webpack-node-externals";
|
|
4
7
|
import { initModuleLink } from "../module-link/index.mjs";
|
|
5
|
-
export function createChainConfig(esmx, buildTarget, options) {
|
|
8
|
+
export async function createChainConfig(esmx, buildTarget, options) {
|
|
6
9
|
const isHot = buildTarget === "client" && !esmx.isProd;
|
|
7
10
|
const isClient = buildTarget === "client";
|
|
8
11
|
const isServer = buildTarget === "server";
|
|
@@ -38,8 +41,7 @@ export function createChainConfig(esmx, buildTarget, options) {
|
|
|
38
41
|
}
|
|
39
42
|
chain.module.parser.set("javascript", {
|
|
40
43
|
url: isClient ? true : "relative",
|
|
41
|
-
importMeta: false
|
|
42
|
-
strictExportPresence: true
|
|
44
|
+
importMeta: false
|
|
43
45
|
});
|
|
44
46
|
chain.module.generator.set("asset", {
|
|
45
47
|
emit: isClient
|
|
@@ -48,7 +50,7 @@ export function createChainConfig(esmx, buildTarget, options) {
|
|
|
48
50
|
emit: isClient
|
|
49
51
|
});
|
|
50
52
|
chain.resolve.alias.set(esmx.name, esmx.root);
|
|
51
|
-
chain.optimization.minimize(options.minimize ?? esmx.isProd).emitOnErrors(true);
|
|
53
|
+
chain.optimization.minimize(options.minimize ?? esmx.isProd).emitOnErrors(true).runtimeChunk("single");
|
|
52
54
|
chain.externalsPresets({
|
|
53
55
|
web: isClient,
|
|
54
56
|
node: isServer || isNode
|
|
@@ -63,31 +65,39 @@ export function createChainConfig(esmx, buildTarget, options) {
|
|
|
63
65
|
})
|
|
64
66
|
]);
|
|
65
67
|
}
|
|
66
|
-
chain.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
nativeWatcher: true,
|
|
70
|
-
rspackFuture: {
|
|
71
|
-
bundlerInfo: { force: false }
|
|
72
|
-
}
|
|
73
|
-
});
|
|
68
|
+
chain.output.set("module", true);
|
|
69
|
+
chain.output.bundlerInfo({ force: false });
|
|
70
|
+
chain.set("nativeWatcher", true);
|
|
74
71
|
chain.set("lazyCompilation", false);
|
|
75
|
-
|
|
72
|
+
const { linkOpts, virtualModules } = await createModuleLinkConfig(
|
|
73
|
+
esmx,
|
|
74
|
+
buildTarget
|
|
75
|
+
);
|
|
76
|
+
if (Object.keys(virtualModules).length > 0) {
|
|
77
|
+
chain.plugin("esmx-pkg-virtual-modules").use(RspackVirtualModulePlugin, [
|
|
78
|
+
virtualModules,
|
|
79
|
+
`.esmx-virtual-${esmx.name}`
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
initModuleLink(chain, linkOpts, esmx.isProd);
|
|
76
83
|
return chain;
|
|
77
84
|
}
|
|
78
|
-
function createModuleLinkConfig(esmx, buildTarget) {
|
|
85
|
+
async function createModuleLinkConfig(esmx, buildTarget) {
|
|
79
86
|
const isClient = buildTarget === "client";
|
|
80
87
|
const isServer = buildTarget === "server";
|
|
81
88
|
const isNode = buildTarget === "node";
|
|
82
89
|
if (isNode) {
|
|
83
90
|
return {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
linkOpts: {
|
|
92
|
+
name: esmx.name,
|
|
93
|
+
exports: {
|
|
94
|
+
"src/entry.node": {
|
|
95
|
+
pkg: false,
|
|
96
|
+
file: "./src/entry.node"
|
|
97
|
+
}
|
|
89
98
|
}
|
|
90
|
-
}
|
|
99
|
+
},
|
|
100
|
+
virtualModules: {}
|
|
91
101
|
};
|
|
92
102
|
}
|
|
93
103
|
const preEntries = [];
|
|
@@ -96,16 +106,45 @@ function createModuleLinkConfig(esmx, buildTarget) {
|
|
|
96
106
|
`${import.meta.resolve("webpack-hot-middleware/client.js")}?path=/${esmx.name}/hot-middleware`
|
|
97
107
|
);
|
|
98
108
|
}
|
|
109
|
+
const env = esmx.moduleConfig.environments[buildTarget];
|
|
110
|
+
const patchedExports = {};
|
|
111
|
+
const virtualModules = {};
|
|
112
|
+
const wrapperFiles = [];
|
|
113
|
+
const virtualBaseDir = path.join(
|
|
114
|
+
esmx.root,
|
|
115
|
+
"node_modules",
|
|
116
|
+
`.esmx-virtual-${esmx.name}`
|
|
117
|
+
);
|
|
118
|
+
for (const [name, exp] of Object.entries(env.exports)) {
|
|
119
|
+
if (exp.pkg && exp.file) {
|
|
120
|
+
const { source } = await buildPkgWrapper({
|
|
121
|
+
root: esmx.root,
|
|
122
|
+
spec: exp.file
|
|
123
|
+
});
|
|
124
|
+
const safeName = `${exp.file.replace(/[^A-Za-z0-9_-]/g, "_")}.mjs`;
|
|
125
|
+
virtualModules[safeName] = source;
|
|
126
|
+
const actualPath = path.join(virtualBaseDir, safeName);
|
|
127
|
+
wrapperFiles.push(actualPath);
|
|
128
|
+
patchedExports[name] = { ...exp, file: actualPath };
|
|
129
|
+
} else {
|
|
130
|
+
patchedExports[name] = exp;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
99
133
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
134
|
+
linkOpts: {
|
|
135
|
+
...env,
|
|
136
|
+
exports: patchedExports,
|
|
137
|
+
name: esmx.name,
|
|
138
|
+
injectChunkName: isServer,
|
|
139
|
+
deps: Object.keys(esmx.moduleConfig.links),
|
|
140
|
+
preEntries,
|
|
141
|
+
wrapperFiles
|
|
142
|
+
},
|
|
143
|
+
virtualModules
|
|
105
144
|
};
|
|
106
145
|
}
|
|
107
|
-
export function createRspackConfig(esmx, buildTarget, options) {
|
|
108
|
-
const chain = createChainConfig(esmx, buildTarget, options);
|
|
146
|
+
export async function createRspackConfig(esmx, buildTarget, options) {
|
|
147
|
+
const chain = await createChainConfig(esmx, buildTarget, options);
|
|
109
148
|
options.chain?.({ esmx, options, buildTarget, chain });
|
|
110
149
|
const config = chain.toConfig();
|
|
111
150
|
options.config?.({ esmx, options, buildTarget, config });
|
|
@@ -89,7 +89,7 @@ function configureOptimization(chain, options) {
|
|
|
89
89
|
{
|
|
90
90
|
minimizerOptions: {
|
|
91
91
|
targets: getTargetSetting(options?.target, "client"),
|
|
92
|
-
errorRecovery:
|
|
92
|
+
errorRecovery: true
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
]);
|
|
@@ -139,14 +139,7 @@ function configureCssInJS(chain, esmx, options) {
|
|
|
139
139
|
lessRule.type("javascript/auto");
|
|
140
140
|
}
|
|
141
141
|
function configureCssExtract(chain, options) {
|
|
142
|
-
chain.
|
|
143
|
-
...chain.get("experiments") ?? {},
|
|
144
|
-
css: true
|
|
145
|
-
});
|
|
146
|
-
const experiments = chain.get("experiments");
|
|
147
|
-
if (!experiments || !experiments.css) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
142
|
+
chain.module.rule("css").test(/\.css$/).type("css/auto");
|
|
150
143
|
const lessRule = chain.module.rule("less").test(/\.less$/).use("less-loader").loader(options.loaders?.lessLoader ?? RSPACK_LOADER.lessLoader).options(options.lessLoader ?? {}).end();
|
|
151
144
|
if (options.styleResourcesLoader) {
|
|
152
145
|
lessRule.use("style-resources-loader").loader(
|
package/package.json
CHANGED
|
@@ -63,35 +63,37 @@
|
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@esmx/import": "3.0.0-rc.
|
|
66
|
+
"@esmx/import": "3.0.0-rc.118",
|
|
67
|
+
"@esmx/pkg-wrapper": "3.0.0-rc.118",
|
|
67
68
|
"@npmcli/arborist": "^9.1.6",
|
|
68
|
-
"@rspack/core": "
|
|
69
|
+
"@rspack/core": "^2.0.6",
|
|
69
70
|
"css-loader": "^7.1.2",
|
|
70
71
|
"less-loader": "^12.3.0",
|
|
71
72
|
"node-polyfill-webpack-plugin": "^4.1.0",
|
|
72
73
|
"pacote": "^21.0.0",
|
|
73
|
-
"rspack-chain": "^
|
|
74
|
+
"rspack-chain": "^2.0.2",
|
|
75
|
+
"rspack-plugin-virtual-module": "^1.0.1",
|
|
74
76
|
"style-loader": "^4.0.0",
|
|
75
77
|
"style-resources-loader": "^1.5.0",
|
|
76
78
|
"upath": "^2.0.1",
|
|
77
79
|
"webpack-hot-middleware": "^2.26.1",
|
|
78
80
|
"webpack-node-externals": "~3.0.0",
|
|
79
|
-
"worker-rspack-loader": "^3.1.
|
|
81
|
+
"worker-rspack-loader": "^3.1.5"
|
|
80
82
|
},
|
|
81
83
|
"devDependencies": {
|
|
82
84
|
"@biomejs/biome": "2.3.7",
|
|
83
|
-
"@esmx/core": "3.0.0-rc.
|
|
85
|
+
"@esmx/core": "3.0.0-rc.118",
|
|
84
86
|
"@types/node": "^24.0.0",
|
|
85
87
|
"@types/npmcli__arborist": "^6.3.1",
|
|
86
88
|
"@types/pacote": "^11.1.8",
|
|
87
89
|
"@types/webpack-hot-middleware": "^2.25.12",
|
|
88
90
|
"@types/webpack-node-externals": "^3.0.4",
|
|
89
|
-
"@vitest/coverage-v8": "3.2.
|
|
91
|
+
"@vitest/coverage-v8": "3.2.6",
|
|
90
92
|
"typescript": "5.9.3",
|
|
91
93
|
"unbuild": "3.6.1",
|
|
92
|
-
"vitest": "3.2.
|
|
94
|
+
"vitest": "3.2.6"
|
|
93
95
|
},
|
|
94
|
-
"version": "3.0.0-rc.
|
|
96
|
+
"version": "3.0.0-rc.118",
|
|
95
97
|
"type": "module",
|
|
96
98
|
"private": false,
|
|
97
99
|
"exports": {
|
|
@@ -110,5 +112,8 @@
|
|
|
110
112
|
"template",
|
|
111
113
|
"public"
|
|
112
114
|
],
|
|
113
|
-
"gitHead": "
|
|
115
|
+
"gitHead": "2fdbd62470f64e6e45568550941c78cb259e4917",
|
|
116
|
+
"publishConfig": {
|
|
117
|
+
"access": "public"
|
|
118
|
+
}
|
|
114
119
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExternalItem, ExternalItemFunctionData } from '@rspack/core';
|
|
2
|
-
import type RspackChain from 'rspack-chain';
|
|
2
|
+
import type { RspackChain } from 'rspack-chain';
|
|
3
3
|
import type { ParsedModuleLinkPluginOptions } from './types';
|
|
4
4
|
|
|
5
5
|
export function applyEntryConfig(
|
|
@@ -73,9 +73,11 @@ function createExternalsFunction(
|
|
|
73
73
|
importMap.set(identifier, identifier);
|
|
74
74
|
importMap.set(value.name, identifier);
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
if (value.file) {
|
|
77
|
+
const resolvedPath = await resolvePath(value.file);
|
|
78
|
+
if (resolvedPath) {
|
|
79
|
+
importMap.set(resolvedPath, identifier);
|
|
80
|
+
}
|
|
79
81
|
}
|
|
80
82
|
})
|
|
81
83
|
);
|
|
@@ -118,6 +120,8 @@ function createExternalsFunction(
|
|
|
118
120
|
const FILE_EXT_REGEX =
|
|
119
121
|
/\.worker\.(js|mjs|cjs|jsx|mjsx|cjsx|ts|mts|cts|tsx|mtsx|ctsx)$/i;
|
|
120
122
|
|
|
123
|
+
const wrapperFiles = new Set(opts.wrapperFiles);
|
|
124
|
+
|
|
121
125
|
return async (data: ExternalItemFunctionData) => {
|
|
122
126
|
if (
|
|
123
127
|
!data.request ||
|
|
@@ -127,6 +131,11 @@ function createExternalsFunction(
|
|
|
127
131
|
)
|
|
128
132
|
return;
|
|
129
133
|
|
|
134
|
+
// A pkg-export wrapper file IS the federation chunk for its package.
|
|
135
|
+
// Don't externalize its inner `import __m from '<abs-pkg-path>'` —
|
|
136
|
+
// that would route back to the wrapper itself (cyclic).
|
|
137
|
+
if (wrapperFiles.has(data.contextInfo.issuer)) return;
|
|
138
|
+
|
|
130
139
|
const defaultContext = compilerContext;
|
|
131
140
|
const resolvePath: ResolvePath = async (
|
|
132
141
|
request: string,
|
package/src/module-link/index.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import type RspackChain from 'rspack-chain';
|
|
1
|
+
import type { RspackChain } from 'rspack-chain';
|
|
2
2
|
import { applyChainConfig1 } from './config1';
|
|
3
|
-
// import { applyChainConfig2 } from './config2';
|
|
4
3
|
import { ManifestPlugin } from './manifest-plugin';
|
|
5
4
|
import { parseOptions } from './parse';
|
|
6
5
|
import type { ModuleLinkPluginOptions } from './types';
|
|
7
6
|
|
|
8
7
|
export function initModuleLink(
|
|
9
8
|
chain: RspackChain,
|
|
10
|
-
options: ModuleLinkPluginOptions
|
|
9
|
+
options: ModuleLinkPluginOptions,
|
|
10
|
+
isProduction = false
|
|
11
11
|
): void {
|
|
12
12
|
const opts = parseOptions(options);
|
|
13
13
|
applyChainConfig1(chain, opts);
|
|
14
|
-
// applyChainConfig2(chain, opts);
|
|
15
14
|
|
|
16
|
-
chain
|
|
15
|
+
chain
|
|
16
|
+
.plugin('module-link-manifest')
|
|
17
|
+
.use(ManifestPlugin, [opts, isProduction]);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export type { ModuleLinkPluginOptions } from './types';
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { StatsCompilation } from '@rspack/core';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { generateIdentifier, getChunks, getExports } from './manifest-plugin';
|
|
4
|
+
import type { ParsedModuleLinkPluginOptions } from './types';
|
|
5
|
+
|
|
6
|
+
const baseOpts: ParsedModuleLinkPluginOptions = {
|
|
7
|
+
name: 'remote',
|
|
8
|
+
exports: {},
|
|
9
|
+
imports: {},
|
|
10
|
+
scopes: {},
|
|
11
|
+
ext: '.mjs',
|
|
12
|
+
deps: [],
|
|
13
|
+
preEntries: [],
|
|
14
|
+
wrapperFiles: [],
|
|
15
|
+
injectChunkName: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const ROOT = '/abs/project';
|
|
19
|
+
|
|
20
|
+
function mkChunk(o: {
|
|
21
|
+
files?: string[];
|
|
22
|
+
modules?: Array<{
|
|
23
|
+
nameForCondition?: string;
|
|
24
|
+
moduleType?: string;
|
|
25
|
+
index?: number;
|
|
26
|
+
}>;
|
|
27
|
+
auxiliaryFiles?: string[];
|
|
28
|
+
}): NonNullable<StatsCompilation['chunks']>[number] {
|
|
29
|
+
return {
|
|
30
|
+
files: o.files ?? [],
|
|
31
|
+
auxiliaryFiles: o.auxiliaryFiles ?? [],
|
|
32
|
+
modules: (o.modules ?? []).map((m, i) => ({
|
|
33
|
+
id: i,
|
|
34
|
+
index: m.index ?? i,
|
|
35
|
+
nameForCondition: m.nameForCondition,
|
|
36
|
+
moduleType: m.moduleType ?? 'javascript/auto'
|
|
37
|
+
}))
|
|
38
|
+
} as never;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('getChunks — CSS extraction', () => {
|
|
42
|
+
it('populates css[] when a chunk emitted .css files', () => {
|
|
43
|
+
const stats: StatsCompilation = {
|
|
44
|
+
chunks: [
|
|
45
|
+
mkChunk({
|
|
46
|
+
files: [
|
|
47
|
+
'src/entry.client.95f6085b.final.mjs',
|
|
48
|
+
'src/entry.client.a73d6772.final.css'
|
|
49
|
+
],
|
|
50
|
+
modules: [
|
|
51
|
+
{
|
|
52
|
+
nameForCondition: `${ROOT}/src/entry.client.ts`,
|
|
53
|
+
moduleType: 'javascript/auto'
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const chunks = getChunks(baseOpts, stats, ROOT);
|
|
61
|
+
const entry = chunks['remote@src/entry.client.ts'];
|
|
62
|
+
expect(entry).toBeDefined();
|
|
63
|
+
expect(entry.js).toBe('src/entry.client.95f6085b.final.mjs');
|
|
64
|
+
expect(entry.css).toEqual(['src/entry.client.a73d6772.final.css']);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('returns empty css[] when chunk has no stylesheet', () => {
|
|
68
|
+
const stats: StatsCompilation = {
|
|
69
|
+
chunks: [
|
|
70
|
+
mkChunk({
|
|
71
|
+
files: ['vue.b8a9c2d3.final.mjs'],
|
|
72
|
+
modules: [
|
|
73
|
+
{
|
|
74
|
+
nameForCondition: `${ROOT}/node_modules/vue/index.mjs`,
|
|
75
|
+
moduleType: 'javascript/esm'
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const entry = getChunks(baseOpts, stats, ROOT)[
|
|
83
|
+
'remote@node_modules/vue/index.mjs'
|
|
84
|
+
];
|
|
85
|
+
expect(entry).toBeDefined();
|
|
86
|
+
expect(entry.css).toEqual([]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('extracts multiple CSS chunks per JS chunk (split stylesheets)', () => {
|
|
90
|
+
const stats: StatsCompilation = {
|
|
91
|
+
chunks: [
|
|
92
|
+
mkChunk({
|
|
93
|
+
files: [
|
|
94
|
+
'chunks/widget.111.final.mjs',
|
|
95
|
+
'chunks/widget.111.final.css',
|
|
96
|
+
'chunks/widget.111.theme.css'
|
|
97
|
+
],
|
|
98
|
+
modules: [
|
|
99
|
+
{
|
|
100
|
+
nameForCondition: `${ROOT}/src/widget.ts`,
|
|
101
|
+
moduleType: 'javascript/esm'
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const entry = getChunks(baseOpts, stats, ROOT)['remote@src/widget.ts'];
|
|
109
|
+
expect(entry.css).toEqual([
|
|
110
|
+
'chunks/widget.111.final.css',
|
|
111
|
+
'chunks/widget.111.theme.css'
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('ignores chunks without a JS module (CSS-only / asset-only)', () => {
|
|
116
|
+
const stats: StatsCompilation = {
|
|
117
|
+
chunks: [
|
|
118
|
+
mkChunk({
|
|
119
|
+
files: ['orphan.111.final.css'],
|
|
120
|
+
modules: []
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
expect(getChunks(baseOpts, stats, ROOT)).toEqual({});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('respects opts.ext for the JS file lookup (.cjs / .js / .mjs)', () => {
|
|
129
|
+
const stats: StatsCompilation = {
|
|
130
|
+
chunks: [
|
|
131
|
+
mkChunk({
|
|
132
|
+
files: ['src/x.111.js', 'src/x.111.css'],
|
|
133
|
+
modules: [
|
|
134
|
+
{
|
|
135
|
+
nameForCondition: `${ROOT}/src/x.ts`,
|
|
136
|
+
moduleType: 'javascript/auto'
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
})
|
|
140
|
+
]
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const chunks = getChunks({ ...baseOpts, ext: '.js' }, stats, ROOT);
|
|
144
|
+
expect(chunks['remote@src/x.ts'].js).toBe('src/x.111.js');
|
|
145
|
+
expect(chunks['remote@src/x.ts'].css).toEqual(['src/x.111.css']);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('writes auxiliaryFiles (sourcemaps, images) into resources[]', () => {
|
|
149
|
+
const stats: StatsCompilation = {
|
|
150
|
+
chunks: [
|
|
151
|
+
mkChunk({
|
|
152
|
+
files: ['src/a.111.mjs'],
|
|
153
|
+
auxiliaryFiles: ['src/a.111.mjs.map', 'assets/logo.svg'],
|
|
154
|
+
modules: [
|
|
155
|
+
{
|
|
156
|
+
nameForCondition: `${ROOT}/src/a.ts`,
|
|
157
|
+
moduleType: 'javascript/esm'
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
})
|
|
161
|
+
]
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const entry = getChunks(baseOpts, stats, ROOT)['remote@src/a.ts'];
|
|
165
|
+
expect(entry.resources).toEqual([
|
|
166
|
+
'src/a.111.mjs.map',
|
|
167
|
+
'assets/logo.svg'
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('returns {} when stats has no chunks at all', () => {
|
|
172
|
+
expect(getChunks(baseOpts, {}, ROOT)).toEqual({});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('generateIdentifier', () => {
|
|
177
|
+
it('normalises absolute path to root-relative POSIX form', () => {
|
|
178
|
+
expect(
|
|
179
|
+
generateIdentifier({
|
|
180
|
+
root: '/abs/project',
|
|
181
|
+
name: 'remote',
|
|
182
|
+
filePath: '/abs/project/src/entry.client.ts'
|
|
183
|
+
})
|
|
184
|
+
).toBe('remote@src/entry.client.ts');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('falls back to absolute path when no root provided', () => {
|
|
188
|
+
expect(
|
|
189
|
+
generateIdentifier({
|
|
190
|
+
root: '',
|
|
191
|
+
name: 'remote',
|
|
192
|
+
filePath: '/abs/project/src/x.ts'
|
|
193
|
+
})
|
|
194
|
+
).toBe('remote@/abs/project/src/x.ts');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('getExports', () => {
|
|
199
|
+
it('binds export entries to their hashed asset filenames', () => {
|
|
200
|
+
const opts: ParsedModuleLinkPluginOptions = {
|
|
201
|
+
...baseOpts,
|
|
202
|
+
exports: {
|
|
203
|
+
'src/entry.client': {
|
|
204
|
+
name: 'src/entry.client',
|
|
205
|
+
file: './src/entry.client',
|
|
206
|
+
pkg: false,
|
|
207
|
+
identifier: 'remote/src/entry.client'
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const stats: StatsCompilation = {
|
|
212
|
+
entrypoints: {
|
|
213
|
+
'src/entry.client': {
|
|
214
|
+
chunks: [],
|
|
215
|
+
assets: [
|
|
216
|
+
{ name: 'src/entry.client.95f6085b.final.mjs' } as never
|
|
217
|
+
]
|
|
218
|
+
} as never
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const out = getExports(opts, stats);
|
|
223
|
+
expect(out['src/entry.client'].file).toBe(
|
|
224
|
+
'src/entry.client.95f6085b.final.mjs'
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { buildManifestProtocolFields } from '@esmx/core';
|
|
2
|
+
import type { Compiler, StatsChunk, StatsCompilation } from '@rspack/core';
|
|
2
3
|
import upath from 'upath';
|
|
3
4
|
import type {
|
|
4
5
|
ManifestJson,
|
|
@@ -10,7 +11,10 @@ import type {
|
|
|
10
11
|
export const RSPACK_PLUGIN_NAME = 'rspack-module-link-plugin';
|
|
11
12
|
|
|
12
13
|
export class ManifestPlugin {
|
|
13
|
-
constructor(
|
|
14
|
+
constructor(
|
|
15
|
+
private opts: ParsedModuleLinkPluginOptions,
|
|
16
|
+
isProduction: boolean
|
|
17
|
+
) {}
|
|
14
18
|
|
|
15
19
|
apply(compiler: Compiler) {
|
|
16
20
|
const opts = this.opts;
|
|
@@ -19,6 +23,10 @@ export class ManifestPlugin {
|
|
|
19
23
|
RSPACK_PLUGIN_NAME,
|
|
20
24
|
(compilation) => {
|
|
21
25
|
let manifestJson: ManifestJson = {
|
|
26
|
+
...buildManifestProtocolFields(
|
|
27
|
+
compiler.options.context ?? process.cwd(),
|
|
28
|
+
[]
|
|
29
|
+
),
|
|
22
30
|
name: opts.name,
|
|
23
31
|
exports: {},
|
|
24
32
|
scopes: opts.scopes,
|
|
@@ -41,12 +49,29 @@ export class ManifestPlugin {
|
|
|
41
49
|
const resources = Object.keys(assets)
|
|
42
50
|
.map(transFileName)
|
|
43
51
|
.filter((file) => !file.includes('hot-update'));
|
|
52
|
+
const chunksStats = compilation.getStats().toJson({
|
|
53
|
+
all: false,
|
|
54
|
+
chunks: true,
|
|
55
|
+
modules: true,
|
|
56
|
+
chunkModules: true,
|
|
57
|
+
ids: true,
|
|
58
|
+
entrypoints: true,
|
|
59
|
+
chunkRelations: true
|
|
60
|
+
});
|
|
61
|
+
const root =
|
|
62
|
+
compilation.options.context ?? process.cwd();
|
|
44
63
|
manifestJson = {
|
|
64
|
+
...buildManifestProtocolFields(
|
|
65
|
+
root,
|
|
66
|
+
Object.values(exports)
|
|
67
|
+
.filter((exp) => exp.pkg)
|
|
68
|
+
.map((exp) => exp.name)
|
|
69
|
+
),
|
|
45
70
|
name: opts.name,
|
|
46
71
|
exports: exports,
|
|
47
72
|
scopes: opts.scopes,
|
|
48
73
|
files: resources,
|
|
49
|
-
chunks: getChunks(opts,
|
|
74
|
+
chunks: getChunks(opts, chunksStats, root)
|
|
50
75
|
};
|
|
51
76
|
const { RawSource } = compiler.rspack.sources;
|
|
52
77
|
|
|
@@ -115,34 +140,40 @@ export function getExports(
|
|
|
115
140
|
return exports;
|
|
116
141
|
}
|
|
117
142
|
|
|
118
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Build the `chunks` section of the federation manifest from rspack stats.
|
|
145
|
+
*
|
|
146
|
+
* Each emitted chunk that owns at least one JS module gets a manifest entry
|
|
147
|
+
* keyed by a stable, root-relative identifier (`<remote>@<source-path>`). The
|
|
148
|
+
* entry lists:
|
|
149
|
+
*
|
|
150
|
+
* - `js` — the chunk's primary JS asset
|
|
151
|
+
* - `css` — every `.css` file the chunk emitted; consumed by the host's
|
|
152
|
+
* renderHost to inject `<link rel="stylesheet">` into `<head>`
|
|
153
|
+
* (G5). This is how `import './x.css'` in a remote translates
|
|
154
|
+
* into stylesheet delivery in the browser without the runtime
|
|
155
|
+
* having to evaluate CSS as JS.
|
|
156
|
+
* - `resources` — auxiliary assets (sourcemaps, images, fonts) so the host
|
|
157
|
+
* can preload / fingerprint them.
|
|
158
|
+
*
|
|
159
|
+
* Pure function over stats + root path — kept testable without booting a
|
|
160
|
+
* real rspack build.
|
|
161
|
+
*/
|
|
162
|
+
export function getChunks(
|
|
119
163
|
opts: ParsedModuleLinkPluginOptions,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
all: false,
|
|
124
|
-
chunks: true,
|
|
125
|
-
modules: true,
|
|
126
|
-
chunkModules: true,
|
|
127
|
-
ids: true
|
|
128
|
-
});
|
|
164
|
+
stats: StatsCompilation,
|
|
165
|
+
root: string
|
|
166
|
+
): ManifestJsonChunks {
|
|
129
167
|
const buildChunks: ManifestJsonChunks = {};
|
|
130
168
|
if (!stats.chunks) return buildChunks;
|
|
131
169
|
|
|
132
170
|
for (const chunk of stats.chunks) {
|
|
133
|
-
const module = chunk
|
|
134
|
-
?.sort((a, b) => {
|
|
135
|
-
return (a.index ?? -1) - (b?.index ?? -1);
|
|
136
|
-
})
|
|
137
|
-
?.find((module) => {
|
|
138
|
-
return module.moduleType?.includes('javascript/');
|
|
139
|
-
});
|
|
171
|
+
const module = pickPrimaryJsModule(chunk);
|
|
140
172
|
if (!module?.nameForCondition) continue;
|
|
141
173
|
|
|
142
174
|
const js = chunk.files?.find((file) => file.endsWith(opts.ext));
|
|
143
175
|
if (!js) continue;
|
|
144
176
|
|
|
145
|
-
const root = compilation.options.context ?? process.cwd();
|
|
146
177
|
const name = generateIdentifier({
|
|
147
178
|
root,
|
|
148
179
|
name: opts.name,
|
|
@@ -161,6 +192,12 @@ function getChunks(
|
|
|
161
192
|
return buildChunks;
|
|
162
193
|
}
|
|
163
194
|
|
|
195
|
+
function pickPrimaryJsModule(chunk: StatsChunk) {
|
|
196
|
+
return chunk.modules
|
|
197
|
+
?.sort((a, b) => (a.index ?? -1) - (b?.index ?? -1))
|
|
198
|
+
?.find((module) => module.moduleType?.includes('javascript/'));
|
|
199
|
+
}
|
|
200
|
+
|
|
164
201
|
export function generateIdentifier({
|
|
165
202
|
root,
|
|
166
203
|
name,
|
package/src/module-link/parse.ts
CHANGED