Netlify

Netlify 為靜態網頁提供了一個自動化建構與部屬的平台,而且對白嫖黨個人開發而言,更重要的是,它是免費的!免費的!免費的!!! Netlify 可以與一個專案倉庫的 Webhook 串接,若發生 Push 事件 (也就是跑了 git push 這個命令之後),則觸發重新自動建構和部屬的流程。

本站就是透過 Netlify 搭配 Github 上的倉庫進行部屬的。而這次牛牛踩了的坑,便是發生在 Netlify 對 Nodejs 專案建構後所保存的 Build Cache 上。


在 Netlify 建構 Nodejs 專案

在描述問題之前,讓我先稍微提及一下在 Netlify 上建構 Nodejs 專案的幾個流程。

安裝依賴

Netlify 會依據所提供的 Base directory 這個設定項 (預設為專案根目錄),在這個目錄下執行 npm install 安裝所有依賴。

值得注意的是,Netlify 預想 Nodejs 專案就該這麼安裝依賴,因此這個命令 (即 npm install) 是無法修改的!

執行建構腳本

Netlify 會以 Build command 中設置的命令建構網站,以本站為例,則會執行 hexo generate

網站上線

建構無誤就會發布網站囉。

關於 Netlify 的 Build Cache

個人網站上線後,通常依賴不會太頻繁的更動,主要的更新都在於網站的內容文本。因此 Netlify 有了 Build Cache 的機制,會保存建構網站時所需的依賴,以便在下次略過安裝依賴的步驟,直接進入建構流程,加速整個網站建構、一直到上線的時間。

Netlify 會根據專案使用的語言不同,暫存不同的檔案和目錄。[1]對於 Nodejs 來說,依賴都會放在 node_modules/ 目錄下,因此 Netlify 會將這個目錄保存下來。

問題描述 🐛

問題就發生在 Netlify 只會保存位於 Base directory 這個目錄下的 node_modules/

在本站的專案架構上,分為了網站模塊主題模塊 (即各自擁有一個 package.json)。主題 (hexo-theme-cowsay) 屬於網站的其中一項依賴,在 package.jsondependencies 中聲明了 "hexo-theme-cowsay": "file:themes/cowsay" 這樣形式的本地依賴。

原先使用本地依賴的初衷,是因為主題也可以自帶 Hexo 腳本[2]或者安裝第三方的 Hexo 插件,因此主題也有了屬於自己的依賴,透過聲明本地依賴,大部分來自主題的依賴將會在 Netlify 安裝依賴的步驟中,被安裝在 Base directory 目錄下的 node_modules/ 中。

會發生少部分依賴未被安裝在 Base directory 目錄下 node_modules/ 中如此狀況,原因是網站模塊主題模塊雙方的共同依賴發生了版本衝突[3]的緣故,NPM 為了確保雙方都能夠正確的引用到合適的依賴版本,因此會將衝突的主題模塊的依賴,放到了主題模塊的目錄下,也就是 themes/cowsay/node_modules 下。

這些位置尷尬的依賴將不會被 Build cache 機制保存,當下一次 git push 部屬網站之後,Netlify 會以為依賴都已經安裝完成了而略過安裝依賴的步驟,直接進入執行建構腳本的步驟,就會噴出如下一般的引用錯誤了。

1
Module not found: Error: Can't resolve 'xxx'

問題分析

這個問題的其中一個關鍵點在於,網站模塊主題模塊雙方的共同依賴發生了版本衝突

要解決這個問題,其中一個想法是解決衝突,使網站模塊主題模塊使用相同版本的依賴,然而困難點在於衝突的依賴未必是網站模塊主題模塊的直接依賴,若是間接依賴,雖可透過 package-lock.json 去調控版本,但問題只會更加複雜,解了一個🐛又帶來了另一個🐛。

不如換個角度思考,既然無法解決依賴版本衝突,那就設法解決被安裝在主題模塊目錄下的 node_modules 無法進入 Build cache 這個問題,是不是只要我設法讓那些孤立在主題模塊目錄下的依賴進入根目錄的 node_modules 就可以了?

可行的方向,目前想到的是避免使用本地依賴,因為 npm 處理本地依賴的做法為,在 node_modules 下製作一個 symlink 指向本地依賴的路徑,因此 Build cache 內部得到的是一個 symlink 而非真實的依賴。

解決方案

若要避免使用本地依賴,npm install 還有其他可行方案,例如發布至 NPM Registry 或者使用 GitHub 或其他 Git 倉庫進行安裝,若使用 Git 倉庫安裝則要注意 SSH 權限問題。

迴避掉本地依賴之後,事情還沒結束,因為 Hexo 要求主題一定要可透過路徑 themes/<主題名稱>/ 訪問,因此我們需要在 hexo generate 之前先行生成一個 symlink 指向 node_modules 下的主題目錄,以本站為例,themes/cowsay 會是一個 symlink 指向 node_modules/hexo-theme-cowsay

然而牛牛很懶惰,本站含主題使用上的依賴還不算太多,因此將建構腳本階段設置了 npm install && hexo generate 命令,直接強迫重新安裝一次依賴。

相信我,我要是有空,一定將本站修正為好好利用 Build cache 的方式 (咕 🕊️


說起來,一整個問題都是 Netlify 沒有提供方式來決定誰該進 Build cache,才會有如此一般的騷操作存在。


題外話,以後 wtf 標籤將用來註記所有我踩過坑後寫的文章,若有興趣的同學歡迎利用這個標籤查找文章!


  1. Netlify Build Cache: https://www.netlify.com/docs/build-gotchas/#build-cache ↩︎

  2. Hexo 腳本: https://hexo.io/docs/themes#scripts ↩︎

  3. 所謂版本衝突就是無法找到一個雙方都接受的版本,例如一方聲明依賴版本為^1.0.0 (只接受 1.x.x 版)、而另一方則聲明了^2.0.0 (只接受 2.x.x 版),版本比較的規則,可以參考 semver: Advanced Range Syntax↩︎