preface

Recently, I found that every time when a major version goes live, I always have to manually notify users in groups or channels to manually refresh the page and clear the browser cache. Controllable when the user base is not large. Once the user base has doubled, it is not easy to inform each user. Therefore, after the launch of the new version, can it take the initiative to prompt the user to update the system and ask him to take the initiative to refresh like the APP? The answer is yes.

Train of thought

  • The file name is added to the hash when webPack is packaged
  • The system checks whether the current hash value is consistent with the latest version. If no, the system prompts the user that a new version exists.

implementation

1. Add a hash value to the file

  • Add the following configuration to config-overrides. Js using the React-app-rewired plug-in
module.exports = function (config, env) { ... Config. Output = {filename: 'bundle.[hash].js', path: path.resolve(__dirname, 'build'),}; } return config;Copy the code
  • If you are building your own webPack project, add the following configuration directly to webpack.config.js
module.exports = { ... Output: {filename: 'bundle.[hash].js', path: path.resolve(__dirname, 'dist')},... (Omit other configuration)Copy the code

But with the hash value. Every time you change the source code, you compile a new hash file, and you end up with a lot of useless, outdated files in your build. Manually delete each time. More vexed. You can clean it up using the clean-webpack-plugin.

2. Check whether the current hash value is consistent with that of the latest version

  • Get the current hash
/** Get the current hash */ const getCurrentHash = useCallback(() => {const curHashSrc = document.getelementSByTagName ('body')[0] .getElementsByTagName('script')[0] .src.split('/'); curHash.current = curHashSrc[curHashSrc.length - 1].split('.')[1]; } []);Copy the code
  • Get the latest Hash
*/ const fetchNewHash = useCallback(async () => {// const fetchNewHash = useCallback const timestamp = new Date().getTime(); const response = await axios.get(`${window.location.origin}? time=${timestamp}`); // Return a string that needs to be converted to HTML const el = document.createElement(' HTML '); el.innerHTML = response.data; // Get hash const newHashSrc = el.getelementsByTagName ('body')[0].getelementsByTagName ('script')[0].src.split('/'); const newHash = newHashSrc[newHashSrc.length - 1].split('.')[1]; console.log('fetchNewHash', curHash.current, newHash); if (newHash && newHash ! == curHash.current && ! Visible) {setVisible(true); } else if (newHash && newHash === curHash.current && visible) { setVisible(false); } }, [visible]);Copy the code

At this point you might wonder, at what point do you trigger detection. My idea is to encapsulate it as a component and mount it under the root node of the project. When the project is initialized, an update is triggered and polling is enabled (half an hour/an hour) to clear the polling if the user leaves or the screen goes off. Lighting up the screen or switching back to the TAB on which the web page is located triggers detection and polling is enabled.

3. Prompt the user to update

After the user clicks refresh, the browser automatically refreshes and closes the prompt.

Const handleConfirm = useCallback(() => {setVisible(false); setTrackerUserId(); window.location.reload(); } []);Copy the code

Write in the last

One drawback of this approach is that the scalability of obtaining hash values is not high (recommended solution 2). It should be used according to the actual situation of different projects. Before doing this, I found a similar plugin @femessage/update-popup on the Internet, but I couldn’t customize it.

  • The complete code
import React, { useState, useRef, useEffect, useCallback, useMemo, memo } from 'react'; import axios from 'axios'; import LionetConfirm from '@/shared/components/LionetConfirm'; import { setTrackerUserId } from './Tracker'; /** Detecting version updates */ const VersionUpdateToast = () => {/** Current hash value */ const curHash = useRef<any>(null); /** timer */ const timer = useRef<any>(null); const [visible, setVisible] = useState(false); Const pollingTime = useMemo(() => {return 30 * 60 * 1000; } []); Const fetchNewHash = useCallback(async () => {const fetchNewHash = useCallback(async () => timestamp = new Date().getTime(); const response = await axios.get(`${window.location.origin}? time=${timestamp}`); // Return a string that needs to be converted to HTML const el = document.createElement(' HTML '); el.innerHTML = response.data; // Get hash const newHashSrc = el.getelementsByTagName ('body')[0].getelementsByTagName ('script')[0].src.split('/'); const newHash = newHashSrc[newHashSrc.length - 1].split('.')[1]; console.log('fetchNewHash', curHash.current, newHash); if (newHash && newHash ! == curHash.current && ! Visible) {setVisible(true); } else if (newHash && newHash === curHash.current && visible) { setVisible(false); } }, [visible]); /** Start timer */ const setTimer = useCallback(() => {timer.current = setInterval(fetchNewHash, pollingTime); }, [fetchNewHash, pollingTime]); / / const clearTimer = useCallback(() => {if (timer.current) {clearInterval(timer.current); timer.current = null; }} []); /** Get the current hash */ const getCurrentHash = useCallback(() => {const curHashSrc = document.getelementSByTagName ('body')[0] .getElementsByTagName('script')[0] .src.split('/'); // eslint-disable-next-line prefer-destructuring curHash.current = curHashSrc[curHashSrc.length - 1].split('.')[1]; } []); /** getHash */ const getHash = useCallback(() => {getCurrentHash(); fetchNewHash(); }, [fetchNewHash, getCurrentHash]); // const onVisibilityChange = useCallback(() => {// eslint-disable-next-line prefer-destructuring if (! document.hidden) { setTimer(); getHash(); } else { clearTimer(); } }, [clearTimer, getHash, setTimer]); useEffect(() => { getHash(); setTimer(); return () => { clearTimer(); }; } []); useEffect(() => { document.addEventListener('visibilitychange', onVisibilityChange); return () => { document.removeEventListener('visibilitychange', onVisibilityChange); }; } []); Const handleConfirm = useCallback(() => {setVisible(false); setTrackerUserId(); window.location.reload(); } []); Return (<LionetConfirm visible={visible} isShowCancelBtn={false} confirmText=" refresh "onConfirm={handleConfirm} > <span> </LionetConfirm>); }; export default memo(VersionUpdateToast);Copy the code