You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
classSemaphore{constructor(available){// available 为最大的并发数量this.available=available;this.waiters=[];this._continue=this._continue.bind(this);}acquire(callback){if(this.available>0){this.available--;callback();}else{this.waiters.push(callback);}}release(){this.available++;if(this.waiters.length>0){process.nextTick(this._continue);}}_continue(){if(this.available>0){if(this.waiters.length>0){this.available--;constcallback=this.waiters.pop();callback();}}}}
constSingleEntryPlugin=require("./SingleEntryPlugin");constMultiEntryPlugin=require("./MultiEntryPlugin");constDynamicEntryPlugin=require("./DynamicEntryPlugin");constitemToPlugin=(context,item,name)=>{if(Array.isArray(item)){returnnewMultiEntryPlugin(context,item,name);}returnnewSingleEntryPlugin(context,item,name);};module.exports=classEntryOptionPlugin{apply(compiler){compiler.hooks.entryOption.tap("EntryOptionPlugin",(context,entry)=>{// string 类型则为 new SingleEntryPlugin// array 类型则为 new MultiEntryPluginif(typeofentry==="string"||Array.isArray(entry)){itemToPlugin(context,entry,"main").apply(compiler);}elseif(typeofentry==="object"){// 对于 object 类型,遍历其中每一项for(constnameofObject.keys(entry)){itemToPlugin(context,entry[name],name).apply(compiler);}}elseif(typeofentry==="function"){// function 类型则为 DynamicEntryPluginnewDynamicEntryPlugin(context,entry).apply(compiler);}returntrue;});}};
staticcreateDependency(entries,name){returnnewMultiEntryDependency(entries.map((e,idx)=>{constdep=newSingleEntryDependency(e);// Because entrypoints are not dependencies found in an// existing module, we give it a synthetic iddep.loc=`${name}:${100000+idx}`;returndep;}),name);}
引言
对于 webpack 来说每个文件都是一个 module,这篇文章带你来看 webpack 如何从配置中 entry 的定义开始,顺藤摸瓜找到全部的文件,并转化为 module。
总览
webpack 入口 entry,entry 参数是单入口字符串、单入口数组、多入口对象还是动态函数,无论是什么都会调用
compilation.addEntry
方法,这个方法会执行_addModuleChain
,将入口文件加入需要编译的队列中。然后队列中的文件被一个一个处理,文件中的import
引入了其他的文件又会通过addModuleDependencies
加入到编译队列中。最终当这个编译队列中的内容完成被处理完时,就完成了文件到 module 的转化。上面是一个粗略的轮廓,接下来我们将细节一一补充进这个轮廓中。首先看编译的总流程控制——编译队列的控制
编译队列控制 —— Semaphore
_addModuleChain 和 addModuleDependencies
函数中都会调用this.semaphore.acquire
这个函数的具体实现在lib/util/Semaphore.js
文件中。看一下具体的实现对外暴露的只有两个个方法:
这个 Semaphore 类借鉴了在多线程环境中,对使用资源进行控制的 Semaphore(信号量)的概念。其中并发个数通过 available 来定义,那么默认值是多少呢?在
Compilation.js
中可以找到默认的并发数是 100,注意这里说的并发只是代码设计中的并发,不要和js的单线程特性搞混了。总的来看编译流程如下图
从入口到 _addModuleChain
webpack 官网配置指南中 entry 可以有下面几种形式:
这些是哪里处理的呢?
webpack 的启动文件 webpack.js 中, 会先对 options 进行处理,有如下一句
在
process
的过程中会对entry
的配置做处理先看
EntryOptionsPlugin
做了什么在
EntryOptionsPlugin
中注册了entryOption
的事件处理函数,根据entry
值的不同类型(string/array/object中每一项/functioin)实例化和执行不同的EntryPlugin
:string 对应SingleEntryPlugin
; array 对应MultiEntryPlugin
;function 对应DynamicEntryPlugin
。而对于 object 类型来说遍历其中的每一个 key,将每一个 key 当做一个入口,并根据类型 string/array 的不同选择 SingleEntryPlugin 或 MultiEntryPlugin。下面我们主要分析:SingleEntryPlugin,MultiEntryPlugin,DynamicEntryPlugin横向对比一下这三个 Plugin,都做了两件事:
dependencyFactories
_addModuleChain
进入正式的编译阶段。结合 webpack 的打包流程,我们从 Compiler.js 中的 compile 方法开始,看一下 compilation 事件和 make 事件回调起了什么作用
xxxEntryPlugin 在 compilation 事件中回调用来设置
compilation.dependencyFactories
,保证在后面_addModuleChain
回调阶段可以根据 dependency 获取到对应的moduleFactory
。make 事件回调中根据不同的 entry 配置,生成 dependency,然后调用
addEntry
,并将 dependency 传入。在
_addModuleChain
回调中根据不同 dependency 类型,然后执行multiModuleFactory.create
或者normalModuleFacotry.create
。上面的步骤中不停的提到 dependency,在接下来的文章中将会出现各种 dependency。可见,dependency 是 webpack 中一个很关键的东西,在 webpack/lib/dependencies 文件夹下,你会看到各种各样的 dependency。dependency 和 module 的关系结构如下:
webpack 中将入口文件也当成入口的依赖来处理,所以上面 xxEntryPlugin 中生成的是 xxEntryDependency。module 中的 dependency 保存了这个 module 对其他文件的依赖信息、自身 export 出去的内容等。后面的文章中,你会看到在生成 chunk 时会依靠 dependency 来得到依赖关系图,生成最终文件时会依赖 dependency 中方法和保存的信息将源文件中的
import
等语句替换成最终输出的可执行的 js 语句。看完了各个 entryPlugin 的共同点之后,我们纵向深入每个 plugin,对比一下不同之处。
SingleEntryPlugin
SingleEntryPlugin 逻辑很简单:将 SingleEntryDependency 和 normalModuleFactory 关联起来,所以后续的 create 方法会执行
normalModuleFactory.create
方法。MultiEntryPlugin
与上面 SingleEntryPlugin 相比,
3.multiModuleFactory.create
在第二步中,由
MultiEntryPlugin.createDependency
生成的 dep,结构如下:dependencies 是一个数组,包含多个 SingleEntryDependency。这个 dep 会当做参数传给 multiModuleFactory.create 方法,即下面代码中 data.dependencies[0]
create 中生成了 new MultiModule,在 callback 中会执行 MultiModule 中 build 方法,
这个方法中将编译是否完成的变量值设置为 true,然后直接进入的成功的回调。此时,入口已经完成了编译被转化为一个 module, 并且是一个只有 dependencies 的 module。由于在 createDependency 中每一项都作为一个 SingleEntryDependency 处理,所以 dependencies 中每一项都是一个 SingleEntryDependency。随后进入对这个 module 的依赖处理阶段,我们配置在 entry 中的多个文件就被当做依赖加入到编译链中,被作为 SingleEntryDependency 处理。
总的来看,对于多文件的入口,可以简单理解为 webpack 内部先把入口转化为一个下面的形式:
然后对其做处理。
DynamicEntryPlugin
动态的 entry 配置中同时支持同步方式和返回值为 Promise 类型的异步方式,所以在处理 addEntry 的时候首先调用 entry 函数,然后根据返回的结果类型的不同,进入 string/array/object 的逻辑。
所以动态入口与其他的差别仅在于多了一层函数的调用。
入口找到了之后,就是将文件转为 module 了。接下来的一篇文章中,将详细介绍转 module 的过程。
The text was updated successfully, but these errors were encountered: