webpack本质是一个工具,这个工具的内部体系其实很是庞大复杂,它在支持高度可配置的同时(可以使用自定义的打包方式及loader和plugin),又作到了开箱即用(咱们在使用webpack的时候几乎能够什么都不配置)。node
若是咱们本身写一个工具,咱们会怎么开启用户的使用之旅,怎么引导用户去进行正确的配置和使用,怎么收集用户的配置,怎么判断用户输入的配置符合规范,怎么去友好提示用户进行正确配置,怎么设置默认配置。。。 这一章,咱们来看看webpack是怎么作的。webpack
一切开始于webpack的安装,执行'npm install webpack'以后,找到node_modules下的webpack,在package.json里边,注意两个属性:"main"和"bin",前者是模块的主入口文件,咱们在项目里边reqire('webpack')时,引入的其实就是main属性所指向的文件,后者是可执行的node脚本,咱们在控制台执行'npm run webpack'的时候,执行的就是这个文件。web
在这个可执行文件里边,主要作了一件事,获取webpack的CLI工具包并执行,有两个选择供咱们安装,官方更推荐webpack-cli,webpack-command已经再也不维护(webpack4版本之前,webpack和webpack-cli是放在一块儿的)。npm
这个脚本里边,做者使用了node提供的require模块,经过在一个try...catch语句里执行require.resolve('模块名'),来判断是否安装了CLI包;经过判断本地是否存在yarn.lock文件来判断是否使用的yarn;使用child_process模块执行命令;使用readline模块获取用户输入的命令。json
不过这个地方有一个可优化的小点,就是在提示用户未安装CLI工具时,是经过遍历数组CLIs,判断数组对象的recommended字段,然而在真正安装包的时候,用的是经过写死的包名webpack-cli。这可能会致使之后不管如何修改对象的recommended属性,安装的都是webpack-cli。数组
咱们在摘要里边最后提的几个问题,答案都在webpack-cli里边。缓存
webpack-cli自己提供了几个命令任务:init、info、server、migrate等,输入不一样的命令(eg. webpack-cli init),都会触发判断、安装、执行相应模块的操做,具体步骤跟webpack的命令行脚本差很少。咱们看下包里的文件:markdown
配置参数的获取,主要依赖yargs模块,它主要用来管理命令行参数。函数
第一步,定义一个options数组,用来存放收集到的配置数据,这些数据包括webpack运行的全部配置信息,至关因而启动webpack的一把钥匙;工具
第二步,肯定配置渠道,webpack-cli支持从命令行输入文件地址或者经过本地文件进行配置,前者直接拿到文件的路径;后者经过正则匹配,使用find-sync模块同步地查找本地配置文件,来配置文件路径。这里有一个技巧,因为webpack支持不一样语言的配置,所以就须要处理获取文件的格式并引入相应的loader(此处使用了interpret模块),文件格式有几十种,代码先将文件格式按长度由短到长进行排序,将'.js'排在第一位,由于大部分仍是js文件,这样在遍历查找的时候每每第一步就能够查到返回,大大提高了效率,且indexOf第二个参数传的很巧妙,如图:
第三步,require配置文件并收集(而不是解析)配置数据,由于webpack支持多种配置类型,好比导出一个函数、一个Promise、一个或多个多个配置对象等,此时,全部的配置数据收集完毕;
第四步,获取最终的标准配置对象,拿到配置数据后,先进行校验,配置字段不符合规范的话,会提示报错信息并终止程序,校验函数是webpack提供的(依赖Ajv模块);其次,解析配置数据,经过一系列的函数处理,将webpack须要的配置字段依次地写入options,同时作一些默认处理,好比设置默认的main入口;一些特定模式的配置会对应一些插件,它会默认去添加这些插件,好比配置热更新模式的话会会默认在plugins里边添加HotModuleReplacementPlugin插件;最后,根据命令行输入的其余参数,继续丰富options对象,好比咱们经常使用的监听模式,就会将options.watch设置为true。
通过第一阶段,启动webpack的这把"钥匙"就打造好了。
第一步,肯定输出信息。输入信息有了,那么输出的bundle信息呢,咱们能够给stats一个字符串,去指定任意一种模式(webpack提供了6种输出模式),或给stats一个对象,去更细粒度地控制是否要展现某一个特定的字段;
第二步,实例化webpack并传入options对象,赋给compiler,这个compiler就是webpack的化身了,掌控者webpack执行整个的生命周期。根据用户自定义的配置,决定如何监控编译进度,从而在compiler对象提供的钩子上订阅一些事件。同时定义了一个lastHash变量,它存储的是每一轮webpack编译对应的hash值,只有当这个值变化的时候才会执行一些操做,这样就避免了浪费,同时,若是是非监听模式,在compiler.close()的回调里边会清除缓存。最后,根据配置状况,去执行compiler.watch() 或者 compiler.run()。至此,初始化工做就差很少完成了,剩下的交给了webpack模块。这个第二步,都汇聚在这张图: