详解webpack分包及异步加载套路
最近一个小项目是用webpack来进行构建的。其中用到了webpack分包异步加载的功能。今天抽时间看了下webpack打包后的文件,大致弄明白了webpack分包及异步加载的套路。
由于这个小项目是用自己写的一个路由,路由定义好了不同路径对应下的模板及逻辑代码:
webpack配置文件:
varpath=require('path'), DashboardPlugin=require('webpack-dashboard/plugin'), HtmlWebpackPlugin=require('html-webpack-plugin'), webpack=require('webpack'), ExtractTextPlugin=require('extract-text-webpack-plugin'); varPATHS={ app:path.join(__dirname,'src'), dist:path.join(__dirname,'dist') } varPKG=require('./package.json'); varTARGET=process.env.npm_lifecycle_event;//获取当前正在运行的脚本名称 varisProduction=function(){ returnprocess.env.NODE_ENV==='production'; } module.exports={ entry:{ 'index':path.join(__dirname,'src/index.js'), 'lib':['./src/lib/js/index.js'], }, //filename是主入口文件的名称,即对应的entry //chunkFilename对应的是非主入口文件的名称,chunk output:{ path:PATHS.dist, publicPath:'/static/taxi-driver/',//publicPath的话是打包的时候生成的文件链接,如果是在生产环境当然是用服务器地址,如果是开发环境就是用本地静态服务器的地址 filename:'js/register/[name].js', chunkFilename:'js/register/[name].js', //TODO:build文件中加入hash值 }, //生成source-map文件 devtool:isProduction?null:'source-map', devServer:{ proxy:{ '/api/*':{ target:'http://localhost:3000', secure:false } } }, module:{ loaders:[ { test:/\.js$/, exclude:/node_modules|picker.min.js/, loader:'babel' }, { test:/\.less$/, loader:ExtractTextPlugin.extract('style','css!less') }, { test:/\.html$/, loader:'raw' }, { test:/\.css$/, loader:ExtractTextPlugin.extract('style','css') }, { test:/\.json$/, loader:'json' } ] }, resolve:{ alias:{ src:path.join(__dirname,'src'), modules:path.join(__dirname,'src/modules'), lessLib:path.join(__dirname,'src/lib/less'), jsLib:path.join(__dirname,'src/lib/js'), components:path.join(__dirname,'src/components') }, extensions:['','.js','.less','.html','.json'], }, plugins:[ newHtmlWebpackPlugin({ title:'认证资料', template:'./dist/assets/info.html', inject:'body', filename:'pages/register/index.html'//输出html文件的位置 }), newDashboardPlugin(), newExtractTextPlugin('css/register/style.css'),//将引入的样式文件单独抽成style.css文件并插入到head标签当中,带有路径时,最后打包 newwebpack.optimize.CommonsChunkPlugin({ name:'common', filename:'js/register/common.js', minChunks:3 }) ] }
接下来是定义好的路由文件:
constRouter=newRoute(); Route .addRoute({ path:'path1', viewBox:'.public-container', template:require('modules/path1/index.html'), pageInit(){ //webpack提供的分包的API.require.ensure require.ensure([],()=>{ letcontroller=require('modules/path1/controller'); Router.registerCtrl('path1',newcontroller('.public-container')); },'path1'); } }) .addRoute({ path:'path2', viewBox:'.public-container', template:require('modules/path2/index.html'), pageInit(){ require.ensure([],()=>{ letcontroller=require('modules/path2/controller'); Router.registerCtrl('path2',newcontroller('.public-container')); },'path2'); } });
最后webpack会将这2个需要异步加载的模块,分别打包成path1.js和path2.js.
当页面的路径为:
http://localhost:8080/pages/register/#/path1时,会加载path1.js文件
http://localhost:8080/pages/register/#/path2时,会加载path2.js文件.
再来看看webpack打包后的文件:
其中在common.js中,webpack定义了一个全局函数webpackJsonp.这个全局函数在项目一启动后就定义好。
局部函数__webpack_require__用以在某一个模块中初始化或者调用其他的模块方法。同时这个函数还有一个静态方法__webpack_require__.e这个方法就是用来异步加载js文件的。
接下来一步一步的看:
//common.js (function(modules){ //modules用来保存所有的分包,它是一个数组,数组每个元素对应的都是callback,每个分包都是通过数字来进行标识的 //定义好的全局函数webpackJsonp //大家可以看看其他打包好的文件,例如index.js,path1.js和path2.js文件.都是webpackJsonp()这种的形式,大家用过JSONP应该会很好理解。首先在前端定义好函数,然后后端下发组装好的函数js文件,前端获取到这个文件后就可以立即进行执行了 varparentJsonpFunction=window["webpackJsonp"]; window["webpackJsonp"]=functionwebpackJsonpCallback(chunkIds,moreModules){ varmoduleId,chunkId,i=0,callbacks=[]; for(;i //Therequirefunction //通过数字标识的moduleId function__webpack_require__(moduleId){ //Checkifmoduleisincache if(installedModules[moduleId]) returninstalledModules[moduleId].exports; //Createanewmodule(andputitintothecache) varmodule=installedModules[moduleId]={ exports:{}, id:moduleId, loaded:false }; //Executethemodulefunction modules[moduleId].call(module.exports,module,module.exports,__webpack_require__); //Flagthemoduleasloaded module.loaded=true; //Returntheexportsofthemodule returnmodule.exports; } //Thisfilecontainsonlytheentrychunk. //Thechunkloadingfunctionforadditionalchunks //异步加载函数 __webpack_require__.e=functionrequireEnsure(chunkId,callback){ //"0"isthesignalfor"alreadyloaded" if(installedChunks[chunkId]===0) returncallback.call(null,__webpack_require__); //anarraymeans"currentlyloading". if(installedChunks[chunkId]!==undefined){ installedChunks[chunkId].push(callback); }else{ //创建script表情,请求js文件 //startchunkloading installedChunks[chunkId]=[callback]; varhead=document.getElementsByTagName('head')[0]; varscript=document.createElement('script'); script.type='text/javascript'; script.charset='utf-8'; script.async=true; script.src=__webpack_require__.p+"js/register/"+({"0":"index","1":"path1","2":"path2"}[chunkId]||chunkId)+".js"; head.appendChild(script); } }; //exposethemodulesobject(__webpack_modules__) __webpack_require__.m=modules; //exposethemodulecache __webpack_require__.c=installedModules; //__webpack_public_path__ //配置文件中定义的publicPath,build完后加载文件的路径 __webpack_require__.p="/static/taxi-driver/"; })在最后输出的index.html文件中首先加载的是这个common.js文件,然后是入口文件index.js。因为这个实例代码里面没有很多共用文件,因此webpack自己提供的commonChunkPlugin这个插件并没有起到作用,本来作为共用文件的xRoute.js因此也被打包进入了index.js.
webpackJsonp([0,3],[ /*0*/ /***/function(module,exports,__webpack_require__){ 'usestrict'; __webpack_require__(1); __webpack_require__(8); /***/}, /*1*/ /*2*/ /*3*/ //.... /*8*/ ])index.js文件在common.js后加载,加载完后即开始执行.大家还记得webpackJsonp这个全局函数里面的倒数3行代码吧。就是用以调用这里:
/*0*/ function(module,exports,__webpack_require__){ 'usestrict'; __webpack_require__(1); __webpack_require__(8); }其中模块Id为1和8的内容请查看相应文件,其中模块1为我定义的路由文件,在执行模块1的代码前,会加载模块2的内容,模块2的内容为我定义的路由库。
接下来就看下模块1中路由定义的具体内容:
/*1*/ /***/function(module,exports,__webpack_require__){ 'usestrict'; Object.defineProperty(exports,"__esModule",{ value:true }); //加载路由库 var_index=__webpack_require__(2); //实例化一个路由 varRouter=new_index.Route(); //定义好的路由规则 Router.home('path1').addRoute({ path:'path1', viewBox:'.public-container', //模板文件,为模块4 template:__webpack_require__(4), pageInit:functionpageInit(){ //这个方法是在common.js中__webpack_require__的静态方法,用来异步加载js。 //异步加载js的文件(即chunk)用来数字来标识,chunk的顺序从0开始. //这里path1.js的chunknum为1,大家可以回过头到common.js的__webpack_require__.e方法里面看看,里面已经做好了chunknum和模块文件的映射,chunk1对应的模块文件为path1.js,chunk2对用的模块文件为path2.js //__webpack_require__.e()接收的第二个参数为异步加载模块后的回调.当path1.js被加载完后,在modules里面进行了缓存.这时就可以通过模块id去获取这个模块。然后进行初始化等后续的操作 __webpack_require__.e/*nsure*/(1,function(){ varcontroller=__webpack_require__(6); Router.registerCtrl('path1',newcontroller('.public-container')); }); } }).addRoute({ path:'path2', viewBox:'.public-container', //模板文件,为模块5 template:__webpack_require__(5), pageInit:functionpageInit(){ __webpack_require__.e/*nsure*/(2,function(){ varcontroller=__webpack_require__(7); Router.registerCtrl('path2',newcontroller('.public-container')); }); } }); Router.bootstrap(); exports.default=Router; /***/},以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。