Skip to content

Commit 7279936

Browse files
committed
fix: resolve lint issues and migrate dev server to esm
1 parent 81041d5 commit 7279936

File tree

7 files changed

+168
-145
lines changed

7 files changed

+168
-145
lines changed

dev-server.js

Lines changed: 0 additions & 131 deletions
This file was deleted.

dev-server.mjs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { createServer } from 'node:http';
2+
import { createServer as createHttpsServer } from 'node:https';
3+
import { readFileSync } from 'node:fs';
4+
import { networkInterfaces } from 'node:os';
5+
import path from 'node:path';
6+
import { fileURLToPath, parse } from 'node:url';
7+
import next from 'next';
8+
9+
const __filename = fileURLToPath(import.meta.url);
10+
const __dirname = path.dirname(__filename);
11+
12+
const dev = process.env.NODE_ENV !== 'production';
13+
const hostname = process.env.HOSTNAME || '0.0.0.0';
14+
const port = Number.parseInt(process.env.PORT ?? '3000', 10) || 3000;
15+
const useHttps = process.env.HTTPS === 'true';
16+
17+
const app = next({ dev, hostname, port });
18+
const handle = app.getRequestHandler();
19+
20+
function getLocalIP() {
21+
const interfaces = networkInterfaces();
22+
23+
const priorityPrefixes = [
24+
'wlp',
25+
'wlan',
26+
'wlx',
27+
'enp',
28+
'eth',
29+
'ens',
30+
];
31+
32+
const skipPrefixes = ['br-', 'docker', 'veth', 'virbr', 'lo'];
33+
34+
for (const prefix of priorityPrefixes) {
35+
for (const [name, entries] of Object.entries(interfaces)) {
36+
if (!name.startsWith(prefix) || !entries) {
37+
continue;
38+
}
39+
40+
for (const iface of entries) {
41+
if (iface.family === 'IPv4' && !iface.internal) {
42+
return iface.address;
43+
}
44+
}
45+
}
46+
}
47+
48+
for (const [name, entries] of Object.entries(interfaces)) {
49+
if (!entries || skipPrefixes.some((prefix) => name.startsWith(prefix))) {
50+
continue;
51+
}
52+
53+
for (const iface of entries) {
54+
if (iface.family === 'IPv4' && !iface.internal) {
55+
return iface.address;
56+
}
57+
}
58+
}
59+
60+
for (const entries of Object.values(interfaces)) {
61+
if (!entries) {
62+
continue;
63+
}
64+
65+
for (const iface of entries) {
66+
if (iface.family === 'IPv4' && !iface.internal) {
67+
return iface.address;
68+
}
69+
}
70+
}
71+
72+
return '127.0.0.1';
73+
}
74+
75+
async function main() {
76+
await app.prepare();
77+
78+
const server = useHttps
79+
? createHttpsServer(
80+
{
81+
key: readFileSync(
82+
path.join(__dirname, 'certificates', 'localhost-key.pem')
83+
),
84+
cert: readFileSync(
85+
path.join(__dirname, 'certificates', 'localhost.pem')
86+
),
87+
},
88+
async (req, res) => {
89+
try {
90+
const parsedUrl = parse(req.url ?? '/', true);
91+
await handle(req, res, parsedUrl);
92+
} catch (error) {
93+
console.error('Error occurred handling', req.url, error);
94+
res.statusCode = 500;
95+
res.end('internal server error');
96+
}
97+
}
98+
)
99+
: createServer(async (req, res) => {
100+
try {
101+
const parsedUrl = parse(req.url ?? '/', true);
102+
await handle(req, res, parsedUrl);
103+
} catch (error) {
104+
console.error('Error occurred handling', req.url, error);
105+
res.statusCode = 500;
106+
res.end('internal server error');
107+
}
108+
});
109+
110+
server.listen(port, hostname, (error) => {
111+
if (error) {
112+
throw error;
113+
}
114+
115+
if (useHttps) {
116+
console.log(
117+
`\n🔒 HTTPS 开发服务器已启动!\n` +
118+
` 本地访问: https://localhost:${port}\n` +
119+
` 局域网访问: https://${getLocalIP()}:${port}\n` +
120+
' (请在局域网设备上信任此自签名证书)\n'
121+
);
122+
return;
123+
}
124+
125+
console.log(
126+
`\n🚀 HTTP 开发服务器已启动!\n` +
127+
` 本地访问: http://localhost:${port}\n` +
128+
` 局域网访问: http://${getLocalIP()}:${port}\n`
129+
);
130+
});
131+
}
132+
133+
void main();

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6-
"dev": "NODE_ENV=development node dev-server.js",
7-
"dev:https": "HTTPS=true NODE_ENV=development node dev-server.js",
6+
"dev": "NODE_ENV=development node dev-server.mjs",
7+
"dev:https": "HTTPS=true NODE_ENV=development node dev-server.mjs",
88
"dev:legacy": "next dev -H 0.0.0.0",
99
"build": "next build",
1010
"start": "next start -H 0.0.0.0",

src/app/page.tsx

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
11
'use client';
22

3-
import { useEffect, useState } from 'react';
3+
import { useEffect, useSyncExternalStore } from 'react';
44
import { AnimatePresence, motion } from 'framer-motion';
55
import { useChatStore } from '@/store/useChatStore';
66
import WelcomeModal from '@/components/WelcomeModal';
77
import ChatInterface from '@/components/ChatInterface';
88

9+
function subscribeToHydration(onStoreChange: () => void) {
10+
const unsubscribeHydrate = useChatStore.persist.onHydrate(onStoreChange);
11+
const unsubscribeFinishHydration =
12+
useChatStore.persist.onFinishHydration(onStoreChange);
13+
14+
return () => {
15+
unsubscribeHydrate();
16+
unsubscribeFinishHydration();
17+
};
18+
}
19+
920
export default function Home() {
10-
const { hasAgreed, initUser } = useChatStore();
11-
const [isMounted, setIsMounted] = useState(false);
21+
const hasAgreed = useChatStore((state) => state.hasAgreed);
22+
const initUser = useChatStore((state) => state.initUser);
23+
const isHydrated = useSyncExternalStore(
24+
subscribeToHydration,
25+
() => useChatStore.persist.hasHydrated(),
26+
() => false
27+
);
28+
29+
useEffect(() => {
30+
void useChatStore.persist.rehydrate();
31+
}, []);
1232

1333
useEffect(() => {
14-
useChatStore.persist.rehydrate();
15-
initUser();
16-
setIsMounted(true);
17-
}, [initUser]);
34+
if (isHydrated) {
35+
initUser();
36+
}
37+
}, [initUser, isHydrated]);
1838

1939
// Prevent hydration mismatch by not rendering until client-side
20-
if (!isMounted) {
40+
if (!isHydrated) {
2141
return <div className="h-screen w-screen bg-stone-50" />;
2242
}
2343

@@ -46,4 +66,4 @@ export default function Home() {
4666
</AnimatePresence>
4767
</main>
4868
);
49-
}
69+
}

src/components/Sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useChatStore, ChatSession } from '@/store/useChatStore';
3+
import { useChatStore } from '@/store/useChatStore';
44
import { Plus, MessageSquare, Trash2, X } from 'lucide-react';
55
import { motion, AnimatePresence } from 'framer-motion';
66
import { cn } from '@/lib/utils';

src/lib/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export function generateUUID() {
1010
return crypto.randomUUID();
1111
}
1212
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
13-
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
13+
const r = Math.random() * 16 | 0;
14+
const v = c == 'x' ? r : (r & 0x3 | 0x8);
1415
return v.toString(16);
1516
});
1617
}

src/store/useChatStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export const useChatStore = create<ChatState>()(
5454
initUser: () => {
5555
const { userId, sessions, activeSessionId } = get();
5656

57-
let newState: Partial<ChatState> = {};
57+
const newState: Partial<ChatState> = {};
5858

5959
if (!userId) {
6060
newState.userId = generateUUID();

0 commit comments

Comments
 (0)