衔接上文,由于每次修改文件,都会触发重新全量打包,在项目体积几乎为0的情况下要花费3.5s,希望能提升性能,只对修改过的文件进行打包,但由于我们没有用到代码拆分code splitting
,所以此处优化为:不会codeSplicing未更改的文件
思路
- 精确获取更改的文件
- 将文件路径当作参数传入browserify函数
- browserify解析文件路径,只codeSplicing该文件
思考
- 之前使用nodemon监听文件变化,没有地方获取变更文件的信息
- 需要改为使用node,fs模块的watch函数进行监听,能够获取到更改文件的信息
- 为了可用性,解析之前的nodemon配置进行文件监听匹配
- 由于可能分为文件夹和文件,且文件夹深度未知,所以需要递归进行监听以及过滤
实现
收集nodemon配置,解析字段
const config = {
"watch": ["."],
"ext": "js,ts,css",
"ignore": ["./dist/chunk.js", "./test/temp*.js"],
"exec": "node ./src/browserify.js ./test/index.js"
}
递归过滤,监听
const extSet = new Set(config.ext.split(','))
const ignoreList = config.ignore.concat(['node_modules', '.git'])
const fileFilter = (path) => {
// 忽略文件校验
const isIgnore = ignoreList.some(item => {
return new RegExp(`${item.replace(/\./g, '\.').replace(/\\/g, '\\\\').replace(/\*/g, '.*')}$`).test(path)
})
if (isIgnore) return
//区分文件或文件夹
const stat = fs.statSync(path)
const isDirectory = stat.isDirectory()
if (isDirectory) {
const dir = fs.readdirSync(path)
dir.forEach(fileName => {
//递归对文件夹内每个文件执行一遍
fileFilter(join(path, fileName))
})
} else {
// 后缀校验
const ext = extname(path).replace(/^\./, '')
if (!ext || !extSet.has(ext)) return
//符合配置,开启监听
fs.watch(path, (eventType, filename) => {
console.log('开始重新构建');
console.log(filename, eventType);
execSync(config.exec)
console.log('完成重新构建');
})
}
}
测试
开始重新构建
module1.js change
完成重新构建
开始重新构建
module1.js change
完成重新构建
可以看到
- 确实实现了按照配置过滤及文件监听
- 能够获取到修改的文件名,以及修改类型
- 改一次文件watch可能会触发两次
关于触发两次的问题,发现官网说法如下
fs.watch API 跨平台并非 100% 一致,并且在某些情况下不可用。
网友反馈
Node.js `fs.watch`:
* 不报告OS X上的文件名。
* 在OS X上使用Sublime等编辑器时,根本不报告事件。
* 经常报告两次事件。
* 发布大多数更改为`rename`。
* 有[a lot of other issues](https://github.com/joyent/node/search?q=fs.watch&type=Issues)
* 不提供递归查看文件树的简便方法。
Node.js `fs.watchFile`:
* 事件处理几乎一样糟糕。
* 也不提供任何递归观看。
* 导致CPU利用率过高。
网上的解决方案如下:
- trick方式,但其实不是每次都会出发两次watch,所以不可取
var fs = require('fs');
var working = false;
fs.watch('directory', function (event, filename) {
if (filename && event == 'change' && active == false) {
active = true;
//do stuff to the new file added
active = false;
});
- 建议使用
chokidar
(https://github.com/paulmillr/chokidar)
好的,话不多说,这就使用chokidar,代码如下:
const chokidar = require('chokidar');
const { execSync } = require("child_process");
// One-liner for current directory
const watcher = chokidar.watch('.', {
persistent: true,
ignored: ['./dist/chunk.js', './test/temp *.js', '.git', '.history', 'node_modules', './src/moduleFuncCache.js'],
ignoreInitial: false,
followSymlinks: true,
cwd: '.',
disableGlobbing: false,
usePolling: false,
interval: 100,
binaryInterval: 300,
alwaysStat: false,
depth: 99,
awaitWriteFinish: {
stabilityThreshold: 2000,
pollInterval: 100
},
ignorePermissionErrors: false,
atomic: true
})
watcher.on('change', path => {
console.log(`更改文件${path}`);
//增量更改模块,需传入更改的文件的路径
execSync(`node ./src/browserify.js ./test/index.js ${path}`)
})
.on('unlink', path => {
// 遇到删除文件操作则重新打包一遍,及时发现错误
execSync(`node ./src/browserify.js ./test/index.js`)
});
更改js和css代码,效果如下:
更改文件test/module2.js
更改文件asserts/css/color.css
注意这句execSync(
node ./src/browserify.js ./test/index.js ${path})
,它会将更改的文件路径传入browserify.js,从而明确哪个模块更改了,从而只针对该模块进行替换
browserify.js文件接收如下:
const [path, changeFilePath] = process.argv.splice(2);