模块化发展
早期,前端这块没有模块化系统,而 Node.js 需要模块化所以只能一直使用 CommonJS 标准凑合着,后来 ECMAScript 委员会通过了 ES Modules 标准。CommonJS 的处境就比较尴尬了,时至今日无论用 JS 来写前后端 ES Modules 都已经成为了标配。
ES Modules 遇到了问题
CommonJS 中提供的全局变量如require
, exports
, module.exports
, __filename
, __dirname
等,在 ES Modules 环境中均是不可用的,require
, exports
, module.exports
在 ES Modules 中基本对应着 import
, export
, export default
。
但是在编程中 __filename
和 __dirname
也是高频 API,当它们出现在 ESM 中,运行代码会抛出错误 ReferenceError: __dirname is not defined in ES module scope
。
难道 ES Modules 里面没有对应的 API 吗,当然有不过完全没有 CommonJS 中的 __filename
和 __dirname
用着方便,我们需要自己模拟。
import.meta.url
import.meta
包含当前模块的一些信息,其中 import.meta.url
表示当前模块的 file:
绝对路径,拿到这个绝对路径我们就可以配合其他 API 来实现 __filename
和 __dirname
。
我们有代码:
console.log(import.meta.url);
运行会得到一个基于 file 协议的 URL:file:///Users/ape/Desktop/temp/index.js
。
fileURLToPath
接下来需要把 file 协议转换成路径,我们需要借助 Node.js 内部 url 模块的 fileURLToPath API。
import { fileURLToPath } from "node:url";
console.log(fileURLToPath(import.meta.url));
运行得到路径:/Users/ape/Desktop/temp/index.js
。
__filename
通过 import.meta.url
和 fileURLToPath
我们能很容易模仿 __filename
API。
const __filename = fileURLToPath(import.meta.url);
__dirname
我们已经拿到了 __filename
的值,实现 __dirname
就简单了,借助 Node.js 的内部模块 path 的 dirname 方法来实现:
import { dirname } from "node:path"
import { fileURLToPath } from "node:url"
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__dirname);
运行得到模块绝对文件夹路径:/Users/ape/Desktop/temp
。
require.resolve
最后给大家来点干货。
描述场景
模块化开发有一个很头疼的问题,就是要经常 import,比如在包含 React、Vue 等库的项目中,就要经常 import { useState } from "React"
或 import { ref } from "vue"
,有没有一种办法可以省略这个步骤,自动 import 用到 API 直接使用,当然有这就是 unplugin-auto-import 包出现的原因,但是 unplugin-auto-import 不够智能,项目哪些需要自动引入的 API,需要我们手动传入,所以 unplugin-auto-import 维护了一份 presets 。
这问题就出现了,npm 的库千千万 presets 一定包含不全,由使用者手动引入就不符合开箱即用的思想了。
解决办法也很简单:
- 动态:我们直接动态 import 一个库,这不就拿到了库所有的导出了。
- 静态:第一步,我们找到库的 package.json 文件,第二步拿到 main 或 exports 字段,找到并读取文件拿到库的导出。
第一种思路,直接 await import("vue")
完事了,我们看第二种思路,重点在第一步如何找到一个库的 package.json
文件,只有拿到它的绝对路径才能解析读取进行后续操作。
如果实在 CJS 中我们好操作,直接上代码:
const getPackageJsonPath = require.resolve('vue/package.json')
console.log(getPackageJsonPath)
看到这个 require.resolve 心中一凉,完了 ESM 没有这个 API 了,啊哈哈,不用担心,有替代还不止一个:
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
不建议使用,目前还在试验阶段没有稳定。