Skip to content

Commit 6361160

Browse files
committed
chore(ci): blog sync
1 parent 2fe1afb commit 6361160

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed

data/blog/post-33.mdx

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
---
2+
title: 实现一个前端开发都使用过的工具
3+
date: 2024-12-15T14:55:36Z
4+
slug: post-33
5+
author: chaseFunny:https://github.com/chaseFunny
6+
tags: ["AI","前端开发","nodeJs"]
7+
---
8+
9+
> 如何使用 Node.js 实现一个类似 VSCode Live Server 的本地静态资源服务器。通过 HTTP 模块搭建基础服务器,结合 livereload 和 connect-livereload 实现文件热更新功能。
10+
11+
最近在系统学习 nodejs,学到 http 模块的时候,想着写个东西熟悉一下相关的 API,那写个什么呢?也不知道怎么得鼠标点击了一下右键发现了它 ,相信每个前端都不会陌生,我们最开始学习前端都会安装的插件:https://open-vsx.org/extension/ritwickdey/LiveServer,它能帮我们启动具有**静态**和**动态页面实时重新加载**功能的**本地开发服务器**。
12+
13+
![image-20241215213702976](https://blog-1304565468.cos.ap-shanghai.myqcloud.com/work/image-20241215213702976.png)
14+
15+
我们一般都会使用它来打开我们的 html 文件,使用它启动有很多好处:
16+
17+
- 实时预览,文件更改自动刷新游览器,支持静态和动态页面的实时更新
18+
- 解决不能访问本地文件问题
19+
- 使用简单,点击一键启动服务
20+
21+
这么神奇的能力,其实实现也很简单,今天就借助 node 来实现一个阉割版本的 liveServer,
22+
23+
## 需求分析
24+
25+
先不急写代码,让我们先分析一下我们要如何实现一个简易版的 liveServer,也就是需求分析,梳理清楚我们的实现思路,这样写代码也就水到渠成,很简单了,而且这样还有一个非常大的好处,我们后面说。实现思路:
26+
27+
1. 创建一个 http 服务器
28+
2. 当启动服务器后,用户访问服务器不同的路由,也就是访问我们本地对应文件夹下的静态资源
29+
3. 首先我们需要把 url 的路径,转为我们本地资源的路径
30+
4. 然后我们需要判断是否存在这个文件,如果存在则读取返回
31+
5. 如果不存在,需要再次判断**是否是目录**,如果是目录,则判断下面是否有默认文件 `index.html` ,如果有则返回文件内容,没有则返回 404
32+
6. 如果不是目录,和文件不存在逻辑一致,返回 404
33+
7. 这样我们就完成了基本的一个静态资源服务器,但是还有几个细节需要处理
34+
35+
1)我们静态资源放在哪里才能被正确读取?
36+
37+
2)当读取的文件后缀不同,如何也做不同的展示形式
38+
39+
3)如何实现静态资源更新网页也实时进行热更新
40+
41+
## 功能实现
42+
43+
已经知道要干什么了,我们先完成初始功能代码:
44+
45+
```js
46+
// 搭建本地静态资源服务器
47+
48+
const http = require("http");
49+
const fs = require("fs");
50+
const path = require("path");
51+
52+
// 记录请求和错误
53+
/**
54+
* 记录请求和错误
55+
* @param {string} message - 要记录的信息
56+
*/
57+
const log = (message) => {
58+
console.log(new Date().toISOString() + ': ' + message);
59+
};
60+
61+
/**
62+
* 处理请求路径并返回文件路径
63+
* @param {string} p - 请求路径
64+
* @returns {string} - 解析后的文件路径
65+
*/
66+
const resolvePath = (p) => {
67+
// 去除开头的 /
68+
const filePath = p.startsWith("/") ? p.slice(1) : p;
69+
// assets 文件夹作为根目录
70+
const fullPath = path.join(__dirname, "../assets", filePath);
71+
// 如果文件不存在
72+
if (!fs.existsSync(fullPath)) {
73+
log(`File not found: ${fullPath}`);
74+
return null;
75+
}
76+
return fullPath;
77+
};
78+
79+
/**
80+
* 获取文件的内容类型
81+
* @param {string} filePath - 文件路径
82+
* @returns {string} - 内容类型
83+
*/
84+
const getContentType = (filePath) => {
85+
const ext = path.extname(filePath);
86+
switch (ext) {
87+
case '.html': return 'text/html;charset=utf-8';
88+
case '.css': return 'text/css;charset=utf-8';
89+
case '.js': return 'application/javascript;charset=utf-8';
90+
case '.json': return 'application/json;charset=utf-8';
91+
default: return 'application/octet-stream';
92+
}
93+
};
94+
95+
/**
96+
* 处理接受的请求和发送合适的响应
97+
* @param {*} req HTTP 请求的请求对象,包含标头、正文和查询参数等属性。
98+
* @param {*} res 响应对象用于将响应发送回客户端,允许您设置状态代码和响应数据。
99+
*/
100+
const handler = (req, res) => {
101+
const filePath = resolvePath(req.url);
102+
if (filePath) {
103+
// 如果文件存在
104+
if (fs.statSync(filePath).isFile()) {
105+
// 读取文件
106+
fs.readFile(filePath, (err, data) => {
107+
if (err) {
108+
log(`Error reading file: ${filePath} - ${err.message}`);
109+
res.statusCode = 500;
110+
res.end('500 Internal Server Error');
111+
return;
112+
}
113+
// 设置状态码和响应头
114+
res.statusCode = 200;
115+
res.setHeader("Content-Type", getContentType(filePath));
116+
// 发送响应
117+
res.end(data);
118+
});
119+
return;
120+
} else if (fs.statSync(filePath).isDirectory()) {
121+
// 如果是目录
122+
// index.html 作为目录的默认文件
123+
const indexPath = path.join(filePath, "index.html");
124+
if (fs.existsSync(indexPath)) {
125+
// 读取 index.html
126+
fs.readFile(indexPath, (err, data) => {
127+
if (err) {
128+
log(`Error reading index file: ${indexPath} - ${err.message}`);
129+
res.statusCode = 500;
130+
res.end('500 Internal Server Error');
131+
return;
132+
}
133+
// 设置状态码和响应头
134+
res.statusCode = 200;
135+
res.setHeader("Content-Type", getContentType(indexPath));
136+
// 发送响应
137+
res.end(data);
138+
});
139+
return;
140+
}
141+
}
142+
}
143+
// 如果文件不存在
144+
res.statusCode = 404;
145+
res.setHeader("Content-Type", "text/html;charset=utf-8");
146+
res.end("404 Not Found");
147+
};
148+
149+
const server = http.createServer(handler);
150+
151+
server.listen(3003, () => {
152+
log('Server running at http://localhost:3003');
153+
});
154+
```
155+
156+
这里我们解决了 1 和 2 两个问题,我们约定了上一级的 assets 为静态资源存放地点,所以我们需要约定式的把要静态资源放在对应的位置;我们 getContentType 函数读取访问资源的后缀,来设置 `Content-Type` ,从而实现不同资源展示不同形式
157+
158+
那如何实现热更新呢?
159+
160+
## 热更新
161+
162+
这里我们需要安装依赖来实现了,我们需要借助 `livereload``connect-livereload` ,使用 express 来搭建一个静态资源访问服务器,我们使用 `livereload` 创建一个服务器来监控文件变化,并使用 `connect-livereload` 中间件在 HTML 文件中注入 LiveReload 脚本,也就实现了实时静态服务器
163+
164+
```js
165+
const fs = require("fs");
166+
const path = require("path");
167+
const livereload = require("livereload");
168+
const connectLivereload = require("connect-livereload");
169+
const express = require("express");
170+
171+
// 创建 Express 应用
172+
const app = express();
173+
174+
// 使用 connect-livereload 中间件
175+
app.use(connectLivereload());
176+
177+
// 记录请求和错误
178+
const log = (message) => {
179+
console.log(new Date().toISOString() + ": " + message);
180+
};
181+
182+
// 处理请求路径并返回文件路径
183+
const resolvePath = (p) => {
184+
const filePath = p.startsWith("/") ? p.slice(1) : p;
185+
const fullPath = path.join(__dirname, "assets", filePath);
186+
if (!fs.existsSync(fullPath)) {
187+
log(`File not found: ${fullPath}`);
188+
return null;
189+
}
190+
return fullPath;
191+
};
192+
193+
// 获取文件的内容类型
194+
const getContentType = (filePath) => {
195+
const ext = path.extname(filePath);
196+
switch (ext) {
197+
case ".html":
198+
return "text/html;charset=utf-8";
199+
case ".css":
200+
return "text/css;charset=utf-8";
201+
case ".js":
202+
return "application/javascript;charset=utf-8";
203+
case ".json":
204+
return "application/json;charset=utf-8";
205+
default:
206+
return "application/octet-stream";
207+
}
208+
};
209+
210+
// 处理接受的请求和发送合适的响应
211+
app.get("*", (req, res) => {
212+
const filePath = resolvePath(req.url);
213+
if (filePath) {
214+
if (fs.statSync(filePath).isFile()) {
215+
fs.readFile(filePath, (err, data) => {
216+
if (err) {
217+
log(`Error reading file: ${filePath} - ${err.message}`);
218+
res.status(500).send("500 Internal Server Error");
219+
return;
220+
}
221+
res.status(200).type(getContentType(filePath)).send(data);
222+
});
223+
} else if (fs.statSync(filePath).isDirectory()) {
224+
const indexPath = path.join(filePath, "index.html");
225+
if (fs.existsSync(indexPath)) {
226+
fs.readFile(indexPath, (err, data) => {
227+
if (err) {
228+
log(`Error reading index file: ${indexPath} - ${err.message}`);
229+
res.status(500).send("500 Internal Server Error");
230+
return;
231+
}
232+
res.status(200).type(getContentType(indexPath)).send(data);
233+
});
234+
}
235+
}
236+
} else {
237+
res.status(404).type("text/html;charset=utf-8").send("404 Not Found");
238+
}
239+
});
240+
241+
// 创建 LiveReload 服务器并监控 assets 目录
242+
const liveReloadServer = livereload.createServer();
243+
liveReloadServer.watch(path.join(__dirname, "assets"));
244+
245+
// 启动服务器
246+
const PORT = 3003;
247+
app.listen(PORT, () => {
248+
log(`Server running at http://localhost:${PORT}`);
249+
});
250+
```
251+
252+
如果你要尝试的话,记得 `npm init -y` ,然后安装依赖:`npm i express livereload connect-livereload`
253+
254+
现在我们再启动,就是一个实时静态服务器了
255+
256+
这个工具虽然简单,但是里面的知识还是很多的:
257+
258+
- 使用 http 模块搭建服务器
259+
- 使用 path 解析请求路径
260+
- 使用 fs 模块进行文件的校验,读取
261+
- 通过拆解需求,来模块化进行开发,让代码好阅读和理解
262+
- 最后的引入两个依赖基于 express 搭建一个静态资源服务器
263+
264+
最后,如果你把我们上面的需求分析喂给 AI ,它大概率也能给出正确的代码,AI 时代了解知识并且知道如何通过知识解决问题的能力变得尤为重要
265+
266+
---
267+
此文自动发布于:<a href="https://github.com/coderPerseus/blog/issues/33" target="_blank">github issues</a>

0 commit comments

Comments
 (0)