首页 > 作文

webpack源码解析(使用介绍)

更新时间:2023-04-03 10:18:35 阅读: 评论:0

webpack 源码解析

序言

项目上在使用webpack,感叹真是神器,既然是神器,就想探知究竟。

总览

webpack整体是一个插件架构,所有的功能都以插件的方式集成在构建流程中,通过发布订阅事件来触发各个插件执行。webpack核心使用Tapable 来实现插件(plugins)的binding和applying.

先整体来看一下webpack事件流:通过在Tapable中打日志获得

         method                       event-name--------------------------------------------------applyPluginsBailResult           |   entry-optionapplyPlugins                     |   after-pluginsapplyPlugins                     |   after-resolversapplyPlugins                     |   environmentapplyPlugins                     |   after-environmentapplyPluginsAsync                |   before-runapplyPluginsAsyncSeries          |   runapplyPlugins                     |   normal-module-factoryapplyPlugins                     |   context-module-factoryapplyPlugins                     |   compileapplyPlugins                     |   this-compilationapplyPlugins                     |   compilationapplyPluginsParallel             |   makeapplyPluginsAsyncWaterfall       |   before-resolveapplyPluginsWaterfall            |   factoryapplyPluginsWaterfall            |   resolverapplyPlugins                     |   resolveapplyPlugins                     |   resolve-stepapplyPluginsParallelBailResult   |   fileapplyPluginsParallelBailResult   |   directoryapplyPlugins                     |   resolve-stepapplyPluginsParallelBailResult   |   resultapplyPluginsAsyncWaterfall       |   after-resolveapplyPluginsBailResult           |   create-moduleapplyPluginsWaterfall            |   moduleapplyPlugins                     |   build-moduleapplyPlugins                     |   normal-module-loaderapplyPluginsBailResult           |   programapplyPluginsBailResult           |   statementapplyPluginsBailResult           |   evaluate MemberExpressionapplyPluginsBailResult           |   evaluate Identifier document.writeapplyPluginsBailResult           |   call document.writeapplyPluginsBailResult           |   expression document.writeapplyPluginsBailResult           |   expression documentapplyPlugins                     |   succeed-moduleapplyPlugins                     |   alapplyPlugins                     |   optimizeapplyPlugins                     |   optimize-modulesapplyPlugins                     |   after-optimize-modulesapplyPlugins                     |   optimize-chunksapplyPlugins                     |   after-optimize-chunksapplyPluginsAsyncSeries          |   optimize-treeapplyPlugins                     |   after-optimize-treeapplyPluginsBailResult           |   should-recordapplyPlugins                     |   revive-modulesapplyPlugins                     |   optimize-module-orderapplyPlugins                     |   before-module-idsapplyPlugins                     |   optimize-module-idsapplyPlugins                     |   after-optimize-module-idsapplyPlugins                     |   record-modulesapplyPlugins                     |   revive-chunksapplyPlugins                     |   optimize-chunk-orderapplyPlugins                     |   before-chunk-idsapplyPlugins                     |   optimize-chunk-idsapplyPlugins                     |   after-optimize-chunk-idsapplyPlugins                     |   record-chunksapplyPlugins                     |   before-hashapplyPlugins                     |   hashapplyPlugins                     |   hashapplyPlugins                     |   hashapplyPlugins                     |   hashapplyPlugins                     |   hash-for-chunkapplyPlugins                     |   chunk-hashapplyPlugins                     |   after-hashapplyPlugins                     |   before-chunk-astsapplyPluginsWaterfall            |   global-hash-pathsapplyPluginsBailResult           |   global-hashapplyPluginsWaterfall            |   bootstrapapplyPluginsWaterfall            |   local-varsapplyPluginsWaterfall            |   requireapplyPluginsWaterfall            |   module-objapplyPluginsWaterfall            |   module-requireapplyPluginsWaterfall            |   require-extensionsapplyPluginsWaterfall            |   ast-pathapplyPluginsWaterfall            |   startupapplyPluginsWaterfall            |   module-requireapplyPluginsWaterfall            |   renderapplyPluginsWaterfall            |   moduleapplyPluginsWaterfall            |   renderapplyPluginsWaterfall            |   packageapplyPluginsWaterfall            |   modulesapplyPluginsWaterfall            |   render-with-entryapplyPluginsWaterfall            |   ast-pathapplyPlugins                     |   chunk-astapplyPlugins                     |   additional-chunk-astsapplyPlugins                     |   recordapplyPluginsAsyncSeries          |   additional-astsapplyPluginsAsyncSeries          |   optimize-chunk-astsapplyPlugins                     |   after-optimize-chunk-astsapplyPluginsAsyncSeries          |   optimize-astsapplyPlugins                     |   after-optimize-astsapplyPluginsAsyncSeries          |   after-compileapplyPluginsBailResult           |   should-emitapplyPlugins汽车销售技巧和话术AsyncSeries          |   emitapplyPluginsWaterfall            |   ast-pathapplyPluginsAsyncSeries          |   after-emitapplyPlugins                     |   done

其中有几个关键节段对应的事件分别是:

entry-option 初始化option

run 开始编译

make 从entry开始递归的分析依赖,对每个依赖模块进行build

before-resolve – after-resolve 对其中一个模块位置进行解析

build-module 开始构建 (build) 这个module,这里将使用文件对应的loader加载

normal-module-loader 对用loader加载完成的module(是一段js代码)进行编译,用 acorn 编译,生成ast抽象语法树。

program 开始对ast进行遍历,当遇到require等一些调用表达式时,触发call require事件的handler执行,收集依赖,并。如:AMDRequireDependenciesBlockParrPlugin等

al 所有依赖build完成,下面将开始对chunk进行优化,比如合并,抽取公共模块,加hash

bootstrap 生成启动代码

emit 把各个chunk输出到结果文件

webpack的关键实体

模块,依赖,模块工厂

模块

Module是webpack的中的核心实体,要加载的一切和所有的依赖都是Module,总之一切都是Module。它有很多子类:RawModule,NormalModule ,MultiModule,ContextModule,DelegatedModule,DllModule,ExternalModule 等

依赖

每一个依赖(Dependency)的实体都包含一个module字段,指向被依赖的Module. 这样通过Module的dependencies数组成员就能找出该模块所依赖的其它模块。 webpack使用不同的Dependency子类,如AMDRequireDependency ,AMDDefineDependency ,AMDRequireArrayDependency,CommonJsRequireDependency,SystemImportDependency来表式不同的模块加载规范, 通过对应的DependencyParrPlugin来加载 AMD或CMD的模块。 后面会专门讲不同DependencyParrPlugin的实现方式 。

依赖模版Template

每个依赖都有相应Template,用来生成加载该依赖模块的js代码。

模块工厂

使用工厂模式创建不同的Module,有四个主要的子类: NormalModuleFactory,ContextModuleFactory , DllModuleFactory,MultiModuleFactory.

调用过程

webpack的实际入口是Compiler类的run方法, 在run方法里调用compile方法开始编译。在编译的时候会使用一个核心对象:Compilation.

核心对象Compilation

该对象负责组织整个编译过程,包含了每个构建环节所对应的方法,如:addEntry ,buildModule,processModuleDependencies,summarizeDependencies,createModuleAsts,createHash等等。

主要的成员

参考源码:

        this.compiler = compiler;       //Compiler对象的引用        this.resolvers = compiler.resolvers;   //模块解析器        this.inputFileSystem = compiler.inputFileSystem;        var options = this.options = compiler.options;        this.outputOptions = options && options.output;        this.bail = options && options.bail;        this.profile = options && options.profile;        this.mainTemplate = new MainTemplate(this.outputOptions);   //这里Template是用来生成js结果文件的。        this.chunkTemplate = new ChunkTemplate(this.outputOptions, this.mainTemplate);        this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(this.outputOptions);        this.moduleTemplate = new ModuleTemplate(this.outputOptions);        this.entries = [];              //入口        this.preparedChunks = [];        //预先加载的chunk        this.chunks = [];                                //所有的chunk        this.namedChunks = {};                        //每个都对应一个名子,可以通过namedChunks[name]获取chunk        this.modules = [];              //所有module        this._modules = {};        this.cache = null;        this.records = null;        this.nextFreeModuleId = 0;        this.nextFreeChunkId = 0;        this.nextFreeModuleIndex = 0;        this.nextFreeModuleIndex2 = 0;        this.additionalChunkAsts = [];        this.asts = {};                                //保存所有生成的文件        this.errors = [];        this.warnings = [];        this.children = [];            // 保存子Compilation对象,子Compilation对象依赖它的上级Compilation对象生成的结果,所以要等父Compilation编译完成才能开始。        this.dependencyFactories = new ArrayMap();   //保存Dependency和ModuleFactory的对应关系,方便创建该依赖对应的Module        this.dependencyTemplates = new ArrayMap();   //保存Dependency和Template对应关系,方便生成加载此模块的代码

程序核心处理流程注释

SingleEntryPlugin,MultiEntryPlugin 两个插件中注册了对make事件的监听,当Compiler执行make时,触发对 Compilation.addEntry 方法的调用. 在addEntry方法内调用私有方法_addModuleChain :

Compilation.prototype._addModuleChain = function process(context, dependency, onModule, callback) {    var start = this.profile && +new Date();    var errorAndCallback = this.bail ? function errorAndCallback(err) {        callback(err);    } : function errorAndCallback(err) {        err.dependencies = [dependency];        this.errors.push(err);        callback();    }.bind(this);    if(typeof dependency !== "object" || dependency === null || !dependency.constructor) {        throw new Error("Parameter 'dependency' must be a Dependency");    }    //根据依赖模块的类型获取对应的模块工厂,用于后边创建模块。    var moduleFactory = this.dependencyFactories.get(dependency.constructor);    if(!moduleFactory) {        throw new Error("No dependency factory available for this dependency type: " + dependency.constructor.name);    }    //使用模块工厂创建模块,并将创建出来的module作为参数传给回调方法:就是下边`function(err, module)`的参数    moduleFactory.create(context, dependency, function(err, module) {        if(err) {            return errorAndCallback(new EntryModuleNotFoundError(err));        }        if(this.profile) {            if(!module.profile) {                module.profile = {};            }            var afterFactory = +new Date();            module.profile.factory = afterFactory - start;        }        var result = this.addModule(module);        //result表示该module是否第一次创建        if(!result) {            //不是第一次创建            module = this.getModule(module);            onModule(module);            if(this.profile) {                var afterBuilding = +new Date();                module.profile.building = afterBuilding - afterFactory;            }            return callback(null, module);        }        //如果module已缓存过,且不需要rebuild。result是一个Module对象,直接返回该缓存的module        if(result instanceof Module) {            if(this.profile) {                result.profile = module.profile;            }            module = result;            onModule(module);            moduleReady.call(this);            return;        }        onModule(module);        //下面要对module进行build了。包括调用loader处理源文件,使用acorn生成AST,将遍历AST,遇到requirt等依赖时,创建依赖(Dependency)加入依赖数组.        this.buildModule(module, function(err) {            if(err) {                return errorAndCallback(err);            }            if(this.profile) {                var afterBuilding = +new Date();                module.profile.building = afterBuilding - afterFactory;            }        //OK,这里module已经build完了,依赖也收集好了,开始处理依赖的module            moduleReady.call(this);        }.bind(this));        function moduleReady() {            this.processModuleDependencies(module, function(err) {                if(err) {                    return callback(err);                }                return callback(null, module);            });        }    }.bind(this));};

递归处理依赖

经过上面buildModule后,程序调用processModuleDependencies开始递归处理依赖的module.:

Compilation.prototype.addModuleDependencies = function(module, dependencies, bail, cacheGroup, recursive, callback) {    var _this = this;    var start = _this.profile && +new Date();    var factories = [];    for(var i = 0; i < dependencies.length; i++) {        var factory = _this.dependencyFactories.get(dependencies[i][0].constructor);        if(!factory) {            return callback(new Error("No module factory available for dependency type: " + dependencies[i][0].constructor.name));        }        factories[i] = [factory, dependencies[i]];    }    //遍历每个依赖模块    async.forEach(factories, function(item, callback) {        //这下面跟上面处理_addModuleChain方法类似        var dependencies = item[1];        var criticalDependencies = dependencies.filter(function(d) {            return !!d.critical;        });        if(criticalDependencies.length > 0) {            _this.warnings.push(new CriticalDependenciesWarning(module, criticalDependencies));        }        var errorAndCallback = function errorAndCallback(err) {            err.dependencies = dependencies;            err.origin = module;            module.dependenciesErrors.push(err);            _this.errors.push(err);            if(bail) {                callback(err);            } el {                callback();            }        };        var warningAndCallback = function warningAndCallback(err) {            err.dependencies = dependencies;            err.origin = module;            module.dependenciesWarnings.push(err);            _this.warnings.push(err);            callback();        };        var factory = item[0];        //创建Module        factory.create(module.context, dependencies[0], function(err, dependentModule) {            function isOptional() {                return dependencies.filter(function(d) {                    return !d.optional;                }).length === 0;            }            function errorOrWarningAndCallback(err) {                if(isOptional()) {                    return warningAndCallback(err);                } el {                    return errorAndCallback(err);                }            }            if(err) {                return errorOrWarningAndCallback(new ModuleNotFoundError(module, err));            }            if(!dependentModule) {                return process.nextTick(callback);            }            if(_this.profile) {                if(!打呼噜是什么原因dependentModule.profile) {                    dependentModule.profile = {};                }                var afterFactory = +new Date();                dependentModule.profile.factory = afterFactory - start;            }            dependentModule.issuer = module.identifier();            var newModule = _this.addModule(dependentModule, cacheGroup);            if(!newModule) { // from cache                dependentModule = _this.getModule(dependentModule);                if(dependentModule.optional) {                    dependentModule.optional = isOptional();                }                dependencies.forEach(function(dep) {                    dep.module = de石家庄城市经济职业学院pendentModule;                    dependentModule.addReason(module, dep);                });                if(_this.profile) {                    if(!module.profile) {                        module.profile = {};                    }                    var time = +new Date() - start;                    if(!module.profile.dependencies || time > module.profile.dependencies) {                        module.profile.dependencies = time;                    }                }                return process.nextTick(callback);            }            if(newModule instanceof Module) {                if(_this.profile) {                    newModule.profile = dependentModule.profile;                }                newModule.optional = isOptional();                newModule.issuer = dependentModule.issuer;                dependentModule = newModule;                dependencies.forEach(function(dep) {                    dep.module = dependentModule;                    dependentModule.addReason(module, dep);                });                if(_this.profile) {                    var afterBuilding = +new Date();                    module.profile.building = afterBuilding - afterFactory;                }                if(recursive) {                    return process.nextTick(_this.processModuleDependencies.bind(_this, dependentModule, callback));                } el {                    return process.nextTick(callback);                }            }            dependentModule.optional = isOptional();            dependencies.forEach(function(dep) {                dep.module = dependentModule;                dependentModule.addReason(module, dep);            });            //build模块            _this.buildModule(dependentModule, function(err) {                if(err) {                    return errorOrWarningAndCallback(err);                }                if(_this.profile) {                    var afterBuilding = +new Date();                    dependentModule.profile.building = afterBuilding - afterFactory;                }                //循环处理此模块的依赖                if(recursive) {                    _this.processModuleDependencies(dependentModule, callback);                } el {                    return callback();                }            });        });    }, function(err) {        if(err) {            return callback(err);        }        return callback();    });};

所有模块build完成,开始封装

调用al方法封装,要逐次对每个module和chunk进行整理,生成编译后的源码,合并,拆分,生成hash。 webpack会根据不同的插件,如MinChunkSizePlugin,LimitChunkCou经典语句ntPlugin 将不同的module整理到不同的chunk里,每个chunk最终对应一个输出文件。此时所有的module仍然保存的是编译前的 原始文件内容。webpack需求将源代码里的require()调用替换成webpack模块加载代码,说白了就是生成最终编译后的代码。

通过Template生成结果代码

生成结果js的调用入口,是compilation类里的createChunkAsts方法:

    //如果是入口,则使用MainTemplate生成结果,否则使用ChunkTemplate.    if(chunk.entry) {        source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates);    } el {        source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates);    }

Template是用来生成结果代码的。webpack中Template有四个子类:

MainTemplate.js 用于生成项目入口文件

ChunkTemplate.js 用于生成异步加载的js代码

ModuleTemplate.js 用于生成某个模块的代码

HotUpdateChunkTemplate.js

在MainTemplate和ChunkTemplate需要根据依赖的模块,逐个调用ModuleTemplate的render方法。下面分析ModuleTemplate是如何生成每个模块的结果代码的:

ModuleTemplate.prototype.render = function(module, dependencyTemplates, chunk) {    var moduleSource = module.source(dependencyTemplates, this.outputOptions, this.requestShortener);    moduleSource = this.applyPluginsWaterfall("module", moduleSource, module, chunk, dependencyTemplates);    moduleSource = this.applyPluginsWaterfall("render", moduleSource, module, chunk, dependencyTemplates);    return this.applyPluginsWaterfall("package", moduleSource, module, chunk, dependencyTemplates);};

第一行module.source()方法即是生成该模块结果代码的方法。source是一个抽象方法,在Module的不同子类里会重写该方法。在子类NormalModule的source方法里,必须把源代码中的require()引入的模块代码替换成 webpack的模块加载代码,完成此功能的代码就是这句:

    //还记得dependencyTemplates是什么吗?就是保存Dependency和Template对应关系,下面这句从获取不同的Dependency.Template实例     //如AMDDefineDependency.Template ,AMDRequireContextDependency.Template ,CommonJsRequireDependency.Template     var template = dependencyTemplates.get(dep.constructor);    if(!template) throw new Error("No template for dependency: " + dep.constructor.name);    //source是一个ReplaceSource,可利用dep参数的range属性定位require调用在源码中的位置,从而实现替换。    //range: 根据par:acorn的文档说明,保存了AST节点在源码中的起始位置和结束位置[ start , end ]    template.apply(dep, source, outputOptions, requestShortener, dependencyTemplates);

比如最终会生成类似以下的代码:

//原始文件内容是:    var kidsico =  require('asts/img/kids.gif') , cloico = require('asts/img/clo.gif');  var kidsico =  __webpack_require__(32) , cloico = __webpack_require__(33); 

最后输出到结果文件

webpack会在Compiler的emitAsts方法里把compilation.asts里的结果写到输出文件里,在此前会先创建输出目录。所有当你要开发一些自定义的 插件要输出一些结果时,把文件放入compilation.asts里即可。

使用acorn生成AST,并遍历AST收集依赖

webpack使用acorn解析每一个经loader处理过的source,并且成AST,然后遍历所有节点,当遇到require调用时,会分析是AMD的还是CMD的调用,或者是require.ensure . 我们不再分析AST的遍历过程了。

对loader的加载和调用

webpack官网对 loader 已经介绍的非常详细了,不再多说。你只需要记住:

webpack在build模块时 (`调用doBuild方法`),要先调用相应的loader对resource进行加工,生成一段js代码后交给acorn解析生成AST.所以不管是css文件,还是jpg文件,还是html模版,最终经过loader处理会变成一个module:一段js代码。

比如:url-loader,根据loader配置生成一段dataURL或者使用调用loadercontext的emitFile方法向asts添加一个文件。

经典插件

html-webpack-plugin

在HtmlWebpackPlugin里通过 var childCompiler = compilation.createChildCompiler(compilerName, outputOptions)创建了childCompiler, 然后调用childCompiler.compile方法进行编译, 使得HtmlWebpackPlugin也可以使用webpack的loader机制,如html-loader,handlebar-loader等等来处理template.最后从compilation对象中取出chunk和css注入到html 的head或者body里。

extract-text-webpack-plugin

extract-text-webpack-plugin 被用来抽取css样式到独立的文件,方便页面引用,因此必须配合css-loader使用。 ExtractTextPlugin.extract(“style-loader”, “css-loader?sourceMap!cssnext-loader”) 这样的loader配置,第一个before参数style-loader会被省略掉不参与loader处理,真正 起作用的是第二个参数css-loader?sourceMap!cssnext-loader ,所以配置成ExtractTextPlugin.extract(“css-loader?sourceMap!cssnext-loader”)也可以。

首先在资源build的时候,使用ExtractTextPlugin的loader将创建一个childCompiler(类似html-webpack-plugin)对css(或者sass,)文件重新进行编译,将编译结果记录在module的meta数组里。原来的位置替换成一行注释:// removed by extract-text-webpack-plugin 编译完成后,在优化chunk的时候 ( optimize-tree 事件触发 )将每个module的meta数组取出来生成独立的css文件。

CommonsChunkPlugin

这个插件用来提取公共的module到独立的chunk文件里。如果只有一个entry是没必须用这个插件 。当有多个entry,可能每个entry有一些公共依赖的module。此时CommonsChunkPlugin会把这些公共的module提取 到独立的文件中。https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin有详细的介绍

UglifyJsPlugin

在optimize-chunk-asts时,将每个chunk逐一uglify一把,然后再输出结果文件。

← 遇到一个jdk建立SSL连接时的坑:Rever DNS lookup during SSL 挂科handshake

webpack 源码解析→

本文发布于:2023-04-03 10:18:32,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/c3e6e6b72c5ca2c069f0ac811f619709.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:webpack源码解析(使用介绍).doc

本文 PDF 下载地址:webpack源码解析(使用介绍).pdf

标签:模块   方法   文件   代码
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图