用vue从零开发和部署一款移动端pwa单页应用

前言

最近秋招之余空出时间来按本身的兴趣动手作了一个项目,一个基于vue-cli3.0, vue,typescript的移动端pwa,如今趁热打铁,将这个项目从开发到部署整个过程记录下来,并将从这个项目中学习到的东西分享出来,若是你们有什么意见或补充也能够在评论区提出。先介绍一下这个项目css

项目介绍

browseexp

基于vue,typescript,pwa的一个移动端webapp,取名叫browseExp,主要功能是浏览学校心理学院部分实验信息。(上图是添加到桌面的一级入口)。这个项目已经部署到了服务器上,咱们看一下项目最终在客户端运行的样子html

show

能够看到我经过桌面上的一级入口,进入了咱们的webapp,而且在断网的条件下进行。这就是pwa的做用,下面开始分享此次的开发到部署的过程。前端

为何要作这个项目呢?

  1. pwa 在国内已经火过一段时间了,可是本身还没作过一款pwa应用。
  2. vue-cli 3.0 增长了对pwa的支持
  3. vue2.5后增长了对ts的支持
  4. 想搞事情!

开发过程

这个项目的地址为: browseExp pwa,想要查看代码的同窗能够看一下。这个项目要注意的点主要是:vue

  • 在vue中使用ts
  • 简单骨架屏的运用
  • 首屏加载时间和seo的优化
  • pwa相关特性的实现
  • 移动端的一些问题解决
  • 如何部署项目

后面的内容也围绕着这些点来展开。node

vue中使用ts

使用ts主要是由于ts给咱们带来了类型系统,可让咱们写出健壮的代码,它的做用在大型项目中尤为突出,因此仍是很是鼓励你们去使用的,咱们使用ts进行开发通常是编写基于类的vue组件,因此可使用官方维护的vue-class-component或者vue-property-decorator,vue-cli3.0也给咱们提供了开箱即用的typescript支持,开发体验仍是至关友好的。一个vue组件demo:webpack

import { Component, Vue, Prop } from 'vue-property-decorator';
@Component
export default class Name extends Vue {
  @Prop() private name!: string;
  private complete!: boolean;
  private data() {
    return {
      complete: false,
    };
  }
  private myMethod() {
    // ...
  }
  private created() {
    // ...
  }
}

另外,在vue-cli3.0提供的脚手架下,能够在shims-tsx.d.ts文件下添加全局接口或变量等,在shims-vue.d.ts定义第三方包的类型声明。nginx

骨架屏的简单运用

骨架屏(skeleton screen)已经不是什么新奇的概念,他的主要做用就是用来过渡页面的空白状态,提高用户体验,好比页面跳转等待,数据加载等待等,传统的骨架平实现方案有 服务端渲染和预渲染等,而这个项目中引入骨架屏主要是想过渡数据加载时页面的局部空白状态,因此就直接采用编写一个骨架屏组件SkeletonExp.vue的方式来过渡。git

skeletonOne
skeletonOne

若是你对骨架屏有更大的需求,能够在网上搜到更多的教程,这里就不列举了。github

首屏加载速度和seo的优化

单页应用(single page web application,SPA)一个缺点就是首次加载须要加载较多的内容,因此首屏加载时间就会比较长。另外,单页应用由于数据前置到了前端,不利于搜索引擎的抓取。因此咱们须要对本身的单页应用进行一些优化。这里咱们使用了prerender-spa-plugin这个webpack插件,他的做用就是将咱们指定的路由进行预渲染到html,进而解决首次加载白屏时间长问题,以及必定程度上解决seo问题。在vue-cli3.0中,咱们的相关配置是被隐藏起来的,咱们能够经过vue.config.js来将咱们的配置合并到默认配置中。web

// vue.config.js

const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')

module.exports = {
  configureWebpack(config) {
    if (process.env.NODE_ENV !== 'production') return;
    return  {
      plugins: [
        new PrerenderSPAPlugin({
          // Required - The path to the webpack-outputted app to prerender.
          staticDir: path.join(__dirname, 'dist'),
          // Required - Routes to render.
          routes: ['/'],
        })
      ]
    }
  },
}

效果:
prerender

上图是该app在网络环境为slow 3G下首次打开时的效果,能够看到整个过程,先由谷歌页面跳至browseExp,首先引入眼帘的是咱们的预渲染页面,它代替我网址跳转后应用加载的白屏时间,(前面的小段白屏是页面跳转的白屏,不是应用加载的白屏)而后加载完毕后就会去请求咱们的数据,这时候骨架屏就出现了,过渡这段页面局部白屏的时间,最后为真实的页面。
预渲染也有它的缺点:那就是预渲染的页面内容可能与真实内容由必定出入,并且还没法交互。因此若是应用的内容具备很强的实时性和交互性的话,能够考虑采用骨架屏的方式来进行首屏加载的白屏过渡,可是这样就没法优化seo了,因此按本身的实际场景来作选择。

另外对于首屏加载速度还能够经过组件懒加载的方式,对组件进行懒加载,只有当须要默写组件的时候才去加载他们,也能够减小首屏加载须要加载的文件大小,提升首屏加载速度,也有利于service worker对app shell进行颗粒度更小的缓存。结合Vue的异步组件和webpack的代码分割功能,轻松实现路由组件的懒加载,例如

// router.js经过动态import来引入组件,其余
import Vue from 'vue';
import Router from 'vue-router';
//这里用home组件作例子
const Home = () => import('./views/Home/Home.vue');

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home,
  ],
});

这样就能够对咱们的路由组件进行懒加载了,你会发现咱们的代码会按组件为单位打包成了多个js文件。

将项目升级为 pwa

在咱们的项目基本成型以后,能够考虑将其升级为pwa了。关于pwa是什么,我相信你们都知道,这玩意在国外已经火了几百年了,但国内除了几家大公司,貌似没多少人去尝试它,不过在上一年开始,pwa在国内仍是热了一下的。pwa是咱们在追求webapp便捷和原生应用良好体验结合的过程当中的产物,目前兼容性是最大障碍,但相信它在国内的前景仍是明朗的。pwa的特性有可离线、添加到桌面(一级入口)、后台同步、服务端推送等等,这个项目的话实现了可离线和添加到桌面这两个功能。起初听闻pwa时觉得会很复杂,实践后发现很简单。

ps: 开发过程在控制台的Application中可调试对应内容

workbox

workbox 是pwa的一个工具集合,围绕它的还有一些列工具,如 workbox-cli、gulp-workbox、workbox-webpack-plagin 等等,workbox自己至关于service worker的一个框架,封装了各类api,和缓存策略,可让咱们更加便捷的使用service worker。vue-cli3.0集成的是workbox-webpack-plagin,咱们能够经过vue.config.js的pwa配置项进行配置
首先,在vue.config.js文件中的进行配置,更详细的配置项

// vue.config.js

module.exports = {
  pwa: {
    // 一些基础配置
    name: 'Browsing-Exp',
    themeColor: '#6476DB',
    msTileColor: '#000000',
    appleMobileWebAppCapable: 'yes',
    appleMobileWebAppStatusBarStyle: 'black',

/*
* 两个模式,GenerateSW(默认)和 InjectManifest
* GenerateSW 在咱们build项目时候,每次都会新建一个service worker文件
* InjectManifest 可让咱们编辑一个自定义的service worker文件,实现更多的功能,而且能够
* 拿到预缓存列表
*/
    workboxPluginMode: 'InjectManifest',
    workboxOptions: {
      // 自定义的service worker文件的位置
      swSrc: 'src/service-worker.js',
      // ...other Workbox options...
    }
}

而后咱们须要在src文件目录下面新建一个service-worker.js,这里拿此项目作例子,workbox的经常使用接口有:

  • workbox.precaching 对静态支援进行缓存
  • workbox.routing 进行路由控制
  • workbox.strategies 提供缓存策略
  • 等等

更详细的 接口和配置教程

// src/service-worker.js

// 设置相应缓存的名字的前缀和后缀
workbox.core.setCacheNameDetails({
  prefix: 'browse-exp',
  suffix: 'v1.0.0',
});
// 让咱们的service worker尽快的获得更新和获取页面的控制权
workbox.skipWaiting();
workbox.clientsClaim();

/*
* vue-cli3.0经过workbox-webpack-plagin 来实现相关功能,咱们须要加入
* 如下语句来获取预缓存列表和预缓存他们,也就是打包项目后生产的html,js,css等* 静态文件
*/
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);

// 对咱们请求的数据进行缓存,这里采用 networkFirst 策略
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()
)

在这里,首先经过workbox.precaching.precacheAndRoute配置app shell的预缓存,而后就是经过workbox.routing.registerRoute对请求数据的缓存,由于对于请求的数据有必定的实时性要求,因此采用网络优先策略 networkFirst ,这里随便提一下相关的策略:

networkFirst

网络优先策略,优先尝试经过网络请求来获取数据,拿到数据后将数据返回给用户,并更新缓存,获取数据失败就使用缓存中的数据。

cacheFirst

缓存优先策略,优先获取缓存中的资源,若是缓存中没有相关资源,那么就发起网络请求。

networkOnly

顾名思义,只使用网络请求获取的资源

cacheOnly

顾名思义,只使用缓存中的资源

stateWhileRevalidate

此策略会直接返回缓存中的资源,确保获取资源的速度,而后再发起网络请求获取数据去更新缓存中的资源。若是缓存中没有对应资源的话就会发起网络请求,并缓存资源。

如何查看效果呢

这些配置可让咱们的得以在离线环境下运行,可是这些配置都是相对于打包出来的项目文件的,也就是dist文件里的内容。咱们在开发过程的dev模式是体验不到效果的,咱们怎么查看效果呢?

  • 方案1:编写一个后台服务,咱们能够经过node.js等编写一个后台服务去访问咱们的应用,service worker原本须要在https环境下运行,可是若是是本地 localhost 环境的话,service worker能够在http协议上运行。
  • 方案2:借助google提供的chrome扩展应用Web Server for Chrome为咱们的应用启动一个服务,比较灵活,因此我采用了这种方式。

Web Server for Chrome

点击choose foloer选择咱们的dist文件夹,勾选Automatically show index.html开启服务,咱们就能够经过下面的连接访问应用了,经过勾选Accessible on local network还能够生成另外一个地址,可让咱们在手机端访问应用。
webserver1

webserver2

manifest.json 网络应用清单

manifest.json 提供了将webapp 添加到设备主屏幕的功能,更详细的配置内容在此查看。咱们能够经过它给咱们的应用设置图标,启动动画,背景颜色等等。它在咱们项目的public下:

// public/manifest.json
// 最基本的配置内容

{
  "name": "浏览咱们的实验吧!",
  "short_name": "BrowseExp",
  "icons": [
    {
      "src": "/img/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/img/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#000000",
  "theme_color": "#4DBA87"
}

当浏览器(支持此功能的浏览器)检测到目录中的manifest.json文件时,就会读取其中的内容。在适当的时机弹出询问框,询问是否将应用添加到桌面。注意它不会在第一次访问就弹出,而是发现用户在必定时间内屡次访问该网站时才会弹出。在开发过程当中咱们能够点击Application -> Manifest -> Add to homescreen 触发弹框弹出。

移动端其余小问题

做为移动端web app,咱们须要解决一些常见的小问题,好比:

  • 各浏览器间样式统一问题
  • 移动端点击300ms延迟问题
  • 点透事件
  • rem的运用

1.各浏览器间样式统一问题

常见作法就是引入normalize.css重置咱们设备的默认样式,使得各浏览器的默认样式高度一致,避免咱们的布局出现意想不到的状况。

2.点击300ms延迟和点透事件

由于咱们的移动端的浏览器须要判断用户是否想要双击放大,因此会有一个300ms的延迟来查看用户是否双击屏幕;点透事件就是当咱们混用touch和click事件的时候,在touch事件响应后,若是该元素隐藏掉,那么300ms后同一位置的底层元素的click事件就会被触发。对于它们经常使用的解决方法就是引入 fastclick.js ,这个库的原理就是:修改浏览器的touch事件来模拟一个click事件,并把浏览器在300ms以后的click事件阻止掉。让前端开发人员能够以熟悉的click来书写代码

3.rem的运用

移动端咱们经常会使用到rem来进行响应式的布局,咱们一般会将htmlfont-size设置为 62.5%,那么咱们的 1rem = 10px,便于咱们的单位转换。

项目部署

开发完毕后,就须要把咱们的项目部署到本身的服务器上面去

编写一个服务

首先咱们编写一个后端服务,让咱们能够访问到项目的index.html文件,这里采用express起个服务。

// browse-exp.js
const fs = require('fs')
const path = require('path')
const express = require('express')

const app = express();

app.use(express.static(path.resolve(__dirname, './dist')))
app.get('*', function(req, res) {
  const html = fs.readFileSync(path.resolve(__dirname, './dist/index.html'), 'utf-8')
  res.send(html)
})

app.listen(3002, function() {
  console.log('server listening on port 3002!')
})

而后将项目经过好比ftp等工具上传到服务器,我用的服务器是nginx,它的特色就是轻量级,高并发,可配置反向代理。而后须要配置个代理将咱们对服务器的访问代理到该项目。在etc/nginx/conf.d目录下建立咱们的配置文件 holyzheng-top-3002.conf

# etc/nginx/conf.d/holyzheng-top-3002.conf

# 实例,表明咱们的应用
upstream browseexp {
  server 127.0.0.1:3002; 
}
# 将以http协议对咱们项目的访问转到https协议
server {
  listen 80; # http监听的端口
  server_name browseexp.holyzheng.top; # 我要使用的ip域名
  error_page 405 =200 @405; # 容许对静态资源进行POST请求
  location @405 {
    proxy_pass http://browseexp;
  }
  rewrite ^(.*) https://$host$1 permanent;
}

# 配置代理,将对域名browseexp.holyzheng.top的访问代理到服务端的127.0.0.1:3002
# 也就是咱们的应用
server {
  listen 443;
  server_name browseexp.holyzheng.top;
# 跟证书有关的配置,在申请证书的时候会有提示这部分配置
  ssl on;
  ssl_certificate /etc/nginx/cert/1538045542271.pem;
  ssl_certificate_key /etc/nginx/cert/1538045542271.key;
  ssl_session_timeout 5m;
  ssl_protocols SSLv2 SSLv3 TLSv1;
  ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
  ssl_prefer_server_ciphers on;

  if ($ssl_protocol = "") { # 判断用户是否输入协议
    rewrite ^(.*) https://$host$1 permanent;
  }

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;

    proxy_set_header Host $http_host;
    proxy_set_header X-Nginx-Proxy true;

    proxy_pass http://browseexp; # 要代理的实例
  }
}

这样咱们就能够经过对于域名来访问了来访问该项目了。这里给出对应二维码,能够进行访问查看:

qrcore

下面是在安卓端UC浏览器访问的结果(由于UC对pwa的支持十分好),在几回访问咱们的应用后就弹出了相关的提示,点击“好的”就能够添加到主屏幕了。

pwademo1

结语

我很是享受尝试新事物(本身没作过)的这个过程,此次记录下来并分享给你们,但愿对你们有帮助,若是你们看后有什么补充或意见的话,欢迎评论区提出。项目地址:browse-Exp

相关文章
相关标签/搜索