ES Modules 中的 __dirname 和 __filename

ES Modules 中的 __dirname 和 __filename.png

模塊化發(fā)展

早期,前端這塊沒(méi)有模塊化系統(tǒng),而 Node.js 需要模塊化所以只能一直使用 CommonJS 標(biāo)準(zhǔn)湊合著,后來(lái) ECMAScript 委員會(huì)通過(guò)了 ES Modules 標(biāo)準(zhǔn)。CommonJS 的處境就比較尷尬了,時(shí)至今日無(wú)論用 JS 來(lái)寫(xiě)前后端 ES Modules 都已經(jīng)成為了標(biāo)配。

ES Modules 遇到了問(wèn)題

CommonJS 中提供的全局變量如require, exports, module.exports, __filename, __dirname 等,在 ES Modules 環(huán)境中均是不可用的,require, exports, module.exports 在 ES Modules 中基本對(duì)應(yīng)著 import, export, export default。

但是在編程中 __filename__dirname 也是高頻 API,當(dāng)它們出現(xiàn)在 ESM 中,運(yùn)行代碼會(huì)拋出錯(cuò)誤 ReferenceError: __dirname is not defined in ES module scope。

難道 ES Modules 里面沒(méi)有對(duì)應(yīng)的 API 嗎,當(dāng)然有不過(guò)完全沒(méi)有 CommonJS 中的 __filename__dirname 用著方便,我們需要自己模擬。

import.meta.url

import.meta 包含當(dāng)前模塊的一些信息,其中 import.meta.url 表示當(dāng)前模塊的 file: 絕對(duì)路徑,拿到這個(gè)絕對(duì)路徑我們就可以配合其他 API 來(lái)實(shí)現(xiàn) __filename__dirname。

我們有代碼:

console.log(import.meta.url);

運(yùn)行會(huì)得到一個(gè)基于 file 協(xié)議的 URL:file:///Users/ape/Desktop/temp/index.js。

fileURLToPath

接下來(lái)需要把 file 協(xié)議轉(zhuǎn)換成路徑,我們需要借助 Node.js 內(nèi)部 url 模塊的 fileURLToPath API。

import { fileURLToPath } from "node:url";
console.log(fileURLToPath(import.meta.url));

運(yùn)行得到路徑:/Users/ape/Desktop/temp/index.js

__filename

通過(guò) import.meta.urlfileURLToPath 我們能很容易模仿 __filename API。

const __filename = fileURLToPath(import.meta.url);

__dirname

我們已經(jīng)拿到了 __filename 的值,實(shí)現(xiàn) __dirname 就簡(jiǎn)單了,借助 Node.js 的內(nèi)部模塊 path 的 dirname 方法來(lái)實(shí)現(xiàn):

import { dirname } from "node:path"
import { fileURLToPath } from "node:url"

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

console.log(__dirname);

運(yùn)行得到模塊絕對(duì)文件夾路徑:/Users/ape/Desktop/temp。

require.resolve

最后給大家來(lái)點(diǎn)干貨。

描述場(chǎng)景

模塊化開(kāi)發(fā)有一個(gè)很頭疼的問(wèn)題,就是要經(jīng)常 import,比如在包含 React、Vue 等庫(kù)的項(xiàng)目中,就要經(jīng)常 import { useState } from "React"import { ref } from "vue",有沒(méi)有一種辦法可以省略這個(gè)步驟,自動(dòng) import 用到 API 直接使用,當(dāng)然有這就是 unplugin-auto-import 包出現(xiàn)的原因,但是 unplugin-auto-import 不夠智能,項(xiàng)目哪些需要自動(dòng)引入的 API,需要我們手動(dòng)傳入,所以 unplugin-auto-import 維護(hù)了一份 presets 。

這問(wèn)題就出現(xiàn)了,npm 的庫(kù)千千萬(wàn) presets 一定包含不全,由使用者手動(dòng)引入就不符合開(kāi)箱即用的思想了。

解決辦法也很簡(jiǎn)單:

  1. 動(dòng)態(tài):我們直接動(dòng)態(tài) import 一個(gè)庫(kù),這不就拿到了庫(kù)所有的導(dǎo)出了。
  2. 靜態(tài):第一步,我們找到庫(kù)的 package.json 文件,第二步拿到 main 或 exports 字段,找到并讀取文件拿到庫(kù)的導(dǎo)出。

第一種思路,直接 await import("vue") 完事了,我們看第二種思路,重點(diǎn)在第一步如何找到一個(gè)庫(kù)的 package.json 文件,只有拿到它的絕對(duì)路徑才能解析讀取進(jìn)行后續(xù)操作。

如果實(shí)在 CJS 中我們好操作,直接上代碼:

const getPackageJsonPath = require.resolve('vue/package.json')

console.log(getPackageJsonPath)

看到這個(gè) require.resolve 心中一涼,完了 ESM 沒(méi)有這個(gè) API 了,啊哈哈,不用擔(dān)心,有替代還不止一個(gè):

createRequire

import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)

const getPackageJsonPath = require.resolve('tsup/package.json')
console.log(getPackageJsonPath)

import.meta.resolve

不建議使用,目前還在試驗(yàn)階段沒(méi)有穩(wěn)定。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容