@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
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
- **High-Performance Build** - Ultra-fast building based on Rspack, providing excellent development experience
|
|
33
33
|
- **Application Support** - Complete support for standard applications and HTML applications development and building
|
|
34
34
|
- **Asset Processing** - Smart processing of various assets including JavaScript, CSS, images
|
|
35
|
-
- **SSR Support** - Built-in
|
|
35
|
+
- **SSR Support** - Built-in SSR support for easy isomorphic application building
|
|
36
36
|
- **Developer Experience** - Supports hot reload, intelligent hints, and TypeScript
|
|
37
37
|
|
|
38
38
|
## 📦 Installation
|
|
@@ -48,6 +48,20 @@ pnpm add @esmx/rspack -D
|
|
|
48
48
|
yarn add @esmx/rspack -D
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## 🚀 Quick Start
|
|
52
|
+
|
|
53
|
+
Use it from your `entry.node.ts`:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import type { EsmxOptions } from '@esmx/core';
|
|
57
|
+
|
|
58
|
+
export default {
|
|
59
|
+
async devApp(esmx) {
|
|
60
|
+
return import('@esmx/rspack').then((m) => m.createRspackHtmlApp(esmx));
|
|
61
|
+
}
|
|
62
|
+
} satisfies EsmxOptions;
|
|
63
|
+
```
|
|
64
|
+
|
|
51
65
|
## 📚 Documentation
|
|
52
66
|
|
|
53
67
|
Visit the [official documentation](https://esmx.dev/api/app/rspack.html) for detailed usage guides and API reference.
|
package/README.zh-CN.md
CHANGED
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
|
|
30
30
|
## 🚀 特性
|
|
31
31
|
|
|
32
|
-
- **高性能构建** - 基于 Rspack
|
|
33
|
-
- **应用支持** -
|
|
32
|
+
- **高性能构建** - 基于 Rspack 的极速构建,提供卓越的开发体验
|
|
33
|
+
- **应用支持** - 完整支持标准应用与 HTML 应用的开发与构建
|
|
34
34
|
- **资源处理** - 智能处理各类资源,支持 JavaScript、CSS、图片等
|
|
35
|
-
- **SSR 支持** -
|
|
36
|
-
- **开发体验** -
|
|
35
|
+
- **SSR 支持** - 内置 SSR 支持,轻松构建同构应用
|
|
36
|
+
- **开发体验** - 支持热更新、智能提示与 TypeScript
|
|
37
37
|
|
|
38
38
|
## 📦 安装
|
|
39
39
|
|
|
@@ -48,6 +48,20 @@ pnpm add @esmx/rspack -D
|
|
|
48
48
|
yarn add @esmx/rspack -D
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## 🚀 快速开始
|
|
52
|
+
|
|
53
|
+
在 `entry.node.ts` 中使用:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import type { EsmxOptions } from '@esmx/core';
|
|
57
|
+
|
|
58
|
+
export default {
|
|
59
|
+
async devApp(esmx) {
|
|
60
|
+
return import('@esmx/rspack').then((m) => m.createRspackHtmlApp(esmx));
|
|
61
|
+
}
|
|
62
|
+
} satisfies EsmxOptions;
|
|
63
|
+
```
|
|
64
|
+
|
|
51
65
|
## 📚 文档
|
|
52
66
|
|
|
53
67
|
访问[官方文档](https://esmx.dev/api/app/rspack.html)获取详细的使用指南和 API 参考。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type RspackChain from 'rspack-chain';
|
|
1
|
+
import type { RspackChain } from 'rspack-chain';
|
|
2
2
|
import type { ParsedModuleLinkPluginOptions } from './types';
|
|
3
3
|
export declare function applyEntryConfig(chain: RspackChain, opts: ParsedModuleLinkPluginOptions): void;
|
|
4
4
|
export declare function applyModuleConfig(chain: RspackChain): void;
|
|
@@ -40,9 +40,11 @@ function createExternalsFunction(opts, compilerContext) {
|
|
|
40
40
|
const identifier = value.pkg ? value.name : value.identifier;
|
|
41
41
|
importMap.set(identifier, identifier);
|
|
42
42
|
importMap.set(value.name, identifier);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (value.file) {
|
|
44
|
+
const resolvedPath = await resolvePath(value.file);
|
|
45
|
+
if (resolvedPath) {
|
|
46
|
+
importMap.set(resolvedPath, identifier);
|
|
47
|
+
}
|
|
46
48
|
}
|
|
47
49
|
})
|
|
48
50
|
);
|
|
@@ -72,9 +74,11 @@ function createExternalsFunction(opts, compilerContext) {
|
|
|
72
74
|
return importName || null;
|
|
73
75
|
};
|
|
74
76
|
const FILE_EXT_REGEX = /\.worker\.(js|mjs|cjs|jsx|mjsx|cjsx|ts|mts|cts|tsx|mtsx|ctsx)$/i;
|
|
77
|
+
const wrapperFiles = new Set(opts.wrapperFiles);
|
|
75
78
|
return async (data) => {
|
|
76
79
|
if (!data.request || !data.context || !data.contextInfo?.issuer || FILE_EXT_REGEX.test(data.contextInfo.issuer))
|
|
77
80
|
return;
|
|
81
|
+
if (wrapperFiles.has(data.contextInfo.issuer)) return;
|
|
78
82
|
const defaultContext = compilerContext;
|
|
79
83
|
const resolvePath = async (request, context = defaultContext) => {
|
|
80
84
|
if (!data.getResolve) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type RspackChain from 'rspack-chain';
|
|
1
|
+
import type { RspackChain } from 'rspack-chain';
|
|
2
2
|
import type { ModuleLinkPluginOptions } from './types';
|
|
3
|
-
export declare function initModuleLink(chain: RspackChain, options: ModuleLinkPluginOptions): void;
|
|
3
|
+
export declare function initModuleLink(chain: RspackChain, options: ModuleLinkPluginOptions, isProduction?: boolean): void;
|
|
4
4
|
export type { ModuleLinkPluginOptions } from './types';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { applyChainConfig1 } from "./config1.mjs";
|
|
2
2
|
import { ManifestPlugin } from "./manifest-plugin.mjs";
|
|
3
3
|
import { parseOptions } from "./parse.mjs";
|
|
4
|
-
export function initModuleLink(chain, options) {
|
|
4
|
+
export function initModuleLink(chain, options, isProduction = false) {
|
|
5
5
|
const opts = parseOptions(options);
|
|
6
6
|
applyChainConfig1(chain, opts);
|
|
7
|
-
chain.plugin("module-link-manifest").use(ManifestPlugin, [opts]);
|
|
7
|
+
chain.plugin("module-link-manifest").use(ManifestPlugin, [opts, isProduction]);
|
|
8
8
|
}
|
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
import type { Compiler, StatsCompilation } from '@rspack/core';
|
|
2
|
-
import type { ManifestJsonExports, ParsedModuleLinkPluginOptions } from './types';
|
|
2
|
+
import type { ManifestJsonChunks, ManifestJsonExports, ParsedModuleLinkPluginOptions } from './types';
|
|
3
3
|
export declare const RSPACK_PLUGIN_NAME = "rspack-module-link-plugin";
|
|
4
4
|
export declare class ManifestPlugin {
|
|
5
5
|
private opts;
|
|
6
|
-
constructor(opts: ParsedModuleLinkPluginOptions);
|
|
6
|
+
constructor(opts: ParsedModuleLinkPluginOptions, isProduction: boolean);
|
|
7
7
|
apply(compiler: Compiler): void;
|
|
8
8
|
}
|
|
9
9
|
export declare function getExports(opts: ParsedModuleLinkPluginOptions, stats: StatsCompilation): ManifestJsonExports;
|
|
10
|
+
/**
|
|
11
|
+
* Build the `chunks` section of the federation manifest from rspack stats.
|
|
12
|
+
*
|
|
13
|
+
* Each emitted chunk that owns at least one JS module gets a manifest entry
|
|
14
|
+
* keyed by a stable, root-relative identifier (`<remote>@<source-path>`). The
|
|
15
|
+
* entry lists:
|
|
16
|
+
*
|
|
17
|
+
* - `js` — the chunk's primary JS asset
|
|
18
|
+
* - `css` — every `.css` file the chunk emitted; consumed by the host's
|
|
19
|
+
* renderHost to inject `<link rel="stylesheet">` into `<head>`
|
|
20
|
+
* (G5). This is how `import './x.css'` in a remote translates
|
|
21
|
+
* into stylesheet delivery in the browser without the runtime
|
|
22
|
+
* having to evaluate CSS as JS.
|
|
23
|
+
* - `resources` — auxiliary assets (sourcemaps, images, fonts) so the host
|
|
24
|
+
* can preload / fingerprint them.
|
|
25
|
+
*
|
|
26
|
+
* Pure function over stats + root path — kept testable without booting a
|
|
27
|
+
* real rspack build.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getChunks(opts: ParsedModuleLinkPluginOptions, stats: StatsCompilation, root: string): ManifestJsonChunks;
|
|
10
30
|
export declare function generateIdentifier({ root, name, filePath }: {
|
|
11
31
|
root: string;
|
|
12
32
|
name: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { buildManifestProtocolFields } from "@esmx/core";
|
|
1
2
|
import upath from "upath";
|
|
2
3
|
export const RSPACK_PLUGIN_NAME = "rspack-module-link-plugin";
|
|
3
4
|
export class ManifestPlugin {
|
|
4
|
-
constructor(opts) {
|
|
5
|
+
constructor(opts, isProduction) {
|
|
5
6
|
this.opts = opts;
|
|
6
7
|
}
|
|
7
8
|
apply(compiler) {
|
|
@@ -11,6 +12,10 @@ export class ManifestPlugin {
|
|
|
11
12
|
RSPACK_PLUGIN_NAME,
|
|
12
13
|
(compilation) => {
|
|
13
14
|
let manifestJson = {
|
|
15
|
+
...buildManifestProtocolFields(
|
|
16
|
+
compiler.options.context ?? process.cwd(),
|
|
17
|
+
[]
|
|
18
|
+
),
|
|
14
19
|
name: opts.name,
|
|
15
20
|
exports: {},
|
|
16
21
|
scopes: opts.scopes,
|
|
@@ -29,12 +34,26 @@ export class ManifestPlugin {
|
|
|
29
34
|
});
|
|
30
35
|
const exports = getExports(opts, stats);
|
|
31
36
|
const resources = Object.keys(assets).map(transFileName).filter((file) => !file.includes("hot-update"));
|
|
37
|
+
const chunksStats = compilation.getStats().toJson({
|
|
38
|
+
all: false,
|
|
39
|
+
chunks: true,
|
|
40
|
+
modules: true,
|
|
41
|
+
chunkModules: true,
|
|
42
|
+
ids: true,
|
|
43
|
+
entrypoints: true,
|
|
44
|
+
chunkRelations: true
|
|
45
|
+
});
|
|
46
|
+
const root = compilation.options.context ?? process.cwd();
|
|
32
47
|
manifestJson = {
|
|
48
|
+
...buildManifestProtocolFields(
|
|
49
|
+
root,
|
|
50
|
+
Object.values(exports).filter((exp) => exp.pkg).map((exp) => exp.name)
|
|
51
|
+
),
|
|
33
52
|
name: opts.name,
|
|
34
53
|
exports,
|
|
35
54
|
scopes: opts.scopes,
|
|
36
55
|
files: resources,
|
|
37
|
-
chunks: getChunks(opts,
|
|
56
|
+
chunks: getChunks(opts, chunksStats, root)
|
|
38
57
|
};
|
|
39
58
|
const { RawSource } = compiler.rspack.sources;
|
|
40
59
|
compilation.emitAsset(
|
|
@@ -91,26 +110,14 @@ export function getExports(opts, stats) {
|
|
|
91
110
|
}
|
|
92
111
|
return exports;
|
|
93
112
|
}
|
|
94
|
-
function getChunks(opts,
|
|
95
|
-
const stats = compilation.getStats().toJson({
|
|
96
|
-
all: false,
|
|
97
|
-
chunks: true,
|
|
98
|
-
modules: true,
|
|
99
|
-
chunkModules: true,
|
|
100
|
-
ids: true
|
|
101
|
-
});
|
|
113
|
+
export function getChunks(opts, stats, root) {
|
|
102
114
|
const buildChunks = {};
|
|
103
115
|
if (!stats.chunks) return buildChunks;
|
|
104
116
|
for (const chunk of stats.chunks) {
|
|
105
|
-
const module = chunk
|
|
106
|
-
return (a.index ?? -1) - (b?.index ?? -1);
|
|
107
|
-
})?.find((module2) => {
|
|
108
|
-
return module2.moduleType?.includes("javascript/");
|
|
109
|
-
});
|
|
117
|
+
const module = pickPrimaryJsModule(chunk);
|
|
110
118
|
if (!module?.nameForCondition) continue;
|
|
111
119
|
const js = chunk.files?.find((file) => file.endsWith(opts.ext));
|
|
112
120
|
if (!js) continue;
|
|
113
|
-
const root = compilation.options.context ?? process.cwd();
|
|
114
121
|
const name = generateIdentifier({
|
|
115
122
|
root,
|
|
116
123
|
name: opts.name,
|
|
@@ -127,6 +134,9 @@ function getChunks(opts, compilation) {
|
|
|
127
134
|
}
|
|
128
135
|
return buildChunks;
|
|
129
136
|
}
|
|
137
|
+
function pickPrimaryJsModule(chunk) {
|
|
138
|
+
return chunk.modules?.sort((a, b) => (a.index ?? -1) - (b?.index ?? -1))?.find((module) => module.moduleType?.includes("javascript/"));
|
|
139
|
+
}
|
|
130
140
|
export function generateIdentifier({
|
|
131
141
|
root,
|
|
132
142
|
name,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { generateIdentifier, getChunks, getExports } from "./manifest-plugin.mjs";
|
|
3
|
+
const baseOpts = {
|
|
4
|
+
name: "remote",
|
|
5
|
+
exports: {},
|
|
6
|
+
imports: {},
|
|
7
|
+
scopes: {},
|
|
8
|
+
ext: ".mjs",
|
|
9
|
+
deps: [],
|
|
10
|
+
preEntries: [],
|
|
11
|
+
wrapperFiles: [],
|
|
12
|
+
injectChunkName: false
|
|
13
|
+
};
|
|
14
|
+
const ROOT = "/abs/project";
|
|
15
|
+
function mkChunk(o) {
|
|
16
|
+
return {
|
|
17
|
+
files: o.files ?? [],
|
|
18
|
+
auxiliaryFiles: o.auxiliaryFiles ?? [],
|
|
19
|
+
modules: (o.modules ?? []).map((m, i) => ({
|
|
20
|
+
id: i,
|
|
21
|
+
index: m.index ?? i,
|
|
22
|
+
nameForCondition: m.nameForCondition,
|
|
23
|
+
moduleType: m.moduleType ?? "javascript/auto"
|
|
24
|
+
}))
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
describe("getChunks \u2014 CSS extraction", () => {
|
|
28
|
+
it("populates css[] when a chunk emitted .css files", () => {
|
|
29
|
+
const stats = {
|
|
30
|
+
chunks: [
|
|
31
|
+
mkChunk({
|
|
32
|
+
files: [
|
|
33
|
+
"src/entry.client.95f6085b.final.mjs",
|
|
34
|
+
"src/entry.client.a73d6772.final.css"
|
|
35
|
+
],
|
|
36
|
+
modules: [
|
|
37
|
+
{
|
|
38
|
+
nameForCondition: `${ROOT}/src/entry.client.ts`,
|
|
39
|
+
moduleType: "javascript/auto"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
]
|
|
44
|
+
};
|
|
45
|
+
const chunks = getChunks(baseOpts, stats, ROOT);
|
|
46
|
+
const entry = chunks["remote@src/entry.client.ts"];
|
|
47
|
+
expect(entry).toBeDefined();
|
|
48
|
+
expect(entry.js).toBe("src/entry.client.95f6085b.final.mjs");
|
|
49
|
+
expect(entry.css).toEqual(["src/entry.client.a73d6772.final.css"]);
|
|
50
|
+
});
|
|
51
|
+
it("returns empty css[] when chunk has no stylesheet", () => {
|
|
52
|
+
const stats = {
|
|
53
|
+
chunks: [
|
|
54
|
+
mkChunk({
|
|
55
|
+
files: ["vue.b8a9c2d3.final.mjs"],
|
|
56
|
+
modules: [
|
|
57
|
+
{
|
|
58
|
+
nameForCondition: `${ROOT}/node_modules/vue/index.mjs`,
|
|
59
|
+
moduleType: "javascript/esm"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
const entry = getChunks(baseOpts, stats, ROOT)["remote@node_modules/vue/index.mjs"];
|
|
66
|
+
expect(entry).toBeDefined();
|
|
67
|
+
expect(entry.css).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
it("extracts multiple CSS chunks per JS chunk (split stylesheets)", () => {
|
|
70
|
+
const stats = {
|
|
71
|
+
chunks: [
|
|
72
|
+
mkChunk({
|
|
73
|
+
files: [
|
|
74
|
+
"chunks/widget.111.final.mjs",
|
|
75
|
+
"chunks/widget.111.final.css",
|
|
76
|
+
"chunks/widget.111.theme.css"
|
|
77
|
+
],
|
|
78
|
+
modules: [
|
|
79
|
+
{
|
|
80
|
+
nameForCondition: `${ROOT}/src/widget.ts`,
|
|
81
|
+
moduleType: "javascript/esm"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
})
|
|
85
|
+
]
|
|
86
|
+
};
|
|
87
|
+
const entry = getChunks(baseOpts, stats, ROOT)["remote@src/widget.ts"];
|
|
88
|
+
expect(entry.css).toEqual([
|
|
89
|
+
"chunks/widget.111.final.css",
|
|
90
|
+
"chunks/widget.111.theme.css"
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
it("ignores chunks without a JS module (CSS-only / asset-only)", () => {
|
|
94
|
+
const stats = {
|
|
95
|
+
chunks: [
|
|
96
|
+
mkChunk({
|
|
97
|
+
files: ["orphan.111.final.css"],
|
|
98
|
+
modules: []
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
};
|
|
102
|
+
expect(getChunks(baseOpts, stats, ROOT)).toEqual({});
|
|
103
|
+
});
|
|
104
|
+
it("respects opts.ext for the JS file lookup (.cjs / .js / .mjs)", () => {
|
|
105
|
+
const stats = {
|
|
106
|
+
chunks: [
|
|
107
|
+
mkChunk({
|
|
108
|
+
files: ["src/x.111.js", "src/x.111.css"],
|
|
109
|
+
modules: [
|
|
110
|
+
{
|
|
111
|
+
nameForCondition: `${ROOT}/src/x.ts`,
|
|
112
|
+
moduleType: "javascript/auto"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
})
|
|
116
|
+
]
|
|
117
|
+
};
|
|
118
|
+
const chunks = getChunks({ ...baseOpts, ext: ".js" }, stats, ROOT);
|
|
119
|
+
expect(chunks["remote@src/x.ts"].js).toBe("src/x.111.js");
|
|
120
|
+
expect(chunks["remote@src/x.ts"].css).toEqual(["src/x.111.css"]);
|
|
121
|
+
});
|
|
122
|
+
it("writes auxiliaryFiles (sourcemaps, images) into resources[]", () => {
|
|
123
|
+
const stats = {
|
|
124
|
+
chunks: [
|
|
125
|
+
mkChunk({
|
|
126
|
+
files: ["src/a.111.mjs"],
|
|
127
|
+
auxiliaryFiles: ["src/a.111.mjs.map", "assets/logo.svg"],
|
|
128
|
+
modules: [
|
|
129
|
+
{
|
|
130
|
+
nameForCondition: `${ROOT}/src/a.ts`,
|
|
131
|
+
moduleType: "javascript/esm"
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
})
|
|
135
|
+
]
|
|
136
|
+
};
|
|
137
|
+
const entry = getChunks(baseOpts, stats, ROOT)["remote@src/a.ts"];
|
|
138
|
+
expect(entry.resources).toEqual([
|
|
139
|
+
"src/a.111.mjs.map",
|
|
140
|
+
"assets/logo.svg"
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
it("returns {} when stats has no chunks at all", () => {
|
|
144
|
+
expect(getChunks(baseOpts, {}, ROOT)).toEqual({});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe("generateIdentifier", () => {
|
|
148
|
+
it("normalises absolute path to root-relative POSIX form", () => {
|
|
149
|
+
expect(
|
|
150
|
+
generateIdentifier({
|
|
151
|
+
root: "/abs/project",
|
|
152
|
+
name: "remote",
|
|
153
|
+
filePath: "/abs/project/src/entry.client.ts"
|
|
154
|
+
})
|
|
155
|
+
).toBe("remote@src/entry.client.ts");
|
|
156
|
+
});
|
|
157
|
+
it("falls back to absolute path when no root provided", () => {
|
|
158
|
+
expect(
|
|
159
|
+
generateIdentifier({
|
|
160
|
+
root: "",
|
|
161
|
+
name: "remote",
|
|
162
|
+
filePath: "/abs/project/src/x.ts"
|
|
163
|
+
})
|
|
164
|
+
).toBe("remote@/abs/project/src/x.ts");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe("getExports", () => {
|
|
168
|
+
it("binds export entries to their hashed asset filenames", () => {
|
|
169
|
+
const opts = {
|
|
170
|
+
...baseOpts,
|
|
171
|
+
exports: {
|
|
172
|
+
"src/entry.client": {
|
|
173
|
+
name: "src/entry.client",
|
|
174
|
+
file: "./src/entry.client",
|
|
175
|
+
pkg: false,
|
|
176
|
+
identifier: "remote/src/entry.client"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const stats = {
|
|
181
|
+
entrypoints: {
|
|
182
|
+
"src/entry.client": {
|
|
183
|
+
chunks: [],
|
|
184
|
+
assets: [
|
|
185
|
+
{ name: "src/entry.client.95f6085b.final.mjs" }
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const out = getExports(opts, stats);
|
|
191
|
+
expect(out["src/entry.client"].file).toBe(
|
|
192
|
+
"src/entry.client.95f6085b.final.mjs"
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -12,6 +12,15 @@ export interface ModuleLinkPluginOptions {
|
|
|
12
12
|
injectChunkName?: boolean;
|
|
13
13
|
preEntries?: string[];
|
|
14
14
|
deps?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Absolute paths of pkg-export wrapper files (one per pkg export). When
|
|
17
|
+
* the externals function sees an import whose issuer is one of these
|
|
18
|
+
* wrappers, it skips externalization — the wrapper IS the federation
|
|
19
|
+
* chunk for that package, so externalizing its inner
|
|
20
|
+
* `import __m from '<pkg-path>'` would route back to the wrapper itself
|
|
21
|
+
* (cyclic).
|
|
22
|
+
*/
|
|
23
|
+
wrapperFiles?: string[];
|
|
15
24
|
}
|
|
16
25
|
export interface ParsedModuleLinkPluginOptions {
|
|
17
26
|
name: string;
|
|
@@ -22,4 +31,5 @@ export interface ParsedModuleLinkPluginOptions {
|
|
|
22
31
|
injectChunkName: boolean;
|
|
23
32
|
preEntries: string[];
|
|
24
33
|
deps: string[];
|
|
34
|
+
wrapperFiles: string[];
|
|
25
35
|
}
|
package/dist/rspack/app.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export interface RspackAppChainContext {
|
|
|
76
76
|
* rspack-chain configuration object.
|
|
77
77
|
* You can use the chain API in chain hooks to modify the configuration.
|
|
78
78
|
*/
|
|
79
|
-
chain: import('rspack-chain').
|
|
79
|
+
chain: import('rspack-chain').RspackChain;
|
|
80
80
|
/**
|
|
81
81
|
* Options object passed when creating the application.
|
|
82
82
|
*/
|
package/dist/rspack/app.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
1
4
|
import { pathToFileURL } from "node:url";
|
|
2
5
|
import {
|
|
3
6
|
createApp,
|
|
@@ -29,10 +32,12 @@ async function createMiddleware(esmx, options = {}) {
|
|
|
29
32
|
if (esmx.command !== esmx.COMMAND.dev) {
|
|
30
33
|
return [];
|
|
31
34
|
}
|
|
32
|
-
const rsBuild = createRsBuild(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const rsBuild = createRsBuild(
|
|
36
|
+
await Promise.all([
|
|
37
|
+
generateBuildConfig(esmx, options, "client"),
|
|
38
|
+
generateBuildConfig(esmx, options, "server")
|
|
39
|
+
])
|
|
40
|
+
);
|
|
36
41
|
rsBuild.watch();
|
|
37
42
|
const hot = hotMiddleware(rsBuild.compilers[0], {
|
|
38
43
|
path: `${esmx.basePath}hot-middleware`
|
|
@@ -46,7 +51,7 @@ async function createMiddleware(esmx, options = {}) {
|
|
|
46
51
|
}
|
|
47
52
|
];
|
|
48
53
|
}
|
|
49
|
-
function generateBuildConfig(esmx, options, buildTarget) {
|
|
54
|
+
async function generateBuildConfig(esmx, options, buildTarget) {
|
|
50
55
|
return createRspackConfig(esmx, buildTarget, options);
|
|
51
56
|
}
|
|
52
57
|
function rewriteRender(esmx) {
|
|
@@ -74,11 +79,18 @@ function rewriteBuild(esmx, options = {}) {
|
|
|
74
79
|
}
|
|
75
80
|
return async () => {
|
|
76
81
|
const ok = await createRsBuild(
|
|
77
|
-
|
|
82
|
+
await Promise.all(
|
|
83
|
+
targets.map(
|
|
84
|
+
(target) => generateBuildConfig(esmx, options, target)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
78
87
|
).build();
|
|
79
88
|
if (!ok) {
|
|
80
89
|
return false;
|
|
81
90
|
}
|
|
91
|
+
if (esmx.isProd) {
|
|
92
|
+
await updateManifestIntegrity(esmx);
|
|
93
|
+
}
|
|
82
94
|
esmx.writeSync(
|
|
83
95
|
esmx.resolvePath("dist/index.mjs"),
|
|
84
96
|
`
|
|
@@ -100,3 +112,36 @@ start();
|
|
|
100
112
|
return pack(esmx);
|
|
101
113
|
};
|
|
102
114
|
}
|
|
115
|
+
async function updateManifestIntegrity(esmx) {
|
|
116
|
+
const clientDir = esmx.resolvePath("dist/client");
|
|
117
|
+
const manifestPath = path.join(clientDir, "manifest.json");
|
|
118
|
+
const manifestExists = await fs.access(manifestPath).then(() => true).catch(() => false);
|
|
119
|
+
if (!manifestExists) {
|
|
120
|
+
console.warn(
|
|
121
|
+
`SRI generation skipped: client manifest not found at ${manifestPath}`
|
|
122
|
+
);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const manifestContent = await fs.readFile(manifestPath, "utf-8");
|
|
126
|
+
const manifest = JSON.parse(manifestContent);
|
|
127
|
+
const integrity = {};
|
|
128
|
+
async function walkDir(dir, relativeDir = "") {
|
|
129
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const fullPath = path.join(dir, entry.name);
|
|
132
|
+
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
133
|
+
if (entry.isDirectory()) {
|
|
134
|
+
await walkDir(fullPath, relativePath);
|
|
135
|
+
} else if (entry.isFile() && (entry.name.endsWith(".mjs") || entry.name.endsWith(".js")) && !entry.name.includes("hot-update") && entry.name !== "manifest.json") {
|
|
136
|
+
const content = await fs.readFile(fullPath);
|
|
137
|
+
const hash = crypto.createHash("sha384").update(content).digest("base64");
|
|
138
|
+
integrity[relativePath] = `sha384-${hash}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
await walkDir(clientDir);
|
|
143
|
+
if (Object.keys(integrity).length > 0) {
|
|
144
|
+
manifest.integrity = integrity;
|
|
145
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 4));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Esmx } from '@esmx/core';
|
|
2
2
|
import type { RspackOptions } from '@rspack/core';
|
|
3
|
-
import RspackChain from 'rspack-chain';
|
|
3
|
+
import { RspackChain } from 'rspack-chain';
|
|
4
4
|
import type { RspackAppOptions } from './app';
|
|
5
5
|
import type { BuildTarget } from './build-target';
|
|
6
|
-
export declare function createChainConfig(esmx: Esmx, buildTarget: BuildTarget, options: RspackAppOptions): RspackChain
|
|
7
|
-
export declare function createRspackConfig(esmx: Esmx, buildTarget: BuildTarget, options: RspackAppOptions): RspackOptions
|
|
6
|
+
export declare function createChainConfig(esmx: Esmx, buildTarget: BuildTarget, options: RspackAppOptions): Promise<RspackChain>;
|
|
7
|
+
export declare function createRspackConfig(esmx: Esmx, buildTarget: BuildTarget, options: RspackAppOptions): Promise<RspackOptions>;
|