Hello,大家好。
在上一篇 webpack練手項目之easySlide(一):初探webpack 中我們一起為大家介紹了webpack的基本用法,使用webpack對前端代碼進行模塊化打包。
但是乍一看webpack只是將所有資源打包到一個JS文件中而已,並沒有做到真正的按需加載,這當然不是我們所想要的。
不急,今天的這一章我們就來一起繼續探索webpack的另外一個功能:code split.
1.什么是code split
英文不好,暫且將其翻譯為代碼分割。也就是我們根據實際業務需求將代碼進行分割,然后在合適的時候在將其加載進入文檔中。
這里舉一個實際應用場景:上次我們做的圖片輪播,我們為每張圖片都添加一個點擊事件,點擊以后我們彈出一個對話框,里面介紹一些詳細內容,然后可以點擊關閉按鈕進行關閉。
在這個需求中我們發現,對話框這個組件比較特殊,他是在用戶點擊圖片以后才需要加載,如果用戶不點擊,那么他就沒有必要加載出來了。
OK,很好。webpack通過code split方法將頁面必須加載的資源放在bundle.js中,然后對於按需加載的資源通過ajax進行異步加載。
webpack通過 require.ensure 來判斷是否對資源進行按需加載。
下面是官網的簡單用例:
1 require.ensure(["module-a", "module-b"], function(require) { 2 var a = require("module-a"); 3 // ... 4 });
2.Demo與Code
同樣的,我已經將上面所說的對話框按需加載實現,大家感興趣的話可以前往查看:
Demo: http://xiaoyunchen.github.io/easySlide/
Code: https://github.com/xiaoyunchen/easySlide
大家可以打開控制台網絡面板,查看資源的加載情況:
頁面加載的時候只有7個請求,這其中其實並沒有包含我們的對話框組件:

然后大家可以點擊任意一張圖片,這個時候網絡瀏覽器發送了新的網絡請求,然后頁面上也打開了對話框:

這個2.chunk.js也就是webpack為我們打包的對話框組件,包括JS邏輯,HTML模板以及CSS樣式,稍后我們將為大家作詳細介紹。
(demo上有幾個menu的link,大家先不用管,那是我們下一章將要介紹的內容)
3.組件引用與webpack打包
接下來我們來看看代碼上都發生了哪些變動。
首先是index.html與index.css,並沒有任何修改。(index.html中增加了header與footer,增加了多個圖片輪播組件,為下一章做的准備。但是都與對話框組件沒有任何關系)
那來看看index.js的修改,具體的源碼大家可以查看github,這里只對后面新增的代碼進行分析:
1 //添加對話框事件 2 var pageDialog=false; 3 $('.pictureShow a').click(function(){ 4 var _id=$(this).attr('dialog-for'); 5 require.ensure(["../module/dialog.js","../module/dialogConfig.js"], function(require) { 6 var dialogModule=require("../module/dialog.js"); 7 var dialogConfig=require("../module/dialogConfig.js"); 8 if(!pageDialog){ //判斷對話框組件是否存在,避免重復創建 9 pageDialog=new dialogModule(); 10 } 11 pageDialog.openDialogWith(dialogConfig[_id]); 12 }); 13 });
首先我們定義了一個對象,然后為頁面上所有的滑動元素增加了一個單擊事件。
第4行:獲取當前事件元素的dialog-for屬性,這是我們在每個滑動元素上新增的屬性,用於指定其對應的對話框id
第5行:使用了webpack的require.ensure異步加載了兩個組件:dialog與dialogConfig,這兩個組件分別是對話框的具體實現邏輯以及對話框內容配置信息,詳細的代碼我們后面再分析
第6/7行:加載完成后得到兩個組件的引用
第8-10行:判斷pageDialog是否存在,如果不存在我們通過調用dialog組件new一個實例,並將賦值給pageDialog。
這里主要是為了避免多次點擊時頁面重復創建dialog Html元素,降低頁面性能
第11行:使用pageDialog實例,調用openDialogWith方法來打開對話框,同時要從dialogConfig中加載指定的對話框配置內容。
是的,哪怕是在我們沒有查看dialog組件具體源碼的情況下,整個邏輯還是相對比較清晰的。我們不用理會dialog組件調用了什么模板,用了什么css樣式,內部實現了哪些方法。
定義組件的一大目的就是降低代碼之前的耦合性,作為組件,我只管定義如何實現一個組件;作為組件調用者,我只管衣來伸手飯來張口的拿來主義,
不管你內部是如何實現的。
接下來看webpack是如果對上面的代碼進行打包的:
以下是webpack.config.js配置文件部分內容:
1 module.exports = { 2 entry: { 3 index:"./src/js/page/index.js", 4 delegate:"./src/js/page/jsEvent.js" 5 }, 6 output: { 7 path: path.join(__dirname,'dist'), 8 publicPath: "/easySlide/dist/", 9 filename: "[name].js", 10 chunkFilename: "[id].chunk.js" 11 }, 12 module: { 13 loaders: [ //加載器 14 {test: /\.css$/, loader: "style!css" }, 15 {test: /\.html$/, loader: "html" }, 16 {test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'} 17 ] 18 } 19 20 };
改動的地方不多,主要是以下幾點:
第10行:定義了chunk的文件名命名規則,這里除了id以外,還可以使用[hash]
第8行:publicPath 這個配置很容易被疏忽。我們的chunk文件默認是跟bundle放在一塊的,都是在dist目錄下,如果不配置正確的publicPath的話,webpack請求chunk文件時將會默認請求根目錄
第15行:新增了HTML加載器,主要是給dialog組件加載模板使用的
codeSplit作為webpack一個比較核心的功能,所以也需要額外的plugins插件配置就能支持。
在項目根目錄下運行 webpack 打包命令以后可以看到dist目錄下就會新增一個 2.chunk.js文件 (2是chunk的ID值)
值得一提的是,chunk文件完全由webpack進行管理和使用,我們無需額外的干預
4.dialog組件的實現
dialog的實現原理比較簡單,我們定義了一個dialog容器,通過css控制其固定在整個頁面之上,然后在生成一個mask陰影層。
具體的實現css樣式大家也可以前往github查看源碼。
dialog組件的實現方法也是與我們之前做的slideModule比較類似:

第10-11行:加載了dialog組件的HTML模板,並將其放置在body中。嗯,之所有是要使用html模板也是為了解耦合,方便對模板和JS進行單獨維護。這里webpack在打包的時候
會自動將html模板壓縮成字符串保存在chunk文件中。
第16-44行:定義了dialog組件的幾個接口方法,其中openDialogWith 就是我們之前調用的根據配置內容更新dialog DOM信息,然后將dialog展示出來。
對於dialogConfig我們也簡單的來看下:

這里全部都是對話框的數據配置項,單獨維護的好處也是解耦合,以外咱們可以將這些數據配置在數據庫中,通過接同樣的接口格式返回來就行了。
其實我這里的組件定義還有一個問題:
我們是將自定義的對象直接作為組件導出,開放給調用者使用。這在操作上其實存在一定的風險性,因為所有接口方法和屬性都是開放的,那就有可能被使用者
給篡改,影響到組件的正常使用。正確的做法是:
在組件內部定義一個私有變量,用戶存放組件所有屬性與方法,然后再將接口方法(一般不建議開放屬性給調用者直接修改)導出給調用者使用。
小結:
通過webpack的codeSplit方法我們可以對代碼進行按需分割以及加載,從而到達頁面性能的最優。
