@lark-apaas/fullstack-rspack-preset 1.0.32-alpha.2 → 1.0.32-alpha.21

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.
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CssLegacyPlugin = void 0;
4
+ const lightningcss_1 = require("lightningcss");
5
+ /**
6
+ * CSS Legacy Plugin
7
+ *
8
+ * 在构建完成后生成一份兼容旧浏览器的 CSS 文件(.legacy.css)
9
+ * 并在 HTML 中注入检测脚本,根据浏览器版本加载对应的 CSS
10
+ */
11
+ class CssLegacyPlugin {
12
+ apply(compiler) {
13
+ compiler.hooks.thisCompilation.tap(CssLegacyPlugin.pluginName, (compilation) => {
14
+ compilation.hooks.processAssets.tapPromise({
15
+ name: CssLegacyPlugin.pluginName,
16
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 1,
17
+ }, async (assets) => {
18
+ const cssFiles = Object.keys(assets).filter((name) => name.endsWith('.css') && !name.endsWith('.legacy.css'));
19
+ for (const cssFile of cssFiles) {
20
+ const asset = assets[cssFile];
21
+ const source = asset.source();
22
+ let cssContent = typeof source === 'string' ? source : source.toString('utf-8');
23
+ try {
24
+ // 1. 先用正则移除/转换 LightningCSS 不支持的特性
25
+ cssContent = this.preprocessCss(cssContent);
26
+ // 2. 使用 LightningCSS 转换其他现代 CSS 特性
27
+ const result = (0, lightningcss_1.transform)({
28
+ filename: cssFile,
29
+ code: Buffer.from(cssContent),
30
+ minify: true,
31
+ targets: {
32
+ ios_saf: (12 << 16),
33
+ safari: (12 << 16),
34
+ chrome: (80 << 16),
35
+ },
36
+ include: lightningcss_1.Features.Nesting |
37
+ lightningcss_1.Features.MediaQueries |
38
+ lightningcss_1.Features.Colors |
39
+ lightningcss_1.Features.Selectors |
40
+ lightningcss_1.Features.ColorFunction |
41
+ lightningcss_1.Features.OklabColors |
42
+ lightningcss_1.Features.LabColors |
43
+ lightningcss_1.Features.P3Colors |
44
+ lightningcss_1.Features.HexAlphaColors |
45
+ lightningcss_1.Features.LightDark,
46
+ errorRecovery: true,
47
+ });
48
+ // 3. 后处理:移除可能残留的不支持特性
49
+ let legacyCss = Buffer.from(result.code).toString('utf-8');
50
+ legacyCss = this.postprocessCss(legacyCss);
51
+ const legacyFileName = cssFile.replace('.css', '.legacy.css');
52
+ compilation.emitAsset(legacyFileName, new compiler.webpack.sources.RawSource(Buffer.from(legacyCss)));
53
+ console.log(`[CssLegacyPlugin] Generated ${legacyFileName} (${Math.round(legacyCss.length / 1024)}KB)`);
54
+ }
55
+ catch (error) {
56
+ console.error(`[CssLegacyPlugin] Failed to transform ${cssFile}:`, error);
57
+ }
58
+ }
59
+ });
60
+ // 修改 HTML,注入 CSS 版本检测脚本
61
+ compilation.hooks.processAssets.tapPromise({
62
+ name: `${CssLegacyPlugin.pluginName}-html`,
63
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE + 1,
64
+ }, async (assets) => {
65
+ const htmlFiles = Object.keys(assets).filter((name) => name.endsWith('.html'));
66
+ for (const htmlFile of htmlFiles) {
67
+ const asset = assets[htmlFile];
68
+ const source = asset.source();
69
+ let htmlContent = typeof source === 'string' ? source : source.toString('utf-8');
70
+ const cssLinkRegex = /<link[^>]+href="([^"]+\.css)"[^>]*>/g;
71
+ const cssLinks = [...htmlContent.matchAll(cssLinkRegex)];
72
+ if (cssLinks.length === 0)
73
+ continue;
74
+ const detectionScript = this.generateDetectionScript(cssLinks);
75
+ for (const match of cssLinks) {
76
+ htmlContent = htmlContent.replace(match[0], '');
77
+ }
78
+ htmlContent = htmlContent.replace(/<head([^>]*)>/i, `<head$1>\n${detectionScript}`);
79
+ compilation.updateAsset(htmlFile, new compiler.webpack.sources.RawSource(htmlContent));
80
+ console.log(`[CssLegacyPlugin] Updated ${htmlFile} with CSS detection script`);
81
+ }
82
+ });
83
+ });
84
+ }
85
+ /**
86
+ * 预处理 CSS:移除/转换 LightningCSS 不支持降级的特性
87
+ */
88
+ preprocessCss(css) {
89
+ // 1. 移除所有 @property 规则(iOS 16.4+ 才支持)
90
+ css = css.replace(/@property\s+--[\w-]+\s*\{[^}]*\}/g, '');
91
+ // 2. 展开 @layer 规则(iOS 15.4+ 才支持)
92
+ // 简单处理:移除 @layer xxx { 和对应的 },保留内部内容
93
+ css = this.unwrapAtLayer(css);
94
+ // 3. 移除 @supports 中的 color-mix 检测块(这些是渐进增强,旧浏览器不需要)
95
+ css = this.removeColorMixSupports(css);
96
+ return css;
97
+ }
98
+ /**
99
+ * 后处理 CSS:清理可能残留的不支持特性
100
+ */
101
+ postprocessCss(css) {
102
+ // 移除可能残留的 @layer
103
+ css = this.unwrapAtLayer(css);
104
+ // 移除残留的 @property
105
+ css = css.replace(/@property\s+--[\w-]+\s*\{[^}]*\}/g, '');
106
+ // 移除残留的 color-mix @supports 块
107
+ css = this.removeColorMixSupports(css);
108
+ // 移除空的 @supports 块
109
+ css = css.replace(/@supports\s*\([^)]*\)\s*\{\s*\}/g, '');
110
+ return css;
111
+ }
112
+ /**
113
+ * 展开 @layer 规则,保留内部样式
114
+ */
115
+ unwrapAtLayer(css) {
116
+ // 匹配 @layer name { ... } 结构
117
+ // 需要处理嵌套大括号
118
+ let result = css;
119
+ let changed = true;
120
+ while (changed) {
121
+ changed = false;
122
+ // 简单的 @layer 匹配(非嵌套情况)
123
+ const layerRegex = /@layer\s+[\w-]+\s*\{/g;
124
+ let match;
125
+ while ((match = layerRegex.exec(result)) !== null) {
126
+ const startIndex = match.index;
127
+ const contentStart = startIndex + match[0].length;
128
+ // 找到匹配的闭合大括号
129
+ let depth = 1;
130
+ let i = contentStart;
131
+ while (i < result.length && depth > 0) {
132
+ if (result[i] === '{')
133
+ depth++;
134
+ else if (result[i] === '}')
135
+ depth--;
136
+ i++;
137
+ }
138
+ if (depth === 0) {
139
+ // 提取 @layer 内部的内容
140
+ const innerContent = result.slice(contentStart, i - 1);
141
+ // 用内部内容替换整个 @layer 块
142
+ result = result.slice(0, startIndex) + innerContent + result.slice(i);
143
+ changed = true;
144
+ break; // 重新开始匹配
145
+ }
146
+ }
147
+ }
148
+ return result;
149
+ }
150
+ /**
151
+ * 移除 @supports (color: color-mix(...)) 块
152
+ * 这些是渐进增强代码,旧浏览器可以使用 fallback
153
+ */
154
+ removeColorMixSupports(css) {
155
+ let result = css;
156
+ let changed = true;
157
+ while (changed) {
158
+ changed = false;
159
+ // 匹配 @supports (color:color-mix(...)) { ... }
160
+ const supportsRegex = /@supports\s*\(\s*color\s*:\s*color-mix\s*\([^)]+\)\s*\)\s*\{/g;
161
+ let match;
162
+ while ((match = supportsRegex.exec(result)) !== null) {
163
+ const startIndex = match.index;
164
+ const contentStart = startIndex + match[0].length;
165
+ // 找到匹配的闭合大括号
166
+ let depth = 1;
167
+ let i = contentStart;
168
+ while (i < result.length && depth > 0) {
169
+ if (result[i] === '{')
170
+ depth++;
171
+ else if (result[i] === '}')
172
+ depth--;
173
+ i++;
174
+ }
175
+ if (depth === 0) {
176
+ // 移除整个 @supports 块
177
+ result = result.slice(0, startIndex) + result.slice(i);
178
+ changed = true;
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ return result;
184
+ }
185
+ generateDetectionScript(cssLinks) {
186
+ const cssUrls = cssLinks.map((match) => match[1]);
187
+ // 检测脚本 + document.write 直接写入正确的 CSS(在加载前完成检测)
188
+ return `<script>
189
+ (function() {
190
+ var isModernBrowser = (function() {
191
+ try {
192
+ if (typeof CSS === 'undefined' || typeof CSS.supports !== 'function') return false;
193
+ if (!CSS.supports('selector(:has(*))')) return false;
194
+ if (!CSS.supports('color', 'color-mix(in srgb, red, blue)')) return false;
195
+ if (!CSS.supports('color', 'oklch(50% 0.1 0)')) return false;
196
+ return true;
197
+ } catch (e) {
198
+ return false;
199
+ }
200
+ })();
201
+
202
+ var cssUrls = ${JSON.stringify(cssUrls)};
203
+ var suffix = isModernBrowser ? '' : '.legacy';
204
+
205
+ cssUrls.forEach(function(url) {
206
+ var finalUrl = url.replace(/\\.css$/, suffix + '.css');
207
+ document.write('<link rel="stylesheet" href="' + finalUrl + '">');
208
+ });
209
+ })();
210
+ </script>
211
+ <noscript>
212
+ ${cssUrls.map((url) => ` <link rel="stylesheet" href="${url}">`).join('\n')}
213
+ </noscript>`;
214
+ }
215
+ }
216
+ exports.CssLegacyPlugin = CssLegacyPlugin;
217
+ CssLegacyPlugin.pluginName = 'CssLegacyPlugin';
218
+ exports.default = CssLegacyPlugin;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Polyfill Plugin
3
+ *
4
+ * 使用 core-js 为旧版浏览器生成独立的 polyfill 文件
5
+ * 通过检测脚本按需加载,现代浏览器不会加载 polyfill
6
+ */
7
+ import type { Compiler } from '@rspack/core';
8
+ export declare class PolyfillPlugin {
9
+ apply(compiler: Compiler): void;
10
+ private generateDetectionScript;
11
+ }
12
+ export default PolyfillPlugin;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * Polyfill Plugin
4
+ *
5
+ * 使用 core-js 为旧版浏览器生成独立的 polyfill 文件
6
+ * 通过检测脚本按需加载,现代浏览器不会加载 polyfill
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PolyfillPlugin = void 0;
13
+ const path_1 = __importDefault(require("path"));
14
+ const PLUGIN_NAME = 'PolyfillPlugin';
15
+ const POLYFILL_ENTRY = path_1.default.resolve(__dirname, '../polyfills/index');
16
+ class PolyfillPlugin {
17
+ apply(compiler) {
18
+ // 添加 polyfills 作为单独的 entry
19
+ compiler.hooks.entryOption.tap(PLUGIN_NAME, (_context, entry) => {
20
+ if (typeof entry === 'function') {
21
+ return;
22
+ }
23
+ // 添加 polyfills entry,单独打包
24
+ entry['polyfills'] = {
25
+ import: [POLYFILL_ENTRY],
26
+ filename: 'polyfills.js',
27
+ // 不需要 runtime,保持文件独立
28
+ runtime: false,
29
+ };
30
+ });
31
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
32
+ // 在 HTML 中注入检测脚本
33
+ compilation.hooks.processAssets.tapPromise({
34
+ name: `${PLUGIN_NAME}-html`,
35
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE,
36
+ }, async (assets) => {
37
+ const htmlFiles = Object.keys(assets).filter((name) => name.endsWith('.html'));
38
+ const publicPath = compilation.outputOptions.publicPath || '/';
39
+ for (const htmlFile of htmlFiles) {
40
+ const asset = assets[htmlFile];
41
+ const source = asset.source();
42
+ let htmlContent = typeof source === 'string' ? source : source.toString('utf-8');
43
+ const polyfillUrl = `${publicPath}polyfills.js`.replace(/\/+/g, '/').replace(':/', '://');
44
+ const detectionScript = this.generateDetectionScript(polyfillUrl);
45
+ // 注入到 <head> 最前面
46
+ htmlContent = htmlContent.replace(/<head([^>]*)>/i, `<head$1>\n${detectionScript}`);
47
+ compilation.updateAsset(htmlFile, new compiler.webpack.sources.RawSource(htmlContent));
48
+ console.log(`[PolyfillPlugin] Updated ${htmlFile} with polyfill detection`);
49
+ }
50
+ });
51
+ });
52
+ }
53
+ generateDetectionScript(polyfillUrl) {
54
+ return `<script>
55
+ (function(){
56
+ var needsPolyfill=(function(){
57
+ try{
58
+ if(typeof crypto==="undefined"||typeof crypto.randomUUID!=="function")return true;
59
+ if(typeof Object.hasOwn!=="function")return true;
60
+ if(typeof[].at!=="function")return true;
61
+ if(typeof[].findLast!=="function")return true;
62
+ return false;
63
+ }catch(e){return true}
64
+ })();
65
+ if(needsPolyfill){
66
+ document.write('<script src="${polyfillUrl}"><\\/script>');
67
+ }
68
+ })();
69
+ </script>`;
70
+ }
71
+ }
72
+ exports.PolyfillPlugin = PolyfillPlugin;
73
+ exports.default = PolyfillPlugin;
@@ -1,12 +1,14 @@
1
1
  interface RouteParserPluginOptions {
2
2
  appPath?: string;
3
3
  outputPath?: string;
4
+ basePath?: string;
4
5
  }
5
6
  declare class RouteParserPlugin {
6
7
  private options;
7
8
  private lastAppPathHash;
8
9
  private cachedRoutes;
9
10
  constructor(options?: RouteParserPluginOptions);
11
+ private normalizeBasePath;
10
12
  private log;
11
13
  apply(compiler: any): void;
12
14
  private shouldRegenerateRoutes;
@@ -17,4 +19,13 @@ declare class RouteParserPlugin {
17
19
  private buildFullPath;
18
20
  private evaluateTemplateLiteral;
19
21
  }
22
+ export declare function normalizeBasePath(basePath: string): string;
23
+ /**
24
+ * Parse routes from the app file at build time.
25
+ * @param appPath - Path to app.tsx (relative to cwd or absolute)
26
+ * @param basePath - Base path prefix for routes (e.g., '/my_plugin')
27
+ */
28
+ export declare function parseRoutesFromFile(appPath: string, basePath: string): Array<{
29
+ path: string;
30
+ }>;
20
31
  export default RouteParserPlugin;
@@ -36,6 +36,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.normalizeBasePath = normalizeBasePath;
40
+ exports.parseRoutesFromFile = parseRoutesFromFile;
39
41
  const fs = __importStar(require("fs"));
40
42
  const path = __importStar(require("path"));
41
43
  const crypto = __importStar(require("crypto"));
@@ -46,11 +48,27 @@ class RouteParserPlugin {
46
48
  constructor(options = {}) {
47
49
  this.lastAppPathHash = null;
48
50
  this.cachedRoutes = null;
51
+ // 从环境变量读取 basePath,并规范化(确保以 / 开头,不以 / 结尾)
52
+ const envBasePath = process.env.CLIENT_BASE_PATH || '';
53
+ const normalizedBasePath = this.normalizeBasePath(options.basePath ?? envBasePath);
49
54
  this.options = {
50
55
  appPath: options.appPath || './client/src/app.tsx',
51
56
  outputPath: options.outputPath || './dist/client/routes.json',
57
+ basePath: normalizedBasePath,
52
58
  };
53
59
  }
60
+ normalizeBasePath(basePath) {
61
+ if (!basePath || basePath === '/') {
62
+ return '';
63
+ }
64
+ // 确保以 / 开头
65
+ let normalized = basePath.startsWith('/') ? basePath : `/${basePath}`;
66
+ // 确保不以 / 结尾
67
+ if (normalized.endsWith('/')) {
68
+ normalized = normalized.slice(0, -1);
69
+ }
70
+ return normalized;
71
+ }
54
72
  log(level, message, ...args) {
55
73
  const prefix = '[route-parser]';
56
74
  const logMessage = `${prefix} ${message}`;
@@ -88,7 +106,9 @@ class RouteParserPlugin {
88
106
  }
89
107
  catch (error) {
90
108
  this.log('warn', '⚠️ 路由解析失败,使用默认路由:', error.message);
91
- const defaultRoutes = [{ path: '/' }];
109
+ const { basePath } = this.options;
110
+ const defaultPath = basePath ? `${basePath}/` : '/';
111
+ const defaultRoutes = [{ path: defaultPath }];
92
112
  const routesJson = JSON.stringify(defaultRoutes, null, 2);
93
113
  compilation.assets['routes.json'] = {
94
114
  source: () => routesJson,
@@ -179,12 +199,19 @@ class RouteParserPlugin {
179
199
  }
180
200
  }
181
201
  });
182
- const routes = Array.from(routeSet).map(routePath => ({ path: routePath }));
183
- return routes.length > 0 ? routes : [{ path: '/' }];
202
+ const { basePath } = this.options;
203
+ const routes = Array.from(routeSet).map(routePath => ({
204
+ path: basePath ? `${basePath}${routePath}` : routePath,
205
+ }));
206
+ // 默认路由也需要加上 basePath
207
+ const defaultPath = basePath ? `${basePath}/` : '/';
208
+ return routes.length > 0 ? routes : [{ path: defaultPath }];
184
209
  }
185
210
  catch (error) {
186
211
  this.log('warn', '⚠️ 路由解析失败,使用默认路由:', error.message);
187
- return [{ path: '/' }];
212
+ const { basePath } = this.options;
213
+ const defaultPath = basePath ? `${basePath}/` : '/';
214
+ return [{ path: defaultPath }];
188
215
  }
189
216
  }
190
217
  isRouteComponent(openingElement) {
@@ -266,4 +293,161 @@ class RouteParserPlugin {
266
293
  return quasis.map((q) => q.value.raw).join('');
267
294
  }
268
295
  }
296
+ // Standalone module-level helpers (mirroring private class methods) for external callers
297
+ function normalizeBasePath(basePath) {
298
+ if (!basePath || basePath === '/') {
299
+ return '';
300
+ }
301
+ let normalized = basePath.startsWith('/') ? basePath : `/${basePath}`;
302
+ if (normalized.endsWith('/')) {
303
+ normalized = normalized.slice(0, -1);
304
+ }
305
+ return normalized;
306
+ }
307
+ function _isRouteComponent(openingElement) {
308
+ return (t.isJSXIdentifier(openingElement.name) &&
309
+ openingElement.name.name === 'Route');
310
+ }
311
+ function _evaluateTemplateLiteral(templateLiteral) {
312
+ const quasis = templateLiteral.quasis;
313
+ const expressions = templateLiteral.expressions;
314
+ if (quasis.length === 1 && expressions.length === 0) {
315
+ return quasis[0].value.raw;
316
+ }
317
+ return quasis.map((q) => q.value.raw).join('');
318
+ }
319
+ function _extractRouteInfo(openingElement) {
320
+ const routeInfo = {};
321
+ openingElement.attributes.forEach((attr) => {
322
+ if (t.isJSXAttribute(attr)) {
323
+ const name = attr.name.name;
324
+ let value;
325
+ if (attr.value) {
326
+ if (t.isStringLiteral(attr.value)) {
327
+ value = attr.value.value;
328
+ }
329
+ else if (t.isJSXExpressionContainer(attr.value)) {
330
+ const expression = attr.value.expression;
331
+ if (t.isStringLiteral(expression)) {
332
+ value = expression.value;
333
+ }
334
+ else if (t.isTemplateLiteral(expression)) {
335
+ value = _evaluateTemplateLiteral(expression);
336
+ }
337
+ else {
338
+ value = true;
339
+ }
340
+ }
341
+ }
342
+ else {
343
+ value = true;
344
+ }
345
+ routeInfo[name] = value;
346
+ }
347
+ });
348
+ return routeInfo;
349
+ }
350
+ function _buildFullPath(routeStack, currentRoute) {
351
+ let fullPath = '';
352
+ for (let i = 0; i < routeStack.length; i++) {
353
+ if (routeStack[i].path) {
354
+ let parentPath = routeStack[i].path;
355
+ if (!parentPath.startsWith('/'))
356
+ parentPath = `/${parentPath}`;
357
+ if (parentPath.endsWith('/') && parentPath !== '/') {
358
+ parentPath = parentPath.slice(0, -1);
359
+ }
360
+ fullPath += parentPath === '/' ? '' : parentPath;
361
+ }
362
+ }
363
+ if (currentRoute.index) {
364
+ return fullPath || '/';
365
+ }
366
+ else if (currentRoute.path) {
367
+ const routePath = currentRoute.path;
368
+ if (routePath === '*') {
369
+ return null;
370
+ }
371
+ if (!routePath.startsWith('/')) {
372
+ fullPath = `${fullPath}/${routePath}`;
373
+ }
374
+ else {
375
+ fullPath = routePath;
376
+ }
377
+ if (fullPath === '')
378
+ fullPath = '/';
379
+ if (!fullPath.startsWith('/'))
380
+ fullPath = `/${fullPath}`;
381
+ return fullPath;
382
+ }
383
+ return null;
384
+ }
385
+ /**
386
+ * Parse routes from the app file at build time.
387
+ * @param appPath - Path to app.tsx (relative to cwd or absolute)
388
+ * @param basePath - Base path prefix for routes (e.g., '/my_plugin')
389
+ */
390
+ function parseRoutesFromFile(appPath, basePath) {
391
+ const prefix = '[route-parser]';
392
+ const defaultPath = basePath ? `${basePath}/` : '/';
393
+ try {
394
+ const appFilePath = path.resolve(process.cwd(), appPath);
395
+ if (!fs.existsSync(appFilePath)) {
396
+ throw new Error(`App.tsx file does not exist: ${appFilePath}`);
397
+ }
398
+ const sourceCode = fs.readFileSync(appFilePath, 'utf-8');
399
+ const ast = (0, parser_1.parse)(sourceCode, {
400
+ sourceType: 'module',
401
+ plugins: [
402
+ 'jsx',
403
+ 'typescript',
404
+ 'decorators-legacy',
405
+ 'classProperties',
406
+ 'objectRestSpread',
407
+ 'functionBind',
408
+ 'exportDefaultFrom',
409
+ 'exportNamespaceFrom',
410
+ 'dynamicImport',
411
+ 'nullishCoalescingOperator',
412
+ 'optionalChaining'
413
+ ]
414
+ });
415
+ const routeSet = new Set();
416
+ const routeStack = [];
417
+ (0, traverse_1.default)(ast, {
418
+ JSXElement: {
419
+ enter(nodePath) {
420
+ const { openingElement } = nodePath.node;
421
+ if (_isRouteComponent(openingElement)) {
422
+ const routeInfo = _extractRouteInfo(openingElement);
423
+ routeStack.push(routeInfo);
424
+ }
425
+ },
426
+ exit(nodePath) {
427
+ const { openingElement } = nodePath.node;
428
+ if (_isRouteComponent(openingElement)) {
429
+ const currentRoute = routeStack.pop();
430
+ if (currentRoute && currentRoute.path === '*') {
431
+ return;
432
+ }
433
+ if (currentRoute && (currentRoute.path || currentRoute.index)) {
434
+ const fullPath = _buildFullPath(routeStack, currentRoute);
435
+ if (fullPath) {
436
+ routeSet.add(fullPath);
437
+ }
438
+ }
439
+ }
440
+ }
441
+ }
442
+ });
443
+ const routes = Array.from(routeSet).map(routePath => ({
444
+ path: basePath ? `${basePath}${routePath}` : routePath,
445
+ }));
446
+ return routes.length > 0 ? routes : [{ path: defaultPath }];
447
+ }
448
+ catch (error) {
449
+ console.warn(`${prefix} Route parsing failed, using default routes:`, error.message);
450
+ return [{ path: defaultPath }];
451
+ }
452
+ }
269
453
  exports.default = RouteParserPlugin;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Runtime Injection Plugin
3
+ *
4
+ * 在编译时自动将 @lark-apaas/client-toolkit/runtime 注入到所有入口之前
5
+ * 支持 extends 配置方式
6
+ *
7
+ * 原理:在 entryOption hook 中原地修改 entry 配置,将 runtime 注入到每个入口的最前面
8
+ */
9
+ import type { Compiler } from '@rspack/core';
10
+ export declare class RuntimeInjectionPlugin {
11
+ apply(compiler: Compiler): void;
12
+ }
13
+ export default RuntimeInjectionPlugin;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ /**
3
+ * Runtime Injection Plugin
4
+ *
5
+ * 在编译时自动将 @lark-apaas/client-toolkit/runtime 注入到所有入口之前
6
+ * 支持 extends 配置方式
7
+ *
8
+ * 原理:在 entryOption hook 中原地修改 entry 配置,将 runtime 注入到每个入口的最前面
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.RuntimeInjectionPlugin = void 0;
12
+ const RUNTIME_MODULE = '@lark-apaas/client-toolkit/runtime';
13
+ const PLUGIN_NAME = 'RuntimeInjectionPlugin';
14
+ class RuntimeInjectionPlugin {
15
+ apply(compiler) {
16
+ // entryOption hook 在配置合并完成后、处理 entry 之前触发
17
+ compiler.hooks.entryOption.tap(PLUGIN_NAME, (_context, entry) => {
18
+ // 跳过动态 entry(函数形式)
19
+ if (typeof entry === 'function') {
20
+ return;
21
+ }
22
+ // 原地修改 entry 对象,将 runtime 注入到每个入口的最前面
23
+ for (const name of Object.keys(entry)) {
24
+ const descriptor = entry[name];
25
+ const imports = descriptor.import || [];
26
+ // 检查是否已经包含 runtime
27
+ if (imports.includes(RUNTIME_MODULE)) {
28
+ continue;
29
+ }
30
+ // 原地修改 import 数组,将 runtime 插入到最前面
31
+ descriptor.import = [RUNTIME_MODULE, ...imports];
32
+ }
33
+ // 不返回 true,让 rspack 继续正常处理 entry
34
+ });
35
+ }
36
+ }
37
+ exports.RuntimeInjectionPlugin = RuntimeInjectionPlugin;
38
+ exports.default = RuntimeInjectionPlugin;
@@ -106,10 +106,14 @@ function getSlardarScriptContent(option = {}) {
106
106
  slardarScript.onload = function() {
107
107
  // 脚本加载完成后执行初始化
108
108
  if (window.${globalName}) {
109
+ window.${globalName}('context.merge', {
110
+ tenantId: window.tenantId ?? '',
111
+ appId: window.appId ?? '',
112
+ });
109
113
  window.${globalName}('init', {
110
114
  bid: '${bid}',
111
- // 四种类型:dev/boe/pre/online
112
- env: 'online',
115
+ env: window.ENVIRONMENT || 'online',
116
+ userId: window.userId ?? '',
113
117
  });
114
118
  window.${globalName}('start');
115
119
  }
@@ -63,9 +63,14 @@ class SourceMapUploadPlugin {
63
63
  const outputBaseDir = path.basename(outputPath); // e.g., 'client'
64
64
  const sourcemapTargetDir = path.join(this.options.outputDir, outputBaseDir); // e.g., 'dist/sourcemaps/client'
65
65
  for (const sourceMap of sourceMaps) {
66
- const assetName = sourceMap.name;
66
+ let assetName = sourceMap.name;
67
+ // Align with Vite's output structure by adding 'assets' and removing 'chunks'
68
+ if (assetName.startsWith('chunks/')) {
69
+ assetName = assetName.substring('chunks/'.length);
70
+ }
71
+ assetName = path.join('assets', assetName);
67
72
  const destinationPath = path.join(sourcemapTargetDir, assetName);
68
- const originalPath = path.join(outputPath, assetName);
73
+ const originalPath = path.join(outputPath, sourceMap.name);
69
74
  try {
70
75
  // Ensure the destination directory for the specific file exists.
71
76
  const destinationDir = path.dirname(destinationPath);