有必要聲明下,不少人在沒看完這篇文章以後,就評論一些和文章主題不相符的內容。javascript
這篇文章主要講述的是如何在本地開發環境下經過啓動node服務器以後,無縫啓動webpack,從而達到先後端配置一體化。html
適合作node全棧項目、node中間層,配合前端項目、等等。前端
- 平常開發中,咱們用的都是使用Webpack這類構建工具進行模塊化開發。
- 或者使用基於create-react-app和vue-cli等腳手架進行開發。
- 若是這個時候咱們須要Node做爲後端,React或者Vue做爲前端,Webpack做爲構建工具,那豈不是咱們須要手動啓動兩個服務?
- 因此這裏選用Koa和Webpack做爲例子,啓動koa服務器時,啓動webpack。
引用依賴vue
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核心配置java
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教程node
對於Koa的配置,是經過實例化一個對象,而後傳入一個Object配置。這裏能夠參考Eggjs的config.default.js配置。react
實例化配置能夠參考下webpack
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壓縮git
//啓動服務器
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.jses6
/** * * @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-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我採用的是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);
}
}
複製代碼
//運行
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的從新構建。
如今的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。
========================================================================== 能夠看個人另一篇如何建立一個可靠穩定的Web服務器