作为一个前端程序员,刚开始入门的时候,你觉得只要学习前端代码(js + css + html)就行了,实际上,到后面很多知识都涉及到服务端,在我们学习的过程中难免需要写一些 demo。比如在浏览器的缓存、或者跨域的知识中,我们需要手动写服务端的代码给响应头增加一些字段才能实现,只有我们自己写出来的代码才能彻底理解。
所以在我们继续学习前端工程化的知识之前,需要学习搭建一个基本的服务端项目,我们前端常用的服务端工具是 express 或者 koa。
一、koa vs express
koa 使用基于 promise的中间件机制,express 使用基于回调函数的中间件机制。但是 express 的社区比较活跃,我们还是使用express 吧,基本上可以实现所有我们想要的功能。
不过无论是koa 还是express 都是基于 node 原生的 http 模块封装的 npm 包,node 中有一个 核心模块 http,是node 网络的关键模块,也是一个很重要的模块。官网文档地址在这里
二、node vs 浏览器
2.1 node 到底是啥
遥想在刚入行的时候,我曾经无数次搜索这个问题,无数次的打开node 的官网,就是搞不通node 到底是啥,我不信只有我一个人这样,不过现在我悟了。如果你还不知道node 到底是个啥?那看看这篇文章
总之一句话, node 可以运行js代码,node 里面有的东西浏览器中不一定有,浏览器中有的东西 node 也不一定有。
2.2 网络请求
作为一个菜鸟,一提到网络请求,你肯定会想到 ajax,怎么实现 ajax?你肯定会想到那必然是使用 XMLHttpRequest 或者 fetch 或者用 axios
你不会又去搜索 ajax 是啥了吧?
我已经为你准备好了,看这里
你还需要知道,axios 就是基于 XMLHttpRequest 封装的。
没错,你想的一点都没错,但是你要注意到,无论是 XMLHttpRequest,还是fetch,都是前端的网络请求方式,也就是浏览器的功能。
node 中是不支持 XMLHttpRequest 和 fetch 的,不信你可以测试一下
- 首先你安装了 node ,打开 vscode,随便新建一个 test.js 文件,然后输入下面的内容
const a = new XMLHttpRequest() console.log(a)
-
然后点击这个按钮运行,会报错
-
这就是在 node 中运行 js 代码,然后结果是 XMLHttpRequest 不存在
-
如果在浏览器中就不同了,你可以随便打开一个网页的开发者工具试一下
总之,我们要学会区分前端和服务端,前端代码运行在浏览器中,使用浏览器支持的请求方式(如 XMLHttpRequest)发起网络请求;node 的网络请求是用于服务端的,node 使用自己的核心模块 http 完成网络请求。
2.3 应用
不少人觉得前端学习写服务端代码没啥用,其实不然,除了我之前说的在学习新知识写 demo 的时候会用到之外,还有应用的场景。
比如开发 electron 应用,electron 基于 chromium【就是chrome的基础】 和 node,是开发桌面端应用的好工具,要实现一个完整的桌面端应用,就需要写 node 相关的服务端代码,比如用来操作本地数据库之类的敏感操作,肯定是不能前端完成了。
所以学习写服务端代码也是很重要的。
三、构建 express
3.1 新建仓库
我的仓库地址是 learn-express
3.2 初始化
3.2.1 增加 .nvmrc
没错,牢记!所有的项目,只要和node 有关,我们都要增加这个文件为了锁定node版本。
3.2.2 增加 package.json
node 项目需要使用 npm init 初始化项目,运行之后会增加一个 package.json 文件。
npm init
3.2.3 提交代码
3.3 安装express
pnpm i express
3.4 增加 .gitignore
初始化的时候不增加 .gitignore 还好,但是如果增加了任何 npm 包,安装之后必定会有一个 node_modules 文件夹,里面文件内容极多,所以必须要增加 .gitingore 文件。
3.5 新建 index.js
从 express 官网复制代码到 index.js 中
3.6 运行 index.js
接下来,就是需要运行这个 index.js, 我们可以点击编辑器的运行功能运行,但是既然我们都用了node 了,自然是要用 node 的命令运行代码更方便了。
在 package.json 中增加运行脚本
然后我们运行 npm run dev 即可。
此时我们在浏览器中打开 localhost:3000,我们已经成功启动了一个服务器。
虽然我们没有新建 html 文件,但是页面上会渲染 res.send 返回的结果,这是浏览器的功能
你在浏览器的输入栏输入任意一个 get 请求,返回的结果都会在页面上展示。
所以其实我们可以在 res.send 的时候返回一些更复杂的html代码,比如
res.send(`<h1>你好你好</h1> `)
这样的话,页面就会展示出一个 h1 标签,在服务端拼接完整的 html 代码,这就是服务端渲染的原理。
服务端渲染 server-side rendering 简称 ssr。是一种在服务端生成并直接返回已经拼接好了的页面给客户端的方式。 ssr 有利于 seo,你又该问seo是啥了?
- seo 是搜索引擎(百度、谷歌等)优化,seo 是一系列的技术和策略,旨在提高网站在搜索引擎中的排名。
我们常用的非服务端渲染的框架,比如普通的vite项目 vue + vite,可以关注我的《前端工程化专栏中的系列教程》之 手把手搭建 vite + ts +vue3项目
服务端渲染的框架有 nuxt,可以关注我的《前端工程化专栏中的系列教程》之 完整的nuxt3 + vue + ts 项目教程
如果你用的是 react 那么就可以用next框架
nuxt next 傻傻分不清~学习之路,任重道远
3.7 提交代码
四、增加index.html
现在我们有了一个服务器,还缺一个客户端,也就说少一个 html 文件写前端的代码。然后访问服务端的地址,就展示我们自己写的 html 页面。
4.1 增加 index.html
4.2 更改 index.js
我们的目的是访问根目录,返回自定义的 index.html 的内容,所以需要修改服务端代码即可。
现在,我们再运行 npm run dev ,访问 localhost:3000 页面就会访问我们自定义的 html 了。
4.3 提交代码
五、增加各种请求
5.1 get 请求
5.1.1 修改 index.js
const express = require('express')
const path = require('path')const app = express()app.get('/', function (req, res) {res.sendFile(path.resolve('index.html'), )
})app.get('/userInfo', function (req, res) {res.status(200).end(JSON.stringify({uid: 1,name: 'test'}))
})app.listen(3000)
5.1.2 修改 index.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>这是自定义的html页面</h1><button onclick="getUserInfo()">获取userInfo</button><script>function getUserInfo() {fetch('/userInfo').then((res) => {return res.json()}).then(data => {console.log('用户信息', data)})}</script>
</body>
</html>
5.1.3 运行
npm run dev
注意这里面前端代码,有 fetch API 的知识点,如果你不会用那就需要学习一下了,具体请看官网
在这个例子中,服务端返回了 json 类型的数据,fetch API 接收到数据后,首先需要使用 res.json() 进行解析,然后下一个 then 的回调中才能获取 json 真实的数据,并且是已经解析过的,请看 response.json()
其他的请求方式,比如 post 、put 等都是大同小异,可以自己在 express 的官网看如何使用。
5.1.4 提交代码
5.2 ejs 模版引擎
5.2.1 模版引擎是啥
模版引擎是一种用于在网页、应用程序或者其他文档中嵌入动态数据的工具或框架,他们允许开发人员将静态模版与动态数据结合,以生成最终的输出,模版引擎通常用于将数据与模版进行结合,生成最终的HTML、XML、JSON 等输出。
首先要明确一件事情,浏览器最终渲染的页面都是 html,这也是为什么我们小白入门要先学习 html + css,因为 html 是本质,也最简单。 所以我们在写 vue 项目的时候,在 .vue 文件中的 template 里面写的代码,最终都会被 vue 的模版引擎编译成 html 文件,然后再渲染。
5.2.2 模版引擎的原理
模板引擎的原理基本上是通过将模板中的标记(markup)和逻辑与数据结合起来,生成最终的输出。下面是一个简化的模板引擎的工作原理:
解析: 模板引擎首先对输入的模板进行解析,识别模板中的标记和逻辑结构。这可能涉及到词法分析和语法分析,以将模板转换为内部的数据结构或语法树。
编译: 解析完成后,模板引擎会将解析得到的数据结构编译成可执行的代码。这些代码通常是目标语言(如JavaScript、Python等)的代码,用于动态地生成最终的输出。
数据绑定: 模板引擎将编译后的代码与数据进行绑定。这意味着模板中的变量或表达式会与特定的数据源关联,以便在最终输出时能够动态地替换这些部分。
输出生成: 当需要生成最终输出时,模板引擎执行编译后的代码,根据数据生成最终的文本输出。这可能包括HTML、XML、JSON等格式,具体取决于模板引擎的用途和设计。
缓存: 为了提高性能,一些模板引擎可能会对已经编译过的模板进行缓存,以避免重复的解析和编译过程。
不同的模板引擎在实现上可能存在一些差异,但上述步骤基本涵盖了模板引擎的通用原理。关键在于将模板中的标记和逻辑与数据进行有效地组合,以生成最终的动态输出。
在前端开发中,例如在Vue.js中,模板引擎的原理也涉及到虚拟DOM的概念,以便更高效地进行DOM更新。虚拟DOM是一个内存中的表示,通过比较虚拟DOM和实际DOM的差异,最小化DOM操作,提高性能。这在响应式框架中尤为重要,因为它允许框架更有效地追踪数据变化并更新视图。
5.2.3 安装 ejs
pnpm i ejs
关于 express 使用 ejs 的方法可以再 express 的官方 demo 中找到例子也可以去看 ejs 的官方例子。
没错,如果你想学习一个新的 npm 包如何使用,最好是去 npm 官网上搜
点击 homepage 主页或者 github 仓库
一个成熟的 npm 项目中一般有一个实例(examples)文件夹,里面有详细的代码
5.2.4 修改 index.js
5.2.5 新建 template.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><%= name %></h1>
</body>
</html>
5.2.6 运行并访问
运行 npm run dev 可以访问 localhost:3000/template
5.2.7 提交代码
这种使用模版引擎的方式,可以直接在服务端设置变量名,然后渲染在页面上,很方便。相比于第四章的使用的 index.html 的方式,如果 index.html 文件要渲染某一个服务端的变量,那么就只能先用请求获取,再用js设置了,比较麻烦。
不过,在以后的教程中我还是会用第四章的index.html的形式,因为逻辑比较简单。
六、整理项目结构
好了,现在我们有了一个简单的服务器的项目,可以供我们后续学习使用,我们整理一下项目结构,让他具有可扩展性,以后新增一个功能就新建一个文件夹。
6.1 新建 ejs 文件夹
在根目录新建 ejs 文件夹,并把 template.html 文件夹移到 ejs 文件夹中,做适当修改
6.2 新建 ejs/index.js
新建 ejs/index.js 并且把之前的index.js中关于模版引擎的代码复制进去,做适当的修改
const express = require('express')
const path = require('path')const app = express()app.engine('.html', require('ejs').__express);
// 设置默认路径 在 ejs 文件夹下
app.set('views', path.join(path.resolve(), 'ejs'));
// 设置引擎文件后缀名是 .html
app.set('view engine', 'html');// 访问路径 改成 /ejs
app.get('/ejs', function (req, res) {res.render('template', {ejsName: 'ejs 你好你好'})
})app.listen(3000)
6.3 运行
想要运行 ejs 文件夹里面的 index.js 已经不能直接运行npm run dev,因为在package.json中我们是这样写的
我们要改一下
然后运行 npm run dev 就可以了
6.3 提交代码
七、增加通用运行脚本
在第六章我们成功的运行了 ejs 目录下的 index.js ,但是好麻烦哦,如果后面我们又加了别的功能,那还得改 package.json,累死了,所以下面我们下一个通用的方法。
7.1 修改 package.json
7.2 新增 start.js
在根目录新增 start.js ,用于 npm run dev 的时候执行,在这个文件文件里面执行 node 命令
const { exec } = require('child_process')// 获取运行命令的参数
// 假设运行 npm run dev ejs ,那么就得到 process.argv[2] === ejs
// 为什么是第2个参数,因为在package.json中已经有两个了,
// 我们可以打印出来看看
console.log('node 参数', process.argv)// 获取当前运行的服务
const funDir = process.argv[2] || '.'// 实际执行的米娜领
const command = `node ${funDir}/index.js`
// child_process 是 node 的子进程, exe c可以执行node 命令
exec(command)
7.2 运行脚本
npm run dev ejs
可以完美的运行!如果不加参数,哪就是运行根目录的 index.js。
7.3 提交代码
总结
好了,现在我们有一个完美的服务端项目了,以后我们可以用这个项目写很多服务端的代码。本篇文章的全部流程如下:
我的仓库地址如下
yangjihong2113/learn-express
内容比较多,难免疏漏,有不对的地方欢迎评论区指出。
这是一个系列的教程,正在持续更新中,欢迎大家关注我的专栏《前端工程化系统教程》
我还有另外一个正在更新的专栏《一网打尽面试题》会总结所有的面试题。