0%

前言

老闆總是覺得前端很簡單,總以為套套版三天就可以上線了
但我能理解,因為國中時我也常把任何事情想得很簡單
沒做過的事都不會知道有什麼難處
所以我並不會對國中生要求太多

但如果能把事情快速做完有何不可?
無奈這間公司的每個網站都是前輩們趕鴨子上架
缺乏一個有系統的管理方式
因此我想來試試看

用 Group 來管理專案

我想做的主要有三件事情

  1. 整合公司 GitLab 內的所有前端專案
  2. 將每個專案中會共用的元件、函式提出來,變成 library
  3. 建立 template

Group 是 GitLab 中的一個功能
你可以把相關的專案放在同一個 group 當中
此時專案的 namespace 就會從

變成

除了一目了然,一下就知道這是前端專案,或是前端模板
最大的優點就是權限控制了

我們試想一個情境
公司內有老闆、PM、RD、Dev Leader、QA
就算每個人都能看到每個專案
每個人應賦予的權限也不一樣
RD 要能編輯,QA 要能開 issue,老闆動口不動手
只要有用 Group 將各個專案進行分類
就不需要對每個專案都設定一次
而是以 Group 為單位來給予權限

  • Owner: 權限最大,掌管專案的生死
  • Maintainer: 通常為資深工程師,負責處理 branch 的異動
  • Developer: 開發人員,專心寫 code
  • Reporter: 測試並回報問題,所以要能開 issue
  • Guest: 只能看,老闆就在這了啦

用 Group 來管理專案

目前公司只有我一個前端,所以權限這件事目前還不需要煩惱
我只要先整理好 Group 的架構就好

首先建立一個名為 Frontend 的 Group
只要是前端的專案都放進來
接著 Frontend Group 底下再建立兩個 sub-group
Templates 和 Shared

Templates 裡面會放一些做好初始設定網站作為模板
包括常用的套件也都會安裝進去
未來要開發新網站就只要從這些模板開始就好
因此我先做了兩個模板
如果需要 SEO,就用 Nextjs 的模板
如果不需要 SEO,或是一次性的網站,就用 Vite 的模板

Shared 裡面放的就是跨專案共用的檔案啦
我分別建立了 component, stylus, utils 三個專案
並以 submodule 的形式加進 template 當中
這樣的好處是,可以維持網站間的一致性
例如每個網站都有一樣的 footer
那我就只要修改 Shared/components 這個 submobule
再打開每個專案更新 submodule,重新跑一次 CICD
就不用怕忘了改(或改錯)某個網站的 footer 了

大至上的架構是這樣,中括號代表 group,其他的就是 repo:

1
2
3
4
5
6
7
8
9
10
11
[Frontend]
[Templates]
Nextjs-web3-template
Vite-web3-template
[Shared]
component
stylus
utils
Website 1
Website 2
Website 3

結論

利用 Group,我們便能將各個 repo 分類
光看 namespace 就知道專案的類型
也能用更方便的方式管理專案權限

前言

最近更新公司的網站,除了 UI 以外也改了架構上的東西
專案不僅要分成 testnet, staging, mainnet 三種環境
在每個環境下又要支援不同鏈的切換,進而顯示不同內容

專案中還放一個策略設定檔,用來顯示該鏈所有策略的資料
這個設定檔的特別之處在於不能納入 build
因為希望之後等網站部署好,其他人(例如marketing)能夠藉由別的方式去修改server上的設定檔
這樣未來無論我們新增了多少策略,都不需要透過前端我更新網頁,也不需要重跑CICD的流程
以達到前端的最終型態

public 資料夾

public 資料夾是 react 專案中特別的存在
在不做其他額外設定之下
裡面的檔案不會經過 compile, 而是在 build 之後被原封不動地放到 BUILD_DIR 中

根據 react 的建議
如果你希望檔案在 build 之後維持檔名,就可以把它放進 public 裡面
或者是你需要動態讀檔(也就是我現在需要的),就必須讓檔案名稱維持在某個規則

機密資料怎麼辦

當我好不容易想好一個架構後,我的主管卻點出我把太多資訊放進 public 資料夾了
因為 public 是一個完全赤裸的空間
不只檔名不會變,檔案結構也會保留下來
等於是會把過多資訊公開到網路上
於是他請我再修改一下

但我左思右想,都想不到要怎麼把比較機密的資料藏起來
例如 RPC URL、測試站網址等等
就連各大網站如 uniswap, mStable, beefy 我都能從 dev tool 裡面輕鬆找到 他們每條鏈所用的 rpc url
那這樣我到底應該藏到什麼程度才夠呢?

我發現很多文章跟影片都會說要把 api key 放進.env才不會外洩
但這不是對的
基本上只要網頁會用到的東西
都能透過 dev tool 或某種方式看到
真正能藏 api key 的方法,是利用 proxy 的方式
把呼叫 api 的人(程式)從前端移到後端的 proxy server
然後利用 cors 或其他方式去限制能呼叫的 domain
可以參考本文最後面的參考資料

結論

最後我去告訴主管前端沒辦法把這些資料隱藏起來
我才知道是我會錯意了
他只是希望我把 public 盡量簡化
採用中性的名稱,例如不要特別在檔名上寫 stagine/testnet
並且不要在正式站暴露測試站的資訊
這樣就夠了

不過現在我更加清楚在前端隱藏「會用到的資料」其實是沒有意義的
因為終究要用在瀏覽器上,也就終究有辦法被看到
而且經過這次 code review 我也見識到主管在架構設計上的能力
我花了兩天想出來的結構,除了沒考慮到安全性,用起來也很複雜
但主管花了一個小時左右跟我一起研究出的架構就十分有邏輯
感覺我在這塊還需要訓練一下(他就是這麼跟我說的XD)

參考資料

How to store Ethereum JSON-RPC node API Key in a React app

How can one secure Web3 JSON-RPC API endpoint?

How do I hide an API key in Create React App?

前言

之前在面試期間稍微碰了一下 web3.js
當時只有照做的感覺,對區塊鏈的運作還很不熟
但至少對怎麼跟小狐狸互動有點概念

後來有幸進到區塊鏈公司,第一件事主管就請我先研究 ethers.js 和 web3modal
我發現 web3 和 ethers 是兩個很類似的套件
都是在處理錢包、智能合約互動之類的事情

整體來說
web3 背後有比較多人在維護,網路上的教學也比較多
ethers 則輕量很多,支援用 ENS name 來呼叫合約,state(provider) 和 key(wallet) 分開管理
另外 ethers 有一個好用的框架:wagmi,真的是方便很多,以後有機會來介紹
現在就筆記一下 Ethers.js 的常用功能吧

延伸閱讀

  1. Announcing ethers.js — a web3 alternative
  2. Web3.js vs Ethers.js 實際應用上的區別

Ethers.js

Install

npm install ethers

Terms

名詞 說明
Provider 與區塊鏈對接,負責提供「唯讀」資訊給網頁
Signer 權限比 Provider 更高,除了讀取還可以寫入區塊鏈
Contract 智能合約的實例化物件,可以用類似 ORM 的方式跟合約互動

Wallet Connection

這邊示範如何連接瀏覽器的 MetaMask 擴充套件
以及如何利用 web3Modal 來連接錢包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import ethers from 'ethers'
import Web3Modal from 'web3modal'

// Connect with MetaMask
// window.ethereum 是 MetaMask plug-in inject 到網頁的物件
const provider = new ethers.providers.Web3Provider(window.ethereum)

// Connect with web3Modal
const web3Modal = new Web3Modal({
cacheProvider: true,
providerOptions: {} // 這邊可以設定能支援什麼錢包,會在跳出 modal 的時候
})
const instance = await web3Modal.connect()
const provider = ethers.providers.Web3Provider(instance)

// Disconnect
// ethers cannot disconnect wallet programmically
// You can clear cached provider and reset the related states to null to pretend disconnection.
// see https://ethereum.stackexchange.com/q/83914

Get Accounts

可用listAccount()取得帳號陣列
不知道為什麼是陣列,難道說可以同時連接不同帳號嗎?

1
2
3
4
5
6
7
// 方法 1
const accounts = await provider.listAccounts()
console.log(account[0])

// 方法 2
const accounts = await provider.send('eth_requestAccounts',[])
console.log(accounts[0])

Get Balance

return <BigNumber>

1
2
3
4
const balance = await provider.getBalance(currentAccount)

const balanceInEther = ethers.utils.formatEther(balance)
console.log(balanceInEther)
  • 回傳 BigNumber,單位是 Wei,顯示時記得換算成容易看的單位,並轉換成 string。
    ethers.utils.formatEther : BigNumber ⇒ string(in Ether)
    ethers.utils.parseEther : string(in Ether) ⇒ BigNumber
    可參考這裡

  • 如果用 walletconnect 必須加上 blocktag pending
    getBalance(currentAccount, ‘pending’)
    否則可能不會抓到最新的數值

Get Network

return <Network> : {name: string, ensAddress: string, chainId: number}
Ethereum mainnet 會顯示 homestead

1
2
const network = await provider.getNetwork(currentAccount)
console.log(network.name)

用戶隨時能夠切換網路(Mainnet or Testnet),如果網頁沒有做相對應的反應,很容易讓用戶疑惑,甚至下錯單,造成金錢上的損失
Best Practice: 偵測到網路改變時,直接 reload 頁面,確保所有的 state 被重置

Network Changes Guideline
https://github.com/ethers-io/ethers.js/issues/866

Get Signer

1
const signer = provider.getSigner(account: string)

小結

以上是一些常用的 function
還有如何讀取 native token
下一篇會介紹如何與合約互動

突然發現前面不小心忽略了 web3modal
web3modal 是一個整合了各大錢包的接口
未來你想要多支援什麼 coinbase, walletconnect 等奇奇怪怪的錢包
只要 web3modal 有支援,都可以很快的添加進來
目前有 v1, v2 兩個版本

10 月底著手研究時,
v2 還在 early alpha,因此先跳過
但寫這篇文章的當下(12/13),已經進展到 beta.9 了
有稍微試用一下,很多當初要花一個禮拜研究的東西他都幫我做好了…
之後可能會再專案用的升級到v2吧哈哈
好 其他細節留到未來某個篇章再紀錄吧