实现构建工具之增量打包

衔接上文,由于每次修改文件,都会触发重新全量打包,在项目体积几乎为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利用率过高。

网上的解决方案如下:

  1. 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;
});
  1. 建议使用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);

你可能感兴趣的:(实现构建工具之增量打包)