wildpig 2.0.1 → 2.2.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 +1 -1
- package/build/built-api-routes.ts +0 -22
- package/index.html +1 -1
- package/index.ts +16 -5
- package/package.json +2 -4
- package/scripts/build.ts +14 -37
- package/scripts/dev.ts +40 -0
- package/scripts/prod.ts +26 -0
- package/src/WildpigServer.ts +138 -0
- package/src/config.ts +22 -0
- package/{entry → src/entry}/client.tsx +2 -3
- package/{entry → src/entry}/server.tsx +3 -3
- package/src/hooks/afterStartServer.ts +19 -0
- package/{router → src/router}/ServerDataGuard.tsx +2 -2
- package/{router → src/router}/index.ts +2 -5
- package/src/router/pageRoutes.ts +4 -0
- package/src/utils/server/globalMap.ts +30 -0
- package/scripts/prodServer.ts +0 -119
- package/scripts/server.ts +0 -158
- /package/{scripts/apiRoutes.ts → src/ApiRoutes.ts} +0 -0
- /package/{router → src/router}/types.ts +0 -0
- /package/{store → src/store}/serverDataStore.tsx +0 -0
- /package/{scripts → src}/types.ts +0 -0
- /package/{utils → src/utils}/client/environment.ts +0 -0
package/README.md
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { middleware } from "@/api/middleware"
|
|
2
|
-
import {
|
|
3
|
-
GET as GET1,
|
|
4
|
-
} from "#/src/api/hello/index";
|
|
5
|
-
import {
|
|
6
|
-
GET as GET2,
|
|
7
|
-
} from "#/src/api/server-data/home/index";
|
|
8
|
-
import {
|
|
9
|
-
GET as GET3,
|
|
10
|
-
} from "#/src/api/server-data/post/index";
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
"/api/hello": {
|
|
14
|
-
GET: (req: any) => middleware(req, GET1),
|
|
15
|
-
},
|
|
16
|
-
"/api/server-data/home": {
|
|
17
|
-
GET: (req: any) => middleware(req, GET2),
|
|
18
|
-
},
|
|
19
|
-
"/api/server-data/post": {
|
|
20
|
-
GET: (req: any) => middleware(req, GET3),
|
|
21
|
-
},
|
|
22
|
-
}
|
package/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title><!--title--></title>
|
|
7
|
-
<script type="module" src="/node_modules/wildpig/entry/client.tsx"></script>
|
|
7
|
+
<script type="module" src="/node_modules/wildpig/src/entry/client.tsx"></script>
|
|
8
8
|
<!--server-data-->
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/index.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { onAfterStartServer } from "./src/hooks/afterStartServer";
|
|
3
|
+
import { defineConfig } from "./src/config";
|
|
4
|
+
import { serverDataStore } from "./src/store/serverDataStore";
|
|
5
|
+
import { ServerDataGuard } from "./src/router/ServerDataGuard";
|
|
6
|
+
import type { WildPigRouteObject } from "./src/router/types";
|
|
4
7
|
|
|
8
|
+
export {
|
|
9
|
+
// config
|
|
10
|
+
defineConfig,
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
// hook
|
|
13
|
+
onAfterStartServer,
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// router
|
|
17
|
+
serverDataStore,
|
|
18
|
+
ServerDataGuard,
|
|
19
|
+
WildPigRouteObject
|
|
9
20
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wildpig",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"author": "eriktse",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"peerDependencies": {
|
|
@@ -25,9 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"license": "ISC",
|
|
27
27
|
"scripts": {
|
|
28
|
-
"
|
|
29
|
-
"build": "wildpig build",
|
|
30
|
-
"start": "wildpig start",
|
|
28
|
+
"build": "bun run build.ts",
|
|
31
29
|
"local-publish": "yalc publish"
|
|
32
30
|
}
|
|
33
31
|
}
|
package/scripts/build.ts
CHANGED
|
@@ -1,70 +1,47 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { packageApiRoutes } from "./apiRoutes";
|
|
3
2
|
import { build as viteBuild } from "vite";
|
|
4
|
-
import {
|
|
3
|
+
import { packageApiRoutes } from "../src/ApiRoutes";
|
|
4
|
+
import { IBuildOptions } from "../src/types";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
|
|
7
7
|
const __rootdir = path.resolve(__dirname, "../../../"); // 项目根目录
|
|
8
8
|
|
|
9
|
-
const prebuild = async (options
|
|
9
|
+
const prebuild = async (options?: IBuildOptions) => {
|
|
10
10
|
const promises = [];
|
|
11
11
|
// 先编译客户端代码
|
|
12
12
|
promises.push(viteBuild({
|
|
13
13
|
configFile: path.resolve(__rootdir, "vite.config.ts"),
|
|
14
14
|
build: {
|
|
15
|
-
outDir: path.resolve(__rootdir, options
|
|
15
|
+
outDir: path.resolve(__rootdir, options?.outdir || "dist", "client"), // 输出目录
|
|
16
16
|
},
|
|
17
17
|
}));
|
|
18
|
-
// promises.push(viteBuild({
|
|
19
|
-
// configFile: path.resolve(__rootdir, "vite.config.ts"),
|
|
20
|
-
// build: {
|
|
21
|
-
// rollupOptions:{
|
|
22
|
-
// input: path.resolve(__dirname, "../entry/server.tsx"),
|
|
23
|
-
// },
|
|
24
|
-
// outDir: path.resolve(__rootdir, options.outdir || "dist", "server"), // 输出目录
|
|
25
|
-
// ssr: true, // 开启ssr
|
|
26
|
-
// },
|
|
27
|
-
// }));
|
|
28
18
|
promises.push(packageApiRoutes());
|
|
29
19
|
await Promise.all(promises);
|
|
30
20
|
};
|
|
31
21
|
|
|
32
22
|
|
|
33
|
-
export const build = async (
|
|
34
|
-
options = Object.assign({
|
|
35
|
-
entry: "server.ts",
|
|
36
|
-
outdir: "dist",
|
|
37
|
-
minify: true,
|
|
38
|
-
target: "bun",
|
|
39
|
-
external: [],
|
|
40
|
-
}, options || {});
|
|
41
|
-
|
|
42
|
-
|
|
23
|
+
export const build = async () => {
|
|
43
24
|
console.log(chalk.green("开始构建..."));
|
|
44
|
-
console.log("构建参数:", options);
|
|
45
25
|
|
|
46
26
|
// 准备阶段
|
|
47
27
|
process.env.NODE_ENV = "production";
|
|
48
28
|
const st = performance.now();
|
|
49
29
|
|
|
50
|
-
|
|
51
30
|
// 前处理
|
|
52
|
-
await prebuild(
|
|
31
|
+
await prebuild();
|
|
53
32
|
// 正式编译
|
|
54
33
|
await Bun.build({
|
|
55
|
-
entrypoints: [path.resolve(
|
|
56
|
-
minify:
|
|
57
|
-
target:
|
|
58
|
-
outdir:
|
|
34
|
+
entrypoints: [path.resolve(__dirname, "../scripts/prod.ts")],
|
|
35
|
+
minify: true, // 压缩
|
|
36
|
+
target: "bun",
|
|
37
|
+
outdir: "./dist",
|
|
59
38
|
format: "esm",
|
|
60
|
-
external: [
|
|
61
|
-
define: {
|
|
62
|
-
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
63
|
-
},
|
|
39
|
+
external: [],
|
|
64
40
|
});
|
|
65
41
|
|
|
66
42
|
|
|
67
43
|
console.log(chalk.green("🐗 [Wildpig] Build done, time:"), chalk.blue(performance.now() - st, "ms"));
|
|
68
44
|
console.log(chalk.green(`✨ [Wildpig] Start by command:`), chalk.blue(`bun run start`));
|
|
69
|
-
|
|
70
|
-
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
build();
|
package/scripts/dev.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
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();
|
package/scripts/prod.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
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();
|
|
26
|
+
await wildpigServer.createServer();
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { getApiRouteModules } from "./ApiRoutes";
|
|
2
|
+
import { type ViteDevServer} from "vite";
|
|
3
|
+
import { matchRoutes } from "react-router";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { WildPigRouteObject } from "./router/types";
|
|
6
|
+
import packageInfo from "../package.json";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
|
|
10
|
+
const __dirname = import.meta.dirname;
|
|
11
|
+
import { handleAfterStartServer } from "./hooks/afterStartServer";
|
|
12
|
+
import { getWildpigConfig } from "./config";
|
|
13
|
+
|
|
14
|
+
const env = process.env;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export class WildpigServer {
|
|
18
|
+
private viteServer: ViteDevServer | undefined;
|
|
19
|
+
constructor(viteServer?: ViteDevServer | undefined){
|
|
20
|
+
if(viteServer)this.viteServer = viteServer;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async frontHandler (apiModules: any){
|
|
24
|
+
return async (request: Request) => {
|
|
25
|
+
// 判断pathname是否匹配pageRoutes
|
|
26
|
+
const url = new URL(request.url);
|
|
27
|
+
|
|
28
|
+
// 判断是否是vite请求
|
|
29
|
+
if(url.pathname.includes(".") || url.pathname.startsWith("/@") || url.pathname.startsWith("/assets")){
|
|
30
|
+
if(this.viteServer){// 交给vite
|
|
31
|
+
const viteURL = new URL(request.url);
|
|
32
|
+
viteURL.port = this.viteServer.config.server.port.toString();
|
|
33
|
+
const response = await fetch(viteURL.toString(), {
|
|
34
|
+
method: request.method,
|
|
35
|
+
headers: request.headers,
|
|
36
|
+
body: request.body,
|
|
37
|
+
});
|
|
38
|
+
return response.clone();
|
|
39
|
+
}else{// production环境,直接返回文件
|
|
40
|
+
const filepath = path.resolve(__dirname, "./client" + url.pathname);
|
|
41
|
+
// 检查文件是否存在
|
|
42
|
+
if(fs.existsSync(filepath) && fs.statSync(filepath).isFile()){
|
|
43
|
+
return new Response(Bun.file(filepath), {
|
|
44
|
+
headers: {
|
|
45
|
+
"Cache-Control": "public, max-age=864000" // 10 天缓存
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// 文件不存在
|
|
50
|
+
return new Response("Not Found", {status: 404});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// 服务端请求,获取服务端数据
|
|
54
|
+
const routes = this.viteServer ? (await this.viteServer.ssrLoadModule("/node_modules/wildpig/src/router/index.ts"!)).default as WildPigRouteObject[] : (await import("./router/index")).default;
|
|
55
|
+
const matches = matchRoutes(routes, url.pathname);
|
|
56
|
+
if(!matches)return new Response("404 Not Found", { status: 404 });
|
|
57
|
+
|
|
58
|
+
// 请求服务端数据
|
|
59
|
+
const matchRoute = matches.at(-1)!;
|
|
60
|
+
let serverDataApi = matchRoute.route.serverDataApi;
|
|
61
|
+
let serverData = await (async () => {
|
|
62
|
+
if(!serverDataApi)return undefined;
|
|
63
|
+
const prefixUrl = request.url.split("/")[0] + "//" + request.url.split("/")[2];
|
|
64
|
+
// 需要请求服务端数据, 替换动态参数
|
|
65
|
+
for(const [key, value] of Object.entries(matchRoute.params)){
|
|
66
|
+
if(value)serverDataApi = serverDataApi.replace(":" + key, value);
|
|
67
|
+
}
|
|
68
|
+
// 加上当前request的query参数
|
|
69
|
+
for(const [key, value] of new URLSearchParams(request.url.split("?")[1]).entries()){
|
|
70
|
+
if(serverDataApi.includes(key + "="))continue; // 已经有这个参数了
|
|
71
|
+
serverDataApi += (serverDataApi.includes("?") ? "&" : "?") + key + "=" + value;
|
|
72
|
+
}
|
|
73
|
+
const serverRequest = new Request({
|
|
74
|
+
...request.clone(),
|
|
75
|
+
url: prefixUrl + serverDataApi, // 替换url
|
|
76
|
+
});
|
|
77
|
+
serverRequest.headers.set("wildpig-server-data-api", serverDataApi);
|
|
78
|
+
const pathname = serverDataApi.split("?")[0]; // 获取路径
|
|
79
|
+
const handler = apiModules?.[pathname]?.GET;
|
|
80
|
+
if(!handler)return undefined; // 没有对应的handler
|
|
81
|
+
const serverData = await handler(serverRequest).then((r: Response) => r.json());
|
|
82
|
+
return serverData;
|
|
83
|
+
})();
|
|
84
|
+
|
|
85
|
+
// 构造响应页面
|
|
86
|
+
// 1. 读取 index.html
|
|
87
|
+
const template = this.viteServer ? await this.viteServer.transformIndexHtml(request.url, fs.readFileSync('./index.html', 'utf-8')) : fs.readFileSync(path.resolve(__dirname, './client/index.html'), 'utf-8');
|
|
88
|
+
// 2. 获取渲染函数
|
|
89
|
+
const { render } = this.viteServer ? await this.viteServer.ssrLoadModule("/node_modules/wildpig/src/entry/server.tsx") : await import('./entry/server')
|
|
90
|
+
// 3. 获取应用程序 HTML
|
|
91
|
+
const appHtml = await render(request, serverData);
|
|
92
|
+
|
|
93
|
+
// 4. 注入渲染后的应用程序 HTML 到模板中。
|
|
94
|
+
const html = template
|
|
95
|
+
.replace(`<!--ssr-outlet-->`, () => appHtml)
|
|
96
|
+
.replace(`<!--title-->`, () => serverData?.title || "title")
|
|
97
|
+
.replace(`<!--server-data-->`, () => `<script>window.__SERVER_DATA__ = ${JSON.stringify(serverData)};</script>`);
|
|
98
|
+
|
|
99
|
+
return new Response(html, {
|
|
100
|
+
headers: {
|
|
101
|
+
"content-type": "text/html; charset=utf-8",
|
|
102
|
+
"Access-Control-Allow-Origin": "*",
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
/** 启动后的描述性文字 */
|
|
110
|
+
async afterStart () {
|
|
111
|
+
const config = getWildpigConfig();
|
|
112
|
+
// 启动后的文字
|
|
113
|
+
console.log(chalk.blue.bgGreen(` 🐗 WildPig version ${packageInfo?.version} by ${packageInfo?.author} `));
|
|
114
|
+
console.log(chalk.green(" Strong & Fast Fullstack Framework\n"));
|
|
115
|
+
console.log(chalk.green("✨ WildPig is running on port " + (config?.server?.port || 3000)));
|
|
116
|
+
console.log(chalk.yellow(`💻 Wildpig is Running in ${chalk.yellow.bold(env.NODE_ENV)} mode.`));
|
|
117
|
+
if(this.viteServer)console.log(chalk.green("⚡ Vite server is running on port " + this.viteServer.config.server?.port));
|
|
118
|
+
console.log(chalk.green(`🔗 Click to debug in Browser: http://localhost:${config?.server?.port || 3000}`));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async createServer () {
|
|
122
|
+
const config = getWildpigConfig();
|
|
123
|
+
const apiModules = await getApiRouteModules(env.NODE_ENV === "development" ? "dev" : "prod");
|
|
124
|
+
const server = Bun.serve({
|
|
125
|
+
port: config?.server?.port || 3000,
|
|
126
|
+
hostname: config?.server?.host || "0.0.0.0",
|
|
127
|
+
routes:{
|
|
128
|
+
...apiModules,
|
|
129
|
+
"/*": await this.frontHandler(apiModules),
|
|
130
|
+
},
|
|
131
|
+
development: env.NODE_ENV === "development",
|
|
132
|
+
})
|
|
133
|
+
await this.afterStart();
|
|
134
|
+
// 服务器创建好了, 触发afterStartServer回调
|
|
135
|
+
await handleAfterStartServer(server);
|
|
136
|
+
return server;
|
|
137
|
+
}
|
|
138
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { wildpigGlobalMap } from "./utils/server/globalMap";
|
|
2
|
+
|
|
3
|
+
export interface IWildpigConfig {
|
|
4
|
+
server?: {
|
|
5
|
+
host?: string,
|
|
6
|
+
port?: number | string,
|
|
7
|
+
},
|
|
8
|
+
/**
|
|
9
|
+
* 入口文件,默认为src/index.ts,用户可以在这里编写一些自定义的初始化代码
|
|
10
|
+
*/
|
|
11
|
+
initEntry?: string,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export const defineConfig = (config: IWildpigConfig) => config;
|
|
17
|
+
|
|
18
|
+
export const getWildpigConfig = () => wildpigGlobalMap.getItem("__wildpigConfig") as IWildpigConfig | undefined;
|
|
19
|
+
|
|
20
|
+
export const setWildpigConfig = (_config: IWildpigConfig) => {
|
|
21
|
+
wildpigGlobalMap.setItem("__wildpigConfig", _config);
|
|
22
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { createBrowserRouter } from "react-router";
|
|
2
|
-
import
|
|
2
|
+
import routes from "../router";
|
|
3
3
|
import { hydrateRoot } from "react-dom/client"
|
|
4
4
|
// 这个文件由Vite加载
|
|
5
|
-
const { App } = await import('
|
|
6
|
-
|
|
5
|
+
const { App } = await import('../../../../src/App'!);
|
|
7
6
|
|
|
8
7
|
const render = () => {
|
|
9
8
|
// 获取serverData
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { renderToString } from "react-dom/server"
|
|
2
2
|
import { createStaticHandler, createStaticRouter } from "react-router"
|
|
3
|
-
import
|
|
3
|
+
import routes from "../router";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const { App } = await import("
|
|
5
|
+
|
|
6
|
+
const { App } = await import("../../../../src/App"!);
|
|
7
7
|
|
|
8
8
|
export const render = async (req: Request, serverData?: any): Promise<string> => {
|
|
9
9
|
// 1. 创建处理器
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Server } from "bun";
|
|
2
|
+
import { wildpigGlobalMap } from "../utils/server/globalMap";
|
|
3
|
+
// 初始化或获取全局回调数组
|
|
4
|
+
const getCallbacks = () => {
|
|
5
|
+
if (!wildpigGlobalMap.getItem("__wildpigAfterStartServerCallbacks")) {
|
|
6
|
+
wildpigGlobalMap.setItem("__wildpigAfterStartServerCallbacks", []);
|
|
7
|
+
}
|
|
8
|
+
return wildpigGlobalMap.getItem("__wildpigAfterStartServerCallbacks") as ((server: Server<undefined>) => void)[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const onAfterStartServer = async (cb: (server: Server<undefined>) => void) => {
|
|
12
|
+
getCallbacks().push(cb);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const handleAfterStartServer = async (server: Server<undefined>) => {
|
|
16
|
+
for (const cb of getCallbacks()) {
|
|
17
|
+
cb(server);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect } from "react"
|
|
2
2
|
import { matchRoutes, Outlet, useLocation, useNavigate } from "react-router"
|
|
3
3
|
import { serverDataStore } from "../store/serverDataStore";
|
|
4
|
-
import {
|
|
4
|
+
import { pageRoutes } from "./pageRoutes";
|
|
5
5
|
|
|
6
6
|
export const ServerDataGuard = () => {
|
|
7
7
|
const location = useLocation();
|
|
@@ -11,7 +11,7 @@ export const ServerDataGuard = () => {
|
|
|
11
11
|
serverDataStore.set(undefined);
|
|
12
12
|
|
|
13
13
|
const pathname = location.pathname;
|
|
14
|
-
const matches = matchRoutes(
|
|
14
|
+
const matches = matchRoutes(pageRoutes, pathname);
|
|
15
15
|
const lastMatch = matches?.at(-1);
|
|
16
16
|
if(!lastMatch) {
|
|
17
17
|
// 404
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
+
import { pageRoutes } from "./pageRoutes";
|
|
1
2
|
import { ServerDataGuard } from "./ServerDataGuard";
|
|
2
3
|
import { WildPigRouteObject } from "./types";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
let pageRoutes: WildPigRouteObject[] = (await import("../../../src/router/routes"!)).default;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export const routes = [
|
|
5
|
+
export default [
|
|
9
6
|
{
|
|
10
7
|
path: "/",
|
|
11
8
|
Component: ServerDataGuard,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// 全局注册器
|
|
2
|
+
declare global {
|
|
3
|
+
var __wildpigGlobalMap: Map<string, any>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const getWildpigGlobalMap = () => {
|
|
7
|
+
if (!globalThis.__wildpigGlobalMap) {
|
|
8
|
+
globalThis.__wildpigGlobalMap = new Map();
|
|
9
|
+
}
|
|
10
|
+
return globalThis.__wildpigGlobalMap;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const getItem = <T>(key: string): T | undefined => {
|
|
14
|
+
if (!getWildpigGlobalMap().has(key)) return undefined;
|
|
15
|
+
return getWildpigGlobalMap().get(key) as T;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const setItem = <T>(key: string, value: T) => {
|
|
19
|
+
getWildpigGlobalMap().set(key, value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getAll = () => {
|
|
23
|
+
return getWildpigGlobalMap();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const wildpigGlobalMap = {
|
|
27
|
+
getItem,
|
|
28
|
+
setItem,
|
|
29
|
+
getAll
|
|
30
|
+
}
|
package/scripts/prodServer.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { getApiRouteModules } from "./apiRoutes";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import { matchRoutes } from "react-router";
|
|
4
|
-
import packageJson from "../package.json";
|
|
5
|
-
import { ICreateServerOptions } from "./types";
|
|
6
|
-
import { routes } from "../router";
|
|
7
|
-
const env = process.env;
|
|
8
|
-
// 用户代码(动态导入)
|
|
9
|
-
import chalk from "chalk";
|
|
10
|
-
|
|
11
|
-
const getPackageInfo = async () => {
|
|
12
|
-
return packageJson;
|
|
13
|
-
}
|
|
14
|
-
const packageInfo = await getPackageInfo();
|
|
15
|
-
|
|
16
|
-
/** 启动后的描述性文字 */
|
|
17
|
-
const afterStart = (options: ICreateServerOptions) => {
|
|
18
|
-
// 启动后的文字
|
|
19
|
-
console.log(` __ __ _ _ _ ____ _
|
|
20
|
-
\\ \\ / /(_)| | __| | | _ \\ (_) __ _
|
|
21
|
-
\\ \\ /\\ / / | || | / _\` | | |_) || | / _\` |
|
|
22
|
-
\\ V V / | || || (_| | | __/ | || (_| |
|
|
23
|
-
\\_/\\_/ |_||_| \\__,_| |_| |_| \\__, |
|
|
24
|
-
|___/ `)
|
|
25
|
-
console.log(chalk.blue.bgGreen(` 🐗 WildPig version ${packageInfo?.version} by ${packageInfo?.author} `));
|
|
26
|
-
console.log(chalk.green(" Strong & Fast Fullstack Framework\n"));
|
|
27
|
-
console.log(chalk.green("✨ WildPig is running on port " + env.PORT || 3000));
|
|
28
|
-
console.log(chalk.green("💻 Wildpig is Running in production mode."));
|
|
29
|
-
console.log(chalk.green(`🔗 Click to play in Browser: http://localhost:${options.port}`));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default async (options?: ICreateServerOptions) => {
|
|
33
|
-
options = Object.assign({
|
|
34
|
-
port: 3000,
|
|
35
|
-
host: "0.0.0.0",
|
|
36
|
-
showInfo: true,
|
|
37
|
-
}, options || {});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const apiModules = await getApiRouteModules("prod") as any;
|
|
41
|
-
const server = Bun.serve({
|
|
42
|
-
port: options.port,
|
|
43
|
-
hostname: options.host,
|
|
44
|
-
routes:{
|
|
45
|
-
...apiModules,
|
|
46
|
-
"/*": async (request: Request) => {
|
|
47
|
-
// 判断pathname是否匹配pageRoutes
|
|
48
|
-
const url = new URL(request.url);
|
|
49
|
-
if(url.pathname.includes(".") || url.pathname.startsWith("/@") || url.pathname.startsWith("/assets")){
|
|
50
|
-
const filepath = "./client" + url.pathname;
|
|
51
|
-
// 检查文件是否存在
|
|
52
|
-
if(fs.existsSync(filepath) && fs.statSync(filepath).isFile()){
|
|
53
|
-
return new Response(Bun.file(filepath), {
|
|
54
|
-
headers: {
|
|
55
|
-
"Cache-Control": "public, max-age=864000" // 10 天缓存
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
// 文件不存在
|
|
60
|
-
return new Response("Not Found", {status: 404});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 请求服务端数据
|
|
64
|
-
const matches = matchRoutes(routes, url.pathname);
|
|
65
|
-
if(!matches)return new Response("Not Found", {status: 404});
|
|
66
|
-
|
|
67
|
-
const matchRoute = matches.at(-1)!;
|
|
68
|
-
let serverDataApi = matchRoute.route.serverDataApi;
|
|
69
|
-
const getServerData = async () => {
|
|
70
|
-
if(!serverDataApi)return undefined;
|
|
71
|
-
const prefixUrl = request.url.split("/")[0] + "//" + request.url.split("/")[2];
|
|
72
|
-
// 需要请求服务端数据, 替换动态参数
|
|
73
|
-
for(const [key, value] of Object.entries(matchRoute.params)){
|
|
74
|
-
if(value)serverDataApi = serverDataApi.replace(":" + key, value);
|
|
75
|
-
}
|
|
76
|
-
// 加上当前request的query参数
|
|
77
|
-
for(const [key, value] of new URLSearchParams(request.url.split("?")[1]).entries()){
|
|
78
|
-
if(serverDataApi.includes(key + "="))continue; // 已经有这个参数了
|
|
79
|
-
serverDataApi += (serverDataApi.includes("?") ? "&" : "?") + key + "=" + value;
|
|
80
|
-
}
|
|
81
|
-
const serverRequest = new Request({
|
|
82
|
-
...request.clone(),
|
|
83
|
-
url: prefixUrl + serverDataApi, // 替换url
|
|
84
|
-
});
|
|
85
|
-
serverRequest.headers.set("wildpig-server-data-api", serverDataApi);
|
|
86
|
-
const pathname = serverDataApi.split("?")[0]; // 获取路径
|
|
87
|
-
const handler = apiModules?.[pathname]?.GET;
|
|
88
|
-
if(!handler)return undefined; // 没有对应的handler
|
|
89
|
-
const serverData = await handler(serverRequest).then((r: Response) => r.json());
|
|
90
|
-
return serverData;
|
|
91
|
-
};
|
|
92
|
-
let serverData = await getServerData();
|
|
93
|
-
|
|
94
|
-
// 1. 读取 index.html
|
|
95
|
-
const template = fs.readFileSync('./client/index.html', 'utf-8');
|
|
96
|
-
// 2. 获取渲染函数
|
|
97
|
-
const { render } = await import("../entry/server"!);
|
|
98
|
-
// 3. 获取应用程序 HTML
|
|
99
|
-
const appHtml = await render(request, serverData);
|
|
100
|
-
|
|
101
|
-
// 4. 注入渲染后的应用程序 HTML 到模板中。
|
|
102
|
-
const html = template
|
|
103
|
-
.replace(`<!--ssr-outlet-->`, () => appHtml)
|
|
104
|
-
.replace(`<!--title-->`, () => serverData?.title || "title")
|
|
105
|
-
.replace(`<!--server-data-->`, () => `<script>window.__SERVER_DATA__ = ${JSON.stringify(serverData)};</script>`);
|
|
106
|
-
|
|
107
|
-
return new Response(html, {
|
|
108
|
-
headers: {
|
|
109
|
-
"content-type": "text/html; charset=utf-8",
|
|
110
|
-
"Access-Control-Allow-Origin": "*",
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
development: false,
|
|
116
|
-
});
|
|
117
|
-
if(options.showInfo)afterStart(options);
|
|
118
|
-
return server;
|
|
119
|
-
}
|
package/scripts/server.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { getApiRouteModules } from "./apiRoutes";
|
|
2
|
-
import { createServer as createViteServer, type ViteDevServer} from "vite";
|
|
3
|
-
import { matchRoutes } from "react-router";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import { WildPigRouteObject } from "../router/types";
|
|
6
|
-
import packageInfo from "../package.json";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const __dirname = import.meta.dirname;
|
|
10
|
-
const __rootdir = path.resolve(__dirname, "../../../");
|
|
11
|
-
import { ICreateServerOptions } from "./types";
|
|
12
|
-
|
|
13
|
-
const env = process.env;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// 用户代码
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
import chalk from "chalk";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// 启动vite server
|
|
23
|
-
let viteServer: ViteDevServer;
|
|
24
|
-
if(env.NODE_ENV === "development"){
|
|
25
|
-
viteServer = await createViteServer({
|
|
26
|
-
configFile: path.resolve(__rootdir, "./vite.config.ts"),
|
|
27
|
-
});
|
|
28
|
-
await viteServer.listen(viteServer.config.server.port);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const frontHandler = (apiModules: any) => async (request: Request) => {
|
|
33
|
-
// 判断pathname是否匹配pageRoutes
|
|
34
|
-
const url = new URL(request.url);
|
|
35
|
-
|
|
36
|
-
// 判断是否是vite请求
|
|
37
|
-
if(url.pathname.includes(".") || url.pathname.startsWith("/@") || url.pathname.startsWith("/assets")){
|
|
38
|
-
if(env.NODE_ENV === "development"){// 交给vite
|
|
39
|
-
const viteURL = new URL(request.url);
|
|
40
|
-
viteURL.port = viteServer.config.server.port.toString();
|
|
41
|
-
const response = await fetch(viteURL.toString(), {
|
|
42
|
-
method: request.method,
|
|
43
|
-
headers: request.headers,
|
|
44
|
-
body: request.body,
|
|
45
|
-
});
|
|
46
|
-
return response.clone();
|
|
47
|
-
}else{// production环境,直接返回文件
|
|
48
|
-
const filepath = path.resolve(__dirname, "./client" + url.pathname);
|
|
49
|
-
// 检查文件是否存在
|
|
50
|
-
if(fs.existsSync(filepath) && fs.statSync(filepath).isFile()){
|
|
51
|
-
return new Response(Bun.file(filepath), {
|
|
52
|
-
headers: {
|
|
53
|
-
"Cache-Control": "public, max-age=864000" // 10 天缓存
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
// 文件不存在
|
|
58
|
-
return new Response("Not Found", {status: 404});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// 服务端请求,获取服务端数据
|
|
65
|
-
const routes = viteServer ? (await viteServer.ssrLoadModule("/node_modules/wildpig/router/index.ts"!)).routes as WildPigRouteObject[] : (await import("../router")).routes;
|
|
66
|
-
const matches = matchRoutes(routes, url.pathname);
|
|
67
|
-
if(!matches)return new Response("404 Not Found", { status: 404 });
|
|
68
|
-
|
|
69
|
-
// 请求服务端数据
|
|
70
|
-
const matchRoute = matches.at(-1)!;
|
|
71
|
-
let serverDataApi = matchRoute.route.serverDataApi;
|
|
72
|
-
let serverData = await (async () => {
|
|
73
|
-
if(!serverDataApi)return undefined;
|
|
74
|
-
const prefixUrl = request.url.split("/")[0] + "//" + request.url.split("/")[2];
|
|
75
|
-
// 需要请求服务端数据, 替换动态参数
|
|
76
|
-
for(const [key, value] of Object.entries(matchRoute.params)){
|
|
77
|
-
if(value)serverDataApi = serverDataApi.replace(":" + key, value);
|
|
78
|
-
}
|
|
79
|
-
// 加上当前request的query参数
|
|
80
|
-
for(const [key, value] of new URLSearchParams(request.url.split("?")[1]).entries()){
|
|
81
|
-
if(serverDataApi.includes(key + "="))continue; // 已经有这个参数了
|
|
82
|
-
serverDataApi += (serverDataApi.includes("?") ? "&" : "?") + key + "=" + value;
|
|
83
|
-
}
|
|
84
|
-
const serverRequest = new Request({
|
|
85
|
-
...request.clone(),
|
|
86
|
-
url: prefixUrl + serverDataApi, // 替换url
|
|
87
|
-
});
|
|
88
|
-
serverRequest.headers.set("wildpig-server-data-api", serverDataApi);
|
|
89
|
-
const pathname = serverDataApi.split("?")[0]; // 获取路径
|
|
90
|
-
const handler = apiModules?.[pathname]?.GET;
|
|
91
|
-
if(!handler)return undefined; // 没有对应的handler
|
|
92
|
-
const serverData = await handler(serverRequest).then((r: Response) => r.json());
|
|
93
|
-
return serverData;
|
|
94
|
-
})();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// 构造响应页面
|
|
100
|
-
// 1. 读取 index.html
|
|
101
|
-
const template = viteServer ? await viteServer.transformIndexHtml(request.url, fs.readFileSync('./index.html', 'utf-8')) : fs.readFileSync(path.resolve(__dirname, './client/index.html'), 'utf-8');
|
|
102
|
-
// 2. 获取渲染函数
|
|
103
|
-
const render = (await import('../entry/server')).render
|
|
104
|
-
// 3. 获取应用程序 HTML
|
|
105
|
-
const appHtml = await render(request, serverData);
|
|
106
|
-
|
|
107
|
-
// 4. 注入渲染后的应用程序 HTML 到模板中。
|
|
108
|
-
const html = template
|
|
109
|
-
.replace(`<!--ssr-outlet-->`, () => appHtml)
|
|
110
|
-
.replace(`<!--title-->`, () => serverData?.title || "title")
|
|
111
|
-
.replace(`<!--server-data-->`, () => `<script>window.__SERVER_DATA__ = ${JSON.stringify(serverData)};</script>`);
|
|
112
|
-
|
|
113
|
-
return new Response(html, {
|
|
114
|
-
headers: {
|
|
115
|
-
"content-type": "text/html; charset=utf-8",
|
|
116
|
-
"Access-Control-Allow-Origin": "*",
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** 启动后的描述性文字 */
|
|
122
|
-
const afterStart = async (options: ICreateServerOptions) => {
|
|
123
|
-
// 启动后的文字
|
|
124
|
-
console.log(` __ __ _ _ _ ____ _
|
|
125
|
-
\\ \\ / /(_)| | __| | | _ \\ (_) __ _
|
|
126
|
-
\\ \\ /\\ / / | || | / _\` | | |_) || | / _\` |
|
|
127
|
-
\\ V V / | || || (_| | | __/ | || (_| |
|
|
128
|
-
\\_/\\_/ |_||_| \\__,_| |_| |_| \\__, |
|
|
129
|
-
|___/ `)
|
|
130
|
-
console.log(chalk.blue.bgGreen(` 🐗 WildPig version ${packageInfo?.version} by ${packageInfo?.author} `));
|
|
131
|
-
console.log(chalk.green(" Strong & Fast Fullstack Framework\n"));
|
|
132
|
-
console.log(chalk.green("✨ WildPig is running on port " + options.port || 3000));
|
|
133
|
-
console.log(chalk.yellow(`💻 Wildpig is Running in ${chalk.yellow.bold(env.NODE_ENV)} mode.`));
|
|
134
|
-
if(viteServer)console.log(chalk.green("⚡ Vite server is running on port " + viteServer.config.server?.port));
|
|
135
|
-
console.log(chalk.green(`🔗 Click to debug in Browser: http://localhost:${options.port}`));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export const createServer = async (options?: ICreateServerOptions) => {
|
|
139
|
-
options = Object.assign({
|
|
140
|
-
port: 3000,
|
|
141
|
-
host: "0.0.0.0",
|
|
142
|
-
showInfo: true,
|
|
143
|
-
}, options || {});
|
|
144
|
-
|
|
145
|
-
// 确保重启后可以重新拿到路由
|
|
146
|
-
const apiModules = await getApiRouteModules(env.NODE_ENV === "development" ? "dev" : "prod") as any;
|
|
147
|
-
const server = Bun.serve({
|
|
148
|
-
port: options.port,
|
|
149
|
-
hostname: options.host,
|
|
150
|
-
routes:{
|
|
151
|
-
...apiModules,
|
|
152
|
-
"/*": frontHandler(apiModules),
|
|
153
|
-
},
|
|
154
|
-
development: env.NODE_ENV === "development",
|
|
155
|
-
})
|
|
156
|
-
if(options.showInfo)await afterStart(options);
|
|
157
|
-
return server;
|
|
158
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|