Node.js 与前端开发实战 | 青训营笔记
这是我参与「第五届青训营」伴学笔记创作活动的第 7 天
0x1 Node.js 的应用场景
- 前端工程化
- 打包工具:webpack、vite、esbuild、parce
- 代码压缩:uglifyjs
- 语法转换:babeljs,typescript
- 难以替代
- Web 服务端应用
- 学习曲线平缓,开发效率较高
- 运行效率接近常见的编程语言
- 社区生态丰富及工具链成熟(npm)
- 与前端结合的场景会有优势(SSR)
- 竞争激烈
- Electron 跨端桌面应用
- 商业应用:vscode、slack、discord、zoom
- 大型公司内的效率工具
- 值得考虑
0x2 Node.js 运行时结构
-
V8、libuv
- V8:JavaScript Runtime,诊断调试工具(inspector)
- libuv:eventloop(事件循环)、syscall(系统调用)
-
特点
-
异步 I/O
setTimeout(() => { console.log('B'); }); console.log('A');
-
当 Node.js 执行 I/O 操作时,会在响应后恢复操作,而非阻塞线程,并占用额外内存等待
举例:在执行读取文件功能
readFile()
时,执行fs.readFile()
方法后,会将方法的请求异步调用至 Node 中处理的同时,继续执行其他调用,并在执行该功能回调前,将 Node 的处理结果返回回来,从而完成异步 I/O -
-
单线程
function fibonacci(num: number): number{ if(num == 1 || num == 2) return 1; return fibonacci(num - 1) + fibonacci(num - 2); } fibonacci(2) fibonacci(3)
- JS 单线程
- 实际情况:JS 线程+uv 线程+V8 任务线程池+V8 Inspector 线程
- 优点
- 不用考虑多线程状态同步问题,不需要锁
- 比较高效地利用系统资源
- 缺点
- 阻塞会产生更多负面影响
- JS 单线程
-
跨平台
const net = require('net'); const socket = new net.Socket('/tmp/scoket.sock')
- Node.js 跨平台+JS 无需编译环境(+Web 跨平台+诊断工具跨平台)=开发和整体学习成本低
-
0x3 编写 Http Server
-
安装 Node.js
推荐使用 nvm 安装 Node.js,可以有效解决跨版本问题(Windows 系统使用 nvm4w)
-
编写 Http Server + Client,进行收发 GET、POST 请求
-
Http Server
项目:Hello World
/* filename:server.js */ const http = require('http'); const port = 3000; const server = http.createServer((req, res) => { res.end('hello') }) server.listen(port, () => { console.log(`server listens on: ${port}`) })
/* filename:jsonServer.js */ const http = require('http'); const port = 3000; const server = http.createServer((req, res) => { const bufs = []; req.on('data', data => { bufs.push(data); }) req.on('end', () => { let reqData = {} try { reqData = JSON.parse(Buffer.concat(bufs).toString()) } catch(err) { // receive invalid json data } res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ echo: reqData.msg || 'Hello', })) }) }) server.listen(port, () => { console.log(`server listens on: ${port}`) })
-
-
编写静态文件服务器
const http = require('http') const fs = require('fs') const path = require('path') const url = reuqire('url') const port = 3000 const server = http.createServer((req, res) => { const info = url.parse(req.url) const file = fs.createReadStream(path.resolve(__dirname, '.' + info.pathname)) file.pipe(res) }) server.listen(port, () => { console.log(`server listens on: ${port}`) })
-
编写 React SSR 服务
- SSR(Server Side Rendering) 特点
- 相比传统 HTML 模板引擎,可以避免重复编写代码
- 相比 SPA(Single Page Application),可以更快渲染首屏,对 SEO(Search Engine Optimization) 友好
- 缺点:通常 qps 较低,编写代码时需要考虑服务端渲染情况
举例:
HTML:
const http = require('http') const port = 3000 const server = http.createServer((req, res) => { res.setHeader('Content-Type', 'text/html') res.end(` <!DOCTYPE html> <html> <head> <title>Title</title> </head> <body> <p>Content</p> </body> </html> `) }) server.listen(port, () => { console.log(`server listens on: ${port}`) })
替换成 React:
const http = require('http') const React = require('react') const ReactDOMServer = require('react-dom/server') function App() { return React.createElement('p', { children: 'Hello' }) } const port = 3000 const server = http.createServer((req, res) => { res.setHeader('Content-Type', 'text/html') res.end(` <!DOCTYPE html> <html> <head> <title>Title</title> </head> <body> <div id="main"> ${ReactDOMServer.renderToString(React.createElement(App))} </div> </body> </html> `) }) server.listen(port, () => { console.log(`server listens on: ${port}`) })
-
SSR 难点:
-
需要打包处理
require('./static/style.css')
-
需要思考前端代码在服务端运行时的逻辑
async componentDidMount() { const res = await fetch('http://my.server.domain') // ... }
-
移除对服务端无意义的副作用,或重置环境
-
- SSR(Server Side Rendering) 特点
-
适用 Inspector 进行调试、诊断
- V8 Inspector
node --inspect
open http://localhost:9229/json
- 场景
- 查看
console.log()
内容 - 断点
- 高 CPU、死循环:
cpuprofile
- 高内存占用:
heapasnapshot
- 性能分析
- 查看
- V8 Inspector
-
部署
- 需要解决的问题
- 守护进程:当进程退出时,重新拉起
- 多进程:cluster 便捷地利用多进程
- 记录进程状态,用于诊断
- 容器环境
- 通常有健康检查的手段,只需考虑多核 CPU 利用率
- 需要解决的问题
0x4 延申
-
Node.js 贡献代码
- 好处
- 理解底层细节
- 自我证明
- 解决社区问题
- 难点
- 费时
- 好处
-
编译 Node.js
- 从认知角度:发生问题时能有迹可循
- 是贡献代码的第一步
- 如何编译
./config && make install
-
诊断 / 追踪
-
诊断
- 低频但重要,充满挑战性的方向
- 利于企业衡量是否可依赖某种语言的重要参考
-
难点
-
需要理解 Node.js 底层、操作系统以及各种工具
-
需要经验
-
-
-
WASM、NAPI
- WASM:WebAssembly
- NAPI:NewAPI