美文网首页
Compiler 和 Compilation 对象

Compiler 和 Compilation 对象

作者: Dabao123 | 来源:发表于2017-11-25 12:21 被阅读315次

    Compiler 和 Compilation 对象

    chunk
    [ { id: 0,
       //chunk id
        rendered: true,
        //https://github.com/liangklfangl/commonchunkplugin-source-code
        initial: false,
        //require.ensure 产生的 chunk,非 initial
        //initial 表示是否在页面初始化就需要加载的模块,而不是按需加载的模块
        entry: false,
        //是否含有 Webpack 的 runtime 环境,通过 CommonChunkPlugin 处理后,runtime 环境被提到最高层级的 chunk
        recorded: undefined,
        extraAsync: false,
        size: 296855,
        //chunk 大小、比特
        names: [],
        //require.ensure 不是通过 Webpack 配置的,所以 chunk 的 names 是空
        files: [ '0.bundle.js' ],
        //该 chunk 产生的输出文件,即输出到特定文件路径下的文件名称
        hash: '42fbfbea594ba593e76a',
        //chunk 的 hash,即 chunkHash
        parents: [ 2 ],
        //父级 chunk 的 id 值
        origins: [ [Object] ] 
        //该 chunk 是如何产生的
        },
      { id: 1,
        rendered: true,
        initial: false,
        entry: false,
        recorded: undefined,
        extraAsync: false,
        size: 297181,
        names: [],
        files: [ '1.bundle.js' ],
        hash: '456d05301e4adca16986',
        parents: [ 2 ],
        origins: [ [Object] ] }
        ```
         chunk -- origins  -- 描述了某一个 chunk 是如何产生的:
         ```
         {
      "loc": "", // Lines of code that generated this chunk
      "module": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
      "moduleId": 0, // The ID of the module
      "moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
      "moduleName": "./lib/index.web.js", // Relative path to the module
      "name": "main", // The name of the chunk
      "reasons": [
        // A list of the same `reasons` found in module objects
      ]
    }
    
    assets
    [ {
      "chunkNames": [], 
      // The chunks this asset contains
      //这个输出资源包含的 chunks 名称。对于图片的 require 或者 require.ensure 动态产生的 chunk 是不会有 chunkNames 的,但是在 entry 中配置的都是会有的
      "chunks": [ 10, 6 ],
       // The chunk IDs this asset contains
       //这个输出资源包含的 chunk的ID。通过 require.ensure 产生的 chunk 或者 entry 配置的文件都会有该 chunks 数组,require 图片不会有
      "emitted": true,
       // Indicates whether or not the asset made it to the `output` directory
       //使用这个属性标识 assets 是否应该输出到 output 文件夹
      "name": "10.web.js", 
      // The `output` filename
      //表示输出的文件名
      "size": 1058 
      // The size of the file in bytes
      //输出的这个资源的文件大小
    }
      { name: '1.bundle.js',
        size: 299469,
        chunks: [ 1, 3 ],
        chunkNames: [],
        emitted: undefined,
        isOverSizeLimit: undefined },
      { name: 'bundle.js',
        
        size: 968,
        
        chunks: [ 2, 3 ],
        
        chunkNames: [ 'main' ],
        
        emitted: undefined,
        
        isOverSizeLimit: undefined },
      { name: 'vendor.bundle.js',
        size: 5562,
        chunks: [ 3 ],
        chunkNames: [ 'vendor' ],
        emitted: undefined,
        isOverSizeLimit: undefined }]
    
    modules
    { id: 10,
    //该模块的 id 和 `module.id` 一样
    identifier: 'C:\\Users\\Administrator\\Desktop\\webpack-chunkfilename\\node_
    odules\\html-loader\\index.js!C:\\Users\\Administrator\\Desktop\\webpack-chunkf
    lename\\src\\Components\\Header.html',
    //Webpack 内部使用这个唯一的 ID 来表示这个模块
    name: './src/Components/Header.html',
    //模块名称,已经转化为相对于根目录的路径
    index: 10,
    index2: 8,
    size: 62,
    cacheable: true,
    //表示这个模块是否可以缓存,调用 this.cacheable()
    built: true,
    //表示这个模块通过 Loader、Parsing、Code Generation 阶段
    optional: false,
    //所以对该模块的加载全部通过 try..catch 包裹
    prefetched: false,
    //表示该模块是否是预加载的。即在第一个 import、require 调用之前就开始解析和打包该模块https://webpack.js.org/plugins/prefetch-plugin/
    chunks: [ 0 ],
    //该模块在那个 chunk 中出现
    assets: [],
    //该模块包含的所有的资源文件集合
    issuer: 'C:\\Users\\Administrator\\Desktop\\webpack-chunkfilename\\node_modu
    es\\eslint-loader\\index.js!C:\\Users\\Administrator\\Desktop\\webpack-chunkfil
    name\\src\\Components\\Header.js',
    //是谁开始本模块的调用的,即模块调用发起者
    issuerId: 1,
    //发起者的 moduleid
    issuerName: './src/Components/Header.js',
    //发起者相对于根目录的路径
    profile: undefined,
    failed: false,
    //在解析或者处理该模块的时候是否失败
    errors: 0,
    //在解析或者处理该模块的是否出现的错误数量
    warnings: 0,
    //在解析或者处理该模块的是否出现的警告数量
    reasons: [ [Object] ],
    usedExports: [ 'default' ],
    providedExports: null,
    depth: 2,
    source: 'module.exports = "<header class=\\"header\\">{{text}}</header>";' }
    //source 是模块内容,但是已经变成了字符串了
    
    {
    "loc": "33:24-93",
    // Lines of code that caused the module to be included
    "module": "./lib/index.web.js",
    // Relative path to the module based on context
    "moduleId": 0, 
    // The ID of the module
    "moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", 
    // Path to the module
    "moduleName": "./lib/index.web.js", 
    // A more readable name for the module (used for "pretty-printing")
    "type": "require.context", 
    // The type of request used
    "userRequest": "../../cases" 
    // Raw string used for the `import` or `require` request
    }
    

    Compiler 对象

    Webpack 的 Compiler 模块是 Webpack 主要引擎,通过它可以创建一个 Compilation 实例,而且所有通过 cli 或者 Webpack 的 API 或者 Webpack 的配置文件传入的配置都会作为参数来构建一个 Compilation 实例。可以通过 webpack.compiler 来访问它。Webpack 通过实例化一个 Compiler 对象,然后调用它的 run 方法来开始一次完整的编译过程。

    watch

    Compiler.prototype.watch = function(watchOptions, handler) {
      this.fileTimestamps = {};
      this.contextTimestamps = {};
      var watching = new Watching(this, watchOptions, handler);
      return watching;
    };
    
    if (args.watch) {
        compiler.watch(args.watch || 200, doneHandler);
      } else {
        compiler.run(doneHandler);
      }
    

    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中注入的回调函数。
    compiler.run

    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'
                });
              });
            });
          });
        });
      });
    };
    

    compiler.compile方法运行结束后会进行相应的回调,其中回调函数就是我们通过compile.run调用时候传入的函数
    其中我们要注意我们传入的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方法,内容就是获取本次编译的主要信息。同时参考文献中也给出了一个输出的例子,可以自己查看。
    我们自己的回调函数是在compiler的'done'回调以后触发的,而且和compiler的'done'回调一样,我们也是也是给我们的函数传入err和Stats对象!

    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);
            });
          });
        });
      });
    }
    
    '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.watch方法的调用 其本质是使用了相关的配置生成了Watching对象:

    var watching = new Watching(this, watchOptions, handler);
    
    Watching.prototype.watch = function(files, dirs, missing) {
      this.watcher = this.compiler.watchFileSystem.watch(files, dirs, missing, this.startTime, this.watchOptions, function(err, filesModified, contextModified, missingModified, fileTimestamps, contextTimestamps) {
        this.watcher = null;
        if(err) return this.handler(err);
        this.compiler.fileTimestamps = fileTimestamps;
        this.compiler.contextTimestamps = contextTimestamps;
        this.invalidate();
      }.bind(this), function(fileName, changeTime) {
        this.compiler.applyPlugins("invalid", fileName, changeTime);
      }.bind(this));
    };
    

    如果我们的文件发生了变化,那么我们直接调用Watching实例的invalidate方法,并通知compiler重新开始编译过程!这也是我们最重要的watch逻辑!这也是我们为什么有上面这样的代码:

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

    compiler的watch方法返回的是一个watching,那么我们看看Watching对象的内部结构:

    function Watching(compiler, watchOptions, handler) {
    this.startTime = null;
    this.invalid = false;//是否已经文件变化
    this.error = null;
    this.stats = null;
    this.handler = handler;
    this.compiler = compiler;//compiler句柄
    this.running = true;
    }
    Watching.prototype._go = function() {
    };
    Watching.prototype._done = function(err, compilation) {
    };
    Watching.prototype.watch = function(files, dirs, missing) {
    };
    
    Watching.prototype.invalidate = function() {
    if(this.watcher) {
     this.watcher.pause();
     this.watcher = null;
    }
    if(this.running) {
     this.invalid = true;
     return false;
    } else {
     this._go();
    }
    };
    Watching.prototype.close = function(callback) {
    if(callback === undefined) callback = function() {};
    if(this.watcher) {
     this.watcher.close();
     this.watcher = null;
    }
    if(this.running) {
     this.invalid = true;
     this._done = function() {
       callback();
     };
    } else {
     callback();
    }
    };
    

    通过上面的结果你应该可以知道invalidate和close方法的具体作用了,这里就不在赘述
    compiler对象

    // Run compiler.
      const compiler = webpack(webpackConfig);
      // Hack: remove extract-text-webpack-plugin log
      if (!args.verbose) {
        compiler.plugin('done', (stats) => {
          stats.stats.forEach((stat) => {
            stat.compilation.children = stat.compilation.children.filter((child) => {
              return child.name !== 'extract-text-webpack-plugin';
            });
          });
        });
      }
    

    'done'回调是当'emit,after-emit'都调用结束了以后才会触发的,所以这时候我们所有的文件assets都已经生成结束了。
    当我们调用webpack方法的时候,返回的就是compiler对象!
    我们的stats对象有一个compilation属性,从构造函数就可以看到:

    class Stats {
     constructor(compilation) {
       this.compilation = compilation;
       this.hash = compilation.hash;
     }
    }
    

    参考资料:
    http://taobaofed.org/blog/2016/09/09/webpack-flow/

    https://github.com/webpack/extract-text-webpack-plugin/issues/35

    http://webpack.github.io/docs/plugins.html#the-parser-instance

    相关文章

      网友评论

          本文标题:Compiler 和 Compilation 对象

          本文链接:https://www.haomeiwen.com/subject/ycqcbxtx.html