[生活筆記] 2022 新冠(武漢)肺炎疫情

進況

2021 年 5 月 ~ 7 月進入 3 級後,理論上都是在 2 級的狀態,
入境人數每日約有 50~100 名確診者入境,
到了 2022 年 4 月以來,疫情指數成長
爆
近日單日確診已破 90000 例。

重複發生的事

  • 2020 搶/吵口罩
  • 2021 搶/吵疫苗
  • 2022 搶/吵快篩

該作的事

  • 載口罩
  • 勤洗手
  • 少去人多的地方
  • 注意自身生心理的變化

現況的因應方針

準備適量的快篩,以 2 人的小家庭其實 1 盒(5 劑)已經夠用,
買不到就作好該作的事,台灣的狀況是可以在幾周到幾個月內調整到位的。
(參考口罩、疫苗與雞蛋的狀況)
無症狀就正常生活,保持樂觀,不要過度被搧動性的偽新聞網軍談話節目影響,
如果真的要看,請深入研究各方的說詞,不要因喜好而偏重某些節目。

自覺有症狀先快篩,
陰性就保持正常生活,擔心的話請就醫。
陽性參考 CDC 指示,自我隔離或就醫,並聽從醫囑。

有症狀而無法篩檢的就可以先自我隔離,
所幸我的工作模式可以遠端處理大部份事宜,
家中也沒有老小,並且都接種過三劑疫苗。
正面面對目前的疫情,並不想介入過多的口水,
看數據作事,或許有點無情但是大局來說才是正確的方向。

分不清的名詞

角色有確診者、密切接觸者、其它
方針居家檢疫、居家隔離`、居家照護、
隔離、自主健康管理、自主防疫、
自我健康監測 ??、自主應變 ??

總覺得每天都在產生新名詞呢

資訊的取得

  • 參考國外數據
  • 聽取 CDC/(所在)縣市記者會
  • 多方參考海外新聞、數據報表
  • 盡可能獲取一些科普、醫療相關的知識,但是要注意背後的科學支持是否可信

心態上不要盲信,也不需要被風向帶到彼此敵視

未來的方向

長期來說,只能試著與病毒共存,
讓整體的生活恢復正常,問題是這個過程是要付出多少的代價 ?

第一個全面接種疫苗,
我們的第一劑覆蓋率只有 85% ,第二劑 80% ,第三劑 69%(資料來源 202205021,GOOGLE
對比世界其它國家不算低,但是疫苗的效力會隨時間下滑,所以定期要打加強劑,
此外仍有特定族群無法接種疫苗,特別為小孩與慢性病者,應儘快加速孩童疫苗的進口與接種.
而之前打氣下滑導致疫苗過期而被丟棄,實在十分浪費。

指揮中心發言人莊人祥 3 月底曾表示,莫德納及 BNT 約有 26.4 萬劑、AZ 則約 30 萬劑,
共計 56.4 萬劑疫苗因到期得銷毀。根據疾管署最新統計,
台灣 COVID-19 疫苗接種人口涵蓋率第 1 劑 84.57%、第 2 劑 79.84%、追加劑接種率 58.24%。

ETtoday <<3 天 1.6 萬例!瘋搶疫苗 醫嘆:丟掉你罵浪費,現在罵打不到?>> 記者陳俊宏/綜合報導

第二個,在不破壞醫療量能前提下,儘可能讓抵抗力高的人感染而達成自然免疫。
不論如何會有人因此死亡,特別是未接種者,
雖然接種疫苗中壯年在數據上中重率不高,
但是對於個體而言,代表的是一個活生生的生命。
沒有人想當那個 0.03 %。
不要過渡消耗醫療量能,
目前快篩的排隊或是 PCR 的排隊,有些人是為了領取保險金,取得確診証明。
無症或輕症者,其實正在壓縮真正需要就醫的人的生命,
讓醫護人員花費大量時間在作文書與檢驗作業。

無症或輕症者,雖是下策,可以透過線上或是購買成藥等方式來撐過疫情。
如果藥物足夠的話,快篩陽且有診狀,醫師就該判斷是否進行投藥,避免後續重症的發生。

(fin)

[活動筆記] 線上活動 TypeScript tips and Tricks with Matt

前情提要

YouTube 頻道Visual Studio Code
前陣子推出了由大神 Matt 所介紹有關 TypeScript 奇技淫巧影片
相當燒腦,特別作此記錄。

5:06 generic-table-component

難度:★★☆☆☆

這題需要有一些 React 的基礎概念,
首先我們有一些 React Component(以下簡稱 RC),
並且用 interface 建立相對應 props。

問題是如果要讓我們的 RC 更泛用應該怎麼作,
在這個例子中,我們建立了一個 Table RC(以下簡稱 Table) ,並且在 props 中傳入一些參數,
這個參數的型別,直接與 Table 產生了耦合,我們可用泛型(Generic)處理。

Before

1
2
3
4
5
6
7
8
interface TableProps {
items: { id: string }[];
renderItem: (item: { id: string }) => React.ReactNode;
}

export const Table = (props: TableProps) => {
return null;
};

After

1
2
3
4
5
6
7
8
interface TableProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}

export const Table = <T,>(props: TableProps<T>) => {
return null;
};

這裡是很簡單的泛型觀念,如果有寫過具備泛型的語言(ex:C#)應該不難理解,
另外需要注意的一個有關 React 的小小 Tricky
注意到 <T,> 這裡有一個小小的逗號,其實是不必要的,
但是在 React 的開發環境之中,角括號<>會被視為未封閉的 RC;
封閉的例子,ex:<myComponent></myComponent> 或是 <myComponent />
未了必免編輯器的警告,需要加上一個逗號,

另外這裡在寫 React Component 時,使用了 arrow function,
我們也可以換成一般的 function 寫法,這種寫法就可以省略 <T,> 這樣 tricky 的寫法

1
2
3
export const Table = function <T>(props: TableProps<T>) {
return null;
};

泛型不難理解,但是考量到需要具備的 React 知識,難度我給他兩顆星

11:19 get-deep-value

難度:★★★☆☆

這題要動態的取出物件深層的屬性的型別,
具體好處是可以讓編輯型別檢查

看一下影片中的範例 getDeepValue 是一個函數,可以用來取物件深層屬性的值
這裡我們看到 obj:any Matt 說道可以視作一個壞味道(不知道他對 unknown 的看法)

1
2
3
export const getDeepValue = (obj: any, firstKey: string, secondKey: string) => {
return obj[firstKey][secondKey];
};

看一下原始範例如下

1
2
3
4
5
6
7
8
9
10
const obj = {
foo: {
a: true,
b: 2,
},
bar: {
c: "12",
d: 18,
},
};

我的作法

如果比較直觀的寫法可以考慮寫個 ObjectType,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// this one is NOT from the sample

const obj: ObjType = {
foo: {
a: true,
b: 2,
},
bar: {
c: "12",
d: 18,
},
};

type FirstKeyType = "foo" | "bar";
type SecondKeyType = "a" | "b" | "c" | "d";
type ObjType = {
[key in FirstKeyType]: { a: boolean; b: number } | { c: string; d: number };
};
export const getDeepValue = (obj: ObjType //...

註:這裡有用到 index-signatures 的概念
看看官方文件的說法:

1
2
3
Sometimes you don’t know all the names of a type’s properties ahead of time, but you do know the shape of the values.

In those cases you can use an index signature to describe the types of possible values
1
const value = getDeepValue(obj, "foo", "a");

Matt 在影片中的作法

1
2
3
4
5
6
7
8
9
10
11
export const getDeepValue = <
TObj,
TFirstKey extends keyof TObj,
TSecondKey extends keyof TObj[TFirstKey]
>(
obj: TObj,
firstKey: TFirstKey,
secondKey: TSecondKey
) => {
return obj[firstKey][secondKey];
};

keyof 關鍵字可以直接參考官方文件

比較兩者的作法

我們加上了 Type,就是要為程式碼加上保護
我跟 Matt 的作法,下面的程式碼都會提出警告

1
var error_value = getDeepValue(obj, "error", "wrong");

但是無法避免以下的錯誤

1
var error_value = getDeepValue(obj, "foo", "c");

obj.foo 是不存在 c 屬性,這樣我們就需要動態判斷型別
看看 Matt 使用 keyof 作法。

1
2
3
4
5
6
7
export const getDeepValue = <TObj, TFirstKey extends keyof TObj>(
obj: TObj,
firstKey: TFirstKey,
secondKey: keyof TObj[TFirstKey]
) => {
return obj[firstKey][secondKey];
};

這裡有點不可思議,首先是 ts 的動態型別推導,
在呼叫方法 getDeepValue 的第 1 個參數會動態推導出 TObj 的型別
大概等價以下的型別

1
2
3
4
5
6
7
8
9
10
type TObj = {
foo: {
a: boolean;
b: number;
};
bar: {
c: string;
d: number;
};
};

keyof TObj 的值會是 foo | bar
而 TFirstKey 的型別,在第 2 個參數傳入之時決定是 foobar
不是上述兩種的參數傳入時,編輯會拋出錯誤警告,非常好用。
下一個燒腦的部份,第 3 個參數 secondKey: keyof TObj[TFirstKey]
透過類似陣列取值的手法,我們可以在傳入第 2 個參數時決定第 3 個參數的值,並且讓編輯器提供保護。
例如,第 2 個參數為 foo 時,第 3 個參數會限定只能傳入 "a"|"b"

這裡需要思考一下,並不難理解,所以給 3 顆星。

如果能應用得好的話,對於開發 ts Library 會相當有幫助
不過我覺得自已無法用得很得心應手,比如說,如果物件深度不固定,應該如處理?
又或者如果不用 keyof 的技巧,對一個已知的物件,我能不能作到類似的效果(編輯器動態檢查)?

28:54 conditional-types

難度:★★★☆☆

這其實是 TypeScript 一個基本的概念與技巧,
可以在官方文件看到更多資訊

一個標準的寫法如下

1
SomeType extends OtherType ? TrueType : FalseType;

記得這裡最終只會回傳 TrueType 或是 FalseType,而 SomeType 只是作為條件檢查。
當然也可以作多重的條件檢查

1
2
3
4
5
SomeType extends OtherType ?
TrueType :
SomeType extends OtherConditionType?
OtherThing :
FalseType;

35:20 更進一步

我們看一下怎麼應用 Condition Type 這個技巧?
參考error-messages-in-ts這個教案
在 deepEqualCompare 這個方法之中,陣列比較需要例外處理,
因為陣列比較永遠會回傳 false
原本的作法,會在程式的 runtime 作型別檢查並拋出例外

1
2
3
4
5
6
7
export const deepEqualCompare = <Arg>(a: Arg, b: Arg): boolean => {
// runtime handle
if (Array.isArray(a) || Array.isArray(b)) {
throw new Error("You cannot compare two arrays using deepEqualCompare");
}
return a === b;
};

而透過 condition type,可以讓編輯器在開發時直接檢查,看起來更加的優雅

1
2
3
4
5
6
export const deepEqualCompare = <Arg>(
a: Arg extends any[] ? "not allow array" : Arg,
b: Arg extends any[] ? "not allow array" : Arg
): boolean => {
return a === b;
};

這應該算是 TypeScript 的基本工,也有在官方文件上有很清楚的講解,
對我來說,第一次看到仍然相當驚訝,我給他三顆星
另外,我有兩個延申問題,

  1. 程式碼中有 any 算不算壞味道? 我是不是應該用 unknown[] 取代 any[]
  2. Arg extends any[] ? "not allow array" : Arg 明顯出現了重複,我有沒有什麼技巧可以消除這樣的重複?

其它

整個影片還有許多燒腦的部分,
Q&A 或是最後的挑戰都有相當多有趣的東西的可挖掘,
有機會我再補上。

參考

(fin)

[實作筆記] 自定義 Hexo Helper

前情提要

我的 Blog 目前是使用 Hexo 搭建的,
使用的主題是 landscape
隨著時光推移, Hexo 漸漸從人們的目光淡出,
主功能、文件、翻譯或是外掛、主題等…, 更新速度變慢很多

我想改寫主題當中的某個輔助函數(Helper), 稍微記錄一下作法。

實作

首先是檔案位置,一個是在根目錄底下建立 scripts 資料夾,
或者你正在使用主題(Theme)底下。
以我的例子來說,我會放在 themes/landscape/scripts 底下

參考下面的圖示

1
2
3
4
5
6
7
8
├── scripts
│ ├── index.js
│ └── your_helper_here.js
├── themes
│ └── the_theme_you_are_using
│ └── scripts
│ ├── index.js
│ └── your_helper_here.js

下一步,你需要建立 index.js,並在其入註冊你的函數,

1
2
3
4
//hexo.extend.helper.register(name, function)
hexo.extend.helper.register("helper_name", function () {
return "<div>your helper function </div>";
});

通常 helper 不會是一個簡單的函數,且會有 UI 相關的程式,
有為了方便管理,我會拆檔,再用 require 來引入函數

1
hexo.extend.helper.register("helper_name", require("./helper_file_name"));

最後,在 *.ejs 檔案中使用 helper

1
<%- helper_name() %>

參考

(fin)

[實作筆記] Dotnet Core 實作隨筆

Web/Api 專案統一錯誤處理

首先建立一個新的類別 ErrorHandlingFilter
繼承 ExceptionFilterAttribute 並複寫 OnException 函數

1
2
3
4
5
6
7
public class ErrorHandlingFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
//do your logical here
}
}

在 Program.cs 中注入

1
builder.Services.AddControllers(options => { options.Filters.Add(new ErrorHandlingFilter()); });

專案更改 Root Namespace

預設會使用專案名稱作為整個專案的 Root Namespace,
但是我們可以在 csproj 檔中設定 RootNamespace 讓它與專案名稱有所差異

1
2
3
4
5
6
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>MyNameSpace</RootNamespace>
</PropertyGroup>
</Project>

Nuget

add nuget registry

1
dotnet nuget add source <registry url> -n SourceName -u UserName -p Password --store-password-in-clear-text

pack

1
dotnet pack -o <output folder> -c NUGET --no-dependencies --force  -p:PackageVersion=<version>

push

1
dotnet nuget push -s SourceName <Packed Version>

建立共用的 HttpClient

在 Program.cs 中註冊

1
2
3
4
5
6
7
builder.Services.AddHttpClient("ThirdService", (provider, client) =>
{
var o = provider.GetRequiredService<IOptions<ThirdServiceOptions>>();
client.BaseAddress = new Uri($"{o.Value.Url}/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", o.Value.Token);
});

再透過 HttpClientFactory 具名取用

1
2
3
4
public MyService(IHttpClientFactory httpClientFactory)
{
HttpClient _httpClient = httpClientFactory.CreateClient("ThirdService");
}

最小化 Docker Image

這一種被稱為 Multiple Stage Builds 的 Docker 技巧,
主要的概念是將建置(Build)與部署(Deploy)的步驟分開,
最終只部署出營運所需的映象檔(Image)。

比如說下面的 Docker File 就是將 dotnet 專案作了兩步驟的處理

此外,如果我們更換適當的 Image,
比如說,在 Create Runtime Image Step,
mcr.microsoft.com/dotnet/aspnet:6.0 改為 mcr.microsoft.com/dotnet/aspnet:6.0-alpine
映象檔的 Size 將減少 46.6% (216.11 MB → 115.4 MB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Build Step
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source
# copy everything else and build app
COPY /src/WebAPI/. ./WebApi/
WORKDIR /source/WebApi
RUN dotnet restore 'WebApi.csproj'
RUN dotnet build 'WebApi.csproj' -c release -o /app --no-restore
RUN dotnet publish 'WebApi.csproj' -c release -o /app --no-restore

# Create Runtime Image Step
FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine
ENV ASPNETCORE_URLS=http://+:5110
WORKDIR /app
COPY --from=build /app ./
EXPOSE 5110
ENTRYPOINT ["dotnet", "WebApi.dll"]

參考

(fin)

[實作筆記] ASP.Net Core Config 的隨筆

提要

  • DI Configuration
  • 組態的層次
  • Options Pattern

DI Configuration

dotnet webapi 專案預設會注入 IConfiguration ,
在專案中的程式建構子放入 IConfiguration 就可以直接取得組態設定

1
2
3
4
5
6
7
8
9
10
11
12
private readonly IConfiguration _config;

public WeatherForecastController(IConfiguration config)
{
_config = config;
}

[HttpGet(Name = "GetWeatherForecast")]
public string Get()
{
return _config["ASPNETCORE_ENVIRONMENT"]!;
}

你可以將組態設定在 appsettings.json 之中
或是透過環境變數,使用環境變數有幾種作法

  • 設定在機器上
  • 設定在 Cloud Service 所提供的組態管理工具之中
  • 使用 launchSettings.json

在開發環境我會使用 launchSettings 好處是可以不需要真的在開發機的環境中設定環境變數,
對同時擁有多個專案的開發者而言,可以有效隔離不同的專案變數,避免彼此影響污染

組態的層次

為了方便管理,我們可以透過組態的層次來加以分門別類。
比如說:

1
2
3
4
5
6
7
8
"Level": {
"First": {
"Key": "1st Value"
},
"Second": {
"Key": "2nd Value"
}
},

一般的 JSON 或 XML 要作到這樣的層次結構都相當的簡單,
而要取用組態的方式如下:

1
var key = _config["Level:First:Key"];

不過我們要注意環境變數並無法使用:字符,也沒有 JSON 或是 XML 所能提供的結構,
這裡要注意可以用 __(雙底線(Double underscore))來建立層次。

1
2
$Level__First__Key
$Level__Second__Key

Options Pattern

Options Pattern 可以提供一種更物件導向的組態配置解決方案,
你只需要兩個步驟,即可實作 Options Pattern

  1. 建立 Option 物件
    我在這裡提供二種不同的方法,後面會再說明

    1
    2
    3
    4
    5
    6
    //方法一 Dictionary
    public class LevelOptions
    {
    public const string Level = "Level";

    public Dictionary<string,string> First { get; set; }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //方法二 Object
    public class LevelOption
    {
    public const string Level = "Level";

    public FirstOptions First { get; set; }
    }

    public class FirstOptions
    {
    public string Key { get; set; } = string.Empty;
    }
  2. 註冊 Option 物件

1
builder.Services.Configure<LevelOptions>(LevelOption.Level)

最後是讀取組態,在方法一(使用 Dictionary)的方法讀取

1
var key = _options.Value.First["Key"];

或是方法二(使用 Object)的方法讀取

1
var key = _options.Value.First.Key;

兩種方法都可以,物件化的方法需要建立多個物件,
但是在取用組態的過程就不會有 hard coding string;
反之 Dictionary 的方法可以少寫很多物件。

對我而言我比較偏向使用 Object 的方法去處理,只要適當的分類,
物件多並不是問題,而且通常一次不會增加太多的組態,可以迭代增量。

順代一提,如果組態檔層次太多,會是另一個困擾 ? 我的建議是,如果組態的深度到達 3 時,
需要考慮這是不是一個壞味道,是不是過度優化了 ?
實務上來說,我會避免建立超過 3 層,也很難會有一定要超過 3 層的情境,
如果你有什麼情境上需要超過 3 層,可以留言給我,我們可以一起討論。

在 Program.cs 使用

如果在 Program.cs 的注入階段,如果要取得組態,
可以用
(new ConfigurationManager()).GetSection("First")["Key"]
或是 Environment.GetEnvironmentVariable("Level__First__Key"); 等方法取得組態。
但是我更推薦 ServiceProvider 的 GetRequiredService 取得 Options

1
2
3
4
5
6
7
8
9
10
//注入在前
builder.Services.Configure<LevelOption>(builder.Configuration.GetSection(LevelOption.Level));
builder.Services.AddHttpClient("OtherService", (provider, client) =>
{
//透過 provider 取得 config
var o = provider.GetRequiredService<IOptions<LevelOptions>>();
client.BaseAddress = new Uri($"{o.Value.Url}/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", o.Value.Token);
});

這樣就可以用一致的方法取得組態了。

參考

(fin)

[實作筆記] Hexo 更新 Google Analytics 4

前情提要

幾天前收到來自 Google Analytics 的這樣一封信

1
2
3
4
5
Google Analytics (分析) 4 即將取代通用 Analytics (分析)

我們即將以新一代的成效評估解決方案 Google Analytics (分析) 4,
取代通用 Analytics (分析)。從 2023 年 7 月 1 日起,通用 Analytics (分析) 資源將停止處理新的命中資料;
如果通用 Analytics (分析) 仍是您常用的工具,建議您採行相應措施,準備好改用 Google Analytics (分析) 4

實作步驟

參考以前我寫的這篇–如何讓 Google Analytics 追踪你的 Hexo Blog
我們知道在 hexo 的佈景主題 landscape 有提供舊版 GA 的設定,
我們只需要修改 theme/layout/_partial/google-analytics.ejs 即可
程式參考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
<% if (theme.google_analytics){ %>
<!-- Google Analytics -->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= theme.google_analytics %>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', '<%= theme.google_analytics %>');
</script>
<!-- End Google Analytics -->
<% } %>

由於通用 GA 在 2023 年後就不再支援,所以我就不特地處理相容,直接切換到 GA4

參考

(fin)

[實作筆記] 簡單的方法 Mock TypeScript Interface

前情提要

我目前在實作一個 React 的專案,並結合 Storybook 來實現 CDD(component driven development),
我很喜歡 CDD 的概念,這裡解決 TDD 對 View 難以趨動開發的痛點,
但是又維持了 Unit Test 的基本精神,進一步結合 Chromatic 可以提供視覺化的檢核頁面,
作為與 PO/PM/UX/UI/DESIGNER 之間的溝通工具非常好用。

問題

我作了一個簡單的 Header 組件,
這通常是網站或 APP 上方的區塊,通常會提供一些額外的資訊或功能,
例如: 登入/登出,而這裡我使用 Firebase 作為實作登入/登出的功能,
作為主要的邏輯判斷,firebase 的 Auth 物件會被傳入

1
2
3
Header = (props: { sighOut: () => void; auth?: Auth }) => {
//...
};

這裡我們先不討論 Auth 的邏輯是否應該與 Header 相依(嗯,這裡看起來是個壞味道),
而是在我們的 Storybook 撰寫測試案例時,應該如何 mock Auth 物件。
我的程式都是用 TypeScript 與 tsx 寫的,
所以如果不符合型別,IDE 會直接報錯,程式跟本無法執行

1
2
3
4
5
6
7
8
const Template: ComponentStory<typeof Header> = () => (
<Header
sighOut={() => {
alert("mocked Signout");
}}
auth={mockAuth}
/>
);

試過的方法

  • jest Mock
  • ts-mockito
  • ts-auto-mock

以上的方法有的成功,有的失敗,最大的問題是在配置與撰寫其實很麻煩,
甚至會有同事說為什麼要用 TypeScript 自找麻煩呢 ?

逐一 mock 所有 interface 的屬性,對於一個像 Auth 這樣複雜的介面實作上相當麻煩,
而且大多 mock 的屬性都用不到

我的想法是,應該是我對 TypeScript 與其生態圈不夠了解,寫起來才會覺得綁手綁腳的,
不能推缷給程式語言,同時 TypeScript 強型別的好處,其實也幫了我不少。
仔細研究後,我找到一個簡單方法如下:

解法

不需要 mock Library , 不需要麻煩的設定或是第三方套件,
最簡單的寫法 as 就可以將物件 mock 成為你想要的 Interface

1
2
3
const mockAuth = {
currentUser: { email: "[email protected]" },
} as Auth;

這樣的寫法除了可以只 mock 必要的屬性外,
如果你物件中包含了不屬於介面應有的屬性,
TypeScript 將會提出警告,非常好用。

(fin)

[實作筆記] 升級現有基於 TypeScript 的 React 17 專案到 React 18

前情提要

升級 React 在官方的文件之中,似乎是一件極為簡單的事,
但是如果你的專案是基於 TypeScript 開發,
在前幾周應該無法順利升級,主要的原因是相對應的 @types 套件並未同時更新,
幸運的是,現在看到文章的你,已經可以順利更新了,請參考以下的步驟。

記錄

安裝 react、react-dom、@types/react、@types/react-dom

1
npm i react@18 react-dom@18 @types/react@18 @types/react-dom@18

更改 ReactDOM.render 的寫法

原本的寫法

1
2
3
4
import React from "react";
import ReactDOM from "react-dom";

ReactDOM.render(<App />, document.getElementById("root"));

後來的寫法

1
2
3
4
5
6
import React from "react";
import ReactDOM from "react-dom/client";

const ele = document.getElementById("root") as Element;
const root = ReactDOM.createRoot(ele);
root.render(<App />);

特別注意要處理 Element 的型別

參考

(fin)

[踩雷筆記] Brave 瀏覽器與本機開發 http

前情提要

Brave 是一款主打安全、注重隱私的瀏覽器,雖然目前仍相當小眾,
但在開發者中有一定使用者。

https 通訊協定現在已經是一個 Web 服務、網站、api 的基本安全配備,但是對於開發者來說,
在本地開發常常僅使用 http 通訊協定就足以作功能性的驗証。

情境

使用 dotnet core 建立了一個 web api 的服務,預設的啟動站台 end point 為 http://localhost:5000
而 Brave 作為預設瀏覽器開啟後,會看到 403 的錯誤頁面。

1
2
3
找不到 localhost
網頁找不到此網址的網頁:http://localhost:5000/
HTTP ERROR 404

解決方法

正常的思路,應該是讓本地開發環境建立起 https 的連線,這裡又會有相關的憑証問題要處理,
應該有蠻多不同的作法,改天我再寫一篇相關的介紹。

短解很簡單,換個瀏覽器就可以了,
如果堅持要用 Brave ,可以至設定 > 防護 > 關閉「升級連線至 HTTPS」的開關即可。
順代一提,當我使用 hexo、React 等工具建立靜態網站時不會有這個問題。

參考

(fin)

[實作筆記] 靜態網站部署整合 GCP (二) --- Load Balancing & Cloud Storage

前請提要

去年有記錄一篇有關 GCP 部署靜態網站時遇到情境與問題,
不過比較偏像流水帳,不同的部署策略都在同一篇文章,另外有一些問題也得到解決,
除了更新原有文章外,這篇的目的是為了將 Load Balancing 與 Cloud Storage 抽出,
並加以潤飾。

概要

  • 在 Cloud Storage 建立一個 Bucket
  • 在 Network Service 建立一個 Load Balancing
  • 設定 Load Balancing 的 Backend 綁定到 Bucket
  • 作為網站,需要允許所有人讀取 Bucket 的內容

必要條件

GCP 平台的帳戶與足夠的權限,並且已建立專案

在 Cloud Storage 建立一個 Bucket

第一步前往Cloud Storage
點擊 +CREATE BUCKET,
在 Choose where to store your data 的區塊,
Location type 有三種

  • multi-region
  • dual-region
  • region

這是地理位置與高可用性的相關設定,越後面的設定成本越便宜,但是可用性也越低。
即使如此 google 仍保証了 SLA: 99.95% 的高可用性。

接下來是 Choose a default storage class for your data 的區塊,有

  • Standard
  • Nearline
  • Coldline
  • Archive

等四種不同的設定,
與檔案的使用頻率有關,對於網站來說建議使用 Standard 。
收費可以參考下表

Standard Nearline Coldline Archive
Storage(per GB-Month) $0.026 $0.01 $0.007 $0.004
retrieval(per GB-Month) Free $0.01 $0.02 $0.05
Class A Operations(per 1000ops) $0.005 $0.01 $0.01 $0.05
Class B Operations(per 1000 ops) $0.0004 $0.001 $0.005 $0.05
SLA 99.95% 99.9% 99.9% 99.9%

接下來是 Choose how to control access to objects 的設定,
不要勾選 Enforce public access prevention on this bucket,
Access control 選擇 Uniform , 這裡的設定是為了避免從 internet 存取 bucket 的資料,
但是我們的目的是放置靜態網站的資料,所以不需設定。

再來是 Choose how to protect object data
這是保護資料的策略,有版本(versioning)與備份(retention)兩種策略,
我們不需要所以選擇 None

按下 Create 以建立 Bucket,
接下來為了讓 web 存取我們選擇 more action(3 個點的 Icon ) > Edit Access
New Principals > 選擇 allUsers > Storage Object Viewer.
接下來可以上傳你的靜態網站的資源了,這裡我們多作一個設定,通常我們要指定網站的首頁為何,
約定成俗是 index.html,一樣 more action(3 個點的 Icon ) > edit website configuration
將 Index (main) page suffix 設定為 index.html(記得 bucket 裡要有這個檔)

Load Balancing 相關設定

接下來設定 Load Balancing,
這只需要設定一次,在目前的實作未涉及 VPC、CDN、Path Rules 等相關議題,
實務上需要的話,請再加上去考量。

  1. GCP > Network Service > Load Balancing
  2. Create Load Balancer
  3. Backend configuration > Create A Backend Bucket
    • 自已取一個 Backend Bucket name
    • Cloud Storage Bucket > Browse > 選取之前所建立的 Bucket
    • 不勾選 Enable Cloud CDN, 相關的設定與應用程式的應用有關, 較為複雜之後再進行處理
  4. Host and path rules > Simple host and path rule
  5. Frontend IP and PORT > Add Frontend IP And Port
    • Protocol > HTTP (正常應使用 HTTPS, 這次為了求快而未作相關設定)
    • Network Service Tier > Premium

可以參考 GCP 的教學與我們實作上的細微差異

  • Create a bucket. → 手動建立只需要處理一次
  • Upload and share your site’s files. → CI 執行
  • Set up a load balancer and SSL certificate. → 手動建立只需要處理一次
  • Connect your load balancer to your bucket. → 手動建立只需要處理一次
  • Point your domain to your load balancer using an A record. → 未處理
  • Test the website.

CI/CD 的相關設定 (已過時)

參考以下部份的 .gitlab-ci.yml 檔
非常簡單,只需要 gsutil rsync -R 將前一個 job 建置的檔案推到 Cloud Storage 即可

1
2
3
4
5
6
7
8
9
10
deploy-job: # This job runs in the deploy stage.
stage: deploy # It only runs when *both* jobs in the test stage complete successfully.
image: google/cloud-sdk
needs:
- job: build-job
artifacts: true
script:
# - gcloud auth list # Show the ACTIVE ACCOUNT *
- gsutil rsync -R build gs://your_bucket_name
- echo "Application successfully deployed."

其它(已過時)

這裡仍有一個未知的設定,gsutil 命令會需要權限才能對 Cloud Storage 寫入,
如何讓 Gitlab-Runner 底下的 Container 擁有指定的 GCP 權限呢 ?
試著下 gcloud auth list 會發現確實有一個可工作的 Account 所以一定有相關的設定要處理,
因為沒有實作到,故不作記錄,但未來再有機會不要忘了這一段的工程。

大部份的工作只需要設定一次,未來只需要 CI 將新版的靜態網站上傳到 Cloud Storage 網站就會更新。

參考

(fin)

Please enable JavaScript to view the LikeCoin. :P