Webpack打包進階


說在前面

由於使用了React直出,頁面各項性能指標使人悅目。本篇將深入探討目前PC部落所采用webpack打包優化策略,以及探討PC部落並未使用的 webpack Code Splitting 代碼分包、異步模塊加載特性。看看它們又是如何對PC部落的性能起到進一步的催化作用。

為什么要使用webpack

如果你曾經使用過 Broserify, RequireJS 或類似的打包工具,並注重:代碼分包、異步加載、靜態資源打包(圖片/CSS)。那么 webpack 就是幫你構建項目的利器!簡單一句話:在webpack中,所有資源都被當作是模塊,js可以引用 css , css 中可以嵌入圖片 dataUrl。

webpack特性

對應不同文件類型的資源,webpack有對應的模塊 loader ,比如對於 less, 使用的是 less-loader,你可以在這里找到 所有loader. webpack 具有requireJS 和 browserify 的功能,但仍有自己的新特性: 1、對 CommonJS、AMD、ES6的語法做了兼容; 2、對js、css、圖片等資源文件都支持打包; 3、串聯式模塊加載器以及插件機制讓其具有更好的靈活性和拓展性,例如對 coffeeScript、ES6的支持; 4、有獨立的配置文件 webpack.config.js; 5、可以將代碼切割成不同 chunk,實現按需加載,降低了初始化時間; 6、支持 SourceUrls 和 SourceMaps,易於調試; 7、具有強大的 Plugin 接口,大多是內部插件,使用起來比較靈活; 8、webpack 使用異步 IO 並具有多級緩存,使得 webpack 在增量編譯上更快!

為什么混用Grunt和webpack

自React誕生以來,耳熟能詳的是 React+webpack 開發大法,而且在大多數 React 網絡教程中也很少提及同時采用了 Grunt 聯合構建項目。

Grunt 可以對整個項目文件做復制、刪除、合並、壓縮等等。而Webpack 的優勢在於對靜態文件(js/jsx/coffeeScript/css/less/sass/iamges)按不同模塊加載(包括按需加載)——這正是我們對webpack感興趣的地方,各個模塊組建化(可以將一個組建的圖片、樣式、腳本、頁面放在同一個文件夾中)。所以,在項目中二者分工不同,各司其職。

注:使用 gulp 替換 grunt 當然也是沒有問題。

webpack配置

webpack 有多種配置方式,由於PC部落中靜態資源文件較多,使用配置文件進行打包會方便很多。

通常情況下,如果我們只使用 webpack 構建項目,那么配置 webpack.config.js 即可。由於在PC部落中使用了 grunt,並在 grunt 組合任務中調用 webpack 任務,因此需要在 grunt 的任務配置中添加 webpack.js(使用了load-grunt-config插件) 進行配置。

配置總覽
var taskConfig = { dev: { entry: { // 入口文件,考慮到多頁面資源緩存,我們打成多個包 "index": path.resolve(config.srcPath, "pages/index/index.jsx"), "detail": path.resolve(config.srcPath, "pages/detail/detail.jsx"), ... }, resolve: { // 請求重定向,顯示指出依賴查找路徑 alias: { img: path.resolve(config.srcPath + 'img'), comps: path.resolve(config.srcPath + 'pages/components') ... } }, output: { // 輸出文件 path: config.devPath + '/js', // 文件絕對路徑 filename: "[name].min.js", // 輸出文件名 publicPath: "http://s.url.cn/qqun/xiaoqu/buluo/p/js/", // 公共訪問路徑,替換CDN chunkFilename: "[name].chunk.min.js" // 異步加載時需要被打包的文件名 }, module: { // 各類文件 loader noParse: [], // 忽略解析的文件 preLoaders: [{ // 預加載的模塊 test: /\.jsx$/, exclude: /node_modules/, loader: 'jsxhint-loader' }], loaders: [{ // 各式加載器 test: /\.jsx$/, loader: 'jsx-loader', include: path.resolve(config.srcPath) }, { test: /\.less$/, // 使用“!”鏈式loader,從右向左依次執行 loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader"), include: path.resolve(config.srcPath) }, { test: /\.(jpe?g|png|gif|svg)$/i, // inline base64url for <=1500 images loader: 'url-loader?limit=1500&name=images/[name].[hash].[ext]', include: path.resolve(config.srcPath) }] }, externals: { // 指定采用外部 CDN 依賴的資源,不被webpack打包 "react": "React", "react-dom": "ReactDOM" }, plugins: [ ... // 公共模塊獨立打包配置 new CommonsChunkPlugin("common", "common.min.js", ["index", "detail", "barindex", "search"]), // 獨立打包css文件以外鏈形式加載 new ExtractTextPlugin("../css/[name].min.css") ], watch: true, keepalive: true, lessLoader: { lessPlugins: [ new LessPluginAutoPrefix() ] } }, 

webpack打包優化

1、請求重定向

resolve.alias 是webpack 的一個配置項,它的作用是把用戶的一個請求重定向到另一個路徑。 比如:

resolve: {  // 顯示指出依賴查找路徑 alias: { comps: 'src/pages/components' } } 

這樣我們在要打包的腳本中的使用 require('comps/Loading.jsx'); 其實就等價於require('src/pages/components/Loading.jsx')。這猶如《高性能javascript》中給查詢壓力較大的對象給了一個別名,通過使用別名可以將本例減少幾乎一半的時間。

2、忽略對已知文件的解析

module.noParse,如果你確定一個模塊中沒有其它新的依賴,就可以配置這項,webpack 將不再掃描這個文件中的依賴。 比如我們在入口文件 entry.js 中檢測到對資源src/pages/components/ueditor.min.js資源的請求,如果我們配置:

module: { noParse: [/ueditor/] } 

noParse規則中的/ueditor/一條生效,所以 webpack 直接把依賴打包進了 entry.js。增加這樣的配置會讓 webpack 編譯時間更短。

3、使用公用CDN

考慮到web上有很多的公用 CDN 服務,那么我們可以將 react 從 bundle 中分離出來,進而不會被 webpack 打包, 作為外部依賴引用 CDN 。 方法是使用 externals 聲明一個外部依賴。 如:

module:{ externals: { // 方式一:申明為外部依賴並指定別名 "react": "React", "react-dom": "ReactDOM" // 方式二:true 為外部依賴,false 則不是 a: false, // a is not external b: true // b is external }, } 

並在 HTML 代碼中加上一行

<script src="//cdn.bootcss.com/react/0.14.2/react.js"> <script src="//cdn.bootcss.com/react/0.14.2/react-dom.js"> 

這樣我們在js中引入React = require('react') , webpack 就不會把 react 打包進來而直接引用CDN,這樣做可以讓 webpack 編譯時間縮減一大半!

系列插件

CommonsChunkPlugin

開發中需要將多個頁面的公用模塊獨立打包,從而可以利用瀏覽器緩存機制來提高頁面加載效率,減少頁面初次加載時間,只有當某功能被用到時才去動態加載。這就要使用到 webpack 中的 CommonsChunkPlugin 插件。

使用:
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { ... /* * @param 1 將公共模塊提取,生成名為 common 的chunk * @param 2 最終生成的公共模塊的 js 文件名 * @param 3 公共模塊提取的資源列表 */ new CommonsChunkPlugin("common", "common.min.js", ["index", "detail", "barindex", "search"]) } 

ExtractTextPlugin

webpack 中編寫js文件時,可以通過 require 的方式引入其他靜態資源,可通過loader對文件自動解析並打包文件。 通常我們會將 js 文件打包合並,css 文件在頁面header中嵌入 style 的方式載入頁面。但在開發過程中我們並不想將樣式打包在腳本中(最好可以獨立生成css文件,以外鏈形式加載)。 ExtractTextPlugin 插件可以幫我們達到這樣的效果。

安裝:

npm install extract-text-webpack-plugin –save-dev

使用:
var ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { ... plugins: [ new ExtractTextPlugin("../css/[name].min.css") ] } 

這樣配置就可以將 js 中的 css 文件提取,並以指定的文件名來進行加載。

LessPluginAutoPrefix

顧名思義,就是autoPrefix插件,用來補全CSS的廠商前綴(-webkit-, -moz-, -o-);

使用:
var LessPluginAutoPrefix = require('less-plugin-autoprefix'); var taskConfig = { dev: { ... lessLoader: { lessPlugins: [ new LessPluginAutoPrefix() ] } } 

Code Splitting

對於一個大型的web app,我們把所有的 js 文件合成一個顯然是非常低效的,因為有些 js 模塊並不是我們當前頁面所需要的(這會大大增加頁面首屏渲染時間)。Webpack 就是這樣一種神器,為您提供優質的代碼分包服務,從此“媽媽再也不用擔心頁面按需加載的問題了”!

方式一:require

require(dependencies, callback) 遵從 AMD 規范定義的異步方法。使用該方法時,所有的依賴被異步加載並從左至右立即執行,依賴都被執行后,執行callback

方式二:require.ensure

require.ensure(dependencies, callback) 遵從 CommonJS 規范,在需要的時候才下載依賴的模塊。當所有的依賴都被加載完畢,便執行 callback(注:require作為callback的參數)。 細心的同學可能還記得 output 配置中有

output: { ... chunkFilename: "[name].chunk.min.js" } 

chunk 到底是什么? chunk 又是怎么生成的呢? 為了實現部分資源的異步加載,有些資源是不打包到入口文件里面的。於是我們使用 require.ensure 作為代碼分割的標識。require.ensure 會創建一個 chunk ,且可以指定該 chunk 的名稱(注:如果這個chunk已經存在了,則將本次依賴的模塊合並到已經存在的chunk中),最后這個 chunk 在 webpack 構建時會單獨生成一個文件。 比如我們要根據當前運行平台,加載兩個不同的UI組建,那么:

var platform = Util.getPlatform(); if( platform === "ios"){ require.ensure(['./components/dialog'], function(require){ ... }, 'popup'); // 最后一個參數是 chunk 名 } if( platform === "android"){ require.ensure(["./components/toast"], function(require){ ... }, 'popup'); } 

通過webpack打包之后,會生成一個 popup.chunk.min.js 文件。在不同的運行平台上,我們會發現 popup.chuck.min.js 文件的內容是相同的(因為我們配置的 chunk 名都是 popup)。 如果我們想讓按需加載的模塊再次拆分成 dialog 和 toast,兩個文件,僅僅需要將 require.ensure 中配置的chunk 名改不同,即可在代碼被執行時加載單一文件。

注意點:

1、require :加載模塊,並立即執行; 2、require.ensure:僅僅加載模塊,但不會執行; 3、不用在 html 中顯示調用生成的 chunk 文件,按需加載時會自動調用; 4、不用擔心第三方庫被反復打包的問題,因為我們已經使用 CommonsChunkPlugin 對公共部分進行提取。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM