<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端重新部署如何使用WebWorker優(yōu)雅地通知用戶刷新網(wǎng)頁?

          共 12818字,需瀏覽 26分鐘

           ·

          2024-04-11 07:08

          本文對(duì)部署后通知用戶刷新網(wǎng)頁感興趣的小伙伴閱讀。

          歡迎關(guān)注 前端早茶 ,與廣東靚仔攜手共同進(jìn)階~

          文章轉(zhuǎn)載于:Zayn

          https://juejin.cn/post/7329280514628534313

          前言

          周五晚上組里說前端有bug,正在吃宵夜的我眉頭一緊,立即打開了釘釘(手賤...),看了一下這不是前幾天剛解決的嗎,果然,使用刷新大法就解決,原因不過是用戶一直停留在頁面上,新的版本發(fā)布后,沒有刷新拿不到新的資源。現(xiàn)在大部分的前端系統(tǒng)都是SPA,用戶在使用中對(duì)系統(tǒng)更新無感知,切換菜單等并不能獲取最新資源,如果前端是覆蓋性部署,切換菜單請(qǐng)求舊資源,這個(gè)舊資源已經(jīng)被覆蓋(hash打包的文件),還會(huì)出現(xiàn)一直無響應(yīng)的情況。那么,當(dāng)前端部署更新后,提示一直停留在系統(tǒng)中的用戶刷新系統(tǒng)很有必要。

          解決方案

          1. 在public文件夾下加入manifest.json文件,記錄版本信息

          2. 前端打包的時(shí)候向manifest.json寫入當(dāng)前時(shí)間戳信息

          3. 在入口JS引入檢查更新的邏輯,有更新則提示更新

          • 路由守衛(wèi)router.beforeResolve(Vue-Router為例),檢查更新,對(duì)比manifest.json文件的響應(yīng)頭Etag判斷是否有更新
          • 通過Worker輪詢,檢查更新,對(duì)比manifest.json文件的響應(yīng)頭Etag判斷是否有更新。當(dāng)然你如果不在乎這點(diǎn)點(diǎn)開銷,可不使用Worker另開一個(gè)線程

          Public下的加入manifest.json文件

                
                {
            "timestamp":1706518420707,
            "msg":"更新內(nèi)容如下:\n--1.添加系統(tǒng)更新提示機(jī)制"
          }
          這里如果是不向用戶提示更新內(nèi)容,可不填,前段開發(fā)者也無需維護(hù)manifest.json的msg內(nèi)容,這里主要考慮到如果用戶在填長表單的時(shí)候,填了一大半,你這時(shí)候給用戶彈個(gè)更新提示,用戶無法判斷是否影響當(dāng)前表單填寫提交,如果將更新信息展示出來,用戶感知更新內(nèi)容,可判斷是否需要立即刷新,還是提交完表單再刷新。

          webpack向manifest.json寫入當(dāng)前時(shí)間戳信息

                
                 // 版本號(hào)文件
              const filePath = path.resolve(`./public`'manifest.json')
              // 讀取文件內(nèi)容
              readFile(filePath, 'utf8', (err, data) => {
                if (err) {
                  console.error('讀取文件時(shí)出錯(cuò):', err)
                  return
                }
                // 將文件內(nèi)容轉(zhuǎn)換JSON
                const dataObj = JSON.parse(data)
                dataObj.timestamp = new Date().getTime()
                // 將修改后的內(nèi)容寫回文件
                writeFile(filePath, JSON.stringify(dataObj), 'utf8', err => {
                  if (err) {
                    console.error('寫入文件時(shí)出錯(cuò):', err)
                    return
                  }
                })
              })
          如果你無需維護(hù)更新內(nèi)容的話,可直接寫入timestamp
                
                // 生成版本號(hào)文件
          const filePath = path.resolve(`./public`'manifest.json')
          writeFileSync(filePath, `${JSON.stringify({ timestamp: new Date().getTime() })}`)

          檢查更新的邏輯

          入口文件main.js處引入

          我這里檢查更新的文件是放在utils/checkUpdate

                
                // 檢查版本更新
          import '@/utils/checkUpdate'
          checkUpdate文件內(nèi)容如下
                
                import router from '@/router'
          import { Modal } from 'ant-design-vue'
          if (process.env.NODE_ENV === 'production') {
            let lastEtag = ''
            let hasUpdate = false
            let worker = null

            async function checkUpdate() {
              try {
                // 檢測前端資源是否有更新
                let response = await fetch(`/manifest.json?v=${Date.now()}`, {
                  method'head'
                })
                // 獲取最新的etag
                let etag = response.headers.get('etag')
                hasUpdate = lastEtag && etag !== lastEtag
                lastEtag = etag
              } catch (e) {
                return Promise.reject(e)
              }
            }

            async function confirmReload(msg = '', lastEtag{
              worker &&
                worker.postMessage({
                  type'pause'
                })
              try {
                Modal.confirm({
                  title'溫馨提示',
                  content'系統(tǒng)后臺(tái)有更新,請(qǐng)點(diǎn)擊“立即刷新”刷新頁面\n' + msg,
                  okText'立即刷新',
                  cancelText'5分鐘后提示我',
                  onOk() {
                    worker.postMessage({
                      type'destroy'
                    })
                    location.reload()
                  },
                  onCancel() {
                    worker &&
                      worker.postMessage({
                        type'recheck',
                        lastEtag: lastEtag
                      })
                  }
                })
              } catch (e) {}
            }

            // 路由攔截
            router.beforeResolve(async (to, from, next) => {
              next()
              try {
                await checkUpdate()
                if (hasUpdate) {
                  worker.postMessage({
                    type'destroy'
                  })
                  location.reload()
                }
              } catch (e) {}
            })

            // 利用worker輪詢
            worker = new Worker(
              /* webpackChunkName: "checkUpdate.worker" */ new URL('../worker/checkUpdate.worker.js'import.meta.url)
            )

            worker.postMessage({
              type'check'
            })
            worker.onmessage = ({ data }) => {
              if (data.type === 'hasUpdate') {
                hasUpdate = true
                confirmReload(data.msg, data.lastEtag)
              }
            }
          }

          這里因?yàn)槿睋Q路由本來就要刷新頁面,用戶可無需感知系統(tǒng)更新信息,直接通過請(qǐng)求頭的Etag即可,這里的Fetch方法就用head獲取相應(yīng)頭就好了。

          checkUpdate.worker.js文件如下

                
                let lastEtag
          let hasUpdate = false
          let intervalId = ''
          async function checkUpdate() {
            try {
              // 檢測前端資源是否有更新
              let response = await fetch(`/manifest.json?v=${Date.now()}`, {
                method'get'
              })
              // 獲取最新的etag和data
              let etag = response.headers.get('etag')
              let data = await response.json()
              hasUpdate = lastEtag !== undefined && etag !== lastEtag
              if (hasUpdate) {
                postMessage({
                  type'hasUpdate',
                  msg: data.msg,
                  lastEtag: lastEtag,
                  etag: etag
                })
              }
              lastEtag = etag
            } catch (e) {
              return Promise.reject(e)
            }
          }

          // 監(jiān)聽主線程發(fā)送過來的數(shù)據(jù)
          addEventListener('message', ({ data }) => {
            if (data.type === 'check') {
              // 每5分鐘執(zhí)行一次
              // 立即執(zhí)行一次,獲取最新的etag,避免在setInterval等待中系統(tǒng)更新,第一次獲取的etag是新的,但是lastEtag還是undefined,不滿足條件,錯(cuò)失刷新時(shí)機(jī)
              checkUpdate()
              intervalId = setInterval(checkUpdate,5 * 60 * 1000)
            }
            if (data.type === 'recheck') {
              // 每5分鐘執(zhí)行一次
              hasUpdate = false
              lastEtag = data.lastEtag
              intervalId = setInterval(checkUpdate,  5 * 60 * 1000)
            }
            if (data.type === 'pause') {
              clearInterval(intervalId)
            }
            if (data.type === 'destroy') {
              clearInterval(intervalId)
              close()
            }
          })
          如果不使用worker直接講輪詢邏輯放在checkUpdate即可

          Worker引入

          從 webpack 5 開始,你可以使用  Web Workers [1] 代替  `worker-loader` [2]。
                
                new Worker(new URL('./worker.js'import.meta.url));

          以下版本的就只能用`worker-loader`[3]咯

          也可以邏輯寫成字符串,然后通過ToURL給new Worker,如下:

                
                function createWorker(f{
            const blob = new Blob(['(' + f.toString() +')()'], {type"application/javascript"});
            const blobUrl = window.URL.createObjectURL(blob);
            const worker = new Worker(blobUrl);
            return worker;
          }

          createWorker(function () {
            self.addEventListener('message'function (event{
              // 消費(fèi)信息
                self.postMessage('send message')
            }, false);
          })

          worker數(shù)據(jù)通信

                
                // 主線程
          var uInt8Array = new Uint8Array(new ArrayBuffer(10));
          for (var i = 0; i < uInt8Array.length; ++i) {
            uInt8Array[i] = i * 2// [0, 2, 4, 6, 8,...]
          }
          worker.postMessage(uInt8Array);
          // Worker 線程
          self.onmessage = function (e{
            var uInt8Array = e.data;
            postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());
            postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
          };

          但是,拷貝方式發(fā)送二進(jìn)制數(shù)據(jù),會(huì)造成性能問題。比如,主線程向 Worker 發(fā)送一個(gè) 500MB 文件,默認(rèn)情況下瀏覽器會(huì)生成一個(gè)原文件的拷貝。為了解決這個(gè)問題,JavaScript 允許主線程把二進(jìn)制數(shù)據(jù)直接轉(zhuǎn)移給子線程,但是一旦轉(zhuǎn)移,主線程就無法再使用這些二進(jìn)制數(shù)據(jù)了,這是為了防止出現(xiàn)多個(gè)線程同時(shí)修改數(shù)據(jù)的麻煩局面。這種轉(zhuǎn)移數(shù)據(jù)的方法,叫做Transferable Objects[4]。這使得主線程可以快速把數(shù)據(jù)交給 Worker,對(duì)于影像處理、聲音處理、3D 運(yùn)算等就非常方便了,不會(huì)產(chǎn)生性能負(fù)擔(dān)。

          如果要直接轉(zhuǎn)移數(shù)據(jù)的控制權(quán),就要使用下面的寫法。

                
                // Transferable Objects 格式
          worker.postMessage(arrayBuffer, [arrayBuffer]);

          // 例子
          var ab = new ArrayBuffer(1);
          worker.postMessage(ab, [ab]);
          Web Worker 使用教程 - 阮一峰的網(wǎng)絡(luò)日志 (ruanyifeng.com) [5]然而,并不是所有的對(duì)象都可以被轉(zhuǎn)移。只有那些被設(shè)計(jì)為可轉(zhuǎn)移的對(duì)象(用[ Transferable ] IDL 擴(kuò)展屬性修飾),比如ArrayBuffer、MessagePort,ImageBitmap,OffscreenCanvas,才能通過這種方式來傳遞。轉(zhuǎn)移操作是不可逆的,一旦對(duì)象被轉(zhuǎn)移,原始上下文中的引用將不再有效。轉(zhuǎn)移對(duì)象可以顯著減少復(fù)制數(shù)據(jù)所需的時(shí)間和內(nèi)存。

          參考資料

          [1]

          https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FAPI%2FWeb_Workers_API%2FUsing_web_workers

          [2]

          https://github.com/webpack-contrib/worker-loader: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fwebpack-contrib%2Fworker-loader

          [3]

          https://github.com/webpack-contrib/worker-loader: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fwebpack-contrib%2Fworker-loader

          [4]

          http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#transferable-objects: https://link.juejin.cn?target=http%3A%2F%2Fwww.w3.org%2Fhtml%2Fwg%2Fdrafts%2Fhtml%2Fmaster%2Finfrastructure.html%23transferable-objects

          [5]

          https://www.ruanyifeng.com/blog/2018/07/web-worker.html: https://link.juejin.cn?target=https%3A%2F%2Fwww.ruanyifeng.com%2Fblog%2F2018%2F07%2Fweb-worker.html

          最后

          關(guān)注我,一起攜手進(jìn)階

          歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~

          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91在线无码精品秘 入口九色1 | 日韩欧美三级片在线观看 | 日本在线AⅤ | 大香蕉三级在线视频 | 高清无码电影 |