
模塊化發(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.url 和 fileURLToPath 我們能很容易模仿 __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)單:
- 動(dòng)態(tài):我們直接動(dòng)態(tài) import 一個(gè)庫(kù),這不就拿到了庫(kù)所有的導(dǎo)出了。
- 靜態(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)定。