webpack2源码make生成模块依赖树

72 阅读4分钟

index.js

import { A } from './a.js'	
function test() {	
  const tmp = 'something'	
  return tmp + A	
}	
const r = test()

a.js

export const A = "a"

如上所示,index.js和a.js文件,根据这个demo看看webpack的make事件流是怎么样的。

var webpack = require("../lib/webpack.js");
var compiler = webpack(options);
compiler.run(compilerCallback);

执行webpack入口webpack函数,返回compiler对象,然后执行compiler.run开始编译。

Compiler.prototype.run = function(callback) {
     this.compile(callback);
}
Compiler.prototype.compile = function(callback){
    var self = this;

    self.applyPluginAsync(“before-compile”, params, function(err) {

         var compilation = self.newCompilation(params);

         self.applyPluginParallel(“make”, compilation, function(err){

              compilation.finish( );
              compilation.seal(callback);
        }
     }
}

Compiler的run方法调用Compiler的compile方法,在compile方法中,生成compilation实例,触发make事件执行对应事件的监听。

compiler.plugin("make", (compilation, callback) => {

    const dep = SingleEntryPlugin.createDependency(this.entry, this.name);
    compilation.addEntry(this.context, dep, this.name, callback);
});

在SingleEntryPlugin插件定义了make事件的监听,根据入口new SingleEntryDependency依赖dep,调用compilaition的addEntry方法。

class Compilation extends Tapable {
    addEntry(context, entry, name, callback) {

        this._addModuleChain(context, entry, (module) => {
            entry.module = module;
            this.entries.push(module);
        }, (err, module) => {
             return callback();
        });
    }
    _addModuleChain(context, dependency, onModule, callback) {
        const moduleFactory = this.denpendencyFactories.get(dependency.constructor);

        moduleFactory.create({
            context: context,
            dependencies: [dependency]
        }, (err, module) => {

            this.addModule(module);
            onModule(module);

            this.buildModule(module, false, null, null, (err) => {
                moduleReady.call(this);
            });
            function moduleReady( ) {

                this.processModuleDependencies(module, err => {
                    if(err)  {
                        return callback(err);
                    }
                    return callback(null, module);
                });
            }
        });
    }
    addModule(module) {

        this._modules[identifier] = module;
        this.modules.push(module);
    }
    buildModule(module, optional, origin, dependencies, thisCallback){

        module.build(this.options, this, this.resolvers.normal, this.inputFileSystem, (err) => {
            module.dependencies.sort(Dependency.compare);
            return thisCallback();
        });

    }
}

addEntry方法内执行_addModuleChain方法,_addModuleChain创建module,把module挂载在compilation上,构建module和dependency的模块依赖树,最后执行make事件的回调函数。在 _addModuleChain中,执行moduleFactory.create方法创建module,执行addModule方法把module存储到modules数组中。执行buildModule方法执行各种loaders解析字符串source,并把source转换成AST收集依赖。执行完buildModule后调用回调函数moduleReady,这个函数调用proceseeModuleDependencies方法处理当前module的dependencies,递归处理子模块。下面看看执行的函数是怎么实现的。 addModule方法,给this._modules对象添加属性identifier,属性值为module。其中变量identifier值为module的绝对路径。把module放到modules数组中。 buildModule方法,执行module模块的build方法,执行各种loaders解析module并收集依赖。

NormalModuleFactory.prototype.create = function(data, callback) {
    var _this = this;
    var dependencies = data.dependencies;
    var context = data.context || this.context;
    var request = dependencies[0].request;
    var contextInfo = data.contextInfo || { };
    _this.applyPluginsAsyncWaterfal("before-resolve", {
            contextInfo: contextInfo,
            context: context,
            request: request,
            Dependencies: dependencies
        }, function(err, result) {

        var factory = _this.applyPluginsWaterfall0("factory", null);

        factory(result, function(err, module) {

            if(module && _this.cachePredicate(module)) {
                dependencies.forEach(function(d) {
                    d._NormalModuleFactoryCache = module;
                });
            }
            
            callback(null, module);
        });    

    });
};

现在看看moduleFactory.create是如何创建模块的。触发factory事件,执行监听函数。

function NormalModuleFactory(context, resolvers, options) {
    Tapable.call(this);
    this.plugin("factory", function( ) {
        var _this = this;

        return function(result, callback) {

            var resolver = _this.applyPluginsWaterfall0("resolver", null);

            resolver(result, function onDoneResolving(err, data) {
                _this.applyPluginsAsyncWaterfall("after-resolve", data, function(err, result) {
                    
                    var createdModule = new NormalModule(
                        result.request,
                        result.userRequest,
                        result.rawRequest,
                        result.loaders,
                        result.resource,
                        result.parser
                    );
                    
                    return callback(null, createdModule);
                });
            });
        }
    }); 
}

 "factory"事件监听是在类NormalModuleFactory构造函数中定义的,监听函数包含两部分,resolver和create module。resolver解析index.js的路径信息和相关的loaders路径等。create module根据解析的信息创建NormalModule对象module。 resolver的信息如下:

{
    context: "/Users/lispringli/Desktop/webpack-2.2.0/bin",
    request: "/Users/lispringli/Desktop/webpack-2.2.0/node_modules/babel-loader/lib/index.js?{\"presets\":[\"@babel/preset-env\"]}!/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
    userRequest: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
    rawRequest: "./test/src/index.js",
    loaders: [
      {
        options: {
          presets: [
            "@babel/preset-env",
          ],
        },
        loader: "/Users/lispringli/Desktop/webpack-2.2.0/node_modules/babel-loader/lib/index.js",
      },
    ],
    resource: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
    parser: {}
}

创建的module对象:

{
    dependencies: [],
    blocks: [],
    variables: [],
    context: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src",
    reasons: [],
    debugId: 1000,
    lastId: -1,
    id: null,
    portableId: null,
    index: null,
    index2: null,
    depth: null,
    used: null,
    usedExports: null,
    providedExports: null,
    chunks: [],
    strict: false,
    meta: {},
    request: "/Users/lispringli/Desktop/webpack-2.2.0/node_modules/babel-loader/lib/index.js?{\"presets\":[\"@babel/preset-env\"]}!/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
    userRequest: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
    rawRequest: "./test/src/index.js",
    parser: {},
    resource: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
    loaders: [
      {
        options: {
          presets: [
            "@babel/preset-env",
          ],
        },
        loader: "/Users/lispringli/Desktop/webpack-2.2.0/node_modules/babel-loader/lib/index.js",
      },
    ],
    fileDependencies: [],
    contextDependencies: [],
    _source: null,
    assets: {},
    built: false,
    _cachedSource: null,
}
NormalModule.prototype.build = function build(options, compilation, resolver, fs, callback){
    var _this = this;

    return _this.doBuild(options, compilation, resolver, fs, function(err){

        _this.parser.parse(_this._source.source(), {
            current: _this,
            module: _this,
            compilation: compilation,
            options: options
        });

        return callback( );
    });
}
    
var runloaders = require("loader-runner").runLoaders;
NormalModule.prototype.doBuild = function doBuild(options, compilation, resolver, fs, callback) {
    runloaders({
        resource: this.resource,
        loaders: this.loaders,
        context: loaderContext,
        readResource: fs.readFile.bind(fs)
     }, function(err, result) {
        
        var source = result.result[0];
        Module._source = new OriginalSource(source, module.identifier( ));
        
        return callback( );
    });
} 

build方法包含两部分,doBuild和doBuild回调。doBuild执行各种loaders解析index.js,生成source字符串。source字符串如下所示。

"import { A } from './a.js';\nfunction test() {\n var tmp = 'something';\n return tmp + A;\n}\nvar r = test();"

doBuild回调执行parse.parse,把source转成AST,遍历AST获取依赖。parse把index.js解析成AST树,然后遍历AST树收集依赖,生成的模块依赖树如下图所示。然后进入seal阶段,运用module以及module的dependencies信息整合出最终的chunk。在遍历AST时,遇到import语句增加HarmonyImportDependency依赖,这个依赖又会创建生成a.js module。在遍历到函数test的return temp + A时,A是引入的变量,给module增加一个HarmonyImportSpecifierDepency依赖,在最终打包文件的时候会把return tmp + A 替换成return tmp + a_js_WEBPACK_IMPORTED_MODULE_0["A"]。

334336315-f20341c1-14ed-45ac-bdc3-7e0543c96949.png

make过程:

  1. 创建 index.js module。
  2. 将module保存到compilition。
  3. build Module。解析index.js文件,执行loaders生成source 字符串。然后通过parse把source生成AST,并遍历AST来收集依赖。
  4. 处理module的依赖,处理递归处理子模块,最终生成模块依赖树。

334400861-001d164d-993b-4564-92b5-bffcbb0e2ee4.png