es module

為什麼 js 中用 ES Module 的方式 import module 時,有時候需要 extension name (副檔名),有時候不用?

最近在研究 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 會根據以下順序來找對應的檔案:

  1. .js
  2. .json
  3. .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

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top