React工程化之PWA之serviceWorker
我们使用create-react-app脚手架时,会发现入口文件index.js中有这样一个引入使用。
这个文件可以视情况用或者不用,它是用来做离线缓存等任务的,实际上就是为react项目注册了一个service worker
。这样的话,如果在线上,只要访问过一次该网站,以后即使没有网络也可以访问(此时使用的是之前缓存的资源)。只在生产环境中有效(process.env.NODE_ENV === ‘production’)
在项目的public目录下,会有一个manifest.json文件,可以在这里做一些配置(图标、名字等等)。当用户把网页生成一个快捷方式时。会感觉用起来像原生APP一样。
源码分析
接下来我们来分析一下源码:看看其实现原理,代码很少也就100多行。该文件主要导出两个方法:
register():注册
register
方法是默认(default
)的导出方法。
export default function register() {
//当前环境是生产环境并且浏览器支持serverWorker
// The URL constructor is available in all browsers that support SW.
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
//返回一个新的URL,作为生成静态文件夹的路径,下面说明...
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
//如果静态文件和当前环境不在同一个域,注册没有意义,那就直接返回。
if (publicUrl.origin !== window.location.origin) {
return;
}
window.addEventListener('load', () => {
//页面加载完成执行以下代码....
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// 如果是本地环境,调用checkValidServiceWorker进行注册
checkValidServiceWorker(swUrl);
// 注册成功后,打印信息
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://goo.gl/SC7cgQ'
);
});
} else {
// 如果不是本地环境(已经暴露在外网)仅仅只注册 service worker
registerValidSW(swUrl);
}
});
}
}
new URL(process.env.PUBLIC_URL, window.location)
,如果第一个参数是绝对路径,第二个参数忽略。如果第一个参数是相对路径,就会把第一个参数拼接到第二个参数后面。这里第一个参数是public
目录的绝对路径。
unregister():取消注册
export function unregister() {
//如果支持serviceWorker,并且处于就绪状态,那么调用其提供的取消注册方法
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
补充:
一个文件中可以用多个export方法,但是只能有一个export default 方法。在使用import导入的时候,可以为export default导出的方法随意起名字且不用使用{}。普通导出的方法则反之,只能用{}并且使用导出时的名字。(可以这样说,用{}就必须使用导出时的名字)
如果按以下写法也是可以的,因为可以随意取名字。
import haha from './registerServiceWorker';
haha();
如果想使用普通导出方法,就需要加上{}了,而且名字必须和导出时的名字一样。
import {unregister} from './registerServiceWorker';
unregister();
然后就是一些内部方法了,在上面的方法中有调用:
isLocalhost:判断是不是本地环境
这其实是一个布尔值,通过匹配当前地址段判断当前是不是本地环境。
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
registerValidSW:注册有效serviceWorker
function registerValidSW(swUrl) {
//注册有效的serviceWorker,然后使用提供的API进行操作
navigator.serviceWorker
.register(swUrl)
.then(registration => {
//如果内容有更新,就会自动进行安装
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
//安装之后判断安装状态进行提示
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
console.log('New content is available; please refresh.');
} else {
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
checkValidServiceWorker:检测serviceWorker状态
function checkValidServiceWorker(swUrl) {
// 向服务端申请资源
fetch(swUrl)
.then(response => {
// 如果连接失败或者没有返回js
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// 那么当 service worker 状态就绪的时候取消其注册状态
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
//并且重新加载页面
window.location.reload();
});
});
} else {
// 如果申请到资源,那么就调用 registerValidSW 方法来进行加载
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
.