收录待用,修改转载已取得腾讯云授权


前言

在 web 前端开发中,我们会借助 Grunt、Gulp 和 Webpack 等工具的 Watch 模块去监听文件变化,那服务端应该怎么做?其实文件变化的监听依然可以借助构建工具,但我们还需要自动重启服务或者热重载。本文将介绍三种常见的方法。

方案一:fs.watch

使用 node 原生的 fs.watch 方法监听文件改动,所谓的“热重载”也不过是及时清除内存中的文件缓存。示例如下:

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 的方式引入模块才能清除缓存。比如:

// 引入中间件或控制器
app.use(async (ctx, next) => {
await require('./controllers/main.js')(ctx);
});
// 或引入路由
app.use(async (ctx, next) => {
await require('./router').routes()(ctx, next)
})

方案二:应用进程管理器

此处以 PM2 为例,supervisorforever 等类似的进程管理工具异曲同工,这里不再赘述。

PM2 是一款带有负载均衡功能的 Node 应用进程管理器,具有 —watch 配置项,用来监听应用目录的变化,一旦发生变化,立即重启。详见:Auto restart apps on file change。他是真正意义上的重启,不是热替换。

缺点:PM2 并不提供优雅的方式告知用户何时重启或者杀掉进程。

以下是一个简单的 PM2 配置 (开发环境) start.js,启动进程 node start.js

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 文档):

1、在 OS X 下不能获取文件名;

2、在 OS X 下 Sublime 修改文件后不能获取到修改事件;

3、修改文件会触发两次事件;

4、不提供文件递归监听;

5、高 CPU 使用率;

6、…

这里使用 babel 的原因是想要支持最新的 js 语法,包括 ES2017、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

Node Server零基础——开发环境文件自动重载的更多相关文章

  1. 精品教程--IOS零基础开发环境搭建

    下载源码 技术要点: 1. 启动XCODE开始开发 2. IOS项目文件结构分析 3. 添加视图label组件 4. 程序的入口以及启动流程 5. 源码详细的中文注释 ...... 详细介绍: 1. ...

  2. JAVA 基础开发环境 vscode 搭建 Windows下VSCode编译运行简单java

    JAVA 基础开发环境 vscode 搭建 来源 https://www.cnblogs.com/freewsf/p/7744728.html 对于使用 Visual Studio Code 的 Ja ...

  3. html5游戏开发-零基础开发《圣诞老人送礼物》小游戏

    开言: 以前lufy前辈写过叫“ HTML5游戏开发-零基础开发RPG游戏”的系列文章,在那里面我学习了他的引擎以及了解了游戏脚本.自从看了那几篇文章,我便对游戏开发有了基本的认识.今天我也以零基础为 ...

  4. 【转载】salesforce 零基础开发入门学习(一)Salesforce功能介绍,IDE配置以及资源下载

    salesforce 零基础开发入门学习(一)Salesforce功能介绍,IDE配置以及资源下载   目前国内已经有很多公司做salesforce,但是国内相关的资料确是少之又少.上个月末跳槽去了新 ...

  5. Centos 基础开发环境搭建之Maven私服nexus

    hmaster 安装nexus及启动方式 /usr/local/nexus-2.6.3-01/bin ./nexus status Centos 基础开发环境搭建之Maven私服nexus . 软件  ...

  6. [Android] 环境配置之基础开发环境(SDK/Android Studio)(转)

    [Android] 环境配置之基础开发环境(SDK/Android Studio)   博客: blog.csdn.net/qiujuer 网站: www.qiujuer.net 开源库: Geniu ...

  7. 零基础开发一款微信小程序商城

    零基础开发一款微信小程序商城 一个朋友问我能不能帮忙做个商城?我一个完整网页都写不出的 菜鸟程序员,我该怎么拒绝呢?好吧,看在小程序这么火的形势下,我还是答应了!找了个开源项目,差不多花了三天时间搞定 ...

  8. 【转载】salesforce 零基础开发入门学习(四)多表关联下的SOQL以及表字段Data type详解

    salesforce 零基础开发入门学习(四)多表关联下的SOQL以及表字段Data type详解   建立好的数据表在数据库中查看有很多方式,本人目前采用以下两种方式查看数据表. 1.采用schem ...

  9. 【转载】salesforce 零基础开发入门学习(三)sObject简单介绍以及简单DML操作(SOQL)

    salesforce 零基础开发入门学习(三)sObject简单介绍以及简单DML操作(SOQL)   salesforce中对于数据库操作和JAVA等语言对于数据库操作是有一定区别的.salesfo ...

随机推荐

  1. python爬虫实战(五)--------智联招聘网

    前些天帮同事爬取一些智联招聘网上的关于数据分析的职位信息,他说要做一些数据分析看看,现在已经帮他爬完了.我本来想用Scrapy来爬的,但是不知道为什么爬取的数据和真实搜到的数据不太一样,比如:搜索到的 ...

  2. Python内置函数__slots__

    ''' 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 2.引子:使用点来访问属性本质就是在访问类或者对象的_ ...

  3. 深度学习应用系列(四)| 使用 TFLite Android构建自己的图像识别App

    深度学习要想落地实践,一个少不了的路径即是朝着智能终端.嵌入式设备等方向发展.但终端设备没有GPU服务器那样的强大性能,那如何使得终端设备应用上深度学习呢? 所幸谷歌已经推出了TFMobile,去年又 ...

  4. debug id

    id是Eclipse的debugger自己生成的,用于告诉你哪些变量是指向同一个对象:id相同即指向同一个对象. primitive不是对象,所以就没有id. 但是如果你用primitive的wrap ...

  5. Xamarin 2017.9.19更新

     Xamarin 2017.9.19更新   本次更新是添加Xamarin.iOS对iOS 11和Xcode 9的支持.Visual Studio 2017升级到15.3.5获得更新功能.Visual ...

  6. lambda 表达式+python内置函数

    #函数 def f1(a,b): retrun  a+b #lambda方式,形参(a,b):返回值(a+b) f2=lambda a,b : a+b 在一些比较简单的过程计算就可以用lambda p ...

  7. ubuntu16.04安装python3

    今天用了下阿里云的云服务器,装个python3真是各种踩坑.记录下吧: ubuntu自带了2.7.想要装3.5并设置为默认python版本. 安装python3.5 sudo add-apt-repo ...

  8. XML XSD XSL区别与联系

    XML: XML(Extensible Markup Language)即可扩展标记语言,它与HTML一样,都是SGML(Standard Generalized Markup Language,标准 ...

  9. java8新特性——四大内置核心函数式接口

    在前面几篇简单介绍了一些Lambda表达式得好处与语法,我们知道使用Lambda表达式是需要使用函数式接口得,那么,岂不是在我们开发过程中需要定义许多函数式接口,其实不然,java8其实已经为我们定义 ...

  10. 【BZOJ 3229】 3229: [Sdoi2008]石子合并 (GarsiaWachs算法)

    3229: [Sdoi2008]石子合并 Description 在一个操场上摆放着一排N堆石子.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合 ...