vue-cli 没有build如何配置_webpack4配置实现浏览器长期缓存
前言
webpack是目前十分火熱的模塊化打包工具,給我們前端在實現工程化方面提供了極大的幫助。webpack在社區中成長,又從社區中吸收各種優秀的配置方式。集成于自身,將社區中優秀的配置置于內部中,為開發者提供號稱0配置的配置。然鵝,還是需要我們手動配置一些,才能實現更好更高效率的方式。
現針對webpack4提供的一些方式,說明如何通過webpack實現前端資源的長期緩存。
本文在跑的所有例子都是基于以下環境
- macOS Mojave 10.14.6
- node v10.15.2
- webpack v4.38.0
目錄
- 什么是長期緩存
- 如何實現長期緩存
- 總結
- 參考文章
什么是長期緩存
webpack4官網針對長期緩存,是有在一些介紹中提及的。英文名叫long term cache。但是,我在官網上找了許久,并沒有找到long term cache的概念和實現的方式。
webpack打包的資源,最終都是在瀏覽器上呈現給用戶,所以webpack4介紹的long term cache,我們可以理解成在瀏覽器下的長期緩存方式。
瀏覽器緩存可以分為兩種:強緩存和協商緩存。
這里大概說一下他們的區別,具體的介紹可以參考文章最后給的參考鏈接。
強緩存是指在不訪問服務器的情況下,直接從瀏覽器獲取前端資源。讀取資源的方式可以從緩存中讀取,也可以從磁盤中讀取。實現的方式是在響應頭設置Expires字段或者Cache-Control字段。具體的表現形式如下
協商緩存是指在訪問服務器的情況下,服務器告知瀏覽器文件沒變化,這時服務器會返回304,可以直接利用緩存中的資源。實現協商緩存的方式是兩對組合(響應頭/請求頭):Last-Modified/If-Modified-Since ;ETag/If-None-Match。
Last-Modified是基于時間來判斷文件是否變化的,而ETag是基于文件內容來判斷的,Etag的精度會高于Last-Modified的方式,但是性能上卻遜于后者。同時,Etag的優先級也比Last-Modified的高,當兩組合都存在的情況下,會優先使用Etag的方式。具體的表現形式如下
假如強緩存和協商緩存同時存在的話,即響應頭/請求頭同時存在Expires、Cache-Control、Etag、Last-Modified/If-None-Match、If-Modified-Since時,強緩存優先于協商緩存。
這里就會有一個問題,假如我們命中強緩存了,那下次我們版本迭代的時候,不是無法更新資源了?
這就是webpack4需要為我們做的事情。在每次迭代時,都會給文件加上一段hash值。這個hash值,就是為了防止瀏覽器緩存(強緩存、協商緩存)舊的資源。這樣每次發布上線后,都會請求最新的資源。
那接下來,我們就來看看webpack4是如何做到高效的長期緩存的(hash的變化)。
如何實現長期緩存
以下的demo,都是基于mode為production來實現的。mode是webpack4新增的環境參數。
npm run build后,如下
Asset Size Chunks Chunk Names app.js 956 bytes 0 [emitted] app這么一看,好像沒什么毛病吧?目前確實沒什么毛病。
2. 【增加第三方庫】但是我們的項目不可能那么簡單吧。項目中會引入很多第三方資源庫來輔助我們開發。
// ./src/index.js import _ from 'lodash' console.log(_.now())這里用了lodash函數來獲取當前的時間,build后
Asset Size Chunks Chunk Names app.js 70.4 KiB 0 [emitted] app看上去貌似沒什么毛病。但是這里會有一個問題是,假如命中了強緩存,第二次發布的時候瀏覽器就沒法及時獲取到最新的資源了,所以我們需要做點調整。給js加上hash值。
module.exports = {...output: {filename: '[name].[hash:8].js'} }在output設置hash:8后,webpack會為我們讀取filename的屬性值,為我們在js加上hash
Asset Size Chunks Chunk Names app.09eb19c5.js 70.4 KiB 0 [emitted] appok,這時候我們想要的hash值就有了。當我們對入口js做一些調整時
// src/index.js console.log(_.now()) console.log(_.add(1, 2))build:Asset Size Chunks Chunk Names app.6a4878dc.js 70.4 KiB 0 [emitted] app前后我們發現,js的hash發生變化了,資源在瀏覽器可以及時更新。但是我們這里會有個問題阿,lodash會隨著我們每次修改業務代碼,都會重新請求一次。業務代碼經常變,第三方庫不經常變化,我們可以將其從業務代碼中,抽離出去,單獨請求。
module.exports = {mode: 'development',entry: {app: './src/index',},output: {filename: '[name].[hash:8].js'},optimization: {splitChunks: {chunks: 'all'}} }build后我們可以發現,lodash的代碼就被分離出去了。這里我們稱這種方式為code split,也就是代碼分離。這里需要注意的是,webpack4在optimization對象里是有一個默認的配置的,在你沒有配置對應字段的前提下,會默認走這些配置。所以看到生成的另一個js格式為vendors~app。具體可看看底部貼出的參考文章。
業務代碼和第三方庫做了分離,但是心細的朋友會發現,這兩個js文件的hash是一樣的。。假如我們對業務代碼進行修改
import _ from 'lodash' console.log(_.now()) console.log(_.add(3, 4))build之后發現,vendor的hash也變化了呀。
我們希望的是改變業務代碼,只改變app的hash。而vendor因為沒有變化,還是繼續走瀏覽器緩存。webpack4針對這種情況提供了另外一種hash方式,chunkhash。hash和chunkhash的區別如下
- [hash]:The hash of the module identifier
- [chunkhash]:The hash of the chunk content
我們需要根據兩個chunk自身的內容,通過某種算法來生成各自的hash。
module.exports = {...output: {filename: '[name].[chunkhash:8].js'} }這時候我們再次build,會發現符合我們的預期。(tips:由于改了webpack配置,導致所有hash都變化了)
針對index自行做一些修改,來查看前后兩次的變化
修改前
修改后
基本符合我們預期,vendor的hash值沒有發生變化。
3. 【增加css】沒有樣式的頁面是沒有靈魂的。現在我們嘗試引入css文件。同時使用MiniCssExtractPlugin插件,將我們的css文件分離出去。
// index.js import _ from 'lodash' import './style.css'console.log(_.now())// style.css body {background: #fff;font-size: 16px; }// config.js module.exports = {...module: {rules: [{test: /.css$/,use: [{ // use多個loaderloader: MiniCssExtractPlugin.loader}, 'css-loader']}]},plugins: [new MiniCssExtractPlugin({filename: '[name].[chunkhash:8].css'})] }build之后,我們可以發現css從js包中分離出來。
ok。看是非常完美,但是這里有個問題。css chunk和app chunk的hash是一樣的,這就會導致他們兩個任何一方的的改動,都會影響到另一方的hash,我們來看看。
先任意改動css內樣式內容,樣式中字體大小改成32px,導致了app chunk的hash發生變化了,build。
再次改動js內容內容,build。
果然,如我們預期的想法一致。那這里我們就要將這兩個chunk的區分開來,自身的改動不影響其他chunk。
這里,webpack4.3.0以上給我們提供了另一個生成hash的配置,contenthash。
- [contenthash]:The hash of the content(only)(webpack > 4.3.0)
contenthash的意思是hash的生成,是基于自身內容來創建的。修改一下config的配置。
module.exports = {...output: {filename: '[name].[contenthash:8].js'},module: {rules: [{test: /.css$/,use: [{ // use多個loaderloader: MiniCssExtractPlugin.loader}, 'css-loader']}]},plugins: [new MiniCssExtractPlugin({filename: '[name].[contenthash:8].css'})] }再次build。
誒!css chunk和app chunk的hash因為是基于各自內容的生成的,現在他們hash是不一樣的。修改任何一方都不會影響另外一方。
改動css。
改動js。
到目前位置,穩定hash的工作我們就完成一大半了,接續來繼續看看。
4. 【按需加載js】按需加載js的場景很常見,這可以提高網頁首屏速度。這里我們使用import函數來實現按需加載能力。
import _ from 'lodash' import './style.css' import $ from './helper'import('./test').then(res => {console.log(2, res) })console.log($)console.log(_.now())// config.js module.exports = {output: {filename: '[name].[contenthash:8].js',chunkFilename: '[name].[contenthash:8].js' // 用于動態加載的chunk} }build。
我們看到,按需加載的js的name是其chunk的id,而不是我們配置中的name值。這里我們可以在引用的時候,使用magic comment來實現name值的生成。
import(/* webpackChunkName: "test" */'./test').then(res => {console.log(2, res) })再次build。
ok,name值固定了。但是,又引來了新的問題。業務js的hash和vendors的hash又發生變化了,我們先來看業務js(app.*8ac.js和app.**6ab.js)的差別在哪里。
兩處不同。我們在使用magic comment生成了以name為命名的按需加載文件,以及chunkid的變化(runtime 代碼)
webpack4默認情況下,是使用自增id來作為每個模塊的標志的。按需加載的id一旦改變就導致引用都變化了。所以我們這里可以使用webpack4提供的moduleIds字段,以及runtimeChunk字段,將自增id替換成hash的方式,同時將webpack的 runtime代碼抽離成獨立的chunk。
module.exports = {...optimization: {moduleIds: 'hashed',runtimeChunk: true,splitChunks: {chunks: 'all'}} }build。
還是原來的套路。只改變css,build。只有css變化。
改變業務js,build。只有業務js變化。
改變按需加載的js,build。只有runtime和test的chunk發生變化。完美。
由于runtime內部引用了按需加載js的hash,所以會導致runtime的hash發生變化。
5. 【多入口】上面我們說明了單入口引入前端資源的變化,接下來我們嘗試下多入口下,前端資源的hash能否穩定。
// config.js module.exports = {entry: {app: './src/index',search: './src/search'} }// index.js import _ from 'lodash' import './style.css'import(/* webpackChunkName: "test" */'./test').then(res => {console.log(2, res) })console.log(_.now())// search.js import _ from 'lodash' import './style.css'import(/* webpackChunkName: "test" */'./test').then(res => {console.log(3, res) })console.log(_.add(2, 4))build。
build后,生成的文件符合我們的預期。
大家可以自行修改一下js、css文件,按照我們的配置。可以符合我們的預期,很好的穩定了hash。
6. 【本地公共庫】在基礎代碼層面,很多公司都會引入第三方庫來支持項目的開發,這些vendor不包含業務代碼邏輯。而在實際項目中,很多基礎庫實際上是需要我們自身開發人員開發完后,給組內或者公司內的的同學使用的,這種基礎庫的變化也比較少,但是又跟業務捆綁在一起,我們可以抽離成一個common chunk來單獨加載這個js,實現長期緩存。接下來我們引入一個30kb(webpack4認為,30kb以上的js抽離成單個chunk才有優化價值。為了分離出一個小于30kb的文件而多走一次HTTP請求,webpack4認為不合適。)以上的基礎庫試試。
// index.js和search.js都引入以下內容。 import $ from './helper' // jquery未壓縮源碼 console.log($)// config.js module.exports = {optimization: {moduleIds: 'hashed',runtimeChunk: true,splitChunks: {chunks: 'all',cacheGroups: {vendors: {test: /[/]node_modules[/]/,name: 'vendors'},commons: {test: /.js$/,minChunks: 2,name: 'commons'}}}} }除了css外,我們發現很多js的hash都發生變化了。由于我們改動了webpack配置文件,所以導致變化是正常的。一旦我們實現完整的配置后,配置文件的變化就相對比較小了。
可以按照我們的套路,嘗試改變各個文件,看下是否會影響到其他文件的hash。筆者測試過,是完全符合我們預期的。這里就不給大家貼截圖了。
總結
好了,webpack4長期緩存的的探索就到這里了。稍微總結下
以下,給出本次demo的全配置。
module.exports = {mode: 'development',entry: {app: './src/index',search: './src/search'},output: {filename: '[name].[contenthash:8].js',chunkFilename: '[name].[contenthash:8].js' // 用于動態加載的chunk},module: {rules: [{test: /.css$/,use: [{ // use多個loaderloader: MiniCssExtractPlugin.loader}, 'css-loader']}]},plugins: [new MiniCssExtractPlugin({filename: '[name].[contenthash:8].css'})],optimization: {moduleIds: 'hashed',runtimeChunk: true,splitChunks: {chunks: 'all',cacheGroups: {vendors: {test: /[/]node_modules[/]/,name: 'vendors'},commons: {test: /.js$/,minChunks: 2,name: 'commons'}}}} }最后,筆者文筆不好,技術不夠厲害,寫的不好的地方。輕噴。感謝大家啦。嘻嘻
參考文章
- 深入理解瀏覽器的緩存機制
- Use `[contenthash]` over `[hash]` and `[chunkhash]` for better long term caching
- 【譯】webpack 4: Code Splitting
- 基于webpack4[.3+]構建可預測的持久化緩存方案
- webpack4可預測持久化緩存方案探索
總結
以上是生活随笔為你收集整理的vue-cli 没有build如何配置_webpack4配置实现浏览器长期缓存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu怎么打中文_记录一下我在笔记
- 下一篇: 详细设计 存储分配_万字长文:云架构设计