改造你的网站,变身 PWA

2019-12-07 23:37栏目:WRB前端

渐进式Web应用(PWA)入门教程(上)

2018/05/23 · 底工本领 · PWA

原稿出处: Craig Buckler   译文出处:葡萄城控件   

近些日子有关渐进式Web应用有过多批评,有生龙活虎对人还在疑忌渐进式Web应用是不是正是移动端未来。

但在此篇文章中小编并不会将渐进式应用程式和原生的APP实行相比,但有一点是能够一定的,那三种APP的靶子都是使客商体验变得更加好。

移动端Web应用有大多出色的概念令人应接不暇,但辛亏编辑一个渐进式Web应用不是贰个很困苦的业务。在此篇小说里将向您介绍怎么着把一个常备的网址转换来渐进式Web应用。你能够服从那篇小说一步一步地做,做完未来您的网址将得以兑现离线访谈,並且能够在桌面上创建该网址的Logo。那么上边就要上马入门教程。

图片 1

什么样是渐进式Web应用?

渐进式Web应用是意气风发种全新的Web工夫,让Web应用和原生应用软件的心得周边或意气风发致。

渐进式Web应用它能够横跨Web本事及Native APP开采的减轻方案,对于开拓者的优势如下:

  1. 你只须求关怀W3C的Web标准,不用关切各个Native 应用程式的代码。
  2. 顾客能够在装置使用从前先试用。
  3. 在渐进式Web应用中,你无需利用各样应用集团来散发应用,也不用关爱应用宣布时匪夷所思的复核标准以致利用内购的平台分红。其余,应用程序更新是电动进行的,不要求顾客人机联作,所以总体的接纳体验对于顾客来讲更为的平缓。
  4. 渐进式Web应用的“安装”进度相当的慢,只需求在主荧屏上增加二个Logo就可以。
  5. 渐进式Web应用运行时能够显得多少个美观的运行漫面。
  6. 您能够在渐进式Web应用中提供全体全屏体验的利用。
  7. 通过系统通报等花样进步客户的粘性。
  8. 渐进式Web应用将会在地头缓存供给的文书,所以渐进式Web应用会比普通的Web应用的质量更加好。
  9. 轻量级安装——你只须求缓存几百KB的多寡就可以。
  10. 负有的数量传输必须利用安全的HTTPS连接
  11. 渐进式Web应用能够离线缓存数据,何况会在再度连接网络时再一次联合数据。

pwa

渐进式Web应用发展的现状

渐进式Web应用才刚刚领头阵展,但事实上在境内,有个别网址已经实际最初PWA的试行了,譬喻:网易、豆瓣、天猫商城等楼台。只怕那个时候聪明的你可能就能够发出疑问,那这一个PWA不就是和Wechat小程序同样吗,对是那样,二者的目标是相仿的,正是在运动端为客户提供丰富轻量且与原生应用使用体验左近的“轻”应用。

但就方今来说,PWA是谷歌首要推荐的生机勃勃项本事规范,FireFox,Chrome甚至一些基于Blink的浏览器已经支撑渐进式Web应用了,Edge上对渐进式Web应用的支撑还在支付。Apple公司也意味着会思忖在协调Safari扶助PWA。但是那项功效已经进来了WebKit内核的三年铺排中。长时间来看,对浏览器宽容性的扶植方面应该已经不算太大标题了。何况在现阶段,在不扶助渐进式Web应用的浏览器中,你的应用也只是不能利用渐进式Web应用的离线成效而已,除了这么些之外的效用均可以健康使用。

而在Wechat那边,依赖宏大的客户基数和容积能或不可能与PWA鼎足而居以至笑到最后近些日子还一无所知。

这两日有比非常多关于 Progressive Web Apps(PWAs)的信息,很五个人都在问这是否(移动)web 的前途。笔者不想陷入native app 和 PWA 的纷争,不过有意气风发件事是明显的 --- PWA不小的升级了运动端表现,校勘了客商体验。

演示代码

大比超多学科都叙述的是哪些在Chrome上从零开端制作八个看似原生分界面的运用。然则在此篇教程中,大家并不酌量做二个单页面应用程序,所以在那大家也无须理解诸如Material Design等学问。那么上面大家就一贯看示例吧。

你能够从GitHub中得到本课程对应的演示代码。

本示例中提供了三个有八个网页的网址,三个CSS文件和叁个JavaScript文件。这些网址能够在具有的现世浏览器上经常工作(IE10 )。若是您的浏览器支持渐进式Web应用,客户能够在离线状态下将会直接待上访谈缓存中的页面。

要想运转此示例,请确定保障您曾经设置了Node.js。并请张开命令行,使用以下命令来运转该示例:

node ./server.js [port]

1
node ./server.js [port]

以上命令中,[port]是可选部分,默以为8888。使用 Ctrl C 就可以结束Web服务器。

开发基于Blink内核的浏览器(Opera,Vivaldi,Chrome),然后在地方栏中输入 只怕 Cmd/Ctrl Shift I)来查看调控台新闻。

图片 2图片 3

查阅首页,也足以在页面上点击一下,然后使用以下办法进入离线格局:

入选Network标签或然Application -> Service Workers 标签下的“离线”选项。重新访问早先访谈过的网页,以前网页还是会加载:

图片 4图片 5

好消息是开辟三个 PWA 并简单。事实上,大家得以将现有的网址进行纠正,使之成为PWA。那也是作者那篇小说要讲的 -- 当您读完那篇小说,你能够将你的网址改良,让他看起来就好像二个 native web app。他能够离线职业还要存有和谐的主屏Logo。

连续几天来移动端安装

除此而外在PC浏览器访谈外,你也得以在运动器材上访谈该示例。使用USB线缆将您的位移设备连接到Computer上,然后从右上角三个点菜单中开发More tools – Remote devices标签

图片 6图片 7

点击左侧的Settings菜单,然后增多一条端口映射(Port Forwarding)的准则,将8888映射为localhost:8888,未来您可以间接在手提式有线话机展开Chrome然后探问http://localhost:8888 。

你能够利用浏览器的“加多到主显示屏”功用将日前网页增多到主显示器,在你寻访了几个页面之后,浏览器会将以此Web应用“安装”到你的配备上。浏览多少个页面,关闭Chrome并将配备与计算机断开连接,点击桌面上生成的Logo,你会看见叁个Splash页面,并且你能够继续浏览在此之前浏览过的页面。

图片 8图片 9

Progressive Web Apps 是什么?

Progressive Web Apps (下文以“PWAs”代指卡塔尔国是贰个令人高兴的前端技艺的改过。PWAs综合了生龙活虎种类技艺使您的 web app表现得就像 native mobile app。相比较于纯 web 建设方案和纯 native 建设方案,PWAs对于开采者和客户有以下优点:

  1. 你只须要依据开放的 W3C 标准的 web 开采工夫来开采三个app。无需多顾客端支付。

  2. 顾客可以在设置前就心得你的 app。

  3. 无需通过 AppStore 下载 app。app 会自动升级无需顾客升高。

  4. 客户会受到‘安装’的晋升,点击安装会追加叁个Logo到顾客首屏。

  5. 被展开时,PWA 交易会示叁个有吸重力的闪屏。

  6. chrome 提供了可选选项,能够使 PWA 获得全屏体验。

  7. 要求的文件会被当地缓存,由此会比规范的web app 响应越来越快(或许也会比native app响应快)

  8. 设置及其轻量 -- 只怕会有几百 kb 的缓存数据。

  9. 网址的多少传输必需是 https 连接。

  10. PWAs 能够离线专门的职业,並且在互连网复苏时方可协同最新数据。

今昔还处于 PWA 的最早,但现本来就有 成都百货上千打响案例 。

PWA 本领近年来被 Firefox,Chrome 和其他基于Blink内核的浏览器帮忙。微软正在全力在Edge浏览器上落到实处。Apple未有动作 although there are promising comments in the WebKit five-year plan。幸运的是,浏览器扶持对于 PWA 就如不太重大...

小结

因此本节对渐进式Web应用的介绍,相信我们对PWA是怎么样已经有了骨干的认识。PWA有不供给担心有无互连网的本性,并有着独立入口与单身的维护机制。新标准的出产很可能会带着 Web 应用在运动器材上浴火重生。所以满意 PWA 模型的前端控件,如纯前端表格控件SpreadJS,将日益成为活动操作系统的一等平民,并将向Native 应用程式发起挑衅。

在下节中大家将带您二只去看看,PWA的原理是什么样,以致它到底是怎么着工作的,敬请期望。

1 赞 1 收藏 评论

图片 10

PWAs 是渐进加强的

你的app仍旧能够运转在不协助 PWA 技巧的浏览器里。顾客无法离线访谈,然而其余功用都像原本相像未有影响。综合利弊得失,未有理由不把你的 app 校勘为 PWA。

不只是 Apps

Google 引领了 PWA 的大器晚成俯拾皆已经动作,所以大多数科目都说什么样从零开头营造四个基于 Chrome,native-looking mobile app。但是并非唯有特殊的单页应用能够PWA化,也不要求确定遵照 material interface design guidelines。大很多网址都足以在数小时内达成 PWA 化。那包罗你的 WordPress站点大概静态站点。

事必躬亲代码

示范代码能够在https://github.com/sitepoint-editors/pwa-retrofit找到。

代码提供了三个轻松的多少个页面包车型地铁网址。当中带有部分图形,一个样式表和二个main javascript 文件。那么些网址能够运营在全数今世浏览器上(IE10 )。借使浏览器扶助 PWA 技巧,当离线时客商能够浏览他们事情发生前看过的页面。

运作代码前,确认保证 Node.js 已经设置,然后再命令行里运营服务:

node ./server.js [port]

[port]是可布置的,默感觉 8888。打开 Chrome 大概别的基于Blink内核的浏览器,比方 Opera 只怕 Vivaldi,然后输入链接 http://localhost:8888/(只怕你钦命的某部端口)。你也可以张开开拓者工具看一下顺序console音信。

图片 11

浏览主页,恐怕别的页面,然后用以下任朝气蓬勃主意使页面离线:

  1. 按下 Cmd/Ctrl C ,停止 node 服务器,或者

  2. 在开垦者工具的 Network 或者 Application - Service Workers 栏里点击 offline 选项。

重复浏览任意从前浏览过的页面,它们仍旧能够浏览到。浏览多少个事情发生前并未有看过的页面,你拜见到三个专程的离线页面,标志“you’re offline”,还应该有八个您能够浏览的页面列表:

图片 12

接连几日来手提式有线电话机

您也得以由此 USB 连接你的安卓手提式有线电话机来预览示例网页。在开垦者工具中展开 Remote devices 菜单。

图片 13

在侧边接纳 Settings ,点击 Add Rule 输入 8888 端口。你能够在你的手提式有线电话机上张开Chrome,打开 http://localhost:8888/。

您能够点击浏览器菜单里的 “Add to Home screen”。浏览多少个页面,浏览器会提示您去安装。那三种办法都能够创立三个新的Logo在你的主屏上。浏览多少个页面后关掉Chrome,断开设备连接。你还能够打开 PWA Website app -- 你会看出一个起动页,何况能够离线访谈早先您探问过的页面。

将你的网址改革为一个 Progressive Web App 总共有多个必备步骤:

第一步:开启 HTTPS

由于部分显著的来头,PWAs 供给 HTTPS 连接。

HTTPS 在演示代码中并不是必得的,因为 Chrome 允许行使 localhost 也许其他127.x.x.x 的地址来测量检验。你也得以在 HTTP 连接下测验你的 PWA,你必要运用 Chrome ,何况输入以下命令行参数:

  • --user-data-dir
  • --unsafety-treat-insecure-origin-as-secure

第二步:创立七个 Web App Manifest

manifest 文件提供了某些大家网址的新闻,举例 name,description 和内需在主屏使用的Logo的图片,运转屏的图片等。

manifest文件是三个 JSON 格式的文书,坐落于你项目标根目录。它必需用Content-Type: application/manifest json 或者 Content-Type: application/json这么的 HTTP 头来必要。这几个文件能够被命名称叫任何名字,在演示代码中他被命名称叫 /manifest.json:

{
  "name"              : "PWA Website",
  "short_name"        : "PWA",
  "description"       : "An example PWA website",
  "start_url"         : "/",
  "display"           : "standalone",
  "orientation"       : "any",
  "background_color"  : "#ACE",
  "theme_color"       : "#ACE",
  "icons": [
    {
      "src"           : "logo/logo072.png",
      "sizes"         : "72x72",
      "type"          : "image/png"
    },
    {
      "src"           : "logo/logo152.png",
      "sizes"         : "152x152",
      "type"          : "image/png"
    },
    {
      "src"           : "logo/logo192.png",
      "sizes"         : "192x192",
      "type"          : "image/png"
    },
    {
      "src"           : "logo/logo256.png",
      "sizes"         : "256x256",
      "type"          : "image/png"
    },
    {
      "src"           : "logo/logo512.png",
      "sizes"         : "512x512",
      "type"          : "image/png"
    }
  ]
}

在页面包车型客车<head>中引入:

<link rel="manifest" href="/manifest.json">

manifest 中第风姿洒脱质量有:

  • name —— 网页显示给客商的欧洲经济共同体名称
  • short_name —— 当空间不足以展现姓名时的网站缩写名称
  • description —— 关于网站的详细描述
  • start_url —— 网页的开始 相对 U奥迪Q7L(比方 /
  • scope —— 导航范围。例如,/app/的scope就限定 app 在这里个文件夹里。
  • background-color —— 运行屏和浏览器的背景颜色
  • theme_color —— 网址的核心颜色,平日都与背景颜色相仿,它能够影响网址的显示
  • orientation —— 首荐的来得方向:any, natural, landscape, landscape-primary, landscape-secondary, portrait, portrait-primary, 和 portrait-secondary
  • display —— 首推的展现情势:fullscreen, standalone(看起来疑似native app卡塔尔(قطر‎,minimal-ui(有简化的浏览器调整选项卡塔尔(قطر‎ 和 browser(常规的浏览器 tab卡塔尔国
  • icons —— 定义了 src URL, sizestype的图样对象数组。

MDN提供了完整的manifest属性列表:Web App Manifest properties

在开荒者工具中的 Application tab 左边有 Manifest 选项,你能够印证你的 manifest JSON 文件,并提供了 “Add to homescreen”。

图片 14

其三步:成立一个 Service Worker

Service Worker 是挡住和响应你的网络央浼的编制程序接口。那是一个坐落于你根目录的叁个独自的 javascript 文件。

你的 js 文件(在演示代码中是 /js/main.js)能够检查是还是不是辅助 ServiceWorker,而且注册:

if ('serviceWorker' in navigator) {

  // register service worker
  navigator.serviceWorker.register('/service-worker.js');

}

意气风发经您无需离线效能,能够简单的创造三个空的 /service-worker.js文本 —— 客户会被提醒安装你的 app。

Service Worker 很复杂,你能够修改示例代码来完毕和煦的目标。那是二个行业内部的 web worker,浏览器用四个独立的线程来下载和进行它。它未有调用 DOM 和别的页面 api 的力量,但他得以阻止互联网诉求,富含页面切换,静态资源下载,ajax哀告所引起的互联网乞请。

那正是索要 HTTPS 的最首要的原委。想象一下第三方代码能够阻挡来自别的网址的 service worker, 将是三个苦难。

service worker 首要有三个事件: installactivatefetch

Install 事件

这几个事件在app被设置时接触。它时时用来缓存须求的文本。缓存通过 Cache API来实现。

首先,我们来布局多少个变量:

  1. 缓存名称(CACHE)和版本号(version)。你的施用能够有八个缓存不过只可以援用一个。我们设置了版本号,那样当大家有主要立异时,大家得以创新缓存,而忽略旧的缓存。

  2. 三个离线页面包车型大巴UPRADOL(offlineURL)。当离线时客商策画访谈在此之前未缓存的页面时,那些页面会突显给客户。

  3. 三个怀有离线效率的页面供给文件的数组(installFilesEssential)。那个数组应该包罗静态财富,比如CSS 和 JavaScript 文件,但作者也把主页面(/)和Logo文件写进去了。假若主页面能够多少个ULacrosseL访谈,你应该把她们都写进去,比如//index.html。注意,offlineURL也要被写入那些数组。

  4. 可选的,描述文件数组(installFilesDesirable)。那些文件都很会被下载,但要是下载战败不会半途而废安装。

// configuration
const
  version = '1.0.0',
  CACHE = version   '::PWAsite',
  offlineURL = '/offline/',
  installFilesEssential = [
    '/',
    '/manifest.json',
    '/css/styles.css',
    '/js/main.js',
    '/js/offlinepage.js',
    'logo/logo152.png'
  ].concat(offlineURL),
  installFilesDesirable = [
    '/favicon.ico',
    'logo/logo016.png',
    'hero/power-pv.jpg',
    'hero/power-lo.jpg',
    'hero/power-hi.jpg'
  ];

installStaticFiles()措施添Gavin件到缓存,那几个方法用到了依赖 promise的 Cache API。当须要的公文都被缓存后才会生成重临值。

// install static assets
function installStaticFiles() {

  return caches.open(CACHE)
    .then(cache => {

      // cache desirable files
      cache.addAll(installFilesDesirable);

      // cache essential files
      return cache.addAll(installFilesEssential);

    });

}

最后,大家抬高install的事件监听函数。 waitUntil主意确定保障全部代码实践达成后,service worker 才会施行install。推行 installStaticFiles()方法,然后施行 self.skipWaiting()方法使service worker进入 active状态。

// application installation
self.addEventListener('install', event => {

  console.log('service worker: install');

  // cache core files
  event.waitUntil(
    installStaticFiles()
    .then(() => self.skipWaiting())
  );

});

Activate 事件

当 install实现后, service worker 走入active状态,这些事件立时实施。你或者无需得以完成那一个事件监听,不过示例代码在这里地删除老旧的不行缓存文件:

// clear old caches
function clearOldCaches() {

  return caches.keys()
    .then(keylist => {

      return Promise.all(
        keylist
          .filter(key => key !== CACHE)
          .map(key => caches.delete(key))
      );

    });

}

// application activated
self.addEventListener('activate', event => {

  console.log('service worker: activate');

    // delete old caches
  event.waitUntil(
    clearOldCaches()
    .then(() => self.clients.claim())
    );

});

注意,最后的self.clients.claim()艺术设置自个儿为active的service worker。

Fetch 事件

当有互联网央浼时这几个事件被触发。它调用respondWith()艺术来威吓 GET 乞请并赶回:

  1. 缓存中的四个静态能源。

  2. 如果 #1 失败了,就用 Fetch API(那与 service worker 的fetch 事件不要紧)去网络央求这些财富。然后将以此财富投入缓存。

  3. 如果 #1 和 #2 都战败了,这就回去一个适龄的值。

// application fetch network data
self.addEventListener('fetch', event => {

  // abandon non-GET requests
  if (event.request.method !== 'GET') return;

  let url = event.request.url;

  event.respondWith(

    caches.open(CACHE)
      .then(cache => {

        return cache.match(event.request)
          .then(response => {

            if (response) {
              // return cached file
              console.log('cache fetch: '   url);
              return response;
            }

            // make network request
            return fetch(event.request)
              .then(newreq => {

                console.log('network fetch: '   url);
                if (newreq.ok) cache.put(event.request, newreq.clone());
                return newreq;

              })
              // app is offline
              .catch(() => offlineAsset(url));

          });

      })

  );

});

最后那几个offlineAsset(url)办法通过多少个援救函数重回二个非常的值:

// is image URL?
let iExt = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].map(f => '.'   f);
function isImage(url) {

  return iExt.reduce((ret, ext) => ret || url.endsWith(ext), false);

}


// return offline asset
function offlineAsset(url) {

  if (isImage(url)) {

    // return image
    return new Response(
      '<svg role="img" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title>offline</title><path d="M0 0h400v300H0z" fill="#eee" /><text x="200" y="150" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="50" fill="#ccc">offline</text></svg>',
      { headers: {
        'Content-Type': 'image/svg xml',
        'Cache-Control': 'no-store'
      }}
    );

  }
  else {

    // return page
    return caches.match(offlineURL);

  }

}

offlineAsset()方式检查是或不是是三个图形诉求,如若是,那么重临三个包涵“offline” 字样的 SVG。假诺不是,重临 offlineURL 页面。

开拓者工具提供了翻看 Service Worker 相关新闻的选项:

图片 15

在开辟者工具的 Cache Storage 选项列出了具有当前域内的缓存和所包涵的静态文件。当缓存更新的时候,你能够点击左下角的刷新开关来更新缓存:

图片 16

不出意料, Clear storage 选项能够去除你的 service worker 和缓存:

图片 17

再来一步 - 第四步:创造一个可用的离线页面

离线页面能够是多个静态页面,来注明当前顾客央浼不可用。不过,大家也能够在此个页面上列出能够访谈的页面链接。

main.js中我们能够利用 Cache API 。不过API 使用promises,在不帮衬的浏览器中会引起全数javascript运营堵塞。为了制止这种情景,大家在加载另贰个 /js/offlinepage.js 文件早先必得检查离线文件列表和是或不是帮衬 Cache API 。

// load script to populate offline page list
if (document.getElementById('cachedpagelist') && 'caches' in window) {
  var scr = document.createElement('script');
  scr.src = '/js/offlinepage.js';
  scr.async = 1;
  document.head.appendChild(scr);
}

/js/offlinepage.js locates the most recent cache by version name, 取到全部 U奥迪Q7L的key的列表,移除全数无用 U景逸SUVL,排序全体的列表並且把他们加到 ID 为cachedpagelist的 DOM 节点中:

// cache name
const
  CACHE = '::PWAsite',
  offlineURL = '/offline/',
  list = document.getElementById('cachedpagelist');

// fetch all caches
window.caches.keys()
  .then(cacheList => {

    // find caches by and order by most recent
    cacheList = cacheList
      .filter(cName => cName.includes(CACHE))
      .sort((a, b) => a - b);

    // open first cache
    caches.open(cacheList[0])
      .then(cache => {

        // fetch cached pages
        cache.keys()
          .then(reqList => {

            let frag = document.createDocumentFragment();

            reqList
              .map(req => req.url)
              .filter(req => (req.endsWith('/') || req.endsWith('.html')) && !req.endsWith(offlineURL))
              .sort()
              .forEach(req => {
                let
                  li = document.createElement('li'),
                  a = li.appendChild(document.createElement('a'));
                  a.setAttribute('href', req);
                  a.textContent = a.pathname;
                  frag.appendChild(li);
              });

            if (list) list.appendChild(frag);

          });

      })

  });

开采工具

借使您认为 javascript 调节和测量试验困难,那么 service worker 也不会很好。Chrome的开采者工具的 Application 提供了生机勃勃多种调节和测量检验工具。

您应有开采 藏匿窗口 来测验你的 app,这样在您关闭那么些窗口之后缓存文件就不会保留下去。

最后,Lighthouse extension for Chrome 提供了过多更上大器晚成层楼 PWA 的有用新闻。

PWA 陷阱

有几点须求小心:

URL 隐藏

我们的亲自去做代码掩没了 U传祺L 栏,小编不推荐这种做法,除非你有二个单 url 应用,举个例子二个嬉戏。对于绝大好些个网址,manifest 选项 display: minimal-ui 或者 display: browser是最佳的选料。

缓存太多

您可以缓存你网址的具有页面和持有静态文件。那对于一个小网址是行得通的,但那对于上千个页面包车型客车大型网址实际吗?未有人会对您网址的装有内容都感兴趣,而器具的内部存储器容积将是叁个范围。纵然你像示例代码同样只缓存访谈过的页面和文件,缓存大小也会压实的很快。

或是你须要留意:

  • 只缓存首要的页面,肖似主页,和明日的小说。
  • 决不缓存图片,录像和任何大型文件
  • 平日删除旧的缓存文件
  • 提供三个缓存开关给客商,让顾客决定是还是不是缓存

缓存刷新

在演示代码中,客户在伏乞互连网前先检查该文件是不是缓存。要是缓存,就应用缓存文件。那在离线情状下很棒,但也代表在联网状态下,顾客获得的恐怕不是新型数据。

静态文件,近似于图片和摄像等,不会时时转移的能源,做长日子缓存未有异常的大的标题。你能够在HTTP 头里设置 Cache-Control 来缓存文件使其缓存时间为一年(31,536,000 seconds):

Cache-Control: max-age=31536000

页面,CSS和 script 文件会时常转移,所以你应有改设置一个十分的短的缓存时间比方 24 时辰,并在联网时与服务端文件进行求证:

Cache-Control: must-revalidate, max-age=86400

译自 Retrofit Your Website as a Progressive Web App

版权声明:本文由威尼斯人app发布于WRB前端,转载请注明出处:改造你的网站,变身 PWA