wildpig 2.4.0 → 2.5.0

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/README.md CHANGED
@@ -1,21 +1,21 @@
1
- # 🐗Wildpig 野猪框架
2
-
3
- 一个健壮的,跑得快的~~野猪~~基于React(Typescript) & Bun的全栈框架。
4
-
5
- # 框架特点
6
-
7
- * 基于Bun,非常快。
8
-
9
- * Typescript,类型安全。
10
-
11
- * 前后端同构,一套代码,函数复用。
12
-
13
- * 【超级重要】SEO友好,支持服务端渲染(首屏超快)。
14
-
15
- * 服务端路由根据文件路径自动生成。
16
-
17
- * 前端开发热重载。
18
-
19
- # 启动项目
20
-
1
+ # 🐗Wildpig 野猪框架
2
+
3
+ 一个健壮的,跑得快的~~野猪~~基于React(Typescript) & Bun的全栈框架。
4
+
5
+ # 框架特点
6
+
7
+ * 基于Bun,非常快。
8
+
9
+ * Typescript,类型安全。
10
+
11
+ * 前后端同构,一套代码,函数复用。
12
+
13
+ * 【超级重要】SEO友好,支持服务端渲染(首屏超快)。
14
+
15
+ * 服务端路由根据文件路径自动生成。
16
+
17
+ * 前端开发热重载。
18
+
19
+ # 启动项目
20
+
21
21
  https://github.com/Eriktse/wildpig-starter
@@ -0,0 +1,2 @@
1
+ import "./index";
2
+ export default undefined;
package/decorators.ts CHANGED
@@ -1,5 +1,5 @@
1
-
2
-
3
-
4
-
1
+
2
+
3
+
4
+
5
5
  export { WildpigParamsGuard } from "./src/decorators/WildpigParamsGuard"
package/index.html CHANGED
@@ -1,13 +1,13 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title><!--title--></title>
7
- <script type="module" src="/node_modules/wildpig/src/entry/client.tsx"></script>
8
- <!--server-data-->
9
- </head>
10
- <body>
11
- <div id="root"><!--ssr-outlet--></div>
12
- </body>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><!--title--></title>
7
+ <script type="module" src="/node_modules/wildpig/src/entry/client.tsx"></script>
8
+ <!--server-data-->
9
+ </head>
10
+ <body>
11
+ <div id="root"><!--ssr-outlet--></div>
12
+ </body>
13
13
  </html>
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
-
2
- export { onAfterStartServer } from "./src/hooks/afterStartServer";
3
- export { defineConfig } from "./src/config";
4
- export { ServerDataGuard } from "./src/router/ServerDataGuard";
1
+
2
+ export { onAfterStartServer } from "./src/hooks/afterStartServer";
3
+ export { defineConfig } from "./src/config";
4
+ export { ServerDataGuard } from "./src/router/ServerDataGuard";
5
5
  export type { WildPigRouteObject } from "./src/router/types";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wildpig",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "author": "eriktse",
5
5
  "type": "module",
6
6
  "peerDependencies": {
package/scripts/build.ts CHANGED
@@ -1,47 +1,66 @@
1
- import path from "node:path";
2
- import { build as viteBuild } from "vite";
3
- import { packageApiRoutes } from "../src/ApiRoutes";
4
- import { IBuildOptions } from "../src/types";
5
- import chalk from "chalk";
6
-
7
- const __rootdir = path.resolve(__dirname, "../../../"); // 项目根目录
8
-
9
- const prebuild = async (options?: IBuildOptions) => {
10
- const promises = [];
11
- // 先编译客户端代码
12
- promises.push(viteBuild({
13
- configFile: path.resolve(__rootdir, "vite.config.ts"),
14
- build: {
15
- outDir: path.resolve(__rootdir, options?.outdir || "dist", "client"), // 输出目录
16
- },
17
- }));
18
- promises.push(packageApiRoutes());
19
- await Promise.all(promises);
20
- };
21
-
22
-
23
- export const build = async () => {
24
- console.log(chalk.green("开始构建..."));
25
-
26
- // 准备阶段
27
- process.env.NODE_ENV = "production";
28
- const st = performance.now();
29
-
30
- // 前处理
31
- await prebuild();
32
- // 正式编译
33
- await Bun.build({
34
- entrypoints: [path.resolve(__dirname, "../scripts/prod.ts")],
35
- minify: false, // 压缩
36
- target: "bun",
37
- outdir: "./dist",
38
- format: "esm",
39
- external: ["*.css"],
40
- });
41
-
42
-
43
- console.log(chalk.green("🐗 [Wildpig] Build done, time:"), chalk.blue(performance.now() - st, "ms"));
44
- console.log(chalk.green(`✨ [Wildpig] Start by command:`), chalk.blue(`bun run start`));
45
- }
46
-
1
+ import path from "node:path";
2
+ import { build as viteBuild } from "vite";
3
+ import { packageApiRoutes } from "../src/ApiRoutes";
4
+ import { IBuildOptions } from "../src/types";
5
+ import chalk from "chalk";
6
+ import { IWildpigConfig, setWildpigConfig } from "../src/config";
7
+ import { makeImportInitFile } from "../src/ImportInit";
8
+
9
+ const __dirname = import.meta.dirname;
10
+ const __rootdir = path.resolve(__dirname, "../../../"); // 项目根目录
11
+
12
+ const prebuild = async (options?: IBuildOptions) => {
13
+ const promises = [];
14
+ // 先编译客户端代码
15
+ promises.push(viteBuild({
16
+ configFile: path.resolve(__rootdir, "vite.config.ts"),
17
+ build: {
18
+ outDir: path.resolve(__rootdir, options?.outdir || "dist", "client"), // 输出目录
19
+ },
20
+ }));
21
+
22
+ // 打包api路由
23
+ promises.push(packageApiRoutes());
24
+
25
+ // 生成初始化文件的import代码
26
+ makeImportInitFile();
27
+
28
+ await Promise.all(promises);
29
+ };
30
+
31
+
32
+ export const build = async () => {
33
+ console.log(chalk.green("开始构建..."));
34
+
35
+ // 加载配置文件
36
+ let config: IWildpigConfig | undefined;
37
+ try{
38
+ config = (await import("../../../wildpig.config.ts"!)).default;
39
+ if(config)setWildpigConfig(config);
40
+ }catch(e){
41
+ console.error("获取wildpig.config.ts配置文件失败,请检查!", e);
42
+ }
43
+
44
+
45
+ // 准备阶段
46
+ process.env.NODE_ENV = "production";
47
+ const st = performance.now();
48
+
49
+ // 前处理
50
+ await prebuild();
51
+ // 正式编译
52
+ await Bun.build({
53
+ entrypoints: [path.resolve(__dirname, "../scripts/prod.ts")],
54
+ minify: false, // 压缩
55
+ target: "bun",
56
+ outdir: "./dist",
57
+ format: "esm",
58
+ external: ["*.css"],
59
+ });
60
+
61
+
62
+ console.log(chalk.green("🐗 [Wildpig] 构建完毕,总耗时:"), chalk.blue(performance.now() - st, "ms"));
63
+ console.log(chalk.green(`✨ [Wildpig] 用这条命令轻松启动:`), chalk.blue(`bun run start`));
64
+ }
65
+
47
66
  build();
package/scripts/dev.ts CHANGED
@@ -1,40 +1,70 @@
1
- import { createServer as createViteServer } from "vite";
2
- import path from "node:path";
3
- import { WildpigServer } from "../src/WildpigServer";
4
- import chalk from "chalk";
5
- import { IWildpigConfig, setWildpigConfig } from "../src/config";
6
-
7
-
8
- const __dirname = import.meta.dirname;
9
- const __rootdir = path.resolve(__dirname, "../../../");
10
-
11
-
12
-
13
- // 启动vite server
14
- const viteServer = await createViteServer({
15
- configFile: path.resolve(__rootdir, "./vite.config.ts"),
16
- });
17
- await viteServer.listen(viteServer.config.server.port);
18
-
19
-
20
-
21
- // 加载配置文件
22
- let config: IWildpigConfig | undefined;
23
- try{
24
- config = (await import("../../../wildpig.config.ts"!)).default;
25
- if(config)setWildpigConfig(config);
26
- }catch(e){
27
- console.error("获取wildpig.config.ts配置文件失败,请检查!", e);
28
- }
29
-
30
- // 运行初始化代码
31
- const initEntry = config?.initEntry || "src/index.ts";
32
- try{
33
- await import(("../../../" + initEntry)!);
34
- console.log(chalk.green("初始化代码执行成功:", initEntry));
35
- }catch(e){
36
- console.warn("未执行初始化代码,请检查文件是否存在:" + initEntry)
37
- }
38
-
39
- const wildpigServer = new WildpigServer(viteServer);
40
- await wildpigServer.createServer();
1
+ import { ConfigEnv, createServer as createViteServer, loadConfigFromFile, resolveConfig } from "vite";
2
+ import path from "node:path";
3
+ import { WildpigServer } from "../src/WildpigServer";
4
+ import chalk from "chalk";
5
+ import { IWildpigConfig, setWildpigConfig } from "../src/config";
6
+ import { wildpigGlobalMap } from "../src/utils/server/globalMap";
7
+
8
+
9
+ const __dirname = import.meta.dirname;
10
+ const __rootdir = path.resolve(__dirname, "../../../");
11
+
12
+ // 加载配置文件
13
+ let config: IWildpigConfig | undefined;
14
+ try{
15
+ config = (await import("../../../wildpig.config.ts"!)).default;
16
+ if(config)setWildpigConfig(config);
17
+ }catch(e){
18
+ console.error("获取wildpig.config.ts配置文件失败,请检查!", e);
19
+ }
20
+
21
+
22
+
23
+ // 启动vite server
24
+ const viteConfig = await loadConfigFromFile(
25
+ {
26
+ mode: "development",
27
+ command: "serve",
28
+ },
29
+ path.resolve(__rootdir, "./vite.config.ts")
30
+ );
31
+ if(!viteConfig)throw new Error("获取vite配置文件失败,请检查vite.config.ts文件是否存在!");
32
+
33
+ // 补充默认配置
34
+ const serverPort = config?.server?.port || 3000;
35
+ const userServerConfig = Object.assign({
36
+ host: "0.0.0.0",
37
+ port: 5173,
38
+ hmr: {
39
+ port: 5173,
40
+ // 客户端会向这个端口+路径发送websocket请求,用于hmr
41
+ clientPort: serverPort,
42
+ path: "/__wildpig_hmr",
43
+ },
44
+ }, viteConfig.config.server || {});
45
+
46
+ const viteServer = await createViteServer({
47
+ configFile: path.resolve(__rootdir, "./vite.config.ts"),
48
+ server: userServerConfig,
49
+ mode: "development",
50
+ command: "serve",
51
+ });
52
+ await viteServer.listen(userServerConfig.port);
53
+
54
+
55
+ // 运行初始化代码
56
+ const initEntry = config?.initEntry || "src/index.ts";
57
+ // 根据全局注册器判断是否已经运行过了
58
+ const isInitialized = wildpigGlobalMap.getItem("__wildpigInitialized");
59
+ if(!isInitialized){
60
+ try{
61
+ await import(("../../../" + initEntry)!);
62
+ console.log(chalk.green("初始化代码执行成功:", initEntry));
63
+ wildpigGlobalMap.setItem("__wildpigInitialized", true);
64
+ }catch(e){
65
+ console.warn("未执行初始化代码,请检查文件是否存在(或存在其他异常):" + initEntry, e)
66
+ }
67
+ }
68
+
69
+ const wildpigServer = new WildpigServer(viteServer);
70
+ await wildpigServer.createServer();
package/scripts/prod.ts CHANGED
@@ -1,26 +1,24 @@
1
- import { WildpigServer } from "../src/WildpigServer";
2
- import { IWildpigConfig, setWildpigConfig } from "../src/config";
3
-
4
- // 加载配置文件
5
- let config: IWildpigConfig | undefined;
6
- try{
7
- config = (await import("../../../wildpig.config.ts"!)).default;
8
- if(config)setWildpigConfig(config);
9
- }catch(e){
10
- console.error("获取wildpig.config.ts配置文件失败,请检查!");
11
- }
12
-
13
-
14
- // 运行初始化代码
15
- const initEntry = config?.initEntry || "src/index.ts";
16
- try{
17
- const initPath = `"../../../${initEntry}`.replace(".ts", "");
18
- await import(initPath);
19
- }catch(e){
20
- console.warn("未执行初始化代码,请检查文件是否存在:" + initEntry);
21
- }
22
-
23
-
24
-
25
- const wildpigServer = new WildpigServer();
1
+ import { WildpigServer } from "../src/WildpigServer";
2
+ import { IWildpigConfig, setWildpigConfig } from "../src/config";
3
+
4
+ // 加载配置文件
5
+ let config: IWildpigConfig | undefined;
6
+ try{
7
+ config = (await import("../../../wildpig.config.ts"!)).default;
8
+ if(config)setWildpigConfig(config);
9
+ }catch(e){
10
+ console.error("获取wildpig.config.ts配置文件失败,请检查!");
11
+ }
12
+
13
+
14
+ // 运行初始化代码
15
+ try{
16
+ await import("../build/import-init");
17
+ }catch(e){
18
+ console.warn("未执行初始化代码,请检查文件是否存在。");
19
+ }
20
+
21
+
22
+
23
+ const wildpigServer = new WildpigServer();
26
24
  await wildpigServer.createServer();
package/src/ApiRoutes.ts CHANGED
@@ -1,100 +1,100 @@
1
- import { readdirSync, statSync, writeFileSync } from "fs";
2
- import path from "node:path";
3
- const __dirname = import.meta.dirname;
4
-
5
- const middleware = (await import("../../../src/endpoints/middleware"!)).middleware;
6
-
7
- const getFilePaths = (dir: string) => {
8
- const res: string[] = [];
9
- const files = readdirSync(dir);
10
- files.forEach(file => {
11
- const filePath = `${dir}/${file}`;
12
- const stat = statSync(filePath);
13
- if (stat.isDirectory()) {
14
- res.push(...getFilePaths(filePath));
15
- } else {
16
- res.push(filePath);
17
- }
18
- });
19
- return res;
20
- }
21
-
22
-
23
- const makeDynamicRoute = (route: string) => route.replaceAll(/\[([^\]]*)\]/g, ':$1');
24
-
25
- export const makeApiRoutePathObj = () => {
26
- // 扫描用户代码路径
27
- const apiDir = "./src/endpoints";
28
- const apiPaths = getFilePaths(apiDir);
29
- const result: Record<string, string> = {};
30
-
31
- for(const apiPath of apiPaths) {
32
- const importPath = apiPath.replace("./src/endpoints", "@/endpoints");
33
- if(!apiPath.includes("index.ts")) continue;
34
- const route = apiPath.replace("./src/endpoints", "").replace("/index.ts", "");
35
- result[route] = importPath.replace(".ts", "");
36
- }
37
- return result;
38
- }
39
-
40
- /**
41
- * 生成api路由的配置文件
42
- */
43
- export const packageApiRoutes = async () => {
44
- const apiRoutes = makeApiRoutePathObj();
45
- let identId = 0;
46
- let importsText = `import { middleware } from "@/endpoints/middleware" \n`;
47
- let routesText = "export default {\n";
48
- for(const route of Object.keys(apiRoutes)) {
49
- const importPath = apiRoutes[route];
50
- // 尝试从文件中获取路由
51
- const module = await import(importPath);
52
- // 没有接口,就跳过
53
- if(!module.GET && !module.POST) continue;
54
- // 标识id
55
- identId ++;
56
- importsText += `import {\n`;
57
- routesText += `\t"${makeDynamicRoute(route)}": {\n`;
58
- if(module.GET) {
59
- importsText += `\tGET as GET${identId},\n`;
60
- routesText += `\t\tGET: (req: any) => middleware(req, GET${identId}),\n`;
61
- }
62
- if(module.POST) {
63
- importsText += `\tPOST as POST${identId},\n`;
64
- routesText += `\t\tPOST: (req: any) => middleware(req, POST${identId}),\n`;
65
- }
66
- importsText += `} from "${importPath}";\n`;
67
- routesText += `\t},\n`;
68
- }
69
- routesText += "}";
70
- writeFileSync(path.resolve(__dirname, "../build/built-api-routes.ts"), importsText + "\n" + routesText);
71
- };
72
-
73
- /**
74
- * 获取api路由,dev模式,直接导入
75
- */
76
- export const getApiRouteModules = async (mode: "dev" | "prod") => {
77
- if(mode === "dev"){
78
- const apiRoutes = makeApiRoutePathObj();
79
- const result: Record<string, {
80
- GET?: (req: any) => Promise<Response>,
81
- POST?: (req: any) => Promise<Response>
82
- } > = {};
83
- for(const route of Object.keys(apiRoutes)) {
84
- const importPath = apiRoutes[route];
85
- const module = await import(importPath.replace(".ts", ""));
86
- if(!module.GET && !module.POST) continue;
87
-
88
- // 新建一个路由
89
- const dynamicRoute = makeDynamicRoute(route);
90
- result[dynamicRoute] = {};
91
- if(module.GET) result[dynamicRoute].GET = (req: any) => middleware(req, module.GET);
92
- if(module.POST) result[dynamicRoute].POST = (req: any) => middleware(req, module.POST);
93
- }
94
- return result;
95
- } else {
96
- // prod模式,直接从文件中导入
97
- const module = await import("../build/built-api-routes"!);
98
- return module.default;
99
- }
1
+ import { readdirSync, statSync, writeFileSync } from "fs";
2
+ import path from "node:path";
3
+ const __dirname = import.meta.dirname;
4
+
5
+ const middleware = (await import("../../../src/endpoints/middleware"!)).middleware;
6
+
7
+ const getFilePaths = (dir: string) => {
8
+ const res: string[] = [];
9
+ const files = readdirSync(dir);
10
+ files.forEach(file => {
11
+ const filePath = `${dir}/${file}`;
12
+ const stat = statSync(filePath);
13
+ if (stat.isDirectory()) {
14
+ res.push(...getFilePaths(filePath));
15
+ } else {
16
+ res.push(filePath);
17
+ }
18
+ });
19
+ return res;
20
+ }
21
+
22
+
23
+ const makeDynamicRoute = (route: string) => route.replaceAll(/\[([^\]]*)\]/g, ':$1');
24
+
25
+ export const makeApiRoutePathObj = () => {
26
+ // 扫描用户代码路径
27
+ const apiDir = "./src/endpoints";
28
+ const apiPaths = getFilePaths(apiDir);
29
+ const result: Record<string, string> = {};
30
+
31
+ for(const apiPath of apiPaths) {
32
+ const importPath = apiPath.replace("./src/endpoints", "@/endpoints");
33
+ if(!apiPath.includes("index.ts")) continue;
34
+ const route = apiPath.replace("./src/endpoints", "").replace("/index.ts", "");
35
+ result[route] = importPath.replace(".ts", "");
36
+ }
37
+ return result;
38
+ }
39
+
40
+ /**
41
+ * 生成api路由的配置文件
42
+ */
43
+ export const packageApiRoutes = async () => {
44
+ const apiRoutes = makeApiRoutePathObj();
45
+ let identId = 0;
46
+ let importsText = `import { middleware } from "@/endpoints/middleware" \n`;
47
+ let routesText = "export default {\n";
48
+ for(const route of Object.keys(apiRoutes)) {
49
+ const importPath = apiRoutes[route];
50
+ // 尝试从文件中获取路由
51
+ const module = await import(importPath);
52
+ // 没有接口,就跳过
53
+ if(!module.GET && !module.POST) continue;
54
+ // 标识id
55
+ identId ++;
56
+ importsText += `import {\n`;
57
+ routesText += `\t"${makeDynamicRoute(route)}": {\n`;
58
+ if(module.GET) {
59
+ importsText += `\tGET as GET${identId},\n`;
60
+ routesText += `\t\tGET: (req: any) => middleware(req, GET${identId}),\n`;
61
+ }
62
+ if(module.POST) {
63
+ importsText += `\tPOST as POST${identId},\n`;
64
+ routesText += `\t\tPOST: (req: any) => middleware(req, POST${identId}),\n`;
65
+ }
66
+ importsText += `} from "${importPath}";\n`;
67
+ routesText += `\t},\n`;
68
+ }
69
+ routesText += "}";
70
+ writeFileSync(path.resolve(__dirname, "../build/built-api-routes.ts"), importsText + "\n" + routesText);
71
+ };
72
+
73
+ /**
74
+ * 获取api路由,dev模式,直接导入
75
+ */
76
+ export const getApiRouteModules = async (mode: "dev" | "prod") => {
77
+ if(mode === "dev"){
78
+ const apiRoutes = makeApiRoutePathObj();
79
+ const result: Record<string, {
80
+ GET?: (req: any) => Promise<Response>,
81
+ POST?: (req: any) => Promise<Response>
82
+ } > = {};
83
+ for(const route of Object.keys(apiRoutes)) {
84
+ const importPath = apiRoutes[route];
85
+ const module = await import(importPath.replace(".ts", ""));
86
+ if(!module.GET && !module.POST) continue;
87
+
88
+ // 新建一个路由
89
+ const dynamicRoute = makeDynamicRoute(route);
90
+ result[dynamicRoute] = {};
91
+ if(module.GET) result[dynamicRoute].GET = (req: any) => middleware(req, module.GET);
92
+ if(module.POST) result[dynamicRoute].POST = (req: any) => middleware(req, module.POST);
93
+ }
94
+ return result;
95
+ } else {
96
+ // prod模式,直接从文件中导入
97
+ const module = await import("../build/built-api-routes"!);
98
+ return module.default;
99
+ }
100
100
  }
@@ -0,0 +1,23 @@
1
+ import chalk from "chalk";
2
+ import { getWildpigConfig } from "./config";
3
+ import fs from "fs";
4
+ import path from "path";
5
+
6
+
7
+
8
+ export const makeImportInitFile = () => {
9
+ // 生成一个仅导入初始化代码的文件
10
+ const config = getWildpigConfig();
11
+ if(!config) throw new Error("获取wildpig.config.ts配置文件失败。");
12
+
13
+ const initEntry = config.initEntry || "src/index.ts";
14
+ if(!fs.existsSync(initEntry)){
15
+ console.warn("初始化文件 " + initEntry + " 不存在,请检查!");
16
+ return;
17
+ }
18
+ const initEntryPath = "../../../" + initEntry;
19
+
20
+
21
+ fs.writeFileSync(path.resolve(__dirname, "../build/import-init.ts"), `//临时文件,仅用于打包时静态导入\n import "${initEntryPath}"\nexport default undefined;`);
22
+ console.log(chalk.green("初始化文件生成成功: " + path.resolve(__dirname, "../build/import-init.ts")));
23
+ }