谢谢你们看我扯技术,最近在对webpack2
进行的配置进行梳理和学习,webpack
是在去年使用vue
开始接触的,个人感觉webpack
融入到编程过程中,提供了模块化,将各种类型的文件都看成模块,通过不同的 loader
进行处理和代码组织,是一个比较新颖的编程体验,应该说webpack
的编程适用场景比较广泛,能够比较方便的引入第三方的各种 npm 模块进行使用, 方便快速开发工作。
打算写几篇文章(如果能坚持的话= =)来总结下 webpack
,文章不是教你怎么使用webpack
,而是让你更好的了解你在使用的webpack
是怎么去运行的 ,想来想去,第一篇就先介绍下webpack
生成的文件,是怎么去执行的。
webpack 的生成信息
首先我们要先通过 webpack 去生成文件(好一句废话),文章所有的代码都会在文章最后面给出链接,下面是本文章使用的代码的目录:
我们现在只要关注js
目录,里面有两个入口 app.js
、bar.js
,然后会引用 es5,es6中的各种测试模块,具体大家可以看代码。然后代码一跑!只见命令行蹭蹭蹭跑出来了好多信息,像下面一样:
首先我们来看下生成的信息:
Asset
: 这个一看就明白是生成的文件相对于配置中output.path
的路径,可以看到图中生成的文件都是在output.path
底下的;然后我们仔细看下文件名,比如第一个0.fb6d7f4.js
,是由[name/chunkname].[hash/chunkhash].js
组成的,这个可以在output.filename
中配置,关于hash
和chunkhash
的区别,这个后面会专门通过一篇文章进行简介。Size
: 这个就没啥好说的,就是生成文件的大小Chunks
: 我们会看到有些Chunks
是两个数字,有些是一个,其实还可能出现更多,经过我的一堆实验= =,发现Chunks
中的第一个数字,就是这个文件的ChunkId
,而后面的是当前这个文件依赖的文件的ChunkId
,从图中我们可以看到,第一个文件的ChunkId
是0
,它依赖的是ChunkId
为3
的manifest.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
会对使用的模块进行单独打包,比如文件中的0
、2
这两个 js,如果没有异步模块调用,那么会将所有的代码生成在一个文件中,webpack
为了使得打包的代码进行优化,可以使用CommonsChunkPlugin
插件对代码进行处理,将库文件单独打包,通过规则生成对应的 chunk
文件,其中的manifest
为 默认的 chunk
,其中包含了打包文件的runtime
信息,还有webpackJsonp
模块加载的封装库,所有的生成模块都是采用webpackJsonp
进行封装的。
manifest
从上面的图中可以看到,浏览器按顺序分别加载了 manifest
,index
,0
,2
文件,其中manifest
相当于webpack
的runtime
工具,用于做模块加载,其他文件是逻辑文件; manifest
中封装了webpackJsonpCallback
方法和__webpack_require__
方法,下面我们来进行分析:
-
webpackJsonpCallback(chunkIds, moreModules, executeModule)
:webpackJsonpCallback
是chunk封装的包装方法,webpack
在生成每一个chunk
的时候都是通过这个方法进行包装的,我们在上面看到的chunksId
,会作为第一个参数,被包含进这个chunk
的module
会被以数组的形式传入第二个参数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,则判断是异步加载的模块已经加载成功,将chunk
的resolve
方法传入resolves
数组,然后后面运行,然后将chunk
对应的状态设置为0
。如果判断之后不存在,这认为这是一个同步加载的chunk
,直接设置为0
,表示chunk
已经加载完毕。 -
加载 module
加载
module
的逻辑比较简单,判断纯不存在这个module
之后,将 其写入modules
参数之中 -
运行需要执行的
module
如果
executeModule
存在,则对其中对应moduleId
的模块进行运行
-
-
__webpack_require__
: 这个对象包含了多个方法,主要用于module
和chunk
的加载,处理和运行,下面我们一个一个分析:__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
:这个是和webpack
的output.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
里面提,本次的项目在分支上面开发,代码点击这里
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。