-
🔭 I’m currently working for Buyandship
-
🌱 I’m currently learning Test Automation, second/third languages
-
💬 Ask me about any Frontend knowledge
-
📄 Know about my experiences here
-
📝 I regularly write articles on here
-
if anything here is helpful to you, you could buy me a coffee here
blog's Introduction
blog's People
Forkers
0xblackteablog's Issues
用 git hook 實現自動化部署
用 git hook 實現自動化部署
前言
以前部署都是直接 github page 或整包丟到 nas 上 (靜態用Nginx開啟 8080 port; nodejs 則是 ssh 到 機器上 node index.js
), 每次更新一次版本都要做一次重複的動作, 實在是太麻煩了, 所以找找看有沒有實現 CI/CD
的方案, 大致看了一下有兩個不錯的服務可以用
- now.sh
- heroku
now.sh
部署最方便, 只要下指令 now
, 就可以完成部署, 而且可以加入 now.json
去執行 package.json 的 script, 但必須把 server 端的code 寫成 lambda (server less)
架構, 所以先不考慮, 之後再研究這種架構有什麼好處
heroku
其實部署也算方便, 而且 heroku 看到 package.json 就會知道部署的是 nodejs, 並執行 npm run build && npm run start
, 這也算是直接完成了 CI/CD
, 不過當初部署完才發現免費版無法在部署的機器上對外發出 request (付費版也有流量限制), GG
自己實現 git hook
最後... 還是把程式部署在自己的機器上比較方便, 那就自己來實現 CI/CD
在 nas 上初始化 git 倉儲
git init
是建立一個普通的倉儲, git init --bare
則是建立前者 .git
資料夾裡面的東西, 並且是不能瀏覽或修改倉儲內的東西的
$ cd volume1/HelloGit #這名字有點憨
$ mkdir deploy.git
$ cd deploy.git
$ git init --bare
設置 hook
$ cd hooks
$ cp post-update.sample post-update
$ chmod 777 post-update
$ vi post-update
先隨便 echo 測試看看
#!/bin/sh
unset GIT_DIR
echo "done~~~~~~"
exit 0
在本地端加入 remote server repo
$ git remote add deploy root@ip:/volume1/HelloGit/deploy.git
$ git push deploy master
看到 done~~~~~, 看來是成功了
在根目錄部署點 clone server repo
$ cd ~/www
$ git clone /volume1/HelloGit/deploy.git
$ chmod 777 -R ../deploy
部署的機器上安裝 pm2
pm2 (performance manager) 是一個管理 nodejs server 運行狀態的一個模組, 可以幫你管理附負載平衡, 實現 hot reload 等等的功能
$ npm install -g pm2
測試是否安裝成功
$ pm2 -v
修改 post-update
#!/bin/sh
unset GIT_DIR
NowPath=`pwd`
DeployPath="/volume1/homes/b3589481400/www/deploy"
cd $DeployPath
git add . -A && git stash
git pull origin master
npm install
npm run build
pm2 restart all
cd $NowPath
echo "deploy success !"
exit 0
之後本地端再改個東西 push 一次
這時發生了問題... npm & pm2 command not found
!!??
後來找到這篇 解答
把 #!/bin/sh
改成 #!/bin/bash -l
-l 參數的意思是執行指令時, 是在login 的 shell 裡執行, 以此例來說就是繼承 b3589481400 這個使用者的路徑和 shell 設定值, 這樣就能使用 npm
等等的指令了
...再 push 一次試試看
大功告成 !!
Polling vs Websocket vs Server-Sent Events
Preface
最近在公司的 member center 有一個 notification center 的 idea
身為一個 類電商 + 類貨運的網站, 有這種 feature 確實會更好用,
real-time 的特性, 也可以在需付款或是到貨時即時的通知使用者
Polling vs Websocket vs Server-Sent Events
在消化了大量資訊後了解到 web 能實現的方法大概有這三種
-
Polling
-
Websocket
-
Server-Sent Events
Polling
client 每隔幾秒就向 server 發出 request
function Polling() {
fetch(url)
.then(data => {})
.catch(err => {
console.log(err)
})
}
setInterval(polling, 5000)
-
優點: 簡單暴力, 使用http協定基本上 client & server 都不用另外做特別的處理
-
缺點:
- 是最消耗資源的方式, client端要一直重複建立 Tcp 會非常消耗資源
- 實際上比較像是
client pull
而不是server push
, 無法做到真正的同步
Long Polling (COMET)
Polling的進化版, client 發出 request 之後, 不會立刻返回請求, 有數據更新或 timeout時才會返回
function LongPolling() {
fetch(url)
.then(LongPolling)
.catch(err => {
LongPolling()
console.log(err)
})
}
LongPolling()
-
優點: 相比普通的 polling, 節省了很多請求次數, 相對節省資源
-
缺點:
- 還是一樣要由 client 發起連接的建立, 消耗資源
- 實際上比較像是
client pull
而不是server push
, 無法做到真正的同步
另外還有一種方式是利用
iframe
實現的, 但是這邊就不詳述了
Websocket
這是一個可以透過單個TCP
連接後, 提供全雙工通信通道的通信協議,
可以在 client 和 server 之間維持持久性的連接
window.WebSocket = window.WebSocket || window.MozWebSocket
const connection = new WebSocket('ws://localhost:8080/githubEvents')
connection.onopen = function () {
// connection is opened and ready to use
}
connection.onerror = function (error) {
// an error occurred when sending/receiving data
}
connection.onmessage = function (message) {
// try to decode json (I assume that each message
// from server is json)
try {
const githubEvent = JSON.parse(message.data) // display to the user appropriately
} catch (e) {
console.log('This doesn\'t look like a valid JSON: ' + message.data)
return
}
// handle incoming message
}
-
優點: 沒有多餘的效能浪費
-
缺點:
- client, server 都需要自行處理 http 已經處理好的問題
- 在需要多路同步傳輸時, 實現起來有其複雜度
Server-Sent Events (SSE)
SSE 是一種單向的訂閱/推播機制, HTML5的標準中提供了一個Web API: EventSource
在TCP連接後, 就可以建立永久性的連接, server 可以一直將數據傳送到 client端
SSE也是一種基於 http 的實作, 所以也可以相容於 http/2
, 回應的 content-type 是較為特殊的 content-type: text/event-stream
const source = new EventSource('/sse')
source.addEventListener('message', function (e) {
console.log(e.data)
}, false)
source.addEventListener('my_msg', function (e) {
process(e.data)
}, false)
source.addEventListener('open', function (e) {
console.log('open sse')
}, false)
source.addEventListener('error', function (e) {
console.log('error')
})
-
優點:
- 輕量, 實作相較於 websocket 簡單很多
- 開箱既用的實現支援 http/2
-
缺點: IE不支援
High Level Comparison
一張圖詳細了解 Polling vs SSE
Conclusion
從各方面比較下來, SSE是一個綜合起來最輕量, 開發容易, 效能又好的一個選擇
SSE 基本上可以很好的取代 polling, 很適合做這些類似的應用
- 即時股價資訊
- 通知/推文/新聞資訊
- 監控系統的 monitor
不過, 若 client 與其他 client 也需要平凡的做資料交換的話, 像是多人的網路遊戲, 這種大量數據的雙向溝通還是需要 Websocket
來處理會比較適合
-
相容性: Polling > Long Polling > SSE > WebSocket
-
性能上: WebSocket > SSE > Long Polling > Polling
最後, 我自己也用了 SSE 簡單做了一個 Chat Room,
Github Repo
Reference Articles
使用 Google Cloud Storage 建立靜態網站
使用 Google Cloud Storage 建立靜態網站
Intro
最近想建個靜態網站
因為想擁有比較多控制權所以使用 Next 去做
上一篇
Next.js 串接 hackmd, 用 markdown 語法快速更新網站內容
Next SSG mode 可以用指令 next export
產生靜態網站
build 出來的東西是可以直接放在任何主機上就可以執行的靜態內容
這篇來記錄一下放到 GCS 起一個網站遇到的問題
準備
- 一個從
GoDaddy
購買的網域 - 一個 static files Project, 我這次是用 Next
- 一個 Google Cloud Storage 空間
GCS 設定
驗證 Domain
因爲 bucket name 要對應 domain, 例如 Domain staging.ninja.com
, bucket name 就要命名成 staging.ninja.com
建立 bucket 時也會要求驗證是不是 Domain 擁有人, 參考文章
這邊 GCS 會要求使用 search console 驗證
只要跟著步驟就可以輕鬆驗證完成
建立 Bucket, 公開 url
驗證完後才可以開始建立 Bucket
- 建立新 Bucket, 命名成
staging.ninja.com
, 地理精度選 region, 位置選台灣, 這樣網站速度才會快, 相對 SEO 分數也會高 - 把靜態檔都丟進去
- bucket 操作頁面選擇
編輯存取權
, 之後選擇新增主體
- 主體輸入
allUsers
(這設定方式真是奇耙 😂), 指派給他權限Storage 繼承物件讀取者
之後就可以看到 公開存取權變成 在網際網路上公開
目前這樣會有一個問題, 隨便開一個 page url 會是 https://storage.googleapis.com/staging.ninja/index.html
但跳頁後因為是相對路徑會變成 https://storage.googleapis.com/about/index.html
找不到這個頁面
網域設定
GCS 公開 url 會長這樣 https://storage.googleapis.com/staging.ninja.com/index.html
所以要在 GoDaddy 設定 CNAME 把 storage.googleapis.com
替換掉
完成後, 就可以在 http://staging.ninja.com/index.html
看到網站 !
但是 http://staging.ninja.com
會找不到頁面, 所以我們繼續設定
設定首頁與 404 頁面
在 bucket 操作頁選擇 編輯網站設定
就可以設定 index.html 和 404.html
替網站加上 Https 加密協議
設定到目前為止已經差不多了, 但目前只能用 http 協議瀏覽
有兩種改善方式
- 使用 google cloud HTTP(S) load balance (要錢)
- 使用 cloudflare (免費)
當然是使用免費的啊!
下一篇會紀錄如何使用 cloudflare 這個服務替網站加上 https 協議
可參考 How to Setup a SSL for Google Cloud Storage hosted Site?
Using Cypress to E2E Testing
Using Cypress to E2E Testing
Preface
既然要完成一個 CI/CD flow, 一定要包含個自動化測試, 既然是 E2E Testing, 就來玩一下目前最夯的 Cypress
Setup
npm install cypress
跑完之後 會在 Project 裡面多出一個路徑, 名稱是 cypress, 還有一個 cypress.json
cd cypress/integration
mkdir sample
touch index.spec.js
touch index_hash.spec.js
我們的目的是寫一個範例, 在 local 起一個 server, index page 在 http://localhost:3000
, 讓 cypress 測試通過; 在 http://localhost:3000/#withHash
模擬其他頁面, 讓 cypress 測試失敗
// index.spec.js
describe('The Home Page', () => {
it('successfully loads', () => {
cy.visit('/')
})
})
// index_hash.spec.js
describe('The Home Page (with Hash)', () => {
it('successfully loads', () => {
cy.visit('/#withHash')
})
})
在 cypress.json 加入 local server url
# cypress.json
{
"baseUrl": "http://localhost:3000"
}
最重要的部分, 在 support/index.js
加入這段, 讓 cypress 可以捕捉 console 的 error 或 warning
// support/index.js
Cypress.on('window:before:load', (win) => {
cy.spy(win.console, 'error')
cy.spy(win.console, 'warn')
})
afterEach(() => {
cy.window().then((win) => {
expect(win.console.error).to.have.callCount(0)
expect(win.console.warn).to.have.callCount(0)
})
})
因為這邊 frontend 是用 vite
, 在 src/App.vue
, 模擬在其他頁面噴錯的狀況
// src/App.vue
if (!!window.location.hash) {
console.error('do some error')
}
Run Test
npx cypress run --spec 'cypress/integration/sample/*'
結果如下
Conclusion
用了一下 cypress 後發現其實語法和行為都蠻像之前用過的 puppeteer
, 也有 headless 模式 (畢竟是一樣用 chromium
核心), 不過 cypress 有一個整合性非常好的 cloud service, 可以在上面的 Dashboard 看到整個完整的 testing record, 還可以串接 Github
, Bitbucket
, GitLab
, Slack
...等等, 實在是方便又好用!
References
Vue - 優化大量數據同時渲染
前言
最近在專案上遇到小小的效能瓶頸
有一個畫面是顯示提貨點資訊的 list,
在初次渲染和使用filter時, 體感上會有明顯的 lag
原因是瀏覽器一次改變300多個dom節點造成的delay
來看看程式碼
這個 list 是由 state.currentRedemptionList
用 v-for 的方式渲染, 讓每一張 BsCard 呈現提貨點資料
而使用者可以藉由畫面上的 filter 對 state.currentRedemptionList
的數量進行增減
在程式碼加上 timelog
onBeforeUpdate(() => {
console.time('update list')
})
onUpdated(() => {
console.timeEnd('update list')
})
可以看到使用 特快取件的 filter 反選時,
因為 list 數量一下從 8 => 346
re-render 過程大約需耗時高達 300ms
第一步
試試看用 Object.freeze
凍結傳進來的 list, 除去 vue observe data 的效能消耗
<BsRedemptionPicker
v-if="state.isShowRedemptionListPanel && stateSelectRedemption.sourceList"
mode="declaration"
:current-id="state.form.address"
:redemption-list="Object.freeze(stateSelectRedemption.sourceList)"
:express-name-key="address.addressFormat.expressNameKey"
:redemption-shortcuts="address.addressFormat.consolidationRedemptionTypesGroupedShortcuts"
@select="onSelectPanel"
@goback="hideRedemptionListPanel"
/>
渲染耗時可以減少到大約 280ms, 效果不大
第二步
試試看將 state.currentRedemptionList
用 slice time 的方式重組
v-for 改成使用 stateTimeSlicing.list
去渲染
使用 setTimeout
在每次 eventLoop 時 分段將資料塞入列表
const stateTimeSlicing: any = reactive({
list: [],
perPage: 20,
timer: null,
})
watch(
() => state.currentRedemptionList,
value => {
timeSlice()
},
{ immediate: true },
)
function timeSlice() {
stateTimeSlicing.list = []
clearTimeout(stateTimeSlicing.timer)
stateTimeSlicing.timer = null
loop(state.currentRedemptionList.length, 0)
}
function loop(curTotal: number, curIndex: number) {
if (curTotal <= 0) {
return false
}
const pageCount = Math.min(curTotal, stateTimeSlicing.perPage)
stateTimeSlicing.timer = setTimeout(() => {
stateTimeSlicing.list.push(
...state.currentRedemptionList.slice(
curIndex,
curIndex + pageCount,
),
)
loop(curTotal - pageCount, curIndex + pageCount)
}, 0)
return true
}
結果:在體感上已經感覺不到延遲了!
可以看到渲染時間被切成碎片化, 甚至第一次 component update 只需要3ms, 效果非常明顯
第三步
在優化後可以發現一個重點
雖然渲染速度變快了
但是卻造成畫面閃爍
原因是 在 timeSlice()
中的 stateTimeSlicing.list = []
這段程式碼會造成 list 為空的狀態, 並且觸發 vue re-render
我們稍微改一下程式碼就可以解決這個問題
function timeSlice() {
clearTimeout(stateTimeSlicing.timer)
stateTimeSlicing.timer = null
loop(state.currentRedemptionList.length, 0)
}
function loop(curTotal: number, curIndex: number) {
if (curTotal <= 0) {
return false
}
const pageCount = Math.min(curTotal, stateTimeSlicing.perPage)
stateTimeSlicing.timer = setTimeout(() => {
if (curIndex === 0) stateTimeSlicing.list = []
stateTimeSlicing.list.push(
...state.currentRedemptionList.slice(
curIndex,
curIndex + pageCount,
),
)
loop(curTotal - pageCount, curIndex + pageCount)
}, 0)
return true
}
看看結果, 已經防止了畫面跳動的問題
以上
大功告成!
參考資料
Currying vs Partial application
柯里化和部分函數應用
基本函式
function sum(a, b, c) {
return a + b + c
}
sum(1, 2, 3) // 6
函式的 length
函式的 length 是參數的數量, 此數值是固定不變的
function test(a, b, c) {
console.log(test.length)
}
test(1, 2) // 3
test(1, 2, 3, 4) // 3
柯里化
Currying(柯里化) 是把一个接受 N 个参数的函数转换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。也就是说每个函数都接受1个参数。
我們可以把 function add
做一個簡單的柯里化
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c
}
}
}
curriedAdd(1)(2)(3) // 6
高階柯里化
...
部分函數應用
部分應用程式
部分應用程式將一個函式的
f:X * Y -> R
第一個參數固定來產生新的函式
f’:Y -> R
f' 和 f 不同,只需要填寫第二個參數,這也是為什麼 f' 比 f 少一個參數的原因。
function add
的部分函數應用
function add5(a, b) {
return 5 + a + b
}
add5(1, 2) // 8
add5(2, 3) // 10
效能測試
使用 benchmark
進行效能測試
幾乎沒有複雜運算的情況
sum(1, 2, 3)
結果:
ops/sec
表示每秒可以運算這個函式的次數
normal x 31,078,534 ops/sec ±1.22% (78 runs sampled)
partail application x 14,939,135 ops/sec ±3.70% (79 runs sampled)
curry x 13,758,207 ops/sec ±1.09% (88 runs sampled)
有複雜運算
function sum(a, b, c) {
return a + b + c
}
// 模擬一個複雜計算的函式, 從 1 加到 10000
function hardFunc() {
let num = Array.from({length: 10000})
return num.map((value, index) => index).reduce((a, value) => a + value)
}
function curry(a) {
return function(b) {
return function(c) {
return a + b + c
}
}
}
const sumPartial = sum.bind(null, hardFunc()) // partail
const sumCurried = curry(hardFunc()) // curry
-
normal
sum(hardFunc(), 2, 3)
-
partail application
sumPartial(2, 3)
-
curry
sumCurried(2)(3)
結果:
normal x 80.08 ops/sec ±3.08% (66 runs sampled)
partail application x 20,190,427 ops/sec ±1.35% (84 runs sampled)
curry x 17,426,307 ops/sec ±1.20% (85 runs sampled)
可以看到不管是 partial application 或 curry 效能都比基本用法好上幾十萬倍, 計算越複雜,差距會越大
有複雜運算但改為二元函式
sum(hardFunc(), 2)
結果:
normal x 62.09 ops/sec ±3.28% (57 runs sampled)
partail application x 17,972,102 ops/sec ±1.00% (85 runs sampled)
curry x 44,001,782 ops/sec ±1.63% (79 runs sampled)
此次結果 curry 大獲全勝
結論
在 Functional programming 中, 依照不同情況,適當的使用 currying 或 partial application 對優化程式是很有幫助的
參考
browserslist & stylelint
browserslist & stylelint
browserslist
這是一個可以在不同開發工具之間, 指定出共同目標的瀏覽器版本, 例如
Autoprefixer
Stylelint
babel-preset-env
stylelint-unsupported-browser-features
- ... 等等
所有的設定條件將基於 can i use ? 的支持列表
設定
使用方式可以在 package.json
加入
{
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
或是在根目錄下新增 .browerslistrc
> 1%
last 2 versions
not ie <= 8
這段設定的語意是, 包含所有使用率大於 1%的瀏覽器, 且是此瀏覽器最新的兩個版本, 並排除 IE8 以下的版本,
not
表示反選
相關小工具
npx browerslist
可以顯示所有目前設定的瀏覽器版本列表
npx browserslit-ga
生成訪問你網站的版本分布數據, 需要有 google analytics
最佳實踐
-
不要僅僅是使用
last 2 Chrome versions
此類的條件,市面上的瀏覽器太多, 瀏覽器版本也非常碎片化, 應多多考慮不同瀏覽器兼容性的問題 -
如果不使用
browserslsit
的默認設定, 推薦使用last 1 version
,not dead
和> 0.2%
(或者> 1% in US
,> 1% in my stats
) -
不刻意移除特定瀏覽器的兼容, Opera mini 在非洲有一億用戶,全球範圍內,它也比 微軟的 Edge 瀏覽器更加流行。 QQ 瀏覽器的使用量比桌面端的火狐和 Safari 瀏覽器加起來還多
stylelint
通常我們 coding convention 會遵照一些規範
stylelint
是一套 css 版的 eslint
, 可以在團隊共同開發時, 讓大家可以在提交代碼的時候去檢查自己寫的 css 有哪些問題, 也可以在 git pre-commit
時作為最後一道防線, 以防 commit 上去之後大爆炸, 更可以在有條件的情況下自動 fix 有問題的 code
安裝
demo 順便安裝一下 stylelint-no-unsupported-browser-features
, 這套工具超方便!
可以搭配 vscode 擴充套件, 在寫 css 就可以發現自己不小心寫了哪些 瀏覽器不支援的 css 屬性
效果如下圖
1. 安裝模組
$ npm install stylint stylelint-no-unsupported-browser-features
2. vscode 擴充套件下載 stylelint
3. package.json 加入 stylelint
設定
"scripts": {
"lint:css": "stylelint **/*.{html,vue,css,sass,scss}"
},
"stylelint": {
"plugins": [
"stylelint-no-unsupported-browser-features"
],
"rules": {
"plugin/no-unsupported-browser-features": true,
"property-no-vendor-prefix": true,
"color-hex-length": "long"
},
"defaultSeverity": "warning"
},
測試
我們可以在一個 css 檔案裡面測試看看
.test-a {
display: grid;
will-change: transform;
}
.test-b {
-webkit-animation: none;
}
vscode 會依據 stylelint
的設定, 即時提示目前檔案有哪些問題, 可以看到 -webkit-animation: none;
, 已經被畫上了綠線, 並標示出 依據 stylelint-no-unsupported-browser-features
, css-animation
屬性不被 Opera Mini
支援, 且依據 property-no-vendor-prefix
, 直接寫 -webkit-
前綴是不被允許的
自動 fix
這是一個很好用的功能, 但其實是有條件的, 它會自動幫你在 rules
裡面寫的條件去修正錯誤, 但只限於 這邊, 後面有列出 Autofixable
的選項才可以, 這邊試了一個例子
.test-c {
color: #fff;
}
因為 rules
裡的 color-hex-length": "long"
的關係, #fff
會出現警告提示, 在下了 npm run lint:css -- --fix
, 之後 stylelint
會幫我們自動進行修正為
.test-c {
color: #ffffff;
}
超級方便!
參考
yarn 小筆記
yarn 小筆記
安裝
- npm
$ npm install -g yarn
- Homebrew
brew install yarn
or
brew install yarn --without-node #如果使用 nvm 之類的管理 node.js 版本,應該排除安装 node.js 以使用 nvm 的 node 版本
yarn 相關指令
$ yarn init # === npm init
$ yarn add # === npm install [packageName] --save
$ yarn add [package] --dev # === npm install [package] --save-dev
$ yarn add [package]@[version] # === npm install [package]@[version] --save
$ yarn add [package]@[tag] # === npm install [package]@[version] --save
$ yarn global add [package] # === npm -g install [package]
# yarn 不推薦全域安裝
$ yarn upgrade # === npm upgrade
$ yarn remove [package] # === npm uninstall [package]
$ yarn run # === npm run
$ yarn start # === npm start
yarn vs npm ?
優點: 安裝 package 較快
npm 執行 npm install
時, 會列隊逐一下載 package, 而 yarn 則是平行非同步下載, 且如果在其他專案有安裝過相關的 package, yarn 會將他存在 cache, 所以甚至離線狀態都能把依賴的 package 安裝回來, 這點在目前公司的環境專案很大包, 以及網路非常慢的狀況下實在非常有感
缺點: 額外的複雜度
其實 yarn 原本還有一個很重要的優勢就是 yarn.lock
, 這個檔案會紀錄該專案依賴的 packages 的詳細版號, 使開發成員可以使用統一版本的 package 來開發, 以防很多不必要的錯誤或bug, 不過 npm 在 npm 5.0 版本後也有自己的 package-lock.json
, 功能幾乎是一樣的
所以其實目前來看 yarn 的確效能比較好, 但該有的 npm 其實都有, 並且而外引入一個工具, 對團隊的新人來說也相對增加複雜度, 是值得考量的地方
參考
iTerm2 + Oh My Zsh 安裝&設定
iTerm2 + Oh My Zsh 安裝&設定
安裝 brew (有裝過 brew 可以跳過此步驟)
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安裝 iTerm2
$ brew cask install iterm2
安裝好後設定 color scheme 為 Solarized Dark
安裝 Oh My Zsh
$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
看看是否成功
$ zsh --version
把預設的終端機改為zsh
$ sudo sh -c "echo $(which zsh) >> /etc/shells"
$ chsh -s $(which zsh)
重新開啟終端機, 輸入
$ echo $SHELL
成功的話會顯示 /usr/local/bin/zsh
更改設定檔
$ vi ~/.zshrc
找到 ZSH_THEME=”…”
, 修改主題為 tonotdo
安裝 Oh My Zsh 外掛
Auto Suggestions
酷酷的會提示打過的字, 按方向鍵右可以直接補完
$ git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
一樣更改設定檔,以空格分開
plugins=(git zsh-autosuggestions)
背景色會和提示字體太接近而看不到, 這裡可以更改字體顏色
$ vi $ZSH_CUSTOM/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
找到 ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
, 修改 fg=240
Syntax Highlighting
打出正確的字元後會高亮, 例如 node
,which
以防不小心打錯字
$ brew install zsh-syntax-highlighting
一樣打開設定檔, 在最下面加上這行
# For zsh syntax-highlighting
source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
修改 prompt
因為預設的是 user@hostname
, 很長很鬧
$ whoami
知道我是誰後
再打開 ZSH 設定檔, 加入下面這段, username
就是剛剛顯示的使用者名稱
# optionally set DEFAULT_USER in ~/.zshrc to your regular username to hide the “user@hostname” info when you’re logged in as yourself on your local machine.
DEFAULT_USER=username
使用鍵盤上的 Home/End 鍵
打開設定檔加入下面這段
bindkey "\033[1~" beginning-of-line
bindkey "\033[4~" end-of-line
Next.js 串接 hackmd, 用 markdown 語法快速更新網站內容
Next.js 串接 hackmd, 用 markdown 語法快速更新網站內容
Intro
最近想做個靜態網站, 想要可以快速更新網站內容
又想要快速使用 React 寫出互動
很直覺的想要用 markdown 語法做內容更新
加上框架 Next.js 快速開箱即用
UI style
UI style 之前就很喜歡 MUI (mui.com) 的風格
加上官網又有 quick start 配置 MUI + Next.js example project (還是 Typescript 版本的)
https://github.com/mui/material-ui/tree/master/examples/material-next-ts
直接拉下來後 yarn install
yarn dev
, 幾分鐘馬上就能使用就是舒服
Markdown 來源選擇
想法有幾種,
- repo 內寫 .md 檔案
缺點是想更新內容還要 commit + push, 會動到 git - repo 寫 issue 後去爬內容
要用 git api token 拿 markdown, 而且總覺得網站內容放在 issue 不太適合 - hackmd
爬 source html id="doc" 可以直接拿到 markdown, 最後決定用這個方式
Next 如何使用 markdown
想法是網站內容放在 hackmd 做公開後
一方面 hackmd 可以自動幫我優化 SEO 散佈內容
一方面我可以在 build time 去爬內容塞到頁面中
爬 hackmd
hackmd 內容全部會放在 id="doc"
中, 並且全部是 markdown 語法
export const getStaticProps: GetStaticProps<Props> = async () => {
const res = await axios.get('https://hackmd.io/@jzAV4awqe2W_9g/S1asqeceh')
const $ = cheerio.load(res.data)
const doc = $('#doc').html();
if (!doc) throw new Error('not found doc')
return {
props: {
article: doc,
},
};
};
markdown to html
這裡安裝一個 package react-markdown
用法很簡單, 會把語法自動轉成 html element
像是 #
會轉成 h1
這個很重要, 因為使用 Next SSG or SSR
搜尋引擎爬到的是 html markup 分數才會高
import ReactMarkdown from 'react-markdown'
<ReactMarkdown children={article} />
內容 style
再把轉過的 element 加工一下好看一點
做一個 container, 這邊用 styled-components
const MarkdownWrap = styled.div`
max-width: 760px;
padding-bottom: 80px;
min-height: calc(100% - 160px);
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
img {
width: 100%;
height: auto;
}
h1 {
font-size: 28px;
}
`
完整內容
interface Props {
article: string;
}
export const getStaticProps: GetStaticProps<Props> = async () => {
const res = await axios.get('https://hackmd.io/@jzAV4awqe2W_9g/S1asqeceh')
const $ = cheerio.load(res.data)
const doc = $('#doc').html();
if (!doc) throw new Error('not found doc')
return {
props: {
article: doc,
},
};
};
const About: React.FC<Props> = ({ article }) => {
return (
<Container maxWidth="lg">
<Box
sx={{
my: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
>
<MarkdownWrap>
<ReactMarkdown children={article} />
</MarkdownWrap>
</Box>
</Container>
);
}
const MarkdownWrap = styled.div`
max-width: 760px;
padding-bottom: 80px;
min-height: calc(100% - 160px);
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
img {
width: 100%;
height: auto;
}
`
export default About;
Build
因為我要部署在 GCS, 要在 Next.config.js
加個設定 exportTrailingSlash: true,
這樣才能讓輸出是我想要的
Next.js 指令可以下 next build && next export
就會 bundle 出完整的靜態檔
可以在測試看看是不是都正常! serve ./out
後續會再把它部署到 GCS 上面
下一篇
npm semver 小筆記 和 npm install 與 npm upgrade 差別
npm semver 小筆記 和 npm install 與 npm upgrade 差別
雖然只是一個小知識, 但每每都忘記這些符號代表什麼, 還是來做個小記錄
semver
npm 依賴管理的一個重要特性是採用了語義化版本 semver 規範
,作為依賴版本管理方案。
semver 約定一個包的版本號必須包含3個數字,格式必須為 MAJOR.MINOR.PATCH
, 意為 主版本號.小版本號.修訂版本號
- MAJOR 對應大的版本號迭代,做了不兼容舊版的修改時要更新 MAJOR 版本號
- MINOR 對應小版本迭代,發生兼容舊版API的修改或功能更新時,更新MINOR版本號
- PATCH 對應修訂版本號,一般針對修復 BUG 的版本號
// 舉例
"dependencies": {
"package1": "1.0.0", // 只接受1.0.0的版本
"package2": "1.0.x", // 接受1.0版本的bug修復或小更新
"package3": "*", // 最新的版本,不推薦
"package4": ">=1.0.0", // 接受任何1.0.0版本後的更新
"package5": "<1.9.0", // 接受不超過1.9.0版本的更新
"package6": "~1.8.0", // 接受 >= 1.8.0 並 < 1.9.0 的版本
"package7": "^1.1.0", // 接受 >=1.1.0 並 < 2.0.0 的版本
"package8": "latest", // tag name for latest version
"package9": "", // 同 * 即最新版本。
"packageX": "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
}
npm install 與 npm upgrade 差別
npm install
會把 package.json 中, 尚未安裝的 package 安裝下來
npm upgrade
也會將尚未安裝的 package 安裝下來, 但如果遇到 package.json 中, 檢查到該 package 版號不是固定版本的話, 就會根據描述的 semver
去更新並安裝
參考
解決 safari mobile 上 css 數值 "vh" 的問題
Preface
最近發現在 safari mobile 上面展開一個 position: fixed; height: 80vh;
的 panel,
和其他瀏覽器相比, 高度明顯高於 80vh
這是在 chrome mobile 的畫面
這是 safari mobile (no address bar)
safari mobile (with address bar)
估狗了一下發現這是很多人都會遇到的問題
其實一張圖就可以完美詮釋這個問題
並且 Apple Safari Team 針對這個問題, 帥氣的回覆了:
It’s not a bug, it’s a feature.
好吧 既然是個 feature, 我們就來實做一個 feature
Try to solve
如果是為了 height: 100vh;
有一個非常單純的解法 height: -webkit-fill-available;
update: chrome v84以後 似乎已經不支援
-webkit-fill-available
如果不是 100vh 可以使用以下 css variables 的解法
css:
:root {
--vh: 1vh;
}
function setCorrectViewHeight() {
const windowsVH = window.innerHeight / 100
document.documentElement.style.setProperty('--vh', windowsVH + 'px')
}
export default function safariHacks() {
setCorrectViewHeight()
window.addEventListener('resize', setCorrectViewHeight)
}
在適當的時候 import 這兩支 css 和 js 既可
然後在需要用到 vh 的時候這樣用就可以了
max-height: 80vh;
max-height: calc(var(--vh) * 80);
完成!
Reference Articles
Mirror - 快速搭建含留言功能的Blog
Mirror
a blog tool base on GitHub issues
安裝
# 注意M開頭為大寫
$ npm install Mirror -g
新建一個 blog
$ cd blog
$ mirror init
設置 token (已有token可以跳過此步驟)
token
是 GitHub API 驗證用的
在這裏新增token
只需要讀取權限, 勾選以下兩項
- read: user Read all user profile data
- user: email Access user email addresses (read-only)
按下 Generate Token
就可以了
配置 blog
修改目錄中的 config.yml
範例 :
# 是否为组织项目
organization: false
# 文章排序,依据更新时间还是创建时间
# 值为 'UPDATED_AT' 或者 'CREATED_AT'
order: 'UPDATED_AT'
# 网站标题
title: my blog
# GitHub 用户名或者组织名
user: jeffkko
# 项目名称,项目里面的 issues 是你的博客内容
# 如果你的项目是组织项目,请设置 'organization' 为 true
repository: blog
# 多用户设置,上面设置的 user 用户名默认已经包括
# 多用户名字必须用 ',' 分割
# 例子: author0,author1
authors:
# token 设置
# token 必须以 '#' 分割
# 如果你的 token 是 5c31bffc137f44faf7efc4a84da827g7ca2cfeaa
# 那么这里填入的值为 5#c31bffc137f44faf7efc4a84da827g7ca2cfeaa
token: 5#c31bffc137f44faf7efc4a84da827g7ca2cfeaa
# 文章列表分页
perpage: 10
注意剛剛拿到的token必須隨機插入一個 #
做分割, 沒有的話等等上傳到 github 會被刪除
生成 blog
$ mirror build
發佈到 github
所有内容都 push 到一個 repository 的 gh-pages 分支
設置 GitHub Pages
到該 repository 的 setting -> GitHub Pages
這裏可以發布一個 gitbuh pages, branch 選擇 gh-pages
成功的話會看到一段訊息:Your site is published at https://<user>.github.io/<repository>/
到這邊算是建置完成了
設定客製化域名
通過修改 CNAME
, 可以藉由 DNS 轉發, 設置自己的 domain
參考這篇文章 gh-pages 域名绑定
完成
現在可以在 github issue
上面寫 blog 了, https://<user>.github.io/<repository>
或是自己 自訂的網域
都會同步更新所有內容!
參考
在 OS X 上設定 Apache & PHP 環境
Apache
為了避免稍候有任何權限問題,先把執行權限調整成 root
sudo su -
然後開啟 Apache
apachectl start
然後看看 http://localhost/ 是否有出現 “It works!” ,如果有就代表 Apache 已經正常運作了,根目錄位置在 /Library/WebServer/Documents
PHP
接下來要開啟 PHP 模組,先把現在的 Config 備份一下等一下搞壞了還可以救回來
cd /etc/apache2/
cp httpd.conf httpd.conf.bak
編輯 httpd.conf
vi httpd.conf
找到以下這行並且把前面的井字號 (#) 去除
#LoadModule php7_module libexec/apache2/libphp7.so
重新啟動 Apache
apachectl restart
可以用 Finder 在根目錄放個 phpinfo() ,或是直接用 CLI 放
(修改檔案時可能會要求 root 權限)
echo '<?php phpinfo();' > /Library/WebServer/Documents/phpinfo.php
打開 http://localhost/phpinfo.php ,看是否有正確跳出 phpinfo 資料
如果不想要每次寫完檔都還要放到預設資料夾中去做設定可以接著做以下的步驟
cd ~
mkdir Sites
接著我們要透過以下指令在Apache的資料夾底下建立一個設定檔,記得將UserName的部分改成自己的使用者名稱
$cd /etc/apache2/users
$vim UserName.conf //username記得換上自己的使用者名稱
將以下內容寫進檔案中
<Directory "/Users/UserName/Sites/">
AllowOverride All
Options Indexes MultiViews FollowSymLinks
Require all granted
</Directory>
設定完之後要將目前的位置先切換到下一個要設定的設定檔所在的資料夾中
$cd /etc/apache2
$vim httpd.conf
之後在大約是第71、72、166、169、503將這幾行前面的#刪除
71 LoadModule authz_core_module libexec/apache2/mod_authz_core.so
72 LoadModule authz_host_module libexec/apache2/mod_authz_host.so
166 LoadModule userdir_module libexec/apache2/mod_userdir.so
169 LoadModule php7_module libexec/apache2/libphp7.so
503 Include /private/etc/apache2/extra/httpd-userdir.conf
並將第290行的 Require all denied
改成
Require all granted
接著修改最後一個設定檔
$cd /etc/apache2/extra
$vim httpd-userdir.conf
再將第16行前面的#刪除
16 Include /private/etc/apache2/users/*.conf
重啟 Apache
apachectl restart
重啟後開啟 http://localhost/~UserName ,記得一樣要將UserName的部分改成自己的使用者名稱,就可以知道有沒有成功了!
環境
macOs Mojave 10.14.2
參考
javascript 型別轉換之效能比較
javascript 型別轉換之效能比較
最近在思考 coding convention
和 program performance
如何取到一個最好的平衡點
通常是這樣
- 好讀的 code 會犧牲掉執行效能
- 效能好的 code 會犧牲掉易讀性
就突然想來比較一下一些js中的奇淫技巧對於效能上來說到底差多少, 於是有了這篇~
Number to String
-
Number.prototype.toString()
function jsToString(num) { return num.toString() }
-
String()
function jsString(num) { return String(num) }
-
plus empty string
function plusEmptyString(num) { return num + '' }
-
es6 string template
function es6StringTpl(num) { return `${num}` }
執行 benckmark 測試結果
jsToString x 39,846,308 ops/sec ±1.23% (39 runs sampled)
index.js:41
jsString x 99,686,491 ops/sec ±1.56% (39 runs sampled)
index.js:41
plusEmptyString x 749,016,046 ops/sec ±3.55% (37 runs sampled)
index.js:41
es6StringTpl x 748,302,252 ops/sec ±2.87% (36 runs sampled)
Number.toString()
和 String()
都是 js 原生提供的 api, 3 和 4
則是 js runtime 時的 隱晦轉型
, 可以看到效能竟然差了快 20 倍!
為什麼呢 ?
-
方法1. 等於是對參數做了
Number.prototype.toString()
的方法, 每次呼叫這個方法都會放入 js engineCall Stack
裡面列隊, 在js runtime
時查詢並執行 -
方法2.
String()
則是等於直接寫一個字串, 例如const str = String(123)
和const str = '123'
基本上是相同的, 變數str
都會是由 建構子String
實例化的實體const str1 = String(123) const str2 = '123' console.log(str1.constructor === str2.constructor) // true
-
方法3, 4 則是在
js precompiler
階段就對參數進行ToPrimitive()
(內部轉換原始值的方法), 所以速度一定是最快的
String to Number
-
parseInt()
function jsParseInt(str) { return parseInt(str) }
-
Number()
function jsNumber(str) { return Number(str) }
-
add plus
function addPlus(str) { return +str }
執行 benckmark 測試結果
jsParseInt x 204,876,967 ops/sec ±1.67% (38 runs sampled)
stringToNumber.js:32
jsNumber x 119,052,148 ops/sec ±0.27% (39 runs sampled)
stringToNumber.js:32
addPlus x 781,953,540 ops/sec ±1.95% (37 runs sampled)
效能由高至低分別是 3 > 1 > 2 (不過parseInt 其實不是正當拿來做嚴格轉型用的)
Number to Boolean
-
Boolean()
function jsBoolean(num) { return Boolean(num) }
-
add plaint
function addPlaint(num) { return !num }
-
add 2 plaint
function addPlaint(num) { return !!num }
執行 benckmark 測試結果
jsBoolean x 740,109,686 ops/sec ±5.36% (35 runs sampled)
numberToBoolean.js:32
addPlaint x 761,570,367 ops/sec ±1.45% (38 runs sampled)
numberToBoolean.js:32
addPlaintPlaint x 749,909,994 ops/sec ±3.85% (36 runs sampled)
這邊很意外的是使用 Boolean()
來轉換布林值, 效能竟然和隱晦轉型是一樣的!
參考
function cache
cache 的型別
分別嘗試兩種看起來適合的型別
- 原生 Object
cache[testKey] //object 的取值方法
- ES6 Map
cacheMap.get(testKey) // map 的取值方法
結果:
ops/sec
表示每秒運算的次數
-
cache 10筆資料
object x 20,175,513 ops/sec ±2.05% (66 runs sampled) map x 7,007,436 ops/sec ±4.15% (69 runs sampled)
-
cache 1000筆資料
object x 20,268,662 ops/sec ±1.62% (65 runs sampled) map x 7,480,171 ops/sec ±0.70% (73 runs sampled)
object
的效能大概是map
的2.5
倍 ,而 cache 筆數多寡並不會對效能造成影響
參數序列化
因為參數有可能是 object
, array
等形式,必須藉由 JSON.stringify
當參數序列化
function memorizeFunc(...args) {
return JSON.stringify(args)
}
memorizeFunc(123, { a : 3 }) //"[123,{"a":3}]"
目前 cache function 大致上可以寫成這樣
function memorizeFunc(...args) {
let key = JSON.stringify(args)
if( key in cache ) {
return cache[key]
} else {
let random = getRandomString(1000)
cache[key] = random
return random
}
}
分別對 1個參數、2個參數、3個參數測試
1 param x 978,054 ops/sec ±5.29% (68 runs sampled)
2 params x 1,177,433 ops/sec ±0.70% (89 runs sampled)
3 params x 1,072,089 ops/sec ±0.84% (91 runs sampled)
優化參數
避免每次執行 JSON.stringify
造成性能消耗,我們先優化只有一個參數的情況
function memorizeFunc(...args) {
let key
if(args.length === 1) {
[key] = args
} else {
key = JSON.stringify(args)
}
if( key in cache ) {
return cache[key]
} else {
return cache[key] = getRandomString(1000)
}
}
結果:
獲取單個參數的效能提升為多參數的 4倍 左右
1 param x 2,751,567 ops/sec ±9.19% (60 runs sampled)
2 params x 662,869 ops/sec ±9.89% (54 runs sampled)
3 params x 708,058 ops/sec ±0.90% (83 runs sampled)
git config 小筆記
git config 小筆記
init profile
$ git config --global user.name "xxxxx"
$ git config --global user.email "[email protected]"
check profile
$ git config --global --list
# or
$ git config -l
ssh key
# check ssh key exist
ls -al ~/.ssh
# gen ssh key
$ ssh-keygen -t rsa -C "[email protected]"
# copy public key
$ cat ~/.ssh/id_rsa.pub | clip
alias
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
$ git config --global alias.l "log --oneline --graph"
$ git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
或者直接編輯設定檔
$ vi ~/.gitconfig
[alias]
co = checkout
br = branch
# 利出 local 與 remote branch
brav = branch -av
ci = commit
st = status
l = log --oneline --graph
# 漂亮一點的 log, 而 ls -p 可以看到變動的內容
ls = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
check branch
$ git branch
# or
$ git branch -r # 遠端的
$ git branch -a # remote + local
參考
PyTorch、CUDA Toolkit 及顯卡驅動的安裝流程
PyTorch、CUDA Toolkit 及顯卡驅動的安裝流程
Intro
安裝的 CUDA 版本必須符合 PyTorch 版本
CUDA 版又必須符合顯卡驅動程式的版本
所以寫個文章記錄一下
指令下
nvidia-smi
可以看到顯卡驅動的版本 (Driver Version)
顯卡驅動版本和 CUDA 對應版的可以看這個表
基本上都是版本高的可向下兼容
https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html
安裝 CUDA
到官網安裝查一下自己電腦適合安裝甚麼版本 CUDA
https://pytorch.org/get-started/locally/
進去後面後自動選的就是了
在到官網下載 CUDA
最新版 https://developer.nvidia.com/cuda-downloads
歷史版本 https://developer.nvidia.com/cuda-toolkit-archive
安裝好後可以下指令確認版本
nvcc -V
可以看到 release ${version}
安裝 PyTorch
CUDA 安裝完成後
回到 Pytron 官網可以看到對應的版本下有安裝指令
例如 pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu117
這個指令會一併安裝 torchvision 和 torchaudio
有人說會安裝失敗可以改 pip3 改成 pip
測試
都安裝好後可以建立一個 test.py
import torch
print(torch.__version__)
tensor = torch.rand(3,4)
print(f"Device tensor is stored on: {tensor.device}")
# Device tensor is stored on: cpu
print(torch.cuda.is_available())
#True
tensor = tensor.to('cuda')
print(f"Device tensor is stored on: {tensor.device}")
# Device tensor is stored on: cuda:0
執行檔案測試
python test.py
2.0.0+cu117
Device tensor is stored on: cpu
True
Device tensor is stored on: cuda:0
看到這個輸出就代表成功了 (這個是 PyTorch 2.0.0 + cuda 11.7版本)
參考
Using pre-commit to avoid mistake
Using pre-commit to avoid mistake
Intro
為了避免糟糕的 commit, git 提供了 pre-commit hook, 衍伸出 husky
及 lint-staged
這類工具, 當然除了 lint
以外, Testing
也可以考慮在 pre-commit 階段去做
Setup
-
husky
-
eslint
-
lint-staged
husky
Husky 可以幫助我們在 git commit 或 git push 時執行一些動作
npm i -D husky && npx husky init
跑完之後, 會在 Project 裡面多出一個路徑, 名稱是 .husky, 預設 pre commit 執行的 script 是 npm test
在 package.json 加入 prepare script, 可以在 install 之後自動執行 husky install
// package.json
"scripts": {
"prepare": "husky install",
}
eslint
增進團隊 code quality 的好工具
npm i -D eslint eslint-config-airbnb eslint-plugin-vue vue-eslint-parser
// eslintrc.js
module.exports = {
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
ecmaFeatures: {
tsx: true, // Allows for the parsing of JSX
},
},
plugins: ['eslint-plugin-vue'],
extends: [
'plugin:vue/base',
'plugin:vue/vue3-recommended',
],
rules: {
'vue/html-self-closing': 'off',
'vue/max-attributes-per-line': 'off',
semi: [2, 'never'],
quotes: [2, 'single'],
'comma-dangle': [2, 'always-multiline'],
},
env: {
browser: true,
node: true,
},
}
基本使用
.node_modules/.bin/eslint .
lint-staged
lint-staged 可以讓我們 lint 那些已經 git add 的 staged files
npm i -D lint-staged
跑完之後, 在 Project 新增一個 .lintstagedrc.js
// lintstagedrc.js
module.exports = {
'*.+(js|vue)': [
'eslint . --fix',
'git add',
]
}
在 Commit 之前執行 lint & E2E Testing
因為 cypress visit url 是 localhost, 所以 run cypress 前必須 run vite 的 local dev server, 這邊可以用 npm-run-all
這個套件幫我們安排執行
npm i -D npm-run-all
最後 package.json script 會長這樣
// package.json
"script": {
"prepare": "husky install",
"dev": "vite",
"test": "npm-run-all test:lint-stage -p -r dev test:e2e",
"test:lint-stage": "lint-staged",
"test:e2e": "cypress run --spec 'cypress/integration/sample/*'"
}
其中 test 這樣寫 會先執行 test:lint-stage
, 在同步執行 dev
和 test:e2e
, 而 dev
, test:e2e
其中有一個 process 結束後, 就會 kill 另一個 precess
-p, --parallel - Run a group of tasks in parallel.
e.g. 'npm-run-all -p foo bar' is similar to
'npm run foo & npm run bar'.
-r, --race - - - - - - - Set the flag to kill all tasks when a task
finished with zero. This option is valid only
with 'parallel' option.
最後, 就可以修改及 git commit 看看了!
Conclusion
這邊目前先做的是 commit 之前執行 lint 和 E2E Testing, 實際在專案上的使用情形可能會把 E2E Testing 放在 trunk branch 下 Tag 之前做, 或者 push 到 trunk branch 之前做, 畢竟每個 commit 都做 E2E Testing 其實沒什麼必要(而且太消耗時間)
References
tRPC - Full-Stack 一條龍的開發好幫手
What is tRPC?
- RPC【Remote Procedure Call】一種遠端通訊方式,是一種技術的想法,而不是規範
- End-to-End typesafe APIs (Not Only BE to FE)
- still use
http
/websocket
protocol
vs REST / GraphQL
- tRPC 是一個函式庫,它允許你使用TypeScript和遠端過程呼叫(RPC)來建立類型安全的API。 RPC協定允許你像本機呼叫函數一樣在遠端伺服器上呼叫函數。 所以..tRPC透過自動產生類型和驗證輸入輸出來簡化這個過程。
輕度 API
- REST 是一種採用HTTP方法(例如GET、POST、PUT和DELETE)在URL標識的資源上執行操作的架構風格。 REST被廣泛使用,並得到許多工具和框架的支援。 然而,對於複雜或嵌套的數據,REST可能會變得冗長、不一致而且效能低。
經典 API
- GraphQL 是一種查詢語言,它允許你使用查詢、變更和訂閱來指定從伺服器取得的精確資料。 GraphQL可以透過允許客戶端僅請求所需資料來減少資料的過度取得或不足獲取。 但GraphQL也有一些缺點,例如複雜度、快取問題和效能開銷。
巨量 API
My Paint Point
正常開發 (前後分離)
1. 寫 API 文件
2. BE/ FE 同時開發
3. 修改 API 文件
4. BE/ FE 同時修改
目前工作上開發流程
因為目前的工作也算是個半個 full-stack, Front-end 這有個 nodeJS server 要自己維運, 所以我們的開發流程會是這樣:
1. 寫 API 文件
2. BE 開發 (Api/Cron Job)
3. FE 開發
4. 修改 Api
5. 修改 Cron Job (可能忘了改)
6. 修改 Api 文件 (可能忘了改)
7. FE 修改 (可能忘了改)
type unsafety!
APIs In CodeBase
目前 codebase 也常常會有這種程式碼 (不管是 FE 或 server side)
const doLogin = async (payload: any) => {
const { data } = await axios.post('url', payload);
if (data.isSuccess) {
...
}
}
// Cannot read properties of undefined (reading 'data')
使用 Typescript 的泛型可以增加安全性, 但 FE 和 server side 要兩邊維護 很麻煩
interface LoginPayload {
username: string;
password: string;
}
interface LoginResult {
isSucces: boolean;
}
const doLogin = async (payload: LoginPayload) => {
const { data } = await axios.post<LoginResult>('url', payload);
if (data.isSuccess) {
...
}
}
Demo
https://trpc.io/docs/quickstart
官網這邊 quickstart 內坎的 source code 可以玩玩
Advantage
- 開發時就可以先發現型別問題
- 開發一條龍的話很好的提升 DX
- Zod schema 可以 runtime 處理 payload 驗證, 不用自己寫
- 效能提升: lib 自動將 request batch
Disadvantage
- 跟 server 耦合度很高不易拆分
- 學習成本
我認為適合使用 tRPC 的時候
玩了一下之後我覺得專案如果有幾下特點的話會很適合
- JS stack
- 前後端都使用 javascript)
- Typescript
- 當然還要搭配 Typescript
- monorepo
- 像 Next.js 這種直接開發前後端就算, 其實不用 monorepo 也可以用, 但開發時就要用 npm link 達到同樣的效果
- Api 和前端高耦合
- 也就是 Api 都只開給前端用, 而沒有給其他 server 去打, 這種情況一般會使用 REST
在 OS X 上安裝 v8js
安裝 brew (有裝過 brew 可以跳過此步驟)
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安裝 v8
brew install v8
安裝 v8js
安裝 PECL
從 github 下載 v8js 安裝包並安裝
cd ~
mkdir tmp && cd tmp
git clone https://github.com/phpv8/v8js
cd v8js
phpize
./configure CXXFLAGS="-Wno-c++11-narrowing"
make
make test #if this step fails you can try to install anyway. should work.
make install
如果在 make install 步驟跳出 operation not permitted
錯誤的話
需要按這篇文章來解除封鎖
如果遇到 'php.h' file not found
的錯誤, 需要重新安裝 common line tool 和 SDK
xcode-select --install
sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /
安裝成功之後 在 php.ini (/etc/php.ini)
裡面加入內容
extension=v8js.so
重新啟動 Apache
sudo apachectl graceful
之後可以輸入以下指令確認 v8js 是否安裝成功
php -i | grep v8js
或是直接開啟 phpinfo 看看有沒有 v8js 擴充套件的資訊
參考
Using Cypress to E2E Testing
Using Cypress to E2E Testing
Preface
既然要完成一個 CI/CD flow, 一定要包含個自動化測試, 既然是 E2E Testing, 就來玩一下目前最夯的 Cypress
Setup
npm install cypress
跑完之後 會在 Project 裡面多出一個路徑, 名稱是 cypress, 還有一個 cypress.json
cd cypress/integration
mkdir sample
touch index.spec.js
touch index_hash.spec.js
我們的目的是寫一個範例, 在 local 起一個 server, index page 在 http://localhost:3000
, 讓 cypress 測試通過; 在 http://localhost:3000/#withHash
模擬其他頁面, 讓 cypress 測試失敗
// index.spec.js
describe('The Home Page', () => {
it('successfully loads', () => {
cy.visit('/')
})
})
// index_hash.spec.js
describe('The Home Page (with Hash)', () => {
it('successfully loads', () => {
cy.visit('/#withHash')
})
})
在 cypress.json 加入 local server url
# cypress.json
{
"baseUrl": "http://localhost:3000"
}
最重要的部分, 在 support/index.js
加入這段, 讓 cypress 可以捕捉 console 的 error 或 warning
// support/index.js
Cypress.on('window:before:load', (win) => {
cy.spy(win.console, 'error')
cy.spy(win.console, 'warn')
})
afterEach(() => {
cy.window().then((win) => {
expect(win.console.error).to.have.callCount(0)
expect(win.console.warn).to.have.callCount(0)
})
})
因為這邊 frontend 是用 vite
, 在 src/App.vue
, 模擬在其他頁面噴錯的狀況
// src/App.vue
if (!!window.location.hash) {
console.error('do some error')
}
執行測試
npx cypress run --spec 'cypress/integration/sample/*'
結果如下
Conclusion
用了一下 cypress 後發現其實語法和行為都蠻像之前用過的 puppeteer
, 也有 headless 模式 (畢竟是一樣用 chromium
核心), 不過 cypress 有一個整合性非常好的 cloud service, 可以在上面的 Dashboard 看到整個完整的 testing record, 還可以串接 Github
, Bitbucket
, GitLab
, Slack
...等等, 實在是方便又好用!
References
CircleCI CI/CD flow of deploy node.js App on Heroku and Slack notification
Preface
最近想做個 CI/CD flow Demo, 讓這個 flow 可以跑過前端部署所需的所有自動化流程
流程大概是這樣: commit => lint => E2E test => Github => Heroku => build & Run App => notify to Slack
好久以前也有玩一下 Jenkins
, Gitlab-CI
甚至是自己寫 .git 裡面的 post-update hook
, 這次就來比較看看最近狠新很夯的 DroneCI
, CircleCI
, 比較一下發現兩個都很厲害, 每個步驟都可以由 Docker 產生的環境,高度的容器化,讓每個步驟可以調用不同的 Docker Image 來實現各種測試。
而剛好相反的是 CircleCI 從單純的 SaaS 到現在最近開放了 Self-hosted, 而 DroneCI 從原本的 Self-hosted 到現在也開放了 cloud service, 這次就先從 cloud service 功能已經非常健全的 CircleCI 玩看看吧!
將 github 整合到 CircleCI
最簡單的整合方式就是用 github 帳號 登入 CircleCI, CircleCI 就會有權限去抓到 github 裡有哪些 repo.
對需要建立配置的 repo 點選 setup project, 就會進入最厲害的頁面 -- 設置 yaml, 並且有各式各項的 template, 以及各式各樣的Orbs, 這裡就直接選擇 Heroku deploy with Node
到這裡, 就可以發現 CircleCI 強大的地方, 最最最簡潔的 yaml 配置如下, �這樣就真的已經設置完成了!
version: 2.1
orbs:
heroku: circleci/[email protected]
workflows:
sample:
jobs:
- heroku/deploy-via-git
再按下 Commit and Run
, CircleCI 就會自動把 SSH key 加到 repo, 並且新增 yml 檔案在一個分支 circleci-project-setup
之下
設置 Heroku 權限
yml 完成之後, 再到 environment variables 去新增兩個 Heroku 需要的環境變數 HEROKU_APP_NAME
, HEROKU_API_KEY
- HEROKU_APP_NAME: Heroku application 的 name
- HEROKU_API_KEY: Heroku 帳號的 API key, 可以在 Account => Account Settings => API Key 找到
設定完後, 就大功告成了, 簡單又暴力
測試
只要 repo 有新的 commit, 在 Dashboard 上就會有一個新的 pipeline, 會包含一個 job heroku/deploy-via-git
, 跑完後也可以到 Heroku 的 Activity 看看是否有新的 deployed
整合 slack 到 CircleCI
新的 slack orb 使用 OAuth
取代原本的 Webhooks
去和 Slack 溝通 (害我找老半天, 沒地方填 Webhook Url), 新版可以用 Slack 的 Block Kit Builder 更加客製化的通知方式
詳細的 OAuth Setup 方式可以參考這裡
這邊在測試時發現 event 除非是設定成 always
, 設定成 pass
or fail
一直都不會有 notify, 後來把 orbs 版本改成新版 @4.3.1 就可以了, 浪費了超級多的時間, 所以有問題時最好還是隨時去看 orbs document 注意有沒有新版本
這是最後成功的一個簡單的配置
version: 2.1
orbs:
slack: circleci/[email protected]
jobs:
notify:
docker:
- image: cimg/base:stable
steps:
- run: echo "test my app"
- slack/notify:
event: fail
template: basic_fail_1
mentions: "@here"
- slack/notify:
event: pass
template: success_tagged_deploy_1
workflows:
send-notification:
jobs:
- notify:
context: slack-secrets
新版的 notification template 上的訊息非常清晰易懂, 範例是用基本的 success 和 fail 的 template
Conclusion
總體來說 CircleCI 因為 Orbs 這個功能讓整個 yaml 配置非常簡潔, 有開箱既用的感覺, 目前先把 build, deploy 和 notification 搞定, 下一篇再把整個流程弄完整
Reference Articles
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.