@lark-apaas/fullstack-rspack-preset 1.0.56-alpha.6 → 1.0.56-alpha.7
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/lib/preset.js +5 -0
- package/lib/rspack-plugins/basename-injection-loader.d.ts +2 -0
- package/lib/rspack-plugins/basename-injection-loader.js +23 -0
- package/lib/rspack-plugins/basename-injection-plugin.d.ts +31 -0
- package/lib/rspack-plugins/basename-injection-plugin.js +115 -0
- package/lib/rspack-plugins/source-map-upload-plugin.js +16 -10
- package/lib/rspack-plugins/view-context-injection-plugin.js +1 -0
- package/package.json +1 -1
package/lib/preset.js
CHANGED
|
@@ -16,6 +16,7 @@ const slardar_performance_monitor_plugin_1 = __importDefault(require("./rspack-p
|
|
|
16
16
|
const view_context_injection_plugin_1 = __importDefault(require("./rspack-plugins/view-context-injection-plugin"));
|
|
17
17
|
const og_meta_injection_plugin_1 = __importDefault(require("./rspack-plugins/og-meta-injection-plugin"));
|
|
18
18
|
const runtime_injection_plugin_1 = __importDefault(require("./rspack-plugins/runtime-injection-plugin"));
|
|
19
|
+
const basename_injection_plugin_1 = __importDefault(require("./rspack-plugins/basename-injection-plugin"));
|
|
19
20
|
const css_legacy_plugin_1 = __importDefault(require("./rspack-plugins/css-legacy-plugin"));
|
|
20
21
|
const polyfill_plugin_1 = __importDefault(require("./rspack-plugins/polyfill-plugin"));
|
|
21
22
|
const static_assets_plugin_1 = __importDefault(require("./rspack-plugins/static-assets-plugin"));
|
|
@@ -222,6 +223,10 @@ function createRecommendRspackConfig(options) {
|
|
|
222
223
|
plugins: [
|
|
223
224
|
// 运行时注入插件 - 自动将 @lark-apaas/client-toolkit/runtime 注入到所有入口之前
|
|
224
225
|
new runtime_injection_plugin_1.default(),
|
|
226
|
+
// basename 运行时注入 - 改写入口文件 process.env.CLIENT_BASE_PATH 为运行时读 window.__BASENAME__
|
|
227
|
+
// 必须在 DefinePlugin 之前生效(plugin 内部 enforce:'pre')
|
|
228
|
+
// 匹配 client/src/ 根层级入口文件(index.tsx / main.tsx 等),不影响 SDK 内部代码
|
|
229
|
+
new basename_injection_plugin_1.default({ envBasePath: clientBasePath }),
|
|
225
230
|
new core_1.default.BannerPlugin({
|
|
226
231
|
banner: `window.__RELEASE_COMMIT_ID__ = '${releaseId}';`,
|
|
227
232
|
raw: true,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Basename Injection Loader
|
|
4
|
+
*
|
|
5
|
+
* 仅由 BasenameInjectionPlugin 在入口文件上挂载,做字符串替换:
|
|
6
|
+
* process.env.CLIENT_BASE_PATH
|
|
7
|
+
* → ((typeof window !== "undefined" && window.__BASENAME__) || "<envBasePath>")
|
|
8
|
+
*
|
|
9
|
+
* loader 阶段天然早于 DefinePlugin 的表达式替换(rspack source-transform → parsing → DefinePlugin 流水线顺序)。
|
|
10
|
+
* 替换后的字符串里没有 process.env.X 形式,DefinePlugin 不会再二次替换。
|
|
11
|
+
*/
|
|
12
|
+
module.exports = function basenameInjectionLoader(source) {
|
|
13
|
+
const options = (typeof this.getOptions === 'function' ? this.getOptions() : this.query) || {};
|
|
14
|
+
const envBasePath = options.envBasePath || '';
|
|
15
|
+
if (typeof source !== 'string' || !source.includes('process.env.CLIENT_BASE_PATH')) {
|
|
16
|
+
return source;
|
|
17
|
+
}
|
|
18
|
+
const fallback = JSON.stringify(envBasePath);
|
|
19
|
+
const runtimeExpr = '((typeof window !== "undefined" && window.__BASENAME__) || ' +
|
|
20
|
+
fallback +
|
|
21
|
+
')';
|
|
22
|
+
return source.replace(/\bprocess\.env\.CLIENT_BASE_PATH\b/g, runtimeExpr);
|
|
23
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basename Injection Plugin for Rspack
|
|
3
|
+
*
|
|
4
|
+
* 在 module rules 最前面注入一个 loader,仅匹配应用入口文件,
|
|
5
|
+
* 把入口里的 process.env.CLIENT_BASE_PATH
|
|
6
|
+
* 改写为运行时读 window.__BASENAME__(fallback 到构建时常量)。
|
|
7
|
+
*
|
|
8
|
+
* 让 BrowserRouter basename 运行时动态化,实现同一份 bundle 跑在不同 basename 下
|
|
9
|
+
* (默认域名 `/app/<appId>`、自定义域名 `/` 或 `/<alias>`)。
|
|
10
|
+
*
|
|
11
|
+
* 入口文件识别:在 entryOption hook(配置已完全解析合并)中从 entry 提取业务入口路径,
|
|
12
|
+
* 过滤掉 node_modules 包名(runtime 注入、polyfill 等非业务入口)和 Module Federation
|
|
13
|
+
* 远程模块,生成精确匹配正则。兜底到 client/src/index.{tsx,ts,jsx,js} 正则。
|
|
14
|
+
*
|
|
15
|
+
* 关键约束:
|
|
16
|
+
* - 只匹配入口文件,不影响 SDK 内部代码(如 client-toolkit-lite 仍读编译期常量)
|
|
17
|
+
* - loader 阶段天然早于 DefinePlugin 的表达式替换(rspack 内部 source-transform → parsing → DefinePlugin 流水线顺序)
|
|
18
|
+
* enforce:'pre' 是用来跟 babel-loader / swc-loader 等其他 loader 抢先,避免被它们先处理掉表达式
|
|
19
|
+
* - 兜底常量来自构建时 process.env.CLIENT_BASE_PATH,保证降级运行
|
|
20
|
+
*/
|
|
21
|
+
import type { Compiler } from '@rspack/core';
|
|
22
|
+
export interface BasenameInjectionPluginOptions {
|
|
23
|
+
/** 编译时兜底 basename,通常等于 process.env.CLIENT_BASE_PATH */
|
|
24
|
+
envBasePath?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare class BasenameInjectionPlugin {
|
|
27
|
+
private readonly envBasePath;
|
|
28
|
+
constructor(options?: BasenameInjectionPluginOptions);
|
|
29
|
+
apply(compiler: Compiler): void;
|
|
30
|
+
}
|
|
31
|
+
export default BasenameInjectionPlugin;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Basename Injection Plugin for Rspack
|
|
4
|
+
*
|
|
5
|
+
* 在 module rules 最前面注入一个 loader,仅匹配应用入口文件,
|
|
6
|
+
* 把入口里的 process.env.CLIENT_BASE_PATH
|
|
7
|
+
* 改写为运行时读 window.__BASENAME__(fallback 到构建时常量)。
|
|
8
|
+
*
|
|
9
|
+
* 让 BrowserRouter basename 运行时动态化,实现同一份 bundle 跑在不同 basename 下
|
|
10
|
+
* (默认域名 `/app/<appId>`、自定义域名 `/` 或 `/<alias>`)。
|
|
11
|
+
*
|
|
12
|
+
* 入口文件识别:在 entryOption hook(配置已完全解析合并)中从 entry 提取业务入口路径,
|
|
13
|
+
* 过滤掉 node_modules 包名(runtime 注入、polyfill 等非业务入口)和 Module Federation
|
|
14
|
+
* 远程模块,生成精确匹配正则。兜底到 client/src/index.{tsx,ts,jsx,js} 正则。
|
|
15
|
+
*
|
|
16
|
+
* 关键约束:
|
|
17
|
+
* - 只匹配入口文件,不影响 SDK 内部代码(如 client-toolkit-lite 仍读编译期常量)
|
|
18
|
+
* - loader 阶段天然早于 DefinePlugin 的表达式替换(rspack 内部 source-transform → parsing → DefinePlugin 流水线顺序)
|
|
19
|
+
* enforce:'pre' 是用来跟 babel-loader / swc-loader 等其他 loader 抢先,避免被它们先处理掉表达式
|
|
20
|
+
* - 兜底常量来自构建时 process.env.CLIENT_BASE_PATH,保证降级运行
|
|
21
|
+
*/
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.BasenameInjectionPlugin = void 0;
|
|
27
|
+
const path_1 = __importDefault(require("path"));
|
|
28
|
+
const PLUGIN_NAME = 'BasenameInjectionPlugin';
|
|
29
|
+
const FALLBACK_ENTRY_REGEX = /[/\\]client[/\\]src[/\\]index\.(tsx?|jsx?)$/;
|
|
30
|
+
/**
|
|
31
|
+
* 判断 import 路径是否为本地业务文件(相对路径或绝对路径),
|
|
32
|
+
* 排除 node_modules 包名(runtime 注入、polyfill、HMR client 等)。
|
|
33
|
+
*/
|
|
34
|
+
function isLocalFile(imp) {
|
|
35
|
+
return imp.startsWith('.') || imp.startsWith('/');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 从 entry 配置中提取所有本地业务入口文件的绝对路径。
|
|
39
|
+
*
|
|
40
|
+
* entry 结构(entryOption hook 阶段已规范化):
|
|
41
|
+
* { main: { import: ['@lark-apaas/client-toolkit/runtime', './client/src/index.tsx'] } }
|
|
42
|
+
*
|
|
43
|
+
* 过滤规则:
|
|
44
|
+
* - 跳过非本地路径(node_modules 包名,如 runtime 注入、polyfill、HMR client)
|
|
45
|
+
* - 跳过 node_modules 内的文件(Module Federation 远程模块等)
|
|
46
|
+
*/
|
|
47
|
+
function resolveEntryFiles(entry, context) {
|
|
48
|
+
const files = [];
|
|
49
|
+
for (const descriptor of Object.values(entry)) {
|
|
50
|
+
const imports = descriptor.import;
|
|
51
|
+
if (!Array.isArray(imports))
|
|
52
|
+
continue;
|
|
53
|
+
for (const imp of imports) {
|
|
54
|
+
if (!isLocalFile(imp))
|
|
55
|
+
continue;
|
|
56
|
+
const resolved = path_1.default.resolve(context, imp);
|
|
57
|
+
if (resolved.includes('node_modules'))
|
|
58
|
+
continue;
|
|
59
|
+
files.push(resolved);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return files;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 构建入口文件匹配正则:精确匹配 resolved 路径,无路径时兜底默认正则。
|
|
66
|
+
* 路径分隔符统一用 [/\\] 匹配,避免跨平台问题(rspack 传给 test 的路径分隔符不确定)。
|
|
67
|
+
*/
|
|
68
|
+
function buildEntryTest(entryFiles) {
|
|
69
|
+
if (entryFiles.length === 0)
|
|
70
|
+
return FALLBACK_ENTRY_REGEX;
|
|
71
|
+
const escaped = entryFiles.map(f => {
|
|
72
|
+
return path_1.default.normalize(f)
|
|
73
|
+
.split(path_1.default.sep) // 按平台分隔符拆段
|
|
74
|
+
.map(seg => seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) // 每段转义正则特殊字符
|
|
75
|
+
.join('[/\\\\]'); // 用 [/\\] 重新拼接
|
|
76
|
+
});
|
|
77
|
+
return new RegExp(`(${escaped.join('|')})$`);
|
|
78
|
+
}
|
|
79
|
+
class BasenameInjectionPlugin {
|
|
80
|
+
constructor(options = {}) {
|
|
81
|
+
this.envBasePath = options.envBasePath ?? process.env.CLIENT_BASE_PATH ?? '';
|
|
82
|
+
}
|
|
83
|
+
apply(compiler) {
|
|
84
|
+
const loaderPath = path_1.default.resolve(__dirname, './basename-injection-loader.js');
|
|
85
|
+
const envBasePath = this.envBasePath;
|
|
86
|
+
// entryOption hook:配置已完全解析合并(包括异步 entry 函数的返回值),
|
|
87
|
+
// 是读 entry 最安全的时机。参考 RuntimeInjectionPlugin 的做法。
|
|
88
|
+
compiler.hooks.entryOption.tap(PLUGIN_NAME, (context, entry) => {
|
|
89
|
+
// 动态 entry(函数形式)无法在此阶段解析,兜底到默认正则
|
|
90
|
+
const entryFiles = (typeof entry !== 'function')
|
|
91
|
+
? resolveEntryFiles(entry, context)
|
|
92
|
+
: [];
|
|
93
|
+
const entryTest = buildEntryTest(entryFiles);
|
|
94
|
+
if (!compiler.options.module) {
|
|
95
|
+
compiler.options.module = { rules: [] };
|
|
96
|
+
}
|
|
97
|
+
const rules = compiler.options.module.rules || [];
|
|
98
|
+
compiler.options.module.rules = [
|
|
99
|
+
{
|
|
100
|
+
test: entryTest,
|
|
101
|
+
enforce: 'pre',
|
|
102
|
+
use: [
|
|
103
|
+
{
|
|
104
|
+
loader: loaderPath,
|
|
105
|
+
options: { envBasePath },
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
...rules,
|
|
110
|
+
];
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.BasenameInjectionPlugin = BasenameInjectionPlugin;
|
|
115
|
+
exports.default = BasenameInjectionPlugin;
|
|
@@ -68,19 +68,25 @@ class SourceMapUploadPlugin {
|
|
|
68
68
|
if (!fs.existsSync(destinationDir)) {
|
|
69
69
|
fs.mkdirSync(destinationDir, { recursive: true });
|
|
70
70
|
}
|
|
71
|
-
//
|
|
72
|
-
const source = sourceMap.source.source();
|
|
73
|
-
const sourceBuffer = Buffer.isBuffer(source) ? source : Buffer.from(source, 'utf-8');
|
|
74
|
-
fs.writeFileSync(destinationPath, sourceBuffer);
|
|
75
|
-
console.log(`SourceMapUploadPlugin: Consolidated ${assetName} to ${destinationPath}`);
|
|
76
|
-
// Delete the original sourcemap file.
|
|
71
|
+
// Move the file; fallback to copy+delete on cross-filesystem (EXDEV)
|
|
77
72
|
try {
|
|
78
|
-
fs.
|
|
79
|
-
console.log(`SourceMapUploadPlugin: Deleted original sourcemap ${originalPath}`);
|
|
73
|
+
fs.renameSync(originalPath, destinationPath);
|
|
80
74
|
}
|
|
81
|
-
catch (
|
|
82
|
-
|
|
75
|
+
catch (renameError) {
|
|
76
|
+
if (renameError.code === 'EXDEV') {
|
|
77
|
+
fs.copyFileSync(originalPath, destinationPath);
|
|
78
|
+
try {
|
|
79
|
+
fs.unlinkSync(originalPath);
|
|
80
|
+
}
|
|
81
|
+
catch (deleteError) {
|
|
82
|
+
console.warn(`SourceMapUploadPlugin: Failed to delete original sourcemap ${originalPath}.`, deleteError);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
throw renameError;
|
|
87
|
+
}
|
|
83
88
|
}
|
|
89
|
+
console.log(`SourceMapUploadPlugin: Consolidated ${assetName} to ${destinationPath}`);
|
|
84
90
|
}
|
|
85
91
|
catch (error) {
|
|
86
92
|
const errorMessage = `SourceMapUploadPlugin: Failed to consolidate ${assetName}.`;
|