webpack是近期最火紅的一款模組載入器兼打包工具,它能把各種資源,例如JS(含JSX)、coffee、樣式(含less/sass)、圖片等都當作模組來使用和處理。這篇文章主要要為大家總結介紹了關於webpack學習教學之前端效能優化的相關資料,需要的朋友可以參考下。
前言
#曾幾何時,我們是如上圖的方式引入JS資源的,相信現在很少遇見了。近年來Web前端開發領域正朝著規範開發的方向演進。體現在以下兩點:
1、MVC研發架構。多多好處(邏輯清晰,程式著重資料與表現分離,可讀性強,利於規避與排查問題...)
2、建構工具層出不窮。多多好處(提升團隊協作,以及工程維,避免人工處理瑣碎而重複的工作)
#模組化開發
將前端性能優化理論落地,代碼壓縮,合併,緩存控制,提取公共代碼等
其他的還包括比如你可以用ES 6 或CoffeeScript寫源碼,然後構建出瀏覽器支持的ES5
所以,前端這麼好玩,如果還有專案沒有前後端分開的話,真的是守舊過頭了。
主流建置工具
市面上有許多建置工具,包括Grunt、Gulp、browserify等,這些和WebPack都是打包工具。但WebPack同時也具備以下特點:
#比較Grunt,WebPack除了具備豐富的插件外,同時也附有一套載入(Loader)系統。使它支援多種規範的載入方式,包括ES6、CommonJS、AMD等方式,這是Grunt、Gulp所不具備的。
從程式碼混淆的角度來看,WebPack更加的極致
程式碼分片為處理單元(而非檔案),使得檔案的分片更為靈活。
P.S.這裡只做簡單的比較,不論孰優孰劣。其實工具都能滿足需求,關鍵在於看怎麼用,工具的使用背後是對前端效能優化的理解程度。
引言
最近在用webpack優化首屏載入效能,透過幾種外掛程式之後我們上線前後的速度快了一倍,在此就簡單的分享下吧,先上個優化前後首屏渲染的比較圖。
可以看到總下載時間從3800ms縮短到1600ms。
我們在用webpack時一般都會選擇多入口檔吧,為的就是將自己的原始碼跟第三方函式庫程式碼分開。這是之前的程式碼,
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的方式去引入,下面是優化過的程式碼
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' },
#這就是優化前後的比較。
然後我們要到html以script標籤的形式去引externals中的cdn。之後就可以在相應的文件中import了,他的好處是不管你在多少vue文件中引用多少次,他都不會打包到所有的trunk(這裡的trunk'指的是按需加載,一會詳細說明)中,這是用webpack-bundle-analyzer外掛程式展示的效果。
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一萬次他都不會打包一次,厲害吧~~)。
對於externals再說兩點-
1.externals中的key是import中使用的
import lodash from "_"; import echarts from "echarts";
2.externals中的value是window下呼叫的
然後我們再來聊聊為什麼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才會解析並打包。
上面是我整理給大家的,希望今後對大家有幫助。
相關文章:
以上是有關webpack前端效能優化(詳細教學)的詳細內容。更多資訊請關注PHP中文網其他相關文章!