blog
blog copied to clipboard
浅探webpack的module
本文主要说的是webpack编译中的make阶段,或者说webpack生成modules的整个流程。
webpack的make过程还是比较复杂的,主要流程如下
1.核心
我认为make阶段的核心是依赖,实际上,make阶段的一切模块都由依赖构建出来。
首先是entry。我们在webpack的配置文件中可以指定entry,entry中指定的文件可以是单个、也可以是多个(字符串),举个entry给多个文件的例子(伪代码):
指定配置::
{
entry: {
main: ['./src/a', './src/b'],
}
}
webpack::
const entryMainModule = {
dependencies: {
'./src/a': Dep('./src/a'),
'./src/b':Dep('./src/b')
},
// webpack的source是在make结束之后才被调用的
source() {
return `
__webpack_require__(${this.dependencies['./src/a'].id});
module.exports = __webpack_require__(${this.dependencies['./src/b'].id});
`;
}
}
可以看出,webpack会把entry转换成一个新增的MultipleModule,entry数组里的模块被指定为依赖。当然如果是指定的单个文件,则entry直接转化为 NormalModule。不过还是会有转化为Dependency的过程。总结如下:
entry --> 是否单文件 --是--> entry设定为为SingleModuleDependency --> 生成NormalModule(会执行loader)
|
否--> entry设定为MultipleModuleDependency --> 生成MultipleModule(无loader, parse)
2. 对引用路径的处理
造过构建工具轮子的人都知道,引用路径的处理是一块很麻烦的事情。webpack没有像babel他们,使用node的核心模块module
, path
等来简化一些处理,而是自己造了一个类Resolver
, 这个类是支持插件的。不管是loader还是contextModule还是normalModule都是他处理,最后处理的结果都是把相对路径转化为绝对路径。(比较常规)
由于loader也是Resolver解析的,所以loader的路径也就可以很灵活了,不一定非要放到node_modules里面(好像没人提这点)。
另外,resolve是比较蠢的,比如引用./a
, 这货会同时找文件夹和文件,文件还会带上extension
找。
此外,每个module都会独立地匹配loader,并不存在什么缓存。
3. 对loader的处理
webpack的loader有pitch特性, 所以
['style', 'picth-enabled', 'css']
加载loader的顺序是从左到右的,虽然执行的顺序是相反的。
loader可以指定raw属性,选择传过来的source是否是buffer。
loader具有一些很实用的方法可以调用,尤其是emitFile
。emitFile
的作用是给当前module附加一个assets(正常情况下assets其实是很后面的阶段才生成的),有没有一种钦定的赶脚。在我们常用的file-loader
中就是用了这个特性。
4. loader之后的parse
很少有文章会提到webpack的parse,实际上这个才是真正意义上的“编译”嘛。在loader执行完之后,webpack会使用acorn将模块解析成ast,然后去遍历ast。
Parser模块支持插件,在遍历大多数语句的时候,都存在钩子。实际上给模块添加依赖就是遍历到"call commonjs:require"这种语句的时候,插件完成的,并且也将依赖模块的名字改为动态的了。
webpack的Parser相比babel还是很弱的。模块自身处理完了之后,都会去处理自身的依赖,也就是把依赖转化为模块。
5. module的大致结构
module: {
request: './src/a',
context: 'xxxx',
loaders: [],
id: 0, // entry的id为0
source() {return xxxx},
dependencies: {
'./b': {
request: './src/a',
context: 'xxxx',
module: module,
}
}
}