記錄一次基於vue、typescript、pwa的項目由開發到部署

前言

最近秋招之餘空出時間來按本身的興趣動手作了一個項目,一個基於vue,typescript,pwa的實驗瀏覽移動端webapp,如今趁熱打鐵,將這個項目從開發到部署整個過程記錄下來,並將從這個項目中學習到的東西分享出來,若是你們有什麼意見或補充也能夠在評論區提出。先介紹一下這個項目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

相關文章
相關標籤/搜索