騷年,Koa和Webpack瞭解一下?

光光同學發表於2018-12-01

騷年,Koa和Webpack瞭解一下?

有必要宣告下,很多人在沒看完這篇文章之後,就評論一些和文章主題不相符的內容。

這篇文章主要講述的是如何在本地開發環境下通過啟動node伺服器之後,無縫啟動webpack,從而達到前後端配置一體化。

適合做node全棧專案、node中間層,配合前端專案、等等。

前言

  • 日常開發中,我們用的都是使用Webpack這類構建工具進行模組化開發。
  • 或者使用基於create-react-app和vue-cli等腳手架進行開發。
  • 如果這個時候我們需要Node作為後端,React或者Vue作為前端,Webpack作為構建工具,那豈不是我們需要手動啟動兩個服務?
  • 所以這裡選用Koa和Webpack作為例子,啟動koa伺服器時,啟動webpack。

文章比較長,請耐心閱讀

前言

搭建一個Koa伺服器

搭建一個webpack伺服器

這裡有個痛點需要解決一下

使用cluster程式

總結

搭建一個Koa伺服器

搭建一個Koa伺服器需要用到什麼?

  • 前端HTML開發模板採用koa-nunjucks-2
  • 處理靜態資源koa-static
  • 處理後端路由koa-router
  • 處理gzip壓縮koa-compress

引用依賴

const Koa = require('koa');
const app = new Koa();
const koaNunjucks = require('koa-nunjucks-2');
const koaStatic = require('koa-static');
const KoaRouter = require('koa-router');
const router = new KoaRouter();
const path = require('path');
const compress = require('koa-compress');
複製程式碼

初始化Koa核心配置

class AngelConfig {
  constructor(options) {
    this.config = require(options.configUrl);
    this.app = app;
    this.router = require(options.routerUrl);
    this.setDefaultConfig();
    this.setServerConfig();
  }

  setDefaultConfig() {
    //靜態檔案根目錄
    this.config.root = this.config.root ? this.config.root : path.join(process.cwd(), 'app/static');
    //預設靜態配置
    this.config.static = this.config.static ? this.config.static : {};
  }

  setServerConfig() {
    //設定埠號
    this.port = this.config.listen.port;
    //cookie簽名加密
    this.app.keys = this.config.keys ? this.config.keys : this.app.keys;
  }
}
複製程式碼

實現繼承,採用的是Es6的class類的方式。如果有不熟悉的,可以參考Es6教程

對於Koa的配置,是通過例項化一個物件,然後傳入一個Object配置。這裡可以參考Eggjs的config.default.js配置。

例項化配置可以參考下

  new AngelServer({
    routerUrl: path.join(process.cwd(), 'app/router.js'),//路由地址
    configUrl: path.join(process.cwd(), 'config/config.default.js') //預設讀取config/config.default.js
  })
複製程式碼

配置Koa中介軟體,包括前端模板,靜態資源,路由,gzip壓縮

//啟動伺服器
class AngelServer extends AngelConfig {
  constructor(options) {
    super(options);
    this.startService();
  }

  startService() {
    //開啟gzip壓縮
    this.app.use(compress(this.config.compress));

      //模板語法
    this.app.use(koaNunjucks({
      ext: 'html',
      path: path.join(process.cwd(), 'app/views'),
      nunjucksConfig: {
        trimBlocks: true
      }
    }));
  
    //訪問日誌
    this.app.use(async (ctx, next) => {
      await next();
      const rt = ctx.response.get('X-Response-Time');
      ctx.logger.info(`angel ${ctx.method}`.green,` ${ctx.url} - `,`${rt}`.green);
    });
    
    // 響應時間
    this.app.use(async (ctx, next) => {
      const start = Date.now();
      await next();
      const ms = Date.now() - start;
      ctx.set('X-Response-Time', `${ms}ms`);
    });
    //路由管理
    this.router({
      router,
      config: this.config,
      app: this.app
    });
  
    this.app.use(router.routes())
      .use(router.allowedMethods());

    // 靜態資源
    this.app.use(koaStatic(this.config.root, this.config.static));
  
    // 啟動伺服器
    this.app.listen(this.port, () => {
      console.log(`當前伺服器已經啟動,請訪問`,`http://127.0.0.1:${this.port}`.green);
    });
  }
}
複製程式碼

這裡可能有些人不知道我這裡的router是怎麼回事。
分享下router.js

/**
 *
 * @param {angel 例項化物件} app
 */

const html = require('./controller/home');
const business  = require('./controller/business');

module.exports = (app) => {
  let { router, config } = app;
  router.get('/',html);
  router.post('/system/api/issue/file',business.serverRelease);
  router.post('/system/api/user/reg',business.reg);
  router.get('/system/api/app/list',business.getList)
  router.get('/system/api/server/info',business.getServerInfo)
  router.get('/system/api/server/RAM',business.getServerRAM)
  router.get('/system/api/server/log',business.getServerLog)
}

複製程式碼

其實,這塊router也是參考了Eggjs的寫法router.js。 到這裡Koa伺服器已經配置完成。

騷年,Koa和Webpack瞭解一下?

搭建一個webpack伺服器

webpack基本搭建原理,就是利用webpack提供的webpack-dev-middlewarewebpack-hot-middleware,然後配合koa服務。

為了方便起見,我採用的是已經基於koa封裝好的koa-webpack-dev-middleware和koa-webpack-hot-middleware。

首先引入依賴

const webpack = require('webpack');
const path = require('path');
const colors = require('colors');

const chokidar = require('chokidar');
const cluster = require('cluster');

const KoaRouter = require('koa-router');
const router = new KoaRouter();
const Koa = require('koa');

const chalk = require('chalk');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const compress = require('koa-compress');
複製程式碼

還是老樣子,先例項化一個核心類

//配置核心配置
class AngelCore {
  constructor(options) {
    this.webpackConfig = require(options.url);
    this.config = options.configUrl ? require(options.configUrl) : require(path.join(process.cwd(), 'config/config.default.js'));
  }
  
}
複製程式碼

這裡引入的是koa-webpack-dev-middleware配置,配置檔案見詳情

這裡webpack我採用的是webpack@4.x版本,而且我們webpack配置一般都是放在專案根目錄下的webpack.config.js內。

所以,我們需要支援匯入webpack.config.js檔案。 定義一個類,繼承核心配置

//處理webpack配置
class dealWebpackConfig extends AngelCore {
  constructor(options) {
    super(options);
    this.readConfig();
  }
  //處理webpack環境變數問題
  readConfig() {
    this.webpackConfig.mode = this.config.env.NODE_ENV;
    this.webpackConfig.plugins.push(new ProgressBarPlugin({
      format: ` ٩(๑❛ᴗ❛๑)۶ build [:bar] ${chalk.green.bold(':percent')}  (:elapsed 秒)`,
      complete: '-',
      clear: false
    }));
    this.compiler = webpack(this.webpackConfig); //webpack進度處理完成
    //匯入webpack配置
    this.devMiddleware = require('koa-webpack-dev-middleware')(this.compiler, this.config.webpack.options);
    this.hotMiddleware = require('koa-webpack-hot-middleware')(this.compiler);
  }

}
複製程式碼
  • 由於webpack4.x版本可以自定義mode設定環境變數,所以,對這個匯入的webpack.config.js進行了更改。同時將webpack打包時的progress進度條進行了替換。

騷年,Koa和Webpack瞭解一下?
新建一個類用於啟動webpack,同時繼承dealWebpackConfig類。

//執行
class angelWebpack extends dealWebpackConfig {
  constructor(options) {
    super(options);
    this.runWebpack();
  }
  //執行webpack
  runWebpack() {
    app.use(this.devMiddleware);
    app.use(this.hotMiddleware);

  }
}
複製程式碼

再給webpack增加服務埠,用於koa伺服器訪問webpack的靜態資源,預設埠9999。

//重新啟動一個koa伺服器
class koaServer extends angelWebpack {
  constructor(options) {
    super(options);
    this.startKoa();
  }

  startKoa() {
    
    //fork新程式
    let port = this.config.webpack.listen.port ? this.config.webpack.listen.port : 9999;
    //開啟gzip壓縮
    app.use(compress(this.config.compress));

    //訪問日誌
    app.use(async (ctx, next) => {
      await next();
      const rt = ctx.response.get('X-Response-Time');
      console.log(`webpack' ${ctx.method}`.green,` ${ctx.url} - `,`${rt}`.green);
    });

    router.get('/',(ctx) => {
      ctx.body = 'webpack';
    });

    app.use(router.routes()).use(router.allowedMethods());
    app.listen(port, () => {
      console.log('webpack伺服器已經啟動,請訪問',`http://127.0.0.1:${port}`.green);
    });
  }
}
複製程式碼

現在這裡的webpack可以說已經配置完成了。

騷年,Koa和Webpack瞭解一下?

這裡有個痛點需要解決一下

我們想要的效果是當前端程式碼更改時,webpack重新構建,node端程式碼更改時,node服務即Koa服務進行重啟,而不是Koa和webpack全部重啟。

所以這裡採用webpack使用主程式,當webpack啟動的時候,然後用work程式啟動koa伺服器,koa程式的重啟,不會影響到webpack的重新構建。

使用cluster程式

現在的koa並沒有監聽程式碼更改,然後重啟koa服務,可能需要使用外界模組 supervisor 重啟程式。
所以這裡我採用chokidar 監聽nodejs檔案是否更改,然後kill掉koa程式,重新fork程式一個新的work程式。 所以對上面的koaServer這個類進行修改。

  class koaServer extends angelWebpack {
  constructor(options) {
    super(options);
    this.startKoa();
  }

  startKoa() {
    
    //fork新程式
    let port = this.config.webpack.listen.port ? this.config.webpack.listen.port : 9999;
    //開啟gzip壓縮
  
    app.use(compress(this.config.compress));

    //訪問日誌
    app.use(async (ctx, next) => {
      await next();
      const rt = ctx.response.get('X-Response-Time');
      console.log(`webpack' ${ctx.method}`.green,` ${ctx.url} - `,`${rt}`.green);
    });

    router.get('/',(ctx) => {
      ctx.body = 'webpack';
    });

    app.use(router.routes()).use(router.allowedMethods());
    //監聽和重啟服務。
    this.watchDir();
    app.listen(port, () => {
      console.log('webpack伺服器已經啟動,請訪問',`http://127.0.0.1:${port}`.green);
    });
  }

  watchDir() {
    let worker = cluster.fork();
    const watchConfig = {
      dir: [ 'app', 'lib', 'bin', 'config'],
      options: {}
    };
    chokidar.watch(watchConfig.dir, watchConfig.options).on('change', filePath =>{
      console.log(`**********************${filePath}**********************`);
      worker && worker.kill();
      worker = cluster.fork().on('listening', (address) =>{
        console.log(`[master] 監聽: id ${worker.id}, pid:${worker.process.pid} ,地址:http://127.0.0.1:${address.port}`);
      });
    });
  }

}
複製程式碼

最後再在服務入口檔案統一呼叫

//fork一個新的程式,用於啟動webpack
if(cluster.isMaster) {
  new angelWebpack({
    url: path.join(process.cwd(), 'assets/webpack.config.js'), //webpack配置地址
    configUrl: path.join(process.cwd(), 'config/config.default.js') //預設讀取config/config.default.js
  });

}

// 啟動angel服務
if(cluster.isWorker) {
  new AngelServer({
    routerUrl: path.join(process.cwd(), 'app/router.js'),//路由地址
    configUrl: path.join(process.cwd(), 'config/config.default.js') //預設讀取config/config.default.js
  })
}
  


複製程式碼

總結

最後,這裡提供的只是一個開發環境用的環境,如果是生產環境的話,就需要去掉webpack層,把koa作為主程式,當然nodejs畢竟只是單程式執行的,所以這個koa服務不能完全發揮機器全部效能。

當然解決這個痛點的方法也有,啟動伺服器的時候fork多個程式用來處理業務邏輯,每過來一個請求,分配一個程式去跑,業務邏輯跑完了,然後關閉程式,主程式再fork出去一個程式,把機器效能發揮最大化。

Koa服務和webpack服務原始碼在我的Github,歡迎star。

騷年,Koa和Webpack瞭解一下?

========================================================================== 可以看我的另外一篇如何建立一個可靠穩定的Web伺服器

相關文章