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)
{
}
}
}
就係咁簡單,淨係講,你未必信,做出嚟俾你睇! 








