騷年,Koa和Webpack瞭解一下?

前言
- 日常開發中,我們用的都是使用Webpack這類構建工具進行模組化開發。
- 或者使用基於create-react-app和vue-cli等腳手架進行開發。
- 如果這個時候我們需要Node作為後端,React或者Vue作為前端,Webpack作為構建工具,那豈不是我們需要手動啟動兩個服務?
- 所以這裡選用Koa和Webpack作為例子,啟動koa伺服器時,啟動webpack。
文章比較長,請耐心閱讀
搭建一個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伺服器已經配置完成。

搭建一個webpack伺服器
webpack基本搭建原理,就是利用webpack提供的 ofollow,noindex">webpack-dev-middleware 和 webpack-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我採用的是[email protected]版本,而且我們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進度條進行了替換。

新建一個類用於啟動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可以說已經配置完成了。

這裡有個痛點需要解決一下
我們想要的效果是當前端程式碼更改時,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。
