/webpack

A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows to load parts for the application on demand. Through "loaders," modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.

Primary LanguageJavaScriptMIT LicenseMIT

  1. Compiler instance of watch method
 Compiler.prototype.watch = function(watchOptions, handler) {
  this.fileTimestamps = {};
  this.contextTimestamps = {};
  var watching = new Watching(this, watchOptions, handler);
  return watching;
};

注意:watch的时候,我们会给我们的Compiler实例对象添加一个fileTimestamps和contextTimestamps对象。而且可以清楚的知道这里返回的是我们通过watchOptions实例化的一个Watching实例对象 我们可以使用下面这种方式调用,例如在atool-build中调用:

if (args.watch) {
    compiler.watch(args.watch || 200, doneHandler);
  } else {
    compiler.run(doneHandler);
  }

2.compiler实例化的时候同时实例化一个Parser对象

function Compiler() {
  Tapable.call(this);
  this.parser = {
    plugin: function(hook, fn) {
      this.plugin("compilation", function(compilation, data) {
        data.normalModuleFactory.plugin("parser", function(parser) {
          parser.plugin(hook, fn);
        });
      });
    }.bind(this),
    apply: function() {
      this.plugin("compilation", function(compilation, data) {
        data.normalModuleFactory.plugin("parser", function(parser) {
          parser.apply.apply(parser, args);
        });
      });
    }.bind(this)
  };

  this.options = {};
}

我们的Compiler会先继承了Tapable;在parser.plugin中注入的回调函数

3.调用compiler.run方法会分别执行'before-run','run',继而调用compiler的compile方法

Compiler.prototype.run = function(callback) {
  var self = this;
  var startTime = new Date().getTime();
   //before run
  self.applyPluginsAsync("before-run", self, function(err) {
    if(err) return callback(err);
        //run
    self.applyPluginsAsync("run", self, function(err) {
      if(err) return callback(err);
      self.readRecords(function(err) {
        if(err) return callback(err);
         //compile函数被调用,我们传入run函数的回调函数会在compile回调函数中调用
        //也就是在compiler的'done'之后回调
        self.compile(function onCompiled(err, compilation) {
          if(err) return callback(err);
          if(self.applyPluginsBailResult("should-emit", compilation) === false) {
            var stats = compilation.getStats();
            stats.startTime = startTime;
            stats.endTime = new Date().getTime();
            self.applyPlugins("done", stats);
            return callback(null, stats);
          }
          self.emitAssets(compilation, function(err) {
            if(err) return callback(err);
            if(compilation.applyPluginsBailResult("need-additional-pass")) {
              compilation.needAdditionalPass = true;
              var stats = compilation.getStats();
              stats.startTime = startTime;
              stats.endTime = new Date().getTime();
              self.applyPlugins("done", stats);
              self.applyPluginsAsync("additional-pass", function(err) {
                if(err) return callback(err);
                self.compile(onCompiled);
              });
              return;
            }
            self.emitRecords(function(err) {
              if(err) return callback(err);
              var stats = compilation.getStats();
              stats.startTime = startTime;
              stats.endTime = new Date().getTime();
              self.applyPlugins("done", stats);
              return callback(null, stats);//调用'done'
            });
          });
        });
      });
    });
  });
};

注意: (1)我们compiler.compile方法运行结束后会进行相应的回调,其中回调函数就是我们通过compile.run调用时候传入的函数 (2)其中我们要注意我们传入的callback会被传入一个参数,这个参数是通过如下方式来获取到的:

   var stats = compilation.getStats();
     stats.startTime = startTime;
     stats.endTime = new Date().getTime();

那么getStats到底得到的是什么呢?

getStats() {
    return new Stats(this);
  }

也就是说我们得到的是一个Stats对象,具体用法看参考文献。那么我们给出一个例子:

function doneHandler(err, stats) {
    if (args.json) {
      const filename = typeof args.json === 'boolean' ? 'build-bundle.json' : args.json;
      const jsonPath = join(fileOutputPath, filename);
      writeFileSync(jsonPath, JSON.stringify(stats.toJson()), 'utf-8');
      console.log(`Generate Json File: ${jsonPath}`);
    }
    //如果出错,那么退出码是1
    const { errors } = stats.toJson();
    if (errors && errors.length) {
      process.on('exit', () => {
        process.exit(1);
      });
    }
    // if watch enabled only stats.hasErrors would log info
    // otherwise  would always log info
    if (!args.watch || stats.hasErrors()) {
      const buildInfo = stats.toString({
        colors: true,
        children: true,
        chunks: !!args.verbose,
        modules: !!args.verbose,
        chunkModules: !!args.verbose,
        hash: !!args.verbose,
        version: !!args.verbose,
      });
      if (stats.hasErrors()) {
        console.error(buildInfo);
      } else {
        console.log(buildInfo);
      }
    }
    if (err) {
      process.on('exit', () => {
        process.exit(1);
      });
      console.error(err);
    }
    if (callback) {
      callback(err);
    }
  }

主要的代码就是调用stats.toJson方法,内容就是获取本次编译的主要信息。同时参考文献中也给出了一个输出的例子,可以自己查看。 (3)我们自己的回调函数是在compiler的'done'回调以后触发的,而且和compiler的'done'回调一样,我们也是也是给我们的函数传入err和Stats对象!

然后我们看看compile中的内容:

Compiler.prototype.compile = function(callback) {
  self.applyPluginsAsync("before-compile", params, function(err) {
    self.applyPlugins("compile", params);
    var compilation = self.newCompilation(params);
    //调用compiler的compile方法,我们才会构建出一个Compilation实例对象,在
    //'make'钩子里面我们就可以获取到compilation对象了
    self.applyPluginsParallel("make", compilation, function(err) {
      compilation.finish();
      compilation.seal(function(err) {
        self.applyPluginsAsync("after-compile", compilation, function(err) {
          //在compilation.seal方法调用以后我们才会执行'after-compile'
        });
      });
    });
  });
};

我们再来看看compilation的finish方法:

  finish() {
      this.applyPlugins1("finish-modules", this.modules);
      this.modules.forEach(m => this.reportDependencyErrorsAndWarnings(m, [m]));
    }

我们再来看看compilation.seal方法:

seal(callback) {
  self.applyPlugins0("seal");
  self.applyPlugins0("optimize");
  while(self.applyPluginsBailResult1("optimize-modules-basic", self.modules) ||
    self.applyPluginsBailResult1("optimize-modules", self.modules) ||
    self.applyPluginsBailResult1("optimize-modules-advanced", self.modules));
  self.applyPlugins1("after-optimize-modules", self.modules);
  //这里是optimize module
  while(self.applyPluginsBailResult1("optimize-chunks-basic", self.chunks) ||
    self.applyPluginsBailResult1("optimize-chunks", self.chunks) ||
    self.applyPluginsBailResult1("optimize-chunks-advanced", self.chunks));
    //这里是optimize chunk
  self.applyPlugins1("after-optimize-chunks", self.chunks);
  //这里是optimize tree
  self.applyPluginsAsyncSeries("optimize-tree", self.chunks, self.modules, function sealPart2(err) {
    self.applyPlugins2("after-optimize-tree", self.chunks, self.modules);
    const shouldRecord = self.applyPluginsBailResult("should-record") !== false;
    self.applyPlugins2("revive-modules", self.modules, self.records);
    self.applyPlugins1("optimize-module-order", self.modules);
    self.applyPlugins1("advanced-optimize-module-order", self.modules);
    self.applyPlugins1("before-module-ids", self.modules);
    self.applyPlugins1("module-ids", self.modules);
    self.applyModuleIds();
    self.applyPlugins1("optimize-module-ids", self.modules);
    self.applyPlugins1("after-optimize-module-ids", self.modules);
    self.sortItemsWithModuleIds();
    self.applyPlugins2("revive-chunks", self.chunks, self.records);
    self.applyPlugins1("optimize-chunk-order", self.chunks);
    self.applyPlugins1("before-chunk-ids", self.chunks);
    self.applyChunkIds();
    self.applyPlugins1("optimize-chunk-ids", self.chunks);
    self.applyPlugins1("after-optimize-chunk-ids", self.chunks);
    self.sortItemsWithChunkIds();
    if(shouldRecord)
      self.applyPlugins2("record-modules", self.modules, self.records);
    if(shouldRecord)
      self.applyPlugins2("record-chunks", self.chunks, self.records);
    self.applyPlugins0("before-hash");
    self.createHash();
    self.applyPlugins0("after-hash");
    if(shouldRecord)
      self.applyPlugins1("record-hash", self.records);
    self.applyPlugins0("before-module-assets");
    self.createModuleAssets();
    if(self.applyPluginsBailResult("should-generate-chunk-assets") !== false) {
      self.applyPlugins0("before-chunk-assets");
      self.createChunkAssets();
    }
    self.applyPlugins1("additional-chunk-assets", self.chunks);
    self.summarizeDependencies();
    if(shouldRecord)
      self.applyPlugins2("record", self, self.records);

    self.applyPluginsAsync("additional-assets", err => {
      if(err) {
        return callback(err);
      }
      self.applyPluginsAsync("optimize-chunk-assets", self.chunks, err => {
        if(err) {
          return callback(err);
        }
        self.applyPlugins1("after-optimize-chunk-assets", self.chunks);
        self.applyPluginsAsync("optimize-assets", self.assets, err => {
          if(err) {
            return callback(err);
          }
          self.applyPlugins1("after-optimize-assets", self.assets);
          if(self.applyPluginsBailResult("need-additional-seal")) {
            self.unseal();
            return self.seal(callback);
          }
          return self.applyPluginsAsync("after-seal", callback);
        });
      });
    });
  });
}

从上面提到的第3点,我们可以知道webpack的编译过程大致如下:

'before run'
  'run'
    compile:func//调用compile函数
        'before compile'
           'compile'//(1)compiler对象的第一阶段
               newCompilation:object//创建compilation对象
               'make' //(2)compiler对象的第二阶段 
                    compilation.finish:func
                       "finish-modules"
                    compilation.seal
                         "seal"
                         "optimize"
                         "optimize-modules-basic"
                         "optimize-modules-advanced"
                         "optimize-modules"
                         "after-optimize-modules"//首先是优化模块
                         "optimize-chunks-basic"
                         "optimize-chunks"//然后是优化chunk
                         "optimize-chunks-advanced"
                         "after-optimize-chunks"
                         "optimize-tree"
                            "after-optimize-tree"
                            "should-record"
                            "revive-modules"
                            "optimize-module-order"
                            "advanced-optimize-module-order"
                            "before-module-ids"
                            "module-ids"//首先优化module-order,然后优化module-id
                            "optimize-module-ids"
                            "after-optimize-module-ids"
                            "revive-chunks"
                            "optimize-chunk-order"
                            "before-chunk-ids"//首先优化chunk-order,然后chunk-id
                            "optimize-chunk-ids"
                            "after-optimize-chunk-ids"
                            "record-modules"//record module然后record chunk
                            "record-chunks"
                            "before-hash"
                               compilation.createHash//func
                                 "chunk-hash"//webpack-md5-hash
                            "after-hash"
                            "record-hash"//before-hash/after-hash/record-hash
                            "before-module-assets"
                            "should-generate-chunk-assets"
                            "before-chunk-assets"
                            "additional-chunk-assets"
                            "record"
                            "additional-assets"
                                "optimize-chunk-assets"
                                   "after-optimize-chunk-assets"
                                   "optimize-assets"
                                      "after-optimize-assets"
                                      "need-additional-seal"
                                         unseal:func
                                           "unseal"
                                      "after-seal"
                    "after-compile"//(4)完成模块构建和编译过程(seal函数回调)    
    "emit"//(5)compile函数的回调,compiler开始输出assets,是改变assets最后机会
    "after-emit"//(6)文件产生完成

注意:上面没有标出第三个阶段,也就是compiler的'build-module'阶段,在这个阶段,我们调用了addEntry等方法通过入口文件_addModuleChain,processModuleDependencies等方法分析模块的依赖关系! 详细内容可以参考下面这种图:

参考资料: http://webpack.github.io/docs/node.js-api.html#stats