随着时代的发展,出于对低成本开发与维护复杂跨平台应用的追求,前端技术正在突破浏览器束缚,开始向桌面、移动等领域扩张,尤其以 Electron、Cordova、ReactNative、Weex 等最为出名。这些混合应用开发架构方案的出现,虽然解决了传统 Web 无法与底层系统深层次交互、原生应用更新繁琐、应用开发维护成本过高等问题,但依旧无法满足用户零安装、零更新、用完即走的需求。当我们再次回顾Web技术发展史,能够满足上述需求的技术往往诞生于不经任何加工的传统技术中,如PWA (Progressive Web App)等。

什么是PWA(渐进式 Web 应用)?

PWA 指的是使用指定技术和标准模式来开发的 Web 应用,让Web应用具有原生应用的特性和体验。使用PWA可以保留原Web应用模式,经过简单的配置,便可直接通过网页访问应用程序。此外,如果用户选择安装应用程序,便可以通过桌面图标快速访问,应用所需的资源在用户第一次安装后便离线缓存在本地,版本升级将以推送的形式完成,无需再次安装。

PWA的发展现状

Google 的工程师于 2015 年提出 PWA(Progressive Web App) 架构,该架构在保持 Web 开放特性的前提下,让 Web 应用能够以渐进的形式撕掉浏览器的标签,最终抹平与原生应用的差异。PWA的技术特点包括:

  1. 可通过 Manifest 配置文件实现将应用添加到主屏幕,以解决应用入口依赖浏览器的问题。

  2. 借助 Service Worker、离线存储、后台同步等技术来提供离线处理能力。

  3. 通过推送通知、蓝牙、支付等新接口来突破浏览器限制,从而达到集成底层系统功能。

然而,即便PWA早在2018年便已迎来重大突破,全球顶级的浏览器厂商,如Google、Microsoft、Apple 已经全数宣布支持 PWA 技术,桌面和移动设备上的所有主流浏览器也都尽数支持,可PWA发展至今仍旧没有占据多少市场。难道,PWA真的没有未来了吗?

PWA的应用场景

从实际工作场景出发,我们在处理日常办公的各项数据时仍依赖于Office等办公软件,随着办公信息化的发展变革,我们开始将各种工具集成到了Web应用中,用“在线”替代本地化,从而进一步加快沟通与协作的效率,如在线Excel、在线报表设计等。也正是由于类似需求的产生,PWA也终于找到了属于他的历史定位。

下图便是使用PWA技术集成了SpreadJS在线表格编辑器的一款在线文档编辑应用,对于最终用户而言,它完全保留了Excel的操作体验和数据处理能力。与此同时,这款应用在执行多任务工作时,使用alt(cmd)——tab便可快速切换,系统级别的推送让使用者可以实时关注其工作状态。

发挥PWA的真正实力,让SpreadJS在线表格编辑器秒变“桌面”编辑器

在操作这一步之前,首先,我们需要下载SpreadJS在线表格编辑器示例,点此下载

其次,让SpreadJS的在线表格编辑器支持PWA,只需要实现App Manifest 和 Service Worker即可。

实现步骤如下:

1、 添加 manifest.json 文件:新建manifest.json,并在index.html中引用。

{
  "name": "SpreadJSDesigner",
  "short_name": "SJSD",
  "descriptions": "SpreadJS在线表格编辑器",
  "start_url": "./",
  "background_color": "#fff",
  "display": "minimal-ui",
  "scope": "./",
  "theme_color": "#fff",
  "icons": [
    {
      "src": "./welcome.png",
      "type": "image/png",
      "sizes": "200x200",
      "purpose": "any"
    }
  ]
}
<link rel="manifest" href="./manifest.json">

2、 实现Service Worker:新建sw.js, 通过Service Worker缓存设计器所需要的spreadjs资源。

var cacheName = 'v14.2.2';
var cacheFiles = [
    '/',
    './index.html',
    './lib/css/gc.spread.sheets.excel2013white.14.2.2.css',
        './lib/css/gc.spread.sheets.designer.14.2.2.css',
        './custom.css',
    './lib/scripts/gc.spread.sheets.all.14.2.2.js',
    './lib/scripts/plugins/gc.spread.sheets.charts.14.2.2.js',
    './lib/scripts/plugins/gc.spread.sheets.shapes.14.2.2.js',
    './lib/scripts/plugins/gc.spread.sheets.print.14.2.2.js',
    './lib/scripts/plugins/gc.spread.sheets.barcode.14.2.2.js',
    './lib/scripts/plugins/gc.spread.sheets.pdf.14.2.2.js',
    './lib/scripts/plugins/gc.spread.pivot.pivottables.14.2.2.js',
    './lib/scripts/interop/gc.spread.excelio.14.2.2.js',
    './lib/scripts/resources/zh/gc.spread.sheets.resources.zh.14.2.2.js',
    './lib/scripts/gc.spread.sheets.designer.resource.cn.14.2.2.js',
    './lib/scripts/gc.spread.sheets.designer.all.14.2.2.js',
];
// 监听 install 事件,安装完成后,进行文件缓存
self.addEventListener('install', function (e) {
    console.log('Service Worker 状态: install');
    var cacheOpenPromise = caches.open(cacheName).then(function (cache) {
        // 把要缓存的 cacheFiles 列表传入
        return cache.addAll(cacheFiles);
    });
    e.waitUntil(cacheOpenPromise);
});
// 监听 fetch 事件,安装完成后,进行文件缓存
self.addEventListener('fetch', function (e) {
    console.log('Service Worker 状态: fetch');
    var cacheMatchPromise = caches.match(e.request).then(function (cache) {
            // 如果有cache则直接返回,否则通过fetch请求
            return cache || fetch(e.request);
        }).catch(function (err) {
            console.log(err);
            return fetch(e.request);
        })
    e.respondWith(cacheMatchPromise);
});
// 监听 activate 事件,清除缓存
self.addEventListener('activate', function (e) {
    console.log('Service Worker 状态: activate');
    var cachePromise = caches.keys().then(function (keys) {
        return Promise.all(keys.map(function (key) {
            if (key !== cacheName) {
                return caches.delete(key);
            }
        }));
    })
    e.waitUntil(cachePromise);
    return self.clients.claim();
});

index.html页面组册sw.js:

<script>
  if ('serviceWorker' in navigator) {
          window.addEventListener('load', function () {
                  navigator.serviceWorker.register('./sw.js')
                  .then(function (registration) {
                          // 注册成功
                          console.log('ServiceWorker registration successful with scope: ', registration.scope);
                  })
                  .catch(function (err) {
                          // 注册失败:
                          console.log('ServiceWorker registration failed: ', err);
                  });
          });
  }
</script>

通过上述两个步骤的操作,SpreadJS的在线表格编辑器就可以支持PWA了,注意PWA需要https的支持,本地通过localhost测试不受影响。
通过localhost访问页面,可以在Chrome地址栏看到安装选项,如下所示:

安装后,就可以通过应用程序按钮双击访问了:

对于Chrome的PWA应用,同样可以通过快捷键开启开发者工具,在Network中可以看到,资源都是通过ServiceWorker缓存获取:

以上便是借助PWA技术让SpreadJS在线表格编辑器变成桌面编辑器的操作步骤,大家在熟练掌握并使用 PWA 架构及其相关技术后,便可以试着用它来构建更具高可用的现代化 Web 应用,快去试试吧!