
最近在研究 js 中 module 的用法,發現在使用 ES Module 的方式 import module 時,有時候會需要 extension name,但我在工作上 import 的時候卻從來沒加過 extension name,好奇什麼時候會需要加 extension name,於是有了這篇文章的誕生。
通常會分為以下三種情境
1. 使用 Webpack 打包
如果專案是使用 webpack 打包的話,可能就會不需要在 import 的時候加上 extension name:
import Button from 'components/Button'
原因如 webpack 的文件說明:
- If the path has a file extension, then the file is bundled straightaway.
- Otherwise, the file extension is resolved using the
[resolve.extensions](<https://webpack.js.org/configuration/resolve/#resolveextensions>)
option, which tells the resolver which extensions are acceptable for resolution e.g..js
,.jsx
.
只要有在 webpack 的 config 中的 resolve.extensions 有設定要 resolve 的檔案類型,那麼在 import 這些檔案類型時,就不需要加上 extension name,webpack 的 config example:
// webpack.config.js
module.exports = {
resolve: {
extensions: ['.js', '.jsx']
}
}
這也是為什麼我在工作上使用 react import 其他的 js 檔時都不需要加上 extension name。
2. 在 Node.js 或是 Browser 中直接使用 ES Module
這裡的直接使用指的是不使用任何工具來打包(ex: webpack, gulp 等等),也就是假設 local 有安裝 Node.js 的情況下,可以直接開一個專案來測試 import module:
2.1 npm init
根據 Node.js 的文件,在沒有特別設定 module type 的情況下,會將檔案視為使用 CommonJS module,為了使用 ES Module,會需要將 package.json 的 type 設定為 module:
{
"name": "js-module",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \\"Error: no test specified\\" && exit 1"
},
"author": "",
"license": "ISC"
}
2.2 export module
新增一個檔案來 export 某個變數
// car.js
export const brand = 'skoda'
2.3 import module
在另一個檔案 import 剛剛宣告的 module
// index.js
import { brand } from './car'
console.log(brand)
2.4 執行 index.js
直接執行這個檔案的話,會發現 Node.js 會噴錯:找不到這個 module
$ node index.js
node:internal/process/esm_loader:94
internalBinding('errors').triggerUncaughtException(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/jimmy/Desktop/js-module/car' imported from /Users/jimmy/Desktop/js-module/index.js
需要在 import module 的 path 加上 extension name Node.js 才能找得到這個 module
// index.js
import { brand } from './car.js'
console.log(brand)
再一次執行 index.js
$ node index.js
skoda
3. 在 Node.js 中使用 CommonJS
要在 Node.js 中使用 CommonJS 的方式來處理 module 的話,可以將 package.json 的 type 改為 commonjs:
{
"name": "js-module",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "commonjs",
"scripts": {
"test": "echo \\"Error: no test specified\\" && exit 1"
},
"author": "",
"license": "ISC"
}
3.1 path name 沒有 extension name
如果 require 的 path 中沒有特別標注 extension name 的話, Node.js 會根據以下順序來找對應的檔案:
- .js
- .json
- .node
也就是說,如果要 require 的 module 是 js 檔的話,可以省略 .js
Node.js 也可以找得到對應的檔案:
// car.js
const brand = 'skoda'
module.exports = brand
// index.js
const brand = require('./car')
console.log(brand)
$ node index.js
skoda
3.2 path name 有 extension name
有 extension name 的話,Node.js 就會直接找對應的檔案,所以當然也可以 work:
// index.js
const brand = require('./car.js')
console.log(brand)
$ node index.js
skoda
4. 結論
- 使用 webpack: 有在 config 的 resolve.extensions 裡設定要 resolve 的檔案種類,在 import 這些檔案種類時,就可以不需要加上 extension name
- 不 compile,直接使用 ES Module:需要加上 extension name
- 在 Node.js 使用 CommonJS:不加 extension name 的話,會按照 .js, .json, .node 的順序來解析檔案
5. 參考資料
When do we need to add file extension when importing JavaScript modules?
Modules: CommonJS modules | Node.js v21.7.3 Documentation
Module Resolution | webpack