摘要: PWA图文教程

Fundebug经授权转载,版权归原作者所有。

简介

Web前端的同学是否想过学习app开发,以弥补自己移动端能力的不足?但在面对一众的选择时很多同学略感迷茫,是学习ios还是android开发?是学习原生开发、混合开发(比如:Ionic),还是使用react native或者flutter这样的跨平台框架?而app开发的学习周期长、学习成本高也让一部分人望而却步。得益于前端技术的飞速发展、浏览器性能的不断提高,使用网页技术开发出接近原生体验的应用得以变为现实,PWA就在这样的背景下应运而生。可以用自己熟悉的HTML、CSS、Javascript开发出媲美原生app的网站,不仅拥有接近原生app的流畅程度,并且具备一些原生app才有的特性,比如:a. 可以在主屏上安装应用图标,b. 离线状态下访问,c. 获取消息通知,等等。。PWA的出现让大家看到了希望!

对比原生应用

那PWA和原生应用相比到底有何竞争力呢?我们分别看一下原生应用和PWA的特点:

原生应用:

  • 使用原生SDK和开发工具开发
  • 需要考虑跨平台,不同系统往往需要独立开发
  • 需要发布到应用商店才能下载使用
  • 可以安装到手机主屏,生成应用图标
  • 直接运行于操作系统上,访问系统资源方便
  • 可以离线使用
  • 可以获取消息通知

PWA应用:

  • 使用HTML,CSS,JS开发
  • 无需考虑跨平台,只需要考虑浏览器兼容性
  • 通过url访问,无需发布到应用商店
  • 可以安装到手机主屏,生成应用图标
  • 运行于浏览器中,可访问系统资源
  • 可以离线使用
  • 可以获取消息通知

可以发现PWA具备了原生应用的主要能力,但是开发流程却比原生应用更加简洁:a. html/css/js的群众基础更好,开发效率更高;b. 省去了为不同系统开发独立版本的大量成本;c. 省去了上架到应用市场的繁琐流程;d. 无需前往应用商店下载,用户使用起来也更加方便。但是值得注意的是,PWA还是相对比较新的技术,实现规范还有很多调整的空间,部分浏览器对PWA的支持也还不完善,但是PWA是一个趋势,所以现在学习正合适!

本文将通过一个简单的列子(一个简单的邮编查询app)向大家展示PWA的开发流程,项目参考:Traversy Media - Build a PWA With Vue & Ionic4。完成后的效果是 这样的

创建项目

项目使用Vue + Ionic的组合进行开发。本文主要关注PWA的搭建,因此vue、ionic等技术不做过多描述。使用VSCode的同学,建议安装Vetur插件增加开发效率。

1. 首先全局安装 @vue/cli

npm install -g @vue/cli

2. 初始化vue项目:

vue create vue-ionic-pwa

3. 因为ionic的路由依赖于vue-router,所以接下来安装 vue-router

vue add router

4. 安装 @ionic/vue

npm install @ionic/vue

5. 在 src/main.js 中添加对ionic的引用:

import Ionic from '@ionic/vue'
import '@ionic/core/css/ionic.bundle.css' Vue.use(Ionic)

6. 在 src/router.js 中使用 IonicVueRouter 替换默认的vue router:

import Vue from 'vue'
import { IonicVueRouter } from '@ionic/vue';
import Home from './views/Home.vue' Vue.use(IonicVueRouter) export default new IonicVueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
}
]
})

7. 将 src/App.vue 内容修改为:

<template>
<div id="app">
<ion-app>
<ion-vue-router/>
</ion-app>
</div>
</template>

8. 将 src/views/Home.vue 内容修改为:

<template>
<div class="ion-page">
<ion-header>
<ion-toolbar>
<ion-title>
ZipInfo
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">My App</ion-content>
</div>
</template> <script>
export default {
name: 'home',
components: {}
}
</script>

最后,我们运行yarn serve看下效果:

App功能实现

App主要有三部分组成:1. 搜索组件,用于输入邮编并查询,2. 展示组件,用于展示查询到的邮编信息,3. 清除按钮,用于清除查询到的邮编信息

1. 搜索组件

我们在 src/components 下面新建 ZipSearch.vue 文件作为邮编搜索组件,主要逻辑为当用户输入一串字符,按下搜索按钮,如果输入合法则触发get-zip事件,如果不合法则给出提示。

ZipSearch.vue

<template>
<ion-grid>
<form @submit="onSubmit">
<ion-col>
<ion-item>
<ion-label>ZipCode:</ion-label>
<ion-input
:value="zip"
@input="zip = $event.target.value"
name="zip"
placeholder="Enter US ZipCode"
/>
</ion-item>
</ion-col>
<ion-col>
<ion-button type="submit" color="primary" expand="block">Find</ion-button>
</ion-col>
</form>
</ion-grid>
</template> <script>
export default {
name: "ZipSearch",
data() {
return {
zip: ""
};
},
methods: {
onSubmit(e) {
e.preventDefault();
const zipRegex = /(^\d{5}$)|(^\d{5}-\d{4}$)/;
const isValid = zipRegex.test(this.zip);
if (!isValid) {
this.showAlert();
} else {
this.$emit("get-zip", this.zip);
}
this.zip = "";
},
showAlert() {
return this.$ionic.alertController
.create({
header: "Enter zipcode",
message: "Please enter a valid US ZipCode",
buttons: ["OK"]
})
.then(a => a.present());
}
}
};
</script>

src/views/Home.vue 中引入 ZipSearch 组件,当Home接收到get-zip事件时调用 https://www.zippopotam.us 的接口,获取邮编对应的信息:

...
<ion-content class="ion-padding">
<ZipSearch v-on:get-zip="getZipInfo"/>
</ion-content>
... <script>
import ZipSearch from "../components/ZipSearch"; export default {
name: "home",
components: {
ZipSearch
},
data() {
return {
info: null
};
},
methods: {
async getZipInfo(zip) {
const res = await fetch(`https://api.zippopotam.us/us/${zip}`);
if (res.status == 404) {
this.showAlert();
}
this.info = await res.json();
},
showAlert() {
return this.$ionic.alertController
.create({
header: "Not Valid",
message: "Please enter a valid US ZipCode",
buttons: ["OK"]
})
.then(a => a.present());
}
}
};
</script>

我们先看一下搜索组件的效果:

输入邮编格式错误:

2. 信息展示和清除组件

获取到邮编信息后我们需要一个展示邮编信息的组件和一个清除信息的按钮,在 src/components 下面新建 ZipInfo.vueClearInfo.vue

ZipInfo.vue

<template>
<ion-card v-if="info">
<ion-card-header>
<ion-card-subtitle>{{info['post code']}}</ion-card-subtitle>
<ion-card-title>{{info['places'][0]['place name']}}</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-list>
<ion-item>
<ion-label>
<strong>State:</strong>
{{info['places'][0]['state']}} ({{info['places'][0]['state abbreviation']}})
</ion-label>
</ion-item>
<ion-item>
<ion-label>
<strong>Latitude:</strong>
{{info['places'][0]['latitude']}}
</ion-label>
</ion-item>
<ion-item>
<ion-label>
<strong>Longitude:</strong>
{{info['places'][0]['longitude']}}
</ion-label>
</ion-item>
</ion-list>
</ion-card-content>
</ion-card>
</template> <script>
export default {
name: "ZipInfo",
props: ["info"]
};
</script>

ClearInfo.vue

<template>
<ion-button color="light" expand="block" v-if="info" @click="$emit('clear-info')">Clear</ion-button>
</template> <script>
export default {
name: "ClearInfo",
props: ["info"]
};
</script>

接着在Home中引入ZipInfoClearInfo组件:

src/views/Home.vue

...
<ion-content class="ion-padding">
<ZipSearch v-on:get-zip="getZipInfo"/>
<ZipInfo v-bind:info="info"/>
<ClearInfo v-bind:info="info" v-on:clear-info="clearInfo"/>
</ion-content>
... import ZipInfo from "../components/ZipInfo";
import ClearInfo from "../components/ClearInfo"; export default {
name: "home",
components: {
ZipSearch, ZipInfo
},
methods:{
...
clearInfo(){
this.info = null;
}
}
}

到此,app的主体就完成了,效果如下:

实现PWA

我们使用现成的 @vue/pwa 插件来给我们的app增加PWA的能力。

安装 @vue/pwa

vue add @vue/pwa

安装完成后项目中增加了 public/manifest.jsonregisterServiceWorker.js两个文件。其中 public/manifest.json 文件内容如下:

{
"name": "vue-ionic-pwa",
"short_name": "vue-ionic-pwa",
"icons": [
{
"src": "./img/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "./img/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#4DBA87"
}

manifest.json中主要包含app的基本信息,比如名称(name)、图标(icons)、显示方式(display)等等,是web app能被以类似原生的方式安装、展示的必要配置。更多的配置项可参考 MDN Web App Manifest

在Chrome浏览器控制台中也可看到app的manifest配置:

registerServiceWorker.js用于注册service worker。service worker通俗来讲就是在浏览器后台独立于网页运行的一段脚本,service worker可以完成一些特殊的功能,比如:消息推送、后台同步、拦截和处理网络请求、管理网络缓存等。Service worker之于pwa的意义在于能够为用户提供离线体验,即掉线状态下用户依旧能够访问网站并获取已被缓存的数据。使用service worker需要HTTPS,并且考虑 浏览器兼容性

registerServiceWorker.js

import { register } from 'register-service-worker'

if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
)
},
registered () {
console.log('Service worker has been registered.')
},
cached () {
console.log('Content has been cached for offline use.')
},
updatefound () {
console.log('New content is downloading.')
},
updated () {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}

在Chrome浏览器控制台中也可看到service worker的状态:

当然,只注册service worker还不够,我们还希望控制service worker的行为,通过在 vue.config.js 中增加相关的配置我们可以设置service worker文件的名称、缓存逻辑等等。

vue.config.js

module.exports = {
pwa: {
workboxPluginMode: 'GenerateSW',
workboxOptions: {
navigateFallback: '/index.html',
runtimeCaching: [
{
urlPattern: new RegExp('^https://api.zippopotam.us/us/'),
handler: 'networkFirst',
options: {
networkTimeoutSeconds: 20,
cacheName: 'api-cache',
cacheableResponse: {
statuses: [0, 200]
}
}
}
]
}
}
}

更多配置请参考:@vue/cli-plugin-pwaworkbox-webpack-plugin。由于@vue/cli-plugin-pwa生成的service worker只在生产环境生效,所以建议将项目build之后部署到生产环境测试。本文示例使用 github pages进行部署和展示。

到此,将普通web app转成PWA的工作基本完成,我们部署到线上看下效果:

文件已被缓存用于离线访问:

查询一个邮编试试,可以发现请求被缓存了下来:

我们接着关掉网络,再查询刚刚的那个邮编,发现在网络请求失败之后立即切换用本地缓存的数据:

好了,一个简单的PWA就已经制作完成了。当然PWA的功能远不止本文所展示的,比如推送、安装到手机,后续有机会再跟大家分享,谢谢

PWA入门:手把手教你制作一个PWA应用的更多相关文章

  1. 手把手教你制作AppPreview视频并上传到appStore进行审核

    手把手教你制作AppPreview视频并上传到appStore进行审核 注意,你需要使用iMovie才能够制作AppPreview视频文件,用QuickTime录制的无效! 最终效果 1. 新建一个事 ...

  2. 只有20行Javascript代码!手把手教你写一个页面模板引擎

    http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...

  3. iOS回顾笔记(05) -- 手把手教你封装一个广告轮播图框架

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  4. R数据分析:跟随top期刊手把手教你做一个临床预测模型

    临床预测模型也是大家比较感兴趣的,今天就带着大家看一篇临床预测模型的文章,并且用一个例子给大家过一遍做法. 这篇文章来自护理领域顶级期刊的文章,文章名在下面 Ballesta-Castillejos ...

  5. 手把手教你画一个 逼格满满圆形水波纹loadingview Android

    才没有完结呢o( ̄︶ ̄)n .大家好,这里是番外篇. 拜读了爱哥的博客,又学到不少东西.爱哥曾经说过: 要站在巨人的丁丁上. 那么今天,我们就站在爱哥的丁丁上来学习制作一款自定义view(开个玩笑,爱 ...

  6. 用Python手把手教你搭一个Transformer!

    来源商业新知网,原标题:百闻不如一码!手把手教你用Python搭一个Transformer 与基于RNN的方法相比,Transformer 不需要循环,主要是由Attention 机制组成,因而可以充 ...

  7. PWA 入门: 写个非常简单的 PWA 页面

    Progressive Web Apps 是 Google 提出的用前沿的 Web 技术为网页提供 App 般使用体验的一系列方案. 这篇文章里我们来完成一个非常简单的 PWA 页面. 一个 PWA ...

  8. 手把手教你制作微信小程序,开源、免费、快速搞定

    最近做了个"罗孚传车"的小程序 一时兴起,做了一个小程序,将个人收集的同汽车相关的行业资讯和学习资料,分享到小程序中,既作为历史资料保存,又提供给更多的人学习和了解,还能装一下:) ...

  9. 手把手教你写一个RN小程序!

    时间过得真快,眨眼已经快3年了! 1.我的第一个App 还记得我14年初写的第一个iOS小程序,当时是给别人写的一个单机的相册,也是我开发的第一个完整的app,虽然功能挺少,但是耐不住心中的激动啊,现 ...

随机推荐

  1. Add a Simple Action添加简单按钮

    In this lesson, you will learn how to create a Simple Action. For this purpose, a new View Controlle ...

  2. ES6-数字操作,判断是否是整数,判断最大值最小值

    S中只有一种类型数,即64位(1bit 的符号位,11bits 的指数部分 ,以及52bits 的小数部分)双精度浮点数,当整数数值过大时,就会发生精度丢失. 所谓安全整数即能够唯一确定的数字,即能够 ...

  3. Cesium专栏-空间分析之坡度分析(附源码下载)

    Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...

  4. SSH远程管理

    目录 SSH远程管理 ssh功能 SSH相关命令 Xshell连接不上虚拟机 scp命令 SSH验证方式 优化 免交互expect[扩展] 免交互sshpass[扩展] 简易跳板机 小结 SSH远程管 ...

  5. MySQL数据库(二)事务

    MySQL的存储引擎InnoDB支持事务,MyISAM不支持事物 数据库事务的四大特性(ACID) 原子性(atomic) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客 ...

  6. c# 获取sqlserver 运行脚本的print消息的方法分享

    转自:http://www.maomao365.com/?p=6923  摘要: 在sql脚本的编写中,我们经常使用sql脚本print消息,作为输出测试, 通过获取print消息,我们可以快速获取程 ...

  7. PM8909 linear charger硬件概述

    电池充电是由qpnp-vm-bus.c(电池驱动BMS)和qpnp-linear-charger.c(线性充电器)组成: SMMB charger:Switch-ModeBattery Charger ...

  8. (转)vue-router原理

    转载地址:https://segmentfault.com/a/1190000014822765 随着前端应用的业务功能起来越复杂,用户对于使用体验的要求越来越高,单面(SPA)成为前端应用的主流形式 ...

  9. Ubuntu Idea 快捷键 Ctrl+Alt+S 无法使用解决

    Idea 里习惯了用 Ctrl+Alt+S 打开设置界面,在 Ubuntu 下会因为快捷键冲突无法使用 系统快捷键 打开系统设置中的快捷键设置,按 Backspace 键禁用 Fcitx 如果你的输入 ...

  10. golang数据结构之环形队列

    目录结构: circlequeue.go package queue import ( "errors" "fmt" ) //CircleQueue 环型队列 ...