Open HolyZheng opened 5 years ago
上一篇文章中,我们了解到了create-react-app 给我们提供了哪些pwa支持,也了解到了有哪些不足。虽然create-react-app会帮我们自动生成一个service-worker.js 去缓存我们的app shell,但是并没有提供让开发者定制service worker的方法,除非我们eject项目,这篇文章继续往下讲,把在这个项目中学到的东西分享给大家。
这是一个移动端的pwa应用,使用react,typescript,react-redux,react-router,workbox 基于create-react-app 开发。可以添加到主屏幕,可以断网条件下正常打开和访问数据。项目地址:browseExpbyReact
typescript是JavaScript的超级,一方面在typescript中我们可以使用最新的特性,另一方面typescript给我们带来了类型系统,可以让我们写出健壮的代码,避免一些潜在的运行时错误。在create-react-app中使用typescript,官网推荐我们使用的是create-react-app的ts版本,他会帮你配置好typescript的相关配置,并使用react-script-ts代替react-script来驱动项目。但是这个版本的更新会稍稍滞后于原版,而且也不利于我们扩展脚手架的配置,所以这里不推荐使用。我们使用 react-app-rewired 来进行配置。
在create-react-app中修改默认配置有两种常用的方法,
这里到了本文的重点:如何在create-react-app中定制自己的service-worker.js。目前的cra引用了Workbox webpack plugin 代替了先前的 sw-precache-webpack-plugin。我们可以借助 react-app-rewired 去改写默认的Workbox webpack plugin 配置。主要步骤:
首先在 config.overrides.js 中配置,替换默认的workbox-webpack-plugin配置:
/* config-overrides.js */
// typescript的配置插件
const rewireTypescript = require('react-app-rewire-typescript');
const workboxPlugin = require('workbox-webpack-plugin')
const path = require('path')
module.exports = {
webpack: function (config, env) {
// typescript的配置插件
config = rewireTypescript(config, env);
if (env === 'production') {
// 在 ‘production’ 模式下加入自己的配置
const workboxConfigProd = {
swSrc: path.join(__dirname, 'public', 'cus-service-worker.js'),
swDest: 'cus-service-worker.js',
importWorkboxFrom: 'disabled'
}
// 删除默认的WorkboxWebpackPlugin配置
config = removePreWorkboxWebpackPluginConfig(config)
// 加入我们的配置
config.plugins.push(new workboxPlugin.InjectManifest(workboxConfigProd))
}
return config
}
}
// 此函数用来找出 默认配置中的 WorkboxWebpackPlugin, 并把它删除
function removePreWorkboxWebpackPluginConfig (config) {
const preWorkboxPluginIndex = config.plugins.findIndex((element) => {
return Object.getPrototypeOf(element).constructor.name === 'GenerateSW'
})
if (preWorkboxPluginIndex !== -1) {
config.plugins.splice(preWorkboxPluginIndex, 1)
}
return config
}
这部分的配置大概意思就是,当环境为生成环境时,找出webpack中关于workbox-webpack-plugin的配置,把它删掉,然后用自己的配置替代它。
这里解释一下 removePreWorkboxWebpackPluginConfig 这个函数。我们可以自己用create-react-app新建一个无用的项目,然后eject它,那么我们可以在暴露出来的config文件夹下的 webpack.config.prod.js 中看到关于 workbox-webpack-plugin 的配置
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: publicUrl + '/index.html',
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in
// public/ and not a SPA route
new RegExp('/[^/]+\\.[^/]+$'),
],
}),
所以我们可以通过下面这段代码找到这段配置的位置:
// 对plugins数组调用findIndex方法,找到构造函数的name属性为‘GenerateSW’的成员
const preWorkboxPluginIndex = config.plugins.findIndex((element) => {
return Object.getPrototypeOf(element).constructor.name === 'GenerateSW'
})
// 删除这个成员
if (preWorkboxPluginIndex !== -1) {
config.plugins.splice(preWorkboxPluginIndex, 1)
}
替换掉workbox-webpack-plugin的配置后,根据自己的配置在public目录下新建cus-service-worker.js文件,这个文件会代替默认生成的service-worker.js文件,我们就可以通过配置cus-service-worker.js来定制自己的pwa配置了,而且cus-service-worker.js 里的内容也是有讲究的,以本项目为例:
// 引入workbox全局变量
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.4.1/workbox-sw.js');
if (workbox) {
console.log(`Yay! Workbox is loaded 🎉`);
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
// set the prefix and suffix of our sw's name
workbox.core.setCacheNameDetails({
prefix: 'browse-exp',
suffix: 'v1.0.0',
});
// have our sw update and control a web page as soon as possible.
workbox.skipWaiting();
workbox.clientsClaim();
// 将静态资源进行预缓存
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
// 定制自己的需求
// cache our data, and use networkFirst strategy.
workbox.routing.registerRoute(
new RegExp('.*experiments\?.*'),
workbox.strategies.networkFirst()
);
workbox.routing.registerRoute(
new RegExp('.*experiments/\\d'),
workbox.strategies.networkFirst()
)
workbox.routing.registerRoute(
new RegExp('.*experiment_types.*'),
workbox.strategies.networkFirst()
)
首先通过importScripts 引入workbox全局变量。在打包的时候,脚手架会为我们生成一个 precache-manifest列表,里面会列举一系列的静态文件,我们可以通过 self.__precacheManifest 拿到这个列表, 所以我们需要通过一下语句预缓存这些静态资源:
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
然后就是为了尽快的让我们的service worker控制页面,我们可以在开头加入一下语句:
// 跳过等待
workbox.skipWaiting();
// 控制客户端
workbox.clientsClaim();
剩下的部分自己就可以按自己的需求进行发挥了,像要什么功能就配置什么功能,这里的话我为自己获取数据的路由进行了缓存,采用的是 networkFirst 策略,什么是networkFirst策略呢?就是首先会进行网络请求,如果失败的话再使用缓存中的数据。
当我们打包项目的时候,就会发现再build文件下,会生成一个cus-service-worker.js文件,并且再开头多了一句:
importScripts("/precache-manifest.cd8115bc0ff644d6d74bec08ffcbdeb4.js");
这就是我们可以通过 self.__precacheManifest 拿到预缓存列表的原因。
到目前为止:我们已经可以定制自己的service-worker.js了。
manifest.json可以让我们的web app添加到桌面,再create-react-app中配置manifest非常简单,直接再public目录下的manifest.json配置就可以了,关于什么么配置项,可以到这里谷歌官网教程查看,另外manifest.json的配置不会马上生效,需要在https协议下,多次进入该网页的时候才会弹出添加到桌面的提示。
到这里我们可以在create-react-app生成的脚手架中定制自己的pwa配置了,在下一篇文章中,我会继续讲解:
感兴趣的同学可以扫描下面二维码体验项目:
note:
- 建议用uc浏览器打开,因为uc浏览器对pwa的支持较好。
- "添加到桌面的提示" 需要短时间多次进入web app 才会触发
项目地址:browseExpByReact 如果感兴趣,可以对比着基于vue的实现来看: browseExpByVue
上一篇“记录一次基于vue、typescript、pwa的项目由开发到部署”,发布后,忙于秋招的我,终于有时间来写这篇文章。最近秋招也挺顺利,拿到了网易广州岗的offer,对目前想留在广州发展的我来说真是太合适不过了。在最近的反思中,楼主我也意识到了自己存在过于急功近利的毛病,前端是一个更新迭代很快的行业,最忌讳的就是浮躁的心态,所以楼主决定要好好调整自己,要有工匠精神和延时满足的意识。以后的文章也要好好调整,尽可能的写细写好。
下面进入正题,本篇文章介绍如何基于目前的create-react-app(目前的项目cra版本为2.0.3)利用workbox开发一款pwa,并将其发布到自己的服务器。
先来说一下项目,这次的项目和上一篇基于vue的项目是同一个项目,可以说是用react的重构,用途是用来浏览学校心理学院部分实验信息,因为楼主用vue较多,所以这个项目也是react的练手项目,为了多实践,项目中还用到了react-redux去进行状态管理(项目引入redux的目的只是为了实践),react-router进行路由控制。看一下效果:![bereactshow](https://raw.githubusercontent.com/HolyZheng/holyZheng-blog/master/images/browse-exp-react.gif)
可以看到,我们从手机桌面的一级入口,在断网的条件下,正常的打开了app,而且带有过渡动画,并正常的访问了实验数据,这就是pwa,拥有原生app般的体验,又拥有web的轻便。下面我们来看看,如何基于create-react-app2与workbox如何实现这个项目。
cra2 给我们带来了什么pwa特性?
我们的项目是基于 create-react-app 2 ,cra2支持了pwa,可以让我们可选的将我们的app 升级为 pwa。 关于cra2添加的新特性,可以在这里看what's new about cra2,首先我们利用cra2生成一个项目,:
可以看到在src文件目录下有一个serviceWorker.js文件,这个文件看起来很复杂,其实就是检查一下环境变量是否为“production”,且当前浏览器是否兼容service worker。 再来看一下我们的 index.js 文件,可以看到最后面有一句:
如果我们想要使用cra2提供的pwa特性的话,我们需要将 serviceWorker.unregister() 改为 serviceWorker.register(),然后我们打包我们的项目,在打包得到的build文件夹下我们可以看到生成了一个server-worker.js文件 和 preche-manifest.xxxxxxxxx.js 和一个manifest.json。 其中的server-worker.js中规定了我们如何缓存我们的资源,preche-manifest 中列出了我们的静态资源 利用上一篇关于vue-cli 和pwa 中提到的web server工具 web-server-for-chrome
我们把网断开,可以发现,我们的app依旧能正常打开。这就是目前的cra2(2.0.3)给我们提供的默认的pwa特性。我们可以选择是否去缓存我们的app shell,使得它可以在离线时正常的打开。
限制性
这足够了吗?很明显是不够的。在实际情况中,除了缓存我们的app shell,我们往往需要动态的对我们的路由和请求的数据进行缓存,这本来是一件很正常的事情,也就是说,我们需要去定制自己的servive worker去实现自己想要的功能。
但是在目前的cra2中,并没有提供这么一个方式让我们去定制自己的service-worker,除非你eject我们的项目,将所以的配置文件暴露出来,但是eject是一个不可逆的过程,将我们的配置文件暴露出来也是一个不优雅的做法。所以我们需要另开途径来实现我们的需求。
为什么 create-react-app 没提供定制service worker 的方法?
其实并不是cra2不提供相应的定制方法,目前cra2 也在准备实现相应的功能,我们可以看一下下面几个issue或request。
但是react.js的工作人员gaearon回应短期内没有此打算,未来或许会实现该想法,并关闭了此issue。
但是最终还是被create-react-app的合作作者Timer关闭了这个request,因为有开发者提出了更合理的实现方式![responseForRequest](https://raw.githubusercontent.com/HolyZheng/holyZheng-blog/master/images/request2.png)
从这些资料中,可以看到,在将来,我们或许就可以像在vue-cli3.0中一样定制自己service worker了,配置的方式便是在根目录下建立 cra.config.js文件,然后写入类似下面的配置代码:
这里不得不感叹一下react社区的力量。
现在怎么办?
既然目前还没有提供定制service worker的方法,那么我们应该怎么做呢?关于如何实现这个app:
楼主打算在下一篇文章中给大家介绍,因为楼主发现与其长篇大论把所有内容都放在同一篇文章,不如分开几篇细细的讲要有效果。
感兴趣可以扫描下面二维码体验项目:
项目地址:browseExpByReact 如果感兴趣,可以对比着基于vue的实现来看: browseExpByVue