Node.js与前端开发实战 | 青训营笔记

152 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第5天

Node.js与前端开发实战

Node.js的应用场景

  • 前端工程化
    • Bundle(构建):webpack,vite,esbuild,parcel
    • Uglify:uglifyjs
    • Transpile(语法转换):bablejs,typescript
    • 其他语言加入竞争:esbuild,parcel,prisma
    • 现状:难以替代
  • Web服务端应用
    • 学习曲线平缓,开发效率较高
    • 运行效率接近常见的编译语言
    • 社区生态丰富工具链成熟(npm,V8 inspector)
    • 与前端结合的场景会有优势(SSR)
    • 现状:竞争激烈,Node.js有自己独特的优势
  • Electron跨端桌面应用
    • 商业应用:vscode,slack,discord,zoom
    • 大型公司内的效率工具
    • 现状:大部分场景在选型的时候都值得考虑
  • Node.js在字节
    • BFF应用、SSR应用,距离:Modern.js
    • 服务端应用:头条搜索,西瓜视频,懂车帝
    • Electron应用:飞连,飞书
    • 每年新增1000+Node.js应用

Node.js运行时结构

image.png

  • V8,libuv
    • V8:JavaScript Runtime,争端调试工具(inspector)
    • libuv:eventloop(事件循环),syscall(系统调用)
  • 特点
    • 异步IO
      • 执行IO操作的时候不需要阻塞线程执行,可以去做其他调用,不会占用额外内存等待
    • 单线程
      • 不适合做CPU密集的工作
      • 但是在新版本中,woker_thread可以独立线程,但每个线程的模型没有太大变化
      • 其实是说JS单线程:实际Node.js还是有很多线程的 :JS线程+uv线程池+V8任务线程池+V8 Inspector线程
      • 优点:不用开率多线程状态同步问题,也不需要锁,同时还能比较高校的利用系统资源
      • 缺点:阻塞会产生更多负面影响;解决办法:多进程或多线程
    • 跨平台
      • 大部分功能、api跨平台
      • Node.js跨平台+JS无需编译环境(+Web跨平台+诊断工具跨平台)
        • 开发成本低,整体学习成本低

安装Node.js

  • Mac,Linux推荐使用nvm,多版本管理
  • Windows推荐nvm4w或是官方安装包
  • 安装慢,安装失败的情况,设置安装源
    • NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node nvm install 16

编写HttpServer

从HelloWorld开始

  • HelloWorld
const http = require('http')
//创建server
const server = http.createServer((req,res)=>{
    res.end('hello')
})
//监听端口(3000)
const port = 3000
server.listen(port,()=>{
    //成功监听,触发回调函数
    console.log(`server listens on: ${port}`)
})

返回Json数据

  • 返回Json
const http = require('http')
//创建server
const server = http.createServer((req,res)=>{
    const bufs=[]
    //从request取出body,绑定data事件,当连接返回数据收集起来
    req.on('data',buf=>{
	bufs.push(buf)
    })
    req.on('end',()=>{
        //bufs
        const buf = Buffer.concat(bufs).toString('utf8')
        let msg='hello'
        try{
            const ret =  JSON.parse(buf)
            msg = ret.msg
        
        }catch(err){
            //如果用户没有传给我们json数据,返回该内容
            //res.end('invalid json')
        }
        const responseJson = {
            msg: `receive:${msg}`
        }
        res.setHeader('Content-Type','application/json')
        //序列化返回json
        res.end(JSON.stringify(responseJson))
    })
})
//监听端口(3000)
const port = 3000
server.listen(port,()=>{
    //成功监听,触发回调函数
    console.log(`server listens on: ${port}`)
})

编写Client

  • Client
const http = require('http')
const body = JSON.stringify({msg:'hello from my client'})
const req = http.request('http://localhost:3000',{
    method:'POST',
    headers:{
        'Content-Type':'application/json',
        'Content-Length':body.length,
    },   
},(res)=>{
    const bufs=[]
    res.on('data',data=>{
        bufs.push(data)
    })
    res.on('end',()=>{
        const receive = JSON.parse(Buffer.concat(bufs).toString())
        console.log('receive:',receive)
    })
})
req.end(body)

Promisify

  • Promisify
    • 用Promise+async await重写
    • 技巧:将callback转成promise
const http = require('http')
//创建server
const server = http.createServer(async(req,res)=>{
    //接收body数据
    const msg = await new Promise((resolve, reject) => {
        const bufs=[]
        req.on('data',buf=>{
            bufs.push(buf)
        })
        req.on('error',(err)=>{
            reject(err)
        })
        req.on('end',()=>{
            //bufs
            const buf = Buffer.concat(bufs).toString('utf8')
            let msg = 'hello'
            try{
                const ret =  JSON.parse(buf)
                msg = ret.msg
                
            }catch(err){
                //如果用户没有传给我们json数据,返回该内容
                //res.end('invalid json')
            }
            resolve(msg)
        })
    })  
    //根据client数据,返回服务端数据
    const responseJson = {
        msg: `receive:${msg}`
    }
    res.setHeader('Content-Type','application/json')
    //序列化返回json
    res.end(JSON.stringify(responseJson))
})
//监听端口(3000)
const port = 3000
server.listen(port,()=>{
    //成功监听,触发回调函数
    console.log(`server listens on: ${port}`)
})

编写静态文件服务

  • 静态文件服务
    • 与高性能可靠的服务相比还差什么
      • CDN:缓存+加速
      • 分布式存储,容灾
const http = require('http')
const fs = require('fs')
const path = require('path')
const url = require('url')

//找到静态文件夹目录
const folderPath = path.resolve(__dirname,'./static')
const server = http.createServer((req,res)=>{
    //期望收到http://127.0.0.1:3000/index.html?abc=10
    const info =  url.parse(req.url)
    //static/index.html
    const filepath=path.resolve(folderPath,'./'+info.path)
    //stream风格api的好处:内部做了处理,占用尽可能少的空间,按需返回给client
    const filestream = fs.createReadStream(filepath)
    filestream.pipe(res)
})

const port = 3000
server.listen(port,()=>{
    //成功监听,触发回调函数
    console.log(`server listens on: ${port}`)
})

React SSR

  • React SSR
    • SSR(server side rendering)特点
      • 相比传统html引擎:避免重复编写代码
      • 相比SPA(single page application):首屏渲染更快,SEO友好
      • 缺点;通常qps较低,前端编写时需要考虑服务端渲染情况
    • SSR难点
      • 需要处理打包代码
      • 需要思考前端代码在服务端运行的逻辑
      • 移除服务端无意义的副作用,或充值环境
const React = require('react')
const ReactDOMServer = require('react-dom/server')
const http = require('http')

function App(props){
    return React.createElement('div',{},props.children|| 'hello')
}
const server = http.createServer((req,res)=>{
    res.end(
        `
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <title>Document</title>
        </head>
        <body>
        ${ReactDOMServer.renderToString(React.createElement(App,{},'my_content'))}        
            <script>
                //初始化React应用
            </script>
        </body>
        </html>
        `
    )
})
const port = 3000
server.listen(port,()=>{
    //成功监听,触发回调函数
    console.log(`server listens on: ${port}`)
})

Debug与部署

  • Debug
    • V8 Inspector:开箱即用,特性丰富强大,与前端开发一致、跨平台
      • node --inspect
      • 查看方式:
      • 方式1.打开 http://localhost:9229/json 进入devtoolsFrontendUrl后的url
      • 方式2.打开 chrome://inspect 然后点击Open dedicated DevTools for Node
    • 场景
      • 查看console.log内容
      • breakpoint
      • 高CPU、死循环:cpuprofile
      • 高内存占用:heapsnapshot
      • 性能分析
  • 部署
    • 需要解决的问题
      • 守护进程:当进程退出时,重新垃圾
      • 多进程:cluster便捷的利用多进程
      • 记录进程状态,用于诊断
    • 容器环境
      • 通常有健康检查手段,只需考虑多核cpu利用率即可