Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在ES模块中的使用 __dirname #39

Open
flytam opened this issue Apr 7, 2024 · 0 comments
Open

在ES模块中的使用 __dirname #39

flytam opened this issue Apr 7, 2024 · 0 comments
Labels
node This issue or pull request already exists

Comments

@flytam
Copy link
Owner

flytam commented Apr 7, 2024

前言

ECMAScript 模块是 JavaScript 的新标准格式。在 Node.js 中越来越多的库逐渐从从 CommonJS 转移到 ES 模块

注:这里是指“真”ES 模块并不是指代码中 Node.js 中使用 import 写法但是实际被 tsc 转成 commonJS 的形式

但是 Node.js ES 开发中此前有一个棘手的问题是获取当前文件目录、路径。不过这个问题在最近也已经解决

结论

在 ES 模块中,现在可以使用以下方式而不是使用__dirname__filename

import.meta.dirname  // 当前模块的目录名 (__dirname)
import.meta.filename //当前模块文件名 (__filename)

获取当前目录

通过访问当前模块的目录路径,可以相对于代码所在位置遍历文件系统并在项目中读取或写入文件,或动态导入代码。相关的使用方式随着时间的推移而发生了一些变化,从 CommonJS 的实现到最新的 ES 模块更新

旧的 CommonJS 方式

Node.js 最初使用 CommonJS 模块系统。CommonJS 提供了两个变量,返回当前模块的目录名称和文件名称,分别是__dirname__filename

__dirname  // 当前模块所在的目录
__filename // 当前模块文件名

旧的 ES 模块方式

__dirname__filename在 ES 模块中不可用。需要使用以下代码来实现获取

import * as url from 'url';

const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const __filename = url.fileURLToPath(import.meta.url);

最新的 ES 模块方式

最终在经过许多讨论后,现在有了更好的方法。自从Node.js20.11.0Deno 1.40.0Bun 1.0.23之后可以调用import.meta对象的dirnamefilename属性来获取了

import.meta.dirname // 当前模块所在的目录
import.meta.filename// 当前模块文件名

为什么需要一个新的 API

ES 模块是 JavaScript 的标准。然而 JavaScript 最初是作为在 Web 浏览器中运行的语言而诞生的。Node.js 流行起来后开始在服务器上运行 JavaScript,但必须使用一些约定来加载模块,Node.js 项目早期做出的一个选择是采用 CommonJS 模块系统及其相关内容

ES 模块是为浏览器和服务器环境设计的。浏览器通常没有文件系统访问权限,因此提供对当前目录或文件名的访问是没有意义。然而对于浏览器处理 URL,可以使用file://scheme以 URL 格式提供文件路径。因此,ES 模块具有对模块的 URL 的引用。即import.meta.url。可以看看在 Node.js 中可以使用 URL 的相关使用

假设一个名为module.js的 ES 模块包含以下代码:

console.log(import.meta.url);

如果使用 Node.js 的服务器上运行此文件,则会得到以下结果:

$ node module.js
file:///path/to/module.js

如果 Web 浏览器中加载 module.js,则会得到以下结果:

https://example.com/module.js

基于不同上下文会有不同的结果

import.meta.url是一个描述 URL 的字符串,而不是一个 URL 对象。可以通过将该字符串传递给URL构造函数将其转换为真正的 URL 对象:

const fileUrl = new URL(import.meta.url);
console.log(url.protocol);
// Node.js: "file:"
// Browser: "https:"

使用 URL 对象,可以使用 Node.js 的 URL 模块将模块的 URL 转换为文件路径,等价于 __filename

import * as url from "url";

const fileUrl = new URL(import.meta.url);
const filePath = url.fileURLToPath(fileUrl);
console.log(filePath);

// /path/to/module.js

也可以操作 URL 来获取目录名,等价于__dirname

import * as url from "url";

const directoryUrl = new URL(".", import.meta.url);
const directoryPath = url.fileURLToPath(directoryUrl);
console.log(directoryPath);

// /path/to

使用 URL 而不是字符串

大多数的代码可能都是需要使用路径字符串来在 Node.js 中执行常见的文件操作。但其实许多在字符串路径上工作的 Node.js API 也可以使用URL对象

__dirname 最常见的用途是遍历目录以查找要加载的数据文件。例如,如果 module.js 文件与名为 data.json 的文件位于同一目录中,并且想将数据加载到脚本中,则以前会像这样使用 __dirname

const { join } = require("node:path");
const { readFile } = require("node:fs/promises");

function readData() {
  const filePath = join(__dirname, "data.json");
  return readFile(filePath, { encoding: "utf8" });
}
}

在 ES 模块中可以直接使用import.meta.dirname

import { join } from "node:path";
import { readFile } from "node:fs/promises";

function readData() {
  const filePath = join(import.meta.dirname, "data.json");
  return readFile(filePath, { encoding: "utf8" });
}

但是也可以像如下使用 URL 对象:

import { readFile } from "node:fs/promises";

function readData() {
  const fileUrl = new URL("data.json", import.meta.url);
  return readFile(fileUrl, { encoding: "utf8" });
}

由于 ES 模块为客户端和服务器编写的 JavaScript 带来了一致性,因此使用 URL 对象而不是路径字符串也可以实现相同的效果。更多关于替代__dirname可以参考

如何找到 import.meta.dirname

import.meta.dirnameimport.meta.filename可以在最新版本的 Node.js、Deno 和 Bun 中使用

Bun 已经提前实现了import.meta.dirimport.meta.pat,它们是等效的,所以dirnamefilename在 bun 其实是dirpath的别名

由于这个属性仅涉及基础文件系统,因此仅在import.meta.url的 scheme 为file:时可用。也就是说在浏览器环境中不可用;在浏览器中尝试使用import.meta.dirname将仅返回 undefined

参考

__dirname is back in Node.js with ES modules

@flytam flytam added the node This issue or pull request already exists label Apr 7, 2024
@flytam flytam changed the title __dirname 在ES模块中的使用 __dirname 在ES模块中的使用 Apr 7, 2024
@flytam flytam changed the title __dirname 在ES模块中的使用 在ES模块中的使用 __dirname Apr 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
node This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

1 participant