wildpig 2.0.0 → 2.1.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/index.ts +3 -5
- package/package.json +1 -1
- package/scripts/build.ts +14 -37
- package/scripts/dev.ts +25 -0
- package/scripts/prod.ts +10 -0
- package/{scripts/apiRoutes.ts → src/ApiRoutes.ts} +1 -2
- package/src/WildpigServer.ts +135 -0
- package/src/hooks/afterStartServer.ts +14 -0
- package/scripts/prodServer.ts +0 -119
- package/scripts/server.ts +0 -158
- /package/{scripts → src}/types.ts +0 -0
package/README.md
CHANGED
package/index.ts
CHANGED
package/package.json
CHANGED
package/scripts/build.ts
CHANGED
|
@@ -1,70 +1,47 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { packageApiRoutes } from "
|
|
2
|
+
import { packageApiRoutes } from "../src/ApiRoutes";
|
|
3
3
|
import { build as viteBuild } from "vite";
|
|
4
|
-
import { IBuildOptions } from "
|
|
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,25 @@
|
|
|
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
|
+
|
|
6
|
+
|
|
7
|
+
const __dirname = import.meta.dirname;
|
|
8
|
+
const __rootdir = path.resolve(__dirname, "../../../");
|
|
9
|
+
|
|
10
|
+
// 启动vite server
|
|
11
|
+
const viteServer = await createViteServer({
|
|
12
|
+
configFile: path.resolve(__rootdir, "./vite.config.ts"),
|
|
13
|
+
});
|
|
14
|
+
await viteServer.listen(viteServer.config.server.port);
|
|
15
|
+
console.log(chalk.green(`Vite server running at port ${viteServer.config.server.port}`));
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
try{
|
|
19
|
+
await import("../../../src/index.ts"!);
|
|
20
|
+
}catch(e){
|
|
21
|
+
console.warn("未执行初始化代码,请检查文件是否存在:src/index.ts")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const wildpigServer = new WildpigServer(viteServer);
|
|
25
|
+
await wildpigServer.createServer();
|
package/scripts/prod.ts
ADDED
|
@@ -2,8 +2,7 @@ import { readdirSync, statSync, writeFileSync } from "fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
const __dirname = import.meta.dirname;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const middleware = (await import("@/endpoints/middleware"!)).middleware;
|
|
5
|
+
const middleware = (await import("../../../src/endpoints/middleware"!)).middleware;
|
|
7
6
|
|
|
8
7
|
const getFilePaths = (dir: string) => {
|
|
9
8
|
const res: string[] = [];
|
|
@@ -0,0 +1,135 @@
|
|
|
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 { ICreateServerOptions } from "./types";
|
|
12
|
+
import { handleAfterStartServer } from "./hooks/afterStartServer";
|
|
13
|
+
|
|
14
|
+
const env = process.env;
|
|
15
|
+
|
|
16
|
+
export class WildpigServer {
|
|
17
|
+
private viteServer: ViteDevServer | undefined;
|
|
18
|
+
constructor(viteServer?: ViteDevServer | undefined){
|
|
19
|
+
if(viteServer)this.viteServer = viteServer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async frontHandler (apiModules: any){
|
|
23
|
+
return async (request: Request) => {
|
|
24
|
+
// 判断pathname是否匹配pageRoutes
|
|
25
|
+
const url = new URL(request.url);
|
|
26
|
+
|
|
27
|
+
// 判断是否是vite请求
|
|
28
|
+
if(url.pathname.includes(".") || url.pathname.startsWith("/@") || url.pathname.startsWith("/assets")){
|
|
29
|
+
if(this.viteServer){// 交给vite
|
|
30
|
+
const viteURL = new URL(request.url);
|
|
31
|
+
viteURL.port = this.viteServer.config.server.port.toString();
|
|
32
|
+
const response = await fetch(viteURL.toString(), {
|
|
33
|
+
method: request.method,
|
|
34
|
+
headers: request.headers,
|
|
35
|
+
body: request.body,
|
|
36
|
+
});
|
|
37
|
+
return response.clone();
|
|
38
|
+
}else{// production环境,直接返回文件
|
|
39
|
+
const filepath = path.resolve(__dirname, "./client" + url.pathname);
|
|
40
|
+
// 检查文件是否存在
|
|
41
|
+
if(fs.existsSync(filepath) && fs.statSync(filepath).isFile()){
|
|
42
|
+
return new Response(Bun.file(filepath), {
|
|
43
|
+
headers: {
|
|
44
|
+
"Cache-Control": "public, max-age=864000" // 10 天缓存
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// 文件不存在
|
|
49
|
+
return new Response("Not Found", {status: 404});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 服务端请求,获取服务端数据
|
|
53
|
+
const routes = this.viteServer ? (await this.viteServer.ssrLoadModule("/node_modules/wildpig/router/index.ts"!)).routes as WildPigRouteObject[] : (await import("../router")).routes;
|
|
54
|
+
const matches = matchRoutes(routes, url.pathname);
|
|
55
|
+
if(!matches)return new Response("404 Not Found", { status: 404 });
|
|
56
|
+
|
|
57
|
+
// 请求服务端数据
|
|
58
|
+
const matchRoute = matches.at(-1)!;
|
|
59
|
+
let serverDataApi = matchRoute.route.serverDataApi;
|
|
60
|
+
let serverData = await (async () => {
|
|
61
|
+
if(!serverDataApi)return undefined;
|
|
62
|
+
const prefixUrl = request.url.split("/")[0] + "//" + request.url.split("/")[2];
|
|
63
|
+
// 需要请求服务端数据, 替换动态参数
|
|
64
|
+
for(const [key, value] of Object.entries(matchRoute.params)){
|
|
65
|
+
if(value)serverDataApi = serverDataApi.replace(":" + key, value);
|
|
66
|
+
}
|
|
67
|
+
// 加上当前request的query参数
|
|
68
|
+
for(const [key, value] of new URLSearchParams(request.url.split("?")[1]).entries()){
|
|
69
|
+
if(serverDataApi.includes(key + "="))continue; // 已经有这个参数了
|
|
70
|
+
serverDataApi += (serverDataApi.includes("?") ? "&" : "?") + key + "=" + value;
|
|
71
|
+
}
|
|
72
|
+
const serverRequest = new Request({
|
|
73
|
+
...request.clone(),
|
|
74
|
+
url: prefixUrl + serverDataApi, // 替换url
|
|
75
|
+
});
|
|
76
|
+
serverRequest.headers.set("wildpig-server-data-api", serverDataApi);
|
|
77
|
+
const pathname = serverDataApi.split("?")[0]; // 获取路径
|
|
78
|
+
const handler = apiModules?.[pathname]?.GET;
|
|
79
|
+
if(!handler)return undefined; // 没有对应的handler
|
|
80
|
+
const serverData = await handler(serverRequest).then((r: Response) => r.json());
|
|
81
|
+
return serverData;
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
// 构造响应页面
|
|
85
|
+
// 1. 读取 index.html
|
|
86
|
+
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');
|
|
87
|
+
// 2. 获取渲染函数
|
|
88
|
+
const { render } = this.viteServer ? await this.viteServer.ssrLoadModule("/node_modules/wildpig/entry/server.tsx") : await import('../entry/server')
|
|
89
|
+
// 3. 获取应用程序 HTML
|
|
90
|
+
const appHtml = await render(request, serverData);
|
|
91
|
+
|
|
92
|
+
// 4. 注入渲染后的应用程序 HTML 到模板中。
|
|
93
|
+
const html = template
|
|
94
|
+
.replace(`<!--ssr-outlet-->`, () => appHtml)
|
|
95
|
+
.replace(`<!--title-->`, () => serverData?.title || "title")
|
|
96
|
+
.replace(`<!--server-data-->`, () => `<script>window.__SERVER_DATA__ = ${JSON.stringify(serverData)};</script>`);
|
|
97
|
+
|
|
98
|
+
return new Response(html, {
|
|
99
|
+
headers: {
|
|
100
|
+
"content-type": "text/html; charset=utf-8",
|
|
101
|
+
"Access-Control-Allow-Origin": "*",
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
/** 启动后的描述性文字 */
|
|
109
|
+
async afterStart (options?: ICreateServerOptions) {
|
|
110
|
+
// 启动后的文字
|
|
111
|
+
console.log(chalk.blue.bgGreen(` 🐗 WildPig version ${packageInfo?.version} by ${packageInfo?.author} `));
|
|
112
|
+
console.log(chalk.green(" Strong & Fast Fullstack Framework\n"));
|
|
113
|
+
console.log(chalk.green("✨ WildPig is running on port " + (options?.port || 3000)));
|
|
114
|
+
console.log(chalk.yellow(`💻 Wildpig is Running in ${chalk.yellow.bold(env.NODE_ENV)} mode.`));
|
|
115
|
+
if(this.viteServer)console.log(chalk.green("⚡ Vite server is running on port " + this.viteServer.config.server?.port));
|
|
116
|
+
console.log(chalk.green(`🔗 Click to debug in Browser: http://localhost:${options?.port || 3000}`));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async createServer (options?: ICreateServerOptions) {
|
|
120
|
+
const apiModules = await getApiRouteModules(env.NODE_ENV === "development" ? "dev" : "prod") as any;
|
|
121
|
+
const server = Bun.serve({
|
|
122
|
+
port: options?.port || 3000,
|
|
123
|
+
hostname: options?.host || "0.0.0.0",
|
|
124
|
+
routes:{
|
|
125
|
+
...apiModules,
|
|
126
|
+
"/*": await this.frontHandler(apiModules),
|
|
127
|
+
},
|
|
128
|
+
development: env.NODE_ENV === "development",
|
|
129
|
+
})
|
|
130
|
+
await this.afterStart(options);
|
|
131
|
+
// 服务器创建好了, 触发afterStartServer回调
|
|
132
|
+
await handleAfterStartServer(server);
|
|
133
|
+
return server;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Server } from "bun";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
let afterStartServerCallbacks: ((server: Server<undefined>) => void)[] = [];
|
|
5
|
+
|
|
6
|
+
export const onAfterStartServer = async (cb: (server: Server<undefined>) => void) => {
|
|
7
|
+
afterStartServerCallbacks.push(cb);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const handleAfterStartServer = async (server: Server<undefined>) => {
|
|
11
|
+
for (const cb of afterStartServerCallbacks) {
|
|
12
|
+
cb(server);
|
|
13
|
+
}
|
|
14
|
+
}
|
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 = "./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
|