[关闭]
@bornkiller 2017-10-13T10:59:50.000000Z 字数 3655 阅读 3084

React 开发 PWA 应用

React


前言

步入移动时代,web app 的发展却并不如意,网络速度,网络稳定些,用户体验等问题在移动端更为突出。PWA 的提出意在提升 web app 体验,降低部分场景开发原生应用的必要性。由于 PWA 关联概念较多,本文只讨论具体实施。

service worker

PWA 应用需要具备快速启动,离线可用特性,实现这两者,需要 service worker 支持,其本质可理解为运行在浏览器中的代理服务器。离线可用,需要实现 app shell 架构。所谓 app shell 定义,可以简单理解为:完全分割应用的静态资源与动态数据。对于一般应用,使用 SW 完全缓存静态资源,对于动态数据自定义离线策略.

首先在页面中注册来启动安装:

  1. if ('serviceWorker' in navigator) {
  2. window.addEventListener('load', function() {
  3. navigator.serviceWorker.register('/sw.js').then(function(registration) {
  4. // Registration was successful
  5. console.log('ServiceWorker registration successful with scope: ', registration.scope);
  6. }).catch(function(err) {
  7. // registration failed :(
  8. console.log('ServiceWorker registration failed: ', err);
  9. });
  10. });
  11. }

安装过程中,存储 app shell 关联资源:

  1. const CACHE_NAME = 'react-pwa-starter';
  2. const urlsToCache = [
  3. '/',
  4. '/styles/main.css',
  5. '/script/main.js'
  6. ];
  7. self.addEventListener('install', function(event) {
  8. // Perform install steps
  9. event.waitUntil(
  10. caches.open(CACHE_NAME)
  11. .then(function(cache) {
  12. return cache.addAll(urlsToCache);
  13. })
  14. );
  15. });

安装成功后,便是最重要的网络代理实现:

  1. self.addEventListener('fetch', function(event) {
  2. event.respondWith(
  3. caches.match(event.request)
  4. .then(function(response) {
  5. // Cache hit - return response
  6. if (response) {
  7. return response;
  8. }
  9. return fetch(event.request);
  10. }
  11. )
  12. );
  13. });

此处是所有步骤中难度最大的环节,代码实现较为复杂,且代理策略需要细细斟酌。本文中采用非常简单的代理策略:所有静态资源使用 cacheFirst,并使用 navigate proxy 提升入口 html 文件加载速度。

实际开发中,一般不建议手动处理 service worker 等文件,笔者采用 workbox 库,此库也是功能强大,细节很多,文档不全的典型,静态代理模式如下:

  1. const InjectServiceWorkerPlugin = require('webpack-plugin-inject-service-worker');
  2. const CopyPlugin = require('copy-webpack-plugin');
  3. const WorkboxPlugin = require('workbox-webpack-plugin');
  4. module.exports = {
  5. plugins: [
  6. Reflect.construct(InjectServiceWorkerPlugin, []),
  7. Reflect.construct(CopyPlugin, [[{
  8. from: 'node_modules/workbox-sw/build/importScripts/workbox-sw.prod.*',
  9. to: '[name].[ext]'
  10. }]]),
  11. Reflect.construct(WorkboxPlugin, [
  12. {
  13. globDirectory: './dist/client',
  14. globPatterns: ['**/*.{html,js,css,png,jpg}'],
  15. swSrc: './public/service-worker.js',
  16. swDest: './dist/client/service-worker.js'
  17. }
  18. ])
  19. ]
  20. };
  1. // Import workbox scripts
  2. importScripts('workbox-sw.prod.v2.0.0.js');
  3. // Construct Workbox
  4. const swWorkBox = new self.WorkboxSW({
  5. cacheId: 'react-pwa-starter',
  6. skipWaiting: true,
  7. clientsClaim: true
  8. });
  9. // Pre-cache static files
  10. swWorkBox.precache([]);
  11. // Register special strategy
  12. // Avoid static file fallback into navigate mode
  13. // Notice registerNavigationRoute will not cooperate with prerender default
  14. swWorkBox.router.registerNavigationRoute('/index.html', {
  15. blacklist: [/\.(js|css|jpe?g|png)$/i]
  16. });

也可单独使用 workbox-cli 处理,特别注意,如果配合 pre-render 策略,务必配置 templatedUrls 配置项:

  1. /**
  2. * @description - Workbox configuration
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. module.exports = {
  6. globDirectory: './dist/client',
  7. globPatterns: ['**/*.{html,js,css,png,jpg}'],
  8. swSrc: './public/service-worker.js',
  9. swDest: './dist/client/service-worker.js',
  10. templatedUrls: {
  11. '/search': ['./search/index.html'],
  12. '/review': ['./review/index.html'],
  13. '/gallery': ['./gallery/index.html']
  14. }
  15. };
  1. workbox inject:manifest --config-file workbox.config.js

manifest

利用 manifest.json 控制在用户想要看到应用的区域中如何向用户显示网络应用或网站,指示用户可以启动哪些功能,以及定义其在启动时的外观。https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/

同样利用 webpack plugin 自动生成关键文件。

  1. const WebpackPwaManifest = require('webpack-pwa-manifest');
  2. module.exports = {
  3. plugins: [
  4. Reflect.construct(WebpackPwaManifest, [
  5. {
  6. short_name: 'Baby',
  7. name: 'Blog promise for Carey baby',
  8. display: 'standalone',
  9. background_color: '#2196F3',
  10. theme_color: '#2196F3',
  11. start_url: '/index.html',
  12. ios: true,
  13. icons: [
  14. {
  15. src: path.resolve('public/android-icon.png'),
  16. sizes: [144, 196, 256, 512],
  17. destination: 'android'
  18. },
  19. {
  20. src: path.resolve('public/ios-icon.png'),
  21. sizes: [144, 196, 256, 512],
  22. ios: true,
  23. destination: 'ios'
  24. }
  25. ]
  26. }
  27. ])
  28. ]
  29. };

效果预览

线上地址:https://www.reverseflower.com
目前只是基本做到离线可用,首屏性能基本上是相当糟糕,作为后续优化点,后续进一步深入,使用更灵活的代理策略与预渲染提升首屏性能。

profile1.png-92.9kB
profile2.png-153.4kB

Contact

Email: hjj491229492@hotmail.com

qrcode_for_gh_d8efb59259e2_344.jpg-8.7kB

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注