PWA 係咩?唔知嘅就去讀喱篇文章,再返嚟。
有啲搞笑?將阿爺嗰代嘅 app 轉成 PWA,我都唔係咁信,不過嘅然 Google 己經將佢大部份嘅 apps 都改用 PWA,咁我都要試下如果將我啲 Visual WebGUI 寫嘅 web apps 轉成 PWA 會係點?
Proof-of-concept 我先簡化一個 Visual WebGUI app,將佢做成 PWA:
哪,都睇倒係咪?一個好簡單嘅功能,右上角有下拉 menu,menu 有 login、轉 dark theme、由 Plate 轉去 Film、Film 同 Plate 都有 checkboxes、有 upload 檔案功能,全 API 操作。
隻 GIF 好眼花,我哋去睇下 Google 嘅 Lighthouse 點講:
PWA 要求,幾乎全部達標!仲可以有得 install 添:
通過,用得,可以 deploy。
點整?依家講:
Create 一個 Visual WebGUI app,要係 library:
即係自己唔識 run 嘅,要由人哋 call 佢先有嘢睇嘅 project。你見喇,有個叫 BaseForm 嘅 嘢,配合 Forms. Film、Login、Plate、同 Theme 嚟,做到隻 GIF 入面嘅功能。然後就 create 一隻 asp.net app:
如上圖,啲 images 可以喺網上 gen 出嚟,通常係你上載一隻 512 Pixels 嘅 JPEG,佢就幫你 gen 其他出嚟,仲會俾埋啲正正常常嘅檔案名俾你,方便好用。
個 project 得幾個重要嘅 programs,Default.aspx、manifest.json、同埋 service-worker.js
隻 Web.config 係由個 library project 直接抄過嚟用就 okay,service-worker.js 係:
// Set this to true for production var doCache = true; // Name our cache var CACHE_NAME = 'x5-speedbox'; // Delete old caches that are not our current one! self.addEventListener("activate", event => { const cacheWhitelist = [CACHE_NAME]; event.waitUntil( caches.keys() .then(keyList => Promise.all(keyList.map(key => { if (!cacheWhitelist.includes(key)) { console.log('Deleting cache: ' + key); return caches.delete(key); } })) ) ); }); // The first time the user starts up the PWA, 'install' is triggered. self.addEventListener('install', function (event) { if (doCache) { event.waitUntil( caches.open(CACHE_NAME) .then(function (cache) { // Get the assets manifest so we can see what our js file is named // This is because webpack hashes it fetch("asset-manifest.json") .then(response => { response.json(); }) .then(assets => { // Open a cache and cache our files // We want to cache the page and the main.js generated by webpack // We could also cache any static assets like CSS or images const urlsToCache = [ '/', //'/Home/About', //'/Home/Index', //'/Home/Contact' ]; cache.addAll(urlsToCache); console.log('cached'); }); }) ); } }); // When the webpage goes to fetch files, we intercept that request and serve up the matching files // if we have them self.addEventListener('fetch', function (event) { if (doCache) { // SOLUTION: // i) detect your upload endpoint // ii) return so that you can use XHR if (event.request.url.indexOf('BaseForm') !== -1) { return; } event.respondWith( caches.match(event.request).then(function (response) { return response || fetch(event.request); }) ); } });
唔係好難明啫,manifest.json 仲簡單:
{ "short_name": "x5 SpeedBox", "name": "x5 SpeedBox PWA", "icons": [ { "src": "Resources/Images/pwa-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "Resources/Images/pwa-512x512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": "/", "background_color": "#222", "theme_color": "#222", "display": "standalone" }
Default.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="xFilm5.SpeedBox.Web.Default" %> <%@ Register Assembly="Gizmox.WebGUI.Forms" Namespace="Gizmox.WebGUI.Forms.Hosts" TagPrefix="vwg"%> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head runat="server"> <title>xFilm SpeedBox</title> <meta name="description" content="xFilm5 SpeedBox PWA" /> <meta charset="utf-8" /> <link rel="shortcut icon" href="/favicon.ico" /> <!-- Suppress the small zoom applied by many smartphones by setting the initial scale and minimum-scale values to 0.86 --> <meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=3.0, minimum-scale=0.86" /> <!-- Add manifest --> <link rel="manifest" href="/manifest.json" /> <!-- Tell the browser it's a PWA --> <meta name="mobile-web-app-capable" content="yes" /> <!-- Tell iOS it's a PWA --> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="theme-color" content="#536878" /> <link rel="apple-touch-icon" href="Resources/Images/ios-appicon-180-180.png" /> <link rel="apple-touch-icon" sizes="152x152" href="Resources/Images/ios-appicon-152-152.png" /> <link rel="apple-touch-icon" sizes="180x180" href="Resources/Images/ios-appicon-180-180.png" /> <!-- @*<link rel="apple-touch-icon" sizes="167x167" href="Resources/Images/touch-icon-ipad-retina.png" />*@ --> </head> <body style="margin:0px;"> <form id="form1" runat="server"> <div> <vwg:FormBox runat="server" Form="BaseForm" Height="400" Width="412" Title="VWG" /> </div> </form> <script> if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('service-worker.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful with scope: ', registration.scope); }, function(err) { // registration failed :( console.log('ServiceWorker registration failed: ', err); }).catch(function(err) { console.log(err) }); }); } else { console.log('service worker is not supported'); } </script> <noscript> <p>Your browser does not support JavaScript or it is disabled<br />This websit requires JavaScript to work properly.</p> </noscript> </body> </html>
#2 配合 #32 用,係 call 隻 Visual WebGUI library 入嚟,即係我用 aspx 包住隻 Visual WebGUI library 嘅 BaseForm;#12~#27、#35~#54 係標準答案,應該係人人都啱用。
隻 Default.aspx.cs 冇嘢嘅,不過都睇埋啦:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace xFilm5.SpeedBox.Web { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } } }
就係咁簡單,淨係講,你未必信,做出嚟俾你睇!