4

谢谢你们看我扯技术,最近在对webpack2进行的配置进行梳理和学习,webpack是在去年使用vue开始接触的,个人感觉webpack 融入到编程过程中,提供了模块化,将各种类型的文件都看成模块,通过不同的 loader 进行处理和代码组织,是一个比较新颖的编程体验,应该说webpack的编程适用场景比较广泛,能够比较方便的引入第三方的各种 npm 模块进行使用, 方便快速开发工作。
打算写几篇文章(如果能坚持的话= =)来总结下 webpack,文章不是教你怎么使用webpack,而是让你更好的了解你在使用的webpack是怎么去运行的 ,想来想去,第一篇就先介绍下webpack生成的文件,是怎么去执行的。

webpack 的生成信息

首先我们要先通过 webpack 去生成文件(好一句废话),文章所有的代码都会在文章最后面给出链接,下面是本文章使用的代码的目录:
图片描述

我们现在只要关注js目录,里面有两个入口 app.jsbar.js,然后会引用 es5,es6中的各种测试模块,具体大家可以看代码。然后代码一跑!只见命令行蹭蹭蹭跑出来了好多信息,像下面一样:
图片描述

首先我们来看下生成的信息:

  • Asset : 这个一看就明白是生成的文件相对于配置中output.path的路径,可以看到图中生成的文件都是在 output.path底下的;然后我们仔细看下文件名,比如第一个0.fb6d7f4.js,是由[name/chunkname].[hash/chunkhash].js组成的,这个可以在output.filename 中配置,关于hashchunkhash的区别,这个后面会专门通过一篇文章进行简介。

  • Size : 这个就没啥好说的,就是生成文件的大小

  • Chunks : 我们会看到有些 Chunks是两个数字,有些是一个,其实还可能出现更多,经过我的一堆实验= =,发现Chunks中的第一个数字,就是这个文件的 ChunkId,而后面的是当前这个文件依赖的文件的ChunkId,从图中我们可以看到,第一个文件的ChunkId0,它依赖的是ChunkId3manifest.a890c12.js

  • Chunk Names : 这个就是这个生成文件的chunkName,可以用于文件命名,可以看到如果没有在entry中指定,那么chunkName会等于chunkId

程序加载流程

了解了生成的信息,接下来我们把项目跑起来(可以用 anywhere 跑项目),通过chrome developer tool可以看到请求情况图片描述

可以看到请求了页面html之后,按顺序分别加载了 manifest,index,0,2文件,这里我们先来分析下文件的分割和加载流程。

分割

可以看到页面的 js 被分割成为了4个文件,通常来说,一个项目定义了一个 entry point,
webpack会以这个entry point作为入口,进行代码回溯,如果存在System.import或者是require.ensure的异步模块调用,webpack会对使用的模块进行单独打包,比如文件中的02这两个 js,如果没有异步模块调用,那么会将所有的代码生成在一个文件中,webpack 为了使得打包的代码进行优化,可以使用CommonsChunkPlugin插件对代码进行处理,将库文件单独打包,通过规则生成对应的 chunk 文件,其中的manifest为 默认的 chunk,其中包含了打包文件的runtime信息,还有webpackJsonp模块加载的封装库,所有的生成模块都是采用webpackJsonp进行封装的。

manifest

从上面的图中可以看到,浏览器按顺序分别加载了 manifest,index,0,2文件,其中manifest相当于webpackruntime工具,用于做模块加载,其他文件是逻辑文件; manifest中封装了webpackJsonpCallback方法和__webpack_require__方法,下面我们来进行分析:

  • webpackJsonpCallback(chunkIds, moreModules, executeModule):webpackJsonpCallback是chunk封装的包装方法,webpack在生成每一个chunk的时候都是通过这个方法进行包装的,我们在上面看到的 chunksId,会作为第一个参数,被包含进这个chunkmodule会被以数组的形式传入第二个参数moreModules中,如果这个chunk中包含可以执行的modules,需要将 moduleId传入第三个参数 executeModule中,下面是 这个方法的代码片段:
    图片描述这个方法主要做了下面几件事:

    • 加载chunk

      我们可以看到这个方法用第一个循环分别将chunkIds处理进入installedChunks对象中,installedChunks对象用于记录chunk的加载情况,分别用0表示当前的chunkId已经加载完成,用一个长度为3的数组表示当前的chunk正在加载中,数据中其实存储着加载过程中的resolve方法、reject方法和pormise对象,这种只在通过require.ensure或者是System.import才会出现。因此我们可以看到,第一个for循环中判断如果chunkId在 installedChunks 中存在且不为0,则判断是异步加载的模块已经加载成功,将chunkresolve方法传入resolves数组,然后后面运行,然后将chunk对应的状态设置为0。如果判断之后不存在,这认为这是一个同步加载的chunk,直接设置为0,表示chunk已经加载完毕。

    • 加载 module

      加载 module 的逻辑比较简单,判断纯不存在这个module之后,将 其写入modules参数之中

    • 运行需要执行的module

      如果executeModule存在,则对其中对应moduleId的模块进行运行

  • __webpack_require__: 这个对象包含了多个方法,主要用于modulechunk的加载,处理和运行,下面我们一个一个分析:

    • __webpack_require__(moduleId) :代码如下
      图片描述 这个方法接收一个moduleId,构建一个 module 对象存入installedModules中,并且初始化这个 module, 最后返回module.export

    • __webpack_require__.e(chunkId) : 这个方法用于通过异步的方式加载 chunk 文件,代码如下:
      图片描述这个方法总体来说就是加载一个 script 文件,生成一个 promise对象,当 script 加载完成后运行,又会执行前面的webpackJsonpCallback注册chunk,然后promise.resolve。这里面需要注意的是红框里面的东西,这个涉及到一个优化点,如果没有在使用CommonsChunkPlugin单独打包manifest,那么一般来说他会和你指定的其他库通过CommonsChunkPlugin打包在一起,那么你会发现即使你只是修改了库之外的逻辑,库文件生成的文件的hash或者是chunkhash也是会变的,原因就在于manifest中红框部分是动态生成的,导致文件的 hash 产生变化,不利于缓存,因此建议单独打包manifest

    • __webpack_require__.oe:定义一个统一的错误处理函数

    • __webpack_require__.p:这个是和webpackoutput.publicPath对应的值

    • __webpack_require__.o: Object.prototype.hasOwnProperty的封装
      前面几个方法在 ES5的情景下面已经足够运行这个模块系统,我们都知道webpack2加入了对ES6 MODULE的支持,下面几个__webpack_require__是为ES6使用的:

    • __webpack_require__.d:代码如下:
      图片描述这个是用于ES6中命名的export比如 图片描述 webpack 遇到这种export,会对其用__webpack_require__.d进行包装,变成: 图片描述

    • __webpack_require__.i:用于返回一个正确的上下文的函数回去,针对的是export直接为一个可运行方法的时候

以上就是webpack manifest中的大部分重要的函数,其实主要就是通过webpackJsonpCallback来注册载入对应的chunk文件,通过__webpack_require__来处理模块的关系。

总结

整个webpack 的在运行时都是通过 manifest去做控制处理的, webpackJsonpCallback对应的是对加载的chunk文件的处理,__webpack_require__是对加载模块的处理,了解这些可以使我们更好的去优化我们的代码,帮助我们去调试代码,帮助我们在复杂情况下去解决问题提供一些其他的思路。

最后附上代码:先介绍下,webpack-base是我在使用webpack的过程中自己总结的一套脚手架,文档还没有完善,如果需要文档可以在issue里面提,本次的项目在分支上面开发,代码点击这里


斑驳光影
2.2k 声望336 粉丝