您好,登錄后才能下訂單哦!
前言
曾幾何時(shí),我們是如上圖的方式引入JS資源的,相信現(xiàn)在很少遇見了。近年來Web前端開發(fā)領(lǐng)域朝著規(guī)范開發(fā)的方向演進(jìn)。體現(xiàn)在以下兩點(diǎn):
1、MVC研發(fā)構(gòu)架。多多益處(邏輯清晰,程序注重?cái)?shù)據(jù)與表現(xiàn)分離,可讀性強(qiáng),利于規(guī)避和排查問題...)
2、構(gòu)建工具層出不窮。多多益處(提升團(tuán)隊(duì)協(xié)作,以及工程運(yùn)維,避免人工處理瑣碎而重復(fù)的工作)
所以,前端這么好玩,如果還有項(xiàng)目沒有前后端分離的話,真的是守舊過頭了。
主流構(gòu)建工具
市面上有許多構(gòu)建工具,包括Grunt、Gulp、browserify等,這些和WebPack都是打包工具。但WebPack同時(shí)也具備以下特點(diǎn):
相比Grunt,WebPack除了具備豐富的插件外,同時(shí)帶有一套加載(Loader)系統(tǒng)。使它支持多種規(guī)范的加載方式,包括ES6、CommonJS、AMD等方式,這是Grunt、Gulp所不具備的。
從代碼混淆的角度來看,WebPack更加的極致
代碼分片為處理單元(而不是文件),使得文件的分片更為靈活。
P.S.此處只做簡(jiǎn)單的比較,不論孰優(yōu)孰劣。其實(shí)工具都能滿足需求,關(guān)鍵是看怎么用,工具的使用背后是對(duì)前端性能優(yōu)化的理解程度。
引言
最近在用webpack優(yōu)化首屏加載性能,通過幾種插件之后我們上線前后的速度快了一倍,在此就簡(jiǎn)單的分享下吧,先上個(gè)優(yōu)化前后首屏渲染的對(duì)比圖。
可以看到總下載時(shí)間從3800ms縮短到1600ms。
我們?cè)谟脀ebpack時(shí)一般都會(huì)選擇多入口文件吧,為的就是將自己的源碼跟第三方庫代碼分離。這是之前的代碼,
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非常大,所以打包時(shí)的vendor.js大概為1.2MB(經(jīng)過gzip壓縮之后),而且首頁沒有用到echarts,所以我之后使用了externals將第三方庫以cdn的方式去引入,下面是優(yōu)化過的代碼
entry: { entry: './src/main.js', vendor: ['vue', 'vue-router', 'vuex', 'element-ui'] }, // 這里的output為base中的output,不是生產(chǎn)的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' },
這就是優(yōu)化前后的對(duì)比。
然后我們要到html中以script標(biāo)簽的形式去引externals中的cdn。之后就可以在相應(yīng)的文件中import了,他的好處是不管你在多少vue文件中引用多少次,他都不會(huì)打包到所有的trunk(這里的trunk'指的是按需加載,一會(huì)詳細(xì)說明)中,這是用webpack-bundle-analyzer插件展示的效果。
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; new BundleAnalyzerPlugin({ // 可以是`server`,`static`或`disabled`。 // 在`server`模式下,分析器將啟動(dòng)HTTP服務(wù)器來顯示軟件包報(bào)告。 // 在“靜態(tài)”模式下,會(huì)生成帶有報(bào)告的單個(gè)HTML文件。 // 在`disabled`模式下,你可以使用這個(gè)插件來將`generateStatsFile`設(shè)置為`true`來生成Webpack Stats JSON文件。 analyzerMode: 'server', // 將在“服務(wù)器”模式下使用的主機(jī)啟動(dòng)HTTP服務(wù)器。 analyzerHost: '127.0.0.1', // 將在“服務(wù)器”模式下使用的端口啟動(dòng)HTTP服務(wù)器。 analyzerPort: 8888, // 路徑捆綁,將在`static`模式下生成的報(bào)告文件。 // 相對(duì)于捆綁輸出目錄。 reportFilename: 'report.html', // 模塊大小默認(rèn)顯示在報(bào)告中。 // 應(yīng)該是`stat`,`parsed`或者`gzip`中的一個(gè)。 // 有關(guān)更多信息,請(qǐng)參見“定義”一節(jié)。 defaultSizes: 'parsed', // 在默認(rèn)瀏覽器中自動(dòng)打開報(bào)告 openAnalyzer: true, // 如果為true,則Webpack Stats JSON文件將在bundle輸出目錄中生成 generateStatsFile: false, // 如果`generateStatsFile`為`true`,將會(huì)生成Webpack Stats JSON文件的名字。 // 相對(duì)于捆綁輸出目錄。 statsFilename: 'stats.json', // stats.toJson()方法的選項(xiàng)。 // 例如,您可以使用`source:false`選項(xiàng)排除統(tǒng)計(jì)文件中模塊的來源。 // 在這里查看更多選項(xiàng):https: //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21 statsOptions: null, logLevel: 'info' //日志級(jí)別??梢允?信息','警告','錯(cuò)誤'或'沉默'。 })
我們會(huì)看到,沒用externals和用了externals后所有的js中都不會(huì)出現(xiàn)類似echarts和lodash的庫出現(xiàn)(就算你import一萬次他都不會(huì)打包一次,厲害吧~~)。
對(duì)于externals再說兩點(diǎn)——
1.externals中的key是import中使用的
import lodash from "_"; import echarts from "echarts";
2.externals中的value是window下調(diào)用的
然后我們?cè)賮砹牧臑槭裁磑utput使用trunkhash不用trunk,這是為了持久化緩存。簡(jiǎn)單說下兩者的區(qū)別——
trunk:每次build之后的版本,就是說所有的build之后的文件hash值一致,比如我只改了一個(gè)文件,最后所有的文件hash都會(huì)變,這樣所有的文件都不會(huì)走cache,這樣緩存就失去了意義。
trunkhash:根據(jù)每個(gè)文件生成不同的hash值,當(dāng)文件變化時(shí)hash會(huì)改變且只會(huì)改變相應(yīng)的文件
然后我們肯定是要用到CommonsChunkPlugin,這個(gè)插件是用來抽取公共代碼的,基本上99%的配置都是長(zhǎng)這樣子或者類似這樣子用兩個(gè)不同的commonschunkPlugin,但這從某方面來說并沒有實(shí)現(xiàn)真正意義上的持久化緩存,這個(gè)一會(huì)我會(huì)通過webpack打包原理來詳細(xì)解釋其中的原因。。。。。。
new webpack.optimize.CommonsChunkPlugin({ names: ['vendor','manifest'] })
在沒用這個(gè)插件之前,我們的main.js和vendor.js會(huì)是這樣子。。。
大家會(huì)看到我們這兩個(gè)文件會(huì)有公共的部分,比如vue和element-ui,所以我們要抽取公共代碼到vendor中,所以我們可以先這樣配置
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', }),
但這樣的話雖然可以提取公共代碼,但我們會(huì)把runtime(webpack運(yùn)行時(shí)的代碼,一會(huì)在打包原理中會(huì)再次提到)也放到vendor中,這里面會(huì)維護(hù)一個(gè)trunk的文件列表,類似于這樣,就是說我們改任意的代碼,這個(gè)table里面的hash會(huì)變,所以vendor的hash也會(huì)變
,所以這沒有實(shí)現(xiàn)真正的持久化緩存。這個(gè)hash table是按需緩存的打包出來的trunk包,一般都是通過require.ensure(就是vue-router中配置的page對(duì)應(yīng)頁面,按需加載)
所以我們就把name改為names,就是上面那個(gè)配置。因?yàn)槭褂眠@個(gè)插件,我們會(huì)把公共代碼抽到第一個(gè)name中,把runtime放到最后一個(gè)name中,也就是我們所謂的“manifest”文件。
并且這個(gè)文件會(huì)比較小,通常都是2kb左右,所以build后會(huì)生成一個(gè)script標(biāo)簽,但這樣的話就多了一個(gè)http請(qǐng)求,所以我們可以用另外一個(gè)插件(InlineManifestWebpackPlugin)將manifest.js內(nèi)聯(lián)進(jìn)去。就會(huì)長(zhǎng)這樣子
再回到我們的CommonsChunkPlugin,現(xiàn)在我們隨便改任何已存在的文件,vendor.js的hash都不會(huì)變,是的,貌似這就實(shí)現(xiàn)了持久化緩存。但是當(dāng)我們新增一個(gè)模塊,并且在入口文件中import一下,我們的vendor就會(huì)跟main一起變。很奇怪對(duì)吧,我們明明已經(jīng)做了自己的源碼跟第三方庫分離,為什么vendor還會(huì)變(到現(xiàn)在應(yīng)該沒有任何一篇博客對(duì)此進(jìn)行詳細(xì)的說明)。下面我就詳細(xì)的給大家解釋下我的看法,如果大家發(fā)現(xiàn)有不對(duì)的地方還請(qǐng)指正。
再解釋為什么之前,我們先簡(jiǎn)單了解下webpack的打包規(guī)則。
webpack一個(gè)entry對(duì)應(yīng)一個(gè)bundle,這個(gè)bundle包括入口文件和其依賴的模塊。其他按需加載的則打包成其他的bundle。還有一個(gè)比較重要的文件時(shí)manifest,它是最先加載的,負(fù)責(zé)打包其他的bundle并按需加載和執(zhí)行。
manifest是一個(gè)自執(zhí)行函數(shù),熟悉angular的同學(xué)看第一行應(yīng)該很了解,因?yàn)閍nguar1.3版本的源碼中啟動(dòng)就是angular.bootstrap,對(duì),這里也是一樣。里面的modules變量就是對(duì)應(yīng)模塊函數(shù),它是webpack處理的基本單位,就是說對(duì)應(yīng)打包前的一個(gè)文件
這是js源文件,
這是打包后的文件,
所有的模塊函數(shù)索引都是連續(xù)的(每個(gè)js文件生成一個(gè)trunkid?。。。。。?,像這種 /* 4 */ 對(duì)應(yīng)的就是js文件,他通過打包就變成了一個(gè)個(gè)trunkid,仔細(xì)看會(huì)看到咱們打包前js文件里的export和require依賴都會(huì)統(tǒng)一轉(zhuǎn)換成webpack模塊。咱們說的webpackJsonp就是除manifest之外打包其他的文件的函數(shù)體。
簡(jiǎn)單說下main吧,這個(gè)圖的trunkid是連續(xù)的,為了在一張圖上顯示,我截掉了trunk3-7.
這里面一共有三個(gè)參數(shù),第一個(gè)是我當(dāng)前文件的trunkid,它是唯一標(biāo)識(shí)符,就是指main的trunkid,第二個(gè)就是打包的所有文件的模塊函數(shù),第三個(gè)是我要立即執(zhí)行的trunkid模塊函數(shù)。
ok,介紹這些就足夠了。
然后我們?cè)倩剡^頭來看看為什么我們所謂的commonschunkPlugin會(huì)變。剛才說過,有幾個(gè)js就有幾個(gè)trunkid。
所以當(dāng)我們新加一個(gè)js并引入到main入口時(shí),webpack再次打包,我的main文件會(huì)多一個(gè)模塊函數(shù),剛剛說過trunkid是依次遞增的而且不會(huì)重復(fù)。所以對(duì)應(yīng)的vendor的id會(huì)+1,就是這么細(xì)微的變化導(dǎo)致hash變了。
大家仔細(xì)看,這兩個(gè)vendor都是10272行,唯一的不同就是我要自執(zhí)行這個(gè)vendor庫,這里我引用的jquery,所以這個(gè)文件只有jquery,自執(zhí)行肯定要有模塊函數(shù),trunkid+1,所以hash會(huì)變。我們?cè)俸煤没貞浺幌拢鋵?shí)這也說明了這個(gè)插件的意義,我就是要抽出公共的庫,OK,這個(gè)插件做到了,但是因?yàn)閣ebpack打包機(jī)制,不同文件生成不同turnkid,所以這是美中不足的一點(diǎn)。再回想一下,我們一般是不會(huì)隨便修改main.js的,所以從另一角度上來說這就是實(shí)現(xiàn)了持久化緩存。但我如果就是想保持vendor的hash不變要怎么辦呢?
這段代碼就可以實(shí)現(xiàn),沒錯(cuò),如果你對(duì)vue-cli了如指掌,這就是vue-cli的官方demo,至于為什么可以,這個(gè)我后續(xù)會(huì)跟大家解釋(實(shí)在是寫不動(dòng)了。。。)。
最后再給大家介紹一個(gè)超級(jí)好用的東西,就是cdn。我們現(xiàn)在的需求是想讓圖片走cdn,讓js走線上路徑,但官方的解釋是通過修改config文件做cdn變化,這樣做的話我的所有輸出都會(huì)走cdn,那所有的ajax請(qǐng)求就跨域了呀。
一開始我的解決方案是,在源文件中挨個(gè)替換,這樣會(huì)比較慢,更重要的是,cdn圖片也是有hash值的,當(dāng)我以后替換圖片時(shí),還得重新改相應(yīng)的hash。有什么方法能讓他自動(dòng)去獲取hash呢。
沒錯(cuò),我們需要在url-loader中單獨(dú)配置cdn,做到j(luò)s訪問線上路徑,靜態(tài)資源使用cdn,兩者互不影響。
簡(jiǎn)單提醒一下,url-loader不能檢測(cè)到j(luò)s中的background,所以我們凡是在js中引用的地址,必須在外面先import這張圖片,url-loader才會(huì)解析并打包。
今天就先到這里吧,改天繼續(xù)。。。。。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。