使用workbox构建pwa应用配置
1.webpack项目引入workbox插件:workbox-webpack-plugin
const WorkBoxPlugin = require('workbox-webpack-plugin')
module.exports = {
// ...
plugins: [
// ...
// swSrc: 自定义部分sw文件
// swDest: 生成sw目标文件,将会用swSrc的sw文件作为模板注入
// 静态资源预缓存列表匹配,配置视情况而定
new WorkBoxPlugin.InjectManifest({
swSrc: path.resolve(__dirname, 'src/service-worker.js'),
swDest: path.resolve(BUILD_PATH, 'service-worker.js'),
include: [/\.(?:js|css)$/],
}),
]
}
2.设置缓存策略: src/service-worker.js
// offline ga init
workbox.googleAnalytics.initialize();
// 引入预缓存列表(precacheManifest 是workbox自动生成的)
workbox.precaching.precacheAndRoute(
self.__precacheManifest || [], {
ignoreUrlParametersMatching: [/./],
cleanUrls: false,
}
);
// 本地图片
workbox.routing.registerRoute(
new RegExp('/static/img/'),
workbox.strategies.cacheFirst({
// Use a custom cache name
cacheName: 'static-img-cache',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
// Cache only 50 images
maxEntries: 50,
})
],
})
)
// CDN图片缓存
workbox.routing.registerRoute(
new RegExp('^https://d1vs5f.XXXX.net/'),
workbox.strategies.cacheFirst({
cacheName: 'cdn-images-cache',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
maxEntries: 300,
// Cache for a maximum of a week
maxAgeSeconds: 7 * 24 * 60 * 60,
})
]
}),
)
// workbox-cdn
workbox.routing.registerRoute(
new RegExp('^https://storage.googleapis.com/workbox-cdn/releases/'),
workbox.strategies.cacheFirst({
cacheName: 'workbox-cdn',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
maxEntries: 10
})
]
})
)
// iconfont
workbox.routing.registerRoute(
new RegExp('^https://m.cfcdn.club/static/icons'),
workbox.strategies.cacheFirst({
cacheName: 'icons-cache',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
maxEntries: 50
})
],
})
)
// cache google analytics js
workbox.routing.registerRoute(
new RegExp('^https://www.google-analytics.com/'),
workbox.strategies.cacheFirst({
cacheName: 'google-analytics',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
maxEntries: 2
})
]
})
)
// cache facebook pixel js
workbox.routing.registerRoute(
new RegExp('^https://connect.facebook.net/'),
workbox.strategies.cacheFirst({
cacheName: 'facebook-pixel',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
maxEntries: 2
})
]
})
)
// 其他静态资源:cacheFirst
workbox.routing.registerRoute(
new RegExp('/static/'),
workbox.strategies.cacheFirst({
cacheName: 'server-static',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
maxEntries: 50
})
]
})
)
// get接口请求: networkFirst
workbox.routing.registerRoute(
new RegExp('/api/'),
workbox.strategies.networkFirst({
cacheName: 'api-cache',
})
)
// html: networkFirst
const routerName = ["home","product","payment","account","service","orders","order","categories","cart","size","search","reviews","cod","trackinginfo","forgot","resetpassword"];
workbox.routing.registerRoute(
// new RegExp('/')
new RegExp('/('+routerName.join("|")+')(?!(.html|.html?.*))'),
workbox.strategies.networkFirst({
cacheName: 'url-cache',
})
)
3.注册:serviceWorker
// 这个文件写到index.js
(function () {
'use strict';
// Check to make sure service workers are supported in the current browser,
// and that the current page is accessed from a secure origin. Using a
// service worker from an insecure origin will trigger JS console errors.
var 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}$/
)
);
window.addEventListener('load', function () {
if ('serviceWorker' in navigator &&
(window.location.protocol === 'https:' || isLocalhost)) {
navigator.serviceWorker.register('/service-worker.js')
.then(function (registration) {
// updatefound is fired if service-worker.js changes.
registration.onupdatefound = function () {
// updatefound is also fired the very first time the SW is installed,
// and there's no need to prompt for a reload at that point.
// So check here to see if the page is already controlled,
// i.e. whether there's an existing service worker.
if (navigator.serviceWorker.controller) {
// The updatefound event implies that registration.installing is set
var installingWorker = registration.installing;
installingWorker.onstatechange = function () {
switch (installingWorker.state) {
case 'installed':
// At this point, the old content will have been purged and the
// fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in the page's interface.
break;
case 'redundant':
throw new Error('The installing ' +
'service worker became redundant.');
default:
// Ignore
}
};
}
};
}).catch(function (e) {
console.error('Error during service worker registration:', e);
});
}
});
window.addEventListener('beforeinstallprompt', function (event) {
event.userChoice.then(choiceResult => {
// add to homescreen report ga
window.ga('send', 'event', 'add2home', choiceResult.outcome)
})
})
})();
--------------------------------------
// webpack
function loadMinified = (filePath) {
const code = fs.readFileSync(filePath, 'utf-8')
const result = UglifyJS.minify(code)
if (result.error) return ''
return result.code
}
...
new HtmlWebpackPlugin({
filename: entryIndex,
template: 'index.html',
serviceWorkerLoader: `<script>${loadMinified(path.join(__dirname,
'./service-worker-prod.js'))}</script>`,
...
}),
....
4. 增加一些离线提示
componentDidMount () {
// 判断是否离线
if (window.navigator && window.navigator.onLine === false) {
this.$toast('兄弟,你当前是离线状态')
}
window.addEventListener('online', () => {
this.$toast('嘿,上线了,请刷新页面')
}, false)
window.addEventListener('offline', () => {
this.$toast('兄弟,你当前是离线状态')
}, false)
}
5. 添加 manifest文件
// webpack
const AppManifestWebpackPlugin = require('app-manifest-webpack-plugin')
...
plugins: [
...
new AppManifestWebpackPlugin({
logo: path.resolve(__dirname, '../static/img/logo.png'),
persistentCache: false,
prefix: config.cdn,
output: '/static/icons-' + config.iconVersion + '-[hash:8]/',
config: {
appName: 'My PWA Site',
shortName: 'MY Site',
scope: config.host,
start_url: url.resolve(config.host, "/?utm_source=homescreen"),
display: "standalone",
orientation: "portrait",
background_color: "#FFFFFF",
theme_color: "#FFFFFF",
version: '1.0',
}
}),
]
...