Skip to content

watch 实现

webpack watch 配置

简单使用

javascript
// webpack.config.js 中配置
{
  watch: true
}

// 获取直接运行命令
webpack watch
// webpack.config.js 中配置
{
  watch: true
}

// 获取直接运行命令
webpack watch

compiler.watch

watchtrue时,执行的是compilerwatch方法,而不是compiler.run方法:

javascript
const { compiler, watch, watchOptions } = create();
if (watch) {
  compiler.watch(watchOptions, callback);
} else {
  compiler.run((err, stats) => {
    compiler.close(err2 => {
      callback(err || err2, stats);
    });
  });
}
const { compiler, watch, watchOptions } = create();
if (watch) {
  compiler.watch(watchOptions, callback);
} else {
  compiler.run((err, stats) => {
    compiler.close(err2 => {
      callback(err || err2, stats);
    });
  });
}

compilerwatch方法如下:

javascript
watch(watchOptions, handler) {
  if (this.running) {
    return handler(new ConcurrentCompilationError());
  }

  this.running = true;
  this.watchMode = true;
  // 实例化 Watching
  this.watching = new Watching(this, watchOptions, handler);
  return this.watching;
}
watch(watchOptions, handler) {
  if (this.running) {
    return handler(new ConcurrentCompilationError());
  }

  this.running = true;
  this.watchMode = true;
  // 实例化 Watching
  this.watching = new Watching(this, watchOptions, handler);
  return this.watching;
}

其核心代码为new Watching()

Watching类

Watching类实例化时,会执行_invalidate方法:

javascript
class Watching {
  constructor(compiler, watchOptions, handler) {
    process.nextTick(() => {
      if (this._initial) this._invalidate();
    });
  }
}
class Watching {
  constructor(compiler, watchOptions, handler) {
    process.nextTick(() => {
      if (this._initial) this._invalidate();
    });
  }
}

_invalidate又会执行_go方法,精简代码如下:

javascript
_go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles) {
  const run = () => {
    this.compiler.hooks.watchRun.callAsync(this.compiler, err => {
      const onCompiled = (err, compilation) => {
        if (this.compiler.hooks.shouldEmit.call(compilation) === false) {
          return this._done(null, compilation);
        }
        process.nextTick(() => {
          this.compiler.emitAssets(compilation, err => {

            this.compiler.emitRecords(err => {

              return this._done(null, compilation);
            });
          });
        });
      };
      this.compiler.compile(onCompiled);
    });
  };
  run();
}
_go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles) {
  const run = () => {
    this.compiler.hooks.watchRun.callAsync(this.compiler, err => {
      const onCompiled = (err, compilation) => {
        if (this.compiler.hooks.shouldEmit.call(compilation) === false) {
          return this._done(null, compilation);
        }
        process.nextTick(() => {
          this.compiler.emitAssets(compilation, err => {

            this.compiler.emitRecords(err => {

              return this._done(null, compilation);
            });
          });
        });
      };
      this.compiler.compile(onCompiled);
    });
  };
  run();
}

可以看出这里的代码与compiler.run执行的过程非常相似,不同的是最后回调执行的是_done方法:

javascript
_done(err, compilation) {
  this.compiler.hooks.done.callAsync(stats, err => {
    this.handler(null, stats);
    this.compiler.cache.storeBuildDependencies(
      compilation.buildDependencies,
      err => {
        process.nextTick(() => {
          if (!this.closed) {
            this.watch(
              compilation.fileDependencies,
              compilation.contextDependencies,
              compilation.missingDependencies
            );
          }
        });
        for (const cb of cbs) cb(null);
        this.compiler.hooks.afterDone.call(stats);
      }
    );
  });
}
_done(err, compilation) {
  this.compiler.hooks.done.callAsync(stats, err => {
    this.handler(null, stats);
    this.compiler.cache.storeBuildDependencies(
      compilation.buildDependencies,
      err => {
        process.nextTick(() => {
          if (!this.closed) {
            this.watch(
              compilation.fileDependencies,
              compilation.contextDependencies,
              compilation.missingDependencies
            );
          }
        });
        for (const cb of cbs) cb(null);
        this.compiler.hooks.afterDone.call(stats);
      }
    );
  });
}

该方法会在hooks.done之后,hooks.afterDone之前执行this.watch方法,参数为编译过程中收集到的dependencies。此时代码已经完成编译,且已经输出成文件,所以可以开始监听这些文件:

javascript
watch(files, dirs, missing) {
  this.pausedWatcher = null;
  this.watcher = this.compiler.watchFileSystem.watch(
    files,
    dirs,
    missing,
    this.lastWatcherStartTime,
    this.watchOptions,
    (
      err,
      fileTimeInfoEntries,
      contextTimeInfoEntries,
      changedFiles,
      removedFiles
    ) => {},
    (fileName, changeTime) => {}
  )
}
watch(files, dirs, missing) {
  this.pausedWatcher = null;
  this.watcher = this.compiler.watchFileSystem.watch(
    files,
    dirs,
    missing,
    this.lastWatcherStartTime,
    this.watchOptions,
    (
      err,
      fileTimeInfoEntries,
      contextTimeInfoEntries,
      changedFiles,
      removedFiles
    ) => {},
    (fileName, changeTime) => {}
  )
}

通过watchFileSystem.watch方法监听dependencies文件变化,如果发生变化,会触发回调函数:

javascript
// 首先执行变动文件的回调函数
(fileName, changeTime) => {}

// 其次执行重新打包的回调函数 this._invalidate
(
err,
 fileTimeInfoEntries,
 contextTimeInfoEntries,
 changedFiles,
 removedFiles
) => {
  this._invalidate(
    fileTimeInfoEntries,
    contextTimeInfoEntries,
    changedFiles,
    removedFiles
  );
  this._onChange();
},
// 首先执行变动文件的回调函数
(fileName, changeTime) => {}

// 其次执行重新打包的回调函数 this._invalidate
(
err,
 fileTimeInfoEntries,
 contextTimeInfoEntries,
 changedFiles,
 removedFiles
) => {
  this._invalidate(
    fileTimeInfoEntries,
    contextTimeInfoEntries,
    changedFiles,
    removedFiles
  );
  this._onChange();
},

这样就完成了当文件改变时,重新进行打包。

总结

watch的实现的核心是在hooks.done之后,也就是编译后的代码生成文件的时候,通过监听收集到的dependencies对应的文件。当文件改变时,触发回调,再次进行build