[實作筆記] passport 與 passport-local 原始碼分析

前言

最近接觸的專案有一部份是 express 與登入機制有相關
使用的套件是 Passport
Passport 是這樣介紹自已的

Simple, unobtrusive authentication for Node.js
Passport is authentication middleware for Node.js. Extremely flexible and modular,
Passport can be unobtrusively dropped in to any Express-based web application.
A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.

這裡我們不作使用教學,官方都有而且相當簡單。
passport.authenticate 本質上就是一個 middleware。
而 passport.use 會用來註冊各種 Strategy。
比如說 facebook login、google login 等…
更多可見這裡

談談 passport-local

開發的過程中,有小朋友反應,如果他不輸入帳密,
頁面就會直接轉導,無法看到錯誤訊(有關 req.flash 的部份,本文不會談到)。

1
2
3
4
5
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/users/login",
failureFlash: true,
});

當然我們很清楚有設定 failureRedirect
不過讓我們不得其解的是,我們寫的整個 LocalStrategy 並沒有被執行,連 log 都沒有

這裡要翻一下原碼
這個作者的專案我覺得很棒,大多數都有測項,
除了文件外,你可以直接看測項理解它的想法。
在 Strategy.prototype.authenticate 可以發現以下程式

1
2
3
4
5
6
if (!username || !password) {
return this.fail(
{ message: options.badRequestMessage || "Missing credentials" },
400
);
}

簡單的說,它預設回傳一個 400 錯誤,而我們又設定了一個錯誤轉導。
使用這類的第三方套件就怕這類的非預期行為,
好在這個套件的文件與測項開源且清楚,我們才能快速定位問題。

參考

(fin)

[實作筆記] .ssh 與 Dropbox

警語

這不是很安全的做法。因為 .ssh 的檔案通常包含了敏感的訊息,比如私鑰。
如果放到雲端服務(Dropbox)上,就會有風險。

為什麼要這樣作 ?

現在混合的工作型態,有時會需要用家中的電腦工作,
每次都要花時間重建或是搬移 ssh pub key 顯得有點麻煩
Dropbox 本身就是一個適合的資料同步工具,之前我也設定過 zshrcvimrc 的同步機制。
同理 .ssh 也可適用,但風險需要評估

實作

  1. 將你的 .ssh 內所有檔案移至 ~/Dropbox/.ssh
  2. 刪除 .ssh 資料夾
  3. 執行以下語法
1
ln -s ~/Dropbox/.ssh ~/.ssh

(fin)

[技術筆記] JSON.stringify 的參數

簡介 JSON.stringify()

JSON.stringify() 是 Javascript 提供的一個方法,
可以將 JavaScript 的值轉換成 JSON 字串(String)。

這方法需要 3 個參數

  1. value
    這個參數是必填的,會被轉成 JSON 字串

    1
    2
    3
    var obj = { name: "John", age: 30, city: "New York" };
    var myJSON = JSON.stringify(obj);
    //myJSON is 「{ "name":"John", "age":30, "city":"New York"}」
  2. replacer
    這個選擇性參數,會是一個陣列或是一個 function ,主要的目的可以過濾 value 的值。
    我們可以用 w3school 的範例說明一下
    如果我們傳入的是一個陣列,那它會像一個過濾器一樣,只篩選出陣列中的字串

    1
    2
    3
    var obj = { name: "John", age: 30, city: "New York" };
    var myJSON = JSON.stringify(obj, ["name", "city"]);
    //myJSON is 「{"name":"John","city":"New York"}」

    如果我們傳入的是一個 function,會將每一個參數丟入 function 之中(可以進行加工或裝飾)

    1
    2
    3
    4
    5
    6
    var obj = { name: "John", age: 30, city: "New York" };
    var myJSON = JSON.stringify(obj, (key, value) => {
    if (key.match(/(age|city)/)) return "*";
    return value;
    });
    //myJSON is 「{"name":"John","age":"*","city":"*"}」
  3. space 選擇性
    這個參數是為了可讀性

    1
    2
    3
    var obj = { name: "John", age: 30, city: "New York" };
    var myJSON = JSON.stringify(obj, null, 2);
    //myJSON is 「{ "name": "John", "age": 30, "city": "New York" }」

JSON.stringify(obj, null, 2);JSON.stringify(obj, null, 4);便是開發者常用的呼叫方式

Ref

[生活筆記] 2023 的展望與希望

引言

回顧過往的 Blog,我很少在作年度的計劃與回顧,
22 年末我個人發生比較大的變化。
甚至讓我斷了每個月都寫一篇 Blog 的紀錄…
下面就是我個人的回顧與反思。

工作相關

我不打算在這裡寫太多細節,畢竟事情都還在進行中,未來有機會再細講。
簡單講是我會離開 T 社,回顧一年,不論是支援前端 Flutter 或是後台 React 的開發,
又或是離職同事的 golang 專案,我自認盡力了,但結果可能不如人意。
好處是我學會了 React、Golang、K8s 與部份 GCP 的服務
— Flutter 不算有掌握,畢竟公司內的專案實務上並無法接觸 —
但是這樣的技能樹是我現在該點的嗎 ? 我心中總有點疑惑…

我的觀察

找工作的過程中,發現了很多,
太多的公司(不論新舊、大小)都不具備技術審核的能力,
這樣的公司都會希望找一個資深的工程師,來帶領團隊,而我似乎是他們的人選,
實際上,我不太確定我是否能作好管理,但是無止的追求新技術,也不是我目前的高優先選項,
我還是追求匠人精神,技術總是 2~3 年一個大迭代,但是產品不可能不停的翻新,
而使用的技術,其實對招募會有很大的影響,畢竟市場上的人才很多會技術導向,
開發、維運、招募之間取得平衡是一個持續不斷的挑戰。

跟很多開發者不一樣的地方,我開發的專案,
追求的是零知識的轉移,讓巴士因子儘可能與人脫勾,
說白話一點,我希望我開發的產品,透過工程的手段與必要的文件,
任何人都可以快速上手,只要搭配不同的組態,可以佈署到不同的環境上。
然後我很追求 XP 的開發精神,以前說蠻多的,以後也會再說,就不再佔本篇篇幅了。

面試感覺

如果要透過 Hunter,可以再看看他手上的機會,
如果都是人力銀行就找得到的機會,可以試試看自已投吧,
Hunter 或許可以談出比較高的薪水,但是我的感覺是在玩遊戲,
當我表達對工作有興趣,但是薪水可不可能再往上談的時候,講得很硬,
當我表達有其它的機會時,薪水就又都可以談了,無言…
至少 A 社與 S 社的 Hunter 我是不推的。

一般的工程師職務都會考 LeetCode,
沒刷過的人去刷吧,也不用卡太久,但是一定要把答案看懂,
常見的資料結構與演算法寫個一輪吧,
是不是需要上百題,就見人見智了。

未來的走向

首先,我最理想的狀態,是找到一份遠端的工作機會,如果這個工作穩定,
或許我會開始數位遊牧的生活,每隔幾年會到不同的國家作線上的工作。
不過我現在有的選項當中,並沒有這個項目

保持現狀

A 社是一個老牌的公司了,系統就是 10 年來的累積,
感覺有要作很多新東西,但是老系統會放不掉,薪資 N 有符合預期,
離家也不算太遠,可以說如果選擇了這份工作,
會最接近我的現況。

出國

另一個可能性是出國遊學,
與打工留學不同的是他沒有年齡限制,
剛好現在長輩健康、沒有家累,工作停下來一下,或許是我現階段需要的
缺點是這段時間不但沒有收入,半年內可能還會花費不少儲蓄。

主管職

另一間 A 社的工作機會是一個主管職,主要的技術棧也與我原本的不大相同。
但轉念一想,或許可以有更高的自由度與挑戰,
團隊的組成是年輕的新創團隊,2 個後端與 1 個前端,1 個全端與 1 個 AI 工程師。
有一種工程師的路走到了盡頭,是不是應該去接受這樣挑戰呢 ?

(fin)

[實作筆記] 設定 Vercel 的 DNS

前情提要

最近很紅的聊天 AI ChatGPT 開始出現各種應用了。
這基本上應驗了我 2010 年說的 「10 年內,軟體工程師不會被取代」的說法
雖然是否真的會取代工程師,我會另文寫寫我現在的看法,不過也算是敲響了一記警鐘。
各式各樣的應用也隨之而來。
此外文末記錄一下 heroku 停止免費服務後的可能方案。

Line Bot 與 chatGPT

最常見的應用就是 Line Bot 與 OpenAI GPT-3 API 進行串接。
來達到一個賴機器人能夠與你有一定程度的智能對話。
(不過實測看來 GPT-3 API 無法與 ChatGPT 的提供智能相比)

這類的應該短短幾周就出現了各式各樣的版本,
語言有 nodejs、python、C#

主要的步驟大概如下,

  1. 主程式 push 到 Github 上(大部份都很佛,可以直接 Clone)
  2. 到 OPEN AI 取得 API KEY
  3. 到 Line Developer 建立一個 Message API
  4. 取得 Line Channel Access Token 、Channel Secret
  5. 調整 Line 相關設定 ex: 停用自動回復
  6. 到 Vercel 申請免費伺服器並連結 GitHub 帳號
  7. Vercel 進行伺服器設定 webhook

即可完成

Vercel 這個服務也可以自定 domain,
當然買了 marsen.me 這個 domain 的我自然不會放棄裝專業自定義 domain 的機會
在 Vercal → 登入 → 找到你的專案 → Settings → Domains
輸入你的網域 subdomain.yourdoma.in

接下來去你的 DNS 服務商的設定表(我是用 cloudflare)加上一個 CNAME 的記錄,

Type Name Content
CNAME gptbot cname.vercel-dns.com

後記

這是我第一次使用 Vercel,沒什麼門檻,簡單好上手。
不過看到各方大神使用了各種免費服務,
想說稍微記錄一下這些服務,以因應 heroku 停止免費服務,
或許未來也會有有機會用到

uptimerobot

免費服務通常一段時間沒有用就會睡著,這是個自動喚醒它們的服務

免費部署 service 的方案

  • heroku(已停止免費方案)
  • Colab + Flask(Google 提供給 python 的雲端 solution)
  • fly.io
  • vercel
  • render

我還沒有用過這些服務,所以先作記錄,想要比較的話,可以使用 slant

參考

(fin)

[實作筆記] TDD 二元搜尋樹(Binary Search)

前情提要

最近想再面試,發現大家都很流行刷題,好像沒有刷個 100、200 題,就不能夠面試了。
我剛開始從事軟體工作之初,刷題並不是一個很主流的面試條件,不過也是會準備一些考古題,
關於這樣的面試方式,是不是真的能找到公司要的人才,或許有機會未來,再寫一篇來聊聊。

總之,我很喜歡 TDD 的開發方式,試著在刷題的過程之中,順便練手一下 TDD

演算法題庫的第一題,就是二元搜尋樹(Binary Search),
概念上很簡單,就是將數列排列後,由中位數去比較大小,
如果目標比中位數大,那麼就往右側的樹,再作一次搜尋
如果目標比中位數小,那麼就往左側的樹,再作一次搜尋
直到找目標,或是抵達樹葉,而仍未找到目標便回傳 -1

二元搜尋樹(Binary Search Tree)

這樣的作法,不考慮極端狀況下,會比遍歷整個樹快一倍

測試案例

目標 5,空陣列,回傳-1

這個案例用來建立方法簽章,
用最簡單的方法實作

1
2
3
public int Search(int[] nums, int target) {
return -1;
}

目標 5,[5],回傳 0

這個案例用來實作判斷 target 是否在 nums 之中,
先 hard code 通過測試

1
2
3
4
5
public int Search(int[] nums, int target) {
if (nums.Length > 0)
return 0;
return -1;
}

接著我們要修改 hard code 的部份,首先實作中位數的比較,
同時代出左右 index 的概念

1
2
3
4
5
6
7
8
9
10
if (nums.Length > 0)
{
int left = 0;
int right = nums.Length - 1;
int mid = (right - left) / 2;
if (nums[mid] == target)
{
return mid;
}
}

重構,利用左右 index 的觀念,改寫進入搜尋的條件式

1
2
3
4
5
6
7
8
9
10
int left = 0;
int right = nums.Length - 1;
if (right - left >= 0)
{
int mid = (right - left) / 2;
if (nums[mid] == target)
{
returN mid;
}
}

目標 5,[5,6],回傳 0

我們的樹開始長出葉子,僅管通過測試,
我們也需要透過這個案例,產生一個 while 迴圈,

1
2
3
4
5
6
7
8
9
10
int left = 0;
int right = nums.Length - 1;
while (right >= left)
{
int mid = (right - left) / 2;
if (nums[mid] == target)
{
returN mid;
}
}

目標 5,[5,6,7],回傳 0

目標 5,[3,4,5],回傳 2

有了 mid 的概念,我們可以以 mid 的左右 假想為不同的子樹,
用這兩個案例逼出左、右樹邊界重設條件,
注意 mid 同時也是邊界重要條件之一,
為此,我們需要調整 mid 的邏輯,
完整的程式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int left = 0;
int right = nums.Length - 1;
while (right >= left)
{
int mid = (right - left) / 2 + left;
if (nums[mid] == target)
{
returN mid;
}
if (nums[mid] < target)
{
left = mid + 1;
}

if (nums[mid] > target)
{
right = mid - 1;
}
}

小結

二元搜尋樹是屬於簡單的題型,
也是資工學習演算法的初級題目,
轉為 TDD 的過程也不困難,掌握幾個原則,
一、先設計再實作
二、用測試案例趨動實作
三、果斷拋棄無法趨動實作的測試案例(案例設計不當的壞味道)
四、適當的重構,以符合當初的設計

(fin)

約耳測試馬森版

前情提要

約耳測試是我早期在評量一間公司的軟體團隊成熟程度的指標,
非常簡單好用,像極了軟體業的艾卜佳評分表,
可以參考我之前的文章

最近要換工作了,再看看約耳測試的評量表,有了一些想法,特別記錄下來

我的看法

比較表

Joel Tests 2022 Marsen’s Explanation
1 你有使用原始碼控制系統嗎? 你有使用 Git 嗎?
2 你能用一個步驟建出所有結果嗎? 新人需要多少步驟才能建立好開發環境 ?
3 你有沒有每天都重新編譯建立(daily builds)嗎? 你有高品質自動化整合/迴歸測試嗎?
4 你有沒有問題追蹤資料庫(bug database)? 你有 Issue Tracking System 嗎?
5 你會先把問題都修好之後才寫新的程式嗎? 你有管理你的技術債嗎?
6 你有一份最新的時程表嗎? Scrum: 你知道 Sprint 的目標嗎? Other:你最重要的事是什麼?
7 你有規格嗎? 你有規格/POC/mockup 嗎?
8 程式人員有沒有安靜的工作環境? 你一天會有多少會議?常常被打斷嗎?
9 你有沒有用市面上最好的工具? Cloud/IDE/所有開發人員的開發體驗是一致的嗎?
10 你有沒有測試人員? 你有 QA 團隊嗎?
11 有沒有在面試時要求面試對象寫程式? 有沒有在面試時要求面試對象寫程式?
12 有沒有做走廊使用性(hallway usability)測試? 你有 UI/UX 團隊嗎?

你有使用原始碼控制系統嗎? 你有使用 Git 嗎 ?

這題我覺得比較沒有爭議,分散式版控與集中式版控之爭已經是十年前的討論,
Git 可以說是唯一選擇,雖然曾有一些說法試著說明 Mercurial 比 Git 更好,
但是市場証明了一切。

初學者學習起來資源多、門檻低、工具鏈完整,進階者不能不會 Git。
不論是開發流程、測試、需求管理 Git 的應用應該被包入日常作業之中。
有用 Git 是基本的基本,困難的是如何整合在你整個日常作業之中。
但原本的題目是「你有使用原始碼控制系統嗎?」 所以我想只你有在用 Git 就當作合格吧。

你能用一個步驟建出所有結果嗎? 新人需要多少步驟才能建立好開發環境 ?

這題有點難,我本來想用「你怎麼實作 CI/CD ?」,
但即使到了今天,這也是個大哉問,一鑑還原可以當作一個目標,
在實務上,通常可以作到 3 大步驟就完成開發環境建置我覺得是最理想的。

  • 取得源碼(git clone)
  • 取得/設定授權(internal auth/password/credential)
  • Build & Run

你有沒有每天都重新編譯建立(daily builds)嗎? 你有高品質的自動化整合/迴歸測試嗎?

這題有兩個面向,一個是測試的頻率,一個是測試的品質,
在工具發達的今日,高頻測試不是難事,你可以每天建置一次,也可以每次部署都建置一次。

重點反而在於,你的整合與測試是不是符合品質的,在實務上看到太多為測試而測試的案例。
或是追求特定指標(每日建置次數/測試覆蓋率等…)而無法真正達到為品質把關。

不過回到這題,你有高品質的自動化整合/迴歸測試嗎 ?
有沒有很好量化,高不高品質就難說了,也許問題可以改成
「你有自動化整合/迴歸測試嗎?如果有的話,請舉例說明曾被測試救了一次的經驗(欄截到的錯誤版本)?」
舉不出例子就是種壞味道,很有可能他的自動化整合/迴歸測試是自我滿足或是吹牛用的工具,
而不是真正對產品有用保護。

你有沒有問題追蹤資料庫(bug database)? 你有 Issue Tracking System 嗎?

這題我覺得也是基本題,如果有的話可以追問

  • 需求的分級
  • 一般需求與緊急需求怎麼處理 ?
  • BUG 與 Defect 怎麼處理 ?

如果沒有 Issue Tracking System 的公司一定雷,自已好自為之。

你會先把問題都修好之後才寫新的程式嗎? 你有管理你的技術債嗎?

這題與上一題有所關連,但是上一與之相關的人員比較像是 PM/PO/管理者的角色,
而這題更多是 RD 需要負起的責任。
實務上為了快速滿足需求,欠技術債是難免的,與其不欠債,不如好好管理債務,
那 Issue Tracking System 就會是一個很好用的工具。

而實際台灣的業界,我蠻常看到 RD 是欠債不理的,用一些神乎其技完成了某功能,
然後離職無人能接手(或是難以接手),再去各大社群或是大會演講,
雖然技術很領先,但這樣的人實在讓人難以尊敬(不過看起來他們是過蠻爽的啦)

你有一份最新的時程表嗎? Scrum: 你知道 Sprint 的目標嗎? Other:你最重要的事是什麼?

這裡特別提到了 Scrum(請參照 Scrum Guide,其它捉文解字的、守破離的、殞石開發的不算 Scrum )
因為在 Scrum 的 Sprint 設計是有目標導向的,
而不是 Scrum 的話,你知道你最重要的事情是什麼嗎 ?
至少要說出前 3 項,與短中長程的目標,人無遠慮必有近憂
這個問題適用於開發主管與產品主管,可以了解這兩個面向公司的發展與未來性。

你有規格嗎? 你有規格/POC/mockup 嗎 ?

這題其實不用改,即使敏捷宣言說Individuals and interactions over processes and tools
但是這些文件實務上是能帶來很多好處的,在各行各業都有類似的手冊、文件來幫助溝通,
很多時候你也可以錄影/錄音來加速增加溝通的效率,不要太過極端就好,
如果是 010 來評分的話,我建議至少要有 68 成規格應被置成文件(不論影音文字)

程式人員有沒有安靜的工作環境? 你一天會有多少會議?常常被打斷嗎?

例行性會議有多少 ? 非例行性會議(頻率)有多少 ?
多久會 1-1 ? 這些問題很看管理者的風格,但是又很打擾開發者,
另外如果面試爽約、逾時、讓你乾等等…可以視作一種壞味道,不仿可以切入追問。

你有沒有用市面上最好的工具? Cloud/IDE/所有開發人員的開發體驗是一致的嗎?

現在的工具實在太多,還有各種外掛,所以要讓開發人員的開發體驗一致是相當困難的,
建議在建立開發人員(可以依團隊劃分)的環境文件之中,應該要有一份共用且最新的相關設定的檔。

~~你有沒有測試人員?~~~ 你有 QA 團隊嗎?

測試已經是一門水很深的領域了,如果沒有一個團隊在負責測試,或是沒有自動化測試,
要小心這是個壞味道,或許你可以把問題變成「測試團隊怎麼驗收功能?」

有沒有在面試時要求面試對象寫程式 ?

不得不說我也很不喜歡面試時寫程式,最主要是常常面試的題目難如登月,
進到公司後,卻在作九九乘法表般的開發。
網路上甚至都有「面試造火箭,入職擰螺絲」一說了。
樂觀的說,如果你不懂得造火箭,那你的螺絲很難擰得正確,
悲觀的說,這些資訊主管,正缺乏一種正確評量人的方法,所以只能用這種方法進行篩選,
而且這方法還算行得通。

反之,我反而很想在面試的時候,有機會可以跟面試管或是未來的同事 pair programming。

有沒有做走廊使用性(hallway usability)測試? 你有 UI/UX 團隊嗎?

走廊使用性測試我並不熟,我的理解是盲測使用者怎麼使用你的產品?
目前的業界通常有專門的 UI/UX 團隊在處理,不過要怎麼預測使用者行為仍然是很難的
最後看看這篇經點的笑話

一個測試工程師走進一家酒吧,要了一杯啤酒
一個測試工程師走進一家酒吧,要了一杯咖啡
一個測試工程師走進一家酒吧,要了 999999999 杯啤酒
一個測試工程師走進一家酒吧,要了 0 杯啤酒
一個測試工程師走進一家酒吧,要了-1 杯啤酒,
一個測試工程師走進一家酒吧,要了 0.7 杯啤酒
一個測試工程師走進一家酒吧,要了-10000 杯啤酒
一個測試工程師走進一家酒吧,要了一杯蜥蜴
一個測試工程師走進一家酒吧,要了一份 Qer@24dg!&*(@
一個測試工程師走進一家酒吧,什麽也沒要
一個測試工程師走進一家酒吧,又走出去又從窗戶進來又從後門出去再從下水道鑽進來
一個測試工程師走進一家酒吧,又走出去又進來又出去又進來又出去,最後在外面把老闆打了一頓
一個測試工程師走進一家酒吧,要了一杯燙燙燙的錕斤拷
一個測試工程師走進一家酒吧,要了 NaN 杯 Null
一個測試工程師沖進一家酒吧,要了 500 杯啤酒咖啡洗腳水野貓狼牙棒奶茶
一個測試工程師把酒吧拆了
一個測試工程師化妝成老闆走進一家酒吧,要了 500 杯啤酒並且不付錢
一萬個測試工程師在酒吧門外呼嘯而過

酒保從容應對

測試工程師很滿意

測試工程師們滿意地離開了酒吧

一名顧客點了一份炒飯

酒吧陷入火海

murmur 一下這些充滿美學卻不能被叫美工的人,說得自已好像賈伯斯,
大多是我覺得或是我的經驗,很少在依賴數據判斷,或是與工程團隊互動,
去建立回饋機制或 A/B Testing。

參考

(fin)

[實作筆記] Hexo CI 自動執行 ncu -u 更新相依套件

前情提要

參考前篇
我針對了我 Blog 文章的發佈流程作了改善,
當中我提到其中並沒有用到多麼了不起的技術或新知,
而是整合現有的技術,讓流程更加的自動化。

這次我又想到一個自動化的流程,
之前提到的 npm-check-updates 工具,
雖然可以很輕鬆的更新 npm 套件,但是什麼時候執行就是一個問題了,
可以的話,我希望讓 CI/CD 代勞。

實作

立刻來捲起袖子吧,
首先回顧一下手動作業的流程。

前置條件

全域安裝 npm-check-updates

流程

  1. 拉取專案 git pull
  2. 執行 ncu -u
  3. 如果有異動的話 git commit -m "some message here"
  4. 推上遠端 repo git push

自動化

看看上面的流程,我們知道其實我們只需要一個安裝好 node、git 與 ncu 的環境,

必要條件

接著就依照流程執行命令即可,這裡我覺得正可以展現 Github Workflow 與 Actions 的強大之處,
你的問題,就是大眾會遇到的問題,而且大部份的情況都有對應的 Actions,
我們所需要作的,就是組合這些步驟,並且放入自已的邏輯。
直接看 workflow 範例

1
2
3
4
5
6
7
8
9
10
11
steps:
- name: Checkout
uses: actions/[email protected]
- name: ncu-upgrade
run: |
npm i -g npm-check-updates
ncu -u
- name: Commit & Push changes
uses: actions-js/[email protected]
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

觀注在 steps 的部份,
透過 actions/checkout 取得最新的 remote repository

1
2
- name: Checkout
uses: actions/[email protected]

接下來,執行一些語法,
安裝 npm i -g npm-check-updates
再執行 ncu -u

最後,透過 action-js/push 將異動推回 remote repository

1
2
- name: Commit & Push changes
uses: action-js/[email protected]

下一個問題是,要怎麼取得 git repo 的權限 ?
登登!! 我們可以使用 GITHUB_TOKEN 就輕易取得權限,
有關 GITHUB_TOKEN 的介紹可以參考下方的連結。

參考

(fin)

[實作筆記] Hexo CI/CD 設置

前情提要

我的 blog 一直以來都是用 markdown 加上 hexo 與 github page 建置的靜態網站。
為此,我也寫了一系列的文章;但是我卻沒想過優化 blog 撰寫的流程。

我目前寫作的的流程是這樣的:

首先我會用任何工具補捉自已的想法,手機、筆記本、便利貼、Notion etc…
然後我會用 hackmd.io 來寫草稿。
草稿完成後,我會在自已電腦的 Marsen.Node.Hexo Repo 上完成文章。
接著我會先執行 git push 上版,再接著手動進行 blog 的建置(hexo g) 與部署(hexo d)。

這次要優化的部份是 **手動進行 blog 的建置(hexo g) 與部署(hexo d)**。
我希望寫完 blog 後推上 main 就是發佈了。

進一步的話,可以考慮用分支作草稿流程管理,但先不過早優化。

實作

首先,來了解我們的架構
hexo CI/CD

如圖,可以被畫分為兩個部份,第一個部份是紅框處,
在取得 Hexo Source 後,執行 hexo g 會產生靜態檔案放在 public 資料夾,
藍框處是第二部份,將 public 資料夾部署到 Github Page 的 repository 當中。

就這麼簡單,整個流程只會用到兩個工具 githexo
我們只要透過 docker 準備好安裝這兩個工具的 image 在 CI/CD 上執行即可
我找到了一個 Github Action 的 repository – HEXO-ACTION
依照以下的 SOP 就可以完成 CI/CD 設定

  1. 準備 Deploy Keys 與 Secrets
    執行以下指令

    $ ssh-keygen -t rsa -C “username@example.com

    這時我們會取得一組私鑰與公鑰,
    請在 Github Page repository 的 Settings > Deploy keys 中,加入公鑰(記得要勾選 Allow write access)

    https://github.com/{your_account}/{your_account}.github.io/settings/keys

    然後依照 hexo-action 的說明,你必需在 Hexo Source 的 repository 的 Settings > Secrets > actions 中加入私鑰
    有關 ssh-keygen 與不對稱加密的相關知識這裡就不多說明了,
    簡單的去理解,這組鑰匙是用來對 Github Page Repository 作讀寫,所以公鑰會放在 Github Page Repository 中。
    想嚐試存取 Github Page Repository 的人都會拿到公鑰,但是真得要存取,你得有私鑰才行。
    這個時候,Hexo Source Repository 會將私鑰以 Secrets 的形式存下來,
    在 Actions 執行時,會透過 {secrets.DEPLOY_KEY} 去取得私鑰,小心別弄丟了,不然你要從頭來過。
    DEPLOY_KEY 是 HEXO-ACTION 所規範的 Key Name。

    如果你真的很想修改這個 Key Name,可以把 hexo-action fork 回去自已改

  2. 配置 Github Workflow
    這步驟就相當簡單了,可以直接參考 HEXO-ACTION 的配置,或是看看我的 workflow 配置

後續

一些小錯誤要注意一下

第一點,Hexo Source repository 的 package-lock.json 應該要進版控,
這個檔案本來就應該進版控,不知道為什麼之前被設成 ignore 了,
剛好 hexo-action 有用到 npm ci 語法才發現這個錯誤,相關文章請參考

在 Hexo Source 的設定檔 _config.yml 中的 deploy 區段有兩點要注意,
第一,type 一定要是 git,第二,repository 的路徑請設定為 ssh (不要用 https),
參考以下範例

1
2
3
4
deploy:
type: git
repository: [email protected]:marsen/marsen.github.io.git
branche: master

如此一來,未來我寫好文章,只要 push 上 Hexo Source,就可以打完收工了,
再也不用額外的手動作業囉,這篇文章剛好就是第一篇,馬上來試試看。

心得

相關的技術與知識我都有兩年以上了,
但是一直沒有發現應該將其結合起來,可以省下許多時間,
讓我想到 91 大說的綜效,我應該要讓想像力再開放一些,儘可能的把知識與資訊變成真正的價值。

參考

(fin)

[學習筆記] React useEffect

簡介 useEffect

在 RC(React Component) 當中,useEffect 是一個常用 Hook,用來處理一些副作用(side-Effect)。

用 FP(Functional Programming) 的角度來看,RC 只是依照狀態(state)或是參數(prop)來呈現不同的外觀,就像是一個單純的 Pure Function。
FP 一些常見的副作用如下:

  • I/O (存取檔案、寫 Log 等)
  • 與資料庫互動
  • Http 請求
  • DOM 操作

而 Http 與 DOM 正好是我們開發 RC 最常接觸到的副作用,
useEffect 就是要來解決這個問題。

如何運作

  1. RC 在渲染的時候會通知 React
  2. React 通知 Browser 渲染
  3. Browser 渲染後,執行 Effect

使用範例

下面是 VSCode 外掛產生的程式片段

1
2
3
4
5
6
7
useEffect(() => {
first;

return () => {
second;
};
}, [third]);

我們可以看到 useEffect 需要提供兩個參數,一個函數與一個陣列
這裡有三處邏輯,first、second、third,
為了更好說明概念,順序會稍會有點跳躍,請再參考上面程式範例

相依(dependencies)

third 就是指與此副作用相依的參數,如果不特別加上這個參數,
每次重新渲染都會觸發 Effect 的第一個參數的函數,
如果只提供一個空陣列,就只會在第一次渲染的時候觸發,這很適合用在初始化的情況。
觸發時機:

  • 不傳值,每次渲染時
  • [],只有第一次渲染時
  • [dep1,dep2,…]

注意的事項與 useMemo

useEffect 在判斷觸發的 dependencies 參數,
會有 Primitive 與 Non-primitive 參數的差別。

Primitive: 比如 number、boolean 與 string
Non-primitive: 比如 object、array
這兩種參數的差異在於 JavaScript 在實作時,記憶體的使用方式

Primitive 會直接將值記錄在記憶體區塊中,
Non-primitive,則會另外劃一塊記憶體位置存值,再將這塊記憶體位址存到參數的記憶體位址中。
所以當我們比較的是記憶體位址時,即使內部的值都相同也會回傳 false

參考範例

1
2
3
4
5
const [staff, setStaff] = useState({ name: "", toggle: false });
useEffect(() => {
console.count(`staff updated:${JSON.stringify(staff)}`);
}, [staff]);
//}, [staff.name, staff.toggle]);

每次我們點擊 Change Name 按鈕的時候,都會呼叫改變命名的方法,

1
const handleName = () => setStaff((prev) => ({ ...prev, name: name }));

但是即時我們的 name 沒有改變,仍然觸發 Effect,
這是因為 staff 是一個 Non-Primitive 型別的變數。
一種簡單暴力的方法是將物件展開放入 [] 之中。

這裡我們無法使用展開運算符(Spread Operator)
因為一般的物件沒有 Symbol.iterator 方法

Only iterable objects, like Array, can be spread in array and function parameters.
Many objects are not iterable, including all plain objects that lack a Symbol.iterator method:

物件展開放入 [] 之中的明顯缺點是,當物件變的太大或複雜的時候,
你的 [] 會變得又臭又長。

解法可以使用 useMemo
useMemo 會回傳一個存在記憶體裡的值,
完整範例請參考

1
2
3
4
5
6
7
8
const memo = useMemo(
() => ({ name: staff.name, toggle: staff.toggle }),
[staff.name, staff.toggle]
);

useEffect(() => {
console.count(`staff updated:${JSON.stringify(memo)}`);
}, [memo]);

函數

回到我們的程式片段
first 其實就是處理 Effect 函數,我們可以把 Effect 寫在這裡,
範例中都是寫 console.log 實務上更多會是打 API、fetch etc… 的行為

Clean up functions

useEffect 的第一個參數是一個函數,可以回傳一個 callback function 用來清除 side Effect。

舉例說明:
想像我們有兩個連結用來切換不同的使用者資料,
在切換的過程中會打 api 取得不同使用者(user1 與 user2)的資料,

我們會先點 user1 的連結取 user1 資料(因網速過慢,此時資料還沒回來),
再點 user2 的連結取取 user2 資料(user2 的資料也還沒回來),
這時,user1 資料回來了,畫面會渲染 user1 資料(實際上,這時候我想看 user2 的資料),
再過一會兒,user2 資料回來了,畫面閃爍,渲染 user2 資料。

線上範例看這,使用網速網路會更明顯。

這是表示當我們點擊 user2 的連結時,其實我們已經拋棄了點擊 user1 的結果(對我們來說已經不需要了),
這時候 Clean up function 就是用來處理這個不再需要的 side-effect。

我們可以簡單設定一個 toggle 來處理這個問題

1
2
3
4
5
6
7
8
9
10
11
12
13
useEffect(() => {
let fetching = true;
fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
.then((res) => res.json())
.then((data) => {
if (fetching) {
setUser(data);
}
});
return () => {
fetching = false;
};
}, [id]);

我們也可使用 AbortController,
有關 AbortController 的相關資訊可以參考隨附連結,或是留言給我再作介紹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(`https://jsonplaceholder.typicode.com/users/${id}`,{ signal })
.then((res) => res.json())
.then((data) => {
setUser(data);
})
.catch(err=>{
if(err.name === "AbortError){
console.log("cancelled!")
}else{
//todo:handle error
}
});
return () => {
controller.abort();
};
}, [id]);

以上,我們就介紹完了 useEffect 的三個部份(函數、回呼函數與相依數列)與用法。

如果你使用常見的套件 axios 應該怎麼作 clean up,
補充範例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
useEffect(() => {
const cancelToken = axios.cancelToken.source();
axios(`https://jsonplaceholder.typicode.com/users/${id}`,{ cancelToken:cancelToken.token })
.then((res) =>
setUser(res.data);
})
.catch(err=>{
if(axios.isCancel(err)){
console.log("cancelled!")
}else{
//todo:handle error
}
});
return () => {
cancelToken.cancel();
};
}, [id]);

why render twice with React.StrictMode

請參考官方文章,
StrictMode 可以幫助開發者即早發現諸如下列的問題:

  • Identifying components with unsafe life-cycles
  • Warning about legacy string ref API usage
  • Warning about deprecated findDOMNode usage
  • Detecting unexpected side effects
  • Detecting legacy context API
  • Ensuring reusable state

小結

  • React.StrictMode 可以幫助你檢查組件的生命周期(不僅僅 useEffect)
  • React.StrictMode 很有用不應該考慮移除它
  • useEffect 包含三個部份
    • 第一個參數(function),處理 side-Effect 的商業邏輯
    • 第一個參數的回傳值(clean-up function),處理 side-Effect 中斷時的邏輯(拋錯、釋放資源 etc…)
    • 第二個參數表示相依的參數陣列
      • 不傳值將會導致每次渲染都觸發副作用
      • 傳空陣列將會只執行一次
      • 相依的參數需注意 Primitive 與 Non-primitive
      • useMemo 可以協助處理 Primitive 與 Non-primitive

參考

Please enable JavaScript to view the LikeCoin. :P