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

56 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天 冲冲冲!

Node

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应用

Node.js运行时结构

运行时结构

V8,libuv

  • V8:JS runtime 诊断调试工具(inspector)
  • libuv:eventloop(事件循环),syscall(系统调用)
  • 举例:用node-fetch发起请求...

特点

  • 异步 I/O
    • 当node执行I/O操作时,会在响应返回后回复操作,而不是阻塞线程并占用额外内存等等

01.png

setTimeout(() => {
    console.log('B')
})
console.log('A')
  • 单线程
    • 实现:JS线程+uv线程池+V8任务线程池+V8 Inspector线程
    • 优点:不用考虑多线程状态同步问题,也就不需要锁,同时还能比较高效地利用系统资源
    • 缺点:阻塞会产生更多负面影响
      • 解决:多进程或多线程
function fibonacci(num) {
    if (num === 1 || num === 2) {
        return 1
    }
    return fibonacci(num - 1) + fibonacci(num - 2)
}

fibonacci(42)
fibonacci(43)
  • 跨平台
    • Node跨平台+JS无需编译环境(+Web跨平台+诊断工具跨平台)
      • =开发成本低(大部分场景无需担心跨平台问题),整体学习成本低

编写Http Server

  • 安装node
    • 推荐使用nvm多版本管理
      • mac、linux---nvm
      • windows---nvm4w或官方安装包
  • 编写http server + client,收发GET、POST请求
// hello
const http = require('http')

const port = 3000

const server = http.createServer((req, res) => {
    res.end('hello')
})

server.listen(port, () => {
    console.log('http:127.0.0.1:3000')
})

// json
const http = require('http')

const server = http.createServer((req, res) => {
    const bufs = []
    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) {
            //res.end('invalid json')
        }

        const responseJson = {
            msg: `receive ${msg}`
        }
        res.setHeader('Content-Type', 'application/json')
        res.end(JSON.stringify(responseJson))
    })
})

server.listen(port, () => {
    console.log('http:127.0.0.1:3000')
})

//client
const http = require('http')

const body = JSON.stringify({
    msg: "hello from my own client"
})

const req = http.request('http:127.0.0.1:3000', {
    method: "POST",
    headers: {
        'Content-Type': 'application/json'
    }
}, (res) => {
    const bufs = []
    res.on('data', (buf) => {
        bufs.push(buf)
    })
    res.on('end', () => {
        const buf = Buffer.concat(bufs)
        const json = JSON.parse(buf)

        console.log('json.msg is:', json.msg)
    })
})

req.end(body)

//Promisify
//Promise+async await
//技巧:将callback转换成promise
const http = require('http')

const port = 3000

const server = http.createServer(async (req, res) => {
    // receive body from client
    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) {
                //res.end('invalid json')
            }
        })
        resolve(msg)
    })

    // response
    const responseJson = {
        msg: `receive ${msg}`
    }
    res.setHeader('Content-Type', 'application/json')
    res.end(JSON.stringify(responseJson))
})

server.listen(port, () => {
    console.log('http:127.0.0.1:3000')
})


// 静态文件
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) => {
    //expected 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..
    const filestream = fs.createReadStream(filepath)
    filestream.pipe(res)
})

const port = 3000

server.listen(port, () => {
    console.log('http://127.0.0.1:3000/index.html')
})

与高性能

  • 编写静态文件服务器
  • 编写React SSR服务
    • SSR特点
      • 相比传统html模版引擎:避免重复编写代码
      • 相比SPA:首屏渲染更快、SEO友好
    • 缺点
      • 通常qps较低,前端代码编写时需要考虑服务端渲染情况
    • 难点:
      • 需要处理打包代码
      • 需要西靠前端代码在服务端运行时的逻辑
      • 移除对服务端无意义的副作用,或重置环境
const React = require('react')
const ReactDOMServer = require('react-dom/server')

const http = require('http')

function App(props) {
    //return <div>Hello</div>
    return React.createElement('div', {}, props.children || 'hello')
}

const server = http.createServer((req, res) => {
    res.end(`
        <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>${ReactDOMServer.renderToString(
        React.createElement(App, {}, 'my_content')
    )}</h1>
    <script>
        // init React application...
        alert('yes')
    </script>
    </body>
    </html>
    `)
})

const port = 3000

server.listen(port, () => {
    console.log('http://127.0.0.1:3000')
})
  • 适用 inspector-Debug
    • V8 Inspector:开箱即用、特性丰富强大、与前端开发一致、跨平台
    • 场景
      • 查看console.log内容
      • breakpoint
      • 搞CPU、死循环:cpuprofile
      • 高内存占用:heapsnapshot
      • 性能分析
  • 部署简介
    • 部署要解决的问题
      • 守护进程:当进程退出时,重新拉起
      • 多进程:cluster便携地利用多进程
      • 记录进程转台,用于诊断
      • 记录进程状态,用于诊断
    • 容器环境
      • 通常有健康检查的手段,只需考虑多核cpu利用率即可