探索webpack模块及webpack3新特性
本文从简单的例子入手,从打包文件去分析以下三个问题:webpack打包文件是怎样的?如何做到兼容各大模块化方案的?webpack3带来的新特性又是什么?
一个简单的例子
webpack配置
//webpack.config.js
module.exports={
entry:'./src/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'dist')
},
};
简单的js文件
//src/index.js
console.log('helloworld');
webpack打包后的代码
一看你就会想,我就一行代码,你给我打包那么多???(黑人问号)
//dist/bundle.js
/******/(function(modules){//webpackBootstrap
/******///Themodulecache
/******/varinstalledModules={};
/******/
/******///Therequirefunction
/******/function__webpack_require__(moduleId){
/******/
/******///Checkifmoduleisincache
/******/if(installedModules[moduleId]){
/******/returninstalledModules[moduleId].exports;
/******/}
/******///Createanewmodule(andputitintothecache)
/******/varmodule=installedModules[moduleId]={
/******/i:moduleId,
/******/l:false,
/******/exports:{}
/******/};
/******/
/******///Executethemodulefunction
/******/modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);
/******/
/******///Flagthemoduleasloaded
/******/module.l=true;
/******/
/******///Returntheexportsofthemodule
/******/returnmodule.exports;
/******/}
/******/
/******/
/******///exposethemodulesobject(__webpack_modules__)
/******/__webpack_require__.m=modules;
/******/
/******///exposethemodulecache
/******/__webpack_require__.c=installedModules;
/******/
/******///definegetterfunctionforharmonyexports
/******/__webpack_require__.d=function(exports,name,getter){
/******/if(!__webpack_require__.o(exports,name)){
/******/Object.defineProperty(exports,name,{
/******/configurable:false,
/******/enumerable:true,
/******/get:getter
/******/});
/******/}
/******/};
/******/
/******///getDefaultExportfunctionforcompatibilitywithnon-harmonymodules
/******/__webpack_require__.n=function(module){
/******/vargetter=module&&module.__esModule?
/******/functiongetDefault(){returnmodule['default'];}:
/******/functiongetModuleExports(){returnmodule;};
/******/__webpack_require__.d(getter,'a',getter);
/******/returngetter;
/******/};
/******/
/******///Object.prototype.hasOwnProperty.call
/******/__webpack_require__.o=function(object,property){returnObject.prototype.hasOwnProperty.call(object,property);};
/******/
/******///__webpack_public_path__
/******/__webpack_require__.p="";
/******/
/******///Loadentrymoduleandreturnexports
/******/return__webpack_require__(__webpack_require__.s=0);
/******/})
/************************************************************************/
/******/([
/*0*/
/***/(function(module,exports){
console.log('helloworld');
/***/})
/******/]);
我们来分析一下这部分代码,先精简一下,其实整体就是一个自执行函数,然后传入一个模块数组
(function(modules){
//...
})([function(module,exports){
//..
}])
好了,传入模块数组做了什么(其实注释都很明显了,我只是大概翻译一下)
/******/(function(modules){//webpackBootstrap
/******///Themodulecache缓存已经load过的模块
/******/varinstalledModules={};
/******/
/******///Therequirefunction引用的函数
/******/function__webpack_require__(moduleId){
/******/
/******///Checkifmoduleisincache假如在缓存里就直接返回
/******/if(installedModules[moduleId]){
/******/returninstalledModules[moduleId].exports;
/******/}
/******///Createanewmodule(andputitintothecache)构造一个模块并放入缓存
/******/varmodule=installedModules[moduleId]={
/******/i:moduleId,//模块id
/******/l:false,//是否已经加载完毕
/******/exports:{}//对外暴露的内容
/******/};
/******/
/******///Executethemodulefunction传入模块参数,并执行模块
/******/modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);
/******/
/******///Flagthemoduleasloaded标记模块已经加载完毕
/******/module.l=true;
/******/
/******///Returntheexportsofthemodule返回模块暴露的内容
/******/returnmodule.exports;
/******/}
/******/
/******/
/******///exposethemodulesobject(__webpack_modules__)暴露模块数组
/******/__webpack_require__.m=modules;
/******/
/******///exposethemodulecache暴露缓存数组
/******/__webpack_require__.c=installedModules;
/******/
/******///definegetterfunctionforharmonyexports为ES6exports定义getter
/******/__webpack_require__.d=function(exports,name,getter){
/******/if(!__webpack_require__.o(exports,name)){//假如exports本身不含有name这个属性
/******/Object.defineProperty(exports,name,{
/******/configurable:false,
/******/enumerable:true,
/******/get:getter
/******/});
/******/}
/******/};
/******/
/******///getDefaultExportfunctionforcompatibilitywithnon-harmonymodules解决ESmodule和Commonjsmodule的冲突,ES则返回module['default']
/******/__webpack_require__.n=function(module){
/******/vargetter=module&&module.__esModule?
/******/functiongetDefault(){returnmodule['default'];}:
/******/functiongetModuleExports(){returnmodule;};
/******/__webpack_require__.d(getter,'a',getter);
/******/returngetter;
/******/};
/******/
/******///Object.prototype.hasOwnProperty.call
/******/__webpack_require__.o=function(object,property){returnObject.prototype.hasOwnProperty.call(object,property);};
/******/
/******///__webpack_public_path__webpack配置下的公共路径
/******/__webpack_require__.p="";
/******/
/******///Loadentrymoduleandreturnexports最后执行entry模块并且返回它的暴露内容
/******/return__webpack_require__(__webpack_require__.s=0);
/******/})
/************************************************************************/
/******/([
/*0*/
/***/(function(module,exports){
console.log('helloworld');
/***/})
/******/]);
整体流程是怎样的呢
- 传入module数组
 - 调用__webpack_require__(__webpack_require__.s=0)
 
构造module对象,放入缓存
调用module,传入相应参数modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);(这里exports会被函数内部的东西修改)
标记module对象已经加载完毕
返回模块暴露的内容(注意到上面函数传入了module.exports,可以对引用进行修改)
- 模块函数中传入module,module.exports,__webpack_require__
 - 执行过程中通过对上面三者的引用修改,完成变量暴露和引用
 
webpack模块机制是怎样的
我们可以去官网看下webpack模块
doc.webpack-china.org/concepts/mo…
webpack模块能够以各种方式表达它们的依赖关系,几个例子如下:
- ES2015import语句
 - CommonJSrequire()语句
 - AMDdefine和require语句
 - css/sass/less文件中的@import语句。
 - 样式(url(...))或HTML文件()中的图片链接(imageurl)
 
强大的webpack模块可以兼容各种模块化方案,并且无侵入性(non-opinionated)
我们可以再编写例子一探究竟
CommonJS
修改src/index.js
varcj=require('./cj.js');
console.log('helloworld');
cj();
新增src/cj.js,保持前面例子其他不变
//src/cj.js
functiona(){
console.log("CommonJS");
}
module.exports=a;
再次运行webpack
/******/(function(modules){//webpackBootstrap
//...省略代码
/******/})
/************************************************************************/
/******/([
/*0*/
/***/(function(module,exports,__webpack_require__){
letcj=__webpack_require__(1);
console.log('helloworld');
cj();
/***/}),
/*1*/
/***/(function(module,exports){
functiona(){
console.log("CommonJS");
}
module.exports=a;
/***/})
/******/]);
我们可以看到模块数组多了个引入的文件,然后index.js模块函数多了个参数__webpack_require__,去引用文件(__webpack_require__在上一节有介绍),整体上就是依赖的模块修改了module.exports,然后主模块执行依赖模块,获取exports即可
ES2015import
新增src/es.js
//src/es.js
exportdefaultfunctionb(){
console.log('ESModules');
}
修改src/index.js
//src/index.js
importesfrom'./es.js';
console.log('helloworld');
es();
webpack.config.js不变,执行webpack
/******/(function(modules){//webpackBootstrap
//...省略代码
/******/})
/************************************************************************/
/******/([
/*0*/
/***/(function(module,__webpack_exports__,__webpack_require__){
"usestrict";
Object.defineProperty(__webpack_exports__,"__esModule",{value:true});
/*harmonyimport*/var__WEBPACK_IMPORTED_MODULE_0__es_js__=__webpack_require__(1);
console.log('helloworld');
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["a"/*default*/])();
/***/}),
/*1*/
/***/(function(module,__webpack_exports__,__webpack_require__){
"usestrict";
/*harmonyexport(immutable)*/__webpack_exports__["a"]=b;
functionb(){
console.log('ESModules');
}
/***/})
/******/]);
我们可以看到它们都变成了严格模式,webpack自动采用的
表现其实跟CommonJS相似,也是传入export然后修改,在主模块再require进来,
我们可以看到这个
Object.defineProperty(__webpack_exports__,"__esModule",{value:true});
这个干嘛用的?其实就是标记当前的exports是es模块,还记得之前的__webpack_require__.n吗,我们再拿出来看看
/******///getDefaultExportfunctionforcompatibilitywithnon-harmonymodules解决ESmodule和Commonjsmodule的冲突,ES则返回module['default']
/******/__webpack_require__.n=function(module){
/******/vargetter=module&&module.__esModule?
/******/functiongetDefault(){returnmodule['default'];}:
/******/functiongetModuleExports(){returnmodule;};
/******/__webpack_require__.d(getter,'a',getter);
/******/returngetter;
/******/};
为了避免跟非ESModules冲突?冲突在哪里呢?
其实这部分如果你看到babel转换ESModules源码就知道了,为了兼容模块,会把ESModules直接挂在exports.default上,然后加上__esModule属性,引入的时候判断一次是否是转换模块,是则引入module['default'],不是则引入module
我们再多引入几个ESModules看看效果
//src/es.js
exportfunctiones(){
console.log('ESModules');
}
exportfunctionesTwo(){
console.log('ESModulesTwo');
}
exportfunctionesThree(){
console.log('ESModulesThree');
}
exportfunctionesFour(){
console.log('ESModulesFour');
}
我们多引入esTwo和esFour,但是不使用esFour
//src/index.js
import{es,esTwo,esFour}from'./es.js';
console.log('helloworld');
es();
esTwo();
得出
/******/(function(modules){//webpackBootstrap
//...
/******/})
/************************************************************************/
/******/([
/*0*/
/***/(function(module,__webpack_exports__,__webpack_require__){
"usestrict";
Object.defineProperty(__webpack_exports__,"__esModule",{value:true});
/*harmonyimport*/var__WEBPACK_IMPORTED_MODULE_0__es_js__=__webpack_require__(1);
console.log('helloworld');
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["a"/*es*/])();
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["b"/*esTwo*/])();
/***/}),
/*1*/
/***/(function(module,__webpack_exports__,__webpack_require__){
"usestrict";
/*harmonyexport(immutable)*/__webpack_exports__["a"]=es;
/*harmonyexport(immutable)*/__webpack_exports__["b"]=esTwo;
/*unusedharmonyexportesThree*/
/*unusedharmonyexportesFour*/
functiones(){
console.log('ESModules');
}
functionesTwo(){
console.log('ESModulesTwo');
}
functionesThree(){
console.log('ESModulesThree');
}
functionesFour(){
console.log('ESModulesFour');
}
/***/})
/******/]);
嗯嗯其实跟前面是一样的,举出这个例子重点在哪里呢,有没有注意到注释中
/*unusedharmonyexportesThree*/ /*unusedharmonyexportesFour*/
esThree是我们没有引入的模块,esFour是我们引用但是没有使用的模块,webpack均对它们做了unused的标记,其实这个如果你使用了webpack插件uglify,通过标记,就会把esThree和esFour这两个未使用的代码消除(其实它就是tree-shaking)
AMD
我们再来看看webpack怎么支持AMD
新增src/amd.js
//src/amd.js
define([
],function(){
return{
amd:function(){
console.log('AMD');
}
};
});
修改index.js
//src/index.js
define([
'./amd.js'
],function(amdModule){
amdModule.amd();
});
得到
/******/(function(modules){//webpackBootstrap
//...省略代码
/******/})
/************************************************************************/
/******/([
/*0*/
/***/(function(module,exports,__webpack_require__){
var__WEBPACK_AMD_DEFINE_ARRAY__,__WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__=[
__webpack_require__(1)
],__WEBPACK_AMD_DEFINE_RESULT__=function(amdModule){
amdModule.amd();
}.apply(exports,__WEBPACK_AMD_DEFINE_ARRAY__),
__WEBPACK_AMD_DEFINE_RESULT__!==undefined&&(module.exports=__WEBPACK_AMD_DEFINE_RESULT__));
/***/}),
/*1*/
/***/(function(module,exports,__webpack_require__){
var__WEBPACK_AMD_DEFINE_ARRAY__,__WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__=[
],__WEBPACK_AMD_DEFINE_RESULT__=function(){
return{
amd:function(){
console.log('AMD');
}
};
}.apply(exports,__WEBPACK_AMD_DEFINE_ARRAY__),
__WEBPACK_AMD_DEFINE_RESULT__!==undefined&&(module.exports=__WEBPACK_AMD_DEFINE_RESULT__));
/***/})
/******/]);
先看amd.js整理一下代码
function(module,exports,__webpack_require__){
var__WEBPACK_AMD_DEFINE_ARRAY__,
__WEBPACK_AMD_DEFINE_RESULT__;
!(
__WEBPACK_AMD_DEFINE_ARRAY__=[],
__WEBPACK_AMD_DEFINE_RESULT__=function(){
return{
amd:function(){
console.log('AMD');
}
};
}.apply(exports,__WEBPACK_AMD_DEFINE_ARRAY__),
__WEBPACK_AMD_DEFINE_RESULT__!==undefined&&
(module.exports=__WEBPACK_AMD_DEFINE_RESULT__)
);
})
简单来讲收集defineArray然后置入返回函数,根据参数获取依赖
apply对数组拆解成一个一个参数
再看index.js模块部分
function(module,exports,__webpack_require__){
var__WEBPACK_AMD_DEFINE_ARRAY__,
__WEBPACK_AMD_DEFINE_RESULT__;
!(
__WEBPACK_AMD_DEFINE_ARRAY__=[__webpack_require__(1)],
__WEBPACK_AMD_DEFINE_RESULT__=function(amdModule){
amdModule.amd();
}.apply(exports,__WEBPACK_AMD_DEFINE_ARRAY__),
__WEBPACK_AMD_DEFINE_RESULT__!==undefined&&
(module.exports=__WEBPACK_AMD_DEFINE_RESULT__)
);
}
其实就是引入了amd.js暴露的{amd:[Function:amd]}
css/image?
css和image也可以成为webpack的模块,这是令人震惊的,这就不能通过普通的hackcommonjs或者函数调用简单去调用了,这就是anythingtoJS,它就需要借助webpackloader去实现了
像css就是转换成一段js代码,通过处理,调用时就是可以用js将这段css插入到style中,image也类似,这部分就不详细阐述了,有兴趣的读者可以深入去研究
webpack3新特性
我们可以再顺便看下webpack3新特性的表现
具体可以看这里medium.com/webpack/web…
ScopeHoisting
我们可以发现模块数组是一个一个独立的函数然后闭包引用webpack主函数的相应内容,每个模块都是独立的,然后带来的结果是在浏览器中执行速度变慢,然后webpack3学习了ClosureCompiler和RollupJS这两个工具,连接所有闭包到一个闭包里,放入一个函数,让执行速度更快,并且整体代码体积也会有所缩小
我们可以实际看一下效果(要注意的是这个特性只支持ESModules,是不支持CommonJs和AMD的)
使用上面的例子,配置webpack.config.js,增加newwebpack.optimize.ModuleConcatenationPlugin()
constpath=require('path');
constwebpack=require('webpack');
module.exports={
entry:'./src/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'dist')
},
module:{
},
plugins:[
newwebpack.optimize.ModuleConcatenationPlugin(),
]
};
打包
/******/(function(modules){//webpackBootstrap
//...省略代码
/******/})
/************************************************************************/
/******/([
/*0*/
/***/(function(module,__webpack_exports__,__webpack_require__){
"usestrict";
Object.defineProperty(__webpack_exports__,"__esModule",{value:true});
//CONCATENATEDMODULE:./src/es.js
functiones(){
console.log('ESModules');
}
functionesTwo(){
console.log('ESModulesTwo');
}
functionesThree(){
console.log('ESModulesThree');
}
functionesFour(){
console.log('ESModulesFour');
}
//CONCATENATEDMODULE:./src/index.js
//src/index.js
console.log('helloworld');
es();
/***/})
/******/]);
我们可以惊喜的发现没有什么require了,它们拼接成了一个函数,good!