@4399ywkf/core 5.0.18 → 5.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,13 @@
1
+ // src/cli/build.ts
2
+ import { rspack as rspack3 } from "@rspack/core";
3
+ import chalk2 from "chalk";
4
+
5
+ // src/config/loader.ts
6
+ import { existsSync } from "fs";
7
+ import { extname, resolve } from "path";
8
+ import { pathToFileURL } from "url";
9
+ import deepmerge from "deepmerge";
10
+
1
11
  // src/config/schema.ts
2
12
  function defineConfig(config) {
3
13
  return config;
@@ -61,16 +71,7 @@ var defaultConfig = {
61
71
  };
62
72
 
63
73
  // src/config/loader.ts
64
- import { existsSync } from "fs";
65
- import { resolve, extname } from "path";
66
- import { pathToFileURL } from "url";
67
- import deepmerge from "deepmerge";
68
- var CONFIG_FILES = [
69
- "ywkf.config.ts",
70
- "ywkf.config.mts",
71
- "ywkf.config.js",
72
- "ywkf.config.mjs"
73
- ];
74
+ var CONFIG_FILES = ["ywkf.config.ts", "ywkf.config.mts", "ywkf.config.js", "ywkf.config.mjs"];
74
75
  function findConfigFile(cwd) {
75
76
  for (const file of CONFIG_FILES) {
76
77
  const configPath = resolve(cwd, file);
@@ -108,9 +109,7 @@ function mergeConfig(userConfig, baseConfig = defaultConfig) {
108
109
  async function resolveConfig(cwd) {
109
110
  const configPath = findConfigFile(cwd);
110
111
  if (!configPath) {
111
- console.warn(
112
- "\u26A0\uFE0F \u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6 (ywkf.config.ts)\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E"
113
- );
112
+ console.warn("\u26A0\uFE0F \u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6 (ywkf.config.ts)\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E");
114
113
  return {
115
114
  config: defaultConfig,
116
115
  configPath: null
@@ -132,36 +131,257 @@ function createPathResolver(cwd) {
132
131
  };
133
132
  }
134
133
 
134
+ // src/plugin/manager.ts
135
+ function createLogger(pluginName) {
136
+ const prefix = `[${pluginName}]`;
137
+ const verbose = !!process.env.DEBUG;
138
+ return {
139
+ info: (msg) => {
140
+ if (verbose) console.log(`${prefix} ${msg}`);
141
+ },
142
+ warn: (msg) => console.warn(`${prefix} ${msg}`),
143
+ error: (msg) => console.error(`${prefix} ${msg}`),
144
+ debug: (msg) => {
145
+ if (verbose) console.debug(`${prefix} ${msg}`);
146
+ }
147
+ };
148
+ }
149
+ async function resolvePlugin(pluginConfig, _cwd) {
150
+ let plugin;
151
+ let options = {};
152
+ if (typeof pluginConfig === "string") {
153
+ const module = await import(pluginConfig);
154
+ plugin = module.default || module;
155
+ } else if (Array.isArray(pluginConfig)) {
156
+ const [pluginOrName, pluginOptions] = pluginConfig;
157
+ options = pluginOptions;
158
+ if (typeof pluginOrName === "string") {
159
+ const module = await import(pluginOrName);
160
+ plugin = module.default || module;
161
+ } else {
162
+ plugin = pluginOrName;
163
+ }
164
+ } else {
165
+ plugin = pluginConfig;
166
+ }
167
+ if (typeof plugin === "function") {
168
+ plugin = plugin(options);
169
+ }
170
+ return { plugin, options };
171
+ }
172
+ var PluginManager = class {
173
+ plugins = /* @__PURE__ */ new Map();
174
+ context;
175
+ constructor(config, cwd, isDev) {
176
+ this.context = {
177
+ cwd,
178
+ isDev,
179
+ isProd: !isDev,
180
+ config,
181
+ logger: createLogger("PluginManager")
182
+ };
183
+ }
184
+ /**
185
+ * 加载并初始化所有插件
186
+ */
187
+ async loadPlugins(pluginConfigs) {
188
+ for (const pluginConfig of pluginConfigs) {
189
+ try {
190
+ const { plugin } = await resolvePlugin(pluginConfig, this.context.cwd);
191
+ if (this.plugins.has(plugin.name)) {
192
+ this.context.logger.warn(`\u63D2\u4EF6 ${plugin.name} \u5DF2\u52A0\u8F7D\uFF0C\u8DF3\u8FC7\u91CD\u590D\u52A0\u8F7D`);
193
+ continue;
194
+ }
195
+ const pluginContext = {
196
+ ...this.context,
197
+ logger: createLogger(plugin.name)
198
+ };
199
+ const hooks = await plugin.setup(pluginContext);
200
+ this.plugins.set(plugin.name, { plugin, hooks });
201
+ this.context.logger.info(`\u5DF2\u52A0\u8F7D\u63D2\u4EF6: ${plugin.name}`);
202
+ } catch (error) {
203
+ this.context.logger.error(`\u52A0\u8F7D\u63D2\u4EF6\u5931\u8D25: ${String(pluginConfig)} - ${error}`);
204
+ }
205
+ }
206
+ }
207
+ /**
208
+ * 执行 modifyRspackConfig 钩子
209
+ */
210
+ async applyRspackConfigHooks(config) {
211
+ let result = config;
212
+ for (const [name, { hooks }] of this.plugins) {
213
+ if (hooks.modifyRspackConfig) {
214
+ try {
215
+ const modified = await hooks.modifyRspackConfig(result, this.context);
216
+ if (modified) {
217
+ result = modified;
218
+ }
219
+ } catch (error) {
220
+ this.context.logger.error(`\u63D2\u4EF6 ${name} modifyRspackConfig \u6267\u884C\u5931\u8D25: ${error}`);
221
+ }
222
+ }
223
+ }
224
+ return result;
225
+ }
226
+ /**
227
+ * 执行 modifyRoutes 钩子
228
+ */
229
+ async applyRoutesHooks(routes) {
230
+ let result = routes;
231
+ for (const [name, { hooks }] of this.plugins) {
232
+ if (hooks.modifyRoutes) {
233
+ try {
234
+ const modified = await hooks.modifyRoutes(result, this.context);
235
+ if (modified) {
236
+ result = modified;
237
+ }
238
+ } catch (error) {
239
+ this.context.logger.error(`\u63D2\u4EF6 ${name} modifyRoutes \u6267\u884C\u5931\u8D25: ${error}`);
240
+ }
241
+ }
242
+ }
243
+ return result;
244
+ }
245
+ /**
246
+ * 执行 modifyAppConfig 钩子
247
+ */
248
+ applyAppConfigHooks(appConfig) {
249
+ let result = appConfig;
250
+ for (const [name, { hooks }] of this.plugins) {
251
+ if (hooks.modifyAppConfig) {
252
+ try {
253
+ const modified = hooks.modifyAppConfig(result, this.context);
254
+ if (modified) {
255
+ result = modified;
256
+ }
257
+ } catch (error) {
258
+ this.context.logger.error(`\u63D2\u4EF6 ${name} modifyAppConfig \u6267\u884C\u5931\u8D25: ${error}`);
259
+ }
260
+ }
261
+ }
262
+ return result;
263
+ }
264
+ /**
265
+ * 收集所有插件的 Provider
266
+ */
267
+ collectProviders() {
268
+ const providers = [];
269
+ for (const [name, { hooks }] of this.plugins) {
270
+ if (hooks.addProvider) {
271
+ try {
272
+ const result = hooks.addProvider(this.context);
273
+ if (Array.isArray(result)) {
274
+ providers.push(...result);
275
+ } else if (result) {
276
+ providers.push(result);
277
+ }
278
+ } catch (error) {
279
+ this.context.logger.error(`\u63D2\u4EF6 ${name} addProvider \u6267\u884C\u5931\u8D25: ${error}`);
280
+ }
281
+ }
282
+ }
283
+ return providers;
284
+ }
285
+ /**
286
+ * 执行 beforeBuild 钩子
287
+ */
288
+ async runBeforeBuild() {
289
+ for (const [name, { hooks }] of this.plugins) {
290
+ if (hooks.beforeBuild) {
291
+ try {
292
+ await hooks.beforeBuild(this.context);
293
+ } catch (error) {
294
+ this.context.logger.error(`\u63D2\u4EF6 ${name} beforeBuild \u6267\u884C\u5931\u8D25: ${error}`);
295
+ }
296
+ }
297
+ }
298
+ }
299
+ /**
300
+ * 执行 afterBuild 钩子
301
+ */
302
+ async runAfterBuild(stats) {
303
+ for (const [name, { hooks }] of this.plugins) {
304
+ if (hooks.afterBuild) {
305
+ try {
306
+ await hooks.afterBuild(this.context, stats);
307
+ } catch (error) {
308
+ this.context.logger.error(`\u63D2\u4EF6 ${name} afterBuild \u6267\u884C\u5931\u8D25: ${error}`);
309
+ }
310
+ }
311
+ }
312
+ }
313
+ /**
314
+ * 执行 beforeDevServer 钩子
315
+ */
316
+ async runBeforeDevServer() {
317
+ for (const [name, { hooks }] of this.plugins) {
318
+ if (hooks.beforeDevServer) {
319
+ try {
320
+ await hooks.beforeDevServer(this.context);
321
+ } catch (error) {
322
+ this.context.logger.error(`\u63D2\u4EF6 ${name} beforeDevServer \u6267\u884C\u5931\u8D25: ${error}`);
323
+ }
324
+ }
325
+ }
326
+ }
327
+ /**
328
+ * 执行 afterDevServer 钩子
329
+ */
330
+ async runAfterDevServer(server) {
331
+ for (const [name, { hooks }] of this.plugins) {
332
+ if (hooks.afterDevServer) {
333
+ try {
334
+ await hooks.afterDevServer(this.context, server);
335
+ } catch (error) {
336
+ this.context.logger.error(`\u63D2\u4EF6 ${name} afterDevServer \u6267\u884C\u5931\u8D25: ${error}`);
337
+ }
338
+ }
339
+ }
340
+ }
341
+ /**
342
+ * 获取所有已加载的插件名称
343
+ */
344
+ getPluginNames() {
345
+ return Array.from(this.plugins.keys());
346
+ }
347
+ /**
348
+ * 获取所有已加载的插件
349
+ */
350
+ getPlugins() {
351
+ return Array.from(this.plugins.values()).map((p) => p.plugin);
352
+ }
353
+ /**
354
+ * 获取所有已加载的插件钩子
355
+ */
356
+ getPluginHooks() {
357
+ return Array.from(this.plugins.values()).map((p) => p.hooks);
358
+ }
359
+ };
360
+
135
361
  // src/rspack/index.ts
136
362
  import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin";
137
363
 
138
364
  // src/rspack/dev.ts
365
+ import { createRequire as createRequire2 } from "module";
139
366
  import ReactRefreshPlugin from "@rspack/plugin-react-refresh";
140
367
  import { merge } from "webpack-merge";
141
- import { createRequire as createRequire2 } from "module";
142
368
 
143
369
  // src/rspack/base.ts
144
- import { rspack } from "@rspack/core";
145
370
  import { createRequire } from "module";
146
- import { fileURLToPath } from "url";
147
371
  import { dirname, join as join5 } from "path";
372
+ import { fileURLToPath } from "url";
373
+ import { rspack } from "@rspack/core";
148
374
 
149
375
  // src/generator/plugin.ts
150
- import { watch, existsSync as existsSync5 } from "fs";
376
+ import { existsSync as existsSync5, watch } from "fs";
151
377
  import { join as join4 } from "path";
152
378
 
153
379
  // src/generator/generator.ts
154
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
380
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
155
381
  import { join as join3 } from "path";
156
382
 
157
383
  // src/router/generator.ts
158
- import {
159
- existsSync as existsSync2,
160
- readdirSync,
161
- statSync,
162
- mkdirSync,
163
- writeFileSync
164
- } from "fs";
384
+ import { existsSync as existsSync2, mkdirSync, readdirSync, statSync, writeFileSync } from "fs";
165
385
  import { join, relative } from "path";
166
386
  function winPath(path) {
167
387
  const isExtendedLengthPath = /^\\\\\?\\/.test(path);
@@ -221,9 +441,7 @@ var ConventionalRouteGenerator = class {
221
441
  const subDirPath = join(dir, subDir);
222
442
  if (subDir.startsWith("__")) {
223
443
  const pathlessRoutes = this.scanDirectory(subDirPath, routePath);
224
- const pathlessLayout = readdirSync(subDirPath).find(
225
- (e) => CONVENTION_FILES.layout.test(e)
226
- );
444
+ const pathlessLayout = readdirSync(subDirPath).find((e) => CONVENTION_FILES.layout.test(e));
227
445
  if (pathlessLayout) {
228
446
  const pathlessChildren = this.scanDirectory(subDirPath, routePath);
229
447
  const pathlessLayoutRel = winPath(
@@ -235,9 +453,7 @@ var ConventionalRouteGenerator = class {
235
453
  layoutFile: pathlessLayoutRel,
236
454
  isLayout: true,
237
455
  pathless: true,
238
- children: pathlessChildren.filter(
239
- (r) => r.layoutFile !== pathlessLayoutRel
240
- )
456
+ children: pathlessChildren.filter((r) => r.layoutFile !== pathlessLayoutRel)
241
457
  });
242
458
  } else {
243
459
  childRoutes.push(...pathlessRoutes);
@@ -255,7 +471,7 @@ var ConventionalRouteGenerator = class {
255
471
  layoutChildren.push({
256
472
  path: routePath,
257
473
  file: relPath(pageFile),
258
- name: routeName + "Index",
474
+ name: `${routeName}Index`,
259
475
  index: true
260
476
  });
261
477
  }
@@ -263,7 +479,7 @@ var ConventionalRouteGenerator = class {
263
479
  layoutChildren.push({
264
480
  path: "*",
265
481
  file: relPath(catchAllFile),
266
- name: routeName + "CatchAll"
482
+ name: `${routeName}CatchAll`
267
483
  });
268
484
  }
269
485
  layoutChildren.push(...childRoutes);
@@ -292,7 +508,7 @@ var ConventionalRouteGenerator = class {
292
508
  result.push({
293
509
  path: "*",
294
510
  file: relPath(catchAllFile),
295
- name: routeName + "CatchAll"
511
+ name: `${routeName}CatchAll`
296
512
  });
297
513
  }
298
514
  result.push(...childRoutes);
@@ -327,15 +543,13 @@ var ConventionalRouteGenerator = class {
327
543
  * 生成有效的 JS 标识符名称
328
544
  */
329
545
  generateRouteName(path) {
330
- const name = path.split("/").filter(Boolean).map(
331
- (s) => s.replace(/^:/, "Param").replace(/\?$/, "Optional").replace(/^\*$/, "CatchAll")
332
- ).map((s) => {
546
+ const name = path.split("/").filter(Boolean).map((s) => s.replace(/^:/, "Param").replace(/\?$/, "Optional").replace(/^\*$/, "CatchAll")).map((s) => {
333
547
  const cleaned = s.replace(/[^a-zA-Z0-9]/g, "");
334
548
  if (!cleaned) return "";
335
549
  return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
336
550
  }).join("");
337
551
  if (!name || /^\d/.test(name)) {
338
- return "Page" + (name || "Root");
552
+ return `Page${name || "Root"}`;
339
553
  }
340
554
  return name;
341
555
  }
@@ -354,8 +568,12 @@ import { createBrowserRouter, type RouteObject } from "react-router";
354
568
 
355
569
  // \u61D2\u52A0\u8F7D\u9875\u9762\u7EC4\u4EF6
356
570
  ${lazyImports.join("\n")}
357
- ${errorImports.length > 0 ? "\n// \u9519\u8BEF\u8FB9\u754C\u7EC4\u4EF6\n" + errorImports.join("\n") : ""}
358
- ${loadingImports.length > 0 ? "\n// \u52A0\u8F7D\u72B6\u6001\u7EC4\u4EF6\n" + loadingImports.join("\n") : ""}
571
+ ${errorImports.length > 0 ? `
572
+ // \u9519\u8BEF\u8FB9\u754C\u7EC4\u4EF6
573
+ ${errorImports.join("\n")}` : ""}
574
+ ${loadingImports.length > 0 ? `
575
+ // \u52A0\u8F7D\u72B6\u6001\u7EC4\u4EF6
576
+ ${loadingImports.join("\n")}` : ""}
359
577
 
360
578
  // \u9ED8\u8BA4\u52A0\u8F7D\u72B6\u6001
361
579
  const DefaultLoading = () => <div style={{ padding: 24, textAlign: "center" }}>\u52A0\u8F7D\u4E2D...</div>;
@@ -394,23 +612,17 @@ export default routes;
394
612
  const toImportPath = (f) => f.replace(/\.(tsx?|jsx?)$/, "");
395
613
  if (route.isLayout && route.layoutFile) {
396
614
  lazyImports.push(
397
- `const ${name}Layout = lazy(() => import("@/pages/${toImportPath(
398
- route.layoutFile
399
- )}"));`
615
+ `const ${name}Layout = lazy(() => import("@/pages/${toImportPath(route.layoutFile)}"));`
400
616
  );
401
617
  }
402
618
  if (route.file) {
403
619
  lazyImports.push(
404
- `const ${name}Page = lazy(() => import("@/pages/${toImportPath(
405
- route.file
406
- )}"));`
620
+ `const ${name}Page = lazy(() => import("@/pages/${toImportPath(route.file)}"));`
407
621
  );
408
622
  }
409
623
  if (route.errorFile) {
410
624
  errorImports.push(
411
- `const ${name}Error = lazy(() => import("@/pages/${toImportPath(
412
- route.errorFile
413
- )}"));`
625
+ `const ${name}Error = lazy(() => import("@/pages/${toImportPath(route.errorFile)}"));`
414
626
  );
415
627
  }
416
628
  if (route.loadingFile) {
@@ -421,12 +633,7 @@ export default routes;
421
633
  );
422
634
  }
423
635
  if (route.children) {
424
- this.collectImports(
425
- route.children,
426
- lazyImports,
427
- errorImports,
428
- loadingImports
429
- );
636
+ this.collectImports(route.children, lazyImports, errorImports, loadingImports);
430
637
  }
431
638
  }
432
639
  }
@@ -452,9 +659,7 @@ export default routes;
452
659
  }
453
660
  if (route.isLayout && route.layoutFile) {
454
661
  const loadingProp = route.loadingFile ? ` Loading={${name}Loading}` : "";
455
- parts.push(
456
- `element: <LazyRoute Component={${name}Layout}${loadingProp} />`
457
- );
662
+ parts.push(`element: <LazyRoute Component={${name}Layout}${loadingProp} />`);
458
663
  } else if (route.file) {
459
664
  parts.push(`element: <LazyRoute Component={${name}Page} />`);
460
665
  }
@@ -463,9 +668,7 @@ export default routes;
463
668
  }
464
669
  if (route.children && route.children.length > 0) {
465
670
  const childParent = route.pathless ? parentPath : route.path;
466
- parts.push(
467
- `children: ${this.emitRouteArray(route.children, childParent)}`
468
- );
671
+ parts.push(`children: ${this.emitRouteArray(route.children, childParent)}`);
469
672
  }
470
673
  return `{
471
674
  ${parts.join(",\n ")}
@@ -488,53 +691,13 @@ function generateConventionalRoutes(options) {
488
691
  new ConventionalRouteGenerator(options).write();
489
692
  }
490
693
 
491
- // src/generator/templates/entry.ts
492
- function generateEntry(config, injections = {}) {
694
+ // src/generator/templates/bootstrap.ts
695
+ function generateBootstrap(config, injections = {}) {
696
+ const { appName, router } = config;
697
+ const routerImport = router.conventional ? `import { createRouter } from "./routes";` : `import { createRouter } from "@/routes";`;
493
698
  const imports = [
494
- `import "@/index.css";`,
495
- `import { runApp } from "./bootstrap";`,
496
- ...injections.imports || []
497
- ];
498
- const topLevel = injections.topLevel || [];
499
- const exports = injections.exports || [];
500
- const hasPluginExports = exports.length > 0;
501
- const hasAsyncTopLevel = topLevel.some((line) => line.includes("await "));
502
- let startupBody;
503
- if (hasPluginExports) {
504
- startupBody = [
505
- ...topLevel,
506
- ...exports,
507
- ``,
508
- `if (shouldRunIndependently !== false) {`,
509
- ` runApp();`,
510
- `}`
511
- ].join("\n");
512
- } else if (hasAsyncTopLevel) {
513
- startupBody = [
514
- `(async () => {`,
515
- ...topLevel.map((l) => ` ${l}`),
516
- ` await runApp();`,
517
- `})();`
518
- ].join("\n");
519
- } else {
520
- startupBody = topLevel.length > 0 ? [...topLevel, `runApp();`].join("\n") : `runApp();`;
521
- }
522
- return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/core \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539
523
- // Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
524
-
525
- ${imports.join("\n")}
526
-
527
- ${startupBody}
528
- `;
529
- }
530
-
531
- // src/generator/templates/bootstrap.ts
532
- function generateBootstrap(config, injections = {}) {
533
- const { appName, router } = config;
534
- const routerImport = router.conventional ? `import { createRouter } from "./routes";` : `import { createRouter } from "@/routes";`;
535
- const imports = [
536
- `import { bootstrap, type AppConfig } from "@4399ywkf/core/runtime";`,
537
- routerImport,
699
+ `import { bootstrap, type AppConfig } from "@4399ywkf/core/runtime";`,
700
+ routerImport,
538
701
  ...injections.imports || []
539
702
  ];
540
703
  const topLevel = injections.topLevel || [];
@@ -616,8 +779,50 @@ export async function runApp(): Promise<void> {
616
779
  const userConfig = await getUserConfig();
617
780
  await bootstrap(createAppConfig(userConfig));
618
781
  }
619
- ${topLevel.length > 0 ? "\n" + topLevel.join("\n") : ""}
620
- ${exports.length > 0 ? "\n" + exports.join("\n") : ""}
782
+ ${topLevel.length > 0 ? `
783
+ ${topLevel.join("\n")}` : ""}
784
+ ${exports.length > 0 ? `
785
+ ${exports.join("\n")}` : ""}
786
+ `;
787
+ }
788
+
789
+ // src/generator/templates/entry.ts
790
+ function generateEntry(_config, injections = {}) {
791
+ const imports = [
792
+ `import "@/index.css";`,
793
+ `import { runApp } from "./bootstrap";`,
794
+ ...injections.imports || []
795
+ ];
796
+ const topLevel = injections.topLevel || [];
797
+ const exports = injections.exports || [];
798
+ const hasPluginExports = exports.length > 0;
799
+ const hasAsyncTopLevel = topLevel.some((line) => line.includes("await "));
800
+ let startupBody;
801
+ if (hasPluginExports) {
802
+ startupBody = [
803
+ ...topLevel,
804
+ ...exports,
805
+ ``,
806
+ `if (shouldRunIndependently !== false) {`,
807
+ ` runApp();`,
808
+ `}`
809
+ ].join("\n");
810
+ } else if (hasAsyncTopLevel) {
811
+ startupBody = [
812
+ `(async () => {`,
813
+ ...topLevel.map((l) => ` ${l}`),
814
+ ` await runApp();`,
815
+ `})();`
816
+ ].join("\n");
817
+ } else {
818
+ startupBody = topLevel.length > 0 ? [...topLevel, `runApp();`].join("\n") : `runApp();`;
819
+ }
820
+ return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/core \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539
821
+ // Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
822
+
823
+ ${imports.join("\n")}
824
+
825
+ ${startupBody}
621
826
  `;
622
827
  }
623
828
 
@@ -781,17 +986,14 @@ var YwkfGenerator = class {
781
986
  const { outputDir, config } = this.context;
782
987
  const configPath = join3(outputDir, "config.json");
783
988
  const serializableConfig = JSON.parse(
784
- JSON.stringify(config, (key, value) => {
989
+ JSON.stringify(config, (_key, value) => {
785
990
  if (typeof value === "function") {
786
991
  return "[Function]";
787
992
  }
788
993
  return value;
789
994
  })
790
995
  );
791
- this.writeFileIfChanged(
792
- configPath,
793
- JSON.stringify(serializableConfig, null, 2)
794
- );
996
+ this.writeFileIfChanged(configPath, JSON.stringify(serializableConfig, null, 2));
795
997
  }
796
998
  /**
797
999
  * 生成约定式路由
@@ -895,16 +1097,10 @@ var YwkfGenerator = class {
895
1097
  generateTypes() {
896
1098
  const { outputDir, config, cwd } = this.context;
897
1099
  const envTypesContent = generateEnvTypes(cwd, config);
898
- this.writeFileIfChanged(
899
- join3(outputDir, "types", "env.d.ts"),
900
- envTypesContent
901
- );
1100
+ this.writeFileIfChanged(join3(outputDir, "types", "env.d.ts"), envTypesContent);
902
1101
  if (config.router.conventional) {
903
1102
  const routeTypesContent = generateRouteTypes();
904
- this.writeFileIfChanged(
905
- join3(outputDir, "types", "routes.d.ts"),
906
- routeTypesContent
907
- );
1103
+ this.writeFileIfChanged(join3(outputDir, "types", "routes.d.ts"), routeTypesContent);
908
1104
  }
909
1105
  }
910
1106
  /**
@@ -1039,7 +1235,7 @@ var YwkfGeneratorPlugin = class {
1039
1235
  }
1040
1236
  apply(compiler) {
1041
1237
  const pluginName = "YwkfGeneratorPlugin";
1042
- compiler.hooks.beforeCompile.tapAsync(pluginName, async (params, callback) => {
1238
+ compiler.hooks.beforeCompile.tapAsync(pluginName, async (_params, callback) => {
1043
1239
  try {
1044
1240
  await this.initialize();
1045
1241
  if (!this.hasGenerated && this.generator) {
@@ -1067,24 +1263,20 @@ var YwkfGeneratorPlugin = class {
1067
1263
  }
1068
1264
  this.isWatching = true;
1069
1265
  let debounceTimer = null;
1070
- const watcher = watch(
1071
- pagesDir,
1072
- { recursive: true },
1073
- (eventType, filename) => {
1074
- if (!filename?.match(/\.(tsx?|jsx?)$/)) {
1075
- return;
1076
- }
1077
- if (debounceTimer) {
1078
- clearTimeout(debounceTimer);
1079
- }
1080
- debounceTimer = setTimeout(async () => {
1081
- if (process.env.DEBUG) console.log(`[ywkf] \u68C0\u6D4B\u5230\u9875\u9762\u53D8\u5316: ${filename}`);
1082
- if (this.generator) {
1083
- await this.generator.generate();
1084
- }
1085
- }, 500);
1266
+ const watcher = watch(pagesDir, { recursive: true }, (_eventType, filename) => {
1267
+ if (!filename?.match(/\.(tsx?|jsx?)$/)) {
1268
+ return;
1086
1269
  }
1087
- );
1270
+ if (debounceTimer) {
1271
+ clearTimeout(debounceTimer);
1272
+ }
1273
+ debounceTimer = setTimeout(async () => {
1274
+ if (process.env.DEBUG) console.log(`[ywkf] \u68C0\u6D4B\u5230\u9875\u9762\u53D8\u5316: ${filename}`);
1275
+ if (this.generator) {
1276
+ await this.generator.generate();
1277
+ }
1278
+ }, 500);
1279
+ });
1088
1280
  process.on("exit", () => {
1089
1281
  watcher.close();
1090
1282
  });
@@ -1115,8 +1307,10 @@ var YwkfGeneratorPlugin = class {
1115
1307
  this.resetState();
1116
1308
  await this.initialize();
1117
1309
  await this.regenerate();
1118
- console.log(` [ywkf] .ywkf \u76EE\u5F55\u5DF2\u91CD\u65B0\u751F\u6210\u3002\u90E8\u5206\u914D\u7F6E\u53D8\u66F4\uFF08\u5982\u7AEF\u53E3\u3001\u4EE3\u7406\uFF09\u9700\u91CD\u542F dev server \u751F\u6548\u3002
1119
- `);
1310
+ console.log(
1311
+ ` [ywkf] .ywkf \u76EE\u5F55\u5DF2\u91CD\u65B0\u751F\u6210\u3002\u90E8\u5206\u914D\u7F6E\u53D8\u66F4\uFF08\u5982\u7AEF\u53E3\u3001\u4EE3\u7406\uFF09\u9700\u91CD\u542F dev server \u751F\u6548\u3002
1312
+ `
1313
+ );
1120
1314
  } catch (error) {
1121
1315
  console.error(` [ywkf] \u914D\u7F6E\u91CD\u8F7D\u5931\u8D25:`, error);
1122
1316
  }
@@ -1135,15 +1329,7 @@ var coreNodeModules = join5(__dirname, "../../node_modules");
1135
1329
  function createBaseConfig(config, cwd, options = {}) {
1136
1330
  const isDev = options.isDev ?? process.env.NODE_ENV !== "production";
1137
1331
  const { resolveApp } = createPathResolver(cwd);
1138
- const {
1139
- appName,
1140
- appCName,
1141
- output,
1142
- html,
1143
- alias: userAlias,
1144
- microFrontend,
1145
- router
1146
- } = config;
1332
+ const { appName, appCName, output, html, alias: userAlias, microFrontend, router } = config;
1147
1333
  const ywkfOutputDir = resolveApp(".ywkf");
1148
1334
  const defaultAlias = {
1149
1335
  "@": resolveApp("src"),
@@ -1319,7 +1505,7 @@ function createBaseConfig(config, cwd, options = {}) {
1319
1505
  }
1320
1506
 
1321
1507
  // src/rspack/dev.ts
1322
- var require3 = createRequire2(import.meta.url);
1508
+ var _require = createRequire2(import.meta.url);
1323
1509
  function convertProxyToArray(proxy) {
1324
1510
  if (!proxy || Object.keys(proxy).length === 0) {
1325
1511
  return void 0;
@@ -1350,10 +1536,7 @@ function createDevConfig(config, cwd) {
1350
1536
  // Less - antd
1351
1537
  {
1352
1538
  test: /\.less$/,
1353
- include: [
1354
- /[\\/]node_modules[\\/].*antd/,
1355
- /[\\/]node_modules[\\/]@4399ywkf[\\/]design/
1356
- ],
1539
+ include: [/[\\/]node_modules[\\/].*antd/, /[\\/]node_modules[\\/]@4399ywkf[\\/]design/],
1357
1540
  use: [
1358
1541
  { loader: "style-loader" },
1359
1542
  {
@@ -1526,9 +1709,7 @@ function createDevConfig(config, cwd) {
1526
1709
  clean: false
1527
1710
  },
1528
1711
  stats: "errors-only",
1529
- plugins: [
1530
- new ReactRefreshPlugin()
1531
- ],
1712
+ plugins: [new ReactRefreshPlugin()],
1532
1713
  devServer: {
1533
1714
  host: dev2.host,
1534
1715
  port: dev2.port,
@@ -1559,10 +1740,7 @@ function createProdConfig(config, cwd) {
1559
1740
  // Less - antd
1560
1741
  {
1561
1742
  test: /\.less$/,
1562
- include: [
1563
- /[\\/]node_modules[\\/].*antd/,
1564
- /[\\/]node_modules[\\/]@4399ywkf[\\/]design/
1565
- ],
1743
+ include: [/[\\/]node_modules[\\/].*antd/, /[\\/]node_modules[\\/]@4399ywkf[\\/]design/],
1566
1744
  use: [
1567
1745
  { loader: rspack2.CssExtractRspackPlugin.loader },
1568
1746
  {
@@ -1778,10 +1956,7 @@ function createRspackConfig(config, cwd, options = {}) {
1778
1956
  const isDev = options.isDev ?? process.env.NODE_ENV !== "production";
1779
1957
  let rspackConfig = isDev ? createDevConfig(config, cwd) : createProdConfig(config, cwd);
1780
1958
  if (config.performance.rsdoctor) {
1781
- rspackConfig.plugins = [
1782
- ...rspackConfig.plugins || [],
1783
- new RsdoctorRspackPlugin({})
1784
- ];
1959
+ rspackConfig.plugins = [...rspackConfig.plugins || [], new RsdoctorRspackPlugin({})];
1785
1960
  }
1786
1961
  if (config.tools.rspack) {
1787
1962
  const userConfig = config.tools.rspack(rspackConfig, {
@@ -1795,477 +1970,484 @@ function createRspackConfig(config, cwd, options = {}) {
1795
1970
  return rspackConfig;
1796
1971
  }
1797
1972
 
1798
- // src/router/plugin.ts
1799
- import { join as join6 } from "path";
1800
- import { watch as watch2, existsSync as existsSync6 } from "fs";
1801
- var ConventionalRoutePlugin = class {
1802
- options;
1803
- isWatching = false;
1804
- hasGenerated = false;
1805
- lastGeneratedContent = "";
1806
- constructor(options) {
1807
- this.options = options;
1808
- }
1809
- apply(compiler) {
1810
- const pluginName = "ConventionalRoutePlugin";
1811
- compiler.hooks.beforeCompile.tapAsync(pluginName, (params, callback) => {
1812
- if (!this.hasGenerated) {
1813
- this.generateRoutes();
1814
- this.hasGenerated = true;
1815
- }
1816
- callback();
1817
- });
1818
- if (this.options.watch && !this.isWatching) {
1819
- this.watchPages();
1973
+ // src/cli/env.ts
1974
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
1975
+ import { resolve as resolve2 } from "path";
1976
+ import dotenv from "dotenv";
1977
+ function preloadEnv(cwd, mode, nodeEnv) {
1978
+ process.env.NODE_ENV = nodeEnv;
1979
+ process.env.APP_MODE = mode;
1980
+ const defaultPaths = [
1981
+ resolve2(cwd, "config/env/.env.public"),
1982
+ resolve2(cwd, ".env.public"),
1983
+ resolve2(cwd, ".env")
1984
+ ];
1985
+ for (const envPath of defaultPaths) {
1986
+ if (existsSync6(envPath)) {
1987
+ dotenv.config({ path: envPath });
1988
+ break;
1820
1989
  }
1821
1990
  }
1822
- /**
1823
- * 生成路由文件(带内容比对,避免不必要的重复生成)
1824
- */
1825
- generateRoutes() {
1826
- try {
1827
- const generator = new ConventionalRouteGenerator({
1828
- pagesDir: this.options.pagesDir,
1829
- outputDir: this.options.outputDir,
1830
- basename: this.options.basename
1831
- });
1832
- const newContent = generator.generateCode();
1833
- const outputPath = join6(this.options.outputDir, "routes.tsx");
1834
- const normalizedNew = this.normalizeContent(newContent);
1835
- const normalizedOld = this.normalizeContent(this.lastGeneratedContent);
1836
- if (normalizedNew === normalizedOld) {
1837
- return;
1991
+ const modeEnvPaths = [resolve2(cwd, `config/env/.env.${mode}`), resolve2(cwd, `.env.${mode}`)];
1992
+ for (const envPath of modeEnvPaths) {
1993
+ if (existsSync6(envPath)) {
1994
+ const envContent = readFileSync3(envPath, "utf-8");
1995
+ const parsed = dotenv.parse(envContent);
1996
+ for (const key in parsed) {
1997
+ process.env[key] = parsed[key];
1838
1998
  }
1839
- generator.write();
1840
- this.lastGeneratedContent = newContent;
1841
- } catch (error) {
1842
- console.error("[ywkf] \u751F\u6210\u8DEF\u7531\u5931\u8D25:", error);
1999
+ break;
1843
2000
  }
1844
2001
  }
1845
- /**
1846
- * 标准化内容(去掉时间戳等动态部分)
1847
- */
1848
- normalizeContent(content) {
1849
- return content.replace(/\/\/ Generated at: .+/g, "");
2002
+ process.env.NODE_ENV = nodeEnv;
2003
+ process.env.APP_MODE = mode;
2004
+ }
2005
+ function loadEnv(config, cwd, mode, nodeEnv) {
2006
+ const { env } = config;
2007
+ const publicEnvPath = resolve2(cwd, env.publicEnvFile ?? "config/env/.env.public");
2008
+ if (existsSync6(publicEnvPath)) {
2009
+ dotenv.config({ path: publicEnvPath });
1850
2010
  }
1851
- /**
1852
- * 监听页面目录变化
1853
- */
1854
- watchPages() {
1855
- if (!existsSync6(this.options.pagesDir)) {
1856
- return;
2011
+ const modeEnvPath = resolve2(cwd, env.envDir ?? "config/env", `.env.${mode}`);
2012
+ if (existsSync6(modeEnvPath)) {
2013
+ const envContent = readFileSync3(modeEnvPath, "utf-8");
2014
+ const parsed = dotenv.parse(envContent);
2015
+ for (const key in parsed) {
2016
+ process.env[key] = parsed[key];
1857
2017
  }
1858
- this.isWatching = true;
1859
- let debounceTimer = null;
1860
- const watcher = watch2(
1861
- this.options.pagesDir,
1862
- { recursive: true },
1863
- (eventType, filename) => {
1864
- if (!filename?.match(/\.(tsx?|jsx?)$/)) {
1865
- return;
1866
- }
1867
- if (debounceTimer) {
1868
- clearTimeout(debounceTimer);
1869
- }
1870
- debounceTimer = setTimeout(() => {
1871
- if (process.env.DEBUG) console.log(`[ywkf] \u68C0\u6D4B\u5230\u9875\u9762\u53D8\u5316: ${filename}`);
1872
- this.generateRoutes();
1873
- }, 500);
1874
- }
1875
- );
1876
- process.on("exit", () => {
1877
- watcher.close();
1878
- });
1879
2018
  }
1880
- };
2019
+ process.env.NODE_ENV = nodeEnv;
2020
+ process.env.APP_MODE = mode;
2021
+ process.env.APP_NAME = process.env.APP_NAME || config.appName;
2022
+ process.env.APP_CNAME = process.env.APP_CNAME || config.appCName;
2023
+ process.env.OUTPUT_PATH = process.env.OUTPUT_PATH || config.output.path;
2024
+ process.env.PUBLIC_PATH = process.env.PUBLIC_PATH || config.output.publicPath;
2025
+ process.env.APP_HOST = process.env.APP_HOST || config.dev.host;
2026
+ process.env.APP_PORT = process.env.APP_PORT || String(config.dev.port);
2027
+ }
1881
2028
 
1882
- // src/runtime/context.tsx
1883
- import { createContext, useContext } from "react";
1884
- import { jsx } from "react/jsx-runtime";
1885
- var defaultContextValue = {
1886
- appName: "app",
1887
- basename: "/",
1888
- isDev: process.env.NODE_ENV === "development",
1889
- env: {}
1890
- };
1891
- var AppContext = createContext(defaultContextValue);
1892
- function AppContextProvider({
1893
- value,
1894
- children
1895
- }) {
1896
- const contextValue = {
1897
- ...defaultContextValue,
1898
- ...value,
1899
- env: {
1900
- ...defaultContextValue.env,
1901
- ...value.env
1902
- }
1903
- };
1904
- return /* @__PURE__ */ jsx(AppContext.Provider, { value: contextValue, children });
2029
+ // src/cli/printer.ts
2030
+ import { createRequire as createRequire3 } from "module";
2031
+ import os from "os";
2032
+ import { dirname as dirname2, join as join6 } from "path";
2033
+ import { fileURLToPath as fileURLToPath2 } from "url";
2034
+ import chalk from "chalk";
2035
+ var __filename = fileURLToPath2(import.meta.url);
2036
+ var __dirname2 = dirname2(__filename);
2037
+ var require3 = createRequire3(import.meta.url);
2038
+ var _version = null;
2039
+ function getFrameworkVersion() {
2040
+ if (_version) return _version;
2041
+ try {
2042
+ const pkgPath = join6(__dirname2, "../../package.json");
2043
+ const pkg = require3(pkgPath);
2044
+ _version = pkg.version || "0.0.0";
2045
+ } catch {
2046
+ _version = "0.0.0";
2047
+ }
2048
+ return _version;
1905
2049
  }
1906
- function useApp() {
1907
- const context = useContext(AppContext);
1908
- if (!context) {
1909
- throw new Error("useApp must be used within AppContextProvider");
2050
+ function getNetworkAddress(port) {
2051
+ const interfaces = os.networkInterfaces();
2052
+ for (const entries of Object.values(interfaces)) {
2053
+ if (!entries) continue;
2054
+ for (const entry of entries) {
2055
+ if (entry.family === "IPv4" && !entry.internal) {
2056
+ return `http://${entry.address}:${port}/`;
2057
+ }
2058
+ }
1910
2059
  }
1911
- return context;
2060
+ return null;
1912
2061
  }
1913
- function useAppName() {
1914
- return useApp().appName;
2062
+ var BAR_WIDTH = 30;
2063
+ function renderBar(percent) {
2064
+ const filled = Math.round(BAR_WIDTH * percent);
2065
+ const empty = BAR_WIDTH - filled;
2066
+ const bar = chalk.green("\u2501".repeat(filled)) + chalk.gray("\u2501".repeat(empty));
2067
+ return bar;
1915
2068
  }
1916
- function useBasename() {
1917
- return useApp().basename;
1918
- }
1919
- function useEnv() {
1920
- return useApp().env;
1921
- }
1922
- function useIsDev() {
1923
- return useApp().isDev;
1924
- }
1925
-
1926
- // src/runtime/error-boundary.tsx
1927
- import { Component } from "react";
1928
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1929
- var ErrorBoundary = class extends Component {
1930
- constructor(props) {
1931
- super(props);
1932
- this.state = { hasError: false, error: null };
2069
+ var DevPrinter = class {
2070
+ constructor(_host, port, pluginNames, isBuild = false) {
2071
+ this._host = _host;
2072
+ this.port = port;
2073
+ this.pluginNames = pluginNames;
2074
+ this.isBuild = isBuild;
1933
2075
  }
1934
- static getDerivedStateFromError(error) {
1935
- return { hasError: true, error };
2076
+ startTime = 0;
2077
+ lastProgressLine = "";
2078
+ firstCompileDone = false;
2079
+ /**
2080
+ * 打印框架 banner
2081
+ */
2082
+ printBanner() {
2083
+ const version = getFrameworkVersion();
2084
+ console.log();
2085
+ console.log(` ${chalk.bold.cyan("@4399ywkf/core")} ${chalk.green(`Framework v${version}`)}`);
2086
+ console.log();
1936
2087
  }
1937
- componentDidCatch(error, errorInfo) {
1938
- console.error("[ErrorBoundary] Caught error:", error, errorInfo);
1939
- this.props.onError?.(error, errorInfo);
2088
+ /**
2089
+ * 打印 "start build started..."
2090
+ */
2091
+ printBuildStart() {
2092
+ this.startTime = Date.now();
2093
+ this.compileDone = false;
2094
+ const label = chalk.gray("start");
2095
+ console.log(` ${label} build started...`);
1940
2096
  }
1941
- reset = () => {
1942
- this.setState({ hasError: false, error: null });
1943
- };
1944
- render() {
1945
- const { hasError, error } = this.state;
1946
- const { children, fallback } = this.props;
1947
- if (hasError && error) {
1948
- if (typeof fallback === "function") {
1949
- return fallback(error, this.reset);
2097
+ /**
2098
+ * 更新编译进度条(原地覆写同一行)
2099
+ */
2100
+ updateProgress(percent, message) {
2101
+ const pct = Math.min(Math.round(percent * 100), 100);
2102
+ const bar = renderBar(percent);
2103
+ const status = message || "compiling";
2104
+ const line = ` ${chalk.yellow("\u25CF")} client ${bar} ${chalk.bold(`(${pct}%)`)} ${chalk.gray(status)}`;
2105
+ if (process.stdout.isTTY) {
2106
+ if (this.lastProgressLine) {
2107
+ process.stdout.write("\x1B[1A\x1B[2K");
1950
2108
  }
1951
- if (fallback) {
1952
- return fallback;
2109
+ process.stdout.write(`${line}
2110
+ `);
2111
+ } else if (!this.firstCompileDone && pct === 100) {
2112
+ console.log(line);
2113
+ }
2114
+ this.lastProgressLine = line;
2115
+ }
2116
+ /**
2117
+ * 编译完成
2118
+ */
2119
+ printBuildDone(hasErrors = false) {
2120
+ if (this.firstCompileDone) {
2121
+ if (!hasErrors) {
2122
+ const elapsed2 = ((Date.now() - this.startTime) / 1e3).toFixed(2);
2123
+ console.log(` ${chalk.green("hmr")} update in ${chalk.bold(`${elapsed2} s`)}`);
1953
2124
  }
1954
- return /* @__PURE__ */ jsx2(DefaultErrorFallback, { error, onReset: this.reset });
2125
+ return;
2126
+ }
2127
+ this.firstCompileDone = true;
2128
+ this.lastProgressLine = "";
2129
+ if (hasErrors) return;
2130
+ const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(2);
2131
+ const label = chalk.green("ready");
2132
+ console.log(` ${label} built in ${chalk.bold(`${elapsed} s`)}`);
2133
+ console.log();
2134
+ if (this.isBuild) {
2135
+ this.printPluginList();
2136
+ } else {
2137
+ this.printServerInfo();
1955
2138
  }
1956
- return children;
1957
2139
  }
1958
- };
1959
- function DefaultErrorFallback({ error, onReset }) {
1960
- const isDev = process.env.NODE_ENV === "development";
1961
- return /* @__PURE__ */ jsx2(
1962
- "div",
1963
- {
1964
- style: {
1965
- padding: 24,
1966
- display: "flex",
1967
- flexDirection: "column",
1968
- alignItems: "center",
1969
- justifyContent: "center",
1970
- minHeight: "100vh",
1971
- backgroundColor: "#f5f5f5"
1972
- },
1973
- children: /* @__PURE__ */ jsxs(
1974
- "div",
1975
- {
1976
- style: {
1977
- maxWidth: 600,
1978
- padding: 32,
1979
- backgroundColor: "#fff",
1980
- borderRadius: 8,
1981
- boxShadow: "0 2px 8px rgba(0,0,0,0.1)"
1982
- },
1983
- children: [
1984
- /* @__PURE__ */ jsx2("h1", { style: { color: "#ff4d4f", marginBottom: 16 }, children: "\u9875\u9762\u51FA\u9519\u4E86" }),
1985
- /* @__PURE__ */ jsx2("p", { style: { color: "#666", marginBottom: 16 }, children: "\u62B1\u6B49\uFF0C\u9875\u9762\u9047\u5230\u4E86\u4E00\u4E9B\u95EE\u9898\u3002\u8BF7\u5C1D\u8BD5\u5237\u65B0\u9875\u9762\u6216\u8054\u7CFB\u7BA1\u7406\u5458\u3002" }),
1986
- isDev && /* @__PURE__ */ jsxs(
1987
- "details",
1988
- {
1989
- style: {
1990
- marginBottom: 16,
1991
- padding: 12,
1992
- backgroundColor: "#fff2f0",
1993
- borderRadius: 4,
1994
- border: "1px solid #ffccc7"
1995
- },
1996
- children: [
1997
- /* @__PURE__ */ jsx2("summary", { style: { cursor: "pointer", fontWeight: "bold" }, children: "\u9519\u8BEF\u8BE6\u60C5\uFF08\u4EC5\u5F00\u53D1\u73AF\u5883\u53EF\u89C1\uFF09" }),
1998
- /* @__PURE__ */ jsxs(
1999
- "pre",
2000
- {
2001
- style: {
2002
- marginTop: 8,
2003
- padding: 8,
2004
- backgroundColor: "#fff",
2005
- borderRadius: 4,
2006
- overflow: "auto",
2007
- fontSize: 12
2008
- },
2009
- children: [
2010
- error.message,
2011
- "\n\n",
2012
- error.stack
2013
- ]
2014
- }
2015
- )
2016
- ]
2017
- }
2018
- ),
2019
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
2020
- /* @__PURE__ */ jsx2(
2021
- "button",
2022
- {
2023
- onClick: onReset,
2024
- style: {
2025
- padding: "8px 16px",
2026
- backgroundColor: "#1890ff",
2027
- color: "#fff",
2028
- border: "none",
2029
- borderRadius: 4,
2030
- cursor: "pointer"
2031
- },
2032
- children: "\u91CD\u8BD5"
2033
- }
2034
- ),
2035
- /* @__PURE__ */ jsx2(
2036
- "button",
2037
- {
2038
- onClick: () => window.location.reload(),
2039
- style: {
2040
- padding: "8px 16px",
2041
- backgroundColor: "#fff",
2042
- color: "#666",
2043
- border: "1px solid #d9d9d9",
2044
- borderRadius: 4,
2045
- cursor: "pointer"
2046
- },
2047
- children: "\u5237\u65B0\u9875\u9762"
2048
- }
2049
- )
2050
- ] })
2051
- ]
2052
- }
2053
- )
2140
+ /**
2141
+ * 标记新一轮编译开始(HMR)
2142
+ */
2143
+ markRebuildStart() {
2144
+ this.startTime = Date.now();
2145
+ this.lastProgressLine = "";
2146
+ }
2147
+ /**
2148
+ * 仅打印插件列表(build 模式使用)
2149
+ */
2150
+ printPluginList() {
2151
+ if (this.pluginNames.length > 0) {
2152
+ console.log(` ${chalk.bold(">")} Plugins:`);
2153
+ for (const name of this.pluginNames) {
2154
+ const shortName = name.replace("@4399ywkf/plugin-", "").replace("@4399ywkf/", "");
2155
+ console.log(` ${chalk.green("\u2713")} ${shortName}`);
2156
+ }
2157
+ console.log();
2054
2158
  }
2055
- );
2056
- }
2057
-
2058
- // src/runtime/providers.tsx
2059
- import {
2060
- StrictMode,
2061
- Suspense
2062
- } from "react";
2063
- import { RouterProvider } from "react-router";
2064
- import { ConfigProvider } from "antd";
2065
- import zhCN from "antd/locale/zh_CN.js";
2066
- import { jsx as jsx3 } from "react/jsx-runtime";
2067
- function DefaultLoading() {
2068
- return /* @__PURE__ */ jsx3(
2069
- "div",
2070
- {
2071
- style: {
2072
- display: "flex",
2073
- alignItems: "center",
2074
- justifyContent: "center",
2075
- height: "100vh",
2076
- fontSize: 16,
2077
- color: "#999"
2078
- },
2079
- children: "\u52A0\u8F7D\u4E2D..."
2159
+ }
2160
+ /**
2161
+ * 打印服务器信息面板(dev 模式使用)
2162
+ */
2163
+ printServerInfo() {
2164
+ const localUrl = `http://localhost:${this.port}/`;
2165
+ const networkUrl = getNetworkAddress(this.port);
2166
+ console.log(` ${chalk.bold(">")} Local: ${chalk.cyan(localUrl)}`);
2167
+ if (networkUrl) {
2168
+ console.log(` ${chalk.bold(">")} Network: ${chalk.cyan(networkUrl)}`);
2080
2169
  }
2081
- );
2082
- }
2083
- function composeProviders(providers) {
2084
- const sortedProviders = [...providers].sort(
2085
- (a, b) => (a.order ?? 100) - (b.order ?? 100)
2086
- );
2087
- return function ComposedProviders({ children }) {
2088
- return sortedProviders.reduceRight((acc, { component: Provider, props }) => {
2089
- return /* @__PURE__ */ jsx3(Provider, { ...props, children: acc });
2090
- }, children);
2091
- };
2092
- }
2093
- function RootProvider({ config, children }) {
2094
- const {
2095
- appName = "app",
2096
- router,
2097
- basename = "/",
2098
- strictMode = true,
2099
- antd = { enabled: true },
2100
- providers = [],
2101
- lifecycle
2102
- } = config;
2103
- const appContextValue = {
2104
- appName,
2105
- basename,
2106
- isDev: process.env.NODE_ENV === "development",
2107
- env: typeof process !== "undefined" ? process.env : {}
2108
- };
2109
- const allProviders = [
2110
- // 应用上下文 Provider(最外层)
2111
- {
2112
- component: AppContextProvider,
2113
- props: { value: appContextValue },
2114
- order: 0
2115
- },
2116
- // Ant Design ConfigProvider
2117
- ...antd.enabled !== false ? [
2118
- {
2119
- component: ConfigProvider,
2120
- props: {
2121
- locale: antd.locale || zhCN,
2122
- theme: antd.theme,
2123
- ...antd.configProvider
2124
- },
2125
- order: 10
2126
- }
2127
- ] : [],
2128
- // 用户自定义 Providers
2129
- ...providers.map((p) => ({ ...p, order: p.order ?? 50 }))
2130
- ];
2131
- const ComposedProviders = composeProviders(allProviders);
2132
- const content = router ? /* @__PURE__ */ jsx3(Suspense, { fallback: /* @__PURE__ */ jsx3(DefaultLoading, {}), children: /* @__PURE__ */ jsx3(RouterProvider, { router }) }) : children;
2133
- const app = /* @__PURE__ */ jsx3(ErrorBoundary, { onError: lifecycle?.onError, children: /* @__PURE__ */ jsx3(ComposedProviders, { children: content }) });
2134
- if (strictMode) {
2135
- return /* @__PURE__ */ jsx3(StrictMode, { children: app });
2170
+ console.log();
2171
+ this.printPluginList();
2172
+ console.log(` ${chalk.bold(">")} press ${chalk.bold("h + enter")} to show shortcuts`);
2173
+ console.log();
2136
2174
  }
2137
- return app;
2175
+ /**
2176
+ * 打印快捷键帮助
2177
+ */
2178
+ printShortcuts() {
2179
+ console.log();
2180
+ console.log(` ${chalk.bold("Shortcuts:")}`);
2181
+ console.log(` ${chalk.bold("o + enter")} open in browser`);
2182
+ console.log(` ${chalk.bold("c + enter")} clear console`);
2183
+ console.log(` ${chalk.bold("q + enter")} quit`);
2184
+ console.log();
2185
+ }
2186
+ };
2187
+ function createProgressHandler(printer) {
2188
+ return (percent, message, ..._details) => {
2189
+ printer.updateProgress(percent, message);
2190
+ };
2138
2191
  }
2139
- function createProvider(component, props, order) {
2140
- return {
2141
- component,
2142
- props,
2143
- order
2192
+ function registerShortcuts(printer, opts) {
2193
+ if (!process.stdin.isTTY) return;
2194
+ process.stdin.setEncoding("utf-8");
2195
+ process.stdin.setRawMode?.(false);
2196
+ let buffer = "";
2197
+ const handler = (data) => {
2198
+ buffer += data;
2199
+ if (!buffer.includes("\n")) return;
2200
+ const cmd = buffer.trim().toLowerCase();
2201
+ buffer = "";
2202
+ switch (cmd) {
2203
+ case "h":
2204
+ printer.printShortcuts();
2205
+ break;
2206
+ case "o": {
2207
+ const url = `http://localhost:${opts.port}/`;
2208
+ import("child_process").then((cp) => {
2209
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2210
+ cp.exec(`${command} ${url}`);
2211
+ });
2212
+ console.log(` ${chalk.green("\u279C")} Opening ${chalk.cyan(url)}...`);
2213
+ break;
2214
+ }
2215
+ case "c":
2216
+ console.clear();
2217
+ break;
2218
+ case "q":
2219
+ opts.onQuit();
2220
+ break;
2221
+ }
2144
2222
  };
2223
+ process.stdin.on("data", handler);
2224
+ process.stdin.resume();
2145
2225
  }
2146
2226
 
2147
- // src/runtime/bootstrap.tsx
2148
- import { createRoot } from "react-dom/client";
2149
- import { jsx as jsx4 } from "react/jsx-runtime";
2150
- var root = null;
2151
- async function resolveAppConfig(config) {
2152
- if (config.router || !config.conventionalRoutes) {
2153
- return config;
2154
- }
2155
- try {
2156
- const routesModule = await import("@ywkf/routes");
2157
- const createRouter = routesModule.createRouter || routesModule.default?.createRouter;
2158
- if (typeof createRouter === "function") {
2159
- const router = createRouter(config.basename);
2160
- console.log("[ywkf] \u5DF2\u81EA\u52A8\u6CE8\u5165\u7EA6\u5B9A\u5F0F\u8DEF\u7531");
2161
- return { ...config, router };
2162
- } else {
2163
- console.warn("[ywkf] @ywkf/routes \u672A\u5BFC\u51FA createRouter \u51FD\u6570");
2227
+ // src/cli/build.ts
2228
+ function formatSize(bytes) {
2229
+ if (bytes < 1024) return `${bytes} B`;
2230
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
2231
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
2232
+ }
2233
+ function printBuildResult(stats) {
2234
+ const info = stats.toJson({
2235
+ assets: true,
2236
+ errors: true,
2237
+ warnings: true
2238
+ });
2239
+ if (info.errors && info.errors.length > 0) {
2240
+ console.log(chalk2.red("\n Compile errors:"));
2241
+ for (const error of info.errors) {
2242
+ console.log(chalk2.red(` ${error.message}`));
2164
2243
  }
2165
- } catch (error) {
2166
- console.error("[ywkf] \u52A0\u8F7D\u7EA6\u5B9A\u5F0F\u8DEF\u7531\u5931\u8D25:", error);
2167
- console.warn("[ywkf] \u8BF7\u786E\u4FDD\u5DF2\u542F\u7528\u7EA6\u5B9A\u5F0F\u8DEF\u7531\u4E14 src/pages \u76EE\u5F55\u5B58\u5728");
2244
+ return;
2168
2245
  }
2169
- return config;
2170
- }
2171
- async function bootstrap(config) {
2172
- const { rootId = "root", lifecycle } = config;
2173
- const resolvedConfig = await resolveAppConfig(config);
2174
- if (lifecycle?.onBeforeMount) {
2175
- await lifecycle.onBeforeMount();
2246
+ if (info.warnings && info.warnings.length > 0) {
2247
+ console.log(chalk2.yellow("\n Warnings:"));
2248
+ for (const warning of info.warnings) {
2249
+ console.log(chalk2.yellow(` ${warning.message}`));
2250
+ }
2176
2251
  }
2177
- const rootElement = document.getElementById(rootId);
2178
- if (!rootElement) {
2179
- throw new Error(`\u627E\u4E0D\u5230\u6839\u5143\u7D20 #${rootId}`);
2252
+ console.log(chalk2.bold("\n Build output:"));
2253
+ console.log();
2254
+ const assets = info.assets || [];
2255
+ const sortedAssets = assets.filter((asset) => !asset.name.endsWith(".map")).sort((a, b) => b.size - a.size);
2256
+ for (const asset of sortedAssets.slice(0, 15)) {
2257
+ const sizeColor = asset.size > 500 * 1024 ? chalk2.yellow : chalk2.green;
2258
+ console.log(` ${chalk2.dim(asset.name.padEnd(50))} ${sizeColor(formatSize(asset.size))}`);
2180
2259
  }
2181
- root = createRoot(rootElement);
2182
- root.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig }));
2183
- if (lifecycle?.onMounted) {
2184
- setTimeout(() => {
2185
- lifecycle.onMounted?.();
2186
- }, 0);
2260
+ if (sortedAssets.length > 15) {
2261
+ console.log(chalk2.dim(` ... and ${sortedAssets.length - 15} more files`));
2187
2262
  }
2188
- if (lifecycle?.onUnmount) {
2189
- window.addEventListener("beforeunload", () => {
2190
- lifecycle.onUnmount?.();
2263
+ console.log();
2264
+ }
2265
+ async function build(options = {}) {
2266
+ const cwd = options.cwd || process.cwd();
2267
+ const mode = options.mode || "production";
2268
+ try {
2269
+ preloadEnv(cwd, mode, "production");
2270
+ const { config } = await resolveConfig(cwd);
2271
+ loadEnv(config, cwd, mode, "production");
2272
+ const pluginManager = new PluginManager(config, cwd, false);
2273
+ if (config.plugins && config.plugins.length > 0) {
2274
+ await pluginManager.loadPlugins(config.plugins);
2275
+ }
2276
+ await pluginManager.runBeforeBuild();
2277
+ let rspackConfig = createRspackConfig(config, cwd, { isDev: false });
2278
+ rspackConfig = await pluginManager.applyRspackConfigHooks(rspackConfig);
2279
+ const pluginNames = pluginManager.getPluginNames();
2280
+ const printer = new DevPrinter("localhost", 0, pluginNames, true);
2281
+ printer.printBanner();
2282
+ printer.printBuildStart();
2283
+ printer.updateProgress(0, "preparing");
2284
+ rspackConfig.plugins = rspackConfig.plugins || [];
2285
+ rspackConfig.plugins.push(new rspack3.ProgressPlugin(createProgressHandler(printer)));
2286
+ rspackConfig.stats = "none";
2287
+ rspackConfig.infrastructureLogging = { level: "none" };
2288
+ const compiler = rspack3(rspackConfig);
2289
+ const stats = await new Promise((resolve4, reject) => {
2290
+ compiler.run((err, stats2) => {
2291
+ if (err) {
2292
+ reject(err);
2293
+ return;
2294
+ }
2295
+ if (!stats2) {
2296
+ reject(new Error("Build failed: no stats available"));
2297
+ return;
2298
+ }
2299
+ resolve4(stats2);
2300
+ });
2301
+ });
2302
+ await new Promise((resolve4, reject) => {
2303
+ compiler.close((err) => {
2304
+ if (err) reject(err);
2305
+ else resolve4();
2306
+ });
2307
+ });
2308
+ const hasErrors = stats.hasErrors();
2309
+ const statsInfo = stats.toJson({ errors: true });
2310
+ await pluginManager.runAfterBuild({
2311
+ success: !hasErrors,
2312
+ errors: statsInfo.errors?.map((e) => e.message)
2191
2313
  });
2314
+ printer.printBuildDone(hasErrors);
2315
+ if (hasErrors) {
2316
+ printBuildResult(stats);
2317
+ process.exit(1);
2318
+ }
2319
+ printBuildResult(stats);
2320
+ } catch (error) {
2321
+ console.error();
2322
+ console.error(chalk2.red(" \u2716 Build failed"));
2323
+ console.error(error);
2324
+ process.exit(1);
2192
2325
  }
2193
2326
  }
2194
- function unmount() {
2195
- if (root) {
2196
- root.unmount();
2197
- root = null;
2327
+
2328
+ // src/cli/dev.ts
2329
+ import { rspack as rspack4 } from "@rspack/core";
2330
+ import { RspackDevServer } from "@rspack/dev-server";
2331
+ import chalk3 from "chalk";
2332
+
2333
+ // src/cli/port.ts
2334
+ import net from "net";
2335
+ function isPortAvailable(port, host) {
2336
+ return new Promise((resolve4) => {
2337
+ const server = net.createServer();
2338
+ server.once("error", (err) => {
2339
+ if (err.code === "EADDRINUSE") {
2340
+ resolve4(false);
2341
+ } else {
2342
+ resolve4(false);
2343
+ }
2344
+ });
2345
+ server.once("listening", () => {
2346
+ server.close(() => resolve4(true));
2347
+ });
2348
+ server.listen(port, host);
2349
+ });
2350
+ }
2351
+ async function getAvailablePort(preferredPort, host) {
2352
+ const MAX_ATTEMPTS = 20;
2353
+ for (let i = 0; i < MAX_ATTEMPTS; i++) {
2354
+ const port = preferredPort + i;
2355
+ const available = await isPortAvailable(port, host);
2356
+ if (available) return port;
2198
2357
  }
2358
+ throw new Error(
2359
+ `No available port found in range ${preferredPort}-${preferredPort + MAX_ATTEMPTS - 1}`
2360
+ );
2199
2361
  }
2200
- function createMicroApp(getConfig) {
2201
- let microRoot = null;
2202
- return {
2203
- /**
2204
- * 微前端 bootstrap 生命周期
2205
- */
2206
- async bootstrap() {
2207
- console.log("[MicroApp] bootstrap");
2208
- },
2209
- /**
2210
- * 微前端 mount 生命周期
2211
- */
2212
- async mount(props) {
2213
- console.log("[MicroApp] mount", props);
2214
- const config = getConfig(props);
2215
- const resolvedConfig = await resolveAppConfig(config);
2216
- const container = props?.masterProps?.container;
2217
- const rootId = resolvedConfig.rootId || "root";
2218
- const rootElement = container ? container.querySelector(`#${rootId}`) : document.getElementById(rootId);
2219
- if (!rootElement) {
2220
- throw new Error(`[MicroApp] \u627E\u4E0D\u5230\u6839\u5143\u7D20 #${rootId}`);
2221
- }
2222
- if (resolvedConfig.lifecycle?.onBeforeMount) {
2223
- await resolvedConfig.lifecycle.onBeforeMount();
2224
- }
2225
- microRoot = createRoot(rootElement);
2226
- microRoot.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig }));
2227
- if (resolvedConfig.lifecycle?.onMounted) {
2228
- setTimeout(() => {
2229
- resolvedConfig.lifecycle?.onMounted?.();
2230
- }, 0);
2231
- }
2232
- },
2233
- /**
2234
- * 微前端 unmount 生命周期
2235
- */
2236
- async unmount(props) {
2237
- console.log("[MicroApp] unmount", props);
2238
- const config = getConfig(props);
2239
- if (config.lifecycle?.onUnmount) {
2240
- config.lifecycle.onUnmount();
2362
+
2363
+ // src/cli/dev.ts
2364
+ async function dev(options = {}) {
2365
+ const cwd = options.cwd || process.cwd();
2366
+ const mode = options.mode || "development";
2367
+ try {
2368
+ preloadEnv(cwd, mode, "development");
2369
+ const { config } = await resolveConfig(cwd);
2370
+ loadEnv(config, cwd, mode, "development");
2371
+ const pluginManager = new PluginManager(config, cwd, true);
2372
+ if (config.plugins && config.plugins.length > 0) {
2373
+ await pluginManager.loadPlugins(config.plugins);
2374
+ }
2375
+ await pluginManager.runBeforeDevServer();
2376
+ let rspackConfig = createRspackConfig(config, cwd, { isDev: true });
2377
+ rspackConfig = await pluginManager.applyRspackConfigHooks(rspackConfig);
2378
+ const host = config.dev.host || "localhost";
2379
+ const preferredPort = config.dev.port || 3e3;
2380
+ const port = await getAvailablePort(preferredPort, host);
2381
+ const pluginNames = pluginManager.getPluginNames();
2382
+ if (port !== preferredPort) {
2383
+ console.log(chalk3.yellow(` Port ${preferredPort} is in use, using ${port} instead.
2384
+ `));
2385
+ }
2386
+ const printer = new DevPrinter(host, port, pluginNames);
2387
+ printer.printBanner();
2388
+ printer.printBuildStart();
2389
+ printer.updateProgress(0, "preparing");
2390
+ rspackConfig.plugins = rspackConfig.plugins || [];
2391
+ rspackConfig.plugins.push(new rspack4.ProgressPlugin(createProgressHandler(printer)));
2392
+ rspackConfig.stats = "none";
2393
+ rspackConfig.infrastructureLogging = { level: "none" };
2394
+ if (rspackConfig.devServer) {
2395
+ const ds = rspackConfig.devServer;
2396
+ const existingClient = ds.client ?? {};
2397
+ ds.client = {
2398
+ ...existingClient,
2399
+ logging: "warn",
2400
+ overlay: false,
2401
+ progress: false
2402
+ };
2403
+ }
2404
+ const compiler = rspack4(rspackConfig);
2405
+ compiler.hooks.done.tap("ywkf-dev-printer", (stats) => {
2406
+ const hasErrors = stats.hasErrors();
2407
+ if (hasErrors) {
2408
+ const info = stats.toJson({ errors: true });
2409
+ console.log();
2410
+ console.log(chalk3.red(" Compile error:"));
2411
+ for (const err of info.errors || []) {
2412
+ console.log(chalk3.red(` ${err.message}`));
2413
+ }
2414
+ console.log();
2241
2415
  }
2242
- if (microRoot) {
2243
- microRoot.unmount();
2244
- microRoot = null;
2416
+ printer.printBuildDone(hasErrors);
2417
+ });
2418
+ compiler.hooks.invalid.tap("ywkf-dev-printer", () => {
2419
+ printer.markRebuildStart();
2420
+ printer.updateProgress(0, "rebuilding");
2421
+ });
2422
+ const devServerOptions = {
2423
+ ...rspackConfig.devServer || {},
2424
+ host,
2425
+ port
2426
+ };
2427
+ const server = new RspackDevServer(devServerOptions, compiler);
2428
+ await server.start();
2429
+ await pluginManager.runAfterDevServer({ host, port });
2430
+ registerShortcuts(printer, {
2431
+ port,
2432
+ onQuit: async () => {
2433
+ console.log(chalk3.gray("\n Shutting down...\n"));
2434
+ await server.stop();
2435
+ process.exit(0);
2245
2436
  }
2246
- },
2247
- /**
2248
- * 微前端 update 生命周期(可选)
2249
- */
2250
- async update(props) {
2251
- console.log("[MicroApp] update", props);
2437
+ });
2438
+ const signals = ["SIGINT", "SIGTERM"];
2439
+ for (const signal of signals) {
2440
+ process.on(signal, async () => {
2441
+ await server.stop();
2442
+ process.exit(0);
2443
+ });
2252
2444
  }
2253
- };
2254
- }
2255
- function isMicroAppEnv() {
2256
- if (window.__POWERED_BY_QIANKUN__) {
2257
- return true;
2258
- }
2259
- if (window.__GARFISH__) {
2260
- return true;
2261
- }
2262
- return false;
2263
- }
2264
- function getMicroAppPublicPath() {
2265
- if (window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__) {
2266
- return window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
2445
+ } catch (error) {
2446
+ console.error();
2447
+ console.error(chalk3.red(" \u2716 Dev server failed to start"));
2448
+ console.error(error);
2449
+ process.exit(1);
2267
2450
  }
2268
- return "/";
2269
2451
  }
2270
2452
 
2271
2453
  // src/plugin/define.ts
@@ -2276,250 +2458,200 @@ function createPlugin(factory) {
2276
2458
  return factory;
2277
2459
  }
2278
2460
 
2279
- // src/plugin/manager.ts
2280
- function createLogger(pluginName) {
2281
- const prefix = `[${pluginName}]`;
2282
- const verbose = !!process.env.DEBUG;
2461
+ // src/plugin/builtin/analytics.ts
2462
+ var analyticsPlugin = createPlugin((options = {}) => ({
2463
+ name: "@4399ywkf/plugin-analytics",
2464
+ version: "1.0.0",
2465
+ description: "\u6784\u5EFA\u5206\u6790\u63D2\u4EF6",
2466
+ setup(context) {
2467
+ const {
2468
+ buildAnalysis = true,
2469
+ timing = true,
2470
+ bundleSize = true,
2471
+ sizeWarningThreshold = 500
2472
+ } = options;
2473
+ const { logger, isProd } = context;
2474
+ let buildStartTime;
2475
+ const hooks = {
2476
+ beforeBuild() {
2477
+ if (timing) {
2478
+ buildStartTime = Date.now();
2479
+ logger.info("\u5F00\u59CB\u6784\u5EFA...");
2480
+ }
2481
+ },
2482
+ afterBuild(_context, stats) {
2483
+ if (timing) {
2484
+ const duration = Date.now() - buildStartTime;
2485
+ logger.info(`\u6784\u5EFA\u5B8C\u6210\uFF0C\u8017\u65F6: ${(duration / 1e3).toFixed(2)}s`);
2486
+ }
2487
+ if (!stats.success && stats.errors) {
2488
+ logger.error(`\u6784\u5EFA\u5931\u8D25\uFF0C\u9519\u8BEF\u6570: ${stats.errors.length}`);
2489
+ }
2490
+ },
2491
+ modifyRspackConfig(config) {
2492
+ if (buildAnalysis && isProd) {
2493
+ logger.info("\u6784\u5EFA\u5206\u6790\u5DF2\u542F\u7528");
2494
+ }
2495
+ return config;
2496
+ }
2497
+ };
2498
+ return hooks;
2499
+ }
2500
+ }));
2501
+
2502
+ // src/plugin/builtin/biome.ts
2503
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "fs";
2504
+ import { join as join7 } from "path";
2505
+ var DEFAULT_IGNORE = [".vscode/**/*", "node_modules/**/*", "dist/**/*", ".ywkf/**/*"];
2506
+ var biomePlugin = createPlugin((options = {}) => ({
2507
+ name: "@4399ywkf/plugin-biome",
2508
+ version: "1.0.0",
2509
+ description: "Biome \u4EE3\u7801\u89C4\u8303\u96C6\u6210",
2510
+ setup(context) {
2511
+ const {
2512
+ scaffold = true,
2513
+ indentStyle = "space",
2514
+ quoteStyle = "double",
2515
+ arrowParentheses = "always",
2516
+ lineWidth = 100,
2517
+ organizeImports = true,
2518
+ ignore = [],
2519
+ rules: customRules = {}
2520
+ } = options;
2521
+ const { cwd, logger } = context;
2522
+ if (scaffold) {
2523
+ const biomeConfigPath = join7(cwd, "biome.json");
2524
+ if (!existsSync7(biomeConfigPath)) {
2525
+ const config = buildBiomeConfig({
2526
+ indentStyle,
2527
+ quoteStyle,
2528
+ arrowParentheses,
2529
+ lineWidth,
2530
+ organizeImports,
2531
+ ignore: [...DEFAULT_IGNORE, ...ignore],
2532
+ customRules
2533
+ });
2534
+ writeFileSync3(biomeConfigPath, `${JSON.stringify(config, null, 2)}
2535
+ `, "utf-8");
2536
+ logger.info("\u5DF2\u751F\u6210 biome.json");
2537
+ }
2538
+ }
2539
+ const hooks = {
2540
+ modifyRspackConfig(rspackConfig) {
2541
+ logger.info("Biome \u4EE3\u7801\u89C4\u8303\u5DF2\u542F\u7528");
2542
+ return rspackConfig;
2543
+ }
2544
+ };
2545
+ return hooks;
2546
+ }
2547
+ }));
2548
+ function buildBiomeConfig(params) {
2549
+ const {
2550
+ indentStyle,
2551
+ quoteStyle,
2552
+ arrowParentheses,
2553
+ lineWidth,
2554
+ organizeImports: enableOrganizeImports,
2555
+ ignore,
2556
+ customRules
2557
+ } = params;
2558
+ const defaultRules = {
2559
+ recommended: { "": "true" },
2560
+ style: {
2561
+ useNodejsImportProtocol: "off",
2562
+ noNonNullAssertion: "off",
2563
+ noUnusedTemplateLiteral: "off",
2564
+ noUselessElse: "off",
2565
+ useNumberNamespace: "off"
2566
+ },
2567
+ suspicious: {
2568
+ noExplicitAny: "off",
2569
+ noConfusingVoidType: "off",
2570
+ noImplicitAnyLet: "off",
2571
+ noAssignInExpressions: "off",
2572
+ noPrototypeBuiltins: "off"
2573
+ },
2574
+ complexity: {
2575
+ noForEach: "off",
2576
+ noBannedTypes: "off",
2577
+ useArrowFunction: "off"
2578
+ },
2579
+ correctness: {
2580
+ useExhaustiveDependencies: "off"
2581
+ },
2582
+ a11y: {
2583
+ useAltText: "off",
2584
+ useKeyWithClickEvents: "off",
2585
+ useButtonType: "off",
2586
+ noSvgWithoutTitle: "off"
2587
+ },
2588
+ performance: {
2589
+ noDelete: "off"
2590
+ },
2591
+ security: {
2592
+ noDangerouslySetInnerHtml: "off"
2593
+ }
2594
+ };
2595
+ const mergedRules = { recommended: true };
2596
+ for (const [category, categoryRules] of Object.entries(defaultRules)) {
2597
+ if (category === "recommended") continue;
2598
+ const customCategoryRules = customRules[category] || {};
2599
+ mergedRules[category] = { ...categoryRules, ...customCategoryRules };
2600
+ }
2601
+ const includesPatterns = ["**", ...ignore.map((p) => `!${p}`)];
2283
2602
  return {
2284
- info: (msg) => {
2285
- if (verbose) console.log(`${prefix} ${msg}`);
2603
+ $schema: "https://biomejs.dev/schemas/2.3.2/schema.json",
2604
+ vcs: {
2605
+ enabled: true,
2606
+ defaultBranch: "main",
2607
+ clientKind: "git",
2608
+ useIgnoreFile: true
2286
2609
  },
2287
- warn: (msg) => console.warn(`${prefix} ${msg}`),
2288
- error: (msg) => console.error(`${prefix} ${msg}`),
2289
- debug: (msg) => {
2290
- if (verbose) console.debug(`${prefix} ${msg}`);
2610
+ formatter: {
2611
+ enabled: true,
2612
+ indentStyle
2613
+ },
2614
+ css: {
2615
+ formatter: {
2616
+ quoteStyle: "single"
2617
+ }
2618
+ },
2619
+ javascript: {
2620
+ formatter: {
2621
+ quoteStyle,
2622
+ arrowParentheses,
2623
+ jsxQuoteStyle: "double",
2624
+ lineWidth
2625
+ }
2626
+ },
2627
+ linter: {
2628
+ enabled: true,
2629
+ rules: mergedRules
2630
+ },
2631
+ assist: {
2632
+ actions: {
2633
+ source: {
2634
+ organizeImports: enableOrganizeImports ? "on" : "off"
2635
+ }
2636
+ }
2637
+ },
2638
+ files: {
2639
+ ignoreUnknown: true,
2640
+ includes: includesPatterns
2291
2641
  }
2292
2642
  };
2293
2643
  }
2294
- async function resolvePlugin(pluginConfig, cwd) {
2295
- let plugin;
2296
- let options = {};
2297
- if (typeof pluginConfig === "string") {
2298
- const module = await import(pluginConfig);
2299
- plugin = module.default || module;
2300
- } else if (Array.isArray(pluginConfig)) {
2301
- const [pluginOrName, pluginOptions] = pluginConfig;
2302
- options = pluginOptions;
2303
- if (typeof pluginOrName === "string") {
2304
- const module = await import(pluginOrName);
2305
- plugin = module.default || module;
2306
- } else {
2307
- plugin = pluginOrName;
2308
- }
2309
- } else {
2310
- plugin = pluginConfig;
2311
- }
2312
- if (typeof plugin === "function") {
2313
- plugin = plugin(options);
2314
- }
2315
- return { plugin, options };
2644
+ function getDefaultBiomeConfig() {
2645
+ return buildBiomeConfig({
2646
+ indentStyle: "space",
2647
+ quoteStyle: "double",
2648
+ arrowParentheses: "always",
2649
+ lineWidth: 100,
2650
+ organizeImports: true,
2651
+ ignore: DEFAULT_IGNORE,
2652
+ customRules: {}
2653
+ });
2316
2654
  }
2317
- var PluginManager = class {
2318
- plugins = /* @__PURE__ */ new Map();
2319
- context;
2320
- constructor(config, cwd, isDev) {
2321
- this.context = {
2322
- cwd,
2323
- isDev,
2324
- isProd: !isDev,
2325
- config,
2326
- logger: createLogger("PluginManager")
2327
- };
2328
- }
2329
- /**
2330
- * 加载并初始化所有插件
2331
- */
2332
- async loadPlugins(pluginConfigs) {
2333
- for (const pluginConfig of pluginConfigs) {
2334
- try {
2335
- const { plugin } = await resolvePlugin(pluginConfig, this.context.cwd);
2336
- if (this.plugins.has(plugin.name)) {
2337
- this.context.logger.warn(`\u63D2\u4EF6 ${plugin.name} \u5DF2\u52A0\u8F7D\uFF0C\u8DF3\u8FC7\u91CD\u590D\u52A0\u8F7D`);
2338
- continue;
2339
- }
2340
- const pluginContext = {
2341
- ...this.context,
2342
- logger: createLogger(plugin.name)
2343
- };
2344
- const hooks = await plugin.setup(pluginContext);
2345
- this.plugins.set(plugin.name, { plugin, hooks });
2346
- this.context.logger.info(`\u5DF2\u52A0\u8F7D\u63D2\u4EF6: ${plugin.name}`);
2347
- } catch (error) {
2348
- this.context.logger.error(
2349
- `\u52A0\u8F7D\u63D2\u4EF6\u5931\u8D25: ${String(pluginConfig)} - ${error}`
2350
- );
2351
- }
2352
- }
2353
- }
2354
- /**
2355
- * 执行 modifyRspackConfig 钩子
2356
- */
2357
- async applyRspackConfigHooks(config) {
2358
- let result = config;
2359
- for (const [name, { hooks }] of this.plugins) {
2360
- if (hooks.modifyRspackConfig) {
2361
- try {
2362
- const modified = await hooks.modifyRspackConfig(result, this.context);
2363
- if (modified) {
2364
- result = modified;
2365
- }
2366
- } catch (error) {
2367
- this.context.logger.error(
2368
- `\u63D2\u4EF6 ${name} modifyRspackConfig \u6267\u884C\u5931\u8D25: ${error}`
2369
- );
2370
- }
2371
- }
2372
- }
2373
- return result;
2374
- }
2375
- /**
2376
- * 执行 modifyRoutes 钩子
2377
- */
2378
- async applyRoutesHooks(routes) {
2379
- let result = routes;
2380
- for (const [name, { hooks }] of this.plugins) {
2381
- if (hooks.modifyRoutes) {
2382
- try {
2383
- const modified = await hooks.modifyRoutes(result, this.context);
2384
- if (modified) {
2385
- result = modified;
2386
- }
2387
- } catch (error) {
2388
- this.context.logger.error(
2389
- `\u63D2\u4EF6 ${name} modifyRoutes \u6267\u884C\u5931\u8D25: ${error}`
2390
- );
2391
- }
2392
- }
2393
- }
2394
- return result;
2395
- }
2396
- /**
2397
- * 执行 modifyAppConfig 钩子
2398
- */
2399
- applyAppConfigHooks(appConfig) {
2400
- let result = appConfig;
2401
- for (const [name, { hooks }] of this.plugins) {
2402
- if (hooks.modifyAppConfig) {
2403
- try {
2404
- const modified = hooks.modifyAppConfig(result, this.context);
2405
- if (modified) {
2406
- result = modified;
2407
- }
2408
- } catch (error) {
2409
- this.context.logger.error(
2410
- `\u63D2\u4EF6 ${name} modifyAppConfig \u6267\u884C\u5931\u8D25: ${error}`
2411
- );
2412
- }
2413
- }
2414
- }
2415
- return result;
2416
- }
2417
- /**
2418
- * 收集所有插件的 Provider
2419
- */
2420
- collectProviders() {
2421
- const providers = [];
2422
- for (const [name, { hooks }] of this.plugins) {
2423
- if (hooks.addProvider) {
2424
- try {
2425
- const result = hooks.addProvider(this.context);
2426
- if (Array.isArray(result)) {
2427
- providers.push(...result);
2428
- } else if (result) {
2429
- providers.push(result);
2430
- }
2431
- } catch (error) {
2432
- this.context.logger.error(
2433
- `\u63D2\u4EF6 ${name} addProvider \u6267\u884C\u5931\u8D25: ${error}`
2434
- );
2435
- }
2436
- }
2437
- }
2438
- return providers;
2439
- }
2440
- /**
2441
- * 执行 beforeBuild 钩子
2442
- */
2443
- async runBeforeBuild() {
2444
- for (const [name, { hooks }] of this.plugins) {
2445
- if (hooks.beforeBuild) {
2446
- try {
2447
- await hooks.beforeBuild(this.context);
2448
- } catch (error) {
2449
- this.context.logger.error(
2450
- `\u63D2\u4EF6 ${name} beforeBuild \u6267\u884C\u5931\u8D25: ${error}`
2451
- );
2452
- }
2453
- }
2454
- }
2455
- }
2456
- /**
2457
- * 执行 afterBuild 钩子
2458
- */
2459
- async runAfterBuild(stats) {
2460
- for (const [name, { hooks }] of this.plugins) {
2461
- if (hooks.afterBuild) {
2462
- try {
2463
- await hooks.afterBuild(this.context, stats);
2464
- } catch (error) {
2465
- this.context.logger.error(
2466
- `\u63D2\u4EF6 ${name} afterBuild \u6267\u884C\u5931\u8D25: ${error}`
2467
- );
2468
- }
2469
- }
2470
- }
2471
- }
2472
- /**
2473
- * 执行 beforeDevServer 钩子
2474
- */
2475
- async runBeforeDevServer() {
2476
- for (const [name, { hooks }] of this.plugins) {
2477
- if (hooks.beforeDevServer) {
2478
- try {
2479
- await hooks.beforeDevServer(this.context);
2480
- } catch (error) {
2481
- this.context.logger.error(
2482
- `\u63D2\u4EF6 ${name} beforeDevServer \u6267\u884C\u5931\u8D25: ${error}`
2483
- );
2484
- }
2485
- }
2486
- }
2487
- }
2488
- /**
2489
- * 执行 afterDevServer 钩子
2490
- */
2491
- async runAfterDevServer(server) {
2492
- for (const [name, { hooks }] of this.plugins) {
2493
- if (hooks.afterDevServer) {
2494
- try {
2495
- await hooks.afterDevServer(this.context, server);
2496
- } catch (error) {
2497
- this.context.logger.error(
2498
- `\u63D2\u4EF6 ${name} afterDevServer \u6267\u884C\u5931\u8D25: ${error}`
2499
- );
2500
- }
2501
- }
2502
- }
2503
- }
2504
- /**
2505
- * 获取所有已加载的插件名称
2506
- */
2507
- getPluginNames() {
2508
- return Array.from(this.plugins.keys());
2509
- }
2510
- /**
2511
- * 获取所有已加载的插件
2512
- */
2513
- getPlugins() {
2514
- return Array.from(this.plugins.values()).map((p) => p.plugin);
2515
- }
2516
- /**
2517
- * 获取所有已加载的插件钩子
2518
- */
2519
- getPluginHooks() {
2520
- return Array.from(this.plugins.values()).map((p) => p.hooks);
2521
- }
2522
- };
2523
2655
 
2524
2656
  // src/plugin/builtin/garfish.ts
2525
2657
  var garfishPlugin = createPlugin((options = {}) => ({
@@ -2563,23 +2695,16 @@ var garfishPlugin = createPlugin((options = {}) => ({
2563
2695
  }
2564
2696
  },
2565
2697
  // ===== 代码生成阶段钩子 =====
2566
- injectEntry(ctx) {
2698
+ injectEntry(_ctx) {
2567
2699
  if (master && apps.length > 0) {
2568
2700
  return {
2569
- imports: [
2570
- `import { initGarfishMaster } from "./bootstrap";`
2571
- ],
2572
- topLevel: [
2573
- `// Garfish \u4E3B\u5E94\u7528\uFF1A\u521D\u59CB\u5316\u5FAE\u524D\u7AEF\u5E76\u6CE8\u518C\u5B50\u5E94\u7528`,
2574
- `await initGarfishMaster();`
2575
- ]
2701
+ imports: [`import { initGarfishMaster } from "./bootstrap";`],
2702
+ topLevel: [`// Garfish \u4E3B\u5E94\u7528\uFF1A\u521D\u59CB\u5316\u5FAE\u524D\u7AEF\u5E76\u6CE8\u518C\u5B50\u5E94\u7528`, `await initGarfishMaster();`]
2576
2703
  };
2577
2704
  }
2578
2705
  if (!master) {
2579
2706
  return {
2580
- imports: [
2581
- `import { garfishProvider, isMicroAppEnv } from "./bootstrap";`
2582
- ],
2707
+ imports: [`import { garfishProvider, isMicroAppEnv } from "./bootstrap";`],
2583
2708
  exports: [
2584
2709
  `// Garfish \u5B50\u5E94\u7528\u751F\u547D\u5468\u671F\u5BFC\u51FA\uFF08provider \u51FD\u6570\u683C\u5F0F\uFF09`,
2585
2710
  `export const provider = garfishProvider;`
@@ -2592,7 +2717,7 @@ var garfishPlugin = createPlugin((options = {}) => ({
2592
2717
  }
2593
2718
  return {};
2594
2719
  },
2595
- injectBootstrap(ctx) {
2720
+ injectBootstrap(_ctx) {
2596
2721
  const imports = [];
2597
2722
  const exports = [];
2598
2723
  const topLevel = [];
@@ -2619,12 +2744,9 @@ var garfishPlugin = createPlugin((options = {}) => ({
2619
2744
  }
2620
2745
  return { imports, exports, topLevel };
2621
2746
  },
2622
- modifyBootstrapCode(code, ctx) {
2747
+ modifyBootstrapCode(code, _ctx) {
2623
2748
  if (!master) {
2624
- return code.replace(
2625
- /antd:\s*\{\s*enabled:\s*true/,
2626
- "antd: { enabled: false"
2627
- );
2749
+ return code.replace(/antd:\s*\{\s*enabled:\s*true/, "antd: { enabled: false");
2628
2750
  }
2629
2751
  return code;
2630
2752
  },
@@ -2669,143 +2791,58 @@ export async function initGarfishMaster(): Promise<void> {
2669
2791
  `;
2670
2792
  }
2671
2793
 
2672
- // src/plugin/builtin/tailwind.ts
2673
- import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "fs";
2674
- import { join as join7 } from "path";
2675
- import { rspack as rspack3 } from "@rspack/core";
2676
- var tailwindPlugin = createPlugin((options = {}) => ({
2677
- name: "@4399ywkf/plugin-tailwind",
2794
+ // src/plugin/builtin/i18n.ts
2795
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2796
+ import { join as join8 } from "path";
2797
+ var i18nPlugin = createPlugin((options = {}) => ({
2798
+ name: "@4399ywkf/plugin-i18n",
2678
2799
  version: "1.0.0",
2679
- description: "Tailwind CSS \u96C6\u6210\u63D2\u4EF6",
2800
+ description: "\u56FD\u9645\u5316 (i18next) \u63D2\u4EF6 \u2014 TS-first \u5DE5\u4F5C\u6D41",
2680
2801
  setup(context) {
2681
2802
  const {
2682
- darkModeSelector = '&:where([data-theme="dark"], [data-theme="dark"] *)',
2683
- autoConfig = true,
2684
- extraCSS = ""
2803
+ defaultLocale = "zh-CN",
2804
+ locales = ["zh-CN", "en-US"],
2805
+ localesDir = "locales",
2806
+ sourceDir = "src/locales/default",
2807
+ defaultNS = ["common"],
2808
+ autoScaffold = true
2685
2809
  } = options;
2686
- const { cwd, logger } = context;
2687
- if (autoConfig) {
2688
- ensurePostcssConfig(cwd, logger);
2810
+ const { cwd, logger, isDev } = context;
2811
+ if (autoScaffold) {
2812
+ scaffoldSourceFiles(cwd, sourceDir, defaultNS, logger);
2813
+ scaffoldResourcesFile(cwd, sourceDir, defaultNS, locales, defaultLocale, logger);
2814
+ scaffoldConvertScript(cwd, sourceDir, localesDir, defaultLocale, logger);
2815
+ scaffoldI18nRc(cwd, localesDir, defaultLocale, locales, logger);
2816
+ scaffoldLocaleJsonDirs(cwd, localesDir, locales, defaultLocale, defaultNS, logger);
2689
2817
  }
2690
2818
  const hooks = {
2691
2819
  modifyRspackConfig(rspackConfig) {
2692
- logger.info("Tailwind CSS \u5DF2\u542F\u7528");
2693
- const rules = rspackConfig.module?.rules || [];
2694
- const postcssConfigPath = join7(cwd, "postcss.config.js");
2695
- const isProd = rspackConfig.mode === "production";
2696
- const tailwindRule = {
2697
- test: /tailwind\.css$/,
2698
- use: [
2699
- isProd ? rspack3.CssExtractRspackPlugin.loader : "style-loader",
2700
- "css-loader",
2701
- {
2702
- loader: "postcss-loader",
2703
- options: {
2704
- postcssOptions: {
2705
- config: postcssConfigPath
2706
- }
2707
- }
2708
- }
2709
- ]
2710
- };
2711
- rules.unshift(tailwindRule);
2712
- rspackConfig.module = { ...rspackConfig.module, rules };
2820
+ const aliases = rspackConfig.resolve?.alias || {};
2821
+ if (!aliases["@locales"]) {
2822
+ aliases["@locales"] = join8(cwd, localesDir);
2823
+ }
2824
+ rspackConfig.resolve = { ...rspackConfig.resolve, alias: aliases };
2713
2825
  return rspackConfig;
2714
2826
  },
2715
- generateFiles(ctx) {
2716
- const cssContent = buildTailwindCSS(darkModeSelector, extraCSS);
2827
+ generateFiles(_ctx) {
2717
2828
  return [
2718
2829
  {
2719
- path: "tailwind.css",
2720
- content: cssContent
2830
+ path: "i18n.ts",
2831
+ content: generateI18nCode({
2832
+ defaultLocale,
2833
+ locales,
2834
+ localesDir,
2835
+ sourceDir,
2836
+ defaultNS,
2837
+ isDev
2838
+ })
2721
2839
  }
2722
2840
  ];
2723
2841
  },
2724
- injectEntry(ctx) {
2842
+ injectBootstrap(_ctx) {
2725
2843
  return {
2726
- imports: [
2727
- `import "./tailwind.css";`
2728
- ]
2729
- };
2730
- }
2731
- };
2732
- return hooks;
2733
- }
2734
- }));
2735
- function buildTailwindCSS(darkModeSelector, extraCSS) {
2736
- return `/* \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-tailwind \u81EA\u52A8\u751F\u6210 */
2737
- @import "tailwindcss";
2738
- @variant dark (${darkModeSelector});
2739
- ${extraCSS ? `
2740
- ${extraCSS}` : ""}
2741
- `;
2742
- }
2743
- function ensurePostcssConfig(cwd, logger) {
2744
- const configPath = join7(cwd, "postcss.config.js");
2745
- if (!existsSync7(configPath)) {
2746
- writeFileSync3(
2747
- configPath,
2748
- `module.exports = {
2749
- plugins: {
2750
- "@tailwindcss/postcss": {},
2751
- },
2752
- };
2753
- `,
2754
- "utf-8"
2755
- );
2756
- logger.info("\u5DF2\u81EA\u52A8\u751F\u6210 postcss.config.js");
2757
- }
2758
- }
2759
-
2760
- // src/plugin/builtin/i18n.ts
2761
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2762
- import { join as join8 } from "path";
2763
- var i18nPlugin = createPlugin((options = {}) => ({
2764
- name: "@4399ywkf/plugin-i18n",
2765
- version: "1.0.0",
2766
- description: "\u56FD\u9645\u5316 (i18next) \u63D2\u4EF6 \u2014 TS-first \u5DE5\u4F5C\u6D41",
2767
- setup(context) {
2768
- const {
2769
- defaultLocale = "zh-CN",
2770
- locales = ["zh-CN", "en-US"],
2771
- localesDir = "locales",
2772
- sourceDir = "src/locales/default",
2773
- defaultNS = ["common"],
2774
- autoScaffold = true
2775
- } = options;
2776
- const { cwd, logger, isDev } = context;
2777
- if (autoScaffold) {
2778
- scaffoldSourceFiles(cwd, sourceDir, defaultNS, logger);
2779
- scaffoldResourcesFile(cwd, sourceDir, defaultNS, locales, defaultLocale, logger);
2780
- scaffoldConvertScript(cwd, sourceDir, localesDir, defaultLocale, logger);
2781
- scaffoldI18nRc(cwd, localesDir, defaultLocale, locales, logger);
2782
- scaffoldLocaleJsonDirs(cwd, localesDir, locales, defaultLocale, defaultNS, logger);
2783
- }
2784
- const hooks = {
2785
- modifyRspackConfig(rspackConfig) {
2786
- const aliases = rspackConfig.resolve?.alias || {};
2787
- if (!aliases["@locales"]) {
2788
- aliases["@locales"] = join8(cwd, localesDir);
2789
- }
2790
- rspackConfig.resolve = { ...rspackConfig.resolve, alias: aliases };
2791
- return rspackConfig;
2792
- },
2793
- generateFiles(ctx) {
2794
- return [
2795
- {
2796
- path: "i18n.ts",
2797
- content: generateI18nCode({ defaultLocale, locales, localesDir, sourceDir, defaultNS, isDev })
2798
- }
2799
- ];
2800
- },
2801
- injectBootstrap(ctx) {
2802
- return {
2803
- imports: [
2804
- `import { initI18n } from "./i18n";`
2805
- ],
2806
- topLevel: [
2807
- `await initI18n();`
2808
- ]
2844
+ imports: [`import { initI18n } from "./i18n";`],
2845
+ topLevel: [`await initI18n();`]
2809
2846
  };
2810
2847
  }
2811
2848
  };
@@ -2916,7 +2953,7 @@ function buildDefaultNsFile(ns) {
2916
2953
  } as const;
2917
2954
  `;
2918
2955
  }
2919
- function scaffoldResourcesFile(cwd, sourceDir, namespaces, locales, defaultLocale, logger) {
2956
+ function scaffoldResourcesFile(cwd, sourceDir, _namespaces, locales, defaultLocale, logger) {
2920
2957
  const parentDir = join8(cwd, sourceDir, "..");
2921
2958
  const filePath = join8(parentDir, "resources.ts");
2922
2959
  if (existsSync8(filePath)) return;
@@ -2967,7 +3004,7 @@ function getLocaleLabel(locale) {
2967
3004
  "ru-RU": "\u0420\u0443\u0441\u0441\u043A\u0438\u0439",
2968
3005
  "it-IT": "Italiano",
2969
3006
  "vi-VN": "Ti\u1EBFng Vi\u1EC7t",
2970
- "ar": "\u0627\u0644\u0639\u0631\u0628\u064A\u0629"
3007
+ ar: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629"
2971
3008
  };
2972
3009
  return map[locale] || locale;
2973
3010
  }
@@ -3043,98 +3080,152 @@ function scaffoldLocaleJsonDirs(cwd, localesDir, locales, defaultLocale, namespa
3043
3080
  for (const ns of namespaces) {
3044
3081
  const filePath = join8(localeDir, `${ns}.json`);
3045
3082
  if (!existsSync8(filePath)) {
3046
- const placeholder = ns === "common" ? JSON.stringify({
3047
- welcome: "Welcome",
3048
- loading: "Loading...",
3049
- confirm: "OK",
3050
- cancel: "Cancel",
3051
- save: "Save",
3052
- delete: "Delete",
3053
- edit: "Edit",
3054
- back: "Back"
3055
- }, null, 2) : JSON.stringify({ title: ns }, null, 2);
3056
- writeFileSync4(filePath, placeholder + "\n", "utf-8");
3083
+ const placeholder = ns === "common" ? JSON.stringify(
3084
+ {
3085
+ welcome: "Welcome",
3086
+ loading: "Loading...",
3087
+ confirm: "OK",
3088
+ cancel: "Cancel",
3089
+ save: "Save",
3090
+ delete: "Delete",
3091
+ edit: "Edit",
3092
+ back: "Back"
3093
+ },
3094
+ null,
3095
+ 2
3096
+ ) : JSON.stringify({ title: ns }, null, 2);
3097
+ writeFileSync4(filePath, `${placeholder}
3098
+ `, "utf-8");
3057
3099
  logger.info(`\u5DF2\u751F\u6210\u7FFB\u8BD1\u6587\u4EF6: ${localesDir}/${locale}/${ns}.json`);
3058
3100
  }
3059
3101
  }
3060
3102
  }
3061
3103
  }
3062
3104
 
3063
- // src/plugin/builtin/theme.ts
3064
- import { join as join9 } from "path";
3065
- var themePlugin = createPlugin((options = {}) => ({
3066
- name: "@4399ywkf/plugin-theme",
3067
- version: "3.0.0",
3068
- description: "Lobe-UI \u98CE\u683C\u54CD\u5E94\u5F0F\u4E3B\u9898\u7CFB\u7EDF\u63D2\u4EF6",
3105
+ // src/plugin/builtin/mock.ts
3106
+ import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3107
+ import { join as join9, resolve as resolve3 } from "path";
3108
+ var mockPlugin = createPlugin((options = {}) => ({
3109
+ name: "@4399ywkf/plugin-mock",
3110
+ version: "1.0.0",
3111
+ description: "Mock \u6570\u636E\u63D2\u4EF6",
3112
+ setup(context) {
3113
+ const { mockDir = "mock", enableInProd = false, delay = 0, prefix = "" } = options;
3114
+ const { cwd, isDev, logger } = context;
3115
+ if (!isDev && !enableInProd) {
3116
+ logger.info("\u751F\u4EA7\u73AF\u5883\u5DF2\u7981\u7528 Mock");
3117
+ return {};
3118
+ }
3119
+ const mockPath = resolve3(cwd, mockDir);
3120
+ if (!existsSync9(mockPath)) {
3121
+ logger.warn(`Mock \u76EE\u5F55\u4E0D\u5B58\u5728: ${mockPath}`);
3122
+ return {};
3123
+ }
3124
+ const hooks = {
3125
+ modifyRspackConfig(rspackConfig) {
3126
+ if (isDev && rspackConfig.devServer) {
3127
+ const mockFiles = scanMockFiles(mockPath);
3128
+ logger.info(`\u627E\u5230 ${mockFiles.length} \u4E2A Mock \u6587\u4EF6`);
3129
+ rspackConfig.devServer.setupMiddlewares = (middlewares, _devServer) => {
3130
+ middlewares.unshift({
3131
+ name: "mock-middleware",
3132
+ middleware: createMockMiddleware(mockPath, { delay, prefix, logger })
3133
+ });
3134
+ return middlewares;
3135
+ };
3136
+ }
3137
+ return rspackConfig;
3138
+ },
3139
+ beforeDevServer() {
3140
+ logger.info(`Mock \u5DF2\u542F\u7528\uFF0C\u76EE\u5F55: ${mockPath}`);
3141
+ if (delay > 0) {
3142
+ logger.info(`\u6A21\u62DF\u5EF6\u8FDF: ${delay}ms`);
3143
+ }
3144
+ }
3145
+ };
3146
+ return hooks;
3147
+ }
3148
+ }));
3149
+ function scanMockFiles(dir) {
3150
+ const files = [];
3151
+ if (!existsSync9(dir)) {
3152
+ return files;
3153
+ }
3154
+ const entries = readdirSync2(dir);
3155
+ for (const entry of entries) {
3156
+ const fullPath = join9(dir, entry);
3157
+ const stat = statSync2(fullPath);
3158
+ if (stat.isFile() && /\.(ts|js|mjs)$/.test(entry)) {
3159
+ files.push(fullPath);
3160
+ } else if (stat.isDirectory()) {
3161
+ files.push(...scanMockFiles(fullPath));
3162
+ }
3163
+ }
3164
+ return files;
3165
+ }
3166
+ function createMockMiddleware(mockPath, options) {
3167
+ const { delay } = options;
3168
+ scanMockFiles(mockPath);
3169
+ return async (_req, _res, next) => {
3170
+ if (delay > 0) {
3171
+ await new Promise((resolve4) => setTimeout(resolve4, delay));
3172
+ }
3173
+ next();
3174
+ };
3175
+ }
3176
+
3177
+ // src/plugin/builtin/react-query.ts
3178
+ var reactQueryPlugin = createPlugin((options = {}) => ({
3179
+ name: "@4399ywkf/plugin-react-query",
3180
+ version: "1.0.0",
3181
+ description: "React Query + Axios \u8BF7\u6C42\u5C42\u96C6\u6210",
3069
3182
  setup(context) {
3070
3183
  const {
3071
- darkMode = true,
3072
- defaultAppearance = "light",
3073
- primaryColor,
3074
- neutralColor,
3075
- prefixCls = "ant",
3076
- cssVar = true,
3077
- globalReset = true,
3078
- externalTheme = false
3184
+ staleTime = 5 * 60 * 1e3,
3185
+ gcTime = 10 * 60 * 1e3,
3186
+ retry = 1,
3187
+ devtools = true,
3188
+ baseURL = "",
3189
+ timeout = 15e3
3079
3190
  } = options;
3080
- const { logger } = context;
3081
- logger.info(`\u4E3B\u9898\u6A21\u5F0F: ${defaultAppearance}, \u4E3B\u8272: ${primaryColor ?? "primary(\u9ED8\u8BA4\u9ED1)"}, \u54CD\u5E94\u5F0F: \u2713`);
3191
+ const { logger, isDev } = context;
3082
3192
  const hooks = {
3083
- modifyRspackConfig(config, ctx) {
3084
- const themePath = join9(ctx.cwd, ".ywkf", "theme.tsx");
3085
- const currentAlias = config.resolve?.alias ?? {};
3086
- config.resolve = {
3087
- ...config.resolve,
3088
- alias: {
3089
- ...currentAlias,
3090
- "@ywkf/theme": themePath
3091
- }
3092
- };
3093
- return config;
3094
- },
3095
- generateFiles(ctx) {
3096
- return [
3193
+ generateFiles(_ctx) {
3194
+ logger.info("React Query \u5DF2\u542F\u7528");
3195
+ const files = [
3097
3196
  {
3098
- path: "theme.tsx",
3099
- content: generateThemeProvider({
3100
- darkMode,
3101
- defaultAppearance,
3102
- primaryColor,
3103
- neutralColor,
3104
- prefixCls,
3105
- cssVar,
3106
- globalReset,
3107
- externalTheme
3108
- })
3197
+ path: "query-client.ts",
3198
+ content: buildQueryClientFile({ staleTime, gcTime, retry, devtools, isDev })
3199
+ },
3200
+ {
3201
+ path: "request.ts",
3202
+ content: buildRequestFile({ baseURL, timeout })
3109
3203
  }
3110
3204
  ];
3205
+ return files;
3111
3206
  },
3112
- injectBootstrap(ctx) {
3207
+ injectBootstrap(_ctx) {
3113
3208
  return {
3114
3209
  imports: [
3115
- `import { ThemeWrapper } from "./theme";`
3116
- ]
3210
+ `import { QueryClientProvider } from "@tanstack/react-query";`,
3211
+ `import { queryClient } from "./query-client";`
3212
+ ],
3213
+ topLevel: [`// React Query Provider\uFF08\u7531 @4399ywkf/plugin-react-query \u6CE8\u5165\uFF09`]
3117
3214
  };
3118
3215
  },
3119
3216
  modifyBootstrapCode(code) {
3120
3217
  const providerEntry = ` {
3121
- component: ThemeWrapper as React.ComponentType<{ children: React.ReactNode }>,
3122
- props: {},
3123
- order: 10,
3218
+ component: QueryClientProvider as React.ComponentType<{ children: React.ReactNode }>,
3219
+ props: { client: queryClient },
3220
+ order: 20,
3124
3221
  }`;
3125
3222
  if (code.includes("providers: []")) {
3126
- code = code.replace(
3127
- "providers: []",
3128
- `providers: [
3223
+ code = code.replace("providers: []", `providers: [
3129
3224
  ${providerEntry},
3130
- ]`
3131
- );
3225
+ ]`);
3132
3226
  } else if (code.includes("providers: [")) {
3133
- code = code.replace(
3134
- "providers: [",
3135
- `providers: [
3136
- ${providerEntry},`
3137
- );
3227
+ code = code.replace("providers: [", `providers: [
3228
+ ${providerEntry},`);
3138
3229
  }
3139
3230
  if (!code.includes("import React")) {
3140
3231
  code = code.replace(
@@ -3149,489 +3240,366 @@ import { bootstrap`
3149
3240
  return hooks;
3150
3241
  }
3151
3242
  }));
3152
- function generateThemeProvider(opts) {
3153
- const {
3154
- darkMode,
3155
- defaultAppearance,
3156
- primaryColor,
3157
- neutralColor,
3158
- prefixCls,
3159
- cssVar,
3160
- globalReset,
3161
- externalTheme
3162
- } = opts;
3163
- const sections = [];
3164
- sections.push(`// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-theme v3 \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539`);
3165
- sections.push(buildImports({ globalReset, cssVar }));
3166
- sections.push(TYPES_CODE);
3167
- sections.push(buildStoreCode({ defaultAppearance, primaryColor, neutralColor }));
3168
- sections.push(HOOKS_CODE);
3169
- if (globalReset) sections.push(GLOBAL_RESET_CODE);
3170
- if (cssVar) sections.push(CSS_VAR_SYNC_CODE);
3171
- if (darkMode) sections.push(APPEARANCE_SYNC_CODE);
3172
- if (externalTheme) sections.push(EXTERNAL_THEME_CODE);
3173
- sections.push(buildWrapperCode({ darkMode, cssVar, globalReset, externalTheme, prefixCls }));
3174
- return sections.join("\n");
3175
- }
3176
- function buildImports(opts) {
3177
- const reactImports = [
3178
- "useCallback",
3179
- "useEffect",
3180
- "useMemo",
3181
- ...opts.cssVar ? ["useLayoutEffect", "useRef"] : [],
3182
- "type ReactNode"
3183
- ];
3184
- const antdStyleImports = [
3185
- "ThemeProvider as AntdThemeProvider",
3186
- "type GetAntdTheme",
3187
- ...opts.globalReset ? ["createGlobalStyle", "css"] : []
3188
- ];
3189
- return `
3190
- import React, { ${reactImports.join(", ")} } from "react";
3191
- import { ${antdStyleImports.join(", ")} } from "antd-style";
3192
- import { createWithEqualityFn } from "zustand/traditional";
3193
- import { shallow } from "zustand/shallow";
3194
- import { createThemeConfig, type PrimaryColors, type NeutralColors } from "@4399ywkf/theme-system";`;
3195
- }
3196
- var TYPES_CODE = `
3197
- // \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
3243
+ function buildQueryClientFile(opts) {
3244
+ const { staleTime, gcTime, retry, devtools, isDev } = opts;
3245
+ return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-react-query \u81EA\u52A8\u751F\u6210
3246
+ import { QueryClient } from "@tanstack/react-query";
3198
3247
 
3199
- export type ThemeAppearance = "light" | "dark" | "auto";
3200
-
3201
- export interface ThemeState {
3202
- appearance: ThemeAppearance;
3203
- primaryColor?: PrimaryColors;
3204
- neutralColor?: NeutralColors;
3205
- }
3248
+ /**
3249
+ * \u5168\u5C40 QueryClient \u5B9E\u4F8B
3250
+ *
3251
+ * \u9ED8\u8BA4\u914D\u7F6E\u53EF\u5728 ywkf.config.ts \u7684 reactQueryPlugin \u9009\u9879\u4E2D\u4FEE\u6539\u3002
3252
+ * \u8FD0\u884C\u65F6\u81EA\u5B9A\u4E49\u53EF\u901A\u8FC7 src/app.config.ts \u8986\u76D6\u3002
3253
+ */
3254
+ export const queryClient = new QueryClient({
3255
+ defaultOptions: {
3256
+ queries: {
3257
+ staleTime: ${staleTime},
3258
+ gcTime: ${gcTime},
3259
+ retry: ${typeof retry === "boolean" ? retry : retry},
3260
+ refetchOnWindowFocus: false,
3261
+ },
3262
+ mutations: {
3263
+ retry: false,
3264
+ },
3265
+ },
3266
+ });
3206
3267
 
3207
- export interface ThemeActions {
3208
- setAppearance: (mode: ThemeAppearance) => void;
3209
- setPrimaryColor: (color: PrimaryColors | undefined) => void;
3210
- setNeutralColor: (color: NeutralColors | undefined) => void;
3211
- setTheme: (partial: Partial<ThemeState>) => void;
3268
+ export default queryClient;
3269
+ `;
3212
3270
  }
3271
+ function buildRequestFile(opts) {
3272
+ const { baseURL, timeout } = opts;
3273
+ return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-react-query \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539
3274
+ // \u5982\u9700\u5B9A\u5236\u62E6\u622A\u5668\uFF0C\u8BF7\u7F16\u8F91 src/request.ts
3275
+ import axios from "axios";
3276
+ import qs from "qs";
3277
+ import type { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
3278
+ import type { RequestConfig, Result } from "@4399ywkf/core/runtime";
3213
3279
 
3214
- export type ThemeStore = ThemeState & ThemeActions;
3215
-
3216
- export type { PrimaryColors, NeutralColors };`;
3217
- function buildStoreCode(opts) {
3218
- const primaryLine = opts.primaryColor ? ` primaryColor: "${opts.primaryColor}" as PrimaryColors,` : ` primaryColor: undefined,`;
3219
- const neutralLine = opts.neutralColor ? ` neutralColor: "${opts.neutralColor}" as NeutralColors,` : ` neutralColor: undefined,`;
3220
- return `
3221
- // \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
3280
+ // ============ \u52A0\u8F7D\u7528\u6237\u914D\u7F6E ============
3222
3281
 
3223
- const DEFAULT_THEME: ThemeState = {
3224
- appearance: "${opts.defaultAppearance}",
3225
- ${primaryLine}
3226
- ${neutralLine}
3227
- };
3282
+ let userConfig: RequestConfig = {};
3228
3283
 
3229
- export const useThemeStore = createWithEqualityFn<ThemeStore>()(
3230
- (set) => ({
3231
- ...DEFAULT_THEME,
3232
- setAppearance: (mode) => set({ appearance: mode }),
3233
- setPrimaryColor: (color) => set({ primaryColor: color }),
3234
- setNeutralColor: (color) => set({ neutralColor: color }),
3235
- setTheme: (partial) => set(partial),
3236
- }),
3237
- shallow,
3238
- );`;
3284
+ try {
3285
+ const mod = require("@/request");
3286
+ userConfig = mod.default || mod;
3287
+ } catch {
3288
+ // src/request.ts \u4E0D\u5B58\u5728\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E
3239
3289
  }
3240
- var HOOKS_CODE = `
3241
- // \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
3242
3290
 
3243
- export const useTheme = () =>
3244
- useThemeStore(
3245
- (s) => ({
3246
- appearance: s.appearance,
3247
- primaryColor: s.primaryColor,
3248
- neutralColor: s.neutralColor,
3249
- }),
3250
- shallow,
3251
- );
3291
+ // ============ \u53C2\u6570\u5E8F\u5217\u5316\uFF08GET \u8BF7\u6C42\u4E13\u7528\uFF09============
3252
3292
 
3253
- export const useAppearance = () => useThemeStore((s) => s.appearance);
3254
- export const usePrimaryColor = () => useThemeStore((s) => s.primaryColor);`;
3255
- var GLOBAL_RESET_CODE = `
3256
- // \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
3293
+ /**
3294
+ * GET \u8BF7\u6C42\u53C2\u6570\u5E8F\u5217\u5316\u89C4\u5219\uFF1A
3295
+ * - \u6570\u7EC4\uFF1Arepeat \u6A21\u5F0F\uFF08key=1&key=2\uFF09
3296
+ * - \u5BF9\u8C61\uFF1AJSON.stringify \u540E\u4F5C\u4E3A\u5B57\u7B26\u4E32\u4F20\u9012
3297
+ * - null / undefined\uFF1AskipNulls \u8DF3\u8FC7
3298
+ * - \u57FA\u672C\u7C7B\u578B\uFF1A\u539F\u6837\u4F20\u9012
3299
+ */
3300
+ export function paramsSerializer(params: Record<string, any>): string {
3301
+ const normalized: Record<string, any> = {};
3257
3302
 
3258
- const GlobalReset = createGlobalStyle(({ theme }) => css\`
3259
- :root {
3260
- --font-settings: "cv01", "tnum", "kern";
3261
- --font-variations: "opsz" auto, tabular-nums;
3262
- }
3303
+ Object.keys(params).forEach((key) => {
3304
+ const value = params[key];
3263
3305
 
3264
- *,
3265
- *::before,
3266
- *::after {
3267
- box-sizing: border-box;
3268
- vertical-align: baseline;
3269
- }
3306
+ if (value === null || value === undefined) {
3307
+ normalized[key] = value;
3308
+ } else if (Array.isArray(value)) {
3309
+ normalized[key] = value;
3310
+ } else if (typeof value === "object") {
3311
+ normalized[key] = JSON.stringify(value);
3312
+ } else {
3313
+ normalized[key] = value;
3314
+ }
3315
+ });
3270
3316
 
3271
- * {
3272
- scrollbar-color: \${theme.colorFill} transparent;
3273
- scrollbar-width: thin;
3274
- }
3317
+ return qs.stringify(normalized, { arrayFormat: "repeat", skipNulls: true });
3318
+ }
3275
3319
 
3276
- html {
3277
- overscroll-behavior: none;
3278
- color-scheme: \${theme.isDarkMode ? "dark" : "light"};
3279
- }
3320
+ // ============ \u521B\u5EFA Axios \u5B9E\u4F8B ============
3280
3321
 
3281
- html, body, #root, #app {
3282
- height: 100%;
3283
- margin: 0;
3284
- padding: 0;
3285
- }
3322
+ const instance: AxiosInstance = axios.create({
3323
+ baseURL: userConfig.baseURL ?? ("${baseURL}" || ""),
3324
+ timeout: userConfig.timeout ?? ${timeout},
3325
+ withCredentials: userConfig.withCredentials ?? false,
3326
+ headers: {
3327
+ "Content-Type": "application/json",
3328
+ ...(userConfig.headers || {}),
3329
+ },
3330
+ });
3286
3331
 
3287
- body {
3288
- overflow: hidden auto;
3289
- min-height: 100vh;
3290
- font-family: \${theme.fontFamily};
3291
- font-size: \${theme.fontSize}px;
3292
- font-feature-settings: var(--font-settings);
3293
- font-variation-settings: var(--font-variations);
3294
- line-height: 1;
3295
- color: \${theme.colorTextBase};
3296
- text-size-adjust: none;
3297
- text-rendering: optimizelegibility;
3298
- word-wrap: break-word;
3299
- background-color: \${theme.colorBgLayout};
3300
- -webkit-font-smoothing: antialiased;
3301
- -moz-osx-font-smoothing: grayscale;
3302
- -webkit-overflow-scrolling: touch;
3303
- -webkit-tap-highlight-color: transparent;
3304
- }
3332
+ // ============ \u8BF7\u6C42\u62E6\u622A\u5668 ============
3305
3333
 
3306
- code {
3307
- font-family: \${theme.fontFamilyCode} !important;
3308
- }
3334
+ instance.interceptors.request.use(
3335
+ (config: InternalAxiosRequestConfig) => {
3336
+ // 1. Token \u6CE8\u5165
3337
+ if (userConfig.getToken) {
3338
+ const token = userConfig.getToken();
3339
+ if (token && config.headers) {
3340
+ const prefix = userConfig.tokenPrefix ?? "Bearer";
3341
+ config.headers.Authorization = prefix ? \`\${prefix} \${token}\` : token;
3342
+ }
3343
+ } else {
3344
+ // \u9ED8\u8BA4\uFF1A\u4ECE localStorage \u8BFB\u53D6
3345
+ const token = typeof localStorage !== "undefined"
3346
+ ? localStorage.getItem("token")
3347
+ : null;
3348
+ if (token && config.headers) {
3349
+ config.headers.Authorization = \`Bearer \${token}\`;
3350
+ }
3351
+ }
3309
3352
 
3310
- ::selection {
3311
- color: #000;
3312
- -webkit-text-fill-color: unset !important;
3313
- }
3314
- \`);`;
3315
- var CSS_VAR_SYNC_CODE = `
3316
- // \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
3353
+ // 2. \u7528\u6237\u81EA\u5B9A\u4E49\u8BF7\u6C42\u62E6\u622A
3354
+ if (userConfig.requestInterceptor) {
3355
+ return userConfig.requestInterceptor(config as any) as any;
3356
+ }
3317
3357
 
3318
- const CSS_VAR_PREFIX = "css-var-";
3358
+ return config;
3359
+ },
3360
+ (error: AxiosError) => Promise.reject(error)
3361
+ );
3319
3362
 
3320
- function useCssVarSync(ref: React.RefObject<HTMLDivElement | null>) {
3321
- useLayoutEffect(() => {
3322
- const node = ref.current;
3323
- if (!node) return;
3363
+ // ============ \u54CD\u5E94\u62E6\u622A\u5668 ============
3324
3364
 
3325
- const htmlEl = document.documentElement;
3326
- let currentClasses: string[] = [];
3365
+ instance.interceptors.response.use(
3366
+ (response: AxiosResponse) => {
3367
+ // \u7528\u6237\u81EA\u5B9A\u4E49\u54CD\u5E94\u62E6\u622A
3368
+ if (userConfig.responseInterceptor) {
3369
+ return userConfig.responseInterceptor(response as any);
3370
+ }
3327
3371
 
3328
- const sync = () => {
3329
- for (const cls of currentClasses) htmlEl.classList.remove(cls);
3372
+ // \u9ED8\u8BA4\uFF1A\u76F4\u63A5\u8FD4\u56DE response.data
3373
+ return response.data;
3374
+ },
3375
+ (error: AxiosError) => {
3376
+ // \u7528\u6237\u81EA\u5B9A\u4E49\u9519\u8BEF\u5904\u7406
3377
+ if (userConfig.errorHandler) {
3378
+ return userConfig.errorHandler(error as any);
3379
+ }
3330
3380
 
3331
- const next: string[] = [];
3332
- let el: HTMLElement | null = node;
3333
- while (el && el !== htmlEl) {
3334
- for (const cls of el.classList) {
3335
- if (cls.startsWith(CSS_VAR_PREFIX)) next.push(cls);
3381
+ // \u9ED8\u8BA4\u9519\u8BEF\u5904\u7406
3382
+ const status = error.response?.status;
3383
+
3384
+ switch (status) {
3385
+ case 401:
3386
+ console.warn("[request] \u672A\u6388\u6743\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
3387
+ break;
3388
+ case 403:
3389
+ console.warn("[request] \u65E0\u8BBF\u95EE\u6743\u9650");
3390
+ break;
3391
+ case 500:
3392
+ console.error("[request] \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
3393
+ break;
3394
+ default:
3395
+ if (!error.response) {
3396
+ console.error("[request] \u7F51\u7EDC\u5F02\u5E38\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5");
3336
3397
  }
3337
- el = el.parentElement;
3338
- }
3398
+ }
3339
3399
 
3340
- for (const cls of next) htmlEl.classList.add(cls);
3341
- currentClasses = next;
3342
- };
3400
+ return Promise.reject(error);
3401
+ }
3402
+ );
3343
3403
 
3344
- sync();
3404
+ // ============ \u8BF7\u6C42\u5C01\u88C5\u7C7B ============
3345
3405
 
3346
- const observer = new MutationObserver(sync);
3347
- let el: HTMLElement | null = node;
3348
- while (el && el !== htmlEl) {
3349
- observer.observe(el, { attributeFilter: ["class"] });
3350
- el = el.parentElement;
3351
- }
3352
-
3353
- return () => {
3354
- observer.disconnect();
3355
- for (const cls of currentClasses) htmlEl.classList.remove(cls);
3356
- };
3357
- }, []);
3358
- }`;
3359
- var APPEARANCE_SYNC_CODE = `
3360
- // \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
3361
-
3362
- function useAppearanceSync(appearance: ThemeAppearance) {
3363
- useEffect(() => {
3364
- if (appearance !== "auto") {
3365
- document.documentElement.dataset.theme = appearance;
3366
- return;
3367
- }
3368
-
3369
- const mq = window.matchMedia("(prefers-color-scheme: dark)");
3370
- document.documentElement.dataset.theme = mq.matches ? "dark" : "light";
3371
-
3372
- function handleChange(e: MediaQueryListEvent) {
3373
- document.documentElement.dataset.theme = e.matches ? "dark" : "light";
3374
- }
3406
+ type ExtraConfig = AxiosRequestConfig & { suppressErrorNotification?: boolean };
3375
3407
 
3376
- mq.addEventListener("change", handleChange);
3377
- return () => mq.removeEventListener("change", handleChange);
3378
- }, [appearance]);
3379
- }`;
3380
- var EXTERNAL_THEME_CODE = `
3381
- // \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
3408
+ class Request {
3409
+ constructor(private readonly http: AxiosInstance) {}
3382
3410
 
3383
- function useExternalTheme() {
3384
- useEffect(() => {
3385
- const hostTheme = (window as any).__YWKF_THEME__;
3386
- if (hostTheme && typeof hostTheme === "object") {
3387
- useThemeStore.getState().setTheme(hostTheme);
3388
- }
3411
+ get<T = any>(url: string, params?: Record<string, any>, config?: ExtraConfig): Promise<Result<T>> {
3412
+ return this.http.get(url, {
3413
+ ...config,
3414
+ params,
3415
+ paramsSerializer,
3416
+ });
3417
+ }
3389
3418
 
3390
- const handler = (e: Event) => {
3391
- const detail = (e as CustomEvent<Partial<ThemeState>>).detail;
3392
- if (detail) useThemeStore.getState().setTheme(detail);
3393
- };
3419
+ post<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
3420
+ return this.http.post(url, data, config);
3421
+ }
3394
3422
 
3395
- window.addEventListener("ywkf:theme-change", handler);
3396
- return () => window.removeEventListener("ywkf:theme-change", handler);
3397
- }, []);
3398
- }`;
3399
- function buildWrapperCode(opts) {
3400
- const { darkMode, cssVar, globalReset, externalTheme, prefixCls } = opts;
3401
- const refLine = cssVar ? "\n const containerRef = useRef<HTMLDivElement>(null);" : "";
3402
- const cssVarSyncLine = cssVar ? "\n useCssVarSync(containerRef);" : "";
3403
- const appearanceSyncLine = darkMode ? "\n useAppearanceSync(appearance);" : "";
3404
- const externalThemeLine = externalTheme ? "\n useExternalTheme();" : "";
3405
- const childrenSlot = cssVar ? `<div ref={containerRef} style={{ display: "contents" }}>
3406
- {children}
3407
- </div>` : "{children}";
3408
- return `
3409
- // \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
3423
+ put<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
3424
+ return this.http.put(url, data, config);
3425
+ }
3410
3426
 
3411
- const RUNTIME_PREFIX_CLS = typeof process !== "undefined"
3412
- && process.env?.YWKF_PREFIX_CLS
3413
- || "${prefixCls}";
3427
+ delete<T = any>(url: string, params?: Record<string, any>, config?: ExtraConfig): Promise<Result<T>> {
3428
+ return this.http.delete(url, {
3429
+ ...config,
3430
+ params,
3431
+ paramsSerializer,
3432
+ });
3433
+ }
3414
3434
 
3415
- interface ThemeWrapperProps {
3416
- children: ReactNode;
3435
+ patch<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
3436
+ return this.http.patch(url, data, config);
3437
+ }
3417
3438
  }
3418
3439
 
3419
- export function ThemeWrapper({ children }: ThemeWrapperProps) {${refLine}
3420
- const { appearance, primaryColor, neutralColor } = useThemeStore(
3421
- (s) => ({ appearance: s.appearance, primaryColor: s.primaryColor, neutralColor: s.neutralColor }),
3422
- shallow,
3423
- );${cssVarSyncLine}${appearanceSyncLine}${externalThemeLine}
3424
-
3425
- const resolvedAppearance = useMemo(() => {
3426
- if (appearance !== "auto") return appearance;
3427
- if (typeof window === "undefined") return "light";
3428
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
3429
- }, [appearance]);
3430
-
3431
- const theme = useCallback<GetAntdTheme>(
3432
- (ap) => createThemeConfig({
3433
- appearance: ap as "light" | "dark",
3434
- primaryColor,
3435
- neutralColor,
3436
- }),
3437
- [primaryColor, neutralColor],
3438
- );
3439
-
3440
- return (
3441
- <AntdThemeProvider
3442
- prefixCls={RUNTIME_PREFIX_CLS}
3443
- appearance={resolvedAppearance}
3444
- themeMode={appearance}
3445
- theme={theme}${cssVar ? "\n customToken={{ cssVar: true }}" : ""}
3446
- >
3447
- ${globalReset ? "<GlobalReset />" : ""}
3448
- ${childrenSlot}
3449
- </AntdThemeProvider>
3450
- );
3451
- }
3440
+ // ============ \u5BFC\u51FA ============
3452
3441
 
3453
- export default ThemeWrapper;
3442
+ export const request = new Request(instance);
3443
+ export type { Result };
3444
+ export default request;
3454
3445
  `;
3455
3446
  }
3456
3447
 
3457
- // src/plugin/builtin/mock.ts
3458
- import { resolve as resolve2, join as join10 } from "path";
3459
- import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3460
- var mockPlugin = createPlugin((options = {}) => ({
3461
- name: "@4399ywkf/plugin-mock",
3448
+ // src/plugin/builtin/tailwind.ts
3449
+ import { existsSync as existsSync10, writeFileSync as writeFileSync5 } from "fs";
3450
+ import { join as join10 } from "path";
3451
+ import { rspack as rspack5 } from "@rspack/core";
3452
+ var tailwindPlugin = createPlugin((options = {}) => ({
3453
+ name: "@4399ywkf/plugin-tailwind",
3462
3454
  version: "1.0.0",
3463
- description: "Mock \u6570\u636E\u63D2\u4EF6",
3455
+ description: "Tailwind CSS \u96C6\u6210\u63D2\u4EF6",
3464
3456
  setup(context) {
3465
3457
  const {
3466
- mockDir = "mock",
3467
- enableInProd = false,
3468
- delay = 0,
3469
- prefix = ""
3458
+ darkModeSelector = '&:where([data-theme="dark"], [data-theme="dark"] *)',
3459
+ autoConfig = true,
3460
+ extraCSS = ""
3470
3461
  } = options;
3471
- const { cwd, isDev, logger } = context;
3472
- if (!isDev && !enableInProd) {
3473
- logger.info("\u751F\u4EA7\u73AF\u5883\u5DF2\u7981\u7528 Mock");
3474
- return {};
3475
- }
3476
- const mockPath = resolve2(cwd, mockDir);
3477
- if (!existsSync9(mockPath)) {
3478
- logger.warn(`Mock \u76EE\u5F55\u4E0D\u5B58\u5728: ${mockPath}`);
3479
- return {};
3462
+ const { cwd, logger } = context;
3463
+ if (autoConfig) {
3464
+ ensurePostcssConfig(cwd, logger);
3480
3465
  }
3481
3466
  const hooks = {
3482
3467
  modifyRspackConfig(rspackConfig) {
3483
- if (isDev && rspackConfig.devServer) {
3484
- const mockFiles = scanMockFiles(mockPath);
3485
- logger.info(`\u627E\u5230 ${mockFiles.length} \u4E2A Mock \u6587\u4EF6`);
3486
- rspackConfig.devServer.setupMiddlewares = (middlewares, devServer) => {
3487
- middlewares.unshift({
3488
- name: "mock-middleware",
3489
- middleware: createMockMiddleware(mockPath, { delay, prefix, logger })
3490
- });
3491
- return middlewares;
3492
- };
3493
- }
3468
+ logger.info("Tailwind CSS \u5DF2\u542F\u7528");
3469
+ const rules = rspackConfig.module?.rules || [];
3470
+ const postcssConfigPath = join10(cwd, "postcss.config.js");
3471
+ const isProd = rspackConfig.mode === "production";
3472
+ const tailwindRule = {
3473
+ test: /tailwind\.css$/,
3474
+ use: [
3475
+ isProd ? rspack5.CssExtractRspackPlugin.loader : "style-loader",
3476
+ "css-loader",
3477
+ {
3478
+ loader: "postcss-loader",
3479
+ options: {
3480
+ postcssOptions: {
3481
+ config: postcssConfigPath
3482
+ }
3483
+ }
3484
+ }
3485
+ ]
3486
+ };
3487
+ rules.unshift(tailwindRule);
3488
+ rspackConfig.module = { ...rspackConfig.module, rules };
3494
3489
  return rspackConfig;
3495
3490
  },
3496
- beforeDevServer() {
3497
- logger.info(`Mock \u5DF2\u542F\u7528\uFF0C\u76EE\u5F55: ${mockPath}`);
3498
- if (delay > 0) {
3499
- logger.info(`\u6A21\u62DF\u5EF6\u8FDF: ${delay}ms`);
3500
- }
3491
+ generateFiles(_ctx) {
3492
+ const cssContent = buildTailwindCSS(darkModeSelector, extraCSS);
3493
+ return [
3494
+ {
3495
+ path: "tailwind.css",
3496
+ content: cssContent
3497
+ }
3498
+ ];
3499
+ },
3500
+ injectEntry(_ctx) {
3501
+ return {
3502
+ imports: [`import "./tailwind.css";`]
3503
+ };
3501
3504
  }
3502
3505
  };
3503
3506
  return hooks;
3504
3507
  }
3505
3508
  }));
3506
- function scanMockFiles(dir) {
3507
- const files = [];
3508
- if (!existsSync9(dir)) {
3509
- return files;
3510
- }
3511
- const entries = readdirSync2(dir);
3512
- for (const entry of entries) {
3513
- const fullPath = join10(dir, entry);
3514
- const stat = statSync2(fullPath);
3515
- if (stat.isFile() && /\.(ts|js|mjs)$/.test(entry)) {
3516
- files.push(fullPath);
3517
- } else if (stat.isDirectory()) {
3518
- files.push(...scanMockFiles(fullPath));
3519
- }
3520
- }
3521
- return files;
3509
+ function buildTailwindCSS(darkModeSelector, extraCSS) {
3510
+ return `/* \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-tailwind \u81EA\u52A8\u751F\u6210 */
3511
+ @import "tailwindcss";
3512
+ @variant dark (${darkModeSelector});
3513
+ ${extraCSS ? `
3514
+ ${extraCSS}` : ""}
3515
+ `;
3522
3516
  }
3523
- function createMockMiddleware(mockPath, options) {
3524
- const { delay } = options;
3525
- scanMockFiles(mockPath);
3526
- return async (req, res, next) => {
3527
- if (delay > 0) {
3528
- await new Promise((resolve4) => setTimeout(resolve4, delay));
3529
- }
3530
- next();
3531
- };
3517
+ function ensurePostcssConfig(cwd, logger) {
3518
+ const configPath = join10(cwd, "postcss.config.js");
3519
+ if (!existsSync10(configPath)) {
3520
+ writeFileSync5(
3521
+ configPath,
3522
+ `module.exports = {
3523
+ plugins: {
3524
+ "@tailwindcss/postcss": {},
3525
+ },
3526
+ };
3527
+ `,
3528
+ "utf-8"
3529
+ );
3530
+ logger.info("\u5DF2\u81EA\u52A8\u751F\u6210 postcss.config.js");
3531
+ }
3532
3532
  }
3533
3533
 
3534
- // src/plugin/builtin/analytics.ts
3535
- var analyticsPlugin = createPlugin((options = {}) => ({
3536
- name: "@4399ywkf/plugin-analytics",
3537
- version: "1.0.0",
3538
- description: "\u6784\u5EFA\u5206\u6790\u63D2\u4EF6",
3534
+ // src/plugin/builtin/theme.ts
3535
+ import { join as join11 } from "path";
3536
+ var themePlugin = createPlugin((options = {}) => ({
3537
+ name: "@4399ywkf/plugin-theme",
3538
+ version: "3.0.0",
3539
+ description: "Lobe-UI \u98CE\u683C\u54CD\u5E94\u5F0F\u4E3B\u9898\u7CFB\u7EDF\u63D2\u4EF6",
3539
3540
  setup(context) {
3540
3541
  const {
3541
- buildAnalysis = true,
3542
- timing = true,
3543
- bundleSize = true,
3544
- sizeWarningThreshold = 500
3545
- } = options;
3546
- const { logger, isProd } = context;
3547
- let buildStartTime;
3548
- const hooks = {
3549
- beforeBuild() {
3550
- if (timing) {
3551
- buildStartTime = Date.now();
3552
- logger.info("\u5F00\u59CB\u6784\u5EFA...");
3553
- }
3554
- },
3555
- afterBuild(_context, stats) {
3556
- if (timing) {
3557
- const duration = Date.now() - buildStartTime;
3558
- logger.info(`\u6784\u5EFA\u5B8C\u6210\uFF0C\u8017\u65F6: ${(duration / 1e3).toFixed(2)}s`);
3559
- }
3560
- if (!stats.success && stats.errors) {
3561
- logger.error(`\u6784\u5EFA\u5931\u8D25\uFF0C\u9519\u8BEF\u6570: ${stats.errors.length}`);
3562
- }
3563
- },
3564
- modifyRspackConfig(config) {
3565
- if (buildAnalysis && isProd) {
3566
- logger.info("\u6784\u5EFA\u5206\u6790\u5DF2\u542F\u7528");
3567
- }
3568
- return config;
3569
- }
3570
- };
3571
- return hooks;
3572
- }
3573
- }));
3574
-
3575
- // src/plugin/builtin/react-query.ts
3576
- var reactQueryPlugin = createPlugin((options = {}) => ({
3577
- name: "@4399ywkf/plugin-react-query",
3578
- version: "1.0.0",
3579
- description: "React Query + Axios \u8BF7\u6C42\u5C42\u96C6\u6210",
3580
- setup(context) {
3581
- const {
3582
- staleTime = 5 * 60 * 1e3,
3583
- gcTime = 10 * 60 * 1e3,
3584
- retry = 1,
3585
- devtools = true,
3586
- baseURL = "",
3587
- timeout = 15e3
3542
+ darkMode = true,
3543
+ defaultAppearance = "light",
3544
+ primaryColor,
3545
+ neutralColor,
3546
+ prefixCls = "ant",
3547
+ cssVar = true,
3548
+ globalReset = true,
3549
+ externalTheme = false
3588
3550
  } = options;
3589
- const { logger, isDev } = context;
3551
+ const { logger } = context;
3552
+ logger.info(
3553
+ `\u4E3B\u9898\u6A21\u5F0F: ${defaultAppearance}, \u4E3B\u8272: ${primaryColor ?? "primary(\u9ED8\u8BA4\u9ED1)"}, \u54CD\u5E94\u5F0F: \u2713`
3554
+ );
3590
3555
  const hooks = {
3591
- generateFiles(ctx) {
3592
- logger.info("React Query \u5DF2\u542F\u7528");
3593
- const files = [
3594
- {
3595
- path: "query-client.ts",
3596
- content: buildQueryClientFile({ staleTime, gcTime, retry, devtools, isDev })
3597
- },
3556
+ modifyRspackConfig(config, ctx) {
3557
+ const themePath = join11(ctx.cwd, ".ywkf", "theme.tsx");
3558
+ const currentAlias = config.resolve?.alias ?? {};
3559
+ config.resolve = {
3560
+ ...config.resolve,
3561
+ alias: {
3562
+ ...currentAlias,
3563
+ "@ywkf/theme": themePath
3564
+ }
3565
+ };
3566
+ return config;
3567
+ },
3568
+ generateFiles(_ctx) {
3569
+ return [
3598
3570
  {
3599
- path: "request.ts",
3600
- content: buildRequestFile({ baseURL, timeout })
3571
+ path: "theme.tsx",
3572
+ content: generateThemeProvider({
3573
+ darkMode,
3574
+ defaultAppearance,
3575
+ primaryColor,
3576
+ neutralColor,
3577
+ prefixCls,
3578
+ cssVar,
3579
+ globalReset,
3580
+ externalTheme
3581
+ })
3601
3582
  }
3602
3583
  ];
3603
- return files;
3604
3584
  },
3605
- injectBootstrap(ctx) {
3585
+ injectBootstrap(_ctx) {
3606
3586
  return {
3607
- imports: [
3608
- `import { QueryClientProvider } from "@tanstack/react-query";`,
3609
- `import { queryClient } from "./query-client";`
3610
- ],
3611
- topLevel: [
3612
- `// React Query Provider\uFF08\u7531 @4399ywkf/plugin-react-query \u6CE8\u5165\uFF09`
3613
- ]
3587
+ imports: [`import { ThemeWrapper } from "./theme";`]
3614
3588
  };
3615
3589
  },
3616
3590
  modifyBootstrapCode(code) {
3617
3591
  const providerEntry = ` {
3618
- component: QueryClientProvider as React.ComponentType<{ children: React.ReactNode }>,
3619
- props: { client: queryClient },
3620
- order: 20,
3592
+ component: ThemeWrapper as React.ComponentType<{ children: React.ReactNode }>,
3593
+ props: {},
3594
+ order: 10,
3621
3595
  }`;
3622
3596
  if (code.includes("providers: []")) {
3623
- code = code.replace(
3624
- "providers: []",
3625
- `providers: [
3597
+ code = code.replace("providers: []", `providers: [
3626
3598
  ${providerEntry},
3627
- ]`
3628
- );
3599
+ ]`);
3629
3600
  } else if (code.includes("providers: [")) {
3630
- code = code.replace(
3631
- "providers: [",
3632
- `providers: [
3633
- ${providerEntry},`
3634
- );
3601
+ code = code.replace("providers: [", `providers: [
3602
+ ${providerEntry},`);
3635
3603
  }
3636
3604
  if (!code.includes("import React")) {
3637
3605
  code = code.replace(
@@ -3646,223 +3614,323 @@ import { bootstrap`
3646
3614
  return hooks;
3647
3615
  }
3648
3616
  }));
3649
- function buildQueryClientFile(opts) {
3650
- const { staleTime, gcTime, retry, devtools, isDev } = opts;
3651
- return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-react-query \u81EA\u52A8\u751F\u6210
3652
- import { QueryClient } from "@tanstack/react-query";
3653
-
3654
- /**
3655
- * \u5168\u5C40 QueryClient \u5B9E\u4F8B
3656
- *
3657
- * \u9ED8\u8BA4\u914D\u7F6E\u53EF\u5728 ywkf.config.ts \u7684 reactQueryPlugin \u9009\u9879\u4E2D\u4FEE\u6539\u3002
3658
- * \u8FD0\u884C\u65F6\u81EA\u5B9A\u4E49\u53EF\u901A\u8FC7 src/app.config.ts \u8986\u76D6\u3002
3659
- */
3660
- export const queryClient = new QueryClient({
3661
- defaultOptions: {
3662
- queries: {
3663
- staleTime: ${staleTime},
3664
- gcTime: ${gcTime},
3665
- retry: ${typeof retry === "boolean" ? retry : retry},
3666
- refetchOnWindowFocus: false,
3667
- },
3668
- mutations: {
3669
- retry: false,
3670
- },
3671
- },
3672
- });
3673
-
3674
- export default queryClient;
3675
- `;
3617
+ function generateThemeProvider(opts) {
3618
+ const {
3619
+ darkMode,
3620
+ defaultAppearance,
3621
+ primaryColor,
3622
+ neutralColor,
3623
+ prefixCls,
3624
+ cssVar,
3625
+ globalReset,
3626
+ externalTheme
3627
+ } = opts;
3628
+ const sections = [];
3629
+ sections.push(`// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-theme v3 \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539`);
3630
+ sections.push(buildImports({ globalReset, cssVar }));
3631
+ sections.push(TYPES_CODE);
3632
+ sections.push(buildStoreCode({ defaultAppearance, primaryColor, neutralColor }));
3633
+ sections.push(HOOKS_CODE);
3634
+ if (globalReset) sections.push(GLOBAL_RESET_CODE);
3635
+ if (cssVar) sections.push(CSS_VAR_SYNC_CODE);
3636
+ if (darkMode) sections.push(APPEARANCE_SYNC_CODE);
3637
+ if (externalTheme) sections.push(EXTERNAL_THEME_CODE);
3638
+ sections.push(buildWrapperCode({ darkMode, cssVar, globalReset, externalTheme, prefixCls }));
3639
+ return sections.join("\n");
3676
3640
  }
3677
- function buildRequestFile(opts) {
3678
- const { baseURL, timeout } = opts;
3679
- return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-react-query \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539
3680
- // \u5982\u9700\u5B9A\u5236\u62E6\u622A\u5668\uFF0C\u8BF7\u7F16\u8F91 src/request.ts
3681
- import axios from "axios";
3682
- import qs from "qs";
3683
- import type { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
3684
- import type { RequestConfig, Result } from "@4399ywkf/core/runtime";
3685
-
3686
- // ============ \u52A0\u8F7D\u7528\u6237\u914D\u7F6E ============
3641
+ function buildImports(opts) {
3642
+ const reactImports = [
3643
+ "useCallback",
3644
+ "useEffect",
3645
+ "useMemo",
3646
+ ...opts.cssVar ? ["useLayoutEffect", "useRef"] : [],
3647
+ "type ReactNode"
3648
+ ];
3649
+ const antdStyleImports = [
3650
+ "ThemeProvider as AntdThemeProvider",
3651
+ "type GetAntdTheme",
3652
+ ...opts.globalReset ? ["createGlobalStyle", "css"] : []
3653
+ ];
3654
+ return `
3655
+ import React, { ${reactImports.join(", ")} } from "react";
3656
+ import { ${antdStyleImports.join(", ")} } from "antd-style";
3657
+ import { createWithEqualityFn } from "zustand/traditional";
3658
+ import { shallow } from "zustand/shallow";
3659
+ import { createThemeConfig, type PrimaryColors, type NeutralColors } from "@4399ywkf/theme-system";`;
3660
+ }
3661
+ var TYPES_CODE = `
3662
+ // \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
3687
3663
 
3688
- let userConfig: RequestConfig = {};
3664
+ export type ThemeAppearance = "light" | "dark" | "auto";
3689
3665
 
3690
- try {
3691
- const mod = require("@/request");
3692
- userConfig = mod.default || mod;
3693
- } catch {
3694
- // src/request.ts \u4E0D\u5B58\u5728\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E
3666
+ export interface ThemeState {
3667
+ appearance: ThemeAppearance;
3668
+ primaryColor?: PrimaryColors;
3669
+ neutralColor?: NeutralColors;
3695
3670
  }
3696
3671
 
3697
- // ============ \u53C2\u6570\u5E8F\u5217\u5316\uFF08GET \u8BF7\u6C42\u4E13\u7528\uFF09============
3672
+ export interface ThemeActions {
3673
+ setAppearance: (mode: ThemeAppearance) => void;
3674
+ setPrimaryColor: (color: PrimaryColors | undefined) => void;
3675
+ setNeutralColor: (color: NeutralColors | undefined) => void;
3676
+ setTheme: (partial: Partial<ThemeState>) => void;
3677
+ }
3698
3678
 
3699
- /**
3700
- * GET \u8BF7\u6C42\u53C2\u6570\u5E8F\u5217\u5316\u89C4\u5219\uFF1A
3701
- * - \u6570\u7EC4\uFF1Arepeat \u6A21\u5F0F\uFF08key=1&key=2\uFF09
3702
- * - \u5BF9\u8C61\uFF1AJSON.stringify \u540E\u4F5C\u4E3A\u5B57\u7B26\u4E32\u4F20\u9012
3703
- * - null / undefined\uFF1AskipNulls \u8DF3\u8FC7
3704
- * - \u57FA\u672C\u7C7B\u578B\uFF1A\u539F\u6837\u4F20\u9012
3705
- */
3706
- export function paramsSerializer(params: Record<string, any>): string {
3707
- const normalized: Record<string, any> = {};
3679
+ export type ThemeStore = ThemeState & ThemeActions;
3708
3680
 
3709
- Object.keys(params).forEach((key) => {
3710
- const value = params[key];
3681
+ export type { PrimaryColors, NeutralColors };`;
3682
+ function buildStoreCode(opts) {
3683
+ const primaryLine = opts.primaryColor ? ` primaryColor: "${opts.primaryColor}" as PrimaryColors,` : ` primaryColor: undefined,`;
3684
+ const neutralLine = opts.neutralColor ? ` neutralColor: "${opts.neutralColor}" as NeutralColors,` : ` neutralColor: undefined,`;
3685
+ return `
3686
+ // \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
3711
3687
 
3712
- if (value === null || value === undefined) {
3713
- normalized[key] = value;
3714
- } else if (Array.isArray(value)) {
3715
- normalized[key] = value;
3716
- } else if (typeof value === "object") {
3717
- normalized[key] = JSON.stringify(value);
3718
- } else {
3719
- normalized[key] = value;
3720
- }
3721
- });
3688
+ const DEFAULT_THEME: ThemeState = {
3689
+ appearance: "${opts.defaultAppearance}",
3690
+ ${primaryLine}
3691
+ ${neutralLine}
3692
+ };
3722
3693
 
3723
- return qs.stringify(normalized, { arrayFormat: "repeat", skipNulls: true });
3694
+ export const useThemeStore = createWithEqualityFn<ThemeStore>()(
3695
+ (set) => ({
3696
+ ...DEFAULT_THEME,
3697
+ setAppearance: (mode) => set({ appearance: mode }),
3698
+ setPrimaryColor: (color) => set({ primaryColor: color }),
3699
+ setNeutralColor: (color) => set({ neutralColor: color }),
3700
+ setTheme: (partial) => set(partial),
3701
+ }),
3702
+ shallow,
3703
+ );`;
3724
3704
  }
3705
+ var HOOKS_CODE = `
3706
+ // \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
3725
3707
 
3726
- // ============ \u521B\u5EFA Axios \u5B9E\u4F8B ============
3708
+ export const useTheme = () =>
3709
+ useThemeStore(
3710
+ (s) => ({
3711
+ appearance: s.appearance,
3712
+ primaryColor: s.primaryColor,
3713
+ neutralColor: s.neutralColor,
3714
+ }),
3715
+ shallow,
3716
+ );
3727
3717
 
3728
- const instance: AxiosInstance = axios.create({
3729
- baseURL: userConfig.baseURL ?? ("${baseURL}" || ""),
3730
- timeout: userConfig.timeout ?? ${timeout},
3731
- withCredentials: userConfig.withCredentials ?? false,
3732
- headers: {
3733
- "Content-Type": "application/json",
3734
- ...(userConfig.headers || {}),
3735
- },
3736
- });
3718
+ export const useAppearance = () => useThemeStore((s) => s.appearance);
3719
+ export const usePrimaryColor = () => useThemeStore((s) => s.primaryColor);`;
3720
+ var GLOBAL_RESET_CODE = `
3721
+ // \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
3737
3722
 
3738
- // ============ \u8BF7\u6C42\u62E6\u622A\u5668 ============
3723
+ const GlobalReset = createGlobalStyle(({ theme }) => css\`
3724
+ :root {
3725
+ --font-settings: "cv01", "tnum", "kern";
3726
+ --font-variations: "opsz" auto, tabular-nums;
3727
+ }
3739
3728
 
3740
- instance.interceptors.request.use(
3741
- (config: InternalAxiosRequestConfig) => {
3742
- // 1. Token \u6CE8\u5165
3743
- if (userConfig.getToken) {
3744
- const token = userConfig.getToken();
3745
- if (token && config.headers) {
3746
- const prefix = userConfig.tokenPrefix ?? "Bearer";
3747
- config.headers.Authorization = prefix ? \`\${prefix} \${token}\` : token;
3748
- }
3749
- } else {
3750
- // \u9ED8\u8BA4\uFF1A\u4ECE localStorage \u8BFB\u53D6
3751
- const token = typeof localStorage !== "undefined"
3752
- ? localStorage.getItem("token")
3753
- : null;
3754
- if (token && config.headers) {
3755
- config.headers.Authorization = \`Bearer \${token}\`;
3756
- }
3757
- }
3729
+ *,
3730
+ *::before,
3731
+ *::after {
3732
+ box-sizing: border-box;
3733
+ vertical-align: baseline;
3734
+ }
3758
3735
 
3759
- // 2. \u7528\u6237\u81EA\u5B9A\u4E49\u8BF7\u6C42\u62E6\u622A
3760
- if (userConfig.requestInterceptor) {
3761
- return userConfig.requestInterceptor(config as any) as any;
3762
- }
3736
+ * {
3737
+ scrollbar-color: \${theme.colorFill} transparent;
3738
+ scrollbar-width: thin;
3739
+ }
3763
3740
 
3764
- return config;
3765
- },
3766
- (error: AxiosError) => Promise.reject(error)
3767
- );
3741
+ html {
3742
+ overscroll-behavior: none;
3743
+ color-scheme: \${theme.isDarkMode ? "dark" : "light"};
3744
+ }
3768
3745
 
3769
- // ============ \u54CD\u5E94\u62E6\u622A\u5668 ============
3746
+ html, body, #root, #app {
3747
+ height: 100%;
3748
+ margin: 0;
3749
+ padding: 0;
3750
+ }
3770
3751
 
3771
- instance.interceptors.response.use(
3772
- (response: AxiosResponse) => {
3773
- // \u7528\u6237\u81EA\u5B9A\u4E49\u54CD\u5E94\u62E6\u622A
3774
- if (userConfig.responseInterceptor) {
3775
- return userConfig.responseInterceptor(response as any);
3776
- }
3752
+ body {
3753
+ overflow: hidden auto;
3754
+ min-height: 100vh;
3755
+ font-family: \${theme.fontFamily};
3756
+ font-size: \${theme.fontSize}px;
3757
+ font-feature-settings: var(--font-settings);
3758
+ font-variation-settings: var(--font-variations);
3759
+ line-height: 1;
3760
+ color: \${theme.colorTextBase};
3761
+ text-size-adjust: none;
3762
+ text-rendering: optimizelegibility;
3763
+ word-wrap: break-word;
3764
+ background-color: \${theme.colorBgLayout};
3765
+ -webkit-font-smoothing: antialiased;
3766
+ -moz-osx-font-smoothing: grayscale;
3767
+ -webkit-overflow-scrolling: touch;
3768
+ -webkit-tap-highlight-color: transparent;
3769
+ }
3777
3770
 
3778
- // \u9ED8\u8BA4\uFF1A\u76F4\u63A5\u8FD4\u56DE response.data
3779
- return response.data;
3780
- },
3781
- (error: AxiosError) => {
3782
- // \u7528\u6237\u81EA\u5B9A\u4E49\u9519\u8BEF\u5904\u7406
3783
- if (userConfig.errorHandler) {
3784
- return userConfig.errorHandler(error as any);
3785
- }
3771
+ code {
3772
+ font-family: \${theme.fontFamilyCode} !important;
3773
+ }
3786
3774
 
3787
- // \u9ED8\u8BA4\u9519\u8BEF\u5904\u7406
3788
- const status = error.response?.status;
3775
+ ::selection {
3776
+ color: #000;
3777
+ -webkit-text-fill-color: unset !important;
3778
+ }
3779
+ \`);`;
3780
+ var CSS_VAR_SYNC_CODE = `
3781
+ // \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
3789
3782
 
3790
- switch (status) {
3791
- case 401:
3792
- console.warn("[request] \u672A\u6388\u6743\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
3793
- break;
3794
- case 403:
3795
- console.warn("[request] \u65E0\u8BBF\u95EE\u6743\u9650");
3796
- break;
3797
- case 500:
3798
- console.error("[request] \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
3799
- break;
3800
- default:
3801
- if (!error.response) {
3802
- console.error("[request] \u7F51\u7EDC\u5F02\u5E38\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5");
3783
+ const CSS_VAR_PREFIX = "css-var-";
3784
+
3785
+ function useCssVarSync(ref: React.RefObject<HTMLDivElement | null>) {
3786
+ useLayoutEffect(() => {
3787
+ const node = ref.current;
3788
+ if (!node) return;
3789
+
3790
+ const htmlEl = document.documentElement;
3791
+ let currentClasses: string[] = [];
3792
+
3793
+ const sync = () => {
3794
+ for (const cls of currentClasses) htmlEl.classList.remove(cls);
3795
+
3796
+ const next: string[] = [];
3797
+ let el: HTMLElement | null = node;
3798
+ while (el && el !== htmlEl) {
3799
+ for (const cls of el.classList) {
3800
+ if (cls.startsWith(CSS_VAR_PREFIX)) next.push(cls);
3803
3801
  }
3802
+ el = el.parentElement;
3803
+ }
3804
+
3805
+ for (const cls of next) htmlEl.classList.add(cls);
3806
+ currentClasses = next;
3807
+ };
3808
+
3809
+ sync();
3810
+
3811
+ const observer = new MutationObserver(sync);
3812
+ let el: HTMLElement | null = node;
3813
+ while (el && el !== htmlEl) {
3814
+ observer.observe(el, { attributeFilter: ["class"] });
3815
+ el = el.parentElement;
3804
3816
  }
3805
3817
 
3806
- return Promise.reject(error);
3807
- }
3808
- );
3818
+ return () => {
3819
+ observer.disconnect();
3820
+ for (const cls of currentClasses) htmlEl.classList.remove(cls);
3821
+ };
3822
+ }, []);
3823
+ }`;
3824
+ var APPEARANCE_SYNC_CODE = `
3825
+ // \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
3809
3826
 
3810
- // ============ \u8BF7\u6C42\u5C01\u88C5\u7C7B ============
3827
+ function useAppearanceSync(appearance: ThemeAppearance) {
3828
+ useEffect(() => {
3829
+ if (appearance !== "auto") {
3830
+ document.documentElement.dataset.theme = appearance;
3831
+ return;
3832
+ }
3811
3833
 
3812
- type ExtraConfig = AxiosRequestConfig & { suppressErrorNotification?: boolean };
3834
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
3835
+ document.documentElement.dataset.theme = mq.matches ? "dark" : "light";
3813
3836
 
3814
- class Request {
3815
- constructor(private readonly http: AxiosInstance) {}
3837
+ function handleChange(e: MediaQueryListEvent) {
3838
+ document.documentElement.dataset.theme = e.matches ? "dark" : "light";
3839
+ }
3816
3840
 
3817
- get<T = any>(url: string, params?: Record<string, any>, config?: ExtraConfig): Promise<Result<T>> {
3818
- return this.http.get(url, {
3819
- ...config,
3820
- params,
3821
- paramsSerializer,
3822
- });
3823
- }
3841
+ mq.addEventListener("change", handleChange);
3842
+ return () => mq.removeEventListener("change", handleChange);
3843
+ }, [appearance]);
3844
+ }`;
3845
+ var EXTERNAL_THEME_CODE = `
3846
+ // \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
3824
3847
 
3825
- post<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
3826
- return this.http.post(url, data, config);
3827
- }
3848
+ function useExternalTheme() {
3849
+ useEffect(() => {
3850
+ const hostTheme = (window as any).__YWKF_THEME__;
3851
+ if (hostTheme && typeof hostTheme === "object") {
3852
+ useThemeStore.getState().setTheme(hostTheme);
3853
+ }
3828
3854
 
3829
- put<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
3830
- return this.http.put(url, data, config);
3831
- }
3855
+ const handler = (e: Event) => {
3856
+ const detail = (e as CustomEvent<Partial<ThemeState>>).detail;
3857
+ if (detail) useThemeStore.getState().setTheme(detail);
3858
+ };
3832
3859
 
3833
- delete<T = any>(url: string, params?: Record<string, any>, config?: ExtraConfig): Promise<Result<T>> {
3834
- return this.http.delete(url, {
3835
- ...config,
3836
- params,
3837
- paramsSerializer,
3838
- });
3839
- }
3860
+ window.addEventListener("ywkf:theme-change", handler);
3861
+ return () => window.removeEventListener("ywkf:theme-change", handler);
3862
+ }, []);
3863
+ }`;
3864
+ function buildWrapperCode(opts) {
3865
+ const { darkMode, cssVar, globalReset, externalTheme, prefixCls } = opts;
3866
+ const refLine = cssVar ? "\n const containerRef = useRef<HTMLDivElement>(null);" : "";
3867
+ const cssVarSyncLine = cssVar ? "\n useCssVarSync(containerRef);" : "";
3868
+ const appearanceSyncLine = darkMode ? "\n useAppearanceSync(appearance);" : "";
3869
+ const externalThemeLine = externalTheme ? "\n useExternalTheme();" : "";
3870
+ const childrenSlot = cssVar ? `<div ref={containerRef} style={{ display: "contents" }}>
3871
+ {children}
3872
+ </div>` : "{children}";
3873
+ return `
3874
+ // \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
3840
3875
 
3841
- patch<T = any>(url: string, data?: any, config?: ExtraConfig): Promise<Result<T>> {
3842
- return this.http.patch(url, data, config);
3843
- }
3876
+ const RUNTIME_PREFIX_CLS = typeof process !== "undefined"
3877
+ && process.env?.YWKF_PREFIX_CLS
3878
+ || "${prefixCls}";
3879
+
3880
+ interface ThemeWrapperProps {
3881
+ children: ReactNode;
3844
3882
  }
3845
3883
 
3846
- // ============ \u5BFC\u51FA ============
3884
+ export function ThemeWrapper({ children }: ThemeWrapperProps) {${refLine}
3885
+ const { appearance, primaryColor, neutralColor } = useThemeStore(
3886
+ (s) => ({ appearance: s.appearance, primaryColor: s.primaryColor, neutralColor: s.neutralColor }),
3887
+ shallow,
3888
+ );${cssVarSyncLine}${appearanceSyncLine}${externalThemeLine}
3847
3889
 
3848
- export const request = new Request(instance);
3849
- export type { Result };
3850
- export default request;
3851
- `;
3852
- }
3890
+ const resolvedAppearance = useMemo(() => {
3891
+ if (appearance !== "auto") return appearance;
3892
+ if (typeof window === "undefined") return "light";
3893
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
3894
+ }, [appearance]);
3853
3895
 
3854
- // src/plugin/builtin/zustand.ts
3855
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
3856
- import { join as join11 } from "path";
3857
- var zustandPlugin = createPlugin((options = {}) => ({
3896
+ const theme = useCallback<GetAntdTheme>(
3897
+ (ap) => createThemeConfig({
3898
+ appearance: ap as "light" | "dark",
3899
+ primaryColor,
3900
+ neutralColor,
3901
+ }),
3902
+ [primaryColor, neutralColor],
3903
+ );
3904
+
3905
+ return (
3906
+ <AntdThemeProvider
3907
+ prefixCls={RUNTIME_PREFIX_CLS}
3908
+ appearance={resolvedAppearance}
3909
+ themeMode={appearance}
3910
+ theme={theme}${cssVar ? "\n customToken={{ cssVar: true }}" : ""}
3911
+ >
3912
+ ${globalReset ? "<GlobalReset />" : ""}
3913
+ ${childrenSlot}
3914
+ </AntdThemeProvider>
3915
+ );
3916
+ }
3917
+
3918
+ export default ThemeWrapper;
3919
+ `;
3920
+ }
3921
+
3922
+ // src/plugin/builtin/zustand.ts
3923
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
3924
+ import { join as join12 } from "path";
3925
+ var zustandPlugin = createPlugin((options = {}) => ({
3858
3926
  name: "@4399ywkf/plugin-zustand",
3859
3927
  version: "1.0.0",
3860
3928
  description: "Zustand \u72B6\u6001\u7BA1\u7406\u96C6\u6210",
3861
3929
  setup(context) {
3862
3930
  const { scaffold = true, storeDir = "store" } = options;
3863
3931
  const { cwd, logger } = context;
3864
- const storePath = join11(cwd, storeDir);
3865
- if (scaffold && !existsSync10(storePath)) {
3932
+ const storePath = join12(cwd, storeDir);
3933
+ if (scaffold && !existsSync11(storePath)) {
3866
3934
  scaffoldStore(storePath, logger);
3867
3935
  }
3868
3936
  const hooks = {
@@ -3875,7 +3943,7 @@ var zustandPlugin = createPlugin((options = {}) => ({
3875
3943
  rspackConfig.resolve = resolve4;
3876
3944
  return rspackConfig;
3877
3945
  },
3878
- generateFiles(ctx) {
3946
+ generateFiles(_ctx) {
3879
3947
  return [
3880
3948
  {
3881
3949
  path: "store.ts",
@@ -3890,47 +3958,31 @@ var zustandPlugin = createPlugin((options = {}) => ({
3890
3958
  function scaffoldStore(storePath, logger) {
3891
3959
  const dirs = [
3892
3960
  storePath,
3893
- join11(storePath, "middleware"),
3894
- join11(storePath, "app"),
3895
- join11(storePath, "app", "slices"),
3896
- join11(storePath, "app", "slices", "counter")
3961
+ join12(storePath, "middleware"),
3962
+ join12(storePath, "app"),
3963
+ join12(storePath, "app", "slices"),
3964
+ join12(storePath, "app", "slices", "counter")
3897
3965
  ];
3898
3966
  for (const dir of dirs) {
3899
- if (!existsSync10(dir)) {
3967
+ if (!existsSync11(dir)) {
3900
3968
  mkdirSync4(dir, { recursive: true });
3901
3969
  }
3902
3970
  }
3903
- writeFileSync5(
3904
- join11(storePath, "middleware", "createDevtools.ts"),
3905
- TPL_CREATE_DEVTOOLS,
3906
- "utf-8"
3907
- );
3908
- writeFileSync5(
3909
- join11(storePath, "app", "slices", "counter", "initialState.ts"),
3971
+ writeFileSync6(join12(storePath, "middleware", "createDevtools.ts"), TPL_CREATE_DEVTOOLS, "utf-8");
3972
+ writeFileSync6(
3973
+ join12(storePath, "app", "slices", "counter", "initialState.ts"),
3910
3974
  TPL_COUNTER_INITIAL_STATE,
3911
3975
  "utf-8"
3912
3976
  );
3913
- writeFileSync5(
3914
- join11(storePath, "app", "slices", "counter", "actions.ts"),
3977
+ writeFileSync6(
3978
+ join12(storePath, "app", "slices", "counter", "actions.ts"),
3915
3979
  TPL_COUNTER_ACTIONS,
3916
3980
  "utf-8"
3917
3981
  );
3918
- writeFileSync5(
3919
- join11(storePath, "app", "initialState.ts"),
3920
- TPL_APP_INITIAL_STATE,
3921
- "utf-8"
3922
- );
3923
- writeFileSync5(
3924
- join11(storePath, "app", "store.ts"),
3925
- TPL_APP_STORE,
3926
- "utf-8"
3927
- );
3928
- writeFileSync5(
3929
- join11(storePath, "app", "index.tsx"),
3930
- TPL_APP_INDEX,
3931
- "utf-8"
3932
- );
3933
- writeFileSync5(join11(storePath, "index.ts"), TPL_STORE_INDEX, "utf-8");
3982
+ writeFileSync6(join12(storePath, "app", "initialState.ts"), TPL_APP_INITIAL_STATE, "utf-8");
3983
+ writeFileSync6(join12(storePath, "app", "store.ts"), TPL_APP_STORE, "utf-8");
3984
+ writeFileSync6(join12(storePath, "app", "index.tsx"), TPL_APP_INDEX, "utf-8");
3985
+ writeFileSync6(join12(storePath, "index.ts"), TPL_STORE_INDEX, "utf-8");
3934
3986
  logger.info("\u5DF2\u751F\u6210 store/ \u811A\u624B\u67B6\uFF08Agent/Slice \u67B6\u6784\uFF09");
3935
3987
  }
3936
3988
  var TPL_CREATE_DEVTOOLS = `import type { DevtoolsOptions } from "zustand/middleware";
@@ -4092,502 +4144,469 @@ export type { StateCreator, StoreApi } from "zustand";
4092
4144
  `;
4093
4145
  }
4094
4146
 
4095
- // src/cli/dev.ts
4096
- import { rspack as rspack4 } from "@rspack/core";
4097
- import { RspackDevServer } from "@rspack/dev-server";
4098
- import chalk2 from "chalk";
4099
-
4100
- // src/cli/env.ts
4101
- import { existsSync as existsSync11, readFileSync as readFileSync4 } from "fs";
4102
- import { resolve as resolve3 } from "path";
4103
- import dotenv from "dotenv";
4104
- function preloadEnv(cwd, mode, nodeEnv) {
4105
- process.env.NODE_ENV = nodeEnv;
4106
- process.env.APP_MODE = mode;
4107
- const defaultPaths = [
4108
- resolve3(cwd, "config/env/.env.public"),
4109
- resolve3(cwd, ".env.public"),
4110
- resolve3(cwd, ".env")
4111
- ];
4112
- for (const envPath of defaultPaths) {
4113
- if (existsSync11(envPath)) {
4114
- dotenv.config({ path: envPath });
4115
- break;
4147
+ // src/router/plugin.ts
4148
+ import { existsSync as existsSync12, watch as watch2 } from "fs";
4149
+ import { join as join13 } from "path";
4150
+ var ConventionalRoutePlugin = class {
4151
+ options;
4152
+ isWatching = false;
4153
+ hasGenerated = false;
4154
+ lastGeneratedContent = "";
4155
+ constructor(options) {
4156
+ this.options = options;
4157
+ }
4158
+ apply(compiler) {
4159
+ const pluginName = "ConventionalRoutePlugin";
4160
+ compiler.hooks.beforeCompile.tapAsync(pluginName, (_params, callback) => {
4161
+ if (!this.hasGenerated) {
4162
+ this.generateRoutes();
4163
+ this.hasGenerated = true;
4164
+ }
4165
+ callback();
4166
+ });
4167
+ if (this.options.watch && !this.isWatching) {
4168
+ this.watchPages();
4116
4169
  }
4117
4170
  }
4118
- const modeEnvPaths = [
4119
- resolve3(cwd, `config/env/.env.${mode}`),
4120
- resolve3(cwd, `.env.${mode}`)
4121
- ];
4122
- for (const envPath of modeEnvPaths) {
4123
- if (existsSync11(envPath)) {
4124
- const envContent = readFileSync4(envPath, "utf-8");
4125
- const parsed = dotenv.parse(envContent);
4126
- for (const key in parsed) {
4127
- process.env[key] = parsed[key];
4171
+ /**
4172
+ * 生成路由文件(带内容比对,避免不必要的重复生成)
4173
+ */
4174
+ generateRoutes() {
4175
+ try {
4176
+ const generator = new ConventionalRouteGenerator({
4177
+ pagesDir: this.options.pagesDir,
4178
+ outputDir: this.options.outputDir,
4179
+ basename: this.options.basename
4180
+ });
4181
+ const newContent = generator.generateCode();
4182
+ const _outputPath = join13(this.options.outputDir, "routes.tsx");
4183
+ const normalizedNew = this.normalizeContent(newContent);
4184
+ const normalizedOld = this.normalizeContent(this.lastGeneratedContent);
4185
+ if (normalizedNew === normalizedOld) {
4186
+ return;
4128
4187
  }
4129
- break;
4188
+ generator.write();
4189
+ this.lastGeneratedContent = newContent;
4190
+ } catch (error) {
4191
+ console.error("[ywkf] \u751F\u6210\u8DEF\u7531\u5931\u8D25:", error);
4130
4192
  }
4131
4193
  }
4132
- process.env.NODE_ENV = nodeEnv;
4133
- process.env.APP_MODE = mode;
4134
- }
4135
- function loadEnv(config, cwd, mode, nodeEnv) {
4136
- const { env } = config;
4137
- const publicEnvPath = resolve3(cwd, env.publicEnvFile ?? "config/env/.env.public");
4138
- if (existsSync11(publicEnvPath)) {
4139
- dotenv.config({ path: publicEnvPath });
4194
+ /**
4195
+ * 标准化内容(去掉时间戳等动态部分)
4196
+ */
4197
+ normalizeContent(content) {
4198
+ return content.replace(/\/\/ Generated at: .+/g, "");
4140
4199
  }
4141
- const modeEnvPath = resolve3(cwd, env.envDir ?? "config/env", `.env.${mode}`);
4142
- if (existsSync11(modeEnvPath)) {
4143
- const envContent = readFileSync4(modeEnvPath, "utf-8");
4144
- const parsed = dotenv.parse(envContent);
4145
- for (const key in parsed) {
4146
- process.env[key] = parsed[key];
4200
+ /**
4201
+ * 监听页面目录变化
4202
+ */
4203
+ watchPages() {
4204
+ if (!existsSync12(this.options.pagesDir)) {
4205
+ return;
4147
4206
  }
4148
- }
4149
- process.env.NODE_ENV = nodeEnv;
4150
- process.env.APP_MODE = mode;
4151
- process.env.APP_NAME = process.env.APP_NAME || config.appName;
4152
- process.env.APP_CNAME = process.env.APP_CNAME || config.appCName;
4153
- process.env.OUTPUT_PATH = process.env.OUTPUT_PATH || config.output.path;
4154
- process.env.PUBLIC_PATH = process.env.PUBLIC_PATH || config.output.publicPath;
4155
- process.env.APP_HOST = process.env.APP_HOST || config.dev.host;
4156
- process.env.APP_PORT = process.env.APP_PORT || String(config.dev.port);
4157
- }
4158
-
4159
- // src/cli/port.ts
4160
- import net from "net";
4161
- function isPortAvailable(port, host) {
4162
- return new Promise((resolve4) => {
4163
- const server = net.createServer();
4164
- server.once("error", (err) => {
4165
- if (err.code === "EADDRINUSE") {
4166
- resolve4(false);
4167
- } else {
4168
- resolve4(false);
4207
+ this.isWatching = true;
4208
+ let debounceTimer = null;
4209
+ const watcher = watch2(this.options.pagesDir, { recursive: true }, (_eventType, filename) => {
4210
+ if (!filename?.match(/\.(tsx?|jsx?)$/)) {
4211
+ return;
4212
+ }
4213
+ if (debounceTimer) {
4214
+ clearTimeout(debounceTimer);
4169
4215
  }
4216
+ debounceTimer = setTimeout(() => {
4217
+ if (process.env.DEBUG) console.log(`[ywkf] \u68C0\u6D4B\u5230\u9875\u9762\u53D8\u5316: ${filename}`);
4218
+ this.generateRoutes();
4219
+ }, 500);
4170
4220
  });
4171
- server.once("listening", () => {
4172
- server.close(() => resolve4(true));
4221
+ process.on("exit", () => {
4222
+ watcher.close();
4173
4223
  });
4174
- server.listen(port, host);
4175
- });
4224
+ }
4225
+ };
4226
+
4227
+ // src/runtime/bootstrap.tsx
4228
+ import { createRoot } from "react-dom/client";
4229
+
4230
+ // src/runtime/providers.tsx
4231
+ import { ConfigProvider } from "antd";
4232
+ import zhCN from "antd/locale/zh_CN.js";
4233
+ import { StrictMode, Suspense } from "react";
4234
+ import { RouterProvider } from "react-router";
4235
+
4236
+ // src/runtime/context.tsx
4237
+ import { createContext, useContext } from "react";
4238
+ import { jsx } from "react/jsx-runtime";
4239
+ var defaultContextValue = {
4240
+ appName: "app",
4241
+ basename: "/",
4242
+ isDev: process.env.NODE_ENV === "development",
4243
+ env: {}
4244
+ };
4245
+ var AppContext = createContext(defaultContextValue);
4246
+ function AppContextProvider({ value, children }) {
4247
+ const contextValue = {
4248
+ ...defaultContextValue,
4249
+ ...value,
4250
+ env: {
4251
+ ...defaultContextValue.env,
4252
+ ...value.env
4253
+ }
4254
+ };
4255
+ return /* @__PURE__ */ jsx(AppContext.Provider, { value: contextValue, children });
4176
4256
  }
4177
- async function getAvailablePort(preferredPort, host) {
4178
- const MAX_ATTEMPTS = 20;
4179
- for (let i = 0; i < MAX_ATTEMPTS; i++) {
4180
- const port = preferredPort + i;
4181
- const available = await isPortAvailable(port, host);
4182
- if (available) return port;
4257
+ function useApp() {
4258
+ const context = useContext(AppContext);
4259
+ if (!context) {
4260
+ throw new Error("useApp must be used within AppContextProvider");
4183
4261
  }
4184
- throw new Error(
4185
- `No available port found in range ${preferredPort}-${preferredPort + MAX_ATTEMPTS - 1}`
4186
- );
4262
+ return context;
4263
+ }
4264
+ function useAppName() {
4265
+ return useApp().appName;
4266
+ }
4267
+ function useBasename() {
4268
+ return useApp().basename;
4269
+ }
4270
+ function useEnv() {
4271
+ return useApp().env;
4272
+ }
4273
+ function useIsDev() {
4274
+ return useApp().isDev;
4187
4275
  }
4188
4276
 
4189
- // src/cli/printer.ts
4190
- import chalk from "chalk";
4191
- import os from "os";
4192
- import { createRequire as createRequire3 } from "module";
4193
- import { fileURLToPath as fileURLToPath2 } from "url";
4194
- import { dirname as dirname2, join as join12 } from "path";
4195
- var __filename = fileURLToPath2(import.meta.url);
4196
- var __dirname2 = dirname2(__filename);
4197
- var require4 = createRequire3(import.meta.url);
4198
- var _version = null;
4199
- function getFrameworkVersion() {
4200
- if (_version) return _version;
4201
- try {
4202
- const pkgPath = join12(__dirname2, "../../package.json");
4203
- const pkg = require4(pkgPath);
4204
- _version = pkg.version || "0.0.0";
4205
- } catch {
4206
- _version = "0.0.0";
4277
+ // src/runtime/error-boundary.tsx
4278
+ import { Component } from "react";
4279
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
4280
+ var ErrorBoundary = class extends Component {
4281
+ constructor(props) {
4282
+ super(props);
4283
+ this.state = { hasError: false, error: null };
4207
4284
  }
4208
- return _version;
4209
- }
4210
- function getNetworkAddress(port) {
4211
- const interfaces = os.networkInterfaces();
4212
- for (const entries of Object.values(interfaces)) {
4213
- if (!entries) continue;
4214
- for (const entry of entries) {
4215
- if (entry.family === "IPv4" && !entry.internal) {
4216
- return `http://${entry.address}:${port}/`;
4285
+ static getDerivedStateFromError(error) {
4286
+ return { hasError: true, error };
4287
+ }
4288
+ componentDidCatch(error, errorInfo) {
4289
+ console.error("[ErrorBoundary] Caught error:", error, errorInfo);
4290
+ this.props.onError?.(error, errorInfo);
4291
+ }
4292
+ reset = () => {
4293
+ this.setState({ hasError: false, error: null });
4294
+ };
4295
+ render() {
4296
+ const { hasError, error } = this.state;
4297
+ const { children, fallback } = this.props;
4298
+ if (hasError && error) {
4299
+ if (typeof fallback === "function") {
4300
+ return fallback(error, this.reset);
4301
+ }
4302
+ if (fallback) {
4303
+ return fallback;
4217
4304
  }
4305
+ return /* @__PURE__ */ jsx2(DefaultErrorFallback, { error, onReset: this.reset });
4218
4306
  }
4219
- }
4220
- return null;
4221
- }
4222
- var BAR_WIDTH = 30;
4223
- function renderBar(percent) {
4224
- const filled = Math.round(BAR_WIDTH * percent);
4225
- const empty = BAR_WIDTH - filled;
4226
- const bar = chalk.green("\u2501".repeat(filled)) + chalk.gray("\u2501".repeat(empty));
4227
- return bar;
4228
- }
4229
- var DevPrinter = class {
4230
- constructor(host, port, pluginNames, isBuild = false) {
4231
- this.host = host;
4232
- this.port = port;
4233
- this.pluginNames = pluginNames;
4234
- this.isBuild = isBuild;
4235
- }
4236
- startTime = 0;
4237
- lastProgressLine = "";
4238
- firstCompileDone = false;
4239
- /**
4240
- * 打印框架 banner
4241
- */
4242
- printBanner() {
4243
- const version = getFrameworkVersion();
4244
- console.log();
4245
- console.log(
4246
- ` ${chalk.bold.cyan("@4399ywkf/core")} ${chalk.green(`Framework v${version}`)}`
4247
- );
4248
- console.log();
4249
- }
4250
- /**
4251
- * 打印 "start build started..."
4252
- */
4253
- printBuildStart() {
4254
- this.startTime = Date.now();
4255
- this.compileDone = false;
4256
- const label = chalk.gray("start");
4257
- console.log(` ${label} build started...`);
4258
- }
4259
- /**
4260
- * 更新编译进度条(原地覆写同一行)
4261
- */
4262
- updateProgress(percent, message) {
4263
- const pct = Math.min(Math.round(percent * 100), 100);
4264
- const bar = renderBar(percent);
4265
- const status = message || "compiling";
4266
- const line = ` ${chalk.yellow("\u25CF")} client ${bar} ${chalk.bold(`(${pct}%)`)} ${chalk.gray(status)}`;
4267
- if (process.stdout.isTTY) {
4268
- if (this.lastProgressLine) {
4269
- process.stdout.write("\x1B[1A\x1B[2K");
4270
- }
4271
- process.stdout.write(line + "\n");
4272
- } else if (!this.firstCompileDone && pct === 100) {
4273
- console.log(line);
4274
- }
4275
- this.lastProgressLine = line;
4276
- }
4277
- /**
4278
- * 编译完成
4279
- */
4280
- printBuildDone(hasErrors = false) {
4281
- if (this.firstCompileDone) {
4282
- if (!hasErrors) {
4283
- const elapsed2 = ((Date.now() - this.startTime) / 1e3).toFixed(2);
4284
- console.log(
4285
- ` ${chalk.green("hmr")} update in ${chalk.bold(`${elapsed2} s`)}`
4286
- );
4287
- }
4288
- return;
4289
- }
4290
- this.firstCompileDone = true;
4291
- this.lastProgressLine = "";
4292
- if (hasErrors) return;
4293
- const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(2);
4294
- const label = chalk.green("ready");
4295
- console.log(` ${label} built in ${chalk.bold(`${elapsed} s`)}`);
4296
- console.log();
4297
- if (this.isBuild) {
4298
- this.printPluginList();
4299
- } else {
4300
- this.printServerInfo();
4301
- }
4302
- }
4303
- /**
4304
- * 标记新一轮编译开始(HMR)
4305
- */
4306
- markRebuildStart() {
4307
- this.startTime = Date.now();
4308
- this.lastProgressLine = "";
4309
- }
4310
- /**
4311
- * 仅打印插件列表(build 模式使用)
4312
- */
4313
- printPluginList() {
4314
- if (this.pluginNames.length > 0) {
4315
- console.log(` ${chalk.bold(">")} Plugins:`);
4316
- for (const name of this.pluginNames) {
4317
- const shortName = name.replace("@4399ywkf/plugin-", "").replace("@4399ywkf/", "");
4318
- console.log(` ${chalk.green("\u2713")} ${shortName}`);
4319
- }
4320
- console.log();
4321
- }
4322
- }
4323
- /**
4324
- * 打印服务器信息面板(dev 模式使用)
4325
- */
4326
- printServerInfo() {
4327
- const localUrl = `http://localhost:${this.port}/`;
4328
- const networkUrl = getNetworkAddress(this.port);
4329
- console.log(` ${chalk.bold(">")} Local: ${chalk.cyan(localUrl)}`);
4330
- if (networkUrl) {
4331
- console.log(` ${chalk.bold(">")} Network: ${chalk.cyan(networkUrl)}`);
4332
- }
4333
- console.log();
4334
- this.printPluginList();
4335
- console.log(
4336
- ` ${chalk.bold(">")} press ${chalk.bold("h + enter")} to show shortcuts`
4337
- );
4338
- console.log();
4339
- }
4340
- /**
4341
- * 打印快捷键帮助
4342
- */
4343
- printShortcuts() {
4344
- console.log();
4345
- console.log(` ${chalk.bold("Shortcuts:")}`);
4346
- console.log(` ${chalk.bold("o + enter")} open in browser`);
4347
- console.log(` ${chalk.bold("c + enter")} clear console`);
4348
- console.log(` ${chalk.bold("q + enter")} quit`);
4349
- console.log();
4307
+ return children;
4350
4308
  }
4351
4309
  };
4352
- function createProgressHandler(printer) {
4353
- return (percent, message, ..._details) => {
4354
- printer.updateProgress(percent, message);
4355
- };
4356
- }
4357
- function registerShortcuts(printer, opts) {
4358
- if (!process.stdin.isTTY) return;
4359
- process.stdin.setEncoding("utf-8");
4360
- process.stdin.setRawMode?.(false);
4361
- let buffer = "";
4362
- const handler = (data) => {
4363
- buffer += data;
4364
- if (!buffer.includes("\n")) return;
4365
- const cmd = buffer.trim().toLowerCase();
4366
- buffer = "";
4367
- switch (cmd) {
4368
- case "h":
4369
- printer.printShortcuts();
4370
- break;
4371
- case "o": {
4372
- const url = `http://localhost:${opts.port}/`;
4373
- import("child_process").then((cp) => {
4374
- const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
4375
- cp.exec(`${command} ${url}`);
4376
- });
4377
- console.log(` ${chalk.green("\u279C")} Opening ${chalk.cyan(url)}...`);
4378
- break;
4379
- }
4380
- case "c":
4381
- console.clear();
4382
- break;
4383
- case "q":
4384
- opts.onQuit();
4385
- break;
4310
+ function DefaultErrorFallback({ error, onReset }) {
4311
+ const isDev = process.env.NODE_ENV === "development";
4312
+ return /* @__PURE__ */ jsx2(
4313
+ "div",
4314
+ {
4315
+ style: {
4316
+ padding: 24,
4317
+ display: "flex",
4318
+ flexDirection: "column",
4319
+ alignItems: "center",
4320
+ justifyContent: "center",
4321
+ minHeight: "100vh",
4322
+ backgroundColor: "#f5f5f5"
4323
+ },
4324
+ children: /* @__PURE__ */ jsxs(
4325
+ "div",
4326
+ {
4327
+ style: {
4328
+ maxWidth: 600,
4329
+ padding: 32,
4330
+ backgroundColor: "#fff",
4331
+ borderRadius: 8,
4332
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)"
4333
+ },
4334
+ children: [
4335
+ /* @__PURE__ */ jsx2("h1", { style: { color: "#ff4d4f", marginBottom: 16 }, children: "\u9875\u9762\u51FA\u9519\u4E86" }),
4336
+ /* @__PURE__ */ jsx2("p", { style: { color: "#666", marginBottom: 16 }, children: "\u62B1\u6B49\uFF0C\u9875\u9762\u9047\u5230\u4E86\u4E00\u4E9B\u95EE\u9898\u3002\u8BF7\u5C1D\u8BD5\u5237\u65B0\u9875\u9762\u6216\u8054\u7CFB\u7BA1\u7406\u5458\u3002" }),
4337
+ isDev && /* @__PURE__ */ jsxs(
4338
+ "details",
4339
+ {
4340
+ style: {
4341
+ marginBottom: 16,
4342
+ padding: 12,
4343
+ backgroundColor: "#fff2f0",
4344
+ borderRadius: 4,
4345
+ border: "1px solid #ffccc7"
4346
+ },
4347
+ children: [
4348
+ /* @__PURE__ */ jsx2("summary", { style: { cursor: "pointer", fontWeight: "bold" }, children: "\u9519\u8BEF\u8BE6\u60C5\uFF08\u4EC5\u5F00\u53D1\u73AF\u5883\u53EF\u89C1\uFF09" }),
4349
+ /* @__PURE__ */ jsxs(
4350
+ "pre",
4351
+ {
4352
+ style: {
4353
+ marginTop: 8,
4354
+ padding: 8,
4355
+ backgroundColor: "#fff",
4356
+ borderRadius: 4,
4357
+ overflow: "auto",
4358
+ fontSize: 12
4359
+ },
4360
+ children: [
4361
+ error.message,
4362
+ "\n\n",
4363
+ error.stack
4364
+ ]
4365
+ }
4366
+ )
4367
+ ]
4368
+ }
4369
+ ),
4370
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
4371
+ /* @__PURE__ */ jsx2(
4372
+ "button",
4373
+ {
4374
+ onClick: onReset,
4375
+ style: {
4376
+ padding: "8px 16px",
4377
+ backgroundColor: "#1890ff",
4378
+ color: "#fff",
4379
+ border: "none",
4380
+ borderRadius: 4,
4381
+ cursor: "pointer"
4382
+ },
4383
+ children: "\u91CD\u8BD5"
4384
+ }
4385
+ ),
4386
+ /* @__PURE__ */ jsx2(
4387
+ "button",
4388
+ {
4389
+ onClick: () => window.location.reload(),
4390
+ style: {
4391
+ padding: "8px 16px",
4392
+ backgroundColor: "#fff",
4393
+ color: "#666",
4394
+ border: "1px solid #d9d9d9",
4395
+ borderRadius: 4,
4396
+ cursor: "pointer"
4397
+ },
4398
+ children: "\u5237\u65B0\u9875\u9762"
4399
+ }
4400
+ )
4401
+ ] })
4402
+ ]
4403
+ }
4404
+ )
4386
4405
  }
4387
- };
4388
- process.stdin.on("data", handler);
4389
- process.stdin.resume();
4406
+ );
4390
4407
  }
4391
4408
 
4392
- // src/cli/dev.ts
4393
- async function dev(options = {}) {
4394
- const cwd = options.cwd || process.cwd();
4395
- const mode = options.mode || "development";
4396
- try {
4397
- preloadEnv(cwd, mode, "development");
4398
- const { config } = await resolveConfig(cwd);
4399
- loadEnv(config, cwd, mode, "development");
4400
- const pluginManager = new PluginManager(config, cwd, true);
4401
- if (config.plugins && config.plugins.length > 0) {
4402
- await pluginManager.loadPlugins(config.plugins);
4403
- }
4404
- await pluginManager.runBeforeDevServer();
4405
- let rspackConfig = createRspackConfig(config, cwd, { isDev: true });
4406
- rspackConfig = await pluginManager.applyRspackConfigHooks(rspackConfig);
4407
- const host = config.dev.host || "localhost";
4408
- const preferredPort = config.dev.port || 3e3;
4409
- const port = await getAvailablePort(preferredPort, host);
4410
- const pluginNames = pluginManager.getPluginNames();
4411
- if (port !== preferredPort) {
4412
- console.log(
4413
- chalk2.yellow(` Port ${preferredPort} is in use, using ${port} instead.
4414
- `)
4415
- );
4416
- }
4417
- const printer = new DevPrinter(host, port, pluginNames);
4418
- printer.printBanner();
4419
- printer.printBuildStart();
4420
- printer.updateProgress(0, "preparing");
4421
- rspackConfig.plugins = rspackConfig.plugins || [];
4422
- rspackConfig.plugins.push(
4423
- new rspack4.ProgressPlugin(createProgressHandler(printer))
4424
- );
4425
- rspackConfig.stats = "none";
4426
- rspackConfig.infrastructureLogging = { level: "none" };
4427
- if (rspackConfig.devServer) {
4428
- const ds = rspackConfig.devServer;
4429
- const existingClient = ds.client ?? {};
4430
- ds.client = {
4431
- ...existingClient,
4432
- logging: "warn",
4433
- overlay: false,
4434
- progress: false
4435
- };
4409
+ // src/runtime/providers.tsx
4410
+ import { jsx as jsx3 } from "react/jsx-runtime";
4411
+ function DefaultLoading() {
4412
+ return /* @__PURE__ */ jsx3(
4413
+ "div",
4414
+ {
4415
+ style: {
4416
+ display: "flex",
4417
+ alignItems: "center",
4418
+ justifyContent: "center",
4419
+ height: "100vh",
4420
+ fontSize: 16,
4421
+ color: "#999"
4422
+ },
4423
+ children: "\u52A0\u8F7D\u4E2D..."
4436
4424
  }
4437
- const compiler = rspack4(rspackConfig);
4438
- compiler.hooks.done.tap("ywkf-dev-printer", (stats) => {
4439
- const hasErrors = stats.hasErrors();
4440
- if (hasErrors) {
4441
- const info = stats.toJson({ errors: true });
4442
- console.log();
4443
- console.log(chalk2.red(" Compile error:"));
4444
- for (const err of info.errors || []) {
4445
- console.log(chalk2.red(` ${err.message}`));
4446
- }
4447
- console.log();
4448
- }
4449
- printer.printBuildDone(hasErrors);
4450
- });
4451
- compiler.hooks.invalid.tap("ywkf-dev-printer", () => {
4452
- printer.markRebuildStart();
4453
- printer.updateProgress(0, "rebuilding");
4454
- });
4455
- const devServerOptions = {
4456
- ...rspackConfig.devServer || {},
4457
- host,
4458
- port
4459
- };
4460
- const server = new RspackDevServer(devServerOptions, compiler);
4461
- await server.start();
4462
- await pluginManager.runAfterDevServer({ host, port });
4463
- registerShortcuts(printer, {
4464
- port,
4465
- onQuit: async () => {
4466
- console.log(chalk2.gray("\n Shutting down...\n"));
4467
- await server.stop();
4468
- process.exit(0);
4425
+ );
4426
+ }
4427
+ function composeProviders(providers) {
4428
+ const sortedProviders = [...providers].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
4429
+ return function ComposedProviders({ children }) {
4430
+ return sortedProviders.reduceRight((acc, { component: Provider, props }) => {
4431
+ return /* @__PURE__ */ jsx3(Provider, { ...props, children: acc });
4432
+ }, children);
4433
+ };
4434
+ }
4435
+ function RootProvider({ config, children }) {
4436
+ const {
4437
+ appName = "app",
4438
+ router,
4439
+ basename = "/",
4440
+ strictMode = true,
4441
+ antd = { enabled: true },
4442
+ providers = [],
4443
+ lifecycle
4444
+ } = config;
4445
+ const appContextValue = {
4446
+ appName,
4447
+ basename,
4448
+ isDev: process.env.NODE_ENV === "development",
4449
+ env: typeof process !== "undefined" ? process.env : {}
4450
+ };
4451
+ const allProviders = [
4452
+ // 应用上下文 Provider(最外层)
4453
+ {
4454
+ component: AppContextProvider,
4455
+ props: { value: appContextValue },
4456
+ order: 0
4457
+ },
4458
+ // Ant Design ConfigProvider
4459
+ ...antd.enabled !== false ? [
4460
+ {
4461
+ component: ConfigProvider,
4462
+ props: {
4463
+ locale: antd.locale || zhCN,
4464
+ theme: antd.theme,
4465
+ ...antd.configProvider
4466
+ },
4467
+ order: 10
4469
4468
  }
4470
- });
4471
- const signals = ["SIGINT", "SIGTERM"];
4472
- for (const signal of signals) {
4473
- process.on(signal, async () => {
4474
- await server.stop();
4475
- process.exit(0);
4476
- });
4477
- }
4478
- } catch (error) {
4479
- console.error();
4480
- console.error(chalk2.red(" \u2716 Dev server failed to start"));
4481
- console.error(error);
4482
- process.exit(1);
4469
+ ] : [],
4470
+ // 用户自定义 Providers
4471
+ ...providers.map((p) => ({ ...p, order: p.order ?? 50 }))
4472
+ ];
4473
+ const ComposedProviders = composeProviders(allProviders);
4474
+ const content = router ? /* @__PURE__ */ jsx3(Suspense, { fallback: /* @__PURE__ */ jsx3(DefaultLoading, {}), children: /* @__PURE__ */ jsx3(RouterProvider, { router }) }) : children;
4475
+ const app = /* @__PURE__ */ jsx3(ErrorBoundary, { onError: lifecycle?.onError, children: /* @__PURE__ */ jsx3(ComposedProviders, { children: content }) });
4476
+ if (strictMode) {
4477
+ return /* @__PURE__ */ jsx3(StrictMode, { children: app });
4483
4478
  }
4479
+ return app;
4484
4480
  }
4485
-
4486
- // src/cli/build.ts
4487
- import { rspack as rspack5 } from "@rspack/core";
4488
- import chalk3 from "chalk";
4489
- function formatSize(bytes) {
4490
- if (bytes < 1024) return `${bytes} B`;
4491
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
4492
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
4481
+ function createProvider(component, props, order) {
4482
+ return {
4483
+ component,
4484
+ props,
4485
+ order
4486
+ };
4493
4487
  }
4494
- function printBuildResult(stats) {
4495
- const info = stats.toJson({
4496
- assets: true,
4497
- errors: true,
4498
- warnings: true
4499
- });
4500
- if (info.errors && info.errors.length > 0) {
4501
- console.log(chalk3.red("\n Compile errors:"));
4502
- for (const error of info.errors) {
4503
- console.log(chalk3.red(` ${error.message}`));
4504
- }
4505
- return;
4488
+
4489
+ // src/runtime/bootstrap.tsx
4490
+ import { jsx as jsx4 } from "react/jsx-runtime";
4491
+ var root = null;
4492
+ async function resolveAppConfig(config) {
4493
+ if (config.router || !config.conventionalRoutes) {
4494
+ return config;
4506
4495
  }
4507
- if (info.warnings && info.warnings.length > 0) {
4508
- console.log(chalk3.yellow("\n Warnings:"));
4509
- for (const warning of info.warnings) {
4510
- console.log(chalk3.yellow(` ${warning.message}`));
4496
+ try {
4497
+ const routesModule = await import("@ywkf/routes");
4498
+ const createRouter = routesModule.createRouter || routesModule.default?.createRouter;
4499
+ if (typeof createRouter === "function") {
4500
+ const router = createRouter(config.basename);
4501
+ console.log("[ywkf] \u5DF2\u81EA\u52A8\u6CE8\u5165\u7EA6\u5B9A\u5F0F\u8DEF\u7531");
4502
+ return { ...config, router };
4503
+ } else {
4504
+ console.warn("[ywkf] @ywkf/routes \u672A\u5BFC\u51FA createRouter \u51FD\u6570");
4511
4505
  }
4506
+ } catch (error) {
4507
+ console.error("[ywkf] \u52A0\u8F7D\u7EA6\u5B9A\u5F0F\u8DEF\u7531\u5931\u8D25:", error);
4508
+ console.warn("[ywkf] \u8BF7\u786E\u4FDD\u5DF2\u542F\u7528\u7EA6\u5B9A\u5F0F\u8DEF\u7531\u4E14 src/pages \u76EE\u5F55\u5B58\u5728");
4512
4509
  }
4513
- console.log(chalk3.bold("\n Build output:"));
4514
- console.log();
4515
- const assets = info.assets || [];
4516
- const sortedAssets = assets.filter((asset) => !asset.name.endsWith(".map")).sort((a, b) => b.size - a.size);
4517
- for (const asset of sortedAssets.slice(0, 15)) {
4518
- const sizeColor = asset.size > 500 * 1024 ? chalk3.yellow : chalk3.green;
4519
- console.log(
4520
- ` ${chalk3.dim(asset.name.padEnd(50))} ${sizeColor(formatSize(asset.size))}`
4521
- );
4510
+ return config;
4511
+ }
4512
+ async function bootstrap(config) {
4513
+ const { rootId = "root", lifecycle } = config;
4514
+ const resolvedConfig = await resolveAppConfig(config);
4515
+ if (lifecycle?.onBeforeMount) {
4516
+ await lifecycle.onBeforeMount();
4522
4517
  }
4523
- if (sortedAssets.length > 15) {
4524
- console.log(chalk3.dim(` ... and ${sortedAssets.length - 15} more files`));
4518
+ const rootElement = document.getElementById(rootId);
4519
+ if (!rootElement) {
4520
+ throw new Error(`\u627E\u4E0D\u5230\u6839\u5143\u7D20 #${rootId}`);
4525
4521
  }
4526
- console.log();
4527
- }
4528
- async function build(options = {}) {
4529
- const cwd = options.cwd || process.cwd();
4530
- const mode = options.mode || "production";
4531
- try {
4532
- preloadEnv(cwd, mode, "production");
4533
- const { config } = await resolveConfig(cwd);
4534
- loadEnv(config, cwd, mode, "production");
4535
- const pluginManager = new PluginManager(config, cwd, false);
4536
- if (config.plugins && config.plugins.length > 0) {
4537
- await pluginManager.loadPlugins(config.plugins);
4538
- }
4539
- await pluginManager.runBeforeBuild();
4540
- let rspackConfig = createRspackConfig(config, cwd, { isDev: false });
4541
- rspackConfig = await pluginManager.applyRspackConfigHooks(rspackConfig);
4542
- const pluginNames = pluginManager.getPluginNames();
4543
- const printer = new DevPrinter("localhost", 0, pluginNames, true);
4544
- printer.printBanner();
4545
- printer.printBuildStart();
4546
- printer.updateProgress(0, "preparing");
4547
- rspackConfig.plugins = rspackConfig.plugins || [];
4548
- rspackConfig.plugins.push(
4549
- new rspack5.ProgressPlugin(createProgressHandler(printer))
4550
- );
4551
- rspackConfig.stats = "none";
4552
- rspackConfig.infrastructureLogging = { level: "none" };
4553
- const compiler = rspack5(rspackConfig);
4554
- const stats = await new Promise((resolve4, reject) => {
4555
- compiler.run((err, stats2) => {
4556
- if (err) {
4557
- reject(err);
4558
- return;
4559
- }
4560
- if (!stats2) {
4561
- reject(new Error("Build failed: no stats available"));
4562
- return;
4563
- }
4564
- resolve4(stats2);
4565
- });
4566
- });
4567
- await new Promise((resolve4, reject) => {
4568
- compiler.close((err) => {
4569
- if (err) reject(err);
4570
- else resolve4();
4571
- });
4572
- });
4573
- const hasErrors = stats.hasErrors();
4574
- const statsInfo = stats.toJson({ errors: true });
4575
- await pluginManager.runAfterBuild({
4576
- success: !hasErrors,
4577
- errors: statsInfo.errors?.map((e) => e.message)
4522
+ root = createRoot(rootElement);
4523
+ root.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig }));
4524
+ if (lifecycle?.onMounted) {
4525
+ setTimeout(() => {
4526
+ lifecycle.onMounted?.();
4527
+ }, 0);
4528
+ }
4529
+ if (lifecycle?.onUnmount) {
4530
+ window.addEventListener("beforeunload", () => {
4531
+ lifecycle.onUnmount?.();
4578
4532
  });
4579
- printer.printBuildDone(hasErrors);
4580
- if (hasErrors) {
4581
- printBuildResult(stats);
4582
- process.exit(1);
4533
+ }
4534
+ }
4535
+ function unmount() {
4536
+ if (root) {
4537
+ root.unmount();
4538
+ root = null;
4539
+ }
4540
+ }
4541
+ function createMicroApp(getConfig) {
4542
+ let microRoot = null;
4543
+ return {
4544
+ /**
4545
+ * 微前端 bootstrap 生命周期
4546
+ */
4547
+ async bootstrap() {
4548
+ console.log("[MicroApp] bootstrap");
4549
+ },
4550
+ /**
4551
+ * 微前端 mount 生命周期
4552
+ */
4553
+ async mount(props) {
4554
+ console.log("[MicroApp] mount", props);
4555
+ const config = getConfig(props);
4556
+ const resolvedConfig = await resolveAppConfig(config);
4557
+ const container = props?.masterProps?.container;
4558
+ const rootId = resolvedConfig.rootId || "root";
4559
+ const rootElement = container ? container.querySelector(`#${rootId}`) : document.getElementById(rootId);
4560
+ if (!rootElement) {
4561
+ throw new Error(`[MicroApp] \u627E\u4E0D\u5230\u6839\u5143\u7D20 #${rootId}`);
4562
+ }
4563
+ if (resolvedConfig.lifecycle?.onBeforeMount) {
4564
+ await resolvedConfig.lifecycle.onBeforeMount();
4565
+ }
4566
+ microRoot = createRoot(rootElement);
4567
+ microRoot.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig }));
4568
+ if (resolvedConfig.lifecycle?.onMounted) {
4569
+ setTimeout(() => {
4570
+ resolvedConfig.lifecycle?.onMounted?.();
4571
+ }, 0);
4572
+ }
4573
+ },
4574
+ /**
4575
+ * 微前端 unmount 生命周期
4576
+ */
4577
+ async unmount(props) {
4578
+ console.log("[MicroApp] unmount", props);
4579
+ const config = getConfig(props);
4580
+ if (config.lifecycle?.onUnmount) {
4581
+ config.lifecycle.onUnmount();
4582
+ }
4583
+ if (microRoot) {
4584
+ microRoot.unmount();
4585
+ microRoot = null;
4586
+ }
4587
+ },
4588
+ /**
4589
+ * 微前端 update 生命周期(可选)
4590
+ */
4591
+ async update(props) {
4592
+ console.log("[MicroApp] update", props);
4583
4593
  }
4584
- printBuildResult(stats);
4585
- } catch (error) {
4586
- console.error();
4587
- console.error(chalk3.red(" \u2716 Build failed"));
4588
- console.error(error);
4589
- process.exit(1);
4594
+ };
4595
+ }
4596
+ function isMicroAppEnv() {
4597
+ if (window.__POWERED_BY_QIANKUN__) {
4598
+ return true;
4599
+ }
4600
+ if (window.__GARFISH__) {
4601
+ return true;
4602
+ }
4603
+ return false;
4604
+ }
4605
+ function getMicroAppPublicPath() {
4606
+ if (window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__) {
4607
+ return window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
4590
4608
  }
4609
+ return "/";
4591
4610
  }
4592
4611
  export {
4593
4612
  AppContext,
@@ -4600,6 +4619,7 @@ export {
4600
4619
  YwkfGenerator,
4601
4620
  YwkfGeneratorPlugin,
4602
4621
  analyticsPlugin,
4622
+ biomePlugin,
4603
4623
  bootstrap,
4604
4624
  build,
4605
4625
  createBaseConfig,
@@ -4615,6 +4635,7 @@ export {
4615
4635
  dev,
4616
4636
  garfishPlugin,
4617
4637
  generateConventionalRoutes,
4638
+ getDefaultBiomeConfig,
4618
4639
  getMicroAppPublicPath,
4619
4640
  i18nPlugin,
4620
4641
  isMicroAppEnv,