@esmx/rspack 3.0.0-rc.18 → 3.0.0-rc.20

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Vanelink
3
+ Copyright (c) 2020 Esmx Team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,29 +1,50 @@
1
- # @esmx/rspack
2
-
3
- 基于 Rspack 提供高性能的构建能力,支持标准应用和 HTML 应用的开发与构建。
4
-
5
- ## 特性
6
-
7
- - 🚀 **高性能构建** - 基于 Rspack 的极速构建,为应用提供卓越的开发体验
8
- - 💡 **应用支持** - 完整支持标准应用和 HTML 应用的开发与构建
9
- - 🎨 **资源处理** - 智能处理各类资源,支持 JavaScript、CSS、图片等
10
- - 🛠️ **SSR 支持** - 内置服务端渲染支持,轻松构建同构应用
11
- - 🔧 **开发体验** - 支持热更新、智能提示和 TypeScript
12
-
13
- ## 安装
1
+ <div align="center">
2
+ <img src="https://www.esmnext.com/logo.svg?t=2025" width="120" alt="Esmx Logo" />
3
+ <h1>@esmx/rspack</h1>
4
+
5
+ <div>
6
+ <a href="https://www.npmjs.com/package/@esmx/rspack">
7
+ <img src="https://img.shields.io/npm/v/@esmx/rspack.svg" alt="npm version" />
8
+ </a>
9
+ <a href="https://github.com/esmnext/esmx/actions/workflows/build.yml">
10
+ <img src="https://github.com/esmnext/esmx/actions/workflows/build.yml/badge.svg" alt="Build" />
11
+ </a>
12
+ <a href="https://www.esmnext.com/coverage/">
13
+ <img src="https://img.shields.io/badge/coverage-live%20report-brightgreen" alt="Coverage Report" />
14
+ </a>
15
+ <a href="https://nodejs.org/">
16
+ <img src="https://img.shields.io/node/v/@esmx/rspack.svg" alt="node version" />
17
+ </a>
18
+ <a href="https://bundlephobia.com/package/@esmx/rspack">
19
+ <img src="https://img.shields.io/bundlephobia/minzip/@esmx/rspack" alt="size" />
20
+ </a>
21
+ </div>
22
+
23
+ <p>A high-performance Rspack integration for Esmx microfrontend framework, providing module federation and SSR capabilities</p>
24
+
25
+ <p>
26
+ English | <a href="https://github.com/esmnext/esmx/blob/master/packages/rspack/README.zh-CN.md">中文</a>
27
+ </p>
28
+ </div>
29
+
30
+ ## 🚀 Features
31
+
32
+ - **High-Performance Build** - Ultra-fast building based on Rspack, providing excellent development experience
33
+ - **Application Support** - Complete support for standard applications and HTML applications development and building
34
+ - **Asset Processing** - Smart processing of various assets including JavaScript, CSS, images
35
+ - **SSR Support** - Built-in server-side rendering support for easy isomorphic application building
36
+ - **Developer Experience** - Supports hot reload, intelligent hints, and TypeScript
37
+
38
+ ## 📦 Installation
14
39
 
15
40
  ```bash
16
- pnpm add @esmx/rspack -D
17
- # 或
18
- yarn add @esmx/rspack -D
19
- # 或
20
41
  npm install @esmx/rspack -D
21
42
  ```
22
43
 
23
- ## 文档
44
+ ## 📚 Documentation
24
45
 
25
- 访问 [@esmx/rspack 官方文档](https://www.esmnext.com/api/app/rspack.html) 获取详细的使用指南和 API 文档。
46
+ Visit the [official documentation](https://www.esmnext.com/api/app/rspack.html) for detailed usage guides and API reference.
26
47
 
27
- ## 许可证
48
+ ## 📄 License
28
49
 
29
- MIT
50
+ MIT © [Esmx Team](https://github.com/esmnext/esmx)
@@ -0,0 +1,50 @@
1
+ <div align="center">
2
+ <img src="https://www.esmnext.com/logo.svg?t=2025" width="120" alt="Esmx Logo" />
3
+ <h1>@esmx/rspack</h1>
4
+
5
+ <div>
6
+ <a href="https://www.npmjs.com/package/@esmx/rspack">
7
+ <img src="https://img.shields.io/npm/v/@esmx/rspack.svg" alt="npm version" />
8
+ </a>
9
+ <a href="https://github.com/esmnext/esmx/actions/workflows/build.yml">
10
+ <img src="https://github.com/esmnext/esmx/actions/workflows/build.yml/badge.svg" alt="Build" />
11
+ </a>
12
+ <a href="https://www.esmnext.com/coverage/">
13
+ <img src="https://img.shields.io/badge/coverage-live%20report-brightgreen" alt="Coverage Report" />
14
+ </a>
15
+ <a href="https://nodejs.org/">
16
+ <img src="https://img.shields.io/node/v/@esmx/rspack.svg" alt="node version" />
17
+ </a>
18
+ <a href="https://bundlephobia.com/package/@esmx/rspack">
19
+ <img src="https://img.shields.io/bundlephobia/minzip/@esmx/rspack" alt="size" />
20
+ </a>
21
+ </div>
22
+
23
+ <p>为 Esmx 微前端框架提供的高性能 Rspack 集成,具备模块联邦和 SSR 能力</p>
24
+
25
+ <p>
26
+ <a href="https://github.com/esmnext/esmx/blob/master/packages/rspack/README.md">English</a> | 中文
27
+ </p>
28
+ </div>
29
+
30
+ ## 🚀 特性
31
+
32
+ - **高性能构建** - 基于 Rspack 的极速构建,为应用提供卓越的开发体验
33
+ - **应用支持** - 完整支持标准应用和 HTML 应用的开发与构建
34
+ - **资源处理** - 智能处理各类资源,支持 JavaScript、CSS、图片等
35
+ - **SSR 支持** - 内置服务端渲染支持,轻松构建同构应用
36
+ - **开发体验** - 支持热更新、智能提示和 TypeScript
37
+
38
+ ## 📦 安装
39
+
40
+ ```bash
41
+ npm install @esmx/rspack -D
42
+ ```
43
+
44
+ ## 📚 文档
45
+
46
+ 访问[官方文档](https://www.esmnext.com/api/app/rspack.html)获取详细的使用指南和 API 参考。
47
+
48
+ ## 📄 许可证
49
+
50
+ MIT © [Esmx Team](https://github.com/esmnext/esmx)
package/dist/app.mjs CHANGED
@@ -1,8 +1,7 @@
1
1
  import fs from "node:fs";
2
- import { pathToFileURL } from "node:url";
3
- import { styleText } from "node:util";
2
+ import path from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import {
5
- PathType,
6
5
  RenderContext,
7
6
  createApp,
8
7
  mergeMiddlewares
@@ -12,6 +11,11 @@ import hotMiddleware from "webpack-hot-middleware";
12
11
  import { createRspackConfig } from "./config.mjs";
13
12
  import { pack } from "./pack.mjs";
14
13
  import { createRsBuild } from "./utils/index.mjs";
14
+ const extension = path.extname(fileURLToPath(import.meta.url));
15
+ const hotFixCode = fs.readFileSync(
16
+ fileURLToPath(new URL(`./hot-fix${extension}`, import.meta.url)),
17
+ "utf-8"
18
+ );
15
19
  export async function createRspackApp(esmx, options) {
16
20
  const app = await createApp(esmx, esmx.command);
17
21
  switch (esmx.command) {
@@ -75,35 +79,19 @@ function rewriteRender(esmx) {
75
79
  const serverRender = module[rc.entryName];
76
80
  if (typeof serverRender === "function") {
77
81
  await serverRender(rc);
82
+ rc.html = rc.html.replace(
83
+ "</head>",
84
+ `
85
+ <script type="module">${hotFixCode}<\/script>
86
+ </head>
87
+ `
88
+ );
78
89
  }
79
90
  return rc;
80
91
  };
81
92
  }
82
93
  function rewriteBuild(esmx, options = {}) {
83
94
  return async () => {
84
- for (const item of esmx.moduleConfig.exports) {
85
- if (item.type === PathType.root) {
86
- const text = fs.readFileSync(
87
- esmx.resolvePath("./", item.exportPath),
88
- "utf-8"
89
- );
90
- if (/\bexport\s+\*\s+from\b/.test(text)) {
91
- console.log(
92
- styleText(
93
- "red",
94
- `The export * syntax is used in the file '${item.exportPath}', which will cause the packaging to fail.`
95
- )
96
- );
97
- console.log(
98
- styleText(
99
- "red",
100
- `Please use specific export syntax, such as export { a, b } from './a';`
101
- )
102
- );
103
- return false;
104
- }
105
- }
106
- }
107
95
  const ok = await createRsBuild([
108
96
  generateBuildConfig(esmx, options, "client"),
109
97
  generateBuildConfig(esmx, options, "server"),
@@ -113,10 +101,10 @@ function rewriteBuild(esmx, options = {}) {
113
101
  return false;
114
102
  }
115
103
  esmx.writeSync(
116
- esmx.resolvePath("dist/index.js"),
104
+ esmx.resolvePath("dist/index.mjs"),
117
105
  `
118
106
  async function start() {
119
- const options = await import('./node/src/entry.node.js').then(
107
+ const options = await import('./node/src/entry.node.mjs').then(
120
108
  (mod) => mod.default
121
109
  );
122
110
  const { Esmx } = await import('@esmx/core');
package/dist/config.mjs CHANGED
@@ -3,52 +3,26 @@ import {
3
3
  rspack
4
4
  } from "@rspack/core";
5
5
  import nodeExternals from "webpack-node-externals";
6
+ import { HMR_DIR, HMR_JSONP } from "./hmr-config.mjs";
6
7
  export function createRspackConfig(esmx, buildTarget, options) {
7
- const isWebApp = buildTarget === "client" || buildTarget === "server";
8
8
  const isHot = buildTarget === "client" && !esmx.isProd;
9
9
  return {
10
10
  /**
11
11
  * 项目根目录,不可修改
12
12
  */
13
13
  context: esmx.root,
14
- entry: (() => {
15
- const importPaths = [];
16
- switch (buildTarget) {
17
- case "client":
18
- importPaths.push(esmx.resolvePath("src/entry.client.ts"));
19
- isHot && importPaths.push(
20
- `${resolve("webpack-hot-middleware/client")}?path=${esmx.basePath}hot-middleware&timeout=5000&overlay=false`
21
- );
22
- break;
23
- case "server":
24
- importPaths.push(esmx.resolvePath("src/entry.server.ts"));
25
- break;
26
- case "node":
27
- importPaths.push(esmx.resolvePath("src/entry.node.ts"));
28
- break;
29
- }
30
- return {
31
- [`./src/entry.${buildTarget}`]: {
32
- import: importPaths
33
- }
34
- };
35
- })(),
36
14
  output: {
37
15
  clean: esmx.isProd,
38
- module: true,
39
- chunkFormat: esmx.isProd ? "module" : void 0,
40
- chunkLoading: esmx.isProd ? "import" : void 0,
41
- chunkFilename: esmx.isProd ? "chunks/[name].[contenthash:8].final.js" : "chunks/[name].js",
42
- library: {
43
- type: esmx.isProd ? "modern-module" : "module"
44
- },
45
- filename: buildTarget !== "node" && esmx.isProd ? "[name].[contenthash:8].final.js" : "[name].js",
16
+ chunkFilename: esmx.isProd ? "[name].[contenthash:8].final.mjs" : "[name].mjs",
17
+ filename: buildTarget !== "node" && esmx.isProd ? "[name].[contenthash:8].final.mjs" : "[name].mjs",
46
18
  cssFilename: esmx.isProd ? "[name].[contenthash:8].final.css" : "[name].css",
47
- cssChunkFilename: esmx.isProd ? "chunks/[name].[contenthash:8].final.css" : "chunks/[name].css",
19
+ cssChunkFilename: esmx.isProd ? "[name].[contenthash:8].final.css" : "[name].css",
48
20
  publicPath: buildTarget === "client" ? "auto" : `${esmx.basePathPlaceholder}${esmx.basePath}`,
49
21
  uniqueName: esmx.varName,
50
- hotUpdateChunkFilename: "__hot__/[id].[fullhash].hot-update.js",
51
- hotUpdateMainFilename: "__hot__/[runtime].[fullhash].hot-update.json",
22
+ hotUpdateGlobal: HMR_JSONP,
23
+ chunkLoadingGlobal: HMR_JSONP + "_chunk",
24
+ hotUpdateChunkFilename: `${HMR_DIR}/[id].[fullhash].hot-update.mjs`,
25
+ hotUpdateMainFilename: `${HMR_DIR}/[runtime].[fullhash].hot-update.json`,
52
26
  path: (() => {
53
27
  switch (buildTarget) {
54
28
  case "client":
@@ -58,33 +32,23 @@ export function createRspackConfig(esmx, buildTarget, options) {
58
32
  case "node":
59
33
  return esmx.resolvePath("dist/node");
60
34
  }
61
- })(),
62
- environment: {
63
- dynamicImport: true,
64
- dynamicImportInWorker: true,
65
- module: true,
66
- nodePrefixForCoreModules: true
67
- }
35
+ })()
68
36
  },
69
- // 默认插件,不可修改
70
37
  plugins: (() => {
71
38
  return [
72
- // 进度条插件
73
39
  new rspack.ProgressPlugin({
74
40
  prefix: buildTarget
75
41
  }),
76
- // 模块链接插件
77
- isWebApp ? moduleLinkPlugin(esmx.moduleConfig) : false,
78
- // 热更新插件
42
+ createModuleLinkPlugin(esmx, buildTarget),
79
43
  isHot ? new rspack.HotModuleReplacementPlugin() : false
80
44
  ];
81
45
  })(),
82
46
  module: {
83
47
  parser: {
84
48
  javascript: {
85
- url: buildTarget === "client" ? true : "relative",
86
- importMeta: false,
87
- strictExportPresence: true
49
+ // DEV hot update fix
50
+ dynamicImportMode: esmx.isProd ? "lazy" : "eager",
51
+ url: buildTarget === "client" ? true : "relative"
88
52
  }
89
53
  },
90
54
  generator: {
@@ -104,12 +68,11 @@ export function createRspackConfig(esmx, buildTarget, options) {
104
68
  },
105
69
  optimization: {
106
70
  minimize: options.minimize ?? esmx.isProd,
107
- avoidEntryIife: esmx.isProd,
108
- concatenateModules: esmx.isProd,
109
71
  emitOnErrors: true,
110
- splitChunks: {
111
- chunks: "async"
112
- }
72
+ // DEV hot update fix
73
+ splitChunks: esmx.isProd ? void 0 : false,
74
+ // DEV hot update fix
75
+ runtimeChunk: esmx.isProd ? void 0 : false
113
76
  },
114
77
  externalsPresets: {
115
78
  web: buildTarget === "client",
@@ -128,18 +91,43 @@ export function createRspackConfig(esmx, buildTarget, options) {
128
91
  }
129
92
  return [];
130
93
  })(),
131
- experiments: {
132
- outputModule: true,
133
- parallelCodeSplitting: true,
134
- rspackFuture: {
135
- bundlerInfo: { force: false }
136
- }
137
- },
138
94
  target: buildTarget === "client" ? "web" : "node22.6",
139
95
  mode: esmx.isProd ? "production" : "development",
140
96
  cache: !esmx.isProd
141
97
  };
142
98
  }
143
- function resolve(name) {
144
- return new URL(import.meta.resolve(name)).pathname;
99
+ function createModuleLinkPlugin(esmx, buildTarget) {
100
+ if (buildTarget === "node") {
101
+ return moduleLinkPlugin({
102
+ name: esmx.name,
103
+ exports: {
104
+ "src/entry.node": {
105
+ rewrite: false,
106
+ file: "./src/entry.node"
107
+ }
108
+ }
109
+ });
110
+ }
111
+ const exports = {};
112
+ for (const [name, item] of Object.entries(esmx.moduleConfig.exports)) {
113
+ if (item.inputTarget[buildTarget]) {
114
+ exports[name] = {
115
+ rewrite: item.rewrite,
116
+ file: item.inputTarget[buildTarget]
117
+ };
118
+ }
119
+ }
120
+ const preEntries = [];
121
+ if (buildTarget === "client" && !esmx.isProd) {
122
+ preEntries.push(
123
+ `webpack-hot-middleware/client?path=/${esmx.name}/hot-middleware`
124
+ );
125
+ }
126
+ return moduleLinkPlugin({
127
+ name: esmx.name,
128
+ injectChunkName: buildTarget === "server",
129
+ imports: esmx.moduleConfig.imports,
130
+ exports,
131
+ preEntries
132
+ });
145
133
  }
@@ -0,0 +1,2 @@
1
+ export declare const HMR_JSONP = "__esmx_rspack_hmr_jsonp__";
2
+ export declare const HMR_DIR = "__hot__";
@@ -0,0 +1,2 @@
1
+ export const HMR_JSONP = "__esmx_rspack_hmr_jsonp__";
2
+ export const HMR_DIR = "__hot__";
File without changes
@@ -0,0 +1,44 @@
1
+ (() => {
2
+ const HMR_JSONP = "__esmx_rspack_hmr_jsonp__";
3
+ const HMR_JSONP_LIST = "__esmx_rspack_hmr_jsonp_list__";
4
+ const list = window[HMR_JSONP_LIST] = window[HMR_JSONP_LIST] || [];
5
+ Object.defineProperty(window, HMR_JSONP, {
6
+ get() {
7
+ return (...args) => {
8
+ const hotUrl = getStackUrl(new Error().stack || "", 1);
9
+ if (hotUrl) {
10
+ const item = list.find(
11
+ (item2) => isSameModule(hotUrl, item2.url)
12
+ );
13
+ if (item) {
14
+ return item.jsonp(...args);
15
+ }
16
+ }
17
+ console.log("%chot update not found", "color: red", args);
18
+ };
19
+ },
20
+ set(jsonp) {
21
+ const url = getStackUrl(new Error().stack || "", 1);
22
+ if (url) {
23
+ list.push({ url, jsonp });
24
+ }
25
+ }
26
+ });
27
+ function isSameModule(hotUrl, originalUrl) {
28
+ const normalizedHotUrl = hotUrl.replace(/\/__hot__\//, "/").replace(/\.\w+\.hot-update\.mjs$/, ".mjs");
29
+ const normalizedOriginalUrl = originalUrl;
30
+ return normalizedHotUrl === normalizedOriginalUrl;
31
+ }
32
+ function getStackUrl(stack, index = 0) {
33
+ const lines = stack.split("\n");
34
+ const stackLines = lines.filter((line2) => line2.includes("at "));
35
+ if (index < 0 || index >= stackLines.length) {
36
+ return null;
37
+ }
38
+ const line = stackLines[index];
39
+ const withoutAt = line.replace(/^\s*at\s+/, "");
40
+ const urlMatch = withoutAt.match(/\((.*?)\)/);
41
+ const url = urlMatch ? urlMatch[1] : withoutAt;
42
+ return url.replace(/:\d+:\d+$/, "");
43
+ }
44
+ })();
package/dist/html-app.mjs CHANGED
@@ -10,7 +10,7 @@ export async function createRspackHtmlApp(esmx, options) {
10
10
  options = {
11
11
  ...options,
12
12
  target: {
13
- web: ["chrome>=87", "edge>=88", "firefox>=78", "safari>=14"],
13
+ web: ["chrome>=63", "firefox>=67", "safari>=11.1"],
14
14
  node: ["node>=22.6"],
15
15
  ...options?.target
16
16
  },
package/dist/pack.mjs CHANGED
@@ -30,8 +30,12 @@ export async function pack(esmx) {
30
30
  }
31
31
  async function buildPackageJson(esmx) {
32
32
  const [clientJson, serverJson, curJson] = await Promise.all([
33
- esmx.readJson(esmx.resolvePath("dist/client/manifest.json")),
34
- esmx.readJson(esmx.resolvePath("dist/server/manifest.json")),
33
+ esmx.readJson(
34
+ esmx.resolvePath("dist/client/manifest.json")
35
+ ),
36
+ esmx.readJson(
37
+ esmx.resolvePath("dist/server/manifest.json")
38
+ ),
35
39
  esmx.readJson(esmx.resolvePath("package.json"))
36
40
  ]);
37
41
  const exports = {
@@ -47,13 +51,13 @@ async function buildPackageJson(esmx) {
47
51
  const exportName = `./${name}`;
48
52
  if (client && server) {
49
53
  exports[exportName] = {
50
- default: `./server/${server}`,
51
- browser: `./client/${client}`
54
+ default: `./server/${server.file}`,
55
+ browser: `./client/${client.file}`
52
56
  };
53
57
  } else if (client) {
54
- exports[exportName] = `./client/${client}`;
58
+ exports[exportName] = `./client/${client.file}`;
55
59
  } else if (server) {
56
- exports[exportName] = `./server/${server}`;
60
+ exports[exportName] = `./server/${server.file}`;
57
61
  }
58
62
  });
59
63
  const buildJson = {
@@ -1,12 +1,6 @@
1
- import { type Compiler, type RspackOptions } from '@rspack/core';
1
+ import { type RspackOptions } from '@rspack/core';
2
2
  export declare function createRsBuild(options: RspackOptions[]): {
3
- readonly compilers: Compiler[];
3
+ readonly compilers: import("@rspack/core").Compiler[];
4
4
  build(): Promise<boolean>;
5
5
  watch(): void;
6
6
  };
7
- export declare class RsBuild {
8
- private compiler;
9
- constructor(options: RspackOptions);
10
- build(): Promise<boolean>;
11
- watch(): void;
12
- }
@@ -1,5 +1,8 @@
1
1
  import { styleText } from "node:util";
2
2
  import { rspack } from "@rspack/core";
3
+ function showError(message) {
4
+ console.error(styleText("red", message));
5
+ }
3
6
  export function createRsBuild(options) {
4
7
  const multiCompiler = rspack(options);
5
8
  return {
@@ -10,17 +13,18 @@ export function createRsBuild(options) {
10
13
  return new Promise((resolve) => {
11
14
  multiCompiler.run((err, stats) => {
12
15
  if (err) {
16
+ showError(err.message);
13
17
  return resolve(false);
14
18
  }
15
19
  if (stats?.hasErrors()) {
16
20
  stats.toJson({ errors: true })?.errors?.forEach((err2) => {
17
- console.log(styleText("red", err2.message));
21
+ showError(err2.message);
18
22
  });
19
23
  return resolve(false);
20
24
  }
21
25
  multiCompiler.close((err2) => {
22
26
  if (err2) {
23
- console.log(styleText("red", err2.message));
27
+ showError(err2.message);
24
28
  return resolve(false);
25
29
  }
26
30
  process.nextTick(() => {
@@ -33,7 +37,7 @@ export function createRsBuild(options) {
33
37
  watch() {
34
38
  const watching = multiCompiler.watch({}, (err, stats) => {
35
39
  if (err) {
36
- console.error(err);
40
+ console.log(styleText("red", err.message));
37
41
  return;
38
42
  }
39
43
  if (stats?.hasErrors()) {
@@ -61,37 +65,3 @@ export function createRsBuild(options) {
61
65
  }
62
66
  };
63
67
  }
64
- export class RsBuild {
65
- compiler;
66
- constructor(options) {
67
- this.compiler = rspack(options);
68
- }
69
- async build() {
70
- return new Promise((resolve) => {
71
- this.compiler.run((err, stats) => {
72
- if (err) {
73
- return resolve(false);
74
- }
75
- if (stats?.hasErrors()) {
76
- stats.toJson({ errors: true })?.errors?.forEach((err2) => {
77
- console.error(err2);
78
- });
79
- return resolve(false);
80
- }
81
- this.compiler.close((err2) => {
82
- if (err2) {
83
- console.error(err2);
84
- return resolve(false);
85
- }
86
- process.nextTick(() => {
87
- resolve(true);
88
- });
89
- });
90
- });
91
- });
92
- }
93
- watch() {
94
- const watching = this.compiler.watch({}, () => {
95
- });
96
- }
97
- }
package/package.json CHANGED
@@ -63,10 +63,10 @@
63
63
  }
64
64
  },
65
65
  "dependencies": {
66
- "@esmx/import": "3.0.0-rc.18",
67
- "@esmx/rspack-module-link-plugin": "3.0.0-rc.18",
66
+ "@esmx/import": "3.0.0-rc.20",
67
+ "@esmx/rspack-module-link-plugin": "3.0.0-rc.20",
68
68
  "@npmcli/arborist": "^9.0.1",
69
- "@rspack/core": "1.3.2",
69
+ "@rspack/core": "1.4.0",
70
70
  "css-loader": "^7.1.2",
71
71
  "less-loader": "^12.2.0",
72
72
  "node-polyfill-webpack-plugin": "^4.1.0",
@@ -79,21 +79,20 @@
79
79
  },
80
80
  "devDependencies": {
81
81
  "@biomejs/biome": "1.9.4",
82
- "@esmx/core": "3.0.0-rc.18",
83
- "@esmx/lint": "3.0.0-rc.18",
84
- "@gez/lint": "3.0.0-rc.9",
85
- "@types/node": "22.13.10",
86
- "@types/npmcli__arborist": "^5.6.11",
82
+ "@esmx/core": "3.0.0-rc.20",
83
+ "@esmx/lint": "3.0.0-rc.20",
84
+ "@types/node": "22.15.18",
85
+ "@types/npmcli__arborist": "^6.3.1",
87
86
  "@types/pacote": "^11.1.8",
88
87
  "@types/webpack-hot-middleware": "^2.25.9",
89
88
  "@types/webpack-node-externals": "^3.0.4",
90
- "@vitest/coverage-v8": "3.0.8",
91
- "stylelint": "16.15.0",
89
+ "@vitest/coverage-v8": "3.1.3",
90
+ "stylelint": "16.19.1",
92
91
  "typescript": "5.8.2",
93
- "unbuild": "2.0.0",
94
- "vitest": "3.0.8"
92
+ "unbuild": "3.5.0",
93
+ "vitest": "3.1.3"
95
94
  },
96
- "version": "3.0.0-rc.18",
95
+ "version": "3.0.0-rc.20",
97
96
  "type": "module",
98
97
  "private": false,
99
98
  "exports": {
@@ -112,5 +111,5 @@
112
111
  "template",
113
112
  "public"
114
113
  ],
115
- "gitHead": "79ceb323f985dd88dbd7f1349f61f341b6d522c4"
114
+ "gitHead": "4c6490c23cbc148cb189b3f2cdae930eed901607"
116
115
  }
package/src/app.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import fs from 'node:fs';
2
- import { pathToFileURL } from 'node:url';
3
- import { styleText } from 'node:util';
2
+ import path from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
4
  import {
5
5
  type App,
6
6
  type Esmx,
7
7
  type Middleware,
8
- PathType,
9
8
  RenderContext,
10
9
  type RenderContextOptions,
11
10
  type ServerRenderHandle,
@@ -20,6 +19,12 @@ import { createRspackConfig } from './config';
20
19
  import { pack } from './pack';
21
20
  import { createRsBuild } from './utils';
22
21
 
22
+ const extension = path.extname(fileURLToPath(import.meta.url));
23
+ const hotFixCode = fs.readFileSync(
24
+ fileURLToPath(new URL(`./hot-fix${extension}`, import.meta.url)),
25
+ 'utf-8'
26
+ );
27
+
23
28
  /**
24
29
  * Rspack 应用配置上下文接口。
25
30
  *
@@ -263,6 +268,13 @@ function rewriteRender(esmx: Esmx) {
263
268
  const serverRender: ServerRenderHandle = module[rc.entryName];
264
269
  if (typeof serverRender === 'function') {
265
270
  await serverRender(rc);
271
+ rc.html = rc.html.replace(
272
+ '</head>',
273
+ `
274
+ <script type="module">${hotFixCode}</script>
275
+ </head>
276
+ `
277
+ );
266
278
  }
267
279
  return rc;
268
280
  };
@@ -270,29 +282,6 @@ function rewriteRender(esmx: Esmx) {
270
282
 
271
283
  function rewriteBuild(esmx: Esmx, options: RspackAppOptions = {}) {
272
284
  return async (): Promise<boolean> => {
273
- for (const item of esmx.moduleConfig.exports) {
274
- if (item.type === PathType.root) {
275
- const text = fs.readFileSync(
276
- esmx.resolvePath('./', item.exportPath),
277
- 'utf-8'
278
- );
279
- if (/\bexport\s+\*\s+from\b/.test(text)) {
280
- console.log(
281
- styleText(
282
- 'red',
283
- `The export * syntax is used in the file '${item.exportPath}', which will cause the packaging to fail.`
284
- )
285
- );
286
- console.log(
287
- styleText(
288
- 'red',
289
- `Please use specific export syntax, such as export { a, b } from './a';`
290
- )
291
- );
292
- return false;
293
- }
294
- }
295
- }
296
285
  const ok = await createRsBuild([
297
286
  generateBuildConfig(esmx, options, 'client'),
298
287
  generateBuildConfig(esmx, options, 'server'),
@@ -302,10 +291,10 @@ function rewriteBuild(esmx: Esmx, options: RspackAppOptions = {}) {
302
291
  return false;
303
292
  }
304
293
  esmx.writeSync(
305
- esmx.resolvePath('dist/index.js'),
294
+ esmx.resolvePath('dist/index.mjs'),
306
295
  `
307
296
  async function start() {
308
- const options = await import('./node/src/entry.node.js').then(
297
+ const options = await import('./node/src/entry.node.mjs').then(
309
298
  (mod) => mod.default
310
299
  );
311
300
  const { Esmx } = await import('@esmx/core');
package/src/config.ts CHANGED
@@ -2,6 +2,7 @@ import type { Esmx } from '@esmx/core';
2
2
  import { moduleLinkPlugin } from '@esmx/rspack-module-link-plugin';
3
3
  import {
4
4
  type ExternalItem,
5
+ type Plugin,
5
6
  type Plugins,
6
7
  type RspackOptions,
7
8
  rspack
@@ -9,6 +10,7 @@ import {
9
10
  import nodeExternals from 'webpack-node-externals';
10
11
  import type { RspackAppOptions } from './app';
11
12
  import type { BuildTarget } from './build-target';
13
+ import { HMR_DIR, HMR_JSONP } from './hmr-config';
12
14
 
13
15
  /**
14
16
  * 构建 Client、Server、Node 的基础配置
@@ -18,65 +20,36 @@ export function createRspackConfig(
18
20
  buildTarget: BuildTarget,
19
21
  options: RspackAppOptions
20
22
  ): RspackOptions {
21
- const isWebApp = buildTarget === 'client' || buildTarget === 'server';
22
23
  const isHot = buildTarget === 'client' && !esmx.isProd;
23
24
  return {
24
25
  /**
25
26
  * 项目根目录,不可修改
26
27
  */
27
28
  context: esmx.root,
28
- entry: (() => {
29
- const importPaths: string[] = [];
30
- switch (buildTarget) {
31
- case 'client':
32
- importPaths.push(esmx.resolvePath('src/entry.client.ts'));
33
- isHot &&
34
- importPaths.push(
35
- `${resolve('webpack-hot-middleware/client')}?path=${esmx.basePath}hot-middleware&timeout=5000&overlay=false`
36
- );
37
- break;
38
- case 'server':
39
- importPaths.push(esmx.resolvePath('src/entry.server.ts'));
40
- break;
41
- case 'node':
42
- importPaths.push(esmx.resolvePath('src/entry.node.ts'));
43
- break;
44
- }
45
- return {
46
- [`./src/entry.${buildTarget}`]: {
47
- import: importPaths
48
- }
49
- };
50
- })(),
51
29
  output: {
52
30
  clean: esmx.isProd,
53
- module: true,
54
- chunkFormat: esmx.isProd ? 'module' : undefined,
55
- chunkLoading: esmx.isProd ? 'import' : undefined,
56
31
  chunkFilename: esmx.isProd
57
- ? 'chunks/[name].[contenthash:8].final.js'
58
- : 'chunks/[name].js',
59
- library: {
60
- type: esmx.isProd ? 'modern-module' : 'module'
61
- },
32
+ ? '[name].[contenthash:8].final.mjs'
33
+ : '[name].mjs',
62
34
  filename:
63
35
  buildTarget !== 'node' && esmx.isProd
64
- ? '[name].[contenthash:8].final.js'
65
- : '[name].js',
36
+ ? '[name].[contenthash:8].final.mjs'
37
+ : '[name].mjs',
66
38
  cssFilename: esmx.isProd
67
39
  ? '[name].[contenthash:8].final.css'
68
40
  : '[name].css',
69
41
  cssChunkFilename: esmx.isProd
70
- ? 'chunks/[name].[contenthash:8].final.css'
71
- : 'chunks/[name].css',
42
+ ? '[name].[contenthash:8].final.css'
43
+ : '[name].css',
72
44
  publicPath:
73
45
  buildTarget === 'client'
74
46
  ? 'auto'
75
47
  : `${esmx.basePathPlaceholder}${esmx.basePath}`,
76
48
  uniqueName: esmx.varName,
77
- hotUpdateChunkFilename: '__hot__/[id].[fullhash].hot-update.js',
78
- hotUpdateMainFilename:
79
- '__hot__/[runtime].[fullhash].hot-update.json',
49
+ hotUpdateGlobal: HMR_JSONP,
50
+ chunkLoadingGlobal: HMR_JSONP + '_chunk',
51
+ hotUpdateChunkFilename: `${HMR_DIR}/[id].[fullhash].hot-update.mjs`,
52
+ hotUpdateMainFilename: `${HMR_DIR}/[runtime].[fullhash].hot-update.json`,
80
53
  path: ((): string => {
81
54
  switch (buildTarget) {
82
55
  case 'client':
@@ -86,33 +59,23 @@ export function createRspackConfig(
86
59
  case 'node':
87
60
  return esmx.resolvePath('dist/node');
88
61
  }
89
- })(),
90
- environment: {
91
- dynamicImport: true,
92
- dynamicImportInWorker: true,
93
- module: true,
94
- nodePrefixForCoreModules: true
95
- }
62
+ })()
96
63
  },
97
- // 默认插件,不可修改
98
64
  plugins: ((): Plugins => {
99
65
  return [
100
- // 进度条插件
101
66
  new rspack.ProgressPlugin({
102
67
  prefix: buildTarget
103
68
  }),
104
- // 模块链接插件
105
- isWebApp ? moduleLinkPlugin(esmx.moduleConfig) : false,
106
- // 热更新插件
69
+ createModuleLinkPlugin(esmx, buildTarget),
107
70
  isHot ? new rspack.HotModuleReplacementPlugin() : false
108
71
  ];
109
72
  })(),
110
73
  module: {
111
74
  parser: {
112
75
  javascript: {
113
- url: buildTarget === 'client' ? true : 'relative',
114
- importMeta: false,
115
- strictExportPresence: true
76
+ // DEV hot update fix
77
+ dynamicImportMode: esmx.isProd ? 'lazy' : 'eager',
78
+ url: buildTarget === 'client' ? true : 'relative'
116
79
  }
117
80
  },
118
81
  generator: {
@@ -132,12 +95,11 @@ export function createRspackConfig(
132
95
  },
133
96
  optimization: {
134
97
  minimize: options.minimize ?? esmx.isProd,
135
- avoidEntryIife: esmx.isProd,
136
- concatenateModules: esmx.isProd,
137
98
  emitOnErrors: true,
138
- splitChunks: {
139
- chunks: 'async'
140
- }
99
+ // DEV hot update fix
100
+ splitChunks: esmx.isProd ? undefined : false,
101
+ // DEV hot update fix
102
+ runtimeChunk: esmx.isProd ? undefined : false
141
103
  },
142
104
  externalsPresets: {
143
105
  web: buildTarget === 'client',
@@ -156,19 +118,51 @@ export function createRspackConfig(
156
118
  }
157
119
  return [];
158
120
  })(),
159
- experiments: {
160
- outputModule: true,
161
- parallelCodeSplitting: true,
162
- rspackFuture: {
163
- bundlerInfo: { force: false }
164
- }
165
- },
166
121
  target: buildTarget === 'client' ? 'web' : 'node22.6',
167
122
  mode: esmx.isProd ? 'production' : 'development',
168
123
  cache: !esmx.isProd
169
124
  };
170
125
  }
171
126
 
172
- function resolve(name: string) {
173
- return new URL(import.meta.resolve(name)).pathname;
127
+ function createModuleLinkPlugin(esmx: Esmx, buildTarget: BuildTarget): Plugin {
128
+ if (buildTarget === 'node') {
129
+ return moduleLinkPlugin({
130
+ name: esmx.name,
131
+ exports: {
132
+ 'src/entry.node': {
133
+ rewrite: false,
134
+ file: './src/entry.node'
135
+ }
136
+ }
137
+ });
138
+ }
139
+ const exports: Record<
140
+ string,
141
+ {
142
+ rewrite: boolean;
143
+ file: string;
144
+ }
145
+ > = {};
146
+ for (const [name, item] of Object.entries(esmx.moduleConfig.exports)) {
147
+ if (item.inputTarget[buildTarget]) {
148
+ exports[name] = {
149
+ rewrite: item.rewrite,
150
+ file: item.inputTarget[buildTarget]
151
+ };
152
+ }
153
+ }
154
+ const preEntries: string[] = [];
155
+ if (buildTarget === 'client' && !esmx.isProd) {
156
+ preEntries.push(
157
+ `webpack-hot-middleware/client?path=/${esmx.name}/hot-middleware`
158
+ );
159
+ }
160
+
161
+ return moduleLinkPlugin({
162
+ name: esmx.name,
163
+ injectChunkName: buildTarget === 'server',
164
+ imports: esmx.moduleConfig.imports,
165
+ exports,
166
+ preEntries
167
+ });
174
168
  }
@@ -0,0 +1,2 @@
1
+ export const HMR_JSONP = '__esmx_rspack_hmr_jsonp__';
2
+ export const HMR_DIR = '__hot__';
package/src/hot-fix.ts ADDED
@@ -0,0 +1,51 @@
1
+ (() => {
2
+ const HMR_JSONP = '__esmx_rspack_hmr_jsonp__';
3
+ const HMR_JSONP_LIST = '__esmx_rspack_hmr_jsonp_list__';
4
+
5
+ const list: Array<{ url: string; jsonp: Function }> = (window[
6
+ HMR_JSONP_LIST
7
+ ] = window[HMR_JSONP_LIST] || []);
8
+
9
+ Object.defineProperty(window, HMR_JSONP, {
10
+ get() {
11
+ return (...args: any[]) => {
12
+ const hotUrl = getStackUrl(new Error().stack || '', 1);
13
+ if (hotUrl) {
14
+ const item = list.find((item) =>
15
+ isSameModule(hotUrl, item.url)
16
+ );
17
+ if (item) {
18
+ return item.jsonp(...args);
19
+ }
20
+ }
21
+ console.log('%chot update not found', 'color: red', args);
22
+ };
23
+ },
24
+ set(jsonp) {
25
+ const url = getStackUrl(new Error().stack || '', 1);
26
+ if (url) {
27
+ list.push({ url, jsonp });
28
+ }
29
+ }
30
+ });
31
+ function isSameModule(hotUrl: string, originalUrl: string): boolean {
32
+ const normalizedHotUrl = hotUrl
33
+ .replace(/\/__hot__\//, '/')
34
+ .replace(/\.\w+\.hot-update\.mjs$/, '.mjs');
35
+ const normalizedOriginalUrl = originalUrl;
36
+ return normalizedHotUrl === normalizedOriginalUrl;
37
+ }
38
+
39
+ function getStackUrl(stack: string, index = 0): string | null {
40
+ const lines = stack.split('\n');
41
+ const stackLines = lines.filter((line) => line.includes('at '));
42
+ if (index < 0 || index >= stackLines.length) {
43
+ return null;
44
+ }
45
+ const line = stackLines[index];
46
+ const withoutAt = line.replace(/^\s*at\s+/, '');
47
+ const urlMatch = withoutAt.match(/\((.*?)\)/);
48
+ const url = urlMatch ? urlMatch[1] : withoutAt;
49
+ return url.replace(/:\d+:\d+$/, '');
50
+ }
51
+ })();
package/src/html-app.ts CHANGED
@@ -326,7 +326,7 @@ export async function createRspackHtmlApp(
326
326
  options = {
327
327
  ...options,
328
328
  target: {
329
- web: ['chrome>=87', 'edge>=88', 'firefox>=78', 'safari>=14'],
329
+ web: ['chrome>=63', 'firefox>=67', 'safari>=11.1'],
330
330
  node: ['node>=22.6'],
331
331
  ...options?.target
332
332
  },
package/src/pack.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import crypto from 'node:crypto';
2
- import type { Esmx } from '@esmx/core';
2
+ import type { Esmx, ManifestJson } from '@esmx/core';
3
3
  import Arborist from '@npmcli/arborist';
4
4
  import pacote from 'pacote';
5
5
 
@@ -38,8 +38,12 @@ export async function pack(esmx: Esmx): Promise<boolean> {
38
38
 
39
39
  async function buildPackageJson(esmx: Esmx): Promise<Record<string, any>> {
40
40
  const [clientJson, serverJson, curJson] = await Promise.all([
41
- esmx.readJson(esmx.resolvePath('dist/client/manifest.json')),
42
- esmx.readJson(esmx.resolvePath('dist/server/manifest.json')),
41
+ esmx.readJson<ManifestJson>(
42
+ esmx.resolvePath('dist/client/manifest.json')
43
+ ),
44
+ esmx.readJson<ManifestJson>(
45
+ esmx.resolvePath('dist/server/manifest.json')
46
+ ),
43
47
  esmx.readJson(esmx.resolvePath('package.json'))
44
48
  ]);
45
49
  const exports: Record<string, any> = {
@@ -55,13 +59,13 @@ async function buildPackageJson(esmx: Esmx): Promise<Record<string, any>> {
55
59
  const exportName = `./${name}`;
56
60
  if (client && server) {
57
61
  exports[exportName] = {
58
- default: `./server/${server}`,
59
- browser: `./client/${client}`
62
+ default: `./server/${server.file}`,
63
+ browser: `./client/${client.file}`
60
64
  };
61
65
  } else if (client) {
62
- exports[exportName] = `./client/${client}`;
66
+ exports[exportName] = `./client/${client.file}`;
63
67
  } else if (server) {
64
- exports[exportName] = `./server/${server}`;
68
+ exports[exportName] = `./server/${server.file}`;
65
69
  }
66
70
  });
67
71
 
@@ -1,5 +1,9 @@
1
1
  import { styleText } from 'node:util';
2
- import { type Compiler, type RspackOptions, rspack } from '@rspack/core';
2
+ import { type RspackOptions, rspack } from '@rspack/core';
3
+
4
+ function showError(message: string) {
5
+ console.error(styleText('red', message));
6
+ }
3
7
 
4
8
  export function createRsBuild(options: RspackOptions[]) {
5
9
  const multiCompiler = rspack(options);
@@ -11,19 +15,20 @@ export function createRsBuild(options: RspackOptions[]) {
11
15
  return new Promise<boolean>((resolve) => {
12
16
  multiCompiler.run((err, stats) => {
13
17
  if (err) {
18
+ showError(err.message);
14
19
  return resolve(false);
15
20
  }
16
21
  if (stats?.hasErrors()) {
17
22
  stats
18
23
  .toJson({ errors: true })
19
24
  ?.errors?.forEach((err) => {
20
- console.log(styleText('red', err.message));
25
+ showError(err.message);
21
26
  });
22
27
  return resolve(false);
23
28
  }
24
29
  multiCompiler.close((err) => {
25
30
  if (err) {
26
- console.log(styleText('red', err.message));
31
+ showError(err.message);
27
32
  return resolve(false);
28
33
  }
29
34
  process.nextTick(() => {
@@ -36,7 +41,7 @@ export function createRsBuild(options: RspackOptions[]) {
36
41
  watch() {
37
42
  const watching = multiCompiler.watch({}, (err, stats) => {
38
43
  if (err) {
39
- console.error(err);
44
+ console.log(styleText('red', err.message));
40
45
  return;
41
46
  }
42
47
  if (stats?.hasErrors()) {
@@ -69,37 +74,3 @@ export function createRsBuild(options: RspackOptions[]) {
69
74
  }
70
75
  };
71
76
  }
72
-
73
- export class RsBuild {
74
- private compiler: Compiler;
75
- public constructor(options: RspackOptions) {
76
- this.compiler = rspack(options);
77
- }
78
- public async build() {
79
- return new Promise<boolean>((resolve) => {
80
- this.compiler.run((err, stats) => {
81
- if (err) {
82
- return resolve(false);
83
- }
84
- if (stats?.hasErrors()) {
85
- stats.toJson({ errors: true })?.errors?.forEach((err) => {
86
- console.error(err);
87
- });
88
- return resolve(false);
89
- }
90
- this.compiler.close((err) => {
91
- if (err) {
92
- console.error(err);
93
- return resolve(false);
94
- }
95
- process.nextTick(() => {
96
- resolve(true);
97
- });
98
- });
99
- });
100
- });
101
- }
102
- public watch() {
103
- const watching = this.compiler.watch({}, () => {});
104
- }
105
- }