Node JS后端项目开发与生产环境总结

V3RZYn.jpg

Node JS 常用后端框架有 express、koa、sails。国产框架有个 egg js,已经在 cnode 投入生产了,还有个 think js,类似 think php,在此支持一波。每个框架在开发环境与生产环境都有所不同,这里以koa为例

# 开发环境与生产环境的区别

建立在后台模板渲染(ejs, pug)的基础上。前后分离架构请参考 webpack 热更新实现

# 开发环境

  • 热更新
  • 错误处理
  • 前端 js 代码自动打包

# 生产环境

  • 静态缓存(static cache)
  • 内容压缩(gzip)
  • 日志文件
  • 进程守护
  • 强制 https
  • 404 处理
  • 负载均衡
  • 前端 js 代码混淆压缩

# 开发环境配置

# 热更新(nodemon)

nodemon (opens new window)在 js 文件变化后悔重新运行程序,在package.jsonscripts中添加:

dev: 'nodemon server.js'
npm run dev

nodemon 还有许多可选配置,具体参阅nodemon 文档 (opens new window)

# 错误处理

以 koa 为例

app.on('error', (err) => {
  log.error('server error', err)
})

如若想要将错误抛出到浏览器页面和美化错误页面,express可用express-error-handler (opens new window)koa可用onerror (opens new window)

# 前端 js 代码自动打包(webpack)

由于是后台模板渲染,所以没法用webpack-dev-server进行自动刷新。能做的就是利用webpackwatch在前端 js 改变后自动打包,当然还是免不了手动刷新

// webpack.config.js
const config = {
  entry: {
    app: path.resolve(root, './modules/app.js'),
    about: path.resolve(root, './modules/about.js')
  },
  output: {
    path: path.resolve(root, './dist'),
    publicPath: path.resolve(root, './dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /(\.js)$/,
        use: {
          loader: 'babel-loader'
        },
        exclude: /node_modules/
      }
    ]
  },
  devtool: '#eval-source-map',
  watch: true,
  watchOptions: {
    poll: 1000, //监测修改的时间(ms)
    ignored: /node_modules/ //不监测
  }
}

注意一定要开启source-map,不然无法定位报错位置。为通知webpack是生产还是开发环境,可以使用cross-env (opens new window),然后在package.jsonscripts中添加:

"watch": "cross-env NODE_ENV=development webpack --watch webpack.config.js"

开发时应运行两个命令:

// nodemon
npm run dev

// webpack
npm run watch

# 生产环境

生产环境一般使用pm2 (opens new window)pm2已经帮我们完成了进程守护和负载均衡,内部实现原理在此不再赘述,具体参考pm2 文档 (opens new window)

// 生成pm2配置文件ecosystem.config.js
pm2 ecosystem

生成的配置文件已包含了生产环境的基本本质。跟多配置请参考pm2 文档 (opens new window),在package.json文件的scripts中添加

"prd": "pm2 start ecosystem.config.js --env production"

生产环境下运行

npm run prd

这时我们可以通过process全局变量获取到环境状态,在app.js中添加

const prdEnv = process.env.NODE_ENV == 'production'

# 静态缓存

const staticCache = require('koa-static-cache')
if (prdEnv) {
  // 静态缓存
  app.use(
    staticCache(path.join(__dirname, 'public'), {
      maxAge: 365 * 24 * 60 * 60
    })
  )
}

# gzip

const compress = require('koa-compress')
if (prdEnv) {
  // gzip
  app.use(
    compress({
      filter: function (content_type) {
        return /text/i.test(content_type)
      },
      threshold: 2048,
      flush: require('zlib').Z_SYNC_FLUSH
    })
  )
}

# 日志文件

类似nginxaccess.logerror.log,利用fs模块的appendFile方法来输出日志。首先在项目根目录下新建文件夹logs

const fs = require('fs')
const path = require('path')
if (prdEnv) {
  // logger
  app.use(async (ctx, next) => {
    const start = new Date()
    await next()
    const ms = new Date() - start
    fs.appendFile(
      path.join(
        __dirname,
        'logs',
        ctx.status < 400 ? 'access.log' : 'error.log'
      ),
      `[${start.toLocaleString()}] ${ctx.status} ${ctx.method} ${
        ctx.url
      } - ${ms}ms\r\n`
    )
  })
}

# 强制 https

// force https
app.use((ctx, next) => {
  if (ctx.protocol == 'http') {
    ctx.redirect(ctx.href.replace('http', 'https'))
  } else {
    return next()
  }
})

# 404 处理

建立一个模板命名为notFound.pug,在路由之后渲染

// 404
app.use(async (ctx) => {
  await ctx.render('notFound')
})

# 前端 js 代码压缩混淆

在 webpack 中添加插件

// webpack.config.js
// 提取公共js
new webpack.optimize.CommonsChunkPlugin({name: 'common'})

// 压缩代码
new webpack.optimize.UglifyJsPlugin({
    sourceMap: false,
    parallel: true,
    mangle: true,
    compress: {
        warnings: false,
        drop_debugger: true,
        drop_console: true
    }
})

// package.json
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js"

发布应用时需运行

npm run build

npm run prd