開發中,模塊化能夠防止變量和方法被污染,只須要關注一部分的邏輯實現,有效地減小了與全局的耦合,也便於後期的維護和拓展javascript
固然,相信瞭解過前端模塊化發展歷史的童鞋,都應該聽過IIFE
、AMD
、CommonJS
等等,它們都是可以實現模塊化的規範,直到ES2015
出來後,才正式把模塊化歸入其標準中。在談到今天主題前,咱們先簡單講解一下上面幾種模塊化方式的實現以及區別,對於後面將要講到的webpack模塊化優化
有必定幫助。html
在各類模塊化規範出來以前,ES5
是不支持模塊化開發,但當時也有一些大牛們爲了更好地避免函數的反作用
和封裝,就開始巧妙地想到了使用IIFE
來實現模塊化(注:JS自己是不存在塊級做用域):前端
能夠看到,在原來的ES5
基礎上能夠封裝部分邏輯模塊,也就是一個簡單的閉包行爲
,避免內部變量收到外部環境的影響。vue
可是,缺陷也是很明顯滴暴露出來,它沒法實現模塊間的依賴,同時代碼是分配到主流程中,對於後期的維護和修改帶來了困難。java
CommonJS規範
實現的是同步加載方式,經常使用於服務端,其終極目標是提供一個相似Python,Ruby和Java模塊化標準庫。node
爲此,NodeJS
的出現,也就正式標誌着Javascript模塊化編程
誕生。在服務端,模塊須要與操做系統或者應用程序進行互動,而NodeJS的模塊系統
就是參照CommonJS規範
進行編寫的。webpack
該規範指出,需經過exports
或者module.exports
(注:這兩個導出方法的使用區別,有興趣的童鞋能夠看看阮一峯對於該API的講解)來導出對外的變量或接口,經過require方法
導入其餘模塊的輸出到當前模塊做用域中。直接上栗子:git
這時候,也許有童鞋會提出疑問,那麼CommonJS
能用於客戶端嗎?es6
答案是確定的。因爲客戶端因爲缺乏四個Node.js環境的變量:module、exports、require、global
,致使客戶端沒法使用CommonJS規範
。爲此,需第三方工具或庫(例如require1k、tiny-browser-require等等)才能讓客戶端實現CommonJS規範
。github
鑑於ES5
內部經過IIFE
實現的模塊化沒法真正意思上(相似Java、Python等)的模塊化,所以有些大牛們就提出了適用於客戶端的AMD規範
,它能夠異步引入模塊,同時模塊能夠很好地將某些邏輯功能封裝在一個文件中以便主流程須要時引入。其中RequireJS
庫很好滴實現了AMD規範
,直接上例子:
RequireJS API暴露了require
和define
兩個全局方法,對於主流程或者模塊中須要依賴其餘模塊時,均可以傳進require
和define
兩個全局方法第一個參數中。同時,咱們能夠看到,AMD規範
實現的是異步加載模塊方式(多個模塊引入時會並行加載,有效滴加快執行效率而且無阻塞頁面的加載)。對於每一個模塊只須要作該模塊該作的事情便可,模塊與主流程之間有效地減低了耦合度。
CMD規範
也叫通用模塊定義(Common Module Definition),實現的也是異步加載模塊方式,SeaJS庫
就很好滴實現該規範,它主要具備如下特色:
AMD規範
相似,都是使用require
和define
兩個全局方法,但使用require
方法時倒是同步執行模塊代碼的,這和CommonJS規範
很相似;CMD規範
核心是提早加載,延遲執行,而AMD規範
核心是提早加載,提早執行,固然CommonJS規範
核心則是延遲加載,延遲執行;須要注意的是,CMD規範
使用require
方法的緣由是由於在提早加載模塊過程當中,會把加載下來的模塊保存在內存中,以致於客戶端執行主流程按需引入模塊時是同步執行內存中保存的模塊。(有興趣的童鞋,能夠看看這邊文章——SeaJS是如何工做的)
在我看來,CMD規範
的懶執行機制可有效地提升頁面交互性能,由於在頁面交互過程完成前不須要執行其餘暫時沒用到的模塊(固然這只是我我的觀點,若是有童鞋有不一樣的見解,能夠在評論區上寫上來一塊兒探討學習一下,對於性能對比上,童鞋們也能夠看看SeaJS的github上關於AMD和CMD對比,挺有趣的)。而這種機制,也對於下面我要提到webpack模塊化優化
密切相關。
如今就直接上一個SeaJS
栗子領略一下CMD規範
(有興趣的童鞋,也能夠看下SeaJS的API):
UMD規範
能夠當作是一種方案,用於解決先後端跨平臺模塊加載,支持AMD規範
和CommonJS規範
。說白了,就是致力於用一種實現方式可以把模塊加載兼容先後端。
話很少說,有興趣的童鞋能夠看看UMD的官方介紹和栗子。下面也用一個簡單的栗子來領略UMD規範
的寫法:
能夠看到,UMD規範
實現方式就是先判斷是否支持AMD規範
,而後再判斷是否支持CommonJS規範
,當二者均不支持時則直接把模塊定義在全局對象上
因爲ES5
缺乏模塊化加載理念,所以在ES6
中正式把模塊化加載歸入其標準
ES6
中模塊是在編譯時輸出接口,而上面提到的各類模塊加載規範都是在運行時輸出接口,能夠看到,ES6 Module
在必定程度上對性能進行了優化。有興趣的童鞋能夠看看阮一峯的對ES6 Module的介紹。下面就舉個栗子:
目前,客戶端基本都是使用ES6 Module
模塊加載方式來對模塊進行加載,而對於Node
服務端尚在逐漸向該模塊加載方式靠攏,可是大部分狀況下依然仍是使用CommonJS規範
。
相信你們看完上面模塊加載的各類實現方式,都應該對模塊化有必定的瞭解。好啦,下面就進入今天的主題,在Webpack
中能夠怎樣去對Application
中模塊化進行優化呢?
在平常模塊化開發中,一個頁面會有不少個組件所構成,而這些組件是須要咱們按需引入的,毫無疑問,如今咱們以Vue
做爲栗子,項目結構(下面只展現主要的目錄和文件,至於其餘目錄和文件就不展現了)以下:
|- src
|--- components
|------ message.vue // 詳細信息框組件
|------ main.vue // 首頁須要的組件
|------ goods.vue // 商品頁面組件
|--- router
|------ index.js // 路由文件
|--- App.vue // 入口vue文件
複製代碼
正常狀況下,咱們是這樣編寫頁面的:
App.vue
index.js 路由文件
main.vue 首頁組件
goods.vue 商品頁面組件
message.vue 商品信息組件
上面的栗子,運行時在網址上輸入localhost:8080/#/
會直接使用Main.vue
首頁組件,當輸入localhost:8080/#/goods
時會展現goods.vue
商品組件,點擊按鈕會直接展現message.vue
詳細信息組件。
到這裏,我會想問,上面的簡單SPA
栗子是否還有更加優化的方案?假若我想加快首頁加載的速度以使用戶有個更好的體驗,該如何處理?
對於上面的問題,咱們會常常遇到,各位童鞋也能夠各抒己見在評論區說說本身的見解。在這裏,就不賣關子,換做是我,首先想到要下手的就是Webpack
,而這也是今天所要說起的核心內容:用Webpack如何更好地優化複雜程序的模塊化
。
目前嘗處於stage 3
階段的ECMAScript提案的import()語法,相信不少童鞋都有了解過或聽過,那它到底是幹嗎的?按照官方的說法就是:
ECMAScript modules are completely static, import() enables dynamic loading of ECMAScript modules.
簡單滴說,就是目前模塊加載都是靜態加載的,而import()可以讓咱們按需加載對應模塊。那麼問題來了,上面栗子也是按需加載,首頁只須要Main
首頁組件,而商品頁面也只加載了goods
和message
組件。那麼再細心看看,上面的栗子是否是真的作到了按需加載
?
答案是否認的,正如我上面說起的ES6 Module
是在編譯階段就輸出接口,所以當咱們使用Webpack
打包後,全部須要的模塊都會打進一個js文件
中。所以,首頁在加載過程當中,就須要加載完整個js文件
才能讓用戶進行體驗效果,固然咱們都知道,這個js文件
也有一些模塊邏輯是咱們暫時並不須要用到,而這也剛好是咱們接下來要處理的問題。
import()
語法的出現,再結合webpack 4
(也能夠選擇webpack 3
),就能夠很優雅地處理上述問題。固然也少不了babel
的轉化。下面就是處理實現:
webpack.config.js 部分配置
再將路由文件進行修改後以下:
能夠看到,當咱們再次訪問localhost:8080/#/
時,會發現加載js文件
比以前小了,並且最重要的該文件裏是沒有Goods
商品組件代碼的。當再訪問localhost:8080/#/goods
時,會異步從服務端加載0.js
文件,而改文件是包含Goods
商品組件代碼的。因而可知,不只減小了主流程js
文件的大小,加快頁面的加載體驗,並且還能夠按需異步下載必要的模塊文件。
好了,到了這裏,若是我想再優化一下有Goods
商品組件中Message
詳細信息組件,由於它是隻有在點擊按鈕纔去加載的,那咱們是否是也可讓其進行懶加載,讓localhost:8080/#/goods
下的頁面加載更加快?
答案是確定的,可是須要用到Vue
中component
語法,事不宜遲,咱們就來動手改改看:
goods.vue 商品頁面組件
當咱們再去訪問localhost:8080/#/goods
時,會發現js文件
也比以前少了Message
詳細信息組件代碼,加快加載體驗,同時點擊按鈕後,會動態引入1.js
文件,這也是從服務端異步引入加載Message
詳細信息組件。
這時候,也許會有童鞋提測疑問,當關閉Message
詳細信息組件彈窗時,再從新點擊按鈕,就須要從新render
,那這樣豈不是須要消耗必定的性能?答案是確定會有所損耗,那麼若是想優化它,能夠怎麼作?很簡單,只須要使用Vue API
暴露的<keep-alive>組件
來包裹咱們的<component>
便可,這樣就會有效滴把組件保存在內存中,下次訪問時可直接從內存中讀取。
至此,也許有童鞋又會提出疑問,上面0.js
文件和1.js
文件究竟是什麼鬼?再耐心點往下看,說不定有你想要的驚喜哈哈🙊
上面之因此會出現0
和1
,緣由是由於對於動態引入import()
不指定模塊名稱chunkName
時,Webpack
會默認從0到n來按序命名接下來動態引入的文件。
可是,對於這些數字0或者n,咱們是沒法知道該動態引入的js文件
是屬於哪個模塊,爲此Webpack
也爲咱們提供了Magic Comments
,以此能夠自定義模塊名稱。
下面就拿上面路由文件動態引入做爲栗子,使用上很簡單:
這時候,從localhost:8080/#/
中訪問localhost:8080/#/goods
時,會發現加載不是0.js
,而是goods.js
,這樣就會很容易知道哪些模塊是動態引入的。固然有興趣的童鞋,也能夠想一想指定Message詳細信息組件
動態引入時的chunkName
。
至此,上面講到的方案,能夠有效滴優化咱們現有階段的模塊化開發,固然童鞋們也能夠嘗試一下。
其實,在我看來,還能夠更深刻點優化一下。就拿上面的Goods
商品組件,當點擊按鈕時,若是加載的模塊比較大,用戶就不得不去等待加載完整個message.js
文件才能展現彈窗出來(固然這樣的狀況出現不是特別多,因爲打包後的js文件通常都會存放到cdn上,而請求時會採起就近原則加載,這也有效避免這個問題,但咱們仍是能夠探討一下這個問題可使用什麼方式避免)。爲此咱們有木有方案能夠將這個時延有效滴去掉?在這裏我就不賣關子,在Webpack 4.6
中,指出明確支持prefetching and preloading
,而這兩個東西剛好就是能夠解決咱們剛剛要面對問題。
Preload
是預加載,PreFetch
是預測將要加載的模塊,這二者都是link
標籤下的屬性。有興趣的童鞋,能夠看看關於這二者在客戶端執行的優先級
簡單來講,Preload
優先級爲Height
,而PreFetch
則爲Low
。這二者是有區別的:
preload
:主要是用於當前頁面的預加載,會和主文件bundle.js
並行下載,且優先獲取,可用於預加載某些必要模塊prefetch
: 主要是用於下一步操做或者頁面,會在瀏覽器空閒時間纔去下載,優先級最低另外,須要說明的是,preload
和prefetch
目前對於現有的瀏覽器的支持程度並非那麼的友好。具體能夠看看兩個兼容性:Preload的兼容性和Prefetch的兼容性
雖然兼容性不是那麼友好,preload
和prefetch
都是申明性質的,因此就算不支持,也不會影響現有頁面的任何功能
看到這裏,估計你們應該也能夠想到,使用prefetch
能夠解決咱們上面遇到的時延等待問題,那麼具體怎麼寫?在這裏,咱們還須要藉助Webpack
的preload-webpack-plugin
插件,配置編寫以下:
webpack.config.js 部分配置
這時候,就要在Webpack 4.6+
版本上,修改商品頁組件以下:
goods.vue 商品頁面組件
能夠看到,只須要加上webpackPrefetch: true
的註釋再結合preload-webpack-plugin
插件便可把動態引入的模塊進行Prefetch
。
當咱們再次訪問localhost:8080/#/goods
時,會發現,加載完主流程的js文件
後,而後在瀏覽器的空閒時間,就會自動加載message.js文件
,而且能夠看到其優先級爲Low
的。另外,咱們也能夠看到在<head>
標籤,是經過<link>
標籤使用rel="prefetch"
來加載message.js文件
。這時候再點擊按鈕,就會再也不須要等待時延直接加載Message組件
。
至於上述運行的效果圖,我就不截圖了,有興趣的童鞋能夠親自去嘗試驗證一下,這樣纔能有更加深入的印象。若有不便,在這裏我就說聲很差意思哈🙈
對於Webpack模塊化加載優化
,也許還會有一些更好更優的方案,也歡迎👏大神們在評論區分享分享來一塊兒學習。而上述的優化方案,其實在平常的模塊化開發中是可使用到的,不過還請你們要結合本身需求的應用場景分析再來優化,一旦使用不當,優化也許就是一個消耗性能的體驗。