@4399ywkf/core 5.0.27 → 5.0.28

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.
@@ -1,9 +1,2125 @@
1
1
  // src/config/loader.ts
2
- import { existsSync } from "fs";
3
- import { extname, resolve } from "path";
2
+ import { existsSync as existsSync7 } from "fs";
3
+ import { extname, resolve as resolve2 } from "path";
4
4
  import { pathToFileURL } from "url";
5
5
  import deepmerge from "deepmerge";
6
6
 
7
+ // src/plugin/define.ts
8
+ function createPlugin(factory) {
9
+ return factory;
10
+ }
11
+
12
+ // src/plugin/builtin/analytics.ts
13
+ var analyticsPlugin = createPlugin((options = {}) => ({
14
+ name: "@4399ywkf/plugin-analytics",
15
+ version: "1.0.0",
16
+ description: "\u6784\u5EFA\u5206\u6790\u63D2\u4EF6",
17
+ setup(context) {
18
+ const {
19
+ buildAnalysis = true,
20
+ timing = true,
21
+ bundleSize = true,
22
+ sizeWarningThreshold = 500
23
+ } = options;
24
+ const { logger, isProd } = context;
25
+ let buildStartTime;
26
+ const hooks = {
27
+ beforeBuild() {
28
+ if (timing) {
29
+ buildStartTime = Date.now();
30
+ logger.info("\u5F00\u59CB\u6784\u5EFA...");
31
+ }
32
+ },
33
+ afterBuild(_context, stats) {
34
+ if (timing) {
35
+ const duration = Date.now() - buildStartTime;
36
+ logger.info(`\u6784\u5EFA\u5B8C\u6210\uFF0C\u8017\u65F6: ${(duration / 1e3).toFixed(2)}s`);
37
+ }
38
+ if (!stats.success && stats.errors) {
39
+ logger.error(`\u6784\u5EFA\u5931\u8D25\uFF0C\u9519\u8BEF\u6570: ${stats.errors.length}`);
40
+ }
41
+ },
42
+ modifyRspackConfig(config) {
43
+ if (buildAnalysis && isProd) {
44
+ logger.info("\u6784\u5EFA\u5206\u6790\u5DF2\u542F\u7528");
45
+ }
46
+ return config;
47
+ }
48
+ };
49
+ return hooks;
50
+ }
51
+ }));
52
+
53
+ // src/plugin/builtin/biome.ts
54
+ import { existsSync, writeFileSync } from "fs";
55
+ import { join } from "path";
56
+ var biomePlugin = createPlugin((options = {}) => ({
57
+ name: "@4399ywkf/plugin-biome",
58
+ version: "1.0.0",
59
+ description: "Biome \u4EE3\u7801\u89C4\u8303\u96C6\u6210",
60
+ setup(context) {
61
+ const {
62
+ scaffold = true,
63
+ ignore = [],
64
+ rules: customRules,
65
+ formatter: customFormatter,
66
+ javascript: customJavascript
67
+ } = options;
68
+ const { cwd, logger } = context;
69
+ if (scaffold) {
70
+ const biomeConfigPath = join(cwd, "biome.json");
71
+ if (!existsSync(biomeConfigPath)) {
72
+ const config = buildBiomeConfig({ ignore, customRules, customFormatter, customJavascript });
73
+ writeFileSync(biomeConfigPath, `${JSON.stringify(config, null, 2)}
74
+ `, "utf-8");
75
+ logger.info("\u5DF2\u751F\u6210 biome.json\uFF08\u7EE7\u627F @4399ywkf/core/biome\uFF09");
76
+ }
77
+ }
78
+ const hooks = {
79
+ modifyRspackConfig(rspackConfig) {
80
+ logger.info("Biome \u4EE3\u7801\u89C4\u8303\u5DF2\u542F\u7528");
81
+ return rspackConfig;
82
+ }
83
+ };
84
+ return hooks;
85
+ }
86
+ }));
87
+ function buildBiomeConfig(params) {
88
+ const { ignore, customRules, customFormatter, customJavascript } = params;
89
+ const config = {
90
+ $schema: "https://biomejs.dev/schemas/2.3.2/schema.json",
91
+ extends: ["@4399ywkf/core/biome"]
92
+ };
93
+ if (customFormatter) {
94
+ config.formatter = customFormatter;
95
+ }
96
+ if (customJavascript) {
97
+ config.javascript = customJavascript;
98
+ }
99
+ if (customRules && Object.keys(customRules).length > 0) {
100
+ config.linter = { rules: customRules };
101
+ }
102
+ if (ignore.length > 0) {
103
+ config.files = {
104
+ includes: ["**", ...ignore.map((p) => `!${p}`)]
105
+ };
106
+ }
107
+ return config;
108
+ }
109
+
110
+ // src/plugin/builtin/browser-check.ts
111
+ import { existsSync as existsSync2, readFileSync } from "fs";
112
+ import { join as join2 } from "path";
113
+ var DEFAULT_FEATURES = ["Object.hasOwn"];
114
+ var DEFAULT_MIN_VERSIONS = {
115
+ chrome: 93,
116
+ edge: 93,
117
+ firefox: 92,
118
+ safari: 15.4
119
+ };
120
+ var browserCheckPlugin = createPlugin((options = {}) => ({
121
+ name: "@4399ywkf/plugin-browser-check",
122
+ version: "1.0.0",
123
+ description: "\u6D4F\u89C8\u5668\u6700\u4F4E\u7248\u672C\u68C0\u67E5\u63D2\u4EF6",
124
+ setup(context) {
125
+ const {
126
+ features = DEFAULT_FEATURES,
127
+ minVersions: explicitVersions,
128
+ fromBrowserslist = false,
129
+ title = "\u6D4F\u89C8\u5668\u7248\u672C\u8FC7\u4F4E",
130
+ message = "\u60A8\u5F53\u524D\u7684\u6D4F\u89C8\u5668\u7248\u672C\u4E0D\u652F\u6301\u672C\u5E94\u7528\u6240\u9700\u7684\u529F\u80FD\u3002\u8BF7\u5347\u7EA7\u5230\u4EE5\u4E0B\u63A8\u8350\u6D4F\u89C8\u5668\u7684\u6700\u65B0\u7248\u672C\uFF1A",
131
+ showDownloadLinks = true,
132
+ renderHtml,
133
+ renderScript
134
+ } = options;
135
+ const { cwd, config, logger } = context;
136
+ const rootId = config.html.mountRoot || config.appName || "root";
137
+ let baseVersions = { ...DEFAULT_MIN_VERSIONS };
138
+ if (fromBrowserslist !== false) {
139
+ const parsed = parseBrowserslistConfig(cwd, fromBrowserslist, logger);
140
+ if (parsed) {
141
+ baseVersions = { ...baseVersions, ...parsed };
142
+ }
143
+ }
144
+ const minVersions = explicitVersions ? { ...baseVersions, ...explicitVersions } : baseVersions;
145
+ logger.info(
146
+ `\u6D4F\u89C8\u5668\u517C\u5BB9\u6027\u68C0\u67E5\u5DF2\u542F\u7528 (\u7279\u6027\u68C0\u6D4B: ${features.join(", ")})`
147
+ );
148
+ logger.info(
149
+ `\u6700\u4F4E\u7248\u672C\u8981\u6C42: Chrome ${minVersions.chrome ?? "\u2014"}, Edge ${minVersions.edge ?? "\u2014"}, Firefox ${minVersions.firefox ?? "\u2014"}, Safari ${minVersions.safari ?? "\u2014"}${fromBrowserslist !== false ? " (\u4ECE browserslist \u8BFB\u53D6)" : ""}`
150
+ );
151
+ const hooks = {
152
+ modifyEntryCode(code) {
153
+ const featureCheckLines = buildFeatureChecks(features);
154
+ const versionCheckLines = buildVersionChecks(minVersions);
155
+ const warningHtml = renderHtml ? renderHtml({ title, message, showDownloadLinks, minVersions }) : buildWarningHtml({ title, message, showDownloadLinks, minVersions });
156
+ const checkBlock = [
157
+ ``,
158
+ `// ======= \u6D4F\u89C8\u5668\u517C\u5BB9\u6027\u68C0\u67E5 (@4399ywkf/plugin-browser-check) =======`,
159
+ `const __ywkfBrowserOk = (() => {`,
160
+ ` try {`,
161
+ ...featureCheckLines.map((l) => ` ${l}`),
162
+ ...versionCheckLines.map((l) => ` ${l}`),
163
+ ` return true;`,
164
+ ` } catch {`,
165
+ ` return false;`,
166
+ ` }`,
167
+ `})();`,
168
+ ``,
169
+ `if (!__ywkfBrowserOk) {`,
170
+ ` const __root = document.getElementById(${JSON.stringify(rootId)}) || document.body;`,
171
+ ` __root.innerHTML = ${JSON.stringify(warningHtml)};`,
172
+ renderScript ? ` ${renderScript}` : ``,
173
+ `}`,
174
+ `// ======= End \u6D4F\u89C8\u5668\u517C\u5BB9\u6027\u68C0\u67E5 =======`,
175
+ ``
176
+ ].join("\n");
177
+ const lines = code.split("\n");
178
+ let lastImportIndex = -1;
179
+ for (let i = 0; i < lines.length; i++) {
180
+ if (lines[i].trimStart().startsWith("import ")) {
181
+ lastImportIndex = i;
182
+ }
183
+ }
184
+ const insertIndex = lastImportIndex >= 0 ? lastImportIndex + 1 : 0;
185
+ lines.splice(insertIndex, 0, checkBlock);
186
+ let result = lines.join("\n");
187
+ result = result.replace(
188
+ /(\s*)(await\s+)?runApp\(\);?/g,
189
+ (_, indent, awaitKw) => `${indent}if (__ywkfBrowserOk) { ${awaitKw || ""}runApp(); }`
190
+ );
191
+ return result;
192
+ }
193
+ };
194
+ return hooks;
195
+ }
196
+ }));
197
+ function buildFeatureChecks(features) {
198
+ if (features.length === 0) return [];
199
+ const lines = ["// \u7279\u6027\u68C0\u6D4B"];
200
+ for (const feature of features) {
201
+ const parts = feature.split(".");
202
+ if (parts.length === 1) {
203
+ lines.push(`if (typeof ${feature} === "undefined") return false;`);
204
+ } else {
205
+ lines.push(`if (typeof ${feature} !== "function") return false;`);
206
+ }
207
+ }
208
+ return lines;
209
+ }
210
+ function buildVersionChecks(minVersions) {
211
+ const entries = Object.entries(minVersions).filter(
212
+ ([, v]) => v != null
213
+ );
214
+ if (entries.length === 0) return [];
215
+ const lines = ["// UA \u7248\u672C\u68C0\u6D4B", "const ua = navigator.userAgent;"];
216
+ for (const [browser, minVer] of entries) {
217
+ switch (browser) {
218
+ case "chrome":
219
+ lines.push(
220
+ `const chromeMatch = /Chrome\\/(\\d+)/.exec(ua);`,
221
+ `if (chromeMatch && !/Edg\\//.test(ua) && Number(chromeMatch[1]) < ${minVer}) return false;`
222
+ );
223
+ break;
224
+ case "edge":
225
+ lines.push(
226
+ `const edgeMatch = /Edg\\/(\\d+)/.exec(ua);`,
227
+ `if (edgeMatch && Number(edgeMatch[1]) < ${minVer}) return false;`
228
+ );
229
+ break;
230
+ case "firefox":
231
+ lines.push(
232
+ `const firefoxMatch = /Firefox\\/(\\d+)/.exec(ua);`,
233
+ `if (firefoxMatch && Number(firefoxMatch[1]) < ${minVer}) return false;`
234
+ );
235
+ break;
236
+ case "safari":
237
+ lines.push(
238
+ `const safariMatch = /Version\\/(\\d+\\.?\\d*)/.exec(ua);`,
239
+ `if (safariMatch && /Safari/.test(ua) && !/Chrome/.test(ua) && Number(safariMatch[1]) < ${minVer}) return false;`
240
+ );
241
+ break;
242
+ }
243
+ }
244
+ return lines;
245
+ }
246
+ function buildWarningHtml(opts) {
247
+ const { title, message, showDownloadLinks, minVersions } = opts;
248
+ const versionHints = [
249
+ minVersions.chrome != null && `Chrome ${minVersions.chrome}+`,
250
+ minVersions.edge != null && `Edge ${minVersions.edge}+`,
251
+ minVersions.firefox != null && `Firefox ${minVersions.firefox}+`,
252
+ minVersions.safari != null && `Safari ${minVersions.safari}+`
253
+ ].filter(Boolean).join("\u3001");
254
+ const downloadLinksHtml = showDownloadLinks ? `<div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:32px">${browserLink("https://www.google.cn/chrome/", "Chrome \u6D4F\u89C8\u5668", "#4285f4", "#356ac3", "66,133,244")}${browserLink("https://www.microsoft.com/edge", "Edge \u6D4F\u89C8\u5668", "#0078d4", "#005a9e", "0,120,212")}${browserLink("https://www.firefox.com.cn/", "Firefox \u6D4F\u89C8\u5668", "#ff7139", "#e05a2b", "255,113,57")}</div>` : "";
255
+ const versionHintHtml = versionHints ? `<p style="margin:28px 0 0;font-size:13px;color:#aaa;letter-spacing:0.3px">\u63A8\u8350\u7248\u672C\uFF1A${versionHints}</p>` : "";
256
+ return [
257
+ `<div style="position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#f5f7fa 0%,#c3cfe2 100%);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Microsoft YaHei',sans-serif;padding:24px;box-sizing:border-box">`,
258
+ `<div style="max-width:520px;width:100%;padding:48px 40px;background:#fff;border-radius:20px;box-shadow:0 8px 40px rgba(0,0,0,0.12);text-align:center">`,
259
+ // 警告图标
260
+ `<div style="width:80px;height:80px;margin:0 auto 28px;background:linear-gradient(135deg,#ff9a56,#ff6b6b);border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 20px rgba(255,107,107,0.3)">`,
261
+ `<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
262
+ `</div>`,
263
+ // 标题 & 描述
264
+ `<h1 style="margin:0 0 16px;font-size:26px;font-weight:700;color:#1a1a2e;letter-spacing:-0.5px">${title}</h1>`,
265
+ `<p style="margin:0;font-size:15px;color:#666;line-height:1.8">${message}</p>`,
266
+ // 下载链接
267
+ downloadLinksHtml,
268
+ // 版本提示
269
+ versionHintHtml,
270
+ `</div></div>`
271
+ ].join("");
272
+ }
273
+ function browserLink(href, label, colorStart, colorEnd, rgbBase) {
274
+ const bg = `background:linear-gradient(135deg,${colorStart},${colorEnd})`;
275
+ const shadow = `box-shadow:0 4px 12px rgba(${rgbBase},0.3)`;
276
+ const hoverShadow = `0 6px 20px rgba(${rgbBase},0.4)`;
277
+ const base = `display:inline-flex;align-items:center;padding:14px 28px;${bg};color:#fff;border-radius:12px;text-decoration:none;font-size:15px;font-weight:500;${shadow};transition:transform .2s,box-shadow .2s`;
278
+ return `<a href="${href}" target="_blank" rel="noopener noreferrer" style="${base}" onmouseover="this.style.transform='translateY(-2px)';this.style.boxShadow='${hoverShadow}'" onmouseout="this.style.transform='';this.style.boxShadow='0 4px 12px rgba(${rgbBase},0.3)'">${label}</a>`;
279
+ }
280
+ var BROWSERSLIST_NAME_MAP = {
281
+ chrome: "chrome",
282
+ chromium: "chrome",
283
+ edge: "edge",
284
+ firefox: "firefox",
285
+ ff: "firefox",
286
+ safari: "safari",
287
+ ios_saf: "safari"
288
+ };
289
+ function parseBrowserslistConfig(cwd, source, logger) {
290
+ let queries = [];
291
+ if (typeof source === "string") {
292
+ const filePath = join2(cwd, source);
293
+ if (!existsSync2(filePath)) {
294
+ logger.warn(`browserslist \u6587\u4EF6\u672A\u627E\u5230: ${filePath}`);
295
+ return null;
296
+ }
297
+ queries = readBrowserslistFile(filePath);
298
+ logger.info(`\u4ECE ${source} \u8BFB\u53D6\u5230 ${queries.length} \u6761 browserslist \u67E5\u8BE2`);
299
+ } else {
300
+ const rcPath = join2(cwd, ".browserslistrc");
301
+ if (existsSync2(rcPath)) {
302
+ queries = readBrowserslistFile(rcPath);
303
+ logger.info(`\u4ECE .browserslistrc \u8BFB\u53D6\u5230 ${queries.length} \u6761 browserslist \u67E5\u8BE2`);
304
+ } else {
305
+ const pkgPath = join2(cwd, "package.json");
306
+ if (existsSync2(pkgPath)) {
307
+ try {
308
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
309
+ if (Array.isArray(pkg.browserslist)) {
310
+ queries = pkg.browserslist;
311
+ logger.info(`\u4ECE package.json#browserslist \u8BFB\u53D6\u5230 ${queries.length} \u6761\u67E5\u8BE2`);
312
+ } else if (typeof pkg.browserslist === "string") {
313
+ queries = [pkg.browserslist];
314
+ logger.info("\u4ECE package.json#browserslist \u8BFB\u53D6\u5230 1 \u6761\u67E5\u8BE2");
315
+ }
316
+ } catch {
317
+ }
318
+ }
319
+ }
320
+ if (queries.length === 0) {
321
+ logger.warn("\u672A\u627E\u5230 .browserslistrc \u6216 package.json#browserslist\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u9ED8\u8BA4\u503C");
322
+ return null;
323
+ }
324
+ }
325
+ return extractMinVersions(queries);
326
+ }
327
+ function readBrowserslistFile(filePath) {
328
+ const content = readFileSync(filePath, "utf-8");
329
+ return content.split("\n").map((line) => line.replace(/#.*$/, "").trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
330
+ }
331
+ function extractMinVersions(queries) {
332
+ const result = {};
333
+ const expanded = queries.flatMap((q) => q.split(",").map((s) => s.trim()));
334
+ const versionPattern = /^(\w+)\s*(?:(>=?)\s*)?(\d+(?:\.\d+)?)\s*$/i;
335
+ for (const query of expanded) {
336
+ if (/^(not|dead|last|defaults|>?\s*\d+%|unreleased|current)/i.test(query)) {
337
+ continue;
338
+ }
339
+ const match = versionPattern.exec(query);
340
+ if (!match) continue;
341
+ const [, browserName, operator, versionStr] = match;
342
+ const key = BROWSERSLIST_NAME_MAP[browserName.toLowerCase()];
343
+ if (!key) continue;
344
+ let version = Number.parseFloat(versionStr);
345
+ if (operator === ">") {
346
+ version = Math.ceil(version) + 1;
347
+ }
348
+ if (result[key] == null || version < result[key]) {
349
+ result[key] = version;
350
+ }
351
+ }
352
+ return result;
353
+ }
354
+
355
+ // src/plugin/builtin/garfish.ts
356
+ var garfishPlugin = createPlugin((options = {}) => ({
357
+ name: "@4399ywkf/plugin-garfish",
358
+ version: "1.0.0",
359
+ description: "Garfish \u5FAE\u524D\u7AEF\u63D2\u4EF6",
360
+ setup(context) {
361
+ const { appName, master = false, apps = [], sandbox = {} } = options;
362
+ const { config, logger, isDev } = context;
363
+ const finalAppName = appName || config.appName;
364
+ const hooks = {
365
+ // ===== 构建阶段钩子 =====
366
+ modifyRspackConfig(rspackConfig) {
367
+ if (!master) {
368
+ rspackConfig.output = {
369
+ ...rspackConfig.output,
370
+ library: finalAppName,
371
+ libraryTarget: "umd",
372
+ globalObject: "window",
373
+ chunkLoadingGlobal: `garfish_${finalAppName}`
374
+ };
375
+ rspackConfig.devServer = {
376
+ ...rspackConfig.devServer,
377
+ headers: {
378
+ ...rspackConfig.devServer?.headers || {},
379
+ "Access-Control-Allow-Origin": "*",
380
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
381
+ "Access-Control-Allow-Headers": "*"
382
+ }
383
+ };
384
+ logger.info(`\u914D\u7F6E UMD \u8F93\u51FA: ${finalAppName}`);
385
+ }
386
+ return rspackConfig;
387
+ },
388
+ beforeDevServer() {
389
+ if (master) {
390
+ logger.info("Garfish \u4E3B\u5E94\u7528\u6A21\u5F0F");
391
+ logger.info(`\u5B50\u5E94\u7528\u5217\u8868: ${apps.map((a) => a.name).join(", ")}`);
392
+ } else {
393
+ logger.info(`Garfish \u5B50\u5E94\u7528\u6A21\u5F0F: ${finalAppName}`);
394
+ }
395
+ },
396
+ // ===== 代码生成阶段钩子 =====
397
+ injectEntry(_ctx) {
398
+ if (master && apps.length > 0) {
399
+ return {
400
+ imports: [`import { initGarfishMaster } from "./bootstrap";`],
401
+ topLevel: [`// Garfish \u4E3B\u5E94\u7528\uFF1A\u521D\u59CB\u5316\u5FAE\u524D\u7AEF\u5E76\u6CE8\u518C\u5B50\u5E94\u7528`, `await initGarfishMaster();`]
402
+ };
403
+ }
404
+ if (!master) {
405
+ return {
406
+ imports: [`import { garfishProvider, isMicroAppEnv } from "./bootstrap";`],
407
+ exports: [
408
+ `// Garfish \u5B50\u5E94\u7528\u751F\u547D\u5468\u671F\u5BFC\u51FA\uFF08provider \u51FD\u6570\u683C\u5F0F\uFF09`,
409
+ `export const provider = garfishProvider;`
410
+ ],
411
+ topLevel: [
412
+ `// Garfish \u5FAE\u524D\u7AEF\uFF1A\u72EC\u7ACB\u8FD0\u884C\u5224\u65AD`,
413
+ `const shouldRunIndependently = !isMicroAppEnv();`
414
+ ]
415
+ };
416
+ }
417
+ return {};
418
+ },
419
+ injectBootstrap(_ctx) {
420
+ const imports = [];
421
+ const exports = [];
422
+ const topLevel = [];
423
+ if (master) {
424
+ if (apps.length > 0) {
425
+ imports.push(`import Garfish from "garfish";`);
426
+ topLevel.push(generateMasterInitCode(apps, config.router.basename));
427
+ }
428
+ } else {
429
+ imports.push(
430
+ `import { createGarfishProvider, isMicroAppEnv } from "@4399ywkf/core/runtime";`
431
+ );
432
+ exports.push(
433
+ `/**`,
434
+ ` * Garfish \u5B50\u5E94\u7528 provider\uFF08render/destroy \u683C\u5F0F\uFF09`,
435
+ ` */`,
436
+ `export const garfishProvider = createGarfishProvider(createAppConfig);`,
437
+ ``,
438
+ `/**`,
439
+ ` * \u5224\u65AD\u662F\u5426\u5728\u5FAE\u524D\u7AEF\u73AF\u5883`,
440
+ ` */`,
441
+ `export { isMicroAppEnv };`
442
+ );
443
+ }
444
+ return { imports, exports, topLevel };
445
+ },
446
+ modifyBootstrapCode(code, _ctx) {
447
+ if (!master) {
448
+ return code.replace(/antd:\s*\{\s*enabled:\s*true/, "antd: { enabled: false");
449
+ }
450
+ return code;
451
+ },
452
+ // ===== 运行时阶段钩子 =====
453
+ modifyAppConfig(appConfig) {
454
+ return {
455
+ ...appConfig,
456
+ // 微前端模式下禁用 antd 样式注入(由主应用管理)
457
+ antd: {
458
+ ...appConfig.antd,
459
+ enabled: master
460
+ }
461
+ };
462
+ }
463
+ };
464
+ return hooks;
465
+ }
466
+ }));
467
+ function generateMasterInitCode(apps, basename) {
468
+ const appConfigs = apps.map(
469
+ (app) => `{
470
+ name: "${app.name}",
471
+ entry: "${app.entry}",
472
+ activeWhen: "${app.activeRule}",
473
+ ${app.basename ? `basename: "${app.basename}",` : ""}
474
+ }`
475
+ ).join(",\n ");
476
+ return `
477
+ /**
478
+ * Garfish \u4E3B\u5E94\u7528\u521D\u59CB\u5316
479
+ * \u5728 runApp \u4E4B\u524D\u8C03\u7528
480
+ */
481
+ export async function initGarfishMaster(): Promise<void> {
482
+ await Garfish.run({
483
+ basename: "${basename || "/"}",
484
+ domGetter: "#subapp-container",
485
+ apps: [
486
+ ${appConfigs}
487
+ ],
488
+ });
489
+ }
490
+ `;
491
+ }
492
+
493
+ // src/plugin/builtin/i18n.ts
494
+ import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
495
+ import { join as join3 } from "path";
496
+ var i18nPlugin = createPlugin((options = {}) => ({
497
+ name: "@4399ywkf/plugin-i18n",
498
+ version: "1.0.0",
499
+ description: "\u56FD\u9645\u5316 (i18next) \u63D2\u4EF6 \u2014 TS-first \u5DE5\u4F5C\u6D41",
500
+ setup(context) {
501
+ const {
502
+ defaultLocale = "zh-CN",
503
+ locales = ["zh-CN", "en-US"],
504
+ localesDir = "locales",
505
+ sourceDir = "src/locales/default",
506
+ defaultNS = ["common"],
507
+ autoScaffold = true
508
+ } = options;
509
+ const { cwd, logger, isDev } = context;
510
+ if (autoScaffold) {
511
+ scaffoldSourceFiles(cwd, sourceDir, defaultNS, logger);
512
+ scaffoldResourcesFile(cwd, sourceDir, defaultNS, locales, defaultLocale, logger);
513
+ scaffoldConvertScript(cwd, sourceDir, localesDir, defaultLocale, logger);
514
+ scaffoldI18nRc(cwd, localesDir, defaultLocale, locales, logger);
515
+ scaffoldLocaleJsonDirs(cwd, localesDir, locales, defaultLocale, defaultNS, logger);
516
+ }
517
+ const hooks = {
518
+ modifyRspackConfig(rspackConfig) {
519
+ const aliases = rspackConfig.resolve?.alias || {};
520
+ if (!aliases["@locales"]) {
521
+ aliases["@locales"] = join3(cwd, localesDir);
522
+ }
523
+ rspackConfig.resolve = { ...rspackConfig.resolve, alias: aliases };
524
+ return rspackConfig;
525
+ },
526
+ generateFiles(_ctx) {
527
+ return [
528
+ {
529
+ path: "i18n.ts",
530
+ content: generateI18nCode({
531
+ defaultLocale,
532
+ locales,
533
+ localesDir,
534
+ sourceDir,
535
+ defaultNS,
536
+ isDev
537
+ })
538
+ }
539
+ ];
540
+ },
541
+ injectBootstrap(_ctx) {
542
+ return {
543
+ imports: [`import { initI18n } from "./i18n";`],
544
+ topLevel: [`await initI18n();`]
545
+ };
546
+ }
547
+ };
548
+ return hooks;
549
+ }
550
+ }));
551
+ function generateI18nCode(opts) {
552
+ const { defaultLocale, locales, defaultNS, sourceDir } = opts;
553
+ const defaultDirAlias = sourceDir.replace(/^src\//, "");
554
+ const parentDirAlias = defaultDirAlias.replace(/\/[^/]+$/, "");
555
+ return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-i18n \u81EA\u52A8\u751F\u6210
556
+ import i18n from "i18next";
557
+ import LanguageDetector from "i18next-browser-languagedetector";
558
+ import resourcesToBackend from "i18next-resources-to-backend";
559
+ import { initReactI18next } from "react-i18next";
560
+ import { normalizeLocale, SUPPORTED_LOCALES, DEFAULT_LOCALE } from "@/${parentDirAlias}/resources";
561
+
562
+ export { DEFAULT_LOCALE, SUPPORTED_LOCALES, normalizeLocale };
563
+ export type { SupportedLocale, NS } from "@/${parentDirAlias}/resources";
564
+
565
+ const isDev = process.env.NODE_ENV === "development";
566
+
567
+ const instance = i18n
568
+ .use(initReactI18next)
569
+ .use(LanguageDetector)
570
+ .use(
571
+ resourcesToBackend(async (lng: string, ns: string) => {
572
+ const normalizedLng = normalizeLocale(lng);
573
+
574
+ // \u5F00\u53D1\u6A21\u5F0F\u4E0B\uFF0C\u9ED8\u8BA4\u8BED\u8A00\u76F4\u63A5 import TS\uFF08\u5373\u65F6\u751F\u6548\uFF0C\u65E0\u9700\u5148\u751F\u6210 JSON\uFF09
575
+ if (isDev && normalizedLng === "${defaultLocale}") {
576
+ return import("@/${defaultDirAlias}/" + ns);
577
+ }
578
+
579
+ // \u5176\u4ED6\u8BED\u8A00 / \u751F\u4EA7\u6A21\u5F0F\uFF1A\u61D2\u52A0\u8F7D JSON
580
+ return import(
581
+ /* webpackInclude: /\\.json$/ */
582
+ /* webpackChunkName: "locales-[request]" */
583
+ \`@locales/\${normalizedLng}/\${ns}.json\`
584
+ );
585
+ })
586
+ );
587
+
588
+ let initialized = false;
589
+
590
+ export async function initI18n(lang?: string): Promise<typeof i18n> {
591
+ if (initialized) return instance;
592
+
593
+ await instance.init({
594
+ defaultNS: ${JSON.stringify(defaultNS)},
595
+ fallbackLng: DEFAULT_LOCALE,
596
+ interpolation: { escapeValue: false },
597
+ lng: lang,
598
+ });
599
+
600
+ initialized = true;
601
+ return instance;
602
+ }
603
+
604
+ export { instance as i18n };
605
+ export default instance;
606
+ `;
607
+ }
608
+ function scaffoldSourceFiles(cwd, sourceDir, namespaces, logger) {
609
+ const dir = join3(cwd, sourceDir);
610
+ if (!existsSync3(dir)) {
611
+ mkdirSync(dir, { recursive: true });
612
+ }
613
+ for (const ns of namespaces) {
614
+ const filePath = join3(dir, `${ns}.ts`);
615
+ if (!existsSync3(filePath)) {
616
+ writeFileSync2(filePath, buildDefaultNsFile(ns), "utf-8");
617
+ logger.info(`\u5DF2\u751F\u6210\u7FFB\u8BD1\u6E90\u6587\u4EF6: ${sourceDir}/${ns}.ts`);
618
+ }
619
+ }
620
+ const indexPath = join3(dir, "index.ts");
621
+ if (!existsSync3(indexPath)) {
622
+ const imports = namespaces.map((ns) => `import ${ns} from "./${ns}";`).join("\n");
623
+ const entries = namespaces.join(",\n ");
624
+ const content = `${imports}
625
+
626
+ const resources = {
627
+ ${entries},
628
+ } as const;
629
+
630
+ export default resources;
631
+ `;
632
+ writeFileSync2(indexPath, content, "utf-8");
633
+ logger.info(`\u5DF2\u751F\u6210\u7FFB\u8BD1\u7D22\u5F15: ${sourceDir}/index.ts`);
634
+ }
635
+ }
636
+ function buildDefaultNsFile(ns) {
637
+ if (ns === "common") {
638
+ return `export default {
639
+ welcome: "\u6B22\u8FCE\u4F7F\u7528",
640
+ loading: "\u52A0\u8F7D\u4E2D...",
641
+ confirm: "\u786E\u5B9A",
642
+ cancel: "\u53D6\u6D88",
643
+ save: "\u4FDD\u5B58",
644
+ delete: "\u5220\u9664",
645
+ edit: "\u7F16\u8F91",
646
+ back: "\u8FD4\u56DE",
647
+ } as const;
648
+ `;
649
+ }
650
+ return `export default {
651
+ title: "${ns}",
652
+ } as const;
653
+ `;
654
+ }
655
+ function scaffoldResourcesFile(cwd, sourceDir, _namespaces, locales, defaultLocale, logger) {
656
+ const parentDir = join3(cwd, sourceDir, "..");
657
+ const filePath = join3(parentDir, "resources.ts");
658
+ if (existsSync3(filePath)) return;
659
+ const content = `import resources from "./default";
660
+
661
+ export const DEFAULT_LOCALE = "${defaultLocale}";
662
+
663
+ export const SUPPORTED_LOCALES = ${JSON.stringify(locales)} as const;
664
+
665
+ export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
666
+
667
+ export type DefaultResources = typeof resources;
668
+
669
+ export type NS = keyof DefaultResources;
670
+
671
+ export const normalizeLocale = (locale?: string): SupportedLocale => {
672
+ if (!locale) return DEFAULT_LOCALE as SupportedLocale;
673
+
674
+ for (const l of SUPPORTED_LOCALES) {
675
+ if (l.startsWith(locale) || locale.startsWith(l.split("-")[0])) {
676
+ return l;
677
+ }
678
+ }
679
+
680
+ return DEFAULT_LOCALE as SupportedLocale;
681
+ };
682
+
683
+ export const localeOptions = ${JSON.stringify(
684
+ locales.map((l) => ({ value: l, label: getLocaleLabel(l) })),
685
+ null,
686
+ 2
687
+ )} as const;
688
+ `;
689
+ writeFileSync2(filePath, content, "utf-8");
690
+ logger.info(`\u5DF2\u751F\u6210\u7C7B\u578B\u6587\u4EF6: ${sourceDir}/../resources.ts`);
691
+ }
692
+ function getLocaleLabel(locale) {
693
+ const map = {
694
+ "zh-CN": "\u7B80\u4F53\u4E2D\u6587",
695
+ "zh-TW": "\u7E41\u9AD4\u4E2D\u6587",
696
+ "en-US": "English",
697
+ "ja-JP": "\u65E5\u672C\u8A9E",
698
+ "ko-KR": "\uD55C\uAD6D\uC5B4",
699
+ "de-DE": "Deutsch",
700
+ "fr-FR": "Fran\xE7ais",
701
+ "es-ES": "Espa\xF1ol",
702
+ "pt-BR": "Portugu\xEAs",
703
+ "ru-RU": "\u0420\u0443\u0441\u0441\u043A\u0438\u0439",
704
+ "it-IT": "Italiano",
705
+ "vi-VN": "Ti\u1EBFng Vi\u1EC7t",
706
+ ar: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629"
707
+ };
708
+ return map[locale] || locale;
709
+ }
710
+ function scaffoldConvertScript(cwd, sourceDir, localesDir, defaultLocale, logger) {
711
+ const scriptsDir = join3(cwd, "scripts");
712
+ const scriptPath = join3(scriptsDir, "i18n-gen.js");
713
+ if (existsSync3(scriptPath)) return;
714
+ if (!existsSync3(scriptsDir)) {
715
+ mkdirSync(scriptsDir, { recursive: true });
716
+ }
717
+ const content = `const fs = require("fs");
718
+ const path = require("path");
719
+
720
+ const sourceDir = path.join(__dirname, "../${sourceDir}");
721
+ const targetDir = path.join(__dirname, "../${localesDir}/${defaultLocale}");
722
+
723
+ const files = fs
724
+ .readdirSync(sourceDir)
725
+ .filter((file) => file.endsWith(".ts") && file !== "index.ts")
726
+ .map((file) => path.basename(file, ".ts"));
727
+
728
+ require("ts-node/register");
729
+
730
+ if (!fs.existsSync(targetDir)) {
731
+ fs.mkdirSync(targetDir, { recursive: true });
732
+ }
733
+
734
+ console.log("\u5F00\u59CB\u8F6C\u6362 TypeScript \u6587\u4EF6\u5230 JSON...\\n");
735
+
736
+ files.forEach((fileName) => {
737
+ try {
738
+ const modulePath = path.join(sourceDir, \`\${fileName}.ts\`);
739
+ delete require.cache[require.resolve(modulePath)];
740
+ const moduleContent = require(modulePath).default;
741
+ const jsonContent = JSON.stringify(moduleContent, null, 2);
742
+ const targetPath = path.join(targetDir, \`\${fileName}.json\`);
743
+ fs.writeFileSync(targetPath, jsonContent, "utf8");
744
+ console.log(\` \${fileName}.ts \u2192 \${fileName}.json\`);
745
+ } catch (error) {
746
+ console.error(\` \u8F6C\u6362 \${fileName}.ts \u5931\u8D25:\`, error.message);
747
+ }
748
+ });
749
+
750
+ console.log("\\n\u8F6C\u6362\u5B8C\u6210\uFF01");
751
+ `;
752
+ writeFileSync2(scriptPath, content, "utf-8");
753
+ logger.info("\u5DF2\u751F\u6210\u8F6C\u6362\u811A\u672C: scripts/i18n-gen.js");
754
+ }
755
+ function scaffoldI18nRc(cwd, localesDir, defaultLocale, locales, logger) {
756
+ const filePath = join3(cwd, ".i18nrc.js");
757
+ if (existsSync3(filePath)) return;
758
+ const outputLocales = locales.filter((l) => l !== defaultLocale).map((l) => `"${l}"`).join(", ");
759
+ const content = `const { defineConfig } = require("@lobehub/i18n-cli");
760
+
761
+ module.exports = defineConfig({
762
+ entry: "${localesDir}/${defaultLocale}",
763
+ entryLocale: "${defaultLocale}",
764
+ output: "${localesDir}",
765
+ outputLocales: [${outputLocales}],
766
+ });
767
+ `;
768
+ writeFileSync2(filePath, content, "utf-8");
769
+ logger.info("\u5DF2\u751F\u6210\u7FFB\u8BD1\u914D\u7F6E: .i18nrc.js\uFF08@lobehub/i18n-cli\uFF09");
770
+ }
771
+ function scaffoldLocaleJsonDirs(cwd, localesDir, locales, defaultLocale, namespaces, logger) {
772
+ const baseDir = join3(cwd, localesDir);
773
+ for (const locale of locales) {
774
+ const localeDir = join3(baseDir, locale);
775
+ if (!existsSync3(localeDir)) {
776
+ mkdirSync(localeDir, { recursive: true });
777
+ }
778
+ if (locale === defaultLocale) continue;
779
+ for (const ns of namespaces) {
780
+ const filePath = join3(localeDir, `${ns}.json`);
781
+ if (!existsSync3(filePath)) {
782
+ const placeholder = ns === "common" ? JSON.stringify(
783
+ {
784
+ welcome: "Welcome",
785
+ loading: "Loading...",
786
+ confirm: "OK",
787
+ cancel: "Cancel",
788
+ save: "Save",
789
+ delete: "Delete",
790
+ edit: "Edit",
791
+ back: "Back"
792
+ },
793
+ null,
794
+ 2
795
+ ) : JSON.stringify({ title: ns }, null, 2);
796
+ writeFileSync2(filePath, `${placeholder}
797
+ `, "utf-8");
798
+ logger.info(`\u5DF2\u751F\u6210\u7FFB\u8BD1\u6587\u4EF6: ${localesDir}/${locale}/${ns}.json`);
799
+ }
800
+ }
801
+ }
802
+ }
803
+
804
+ // src/plugin/builtin/mock.ts
805
+ import { existsSync as existsSync4, readdirSync, statSync } from "fs";
806
+ import { join as join4, resolve } from "path";
807
+ var mockPlugin = createPlugin((options = {}) => ({
808
+ name: "@4399ywkf/plugin-mock",
809
+ version: "1.0.0",
810
+ description: "Mock \u6570\u636E\u63D2\u4EF6",
811
+ setup(context) {
812
+ const { mockDir = "mock", enableInProd = false, delay = 0, prefix = "" } = options;
813
+ const { cwd, isDev, logger } = context;
814
+ if (!isDev && !enableInProd) {
815
+ logger.info("\u751F\u4EA7\u73AF\u5883\u5DF2\u7981\u7528 Mock");
816
+ return {};
817
+ }
818
+ const mockPath = resolve(cwd, mockDir);
819
+ if (!existsSync4(mockPath)) {
820
+ logger.warn(`Mock \u76EE\u5F55\u4E0D\u5B58\u5728: ${mockPath}`);
821
+ return {};
822
+ }
823
+ const hooks = {
824
+ modifyRspackConfig(rspackConfig) {
825
+ if (isDev && rspackConfig.devServer) {
826
+ const mockFiles = scanMockFiles(mockPath);
827
+ logger.info(`\u627E\u5230 ${mockFiles.length} \u4E2A Mock \u6587\u4EF6`);
828
+ rspackConfig.devServer.setupMiddlewares = (middlewares, _devServer) => {
829
+ middlewares.unshift({
830
+ name: "mock-middleware",
831
+ middleware: createMockMiddleware(mockPath, { delay, prefix, logger })
832
+ });
833
+ return middlewares;
834
+ };
835
+ }
836
+ return rspackConfig;
837
+ },
838
+ beforeDevServer() {
839
+ logger.info(`Mock \u5DF2\u542F\u7528\uFF0C\u76EE\u5F55: ${mockPath}`);
840
+ if (delay > 0) {
841
+ logger.info(`\u6A21\u62DF\u5EF6\u8FDF: ${delay}ms`);
842
+ }
843
+ }
844
+ };
845
+ return hooks;
846
+ }
847
+ }));
848
+ function scanMockFiles(dir) {
849
+ const files = [];
850
+ if (!existsSync4(dir)) {
851
+ return files;
852
+ }
853
+ const entries = readdirSync(dir);
854
+ for (const entry of entries) {
855
+ const fullPath = join4(dir, entry);
856
+ const stat = statSync(fullPath);
857
+ if (stat.isFile() && /\.(ts|js|mjs)$/.test(entry)) {
858
+ files.push(fullPath);
859
+ } else if (stat.isDirectory()) {
860
+ files.push(...scanMockFiles(fullPath));
861
+ }
862
+ }
863
+ return files;
864
+ }
865
+ function createMockMiddleware(mockPath, options) {
866
+ const { delay } = options;
867
+ scanMockFiles(mockPath);
868
+ return async (_req, _res, next) => {
869
+ if (delay > 0) {
870
+ await new Promise((resolve3) => setTimeout(resolve3, delay));
871
+ }
872
+ next();
873
+ };
874
+ }
875
+
876
+ // src/plugin/builtin/react-query.ts
877
+ var reactQueryPlugin = createPlugin((options = {}) => ({
878
+ name: "@4399ywkf/plugin-react-query",
879
+ version: "1.0.0",
880
+ description: "React Query + Axios \u8BF7\u6C42\u5C42\u96C6\u6210",
881
+ setup(context) {
882
+ const {
883
+ staleTime = 5 * 60 * 1e3,
884
+ gcTime = 10 * 60 * 1e3,
885
+ retry = 1,
886
+ devtools = true,
887
+ baseURL = "",
888
+ timeout = 15e3
889
+ } = options;
890
+ const { logger, isDev } = context;
891
+ const hooks = {
892
+ generateFiles(_ctx) {
893
+ logger.info("React Query \u5DF2\u542F\u7528");
894
+ const files = [
895
+ {
896
+ path: "query-client.ts",
897
+ content: buildQueryClientFile({ staleTime, gcTime, retry, devtools, isDev })
898
+ },
899
+ {
900
+ path: "request.ts",
901
+ content: buildRequestFile({ baseURL, timeout })
902
+ }
903
+ ];
904
+ return files;
905
+ },
906
+ injectBootstrap(_ctx) {
907
+ return {
908
+ imports: [
909
+ `import { QueryClientProvider } from "@tanstack/react-query";`,
910
+ `import { queryClient } from "./query-client";`
911
+ ],
912
+ topLevel: [`// React Query Provider\uFF08\u7531 @4399ywkf/plugin-react-query \u6CE8\u5165\uFF09`]
913
+ };
914
+ },
915
+ modifyBootstrapCode(code) {
916
+ const providerEntry = ` {
917
+ component: QueryClientProvider as React.ComponentType<{ children: React.ReactNode }>,
918
+ props: { client: queryClient },
919
+ order: 20,
920
+ }`;
921
+ if (code.includes("providers: []")) {
922
+ code = code.replace("providers: []", `providers: [
923
+ ${providerEntry},
924
+ ]`);
925
+ } else if (code.includes("providers: [")) {
926
+ code = code.replace("providers: [", `providers: [
927
+ ${providerEntry},`);
928
+ }
929
+ if (!code.includes("import React")) {
930
+ code = code.replace(
931
+ `import { bootstrap`,
932
+ `import React from "react";
933
+ import { bootstrap`
934
+ );
935
+ }
936
+ return code;
937
+ }
938
+ };
939
+ return hooks;
940
+ }
941
+ }));
942
+ function buildQueryClientFile(opts) {
943
+ const { staleTime, gcTime, retry, devtools, isDev } = opts;
944
+ return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-react-query \u81EA\u52A8\u751F\u6210
945
+ import { QueryClient } from "@tanstack/react-query";
946
+
947
+ /**
948
+ * \u5168\u5C40 QueryClient \u5B9E\u4F8B
949
+ *
950
+ * \u9ED8\u8BA4\u914D\u7F6E\u53EF\u5728 ywkf.config.ts \u7684 reactQueryPlugin \u9009\u9879\u4E2D\u4FEE\u6539\u3002
951
+ * \u8FD0\u884C\u65F6\u81EA\u5B9A\u4E49\u53EF\u901A\u8FC7 src/app.config.ts \u8986\u76D6\u3002
952
+ */
953
+ export const queryClient = new QueryClient({
954
+ defaultOptions: {
955
+ queries: {
956
+ staleTime: ${staleTime},
957
+ gcTime: ${gcTime},
958
+ retry: ${typeof retry === "boolean" ? retry : retry},
959
+ refetchOnWindowFocus: false,
960
+ },
961
+ mutations: {
962
+ retry: false,
963
+ },
964
+ },
965
+ });
966
+
967
+ export default queryClient;
968
+ `;
969
+ }
970
+ function buildRequestFile(opts) {
971
+ const { baseURL, timeout } = opts;
972
+ return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-react-query \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539
973
+ // \u5982\u9700\u5B9A\u5236\u62E6\u622A\u5668\uFF0C\u8BF7\u7F16\u8F91 src/request.ts
974
+ import axios from "axios";
975
+ import qs from "qs";
976
+ import type { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
977
+ import type { RequestConfig, Result } from "@4399ywkf/core/runtime";
978
+
979
+ // ============ \u52A0\u8F7D\u7528\u6237\u914D\u7F6E ============
980
+
981
+ let userConfig: RequestConfig = {};
982
+
983
+ try {
984
+ const mod = require("@/request");
985
+ userConfig = mod.default || mod;
986
+ } catch {
987
+ // src/request.ts \u4E0D\u5B58\u5728\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E
988
+ }
989
+
990
+ // ============ \u53C2\u6570\u5E8F\u5217\u5316\uFF08GET \u8BF7\u6C42\u4E13\u7528\uFF09============
991
+
992
+ /**
993
+ * GET \u8BF7\u6C42\u53C2\u6570\u5E8F\u5217\u5316\u89C4\u5219\uFF1A
994
+ * - \u6570\u7EC4\uFF1Arepeat \u6A21\u5F0F\uFF08key=1&key=2\uFF09
995
+ * - \u5BF9\u8C61\uFF1AJSON.stringify \u540E\u4F5C\u4E3A\u5B57\u7B26\u4E32\u4F20\u9012
996
+ * - null / undefined\uFF1AskipNulls \u8DF3\u8FC7
997
+ * - \u57FA\u672C\u7C7B\u578B\uFF1A\u539F\u6837\u4F20\u9012
998
+ */
999
+ export function paramsSerializer(params: Record<string, any>): string {
1000
+ const normalized: Record<string, any> = {};
1001
+
1002
+ Object.keys(params).forEach((key) => {
1003
+ const value = params[key];
1004
+
1005
+ if (value === null || value === undefined) {
1006
+ normalized[key] = value;
1007
+ } else if (Array.isArray(value)) {
1008
+ normalized[key] = value;
1009
+ } else if (typeof value === "object") {
1010
+ normalized[key] = JSON.stringify(value);
1011
+ } else {
1012
+ normalized[key] = value;
1013
+ }
1014
+ });
1015
+
1016
+ return qs.stringify(normalized, { arrayFormat: "repeat", skipNulls: true });
1017
+ }
1018
+
1019
+ // ============ \u521B\u5EFA Axios \u5B9E\u4F8B ============
1020
+
1021
+ const instance: AxiosInstance = axios.create({
1022
+ baseURL: userConfig.baseURL ?? ("${baseURL}" || ""),
1023
+ timeout: userConfig.timeout ?? ${timeout},
1024
+ withCredentials: userConfig.withCredentials ?? false,
1025
+ headers: {
1026
+ "Content-Type": "application/json",
1027
+ ...(userConfig.headers || {}),
1028
+ },
1029
+ });
1030
+
1031
+ // ============ \u8BF7\u6C42\u62E6\u622A\u5668 ============
1032
+
1033
+ instance.interceptors.request.use(
1034
+ (config: InternalAxiosRequestConfig) => {
1035
+ // 1. Token \u6CE8\u5165
1036
+ if (userConfig.getToken) {
1037
+ const token = userConfig.getToken();
1038
+ if (token && config.headers) {
1039
+ const prefix = userConfig.tokenPrefix ?? "Bearer";
1040
+ config.headers.Authorization = prefix ? \`\${prefix} \${token}\` : token;
1041
+ }
1042
+ } else {
1043
+ // \u9ED8\u8BA4\uFF1A\u4ECE localStorage \u8BFB\u53D6
1044
+ const token = typeof localStorage !== "undefined"
1045
+ ? localStorage.getItem("token")
1046
+ : null;
1047
+ if (token && config.headers) {
1048
+ config.headers.Authorization = \`Bearer \${token}\`;
1049
+ }
1050
+ }
1051
+
1052
+ // 2. \u7528\u6237\u81EA\u5B9A\u4E49\u8BF7\u6C42\u62E6\u622A
1053
+ if (userConfig.requestInterceptor) {
1054
+ return userConfig.requestInterceptor(config as any) as any;
1055
+ }
1056
+
1057
+ return config;
1058
+ },
1059
+ (error: AxiosError) => Promise.reject(error)
1060
+ );
1061
+
1062
+ // ============ \u54CD\u5E94\u62E6\u622A\u5668 ============
1063
+
1064
+ instance.interceptors.response.use(
1065
+ (response: AxiosResponse) => {
1066
+ // \u7528\u6237\u81EA\u5B9A\u4E49\u54CD\u5E94\u62E6\u622A
1067
+ if (userConfig.responseInterceptor) {
1068
+ return userConfig.responseInterceptor(response as any);
1069
+ }
1070
+
1071
+ // \u9ED8\u8BA4\uFF1A\u76F4\u63A5\u8FD4\u56DE response.data
1072
+ return response.data;
1073
+ },
1074
+ (error: AxiosError) => {
1075
+ // \u7528\u6237\u81EA\u5B9A\u4E49\u9519\u8BEF\u5904\u7406
1076
+ if (userConfig.errorHandler) {
1077
+ return userConfig.errorHandler(error as any);
1078
+ }
1079
+
1080
+ // \u9ED8\u8BA4\u9519\u8BEF\u5904\u7406
1081
+ const status = error.response?.status;
1082
+
1083
+ switch (status) {
1084
+ case 401:
1085
+ console.warn("[request] \u672A\u6388\u6743\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
1086
+ break;
1087
+ case 403:
1088
+ console.warn("[request] \u65E0\u8BBF\u95EE\u6743\u9650");
1089
+ break;
1090
+ case 500:
1091
+ console.error("[request] \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
1092
+ break;
1093
+ default:
1094
+ if (!error.response) {
1095
+ console.error("[request] \u7F51\u7EDC\u5F02\u5E38\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5");
1096
+ }
1097
+ }
1098
+
1099
+ return Promise.reject(error);
1100
+ }
1101
+ );
1102
+
1103
+ // ============ \u8BF7\u6C42\u5C01\u88C5\u7C7B ============
1104
+
1105
+ type ExtraConfig = AxiosRequestConfig & { suppressErrorNotification?: boolean };
1106
+
1107
+ class Request {
1108
+ constructor(private readonly http: AxiosInstance) {}
1109
+
1110
+ get<T = any>(url: string, params?: Record<string, any>, config?: ExtraConfig): Promise<Result<T>> {
1111
+ return this.http.get(url, {
1112
+ ...config,
1113
+ params,
1114
+ paramsSerializer,
1115
+ });
1116
+ }
1117
+
1118
+ post<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
1119
+ return this.http.post(url, data, config);
1120
+ }
1121
+
1122
+ put<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
1123
+ return this.http.put(url, data, config);
1124
+ }
1125
+
1126
+ delete<T = any>(url: string, params?: Record<string, any>, config?: ExtraConfig): Promise<Result<T>> {
1127
+ return this.http.delete(url, {
1128
+ ...config,
1129
+ params,
1130
+ paramsSerializer,
1131
+ });
1132
+ }
1133
+
1134
+ patch<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
1135
+ return this.http.patch(url, data, config);
1136
+ }
1137
+ }
1138
+
1139
+ // ============ \u5BFC\u51FA ============
1140
+
1141
+ export const request = new Request(instance);
1142
+ export type { Result };
1143
+ export default request;
1144
+ `;
1145
+ }
1146
+
1147
+ // src/plugin/builtin/tailwind.ts
1148
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3 } from "fs";
1149
+ import { join as join5 } from "path";
1150
+ import { rspack } from "@rspack/core";
1151
+ var tailwindPlugin = createPlugin((options = {}) => ({
1152
+ name: "@4399ywkf/plugin-tailwind",
1153
+ version: "1.0.0",
1154
+ description: "Tailwind CSS \u96C6\u6210\u63D2\u4EF6",
1155
+ setup(context) {
1156
+ const {
1157
+ darkModeSelector = '&:where([data-theme="dark"], [data-theme="dark"] *)',
1158
+ autoConfig = true,
1159
+ extraCSS = ""
1160
+ } = options;
1161
+ const { cwd, logger } = context;
1162
+ if (autoConfig) {
1163
+ ensurePostcssConfig(cwd, logger);
1164
+ }
1165
+ const hooks = {
1166
+ modifyRspackConfig(rspackConfig) {
1167
+ logger.info("Tailwind CSS \u5DF2\u542F\u7528");
1168
+ const rules = rspackConfig.module?.rules || [];
1169
+ const postcssConfigPath = join5(cwd, "postcss.config.js");
1170
+ const isProd = rspackConfig.mode === "production";
1171
+ const tailwindRule = {
1172
+ test: /tailwind\.css$/,
1173
+ use: [
1174
+ isProd ? rspack.CssExtractRspackPlugin.loader : "style-loader",
1175
+ "css-loader",
1176
+ {
1177
+ loader: "postcss-loader",
1178
+ options: {
1179
+ postcssOptions: {
1180
+ config: postcssConfigPath
1181
+ }
1182
+ }
1183
+ }
1184
+ ]
1185
+ };
1186
+ rules.unshift(tailwindRule);
1187
+ rspackConfig.module = { ...rspackConfig.module, rules };
1188
+ return rspackConfig;
1189
+ },
1190
+ generateFiles(_ctx) {
1191
+ const cssContent = buildTailwindCSS(darkModeSelector, extraCSS);
1192
+ return [
1193
+ {
1194
+ path: "tailwind.css",
1195
+ content: cssContent
1196
+ }
1197
+ ];
1198
+ },
1199
+ injectEntry(_ctx) {
1200
+ return {
1201
+ imports: [`import "./tailwind.css";`]
1202
+ };
1203
+ }
1204
+ };
1205
+ return hooks;
1206
+ }
1207
+ }));
1208
+ function buildTailwindCSS(darkModeSelector, extraCSS) {
1209
+ return `/* \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-tailwind \u81EA\u52A8\u751F\u6210 */
1210
+ @import "tailwindcss";
1211
+ @variant dark (${darkModeSelector});
1212
+ ${extraCSS ? `
1213
+ ${extraCSS}` : ""}
1214
+ `;
1215
+ }
1216
+ function ensurePostcssConfig(cwd, logger) {
1217
+ const configPath = join5(cwd, "postcss.config.js");
1218
+ if (!existsSync5(configPath)) {
1219
+ writeFileSync3(
1220
+ configPath,
1221
+ `module.exports = {
1222
+ plugins: {
1223
+ "@tailwindcss/postcss": {},
1224
+ },
1225
+ };
1226
+ `,
1227
+ "utf-8"
1228
+ );
1229
+ logger.info("\u5DF2\u81EA\u52A8\u751F\u6210 postcss.config.js");
1230
+ }
1231
+ }
1232
+
1233
+ // src/plugin/builtin/theme.ts
1234
+ import { join as join6 } from "path";
1235
+ var themePlugin = createPlugin((options = {}) => ({
1236
+ name: "@4399ywkf/plugin-theme",
1237
+ version: "3.0.0",
1238
+ description: "Lobe-UI \u98CE\u683C\u54CD\u5E94\u5F0F\u4E3B\u9898\u7CFB\u7EDF\u63D2\u4EF6",
1239
+ setup(context) {
1240
+ const {
1241
+ darkMode = true,
1242
+ defaultAppearance = "light",
1243
+ primaryColor,
1244
+ neutralColor,
1245
+ prefixCls = "ant",
1246
+ cssVar = true,
1247
+ globalReset = true,
1248
+ externalTheme = false,
1249
+ locale = "auto",
1250
+ scopePopupContainer = true
1251
+ } = options;
1252
+ const { logger } = context;
1253
+ logger.info(
1254
+ `\u4E3B\u9898\u6A21\u5F0F: ${defaultAppearance}, \u4E3B\u8272: ${primaryColor ?? "primary(\u9ED8\u8BA4\u9ED1)"}, \u54CD\u5E94\u5F0F: \u2713`
1255
+ );
1256
+ const hooks = {
1257
+ modifyRspackConfig(config, ctx) {
1258
+ const themePath = join6(ctx.cwd, ".ywkf", "theme.tsx");
1259
+ const currentAlias = config.resolve?.alias ?? {};
1260
+ config.resolve = {
1261
+ ...config.resolve,
1262
+ alias: {
1263
+ ...currentAlias,
1264
+ "@ywkf/theme": themePath
1265
+ }
1266
+ };
1267
+ return config;
1268
+ },
1269
+ generateFiles(_ctx) {
1270
+ return [
1271
+ {
1272
+ path: "theme.tsx",
1273
+ content: generateThemeProvider({
1274
+ darkMode,
1275
+ defaultAppearance,
1276
+ primaryColor,
1277
+ neutralColor,
1278
+ prefixCls,
1279
+ cssVar,
1280
+ globalReset,
1281
+ externalTheme,
1282
+ locale,
1283
+ scopePopupContainer
1284
+ })
1285
+ }
1286
+ ];
1287
+ },
1288
+ injectBootstrap(_ctx) {
1289
+ return {
1290
+ imports: [`import { ThemeWrapper } from "./theme";`]
1291
+ };
1292
+ },
1293
+ modifyBootstrapCode(code) {
1294
+ const providerEntry = ` {
1295
+ component: ThemeWrapper as React.ComponentType<{ children: React.ReactNode }>,
1296
+ props: {},
1297
+ order: 10,
1298
+ }`;
1299
+ if (code.includes("providers: []")) {
1300
+ code = code.replace(
1301
+ "providers: []",
1302
+ `providers: [
1303
+ ${providerEntry},
1304
+ ]`
1305
+ );
1306
+ } else if (code.includes("providers: [")) {
1307
+ code = code.replace(
1308
+ "providers: [",
1309
+ `providers: [
1310
+ ${providerEntry},`
1311
+ );
1312
+ }
1313
+ if (!code.includes("import React")) {
1314
+ code = code.replace(
1315
+ `import { bootstrap`,
1316
+ `import React from "react";
1317
+ import { bootstrap`
1318
+ );
1319
+ }
1320
+ return code;
1321
+ }
1322
+ };
1323
+ return hooks;
1324
+ }
1325
+ }));
1326
+ function generateThemeProvider(opts) {
1327
+ const {
1328
+ darkMode,
1329
+ defaultAppearance,
1330
+ primaryColor,
1331
+ neutralColor,
1332
+ prefixCls,
1333
+ cssVar,
1334
+ globalReset,
1335
+ externalTheme,
1336
+ locale,
1337
+ scopePopupContainer
1338
+ } = opts;
1339
+ const isAutoLocale = locale === "auto";
1340
+ const sections = [];
1341
+ sections.push(`// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-theme v3 \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539`);
1342
+ sections.push(buildImports({ globalReset, cssVar, scopePopupContainer, locale }));
1343
+ sections.push(TYPES_CODE);
1344
+ sections.push(
1345
+ buildStoreCode({ defaultAppearance, primaryColor, neutralColor, locale })
1346
+ );
1347
+ sections.push(HOOKS_CODE);
1348
+ if (globalReset) sections.push(GLOBAL_RESET_CODE);
1349
+ if (cssVar) sections.push(buildCssVarSyncCode());
1350
+ if (darkMode) sections.push(APPEARANCE_SYNC_CODE);
1351
+ if (externalTheme) sections.push(EXTERNAL_THEME_CODE);
1352
+ if (isAutoLocale) sections.push(LOCALE_AUTO_CODE);
1353
+ sections.push(
1354
+ buildWrapperCode({
1355
+ darkMode,
1356
+ cssVar,
1357
+ globalReset,
1358
+ externalTheme,
1359
+ locale,
1360
+ prefixCls,
1361
+ scopePopupContainer
1362
+ })
1363
+ );
1364
+ return sections.join("\n");
1365
+ }
1366
+ var ANTD_LOCALE_MAP = {
1367
+ zhCN: { importName: "zhCN", importPath: "antd/locale/zh_CN.js" },
1368
+ enUS: { importName: "enUS", importPath: "antd/locale/en_US.js" },
1369
+ zhTW: { importName: "zhTW", importPath: "antd/locale/zh_TW.js" },
1370
+ jaJP: { importName: "jaJP", importPath: "antd/locale/ja_JP.js" },
1371
+ koKR: { importName: "koKR", importPath: "antd/locale/ko_KR.js" }
1372
+ };
1373
+ var LOCALE_TO_BCP47 = {
1374
+ zhCN: "zh-CN",
1375
+ enUS: "en-US",
1376
+ zhTW: "zh-TW",
1377
+ jaJP: "ja-JP",
1378
+ koKR: "ko-KR"
1379
+ };
1380
+ function buildImports(opts) {
1381
+ const needsRef = opts.cssVar || opts.scopePopupContainer;
1382
+ const isAutoLocale = opts.locale === "auto";
1383
+ const reactImports = [
1384
+ "useCallback",
1385
+ "useEffect",
1386
+ "useMemo",
1387
+ "useState",
1388
+ ...opts.cssVar ? ["useLayoutEffect"] : [],
1389
+ ...needsRef ? ["useRef"] : [],
1390
+ "type ReactNode"
1391
+ ];
1392
+ const antdStyleImports = [
1393
+ "ThemeProvider as AntdThemeProvider",
1394
+ "StyleProvider",
1395
+ "type GetAntdTheme",
1396
+ ...opts.globalReset ? ["createGlobalStyle", "css"] : []
1397
+ ];
1398
+ const localeEntry = ANTD_LOCALE_MAP[opts.locale];
1399
+ const localeImports = isAutoLocale ? `import zhCN from "antd/locale/zh_CN.js";
1400
+ import type { Locale as AntdLocale } from "antd/es/locale";` : localeEntry ? `import ${localeEntry.importName} from "${localeEntry.importPath}";` : `import zhCN from "antd/locale/zh_CN.js";`;
1401
+ return `
1402
+ import React, { ${reactImports.join(", ")} } from "react";
1403
+ import { ConfigProvider } from "antd";
1404
+ ${localeImports}
1405
+ import { ${antdStyleImports.join(", ")} } from "antd-style";
1406
+ import { createWithEqualityFn } from "zustand/traditional";
1407
+ import { shallow } from "zustand/shallow";
1408
+ import { createThemeConfig, type PrimaryColors, type NeutralColors } from "@4399ywkf/theme-system";`;
1409
+ }
1410
+ var TYPES_CODE = `
1411
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Types \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1412
+
1413
+ export type ThemeAppearance = "light" | "dark" | "auto";
1414
+
1415
+ export interface ThemeState {
1416
+ appearance: ThemeAppearance;
1417
+ primaryColor?: PrimaryColors;
1418
+ neutralColor?: NeutralColors;
1419
+ /** BCP 47 language tag, e.g. "zh-CN", "en-US" */
1420
+ locale: string;
1421
+ }
1422
+
1423
+ export interface ThemeActions {
1424
+ setAppearance: (mode: ThemeAppearance) => void;
1425
+ setPrimaryColor: (color: PrimaryColors | undefined) => void;
1426
+ setNeutralColor: (color: NeutralColors | undefined) => void;
1427
+ setLocale: (locale: string) => void;
1428
+ setTheme: (partial: Partial<ThemeState>) => void;
1429
+ }
1430
+
1431
+ export type ThemeStore = ThemeState & ThemeActions;
1432
+
1433
+ export type { PrimaryColors, NeutralColors };`;
1434
+ function buildStoreCode(opts) {
1435
+ const isAutoLocale = opts.locale === "auto";
1436
+ const primaryLine = opts.primaryColor ? ` primaryColor: "${opts.primaryColor}" as PrimaryColors,` : ` primaryColor: undefined,`;
1437
+ const neutralLine = opts.neutralColor ? ` neutralColor: "${opts.neutralColor}" as NeutralColors,` : ` neutralColor: undefined,`;
1438
+ const localeLine = isAutoLocale ? ` locale: detectInitialLocale(),` : ` locale: "${LOCALE_TO_BCP47[opts.locale] ?? "zh-CN"}",`;
1439
+ const detectionFn = isAutoLocale ? `
1440
+ function detectInitialLocale(): string {
1441
+ if (typeof window !== "undefined") {
1442
+ const hostLocale = (window as any).__YWKF_LOCALE__;
1443
+ if (typeof hostLocale === "string" && hostLocale) return hostLocale;
1444
+ }
1445
+ if (typeof navigator !== "undefined") {
1446
+ return navigator.language || "zh-CN";
1447
+ }
1448
+ return "zh-CN";
1449
+ }
1450
+ ` : "";
1451
+ return `
1452
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Theme Store \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1453
+ ${detectionFn}
1454
+ const DEFAULT_THEME: ThemeState = {
1455
+ appearance: "${opts.defaultAppearance}",
1456
+ ${primaryLine}
1457
+ ${neutralLine}
1458
+ ${localeLine}
1459
+ };
1460
+
1461
+ export const useThemeStore = createWithEqualityFn<ThemeStore>()(
1462
+ (set) => ({
1463
+ ...DEFAULT_THEME,
1464
+ setAppearance: (mode) => set({ appearance: mode }),
1465
+ setPrimaryColor: (color) => set({ primaryColor: color }),
1466
+ setNeutralColor: (color) => set({ neutralColor: color }),
1467
+ setLocale: (locale) => set({ locale }),
1468
+ setTheme: (partial) => set(partial),
1469
+ }),
1470
+ shallow,
1471
+ );`;
1472
+ }
1473
+ var HOOKS_CODE = `
1474
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Convenience Hooks \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1475
+
1476
+ export const useTheme = () =>
1477
+ useThemeStore(
1478
+ (s) => ({
1479
+ appearance: s.appearance,
1480
+ primaryColor: s.primaryColor,
1481
+ neutralColor: s.neutralColor,
1482
+ }),
1483
+ shallow,
1484
+ );
1485
+
1486
+ export const useAppearance = () => useThemeStore((s) => s.appearance);
1487
+ export const usePrimaryColor = () => useThemeStore((s) => s.primaryColor);
1488
+ export const useLocale = () => useThemeStore((s) => s.locale);`;
1489
+ var GLOBAL_RESET_CODE = `
1490
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Global Style \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1491
+
1492
+ const GlobalReset = createGlobalStyle(({ theme }) => css\`
1493
+ :root {
1494
+ --font-settings: "cv01", "tnum", "kern";
1495
+ --font-variations: "opsz" auto, tabular-nums;
1496
+ }
1497
+
1498
+ *,
1499
+ *::before,
1500
+ *::after {
1501
+ box-sizing: border-box;
1502
+ vertical-align: baseline;
1503
+ }
1504
+
1505
+ * {
1506
+ scrollbar-color: \${theme.colorFill} transparent;
1507
+ scrollbar-width: thin;
1508
+ }
1509
+
1510
+ html {
1511
+ overscroll-behavior: none;
1512
+ color-scheme: \${theme.isDarkMode ? "dark" : "light"};
1513
+ }
1514
+
1515
+ html, body, #root, #app {
1516
+ height: 100%;
1517
+ margin: 0;
1518
+ padding: 0;
1519
+ }
1520
+
1521
+ body {
1522
+ overflow: hidden auto;
1523
+ min-height: 100vh;
1524
+ font-family: \${theme.fontFamily};
1525
+ font-size: \${theme.fontSize}px;
1526
+ font-feature-settings: var(--font-settings);
1527
+ font-variation-settings: var(--font-variations);
1528
+ line-height: 1;
1529
+ color: \${theme.colorTextBase};
1530
+ text-size-adjust: none;
1531
+ text-rendering: optimizelegibility;
1532
+ word-wrap: break-word;
1533
+ background-color: \${theme.colorBgLayout};
1534
+ -webkit-font-smoothing: antialiased;
1535
+ -moz-osx-font-smoothing: grayscale;
1536
+ -webkit-overflow-scrolling: touch;
1537
+ -webkit-tap-highlight-color: transparent;
1538
+ }
1539
+
1540
+ code {
1541
+ font-family: \${theme.fontFamilyCode} !important;
1542
+ }
1543
+
1544
+ ::selection {
1545
+ -webkit-text-fill-color: unset !important;
1546
+ }
1547
+ \`);`;
1548
+ function buildCssVarSyncCode() {
1549
+ return `
1550
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 CSS Variable Sync \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1551
+
1552
+ const CSS_VAR_CLASS_RE = /(^|-)css-var(-|$)/;
1553
+
1554
+ function isCssVarClassName(className: string): boolean {
1555
+ return CSS_VAR_CLASS_RE.test(className);
1556
+ }
1557
+
1558
+ function useCssVarSync(ref: React.RefObject<HTMLDivElement | null>) {
1559
+ useLayoutEffect(() => {
1560
+ const node = ref.current;
1561
+ if (!node) return;
1562
+
1563
+ const htmlEl = document.documentElement;
1564
+ let currentClasses: string[] = [];
1565
+
1566
+ const sync = () => {
1567
+ for (const cls of currentClasses) {
1568
+ htmlEl.classList.remove(cls);
1569
+ }
1570
+
1571
+ const nextSet = new Set<string>();
1572
+ let el: HTMLElement | null = node;
1573
+
1574
+ while (el && el !== htmlEl) {
1575
+ for (const cls of el.classList) {
1576
+ if (isCssVarClassName(cls)) {
1577
+ nextSet.add(cls);
1578
+ }
1579
+ }
1580
+ el = el.parentElement;
1581
+ }
1582
+
1583
+ const next = Array.from(nextSet);
1584
+
1585
+ for (const cls of next) {
1586
+ htmlEl.classList.add(cls);
1587
+ }
1588
+
1589
+ currentClasses = next;
1590
+ };
1591
+
1592
+ sync();
1593
+
1594
+ const observer = new MutationObserver(sync);
1595
+ let el: HTMLElement | null = node;
1596
+ while (el && el !== htmlEl) {
1597
+ observer.observe(el, { attributeFilter: ["class"] });
1598
+ el = el.parentElement;
1599
+ }
1600
+
1601
+ return () => {
1602
+ observer.disconnect();
1603
+ for (const cls of currentClasses) {
1604
+ htmlEl.classList.remove(cls);
1605
+ }
1606
+ };
1607
+ }, []);
1608
+ }`;
1609
+ }
1610
+ var APPEARANCE_SYNC_CODE = `
1611
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Appearance Sync \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1612
+
1613
+ function useAppearanceSync(appearance: ThemeAppearance) {
1614
+ useEffect(() => {
1615
+ if (appearance !== "auto") {
1616
+ document.documentElement.dataset.theme = appearance;
1617
+ return;
1618
+ }
1619
+
1620
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
1621
+ document.documentElement.dataset.theme = mq.matches ? "dark" : "light";
1622
+
1623
+ function handleChange(e: MediaQueryListEvent) {
1624
+ document.documentElement.dataset.theme = e.matches ? "dark" : "light";
1625
+ }
1626
+
1627
+ mq.addEventListener("change", handleChange);
1628
+ return () => mq.removeEventListener("change", handleChange);
1629
+ }, [appearance]);
1630
+ }`;
1631
+ var EXTERNAL_THEME_CODE = `
1632
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 External Theme Injection \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1633
+
1634
+ function useExternalTheme() {
1635
+ useEffect(() => {
1636
+ const hostTheme = (window as any).__YWKF_THEME__;
1637
+ if (hostTheme && typeof hostTheme === "object") {
1638
+ useThemeStore.getState().setTheme(hostTheme);
1639
+ }
1640
+
1641
+ const handler = (e: Event) => {
1642
+ const detail = (e as CustomEvent<Partial<ThemeState>>).detail;
1643
+ if (detail) useThemeStore.getState().setTheme(detail);
1644
+ };
1645
+
1646
+ window.addEventListener("ywkf:theme-change", handler);
1647
+ return () => window.removeEventListener("ywkf:theme-change", handler);
1648
+ }, []);
1649
+ }`;
1650
+ var LOCALE_AUTO_CODE = `
1651
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Locale Runtime Management \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1652
+
1653
+ const ANTD_LOCALE_LOADERS: Record<string, () => Promise<{ default: AntdLocale }>> = {
1654
+ en: () => import("antd/locale/en_US.js"),
1655
+ "zh-tw": () => import("antd/locale/zh_TW.js"),
1656
+ "zh-hk": () => import("antd/locale/zh_TW.js"),
1657
+ ja: () => import("antd/locale/ja_JP.js"),
1658
+ ko: () => import("antd/locale/ko_KR.js"),
1659
+ };
1660
+
1661
+ function useAntdLocale(): AntdLocale {
1662
+ const lang = useThemeStore((s) => s.locale);
1663
+ const [antdLocale, setAntdLocale] = useState<AntdLocale>(zhCN);
1664
+
1665
+ useEffect(() => {
1666
+ const normalized = (lang || "zh-CN").toLowerCase();
1667
+
1668
+ if (normalized === "zh" || normalized === "zh-cn") {
1669
+ setAntdLocale(zhCN);
1670
+ return;
1671
+ }
1672
+
1673
+ const loader =
1674
+ ANTD_LOCALE_LOADERS[normalized] ??
1675
+ ANTD_LOCALE_LOADERS[normalized.split("-")[0]];
1676
+
1677
+ if (loader) {
1678
+ let cancelled = false;
1679
+ loader().then((m) => {
1680
+ if (!cancelled) setAntdLocale(m.default);
1681
+ });
1682
+ return () => { cancelled = true; };
1683
+ }
1684
+
1685
+ setAntdLocale(zhCN);
1686
+ }, [lang]);
1687
+
1688
+ return antdLocale;
1689
+ }
1690
+
1691
+ function useExternalLocale() {
1692
+ useEffect(() => {
1693
+ const handler = (e: Event) => {
1694
+ const locale = (e as CustomEvent<string>).detail;
1695
+ if (typeof locale === "string" && locale) {
1696
+ useThemeStore.getState().setLocale(locale);
1697
+ }
1698
+ };
1699
+
1700
+ window.addEventListener("ywkf:locale-change", handler);
1701
+ return () => window.removeEventListener("ywkf:locale-change", handler);
1702
+ }, []);
1703
+ }`;
1704
+ function buildWrapperCode(opts) {
1705
+ const { darkMode, cssVar, globalReset, externalTheme, locale, prefixCls, scopePopupContainer } = opts;
1706
+ const cssVarRefLine = cssVar ? "\n const containerRef = useRef<HTMLDivElement>(null);" : "";
1707
+ const popupRefLine = scopePopupContainer ? "\n const popupContainerRef = useRef<HTMLDivElement>(null);" : "";
1708
+ const cssVarSyncLine = cssVar ? "\n useCssVarSync(containerRef);" : "";
1709
+ const appearanceSyncLine = darkMode ? "\n useAppearanceSync(appearance);" : "";
1710
+ const externalThemeLine = externalTheme ? "\n useExternalTheme();" : "";
1711
+ const isAutoLocale = locale === "auto";
1712
+ const localeEntry = ANTD_LOCALE_MAP[locale];
1713
+ const localeVarName = isAutoLocale ? "antdLocale" : localeEntry?.importName ?? "zhCN";
1714
+ const localeLine = isAutoLocale ? "\n const antdLocale = useAntdLocale();\n useExternalLocale();" : "";
1715
+ const getPopupContainerLine = scopePopupContainer ? `
1716
+ // \u5C06\u5F39\u5C42\u6302\u8F7D\u5230\u5BB9\u5668\u5185\u90E8\uFF0C\u786E\u4FDD\u80FD\u7EE7\u627F StyleProvider \u6CE8\u5165\u7684\u6837\u5F0F\u4E0E CSS \u53D8\u91CF\u3002
1717
+ // \u5FAE\u524D\u7AEF\u6A21\u5F0F\u4E0B\u4F18\u5148\u4F7F\u7528 styleContainer\uFF08\u5B50\u5E94\u7528\u6839\u5BB9\u5668\uFF09\uFF0C
1718
+ // \u666E\u901A\u6A21\u5F0F\u4E0B\u4F7F\u7528 popupContainerRef \u6240\u6307\u5411\u7684\u5305\u88C5 div\u3002
1719
+ const getPopupContainer = useCallback(
1720
+ (): HTMLElement =>
1721
+ (IS_MICRO_APP ? styleContainer : popupContainerRef.current) ?? document.body,
1722
+ [styleContainer],
1723
+ );` : "";
1724
+ const childrenSlot = cssVar ? `<div ref={containerRef} style={{ display: "contents" }}>
1725
+ {children}
1726
+ </div>` : "{children}";
1727
+ const innerContent = scopePopupContainer ? `<ConfigProvider locale={${localeVarName}} getPopupContainer={getPopupContainer}>
1728
+ ${globalReset ? "<GlobalReset />" : ""}
1729
+ ${childrenSlot}
1730
+ </ConfigProvider>` : `<ConfigProvider locale={${localeVarName}}>
1731
+ ${globalReset ? "<GlobalReset />" : ""}
1732
+ ${childrenSlot}
1733
+ </ConfigProvider>`;
1734
+ const antdProvider = `<AntdThemeProvider
1735
+ prefixCls={RUNTIME_PREFIX_CLS}
1736
+ appearance={resolvedAppearance}
1737
+ themeMode={appearance}
1738
+ theme={theme}${cssVar ? "\n customToken={{ cssVar: true }}" : ""}
1739
+ >
1740
+ ${innerContent}
1741
+ </AntdThemeProvider>`;
1742
+ const wrapperOpen = scopePopupContainer ? `<div
1743
+ ref={popupContainerRef}
1744
+ data-ywkf-root
1745
+ style={{ position: "relative", height: "100%" }}
1746
+ >` : "";
1747
+ const wrapperClose = scopePopupContainer ? `</div>` : "";
1748
+ const innerReturn = `IS_MICRO_APP && styleContainer ? (
1749
+ <StyleProvider container={styleContainer}>{provider}</StyleProvider>
1750
+ ) : provider`;
1751
+ const returnBody = scopePopupContainer ? `(
1752
+ ${wrapperOpen}
1753
+ {IS_MICRO_APP && styleContainer ? (
1754
+ <StyleProvider container={styleContainer}>{provider}</StyleProvider>
1755
+ ) : provider}
1756
+ ${wrapperClose}
1757
+ )` : `(${innerReturn})`;
1758
+ return `
1759
+ // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 ThemeWrapper \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1760
+
1761
+ // \u8FD0\u884C\u65F6\u5224\u65AD\u662F\u5426\u5904\u4E8E Garfish \u5B50\u5E94\u7528\u73AF\u5883\uFF0C\u540C\u4E00 bundle \u652F\u6301\u72EC\u7ACB\u90E8\u7F72\u4E0E\u5FAE\u524D\u7AEF\u4E24\u79CD\u6A21\u5F0F\u3002
1762
+ const IS_MICRO_APP = typeof window !== "undefined" && !!(window as any).__GARFISH__;
1763
+
1764
+ const RUNTIME_PREFIX_CLS = typeof process !== "undefined"
1765
+ && process.env?.YWKF_PREFIX_CLS
1766
+ || "${prefixCls}";
1767
+
1768
+ interface ThemeWrapperProps {
1769
+ children: ReactNode;
1770
+ }
1771
+
1772
+ export function ThemeWrapper({ children }: ThemeWrapperProps) {${cssVarRefLine}${popupRefLine}
1773
+ const { appearance, primaryColor, neutralColor } = useThemeStore(
1774
+ (s) => ({ appearance: s.appearance, primaryColor: s.primaryColor, neutralColor: s.neutralColor }),
1775
+ shallow,
1776
+ );${cssVarSyncLine}${appearanceSyncLine}${externalThemeLine}${localeLine}
1777
+
1778
+ // \u5FAE\u524D\u7AEF\u6A21\u5F0F\u4E0B\uFF0C\u5C06 CSS-in-JS \u6837\u5F0F\u6CE8\u5165\u5230 Garfish \u5B50\u5E94\u7528\u5BB9\u5668\u800C\u975E document.head\uFF0C
1779
+ // \u907F\u514D\u4E0E\u4E3B\u5E94\u7528\u5171\u4EAB\u7684 @ant-design/cssinjs StyleContext \u4EA7\u751F\u51B2\u7A81\u3002
1780
+ // useState lazy initializer \u540C\u6B65\u8BFB\u53D6\uFF08Garfish \u6302\u8F7D\u65F6\u5BB9\u5668\u5DF2\u63D0\u524D\u521B\u5EFA\uFF09\uFF0C
1781
+ // useEffect \u515C\u5E95\u5904\u7406\u6781\u7AEF\u7ADE\u6001\uFF08\u5982 SSR hydration\uFF09\u3002
1782
+ const [styleContainer, setStyleContainer] = useState<HTMLElement | undefined>(() => {
1783
+ if (!IS_MICRO_APP || typeof document === "undefined") return undefined;
1784
+ return document.getElementById(RUNTIME_PREFIX_CLS)?.parentElement ?? undefined;
1785
+ });
1786
+
1787
+ useEffect(() => {
1788
+ if (!IS_MICRO_APP) return;
1789
+ const el = document.getElementById(RUNTIME_PREFIX_CLS)?.parentElement;
1790
+ if (el) setStyleContainer(el);
1791
+ }, []);
1792
+
1793
+ const resolvedAppearance = useMemo(() => {
1794
+ if (appearance !== "auto") return appearance;
1795
+ if (typeof window === "undefined") return "light";
1796
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1797
+ }, [appearance]);
1798
+
1799
+ const theme = useCallback<GetAntdTheme>(
1800
+ (ap) => createThemeConfig({
1801
+ appearance: ap as "light" | "dark",
1802
+ primaryColor,
1803
+ neutralColor,
1804
+ }),
1805
+ [primaryColor, neutralColor],
1806
+ );
1807
+ ${getPopupContainerLine}
1808
+ const provider = (
1809
+ ${antdProvider}
1810
+ );
1811
+
1812
+ return ${returnBody};
1813
+ }
1814
+
1815
+ export default ThemeWrapper;
1816
+ `;
1817
+ }
1818
+
1819
+ // src/plugin/builtin/zustand.ts
1820
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
1821
+ import { join as join7 } from "path";
1822
+ var zustandPlugin = createPlugin((options = {}) => ({
1823
+ name: "@4399ywkf/plugin-zustand",
1824
+ version: "1.0.0",
1825
+ description: "Zustand \u72B6\u6001\u7BA1\u7406\u96C6\u6210",
1826
+ setup(context) {
1827
+ const { scaffold = true, storeDir = "store" } = options;
1828
+ const { cwd, logger } = context;
1829
+ const storePath = join7(cwd, storeDir);
1830
+ if (scaffold && !existsSync6(storePath)) {
1831
+ scaffoldStore(storePath, logger);
1832
+ }
1833
+ const hooks = {
1834
+ modifyRspackConfig(rspackConfig) {
1835
+ logger.info("Zustand \u5DF2\u542F\u7528");
1836
+ const resolve3 = rspackConfig.resolve || {};
1837
+ const alias = resolve3.alias || {};
1838
+ alias["@store"] = storePath;
1839
+ resolve3.alias = alias;
1840
+ rspackConfig.resolve = resolve3;
1841
+ return rspackConfig;
1842
+ },
1843
+ generateFiles(_ctx) {
1844
+ return [
1845
+ {
1846
+ path: "store.ts",
1847
+ content: buildStoreHelperFile()
1848
+ }
1849
+ ];
1850
+ }
1851
+ };
1852
+ return hooks;
1853
+ }
1854
+ }));
1855
+ function scaffoldStore(storePath, logger) {
1856
+ const dirs = [
1857
+ storePath,
1858
+ join7(storePath, "middleware"),
1859
+ join7(storePath, "app"),
1860
+ join7(storePath, "app", "slices"),
1861
+ join7(storePath, "app", "slices", "counter")
1862
+ ];
1863
+ for (const dir of dirs) {
1864
+ if (!existsSync6(dir)) {
1865
+ mkdirSync2(dir, { recursive: true });
1866
+ }
1867
+ }
1868
+ writeFileSync4(join7(storePath, "middleware", "createDevtools.ts"), TPL_CREATE_DEVTOOLS, "utf-8");
1869
+ writeFileSync4(
1870
+ join7(storePath, "app", "slices", "counter", "initialState.ts"),
1871
+ TPL_COUNTER_INITIAL_STATE,
1872
+ "utf-8"
1873
+ );
1874
+ writeFileSync4(
1875
+ join7(storePath, "app", "slices", "counter", "actions.ts"),
1876
+ TPL_COUNTER_ACTIONS,
1877
+ "utf-8"
1878
+ );
1879
+ writeFileSync4(join7(storePath, "app", "initialState.ts"), TPL_APP_INITIAL_STATE, "utf-8");
1880
+ writeFileSync4(join7(storePath, "app", "store.ts"), TPL_APP_STORE, "utf-8");
1881
+ writeFileSync4(join7(storePath, "app", "index.tsx"), TPL_APP_INDEX, "utf-8");
1882
+ writeFileSync4(join7(storePath, "index.ts"), TPL_STORE_INDEX, "utf-8");
1883
+ logger.info("\u5DF2\u751F\u6210 store/ \u811A\u624B\u67B6\uFF08Agent/Slice \u67B6\u6784\uFF09");
1884
+ }
1885
+ var TPL_CREATE_DEVTOOLS = `import type { DevtoolsOptions } from "zustand/middleware";
1886
+ import { devtools as devtoolsMiddleware } from "zustand/middleware";
1887
+ import type { StateCreator, StoreMutatorIdentifier } from "zustand/vanilla";
1888
+
1889
+ /**
1890
+ * \u5C01\u88C5 zustand devtools \u4E2D\u95F4\u4EF6
1891
+ * \u751F\u4EA7\u73AF\u5883\u81EA\u52A8\u7981\u7528\uFF0C\u5F00\u53D1\u73AF\u5883\u542F\u7528 trace
1892
+ */
1893
+ export const createDevtools =
1894
+ (name: string, options?: DevtoolsOptions) =>
1895
+ <
1896
+ T,
1897
+ Mps extends [StoreMutatorIdentifier, unknown][] = [],
1898
+ Mcs extends [StoreMutatorIdentifier, unknown][] = [],
1899
+ >(
1900
+ initializer: StateCreator<T, [...Mps, ["zustand/devtools", never]], Mcs>,
1901
+ ) =>
1902
+ devtoolsMiddleware(initializer, {
1903
+ name,
1904
+ enabled: process.env.NODE_ENV === "development",
1905
+ trace: process.env.NODE_ENV === "development",
1906
+ ...options,
1907
+ });
1908
+ `;
1909
+ var TPL_COUNTER_INITIAL_STATE = `export interface CounterState {
1910
+ count: number;
1911
+ loading: boolean;
1912
+ error: string | null;
1913
+ }
1914
+
1915
+ export const initialCounterState: CounterState = {
1916
+ count: 0,
1917
+ loading: false,
1918
+ error: null,
1919
+ };
1920
+ `;
1921
+ var TPL_COUNTER_ACTIONS = `import type { StateCreator } from "zustand/vanilla";
1922
+ import type { AppStore } from "../../store";
1923
+
1924
+ export interface CounterAction {
1925
+ increment: () => void;
1926
+ decrement: () => void;
1927
+ reset: () => void;
1928
+ setLoading: (loading: boolean) => void;
1929
+ setError: (error: string | null) => void;
1930
+ fetchCount: () => Promise<void>;
1931
+ }
1932
+
1933
+ export const createCounterSlice: StateCreator<
1934
+ AppStore,
1935
+ [["zustand/devtools", never]],
1936
+ [],
1937
+ CounterAction
1938
+ > = (set, get) => ({
1939
+ increment: () => set((s) => ({ count: s.count + 1 })),
1940
+ decrement: () => set((s) => ({ count: s.count - 1 })),
1941
+ reset: () => set({ count: 0, error: null }),
1942
+ setLoading: (loading) => set({ loading }),
1943
+ setError: (error) => set({ error }),
1944
+
1945
+ fetchCount: async () => {
1946
+ set({ loading: true, error: null });
1947
+ try {
1948
+ // \u66FF\u6362\u4E3A\u5B9E\u9645 API \u8C03\u7528
1949
+ // import request from "@ywkf/request";
1950
+ // const res = await request.get("/api/count");
1951
+ // set({ count: res.data, loading: false });
1952
+ await new Promise((r) => setTimeout(r, 500));
1953
+ set({ count: 42, loading: false });
1954
+ } catch (err) {
1955
+ set({
1956
+ error: err instanceof Error ? err.message : "\u672A\u77E5\u9519\u8BEF",
1957
+ loading: false,
1958
+ });
1959
+ }
1960
+ },
1961
+ });
1962
+ `;
1963
+ var TPL_APP_INITIAL_STATE = `import {
1964
+ type CounterState,
1965
+ initialCounterState,
1966
+ } from "./slices/counter/initialState";
1967
+
1968
+ // ============== \u805A\u5408\u6240\u6709 Slice \u72B6\u6001 ============== //
1969
+
1970
+ export type AppStoreState = CounterState;
1971
+
1972
+ export const initialState: AppStoreState = {
1973
+ ...initialCounterState,
1974
+ };
1975
+ `;
1976
+ var TPL_APP_STORE = `import { subscribeWithSelector } from "zustand/middleware";
1977
+ import { shallow } from "zustand/shallow";
1978
+ import { createWithEqualityFn } from "zustand/traditional";
1979
+ import type { StateCreator } from "zustand/vanilla";
1980
+
1981
+ import { createDevtools } from "../middleware/createDevtools";
1982
+ import { type AppStoreState, initialState } from "./initialState";
1983
+
1984
+ import {
1985
+ type CounterAction,
1986
+ createCounterSlice,
1987
+ } from "./slices/counter/actions";
1988
+
1989
+ // ============== \u805A\u5408 Store \u7C7B\u578B ============== //
1990
+
1991
+ export type AppStore = AppStoreState & CounterAction;
1992
+
1993
+ // ============== \u521B\u5EFA Store ============== //
1994
+
1995
+ const createStore: StateCreator<AppStore, [["zustand/devtools", never]]> = (
1996
+ ...parameters
1997
+ ) => ({
1998
+ ...initialState,
1999
+ ...createCounterSlice(...parameters),
2000
+ });
2001
+
2002
+ // ============== \u5B9E\u88C5 useStore ============== //
2003
+
2004
+ const devtools = createDevtools("app");
2005
+
2006
+ export const useAppStore = createWithEqualityFn<AppStore>()(
2007
+ subscribeWithSelector(devtools(createStore)),
2008
+ shallow,
2009
+ );
2010
+
2011
+ export const getAppStoreState = () => useAppStore.getState();
2012
+ `;
2013
+ var TPL_APP_INDEX = `export { useAppStore, getAppStoreState } from "./store";
2014
+ export type { AppStore } from "./store";
2015
+ export { initialState } from "./initialState";
2016
+ export type { AppStoreState } from "./initialState";
2017
+ `;
2018
+ var TPL_STORE_INDEX = `/**
2019
+ * Store \u805A\u5408\u5165\u53E3
2020
+ *
2021
+ * \u67B6\u6784\uFF1A
2022
+ * - store/middleware/ \u2014 \u4E2D\u95F4\u4EF6\uFF08devtools \u7B49\uFF09
2023
+ * - store/{domain}/ \u2014 \u6BCF\u4E2A\u57DF\u4EE3\u8868\u4E00\u4E2A\u4E1A\u52A1\u5B50\u7CFB\u7EDF
2024
+ * - store/{domain}/slices \u2014 \u6BCF\u4E2A slice \u62C6\u4E3A initialState + actions
2025
+ *
2026
+ * \u7EA6\u675F\uFF1A
2027
+ * - initialState\uFF1A\u7EAF\u6570\u636E\u5B9A\u4E49\uFF0C\u4E0D\u542B\u903B\u8F91
2028
+ * - actions\uFF1A\u540C\u6B65\u72B6\u6001\u53D8\u66F4 + \u5F02\u6B65\u526F\u4F5C\u7528
2029
+ * - slice \u4E4B\u95F4\u7981\u6B62\u76F4\u63A5\u4FEE\u6539\u5F7C\u6B64\u72B6\u6001
2030
+ * - \u57DF\u5C42\u8D1F\u8D23\u8DE8 slice \u534F\u8C03
2031
+ */
2032
+ export { useAppStore, getAppStoreState } from "./app";
2033
+ export type { AppStore, AppStoreState } from "./app";
2034
+ `;
2035
+ function buildStoreHelperFile() {
2036
+ return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-zustand \u81EA\u52A8\u751F\u6210
2037
+ // \u63D0\u4F9B Zustand \u5DE5\u5177\u51FD\u6570\u7684 re-export\uFF0C\u65B9\u4FBF\u4E1A\u52A1\u4EE3\u7801\u5F15\u7528
2038
+
2039
+ export { useShallow } from "zustand/react/shallow";
2040
+ export type { StateCreator, StoreApi } from "zustand";
2041
+ `;
2042
+ }
2043
+
2044
+ // src/config/normalize.ts
2045
+ var BUILTIN_PLUGINS = [
2046
+ { key: "reactQuery", factory: reactQueryPlugin, pluginName: "@4399ywkf/plugin-react-query" },
2047
+ { key: "zustand", factory: zustandPlugin, pluginName: "@4399ywkf/plugin-zustand" },
2048
+ { key: "tailwind", factory: tailwindPlugin, pluginName: "@4399ywkf/plugin-tailwind" }
2049
+ ];
2050
+ var OPTIONAL_PLUGINS = [
2051
+ { key: "biome", factory: biomePlugin, pluginName: "@4399ywkf/plugin-biome" },
2052
+ { key: "analytics", factory: analyticsPlugin, pluginName: "@4399ywkf/plugin-analytics" },
2053
+ { key: "mock", factory: mockPlugin, pluginName: "@4399ywkf/plugin-mock" },
2054
+ { key: "theme", factory: themePlugin, pluginName: "@4399ywkf/plugin-theme" },
2055
+ { key: "i18n", factory: i18nPlugin, pluginName: "@4399ywkf/plugin-i18n" },
2056
+ { key: "garfish", factory: garfishPlugin, pluginName: "@4399ywkf/plugin-garfish" },
2057
+ { key: "browserCheck", factory: browserCheckPlugin, pluginName: "@4399ywkf/plugin-browser-check" }
2058
+ ];
2059
+ var ALL_SHORTHAND_KEYS = /* @__PURE__ */ new Set([
2060
+ ...BUILTIN_PLUGINS.map((p) => p.key),
2061
+ ...OPTIONAL_PLUGINS.map((p) => p.key)
2062
+ ]);
2063
+ function normalizeConfig(input) {
2064
+ const raw = input;
2065
+ const result = { ...raw };
2066
+ const hasShorthands = hasAnyShorthand(result);
2067
+ if (!hasShorthands) {
2068
+ return input;
2069
+ }
2070
+ inferFields(result);
2071
+ const plugins = result.plugins;
2072
+ const explicitPlugins = plugins ?? [];
2073
+ const explicitNames = collectPluginNames(explicitPlugins);
2074
+ const shorthandPlugins = [];
2075
+ for (const { key, factory, pluginName } of BUILTIN_PLUGINS) {
2076
+ const value = result[key];
2077
+ if (explicitNames.has(pluginName)) continue;
2078
+ if (value === false) continue;
2079
+ if (value === void 0 && !hasShorthands) continue;
2080
+ const opts = typeof value === "object" ? value : void 0;
2081
+ shorthandPlugins.push(factory(opts));
2082
+ }
2083
+ for (const { key, factory, pluginName } of OPTIONAL_PLUGINS) {
2084
+ const value = result[key];
2085
+ if (value === void 0 || explicitNames.has(pluginName)) continue;
2086
+ const opts = value === true ? void 0 : value;
2087
+ shorthandPlugins.push(factory(opts));
2088
+ }
2089
+ result.plugins = [...shorthandPlugins, ...explicitPlugins];
2090
+ for (const key of ALL_SHORTHAND_KEYS) {
2091
+ delete result[key];
2092
+ }
2093
+ return result;
2094
+ }
2095
+ function inferFields(config) {
2096
+ const html = config.html ?? {};
2097
+ config.html = html;
2098
+ if (html.title === void 0 && config.appCName) {
2099
+ html.title = config.appCName;
2100
+ }
2101
+ if (html.mountRoot === void 0 && config.appName) {
2102
+ html.mountRoot = config.appName;
2103
+ }
2104
+ }
2105
+ function hasAnyShorthand(config) {
2106
+ for (const key of ALL_SHORTHAND_KEYS) {
2107
+ if (config[key] !== void 0) {
2108
+ return true;
2109
+ }
2110
+ }
2111
+ return false;
2112
+ }
2113
+ function collectPluginNames(plugins) {
2114
+ const names = /* @__PURE__ */ new Set();
2115
+ for (const p of plugins) {
2116
+ if (typeof p === "object" && p !== null && "name" in p) {
2117
+ names.add(p.name);
2118
+ }
2119
+ }
2120
+ return names;
2121
+ }
2122
+
7
2123
  // src/config/schema.ts
8
2124
  function defineConfig(config) {
9
2125
  return config;
@@ -70,8 +2186,8 @@ var defaultConfig = {
70
2186
  var CONFIG_FILES = ["ywkf.config.ts", "ywkf.config.mts", "ywkf.config.js", "ywkf.config.mjs"];
71
2187
  function findConfigFile(cwd) {
72
2188
  for (const file of CONFIG_FILES) {
73
- const configPath = resolve(cwd, file);
74
- if (existsSync(configPath)) {
2189
+ const configPath = resolve2(cwd, file);
2190
+ if (existsSync7(configPath)) {
75
2191
  return configPath;
76
2192
  }
77
2193
  }
@@ -113,7 +2229,8 @@ async function resolveConfig(cwd) {
113
2229
  }
114
2230
  try {
115
2231
  const userConfig = await loadConfigFile(configPath);
116
- const config = mergeConfig(userConfig);
2232
+ const normalized = normalizeConfig(userConfig);
2233
+ const config = mergeConfig(normalized);
117
2234
  return { config, configPath };
118
2235
  } catch (error) {
119
2236
  console.error("\u274C \u914D\u7F6E\u6587\u4EF6\u52A0\u8F7D\u5931\u8D25:", error);
@@ -122,7 +2239,7 @@ async function resolveConfig(cwd) {
122
2239
  }
123
2240
  function createPathResolver(cwd) {
124
2241
  return {
125
- resolveApp: (relativePath) => resolve(cwd, relativePath),
2242
+ resolveApp: (relativePath) => resolve2(cwd, relativePath),
126
2243
  cwd
127
2244
  };
128
2245
  }
@@ -133,6 +2250,7 @@ export {
133
2250
  findConfigFile,
134
2251
  loadConfigFile,
135
2252
  mergeConfig,
2253
+ normalizeConfig,
136
2254
  resolveConfig
137
2255
  };
138
2256
  //# sourceMappingURL=index.js.map