Node Server零基礎——開發環境文件自動重載

收錄待用,修改轉載已取得騰訊雲受權前端


前言

在 web 前端開發中,咱們會藉助 Grunt、Gulp 和 Webpack 等工具的 Watch 模塊去監聽文件變化,那服務端應該怎麼作?其實文件變化的監聽依然能夠藉助構建工具,但咱們還須要自動重啓服務或者熱重載。本文將介紹三種常見的方法。node

方案一:fs.watch

使用 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 爲例,supervisorforever 等相似的進程管理工具殊途同歸,這裏再也不贅述。github

PM2 是一款帶有負載均衡功能的 Node 應用進程管理器,具備 —watch 配置項,用來監聽應用目錄的變化,一旦發生變化,當即重啓。詳見:Auto restart apps on file change。他是真正意義上的重啓,不是熱替換。web

缺點:PM2 並不提供優雅的方式告知用戶什麼時候重啓或者殺掉進程。
如下是一個簡單的 PM2 配置 (開發環境) start.js,啓動進程 node start.jsnpm

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 + babel

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 足以解決全部問題。


原文連接:https://www.qcloud.com/community/article/476280

相關文章
相關標籤/搜索