前言
最近公司我的项目迭代逐步放缓,下班时间逐步变早,所以本着渐进减少的理念,在上班后,将公司我的项目进行了一下 PWA 革新
为何要革新成 PWA
- 用户需要。咱们的用户有许多电脑小白,不想记网址,又不会应用浏览器的珍藏性能。以前应用的同类软件都有桌面版,有一种感觉桌面版比网页版牢靠,应用简略的错觉,曾多次在钉钉售后群里反映,如何将网页保留至桌面,不便他下次间接在桌面关上
- PWA 是渐进式的,如果用户的浏览器不反对 ServiceWorker 等构建 PWA 所需的 API,并不会对其造成应用上的影响,并且通过埋点平台获知,咱们用户 Chrome 浏览器数量占到 80% 左右
- 离线缓存,可装置,可拦挡 fetch 等性能,对我集体有肯定的吸引力,心愿学习应用
开始革新
为了疾速革新成 PWA, 我这里抉择应用了谷歌推出的 PWA 工具库 workbox, 并且联合 webpack 创立 serviceWorker 文件
装置依赖
npm install --save-dev workbox-webpack-plugin
npm install --save workbox-core workbox-routing workbox-strategies workbox-precaching workbox-expiration workbox-cacheable-response
workbox-webpack-plugin 里提供了两种插件,GenerateSW
以及InjectManifest
。
GenerateSW
GenerateSW
插件能够通过配置间接编译生成对应的 serviceWorker 文件,不须要咱们间接编写 serviceWorker 文件。应用形式大抵如下:
import {InjectManifest} from 'workbox-webpack-plugin';
new GenerateSW({
skipWaiting: true,
clientsClaim: true,
mode: 'development',
runtimeCaching: [
{
urlPattern: /^https?\:\/\/.+?\.alicdn.com\/.+$/,
handler: 'StaleWhileRevalidate'
},
],
});
通过 GenerateSW
编译生成 serviceWorker
文件尽管简略,但不够灵便,所以实际上我应用了另一个 InjectManifestPlugin
插件
InjectManifest
InjectManifest
次要做了两件事
- 将 webpack 编译生成的资源文件清单,以变量
self.__WB_MANIFEST
的模式注入到咱们提供的serviceWorker
模板文件中 - 编译咱们提供的模板文件,生成指标
serviceWorker
文件
应用形式大抵如下:
const {InjectManifest} = require('workbox-webpack-plugin');
new InjectManifest({swSrc: path.resolve('src/sw.js'),
swDest: path.resolve(BUILD_DEST, 'sw.js'),
}),
编写 serviceWorker 模板文件
预缓存动态资源
预缓存会在 serviceWork 激活后,立刻申请并缓存所有预缓存清单中的文件, 之后下载申请同一资源时,会应用缓存优先策略,优先应用曾经预缓存的资源
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
路由申请缓存
- 应用 NavigationRoute 来缓存 html 文件
registerRoute(
new NavigationRoute(
new NetworkFirst({
cacheName: 'navigation-cache',
plugins: [
new CacheableResponsePlugin({statuses: [200]
}),
new ExpirationPlugin({
maxEntries: 300,
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
}),
),
);
- 缓存本地动态资源文件
registerRoute(/\.(css|js|png|jpg|jpeg|svg|webp)$/,
new CacheFirst({
cacheName: 'static-cache',
plugins: [
new CacheableResponsePlugin({statuses: [200]
}),
new ExpirationPlugin({
maxEntries: 300,
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
}),
);
- 缓存 cdn 中的动态资源文件
registerRoute(/^https?\:\/\/.*?\.alicdn.com\/.+?\.(css|js|png|jpg|jpeg|svg|gif|webp)$/,
new CacheFirst({
cacheName: 'alicdn-cache',
plugins: [
new CacheableResponsePlugin({statuses: [200]
}),
new ExpirationPlugin({
maxEntries: 300,
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
}),
);
这里有一个须要留神的点,alicdn 动态资源与我司网页域名不是同域名,存在跨域,当申请动态资源的时候,会返回不通明响应(opaque response); 当咱们应用 Cache-First 策略缓存不通明响应时,workbox 会提醒咱们不要应用这个策略来缓存不通明响应,因为不通明响应对 JavaScript 来说是一个黑盒,无奈获取到正确的 status code, headers, body, 所以咱们缓存中的资源是不牢靠的; 并且当咱们缓存不通明响应时,缓存所占有的空间远大于理论资源的大小,容易造成DOMException: Quota exceeded.
所以须要解决下不通明响应的缓存
不通明响应变成通明响应
既然不通明响应会造成问题,那只有把不通明响应变成通明响应,那就应该没问题了。
通过查看,我发现 alicdn 的响应头会返回access-control-allow-origin: *
, 后端是反对 cors 跨域资源共享的。既然如此,只有当咱们申请动态资源的时候,让申请走 cors 应该就能够了。于是,我尝试在其中一个 img 标签中,启用 cors
<img crossorigin="anonymous" />
不通明响应胜利变成通明响应。但如果给所有 <img /><script /><link />
标签增加 crossorigin, 这工作量也太大了。有没有对立解决的办法呢?有。能够通过拦挡 fetch 申请来对立解决, 在应用 workbox 的场景下,能够通过设置缓存策略类中 fetchOptions 来实现
registerRoute(/^https?\:\/\/.*?\.alicdn.com\/.+?\.(css|js|png|jpg|jpeg|svg|gif|webp)$/,
new CacheFirst({
cacheName: 'alicdn-cache',
plugins: [
new CacheableResponsePlugin({statuses: [200]
}),
new ExpirationPlugin({
maxEntries: 300,
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
// 增加如下 fetch options
fetchOptions: {
mode: 'cors',
credentials: 'omit',
},
}),
);
创立 manifest.json 文件
通过 manifest 配置文件,能够指定 pwa 利用的图标,初始页面,背景色,主题色,显示模式等内容
// manifest.json
{
"name": "xxx",
"short_name": "xxx",
"icons": [
{
"src": "/static/images/favicon@144x144.png",
"sizes": "144x144",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#000",
"theme_color": "#000"
}
<link rel="manifest" href="/manifest.json">
结语
最初,咱们的 PWA 利用革新就实现了。PWA 技术是一系列技术的汇合,这里,我只用到了 serviceWorker, manifest,push/notification 等没有波及到,如果日后有这个必要,再减少相应性能
延长扩大
什么是不通明响应(opaque response)
简略的说,不通明响应就是当咱们应用 fetch,并且设置no-cors
,来申请跨域资源时获取到的响应
fetch('https://www.baidu.com/img/flexible/logo/pc/result@2.png', {mode: 'no-cors'}).then(response => {return console.log(response)
}).catch(error => {return console.log(error)
});
打印的后果为
Response {
body: null
bodyUsed: false
headers: {},
ok: false
redirected: false
status: 0
statusText: ""type:"opaque"url:""
}
从 Response 中,咱们能够发现不通明响应
- status 为 0,而非 200 等 http status code
- statusText 为空
- headers 也为空
- body 也为空
总之,咱们 (JavaScript) 获取不到这个 Response 中的内容