記錄一次基於vue、typescript、pwa的專案由開發到部署
最近秋招之餘空出時間來按自己的興趣動手做了一個專案,一個基於 vue,typescript,pwa
的實驗瀏覽移動端webapp,現在趁熱打鐵,將這個專案從開發到部署整個過程記錄下來,並將從這個專案中學習到的東西分享出來,如果大家有什麼意見或補充也可以在評論區提出。先介紹一下這個專案
專案介紹

基於vue,typescript,pwa的一個移動端webapp,取名叫browseExp,主要功能是瀏覽學校心理學院部分實驗資訊。(上圖是新增到桌面的一級入口)。這個專案已經部署到了伺服器上,我們看一下專案最終在客戶端執行的樣子

可以看到我通過桌面上的一級入口,進入了我們的webapp,並且在斷網的條件下進行。這就是pwa的作用,下面開始分享這次的開發到部署的過程。
為什麼要做這個專案呢?
- pwa 在國內已經火過一段時間了,但是自己還沒做過一款pwa應用。
- vue-cli 3.0 增加了對pwa的支援
- vue2.5後增加了對ts的支援
- 想搞事情!
開發過程
這個專案的地址為: ofollow,noindex">browseExp pwa ,想要檢視程式碼的同學可以看一下。這個專案要注意的點主要是:
- 在vue中使用ts
- 簡單骨架屏的運用
- 首屏載入時間和seo的優化
- pwa相關特性的實現
- 移動端的一些問題解決
- 如何部署專案
後面的內容也圍繞著這些點來展開。
vue中使用ts
使用ts主要是因為ts給我們帶來了型別系統,可以讓我們寫出健壯的程式碼,它的作用在大型專案中尤其突出,所以還是非常鼓勵大家去使用的,我們使用ts進行開發一般是編寫基於類的vue元件,所以可以使用官方維護的 vue-class-component 或者 vue-property-decorator ,vue-cli3.0也給我們提供了開箱即用的typescript支援,開發體驗還是相當友好的。一個vue元件demo:
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
定義第三方包的型別宣告。
骨架屏的簡單運用
骨架屏(skeleton screen)已經不是什麼新奇的概念,他的主要作用就是用來過渡頁面的空白狀態,提升使用者體驗,比如頁面跳轉等待,資料載入等待等,傳統的骨架平實現方案有 服務端渲染和預渲染等,而這個專案中引入骨架屏主要是想過渡資料載入時頁面的區域性空白狀態,所以就直接採用編寫一個骨架屏元件 SkeletonExp.vue 的方式來過渡。


如果你對骨架屏有更大的需求,可以在網上搜到更多的教程,這裡就不列舉了。
首屏載入速度和seo的優化
單頁應用(single page web application,SPA)一個缺點就是首次載入需要載入較多的內容,所以首屏載入時間就會比較長。另外,單頁應用因為資料前置到了前端,不利於搜尋引擎的抓取。所以我們需要對自己的單頁應用進行一些優化。這裡我們使用了 prerender-spa-plugin
這個webpack外掛,他的作用就是將我們指定的路由進行預渲染到html,進而解決首次載入白屏時間長問題,以及一定程度上解決seo問題。在vue-cli3.0中,我們的相關配置是被隱藏起來的,我們可以通過vue.config.js來將我們的配置合併到預設配置中。
// 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: ['/'], }) ] } }, } 複製程式碼
效果:

上圖是該app在網路環境為 slow 3G
下首次開啟時的效果,可以看到整個過程,先由谷歌頁面跳至browseExp,首先引入眼簾的是我們的預渲染頁面,它代替我網址跳轉後應用載入的白屏時間,(前面的小段白屏是頁面跳轉的白屏,不是應用載入的白屏)然後載入完畢後就會去請求我們的資料,這時候骨架屏就出現了,過渡這段頁面區域性白屏的時間,最後為真實的頁面。 預渲染也有它的缺點 :那就是預渲染的頁面內容可能與真實內容由一定出入,而且還無法互動。所以如果應用的內容具有很強的實時性和互動性的話,可以考慮採用骨架屏的方式來進行首屏載入的白屏過渡,但是這樣就無法優化seo了,所以按自己的實際場景來做選擇。
將專案升級為 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
還可以生成另一個地址,可以讓我們在手機端訪問應用。


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來進行響應式的佈局,我們通常會將 html
的 font-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; # 要代理的例項 } } 複製程式碼
這樣我們就可以通過對於域名來訪問了來訪問該專案了。這裡給出對應二維碼,可以進行訪問檢視:

下面是在安卓端UC瀏覽器訪問的結果(UC對pwa的支援十分好),在幾次訪問我們的應用後就彈出了相關的提示,點選“好的”就可以新增到主螢幕了。

結語
我非常享受嘗試新事物(自己沒做過)的這個過程,這次記錄下來並分享給大家,希望對大家有幫助,如果大家看後有什麼補充或意見的話,歡迎評論區提出。專案地址: browse-Exp