本节流程如图:

  现在正式进入打包流程,起步方法为run:

  1. Compiler.prototype.run = (callback) => {
  2. const startTime = Date.now();
  3.  
  4. const onCompiled = (err, compilation) => { /**/ };
  5.  
  6. this.applyPluginsAsync("before-run", this, err => {
  7. if (err) return callback(err);
  8.  
  9. this.applyPluginsAsync("run", this, err => {
  10. if (err) return callback(err);
  11.  
  12. this.readRecords(err => {
  13. if (err) return callback(err);
  14.  
  15. this.compile(onCompiled);
  16. });
  17. });
  18. });
  19. }

  为什么不介绍compiler对象?因为构造函数中并没有一个初始化的方法,只是普通的变量声明,没啥好讲的。

  在run方法中,首先是调用了tapable的applyPluginsAsync执行了before-run事件流,该事件流的定义地点如下:

  1. // NodeEnvironmentPlugin
  2. compiler.plugin("before-run", (compiler, callback) => {
  3. if (compiler.inputFileSystem === inputFileSystem)
  4. inputFileSystem.purge();
  5. callback();
  6. });

  在对compiler对象的文件系统方法的挂载插件中,注入了before-run这个事件流,这里首先看一下applyPluginsAsync(做了小幅度的修改以适应webpack源码):

  1. // tapable
  2. Tapable.prototype.applyPluginsAsync = (name, ...args, callback) => {
  3. var plugins = this._plugins[name];
  4. if (!plugins || plugins.length === 0) return callback();
  5. var i = 0;
  6. var _this = this;
  7. // args为[args,next函数]
  8. args.push(copyProperties(callback, function next(err) {
  9. // 事件流出错或者全部执行完后调用回调函数
  10. if (err) return callback(err);
  11. i++;
  12. if (i >= plugins.length) {
  13. return callback();
  14. }
  15. // 执行下一个事件
  16. plugins[i].apply(_this, args);
  17. }));
  18. // 执行第一个事件
  19. plugins[0].apply(this, args);
  20. };

  当时在第八节没有讲这个系列的事件流触发方式,这里简单说下:

1、copyProperties用于对象属性的拷贝,类似于Object.assign,然而在这里传入的是两个函数,一点用都没有!!!!!(当时没写讲解就是因为一直卡在这个对象拷贝方法在这里有什么毛用)

2、在webpack中,args为一个this,指向compiler的上下文

3、注入该事件流的事件必须要执行callback方法(如上例),此时执行的并不是外部的callback,而是next函数

4、有两种情况下会执行外部callback,中途出错或者所有事件流执行完毕

  这样就很明白了,注入before-run中的函数形参的意义如下:

  1. // before-run
  2. // compiler => this
  3. // callback => next
  4. (compiler, callback) => {
  5. if (compiler.inputFileSystem === inputFileSystem)
  6. inputFileSystem.purge();
  7. callback();
  8. }

  由于before-run中只有一个事件,所以在调用内部callback的next方法后,会由于i大于事件长度而直接调用外部callback。

  这里的purge方法之前见过,这里复习下内容:

  1. // NodeEnvironmentPlugin
  2. compiler.inputFileSystem = new CachedInputFileSystem(new NodeJsInputFileSystem(), 60000);
  3.  
  4. // CachedInputFileSystem
  5. CachedInputFileSystem.prototype.purge = function(what) {
  6. this._statStorage.purge(what);
  7. this._readdirStorage.purge(what);
  8. this._readFileStorage.purge(what);
  9. this._readlinkStorage.purge(what);
  10. this._readJsonStorage.purge(what);
  11. };
  12.  
  13. // CachedInputFileSystem => Storage
  14. Storage.prototype.purge = function(what) {
  15. if (!what) {
  16. this.count = 0;
  17. clearInterval(this.interval);
  18. this.nextTick = null;
  19. this.data.clear();
  20. this.levels.forEach(function(level) {
  21. level.clear();
  22. });
  23. } else if (typeof what === "string") { /**/ } else { /**/ }
  24. };

  一句话概括就是:清除所有打包中缓存的数据。

  由于假设是第一次,所以这里并没有什么实际操作,接着调用外部callback,用同样的方式触发了run事件流。

  run事件流也只有一个方法,来源于CachePlugin插件:

  1. Compiler.plugin("run", (compiler, callback) => {
  2. // 这个属性我暂时也不知道是啥 反正直接callback了
  3. if (!compiler._lastCompilationFileDependencies) return callback();
  4. const fs = compiler.inputFileSystem;
  5. const fileTs = compiler.fileTimestamps = {};
  6. asyncLib.forEach(compiler._lastCompilationFileDependencies, (file, callback) => {
  7. // ...
  8. }, err => {
  9. // ...
  10. });
  11. });

  在第一次触发run事件流时,那个属性是undefined,所以会直接跳过,因为我是边看源码边解析,所以也不知道是啥,哈哈。

  

  接下来下一个callback是这个:

  1. this.readRecords(err => {
  2. if (err) return callback(err);
  3. this.compile(onCompiled);
  4. });

  这是另一个原型方法,源码如下:

  1. Compiler.prototype.readRecords = (callback) => {
  2. // 这个属性也没有
  3. if (!this.recordsInputPath) {
  4. this.records = {};
  5. return callback();
  6. }
  7. this.inputFileSystem.stat(this.recordsInputPath, err => {
  8. // ...
  9. });
  10. }

  这里第一次也会跳过并直接callback,看源码大概是传入一个路径并读取里面的文件信息缓存到records中。

  这下连跳两步,直接进入原型方法compile中,预览一下这个函数:

  1. Compiler.prototype.compile = (callback) => {
  2. const params = this.newCompilationParams();
  3. // 依次触发事件流
  4. this.applyPluginsAsync("before-compile", params, err => {
  5. if (err) return callback(err);
  6. this.applyPlugins("compile", params);
  7. const compilation = this.newCompilation(params);
  8. this.applyPluginsParallel("make", compilation, err => {
  9. if (err) return callback(err);
  10. compilation.finish();
  11. compilation.seal(err => {
  12. if (err) return callback(err);
  13. this.applyPluginsAsync("after-compile", compilation, err => {
  14. if (err) return callback(err);
  15. return callback(null, compilation);
  16. });
  17. });
  18. });
  19. });
  20. }

  编译打包的核心流程已经一览无遗,方法中依次触发了before-compile、compile、make、after-compile事件流,最后调用了回调函数。

  从下一节开始详细讲解每一步的流程(不懂的地方肯定会跳过啦)。

.17-浅析webpack源码之compile流程-入口函数run的更多相关文章

  1. .20-浅析webpack源码之compile流程-Template模块

    这里的编译前指的是开始触发主要的事件流this-compilaiton.compilation之前,由于还有一些准备代码,这一节全部弄出来. 模块基本上只走构造函数,具体的方法调用的时候再具体讲解. ...

  2. .18-浅析webpack源码之compile流程-rules参数处理(1)

    Tips:写到这里,需要对当初的规则进行修改.在必要的地方,会在webpack.config.js中设置特殊的参数来跑源码,例如本例会使用module:{rules:[...]}来测试,基本上测试参数 ...

  3. .19-浅析webpack源码之compile流程-rules参数处理(2)

    第一步处理rule为字符串,直接返回一个包装类,很简单看注释就好了. test/include/exclude 然后处理test.include.exclude,如下: if (rule.test | ...

  4. 51ak带你看MYSQL5.7源码1:main入口函数

    从事DBA工作多年 MYSQL源码也是头一次接触 尝试记录下自己看MYSQL5.7源码的历程 目录: 51ak带你看MYSQL5.7源码1:main入口函数 51ak带你看MYSQL5.7源码2:编译 ...

  5. 从Webpack源码探究打包流程,萌新也能看懂~

    简介 上一篇讲述了如何理解tapable这个钩子机制,因为这个是webpack程序的灵魂.虽然钩子机制很灵活,而然却变成了我们读懂webpack道路上的阻碍.每当webpack运行起来的时候,我的心态 ...

  6. async源码学习 - 控制流程waterfall函数

    waterfall函数会连续执行数组中的函数,每次通过数组下一个函数的结果.然而,数组任务中的任意一个函数结果传递失败,那么该函数的下一个函数将不会执行,并且主回调函数立马把错误作为参数执行. 以上是 ...

  7. .34-浅析webpack源码之事件流make(3)

    新年好呀~过个年光打游戏,function都写不顺溜了. 上一节的代码到这里了: // NormalModuleFactory的resolver事件流 this.plugin("resolv ...

  8. jQuery 2.0.3 源码分析Sizzle引擎 - 编译函数(大篇幅)

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 从Sizzle1.8开始,这是Sizzle的分界线了,引入了编译函数机制 网上基本没有资料细说这个东东的,sizzle引入这 ...

  9. .29-浅析webpack源码之Resolver.prototype.resolve

    在上一节中,最后返回了一个resolver,本质上就是一个Resolver对象: resolver = new Resolver(fileSystem); 这个对象的构造函数非常简单,只是简单的继承了 ...

随机推荐

  1. day01_雷神_Python入门

    day01 1.编程语言 主流的像C.java.python.php.C#.等,可以从不同维度分类如下: 机器码和字节码 机器码: C 字节码: 其他 note: 机器码是电脑的CPU可直接解读的数据 ...

  2. centos下添加epel源

    RHEL以及他的衍生发行版如CentOS.Scientific Linux为了稳定,官方的rpm repository提供的rpm包往往是很滞后的,当然了,这样做这是无可厚非的,毕竟这是服务器版本,安 ...

  3. HDU 1465 2045 已知结果往前推

    1465 不容易系列之一 Time Limit: 1000 MS Memory Limit: 32768 KB 64-bit integer IO format: %I64d , %I64u Java ...

  4. C#调用haskell遭遇Attempted to read or write protected memory

    1. Haskell的代码如下: 上面的代码中readMarkdown与writeHtmlString是pandoc中的函数,newString的作用是将String转换为IO CString. 2. ...

  5. WPF学习笔记(4):获取DataGridTemplateColumn模板定义的内容控件

    在之前的DataGrid的DataGridTemplateColumn列中,自定义了一个TextBox控件,但是在C#代码中提示找不到这个控件,导致无法对该控件进行操作.在网上搜索后,发现一些处理方法 ...

  6. ASP.NET MVC 使用 Log4net 记录日志

    Log4net 介绍 Log4net 是 Apache 下一个开放源码的项目,它是Log4j 的一个克隆版.我们可以控制日志信息的输出目的地.Log4net中定义了多种日志信息输出模式.它可以根据需要 ...

  7. 背水一战 Windows 10 (40) - 控件(导航类): AppBar, CommandBar

    [源码下载] 背水一战 Windows 10 (40) - 控件(导航类): AppBar, CommandBar 作者:webabcd 介绍背水一战 Windows 10 之 控件(导航类) App ...

  8. 背水一战 Windows 10 (61) - 控件(媒体类): InkCanvas 涂鸦编辑

    [源码下载] 背水一战 Windows 10 (61) - 控件(媒体类): InkCanvas 涂鸦编辑 作者:webabcd 介绍背水一战 Windows 10 之 控件(媒体类) InkCanv ...

  9. [实战演练]蜻蜓FM2014年校招笔试题目 - 规则二叉树

    题目:某规则二叉树的定义是:对于树中任意两个叶结点A.B,他们与根结点的距离分别是d1和d2,|d1-d2|<=1.请写出函数 bool isRuledTree(Node *root)的代码实现 ...

  10. WCF接口实例介绍

    Windows Communication Foundation(WCF)是由微软开发的一系列支持数据通信的应用程序框架,可以翻译为Windows 通讯开发平台. WCF整合了原有的windows通讯 ...