Node.js添加了内置模块命名空间“node:”

315 阅读3分钟

用法

Node.js给内置模块添加了命名空间:node:。 举例如下,当我们要引用fs模块时,可以这样引用:

  • CommonJS
const fs = require('node:fs');
...
  • ESM
     import * as fs from 'node:fs'
     ...
    

支持该语法的Node.js版本

其实早在2020年9月份,Node.js社区的贡献者就已经提交了该功能的相关代码,最终于2021年7月份在v12版本首次正式面世。

  • v12.20.0、v14.13.1开始,只有ESM形式支持
  • v16.0.0、v14.18.0开始,ESM和CommonJS都支持
  • 从2022年开始,Typescript也开始支持

什么是内置模块

从Node.js使用者角度看,Node.js有三类模块:

  • 内置模块 Node.js自带的模块,例如文件处理模块fs
  • node_modules中的模块
  • 本地模块 由业务开发人员创建的模块 举例如下: 开发人员创建了server.js文件,一般会这样导入该文件:
    • CommonJS
      const fs = require('./server.js')
	  ...
  • ESM:
	  import * as  server from './server.js'
	  ...

server.js就是本地模块

添加该语法的目的

  • 可以通过node:标识明确所引用的模块是Node.js的内置模块
  • Node.js加载模块时,由于内置模块的优先级是最高的,所以当内置模块名和node_modules中的模块名相同时,node_modules中的模块不会被引用。当Node.js需要添加新的内置模块时,很容易和用户正在使用的node_modules中的文件名重名,一旦重名,由于内置模块的优先级最高,因此原本应该调用node_modules下的模块,却因为Node.js也有相同名称的模块,从而错误地调用了内置模块。这种不能向后兼容的改变显然是Node.js社区不允许的。而node:前缀可以避免该问题,Node.js社区开发人员可以相对宽松的给新内置模块起名字而不同担心重名问题了。

该语法的缺陷

这个语法容易给开发人员带来困惑。 为了向后兼容,require('fs')这种形式依然是允许的,因此开发者会认为require('node:fs')require('fs')是通用的,这个没错,但是对于Node.js新加的test模块就不是这样了,如果我们也省去node:,直接使用test,那大概率是要出问题的。具体解释如下:

首先我们要了解,test这个名字非常常见,npm中已经有相同名称的test模块了,因此开发人员的node_modules中很有可能会有test模块,这个test模块和Node.js内置的node:test不是一回事儿。(这里只是用test举个例子,实际中npm的test模块是node:test的一部分) 然后,如果Node.js社区在某个版本新创建了内置模块test而不是node:test,当开发人员升级到该版本时,由于内置模块的优先级高,require或者import时,会优先调用内置模块 test,而开发人员的预期应该是调用npm中的test模块,这样子程序必然会出错。 针对此问题,Node.js的TSC工作组讨论的结果是,新加的test模块只能通过node:test调用,如果通过test调用,则会调用node_modules下的模块,或者也有可能无法找到该模块。 fs模块和test模块的不一致性会迷惑开发人员。

要想避免上面说的test模块的问题,可以在ESLint等lint工具的帮助下,强制大家给所有内置模块加上node:前缀,相信最终Node.js可以不再需要兼容无前缀的内置模块,这样的话上面说的缺陷也就不存在了。