收錄待用,修改轉載已取得騰訊雲受權前端
在 web 前端開發中,咱們會藉助 Grunt、Gulp 和 Webpack 等工具的 Watch 模塊去監聽文件變化,那服務端應該怎麼作?其實文件變化的監聽依然能夠藉助構建工具,但咱們還須要自動重啓服務或者熱重載。本文將介紹三種常見的方法。node
使用 node 原生的 fs.watch 方法監聽文件改動,所謂的「熱重載」也不過是及時清除內存中的文件緩存。示例以下:webpack
const fs = require('fs'), path = require('path'), projectRootPath = path.resolve(__dirname, './src'); const watch = project => { require('./src'); // 啓動 APP,自動檢索到 src/index.js try { // 監聽文件夾 fs.watch(project, { recursive: true }, cacheClean) } catch(e) { console.error('watch file error'); } } // 清除緩存 const cacheClean = () => { Object.keys(require.cache).forEach(function (id) { if (/[\/\\](src)[\/\\]/.test(id)) { delete require.cache[id] } }) } // 啓動開發模式 watch(projectRootPath);
注意:在服務器入口文件 src/index.js 中引用中間件時須要套一層函數,並使用 require 的方式引入模塊才能清除緩存。好比:git
// 引入中間件或控制器 app.use(async (ctx, next) => { await require('./controllers/main.js')(ctx); }); // 或引入路由 app.use(async (ctx, next) => { await require('./router').routes()(ctx, next) })
此處以 PM2 爲例,supervisor、forever 等相似的進程管理工具殊途同歸,這裏再也不贅述。github
PM2 是一款帶有負載均衡功能的 Node 應用進程管理器,具備 —watch 配置項,用來監聽應用目錄的變化,一旦發生變化,當即重啓。詳見:Auto restart apps on file change。他是真正意義上的重啓,不是熱替換。web
缺點:PM2 並不提供優雅的方式告知用戶什麼時候重啓或者殺掉進程。
如下是一個簡單的 PM2 配置 (開發環境) start.js,啓動進程 node start.js
。npm
const pm2 = require('pm2'); pm2.connect(function(err) { if (err) { console.error(err); process.exit(2); } pm2.start({ "watch": ["./app"], // 開啓 watch 模式,並監聽 app 文件夾下的改動 "ignore_watch": ["node_modules", "assets"], // 忽略監聽的文件 "watch_options": { "followSymlinks": false // 不容許符號連接 }, name: 'httpServer', script: './server/index.js', // APP 入口 exec_mode: 'fockMode', // 開發模式下建議使用 fockModel instances: 1, // 僅啓用 1 個 CPU max_memory_restart: '100M' // 當佔用 100M 內存時重啓 APP }, function(err, apps) { pm2.disconnect(); // Disconnects from PM2 if (err) throw err }); });
每次修改文件以後保存(Ctrl+S),會有個黑框閃一下,說明應用已經成功重啓了。緩存
chokidar 是對 fs.watch / fs.watchFile / fsevents 的一層封裝。它的優點包括解決(出自 chokidar 文檔):服務器
一、在 OS X 下不能獲取文件名;babel
二、在 OS X 下 Sublime 修改文件後不能獲取到修改事件;
三、修改文件會觸發兩次事件;
四、不提供文件遞歸監聽;
五、高 CPU 使用率;
六、…
這裏使用 babel 的緣由是想要支持最新的 js 語法,包括 ES201七、Stage-x,以及 import / export default 等模塊語法。
下面提供一個完整的監聽重載配置文件,並經過註釋說明功能和意義。
const projectRootPath = path.resolve(__dirname, '..'), srcPath = path.join(projectRootPath, 'src'), // 源文件 appPath = path.join(projectRootPath, 'app'), // 編譯後輸出文件夾 devDebug = debug('dev'), watcher = chokidar.watch(path.join(__dirname, '../src')) // 啓動 chokidar 監聽文件改動 watcher.on('ready', function () { // babel 編譯文件夾目錄 babelCliDir({ outDir: 'app/', retainLines: true, sourceMaps: true }, [ 'src/' ]) // compile all when start require('../app') // 啓動 APP(編譯後的文件) // 添加監聽方法 watcher // 文件新增 .on('add', function (absPath) { compileFile('src/', 'app/', path.relative(srcPath, absPath), cacheClean) }) // 文件修改 .on('change', function (absPath) { compileFile('src/', 'app/', path.relative(srcPath, absPath), cacheClean) }) // 文件刪除 .on('unlink', function (absPath) { var rmfileRelative = path.relative(srcPath, absPath) var rmfile = path.join(appPath, rmfileRelative) try { fs.unlinkSync(rmfile) fs.unlinkSync(rmfile + '.map') } catch (e) { devDebug('fail to unlink', rmfile) return } console.log('Deleted', rmfileRelative) cacheClean(); //清除緩存 }) }) // 動態編譯文件 function compileFile (srcDir, outDir, filename, cb) { const outFile = path.join(outDir, filename), srcFile = path.join(srcDir, filename); try { babelCliFile({ outFile: outFile, retainLines: true, highlightCode: true, comments: true, babelrc: true, sourceMaps: true }, [ srcFile ], { highlightCode: true, comments: true, babelrc: true, ignore: [], sourceMaps: true }) } catch (e) { console.error('Error while compiling file %s', filename, e) return } console.log(srcFile + ' -> ' + outFile) cb && cb() // 一般爲清除緩存 } // 清除緩存 function cacheClean () { Object.keys(require.cache).forEach(function (id) { if (/[\/\\](app)[\/\\]/.test(id)) { delete require.cache[id] } }) console.log('App Cache Cleaned...') } // 監聽程序退出 process.on('exit', function (e) { console.log('App Quit') })
注意:爲了能讓緩存失效,咱們一樣須要在 use 裏包裹一層函數,並以 require 的方式引入路由
app.use(async (ctx, next) => { await require('./router').routes()(ctx, next) })
nodemon 和 node-dev 都是可用於 node.js 開發版插件,提供簡單易用的開發環境。以 nodemon 爲例,全局安裝或本地安裝均可
npm install nodemon -g
而後經過 nodemon ./server.js localhost 8080 啓動開發進程。獨立、簡單,好用!
詳見:remy/nodemon
每一個方法都有不一樣的適用場景。若是想要嘗試最新語法,推薦試用方案三;若是追求簡單快捷,方案二是不錯的選擇。
若是我既想用最新的語法特性,又須要像 PM2 那樣簡單,怎麼辦?babel 構建工具(如 webpack)對於每一個前端開發並不陌生,再加一款 PM2 足以解決全部問題。