從原理了解 React 佈署 GitHub Pages

Lastor
Code 隨筆放置場
9 min readApr 19, 2021

這篇以 Vue user 的角度,快速紀錄一下 React 佈署至 GitHub Pages 時,與 Vue 的差異,以及一些相關要點。

Vue 佈署 GitHub Pages

首先 Vue user 們第一次學習佈署到 GitHub Pages 時,應該都碰過一個坑,就是佈署上去之後頁面一片空白。這部分去無論是去查 stackoverflow 或是找官網,都可以看到說,需要修改 webpack 的設定。

Vue 官方文件

module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/my-project/'
: '/'
}

需要設定這個的原因,應該是與 Server 的路徑設定有關。HTTP Server 如果有設定虛擬路由,就會出現檔案實際存放的路徑,並不等於路由位置的情況。

// 路由位置
http://www.myhost.com/project_a/index.html
// 實際檔案位置, 可能是直接放在 root 底下, 並無多包一層資料夾
root/index.html

最後就會因為前端框架請求的路徑,與 Server 中檔案實際的路徑對不上,導致拉不到對應的檔案。

因此,Vue 在這邊就是修改 webpack 產出的專案根路徑 publicPath,使其符合 GitHub Server 的設定,才能讓 client 端順利拿到檔案。

// 路由位置
https://user_name.github.io/repo_name/
// 讓 webpack 打包時, 產出匹配 GitHub 路由設定的路徑
module.exports = {
publicPath: '/repo_name/'
}

而上方 Vue 官方文件提供的寫法,則是為了兼容 localhost 也能啟動專案,用一個條件判斷,讓開發模式時使用正常路徑,產品模式時多包一層路徑。

process.env.NODE_ENV === 'production' ? '/my-project/' : '/'

透過這一行也能看出,Vue 的底層是透過環境變數 NODE_ENV 來做判斷。所以也可以在 cmd 去手動的,臨時覆蓋這個設定。

// for Windows
$ set NODE_ENV=product
$ npm run build

React 佈署 GitHub Pages

不同的前端框架,在這部分可以推測,應該是大同小異的。所以 React 要佈署到 GitHub 鐵定也需要設定 publicPath。

但是由官方 create-react-app 建立起來的專案,與 vue-cli 有一個很大的區別,Vue 那邊是將 webpack 設定開放給開發者的,而 React 這邊則是封裝起來的,在默認情況下,無法直接修改。

於是 React 官方在這部分,開放給用戶使用的 API 就不是直接修改 webpack 了,而是去修改 package.json 裡面的定義。

React 官方文件

// package.json
{
"homepage": "/repo_name/"
}

最後 build 出來的檔案,路徑前面就都會變成上述設定。例如初始專案中,埋在 <head> 裡的 apple-touch-icon 之連結路徑會變成這樣。

<link rel="apple-touch-icon" href="/repo_name/logo192.png">

這邊跟 Vue 有一個差異點,React 在 build 與 npm start 時,路徑設定是區分開來的。就算 homepage 給了一組設定,於本機 npm start 時,並不會採用該設定,所以與 Vue 不同,並不需要特地弄一個兼容性的條件判斷式。

但如果硬要照 Vue 的思路去走走看,也是可以的。

由於 homepage 是寫在 package.json 上,導致我們無法寫條件判斷。那就得更進一步的去挖出這個 homepage 設定是怎麼 run 的。

如果去打開 html 的進入點來看,可以看到 React 的源碼是這樣寫的。

<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />

這下看到了一個有趣的東西 %PUBLIC_URL% ,這玩意以 Vue 的經驗來看,一定程度上可以推測,要不是 webpack 設定的屬性名,要不就是環境變數的名稱。

在官方文件中直接搜尋這個變數名,就能查到這是一個 Node 環境變數。

In JavaScript code, you can use process.env.PUBLIC_URL for similar purposes:

React 官方文件,Using the Public Folder

既然是環境變數,就能透過這個它,來做到類似 Vue 模式判斷,我們就可以透過 cmd 來下手,build 之前先去強制覆蓋環境變數。

// for windows
$ set PUBLIC_URL=/repo_name
$ npm run build

這樣 build 出來的相關路徑連結,就能順利匹配上 GitHub 的路由設定了。也可以把這兩行直接寫成 npm script。

// package.json
// 這邊要特別注意一下 windows 中, 縮成單行要加上「&&」, 其類似 ; 的作用
{
"scripts": {
"build": "set PUBLIC_URL=/repo_name&& react-scripts build",
}
}

自動化佈署設定

無論是 Vue 還是 React,都能區分出 source files 與 bundle files。簡單來說就是打包前的源檔,以及打包後的….「包」?

// React 專案結構,是將 output 放在 build 資料夾中project/
/build
/public
/src
設定檔

這邊就會產生一個直接的需求,build 的資料夾才是最終的輸出檔,我們會希望網站的 root 底下,就直接是最終打包後的內容。

web_root/
(...build) // 會希望 build 的內容是直接解在這

但要做到這件事,可能就得開兩個 GitHub repo,一個是放 source 檔,一個是放 bundle 檔。可是這管理上實在太麻煩了,能夠整合在同一個 repo 才是最理想的。

而這當然也是做得到的,現在常見的做法是利用 branch 來做到這件事情。

// 開兩個 branch 分開放
project
main branch => 只放 source
gh-pages branch => 只放 bundle

但是,兩邊的檔案架構、資料夾層級關係是不一樣的,所以需要比較麻煩的方式來上傳 bundle 檔。

原理上是,以 source 檔作為 repo 的核心,也就是 main branch,然後設定 git 忽略掉 build 資料夾。如此第一個目的,main 只存放 source 檔,就達成了。

接下來是讓 build 裡面的內容,以 build 資料夾自身作為 root,弄成一個新 branch 上傳到同一個 repo,才能使其符合這樣的結構。

web_root/
(...build) 的內容

要做到這件事情,最簡單的方法是,直接把 build 資料夾,初始化成一個新的 git 專案,push 時,直接指定到原專案的特定 branch 上。具體作法如下。

// 初始位置在 ~project_root (main)
$ cd build
$ git init
$ git add -A
$ git commit -m 'deploy'
$ git push -f https://github.com/user/repo master:gh-pages

最後這行是 force 強制覆蓋上傳,後面直接指定到原專案的 repo 路徑,然後定義要將當前的 master branch 上傳到目標的 gh-pages branch。

如果對於 master 這個用詞敏感,當然也可以自己加一行去改 branch 名稱。那最後也要記得改成 main:gh-pages

$ git branch -M main

如此,就能成功做出我們期望的,只存放 bundle 檔的 gh-pages branch 了。接著在 GitHub 的 Setting 部分,把 GitHub Pages 設定打開後,就能順利用瀏覽器連上這個 React 網站了。

但是但是!!

每一次佈署都要這樣打一堆指令,是否很浪費人生呢?! 所以這邊可以自己製作一個 .sh 檔,把這些指令批次處理,之後就能一鍵佈署了。

// 在專案 root 新增一個 deploy.sh# 出錯時退出
set -e
# build 專案
export PUBLIC_URL=.
npm run build
# 進入 build 資料夾
cd build
# git 初始化
git init
git add -A
git commit -m 'deploy'
# 上傳
git push -f https://github.com/user/repo master:gh-pages
cd -

這邊要注意一下,前面提過關於兼容產品模式與開發模式的 PUBLIC_URL 環境變數設定,這邊將其直接寫進來。而 .sh 檔案運行的指令是 Linux 的指令集,而不是 windows 的。所以環境變數的 set 寫法略有不同。

並且,這邊將 path 直接設定為 . 這個寫法同義於相對路徑的寫法 ./ ,如此,未來如果 Server 的路由突然從 host/abc 改成 host/123 ,我們前端這邊由於是設定相對路徑,就不需要特地去改路徑重新 build 了。

但是,這種相對路徑的寫法,會根據前端 router 是 hash mode 還是 history mode,以及 Server 端的設定而有不同的結果,具體能否順利匹配到,實際試試看比較快。如果不成功的話,還是乖乖寫死吧。

之後,每當我們要佈署到 GitHub 時,只要執行這個檔案就可以了。

$ deploy.sh

當然,這部分也可以用 npm 上的 gh-pages lib 來處理,只是環境變數的設定要自己再去改一下。

--

--

Lastor
Code 隨筆放置場

Web Frontend / 3D Modeling / Game and Animation. 設計本科生,前遊戲業 3D Artist,專擅日本動畫與遊戲相關領域。現在轉職為前端工程師,以專業遊戲美術的角度涉足 Web 前端開發。