webpack以前端性能優化(史上最全,不斷更新中。。。)

最近在用webpack優化首屏加載性能,經過幾種插件以後咱們上線先後的速度快了一倍,在此就簡單的分享下吧,先上個優化先後首屏渲染的對比圖。html

能夠看到總下載時間從3800ms縮短到1600ms。vue

咱們在用webpack時通常都會選擇多入口文件吧,爲的就是將本身的源碼跟第三方庫代碼分離。這是以前的代碼,jquery

entry: {
        entry: './src/main.js',
        vendor: ['vue', 'vue-router', 'vuex', 'element-ui','echarts']
},
output: {
        path: config.build.assetsRoot,
        filename: utils.assetsPath('js/[name].[chunkhash].js'),
        chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
}

echarts很是大,因此打包時的vendor.js大概爲1.2MB(通過gzip壓縮以後),並且首頁沒有用到echarts,因此我以後使用了externals將第三方庫以cdn的方式去引入,下面是優化過的代碼webpack

entry: {
        entry: './src/main.js',
        vendor: ['vue', 'vue-router', 'vuex', 'element-ui']
    },
    // 這裏的output爲base中的output,不是生產的output
    output: {
        path: config.build.assetsRoot,
        filename: '[name].js',
        libraryTarget: "umd",
        publicPath: process.env.NODE_ENV === 'production' ?
            config.build.assetsPublicPath : config.dev.assetsPublicPath
    },
    externals: {
        echarts: 'echarts',
        _: 'lodash'
    },

 這就是優化先後的對比。git

而後咱們要到html中以script標籤的形式去引externals中的cdn。以後就能夠在相應的文件中import了,他的好處是無論你在多少vue文件中引用多少次,他都不會打包到全部的trunk(這裏的trunk'指的是按需加載,一會詳細說明)中,這是用webpack-bundle-analyzer插件展現的效果。github

var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
new BundleAnalyzerPlugin({
            //  能夠是`server`,`static`或`disabled`。
            //  在`server`模式下,分析器將啓動HTTP服務器來顯示軟件包報告。
            //  在「靜態」模式下,會生成帶有報告的單個HTML文件。
            //  在`disabled`模式下,你可使用這個插件來將`generateStatsFile`設置爲`true`來生成Webpack Stats JSON文件。
            analyzerMode: 'server',
            //  將在「服務器」模式下使用的主機啓動HTTP服務器。
            analyzerHost: '127.0.0.1',
            //  將在「服務器」模式下使用的端口啓動HTTP服務器。
            analyzerPort: 8888, 
            //  路徑捆綁,將在`static`模式下生成的報告文件。
            //  相對於捆綁輸出目錄。
            reportFilename: 'report.html',
            //  模塊大小默認顯示在報告中。
            //  應該是`stat`,`parsed`或者`gzip`中的一個。
            //  有關更多信息,請參見「定義」一節。
            defaultSizes: 'parsed',
            //  在默認瀏覽器中自動打開報告
            openAnalyzer: true,
            //  若是爲true,則Webpack Stats JSON文件將在bundle輸出目錄中生成
            generateStatsFile: false, 
            //  若是`generateStatsFile`爲`true`,將會生成Webpack Stats JSON文件的名字。
            //  相對於捆綁輸出目錄。
            statsFilename: 'stats.json',
            //  stats.toJson()方法的選項。
            //  例如,您可使用`source:false`選項排除統計文件中模塊的來源。
            //  在這裏查看更多選項:https:  //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
            statsOptions: null,
            logLevel: 'info' //日誌級別。能夠是'信息','警告','錯誤'或'沉默'。
        })

 

 

咱們會看到,沒用externals和用了externals後全部的js中都不會出現相似echarts和lodash的庫出現(就算你import一萬次他都不會打包一次,厲害吧~~)。web

對於externals再說兩點——ajax

1.externals中的key是import中使用的vue-router

import lodash from "_";
import echarts from "echarts";

 

2.externals中的value是window下調用的vuex

而後咱們再來聊聊爲何output使用trunkhash不用trunk,這是爲了持久化緩存。簡單說下二者的區別——

trunk:每次build以後的版本,就是說全部的build以後的文件hash值一致,好比我只改了一個文件,最後全部的文件hash都會變,這樣全部的文件都不會走cache,這樣緩存就失去了意義。

trunkhash:根據每一個文件生成不一樣的hash值,當文件變化時hash會改變且只會改變相應的文件

而後咱們確定是要用到CommonsChunkPlugin,這個插件是用來抽取公共代碼的,基本上99%的配置都是長這樣子或者相似這樣子用兩個不一樣的commonschunkPlugin,但這從某方面來講並無實現真正意義上的持久化緩存,這個一會我會經過webpack打包原理來詳細解釋其中的緣由。。。。。。

new webpack.optimize.CommonsChunkPlugin({
       names: ['vendor','manifest']
})

 

 在沒用這個插件以前,咱們的main.js和vendor.js會是這樣子。。。

你們會看到咱們這兩個文件會有公共的部分,好比vue和element-ui,因此咱們要抽取公共代碼到vendor中,因此咱們能夠先這樣配置

new webpack.optimize.CommonsChunkPlugin({
       name: 'vendor',
}),

但這樣的話雖然能夠提取公共代碼,但咱們會把runtime(webpack運行時的代碼,一會在打包原理中會再次提到)也放到vendor中,這裏面會維護一個trunk的文件列表,相似於這樣,就是說咱們改任意的代碼,這個table裏面的hash會變,因此vendor的hash也會變

,因此這沒有實現真正的持久化緩存。這個hash table是按需緩存的打包出來的trunk包,通常都是經過require.ensure(就是vue-router中配置的page對應頁面,按需加載)

 

 因此咱們就把name改成names,就是上面那個配置。由於使用這個插件,咱們會把公共代碼抽到第一個name中,把runtime放到最後一個name中,也就是咱們所謂的「manifest」文件。

而且這個文件會比較小,一般都是2kb左右,因此build後會生成一個script標籤,但這樣的話就多了一個http請求,因此咱們能夠用另一個插件(InlineManifestWebpackPlugin)將manifest.js內聯進去。就會長這樣子

再回到咱們的CommonsChunkPlugin,如今咱們隨便改任何已存在的文件,vendor.js的hash都不會變,是的,貌似這就實現了持久化緩存。可是當咱們新增一個模塊,而且在入口文件中import一下,咱們的vendor就會跟main一塊兒變。很奇怪對吧,咱們明明已經作了本身的源碼跟第三方庫分離,爲何vendor還會變(到如今應該沒有任何一篇博客對此進行詳細的說明)。下面我就詳細的給你們解釋下個人見解,若是你們發現有不對的地方還請指正。

再解釋爲何以前,咱們先簡單瞭解下webpack的打包規則。

webpack一個entry對應一個bundle,這個bundle包括入口文件和其依賴的模塊。其餘按需加載的則打包成其餘的bundle。還有一個比較重要的文件時manifest,它是最早加載的,負責打包其餘的bundle並按需加載和執行。

manifest是一個自執行函數,熟悉angular的同窗看第一行應該很瞭解,由於anguar1.3版本的源碼中啓動就是angular.bootstrap,對,這裏也是同樣。裏面的modules變量就是對應模塊函數,它是webpack處理的基本單位,就是說對應打包前的一個文件

 

這是js源文件, 

這是打包後的文件,

 

 全部的模塊函數索引都是連續的(每一個js文件生成一個trunkid!!!!!),像這種 /* 4 */ 對應的就是js文件,他經過打包就變成了一個個trunkid,仔細看會看到我們打包前js文件裏的export和require依賴都會統一轉換成webpack模塊。我們說的webpackJsonp就是除manifest以外打包其餘的文件的函數體。

簡單說下main吧,這個圖的trunkid是連續的,爲了在一張圖上顯示,我截掉了trunk3-7.

這裏面一共有三個參數,第一個是我當前文件的trunkid,它是惟一標識符,就是指main的trunkid,第二個就是打包的全部文件的模塊函數,第三個是我要當即執行的trunkid模塊函數。

ok,介紹這些就足夠了。

而後咱們再回過頭來看看爲何咱們所謂的commonschunkPlugin會變。剛纔說過,有幾個js就有幾個trunkid。

因此當咱們新加一個js並引入到main入口時,webpack再次打包,個人main文件會多一個模塊函數,剛剛說過trunkid是依次遞增的並且不會重複。因此對應的vendor的id會+1,就是這麼細微的變化致使hash變了。

 

 

 

 你們仔細看,這兩個vendor都是10272行,惟一的不一樣就是我要自執行這個vendor庫,這裏我引用的jquery,因此這個文件只有jquery,自執行確定要有模塊函數,trunkid+1,因此hash會變。咱們再好好回憶一下,其實這也說明了這個插件的意義,我就是要抽出公共的庫,OK,這個插件作到了,可是由於webpack打包機制,不一樣文件生成不一樣turnkid,因此這是美中不足的一點。再回想一下,咱們通常是不會隨便修改main.js的,因此從另外一角度上來講這就是實現了持久化緩存。但我若是就是想保持vendor的hash不變要怎麼辦呢?

這段代碼就能夠實現,沒錯,若是你對vue-cli瞭如指掌,這就是vue-cli的官方demo,至於爲何能夠,這個我後續會跟你們解釋(實在是寫不動了。。。)。

最後再給你們介紹一個超級好用的東西,就是cdn。咱們如今的需求是想讓圖片走cdn,讓js走線上路徑,但官方的解釋是經過修改config文件作cdn變化,這樣作的話個人全部輸出都會走cdn,那全部的ajax請求就跨域了呀。

一開始個人解決方案是,在源文件中挨個替換,這樣會比較慢,更重要的是,cdn圖片也是有hash值的,當我之後替換圖片時,還得從新改相應的hash。有什麼方法能讓他自動去獲取hash呢。

沒錯,咱們須要在url-loader中單獨配置cdn,作到js訪問線上路徑,靜態資源使用cdn,二者互不影響。

簡單提醒一下,url-loader不能檢測到js中的background,因此咱們凡是在js中引用的地址,必須在外面先import這張圖片,url-loader纔會解析並打包。

今天就先到這裏吧,改天繼續。。。。。

相關文章
相關標籤/搜索