前言: 本篇文章主要是想讲解 .html 文件和 .CSS 文件在实际开发中和后端服务器交互最后上线的基础原理。
面向的人群🆕:是刚入行不久,且目前只会写前端业务代码而不清楚整个工作流的前端新人。我会从 0 开始一步一步带你理解整个流程的底层逻辑是什么,希望你能跟着我一起做完今天的所有步骤。
一. 前期准备
-
为了能让更多的人明白这其中的原理,今天我们回归前端 最原始的本质,抛开 Vue 或 React 这些前端框架,只用最原始的 .js 、.css 文件开始今天的讲解。
-
你的电脑需要安装 node ,因为会用到一些文件读写的操作。
-
创建一个文件夹,然后创建出下面两个文件,一个
index.html
文件和server.js
文件。
-
index.html
文件如下,你也可以自己写喜欢的内容,但是如果懒得写,请 copy 我的代码html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <h1>JavaScript is the best language in the world!</h1> </body> </html>
-
我相信每个前端开发的读者都,或多或少安装过下面这个插件
Live Server
。
实现的效果是将你编写的
.html
文件直接在浏览器打开,可以帮我们极大提升开发体验,实现的效果如下图:
-
看起来很神奇对吧?其实
Live Server
实现的功能和我们今天要讲解的知识非常接近,让我们暂时先关闭它,然后准备实现一个自己的简易版Live Server
。
二. 什么是服务器
-
在此之前,我觉得很有必要先解释一下服务器这个概念,因为这个概念之前困扰我很久。
-
对于前端来讲,因为不像后端开发者一样需要日常跟服务器 打交道,所以可能会觉得服务器 是一个很高大上的东西,是一个我们前端开发人员触不可及的东西。包括我刚入行的时候,我也是这样想的,但随着工作阅历的不断增加,发现服务器是一个再普通不过的东西罢了。
-
我们从现实生活类比,服务器就好比一家超市。
- 你去超市的目的是什么?
- 答:购买日用品 ,那么此时你就是消费者,那么超市就是给你提供服务 的地方对吧?在这种场景下,你充当的其实就是 客户端(client) 的角色,而超市就充当着服务器 的角色。服务器 并不一定只能提供单一的服务 ,就像超市提供了很多类型的柜台,有日用品,有肉类,有水果,这些不同的柜台充当着给你提供服务 的角色,而超市是容纳这些服务 运行的地方,这就是服务器 和服务之间的关系。
-
你还可以自己再类比所有日常生活中需要你花钱消费的地方。如饭店、宾馆、花店、网吧等等。它们都是为了满足你的需求 ,来为你提供这些需求 的场所,它们都充当着 "服务器" 的角色。
-
让我们回到网络上面来,你完全可以把 "服务器" 和上面你类比现实世界所理解到的概念画等号。比如你今天想看视频,我们拿 B站 举例, 那么B站 此时就是一个服务器 ,当你在地址栏输入
bilibili
后,你就相当于走进了 "视频分类" 柜台 ,它上面存放着各种各样的视频,B站 它现在提供了观看视频的一项服务 给你。
-
然后你看视频看累了,想看一看漫画,此时就变成了它提供 "漫画" 服务给你。
-
还是有点抽象?让我们回到
Live Server
。刚刚我们关闭了Live Server
,所以导致我们在浏览器里输入localhost:5500
这个地址后,页面出现了访问错误。
-
但是当我们打开
Live Server
的时候,我们发现页面又恢复了正常工作。
-
那么上面的步骤,我们就可以这样理解:
-
当
Live Server
运行的时候,我们的电脑 给了我们一个可以使用浏览器访问本地.html
文件的服务 。这个服务的提供者 就是Live Server
这个程序。那么谁是服务器 呢?没错,就是帮你运行Live Server
的主机 -----你的电脑。 -
当你关闭
Live Server
的时候,相当于你的电脑关闭了提供服务的程序 ,导致你失去了访问.html
的能力。
-
-
通过上面的例子,不难发现,其实服务 本质上是一段代码,而运行这段代码的容器 被我们叫做了服务器。
-
这里有一个十分重要的概念 ----端口号 。也就是
Live Server
启动的 5500 这个数字。这个数字你可以暂时把它理解为,你的电脑(也就是服务器)给它找了一个唯一的服务窗口,这个窗口号是 5500 。(想象一个政务大厅,里面不同的业务都开设在不同的窗口位置,即使此时没人访问,它也需要有工作人员坐在那里等待。)
-
那么上面整个过程可以这样理解:
-
😣 Live Server : "好烦,周一又要上班了。喂,服务器 (你的电脑),5500 这个窗口还没人值班吧?没人的话我就先坐这了。"
-
🧑🏫 你的电脑:"我看一下啊,哦,暂时没人用。那你坐这里吧,即使没人来,你也不能跑出去啊!"
-
-
聪明的你也许会想到,那如果 5500 窗口如果有人先占了怎么办?注意,这里服务器 (你的电脑 )会自动安排你的程序到另外的端口号 上提供服务(执行代码 )。(如果你的程序中也支持这么做的话,不妨打开两个
vscode
自己尝试一下同时开启live Server
看看会是什么效果。)
三. 编写 server.js 文件
-
既然我们已经知道了,服务 其实就是一段代码。那么我们就可以根据需求,去编写出这样的代码。其实我们的需求很简单,就是想让我的浏览器可以正常渲染我的
.html
文件。 -
这里我们需要从
node
中引入http
模块,这个模块封装了一些可以让我们快速编写http
服务器 的方法。
-
注意:这里我说了编写 "http 服务" 这个概念 ,结合我们上面对服务器的理解。这句话的完整含义应该是:
node 提供的 http 模块,让我们可以快速在电脑上,编写一段代码程序。当我们的电脑运行这段程序的时候,我们的电脑可以提供 http 这样一项服务,此时浏览器可以通过使用 http 协议来和这个 http 服务程序进行通信。
-
然后我们调用
http
模块提供的createServer
方法,具体用法在注释中写的很清楚了,不过多赘述。
-
现在的你已经创建出了一个服务 实例,它虽然还没有任何功能,但你已经可以告诉你的电脑,它现在可以被当作一个服务程序 启动了。那么此时你还需要告诉电脑你想在那个窗口(端口) 去提供服务,这里我随便写了一个 7777 ,你可以选择一个任意你喜欢的数字。(注意,有些端口号是操作系统独享的,你不能占用,最好使用 5000-65535 范围内的数字) 然后使用
http_server.listen()
方法去向电脑 申请这个端口号 。
-
让我们在
http_server
的回调函数中,打印一些数据,来看看我们的服务是正常启动了。
-
让我们在终端用
node
运行这个文件,你可以在控制台看到你的这段代码已经被你的电脑成功启动了。
-
可以看到,随着我用浏览器去访问这个在窗口 7777 提供的服务,我们回调函数监听到请求后成功打印了相对应的输出。
-
但是此时我们的浏览器好像呆呆的,没有展示任何信息。这是因为你这项服务 现在还不够到位,你没有返回给浏览器任何信息。此时我们需要调去
response
身上的end
函数。response.end()
。这个函数第一个参数是你要告诉浏览器的数据,第二个参数也是一个回调函数 ,会在你返回给浏览器消息后被调用。那么我们就可以这样写:(这里别忘了需要
ctrl c
,然后重新执行这个文件)
-
你会看到虽然我们的服务 成功打印了相应的输出,但是我们浏览器显示的却是乱码 。
-
这是因为你没告诉浏览器应该用什么格式去渲染这段数据,你可能会有疑问,浏览器这么笨吗?默认为
utf-8
不就行了?如果你能联想到这里,不得不给你点个赞👍,但是假如这段数据是图片 、视频 呢,那不就乱套了吗?这里不卖关子,解决方法很简单,就是我们的服务 在返回数据之前,告诉浏览器该如何展示我们的内容,怎么告诉?调用response.writeHeader()
设置相对应的Content-type
即可。
现在的显示效果就符合我们的预期啦!
四. 读取 html 文件
-
这里涉及到 node 的一些知识,不过不是本篇文章的重点,故不会做过多解释。
-
这里有两个重点,第一个就是引入
fs文件系统
模块,它提供了一个方法叫做readFileSync
,这个函数是同步读取指定路径下的文件,默认返回值为buffer
类型。
-
当拿到这个
data
后,我们就可以返回给浏览器这个数据。此时你的浏览器应该已经正确渲染出这些内容了。
五. CSS 文件生效的原理
-
让我们在跟文件夹下生成一个
global.css
的文件。
-
别忘了我们最初是如何引入
css
在index.html
的。
-
这里有一个关键的知识点需要了解,我们打开
localhost:7777
,其实是会向我们的服务发起三个请求的。其中,发送index.html
是我们的主动行为,favicon.ico
这个请求是浏览器的默认行为,global.css
是由于我们的index.html
携带了\<link/>
标签,从而引起浏览器附带请求导致的。
-
让我们打印一下
request
的url
参数信息,这里包含了浏览器请求资源的地址。
它对应了浏览器
request
字段的信息。
-
刷新一下浏览器,你会看到控制台有以下三个输出,和我们上面的推测是符合的。注意,这里的根路径
/
路径之后会被我们替换为index.html
。
-
聪明的你可能已经发现了,我们浏览器其实已经请求了
global.css
但是样式好像没有正确的生效。那是因为.css
文件没有设置正确的mime
格式。被浏览器当成普通的文件格式处理了。
-
这里我们就需要为
index.html
和.css
分别设置不同的content-type
来让css
文件生效。此时你的http_server
的代码应该如下。jsconst http_server = http.createServer((request, response) => { let file_path = ""; //1. 这里存放文件的真实路径 let data = ""; //2. 这里准备存放文件的 buffer 数据 let ext = ""; //3. 这里准存放文件的后缀名称 if (request.url === "/") { //4. 如果请求路径是跟路径,那么替换为 index.html file_path = "index.html"; } else { file_path = request.url.replace("/", ""); //5. 否则的话,去掉路径前面的斜杠 '\' } data = fs.readFileSync(file_path); }
-
这里最关键的后缀名如何获取呢?我们需要引入另一个模块
path
。我们利用path.extname
方法,将切割好的file_path
传递为参数即可获取到正确的文件后缀名。
-
之后为每次请求设置正确的类型即可。具体文件类型
mime
和content-type
的映射关系请参照:MDN提供的 MIME 对照表。
-
此时我们可以看到,样式已经正确生效。
六. 80 端口的含义
-
想必大家都知道
http
服务是跑在80
端口这一前端常识的吧?其实它没什么特别的,它只不过是把端口 申请在服务器 的80窗口上而已,然后我们配合浏览器的默认行为---当没有指定明确端口号时,帮你自动填写为 80 端口。 -
我们来试验一下。
注意,此时我没有像之前一样输入
7777
,但是浏览器却依然正确找到了我http-服务 的位置,和我们对浏览器默认行为的猜想一致。
-
所以不要再死记硬背
80
和443
这两个数字了,它们只不过是你的后端搭档 在代码程序里根据业务不同而写下的一个普通数字罢了。 -
为什么要这样做?如果每个
http-server
开发者,大家都用不同的端口号。那么你就需要不仅仅需要把它们的域名记下来,还要记住相对应的端口号。就像上面一样,你不觉得每次手动输入7777
很麻烦吗?那么干脆大家和浏览器商量好,就用80
这个端口,浏览器默认帮你填写就好了。
七. 源码
这里故意屏蔽了 favicon.ico
的请求,和文章整体内容关系不大。
js
const http = require("node:http"); //从 node 中引入 http 模块
const fs = require("node:fs"); //引入 fs 模块
const path = require("node:path");
// 这个函数接收一个回调函数
// 1.第一个函数接收的是前端传递过来的 request 参数
// 2.第二个函数是要返回给浏览器的信息
const http_server = http.createServer((request, response) => {
let file_path = ""; //1. 这里存放文件的真实路径
let data = ""; //2. 这里准备存放文件的 buffer 数据
let ext = ""; //3. 这里准存放文件的后缀名称
if (request.url === "/") {
//4. 如果请求路径是跟路径,那么替换为 index.html
file_path = "index.html";
} else {
file_path = request.url.replace("/", ""); //5. 否则的话,去掉路径前面的斜杠 '\'
}
if (file_path !== "favicon.ico") {
response.writeHeader = `Content-type:text/${ext}`;
data = fs.readFileSync(file_path);
ext = path.extname(file_path);
}
response.end(data);
});
// 告诉你的电脑,你想用 7777 这个端口
http_server.listen("7777", () => {
console.log("我提供的服务在 7777 窗口");
});
八. 总结
-
首先我们要对服务器有清晰的认知,任何一个设备都可以当作一个服务器,你的手机,你的笔记本,你的台式机,一个大型的存储计算机,都可以被叫做服务器。
-
所谓的服务 就是跑在服务器 上的一段普通代码程序而已。当代码运行起来后,服务器需要为这个服务分配一个唯一端口号,其它应用可以访问这个端口来接受你提供的服务。
-
有些服务并不是要公开为别人使用的,查看你的任务管理器或活动监视器。你的电脑开启了这么多服务 ,它们占用着不同的端口,而这些服务有的是只为操作系统 提供的,并不对普通用户提供任何服务。
-
我们的前端代码,不管是
.vue
、.tsx
、.ts
等等文件,最后都会被打包为原始的html
和css
和js
文件,因为浏览器只认识这些内容。(不信你去看看有content-type: vue
这种mime
类型吗?)然后后端会部署一个 http-server 程序,让它跑在一个专属服务器 上执行这段程序,通过我们上面讲解的内容来传递给80
或者其他任何端口上,等待别人访问。 -
你在实际开发中使用的
npm run dev
后,你的前端代码呈现在浏览器上,其底层的原理和上面无异,不过是开发工具帮你将上面步骤封装的功能更加完善和便捷而已。
-
本文中对于
server.js
对创建服务器的流程做了最大化的精简,来确保读者能够适应服务这个概念。在实际开发中,公司真正的后端服务开发绝不是这么简单。