Giter VIP home page Giter VIP logo

blog's Introduction

Hi 👋, I'm Jeff

A passionate frontend developer from Taiwan

  • 🔭 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

Languages and Tools:

html5 css3 javascript typescript sass gulp webpack vuejs vuepress quasar gridsome puppeteer nodejs express firebase heroku

Top Languages Card (Compact layout)

blog's People

Contributors

jeffkko avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Forkers

0xblacktea

blog's Issues

用 git hook 實現自動化部署

用 git hook 實現自動化部署

前言

以前部署都是直接 github page 或整包丟到 nas 上 (靜態用Nginx開啟 8080 port; nodejs 則是 ssh 到 機器上 node index.js), 每次更新一次版本都要做一次重複的動作, 實在是太麻煩了, 所以找找看有沒有實現 CI/CD 的方案, 大致看了一下有兩個不錯的服務可以用

  1. now.sh
  2. 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~~~~~, 看來是成功了

image

在根目錄部署點 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 !!??

image

後來找到這篇 解答

#!/bin/sh 改成 #!/bin/bash -l

-l 參數的意思是執行指令時, 是在login 的 shell 裡執行, 以此例來說就是繼承 b3589481400 這個使用者的路徑和 shell 設定值, 這樣就能使用 npm 等等的指令了

...再 push 一次試試看

image

大功告成 !!

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 都不用另外做特別的處理

  • 缺點:

    1. 是最消耗資源的方式, client端要一直重複建立 Tcp 會非常消耗資源
    2. 實際上比較像是 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, 節省了很多請求次數, 相對節省資源

  • 缺點:

    1. 還是一樣要由 client 發起連接的建立, 消耗資源
    2. 實際上比較像是 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
  }
  • 優點: 沒有多餘的效能浪費

  • 缺點:

    1. client, server 都需要自行處理 http 已經處理好的問題
    2. 在需要多路同步傳輸時, 實現起來有其複雜度

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')
})
  • 優點:

    1. 輕量, 實作相較於 websocket 簡單很多
    2. 開箱既用的實現支援 http/2
  • 缺點: IE不支援

High Level Comparison

一張圖詳細了解 Polling vs SSE

image

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 驗證

image

只要跟著步驟就可以輕鬆驗證完成

image

建立 Bucket, 公開 url

驗證完後才可以開始建立 Bucket

  • 建立新 Bucket, 命名成 staging.ninja.com, 地理精度選 region, 位置選台灣, 這樣網站速度才會快, 相對 SEO 分數也會高
  • 把靜態檔都丟進去
  • bucket 操作頁面選擇 編輯存取權, 之後選擇 新增主體

image

  • 主體輸入 allUsers (這設定方式真是奇耙 😂), 指派給他權限 Storage 繼承物件讀取者

之後就可以看到 公開存取權變成 在網際網路上公開

image

目前這樣會有一個問題, 隨便開一個 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 替換掉

image

完成後, 就可以在 http://staging.ninja.com/index.html 看到網站 !

但是 http://staging.ninja.com 會找不到頁面, 所以我們繼續設定

設定首頁與 404 頁面

在 bucket 操作頁選擇 編輯網站設定 就可以設定 index.html 和 404.html

image

替網站加上 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/*'

結果如下

image

Conclusion

用了一下 cypress 後發現其實語法和行為都蠻像之前用過的 puppeteer, 也有 headless 模式 (畢竟是一樣用 chromium 核心), 不過 cypress 有一個整合性非常好的 cloud service, 可以在上面的 Dashboard 看到整個完整的 testing record, 還可以串接 Github, Bitbucket, GitLab, Slack ...等等, 實在是方便又好用!

References

Vue - 優化大量數據同時渲染

前言

最近在專案上遇到小小的效能瓶頸

有一個畫面是顯示提貨點資訊的 list,
在初次渲染和使用filter時, 體感上會有明顯的 lag
原因是瀏覽器一次改變300多個dom節點造成的delay

Kapture 2020-12-16 at 16 21 28

來看看程式碼
這個 list 是由 state.currentRedemptionList 用 v-for 的方式渲染, 讓每一張 BsCard 呈現提貨點資料
而使用者可以藉由畫面上的 filter 對 state.currentRedemptionList 的數量進行增減

image

在程式碼加上 timelog

onBeforeUpdate(() => {
  console.time('update list')
})

onUpdated(() => {
  console.timeEnd('update list')
})

可以看到使用 特快取件的 filter 反選時,
因為 list 數量一下從 8 => 346
re-render 過程大約需耗時高達 300ms

image

第一步

試試看用 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
}

結果:在體感上已經感覺不到延遲了!

Kapture 2020-12-16 at 16 40 06

可以看到渲染時間被切成碎片化, 甚至第一次 component update 只需要3ms, 效果非常明顯

image

第三步

在優化後可以發現一個重點
雖然渲染速度變快了
但是卻造成畫面閃爍
原因是 在 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
}

看看結果, 已經防止了畫面跳動的問題

Kapture 2020-12-16 at 17 09 32

以上
大功告成!

參考資料

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 對優化程式是很有幫助的

參考

此篇文章所有的benchmark測試

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 屬性

效果如下圖

螢幕快照 2019-06-09 上午12 20 25

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;
}

螢幕快照 2019-06-09 上午12 27 27

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 其實都有, 並且而外引入一個工具, 對團隊的新人來說也相對增加複雜度, 是值得考量的地方

參考

npm和yarn的区别

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 出完整的靜態檔

image

可以在測試看看是不是都正常! serve ./out

後續會再把它部署到 GCS 上面

下一篇

使用 Google Cloud Storage 建立靜態網站

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 的畫面

image

這是 safari mobile (no address bar)

image

safari mobile (with address bar)

image

估狗了一下發現這是很多人都會遇到的問題

其實一張圖就可以完美詮釋這個問題

image

並且 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> 或是自己 自訂的網域 都會同步更新所有內容!

參考

Demo

source

LoeiFy's blog

在 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

參考

1
2

javascript 型別轉換之效能比較

javascript 型別轉換之效能比較

最近在思考 coding conventionprogram performance 如何取到一個最好的平衡點

通常是這樣

  • 好讀的 code 會犧牲掉執行效能
  • 效能好的 code 會犧牲掉易讀性

就突然想來比較一下一些js中的奇淫技巧對於效能上來說到底差多少, 於是有了這篇~

Number to String

  1. Number.prototype.toString()

    function jsToString(num) {
      return num.toString()
    }
  2. String()

    function jsString(num) {
      return String(num)
    }
  3. plus empty string

    function plusEmptyString(num) {
      return num + ''
    }
  4. 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 engine Call 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

  1. parseInt()

    function jsParseInt(str) {
      return parseInt(str)
    }
  2. Number()

    function jsNumber(str) {
      return Number(str)
    }
  3. 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

  1. Boolean()

    function jsBoolean(num) {
      return Boolean(num)
    }
  2. add plaint

    function addPlaint(num) {
      return !num
    }
  3. 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() 來轉換布林值, 效能竟然和隱晦轉型是一樣的!

附上 bench mark 測試

參考

ToPrimitive() 詳解

效能轉換優化

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 的效能大概是 map2.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, 衍伸出 huskylint-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, 在同步執行 devtest: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?

https://trpc.io/

  • 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 可以玩玩

image

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 擴充套件的資訊

參考

1

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.

image

對需要建立配置的 repo 點選 setup project, 就會進入最厲害的頁面 -- 設置 yaml, 並且有各式各項的 template, 以及各式各樣的Orbs, 這裡就直接選擇 Heroku deploy with Node

image

到這裡, 就可以發現 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

image

整合 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

image

Conclusion

總體來說 CircleCI 因為 Orbs 這個功能讓整個 yaml 配置非常簡潔, 有開箱既用的感覺, 目前先把 build, deploy 和 notification 搞定, 下一篇再把整個流程弄完整

Reference Articles

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.