有必要声明下,不少人在没看完这篇文章以后,就评论一些和文章主题不相符的内容。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服务器