Try   HackMD

Node.JS import(require) path

最近剛好同事的 PR 遇到一個 bug, 和 Node.JS import(require) 有關, 大致上的狀況是這樣:

有一個 lib 名稱為 A.js, 但是在使用的時候不小心變成小寫:

const a = require('lib/a');

神奇的是, 在開發階段, 這個錯誤並沒有被偵測到
換句話說, 在本機開發的情況下, 不論是在 VS code 或是 透過 command line 下指令, 程式都會正常運行
也就是, 檔案名稱的大小寫不會影響到 import

但是在線上的環境執行, 就直接噴錯誤了:

Error: Cannot find module 'lib/a'
...

看來在線上環境, 檔案的大小寫會有影響?


實驗

測試環境:

>cat /etc/os-release
PRETTY_NAME="Ubuntu 22.10"
NAME="Ubuntu"
VERSION_ID="22.10"
VERSION="22.10 (Kinetic Kudu)"
VERSION_CODENAME=kinetic
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=kinetic
LOGO=ubuntu-logo

>tree
.
|-- index.js
|-- lib
|   |-- A.js
|   `-- a.js
`-- package.json
// A.js
module.exports = (a, b) => {
    return a + b;
}

// a.js
module.exports = (a, b) => {
    return a - b;
}

// index.js
const A = require('./lib/A');
const a = require('./lib/a');

const v1 = 3;
const v2 = 2;

const ans1 = A(v1, v2);
const ans2 = a(v1, v2);

console.log(ans1, ans2);

結果:

>node ./index.js
5 1

看起來在實驗的環境下, 大小寫的檔案名稱是有區別的

接下來將 A.js 改名為 B.js

結果是:

>node ./index.js
node:internal/modules/cjs/loader:1029
  throw err;
  ^

Error: Cannot find module './lib/A'
Require stack:
- /home/edlo/projects/node/demo-package/index.js
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1026:15)
    at Function.Module._load (node:internal/modules/cjs/loader:871:27)
    at Module.require (node:internal/modules/cjs/loader:1098:19)
    at require (node:internal/modules/cjs/helpers:108:18)
    at Object.<anonymous> (/home/edlo/projects/node/demo-package/index.js:1:11)
    at Module._compile (node:internal/modules/cjs/loader:1196:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1250:10)
    at Module.load (node:internal/modules/cjs/loader:1074:32)
    at Function.Module._load (node:internal/modules/cjs/loader:909:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/home/edlo/projects/node/demo-package/index.js' ]
}

但是同樣的實驗場景, 換到本機後卻完全不同
測試環境:

neofetch
                    'c.          edlo@MacBook-Pro.local
                 ,xNMM.          ----------------------
               .OMMMMo           OS: macOS 13.2.1 22D68 arm64
               OMMM0,            Host: MacBookPro18,1
     .;loddo:' loolloddol;.      Kernel: 22.3.0
   cKMMMMMMMMMMNWMMMMMMMMMM0:    Uptime: 1 day, 7 hours, 19 mins
 .KMMMMMMMMMMMMMMMMMMMMMMMWd.    Packages: 131 (brew)
 XMMMMMMMMMMMMMMMMMMMMMMMX.      Shell: zsh 5.8.1
;MMMMMMMMMMMMMMMMMMMMMMMM:       Resolution: 2560x1440, 1496x967, 1920x1080
:MMMMMMMMMMMMMMMMMMMMMMMM:       DE: Aqua
.MMMMMMMMMMMMMMMMMMMMMMMMX.      WM: Quartz Compositor
 kMMMMMMMMMMMMMMMMMMMMMMMMWd.    WM Theme: Blue (Dark)
 .XMMMMMMMMMMMMMMMMMMMMMMMMMMk   Terminal: WarpTerminal
  .XMMMMMMMMMMMMMMMMMMMMMMMMK.   CPU: Apple M1 Pro
    kMMMMMMMMMMMMMMMMMMMMMMd     GPU: Apple M1 Pro
     ;KMMMMMMMWXXWMMMMMMMk.      Memory: 6422MiB / 32768MiB
       .cooc,.    .,coo:.

同樣的專案架構, 但是明顯的檔案名稱不分大小寫:

> touch A.js
> touch a.js
> ls
A.js

先建立 A.js 之後, 再建立的 a.js 被系統認為是同樣的檔案

即便用 vim 來編輯, 系統也是將 A.jsa.js 當作一樣的檔案

接著在程式中實際測試:

const A = require('./lib/A.js');

const val1 = 3;
const val2 = 2;

const ans1 = A(val1, val2);
console.log(ans1);

結果:

>node index.js
5

如果將 import path 改為小寫:

const A = require('./lib/a.js');
...

結果:

>node index.js
5

看來在 MacOS 的環境, 檔案名稱是不分大小寫的

懷疑是和 OS 的 file system 有關, 這部份還需要再研究

先作個紀錄


補充

檔名在不同OS中的比較

在 macOS 中完美配置文件名大小写敏感

看起來, 有大小寫區分反而比較少, 大都是 unix-like system

tags: NodeJS Computer Science