Skip to content

Latest commit

 

History

History
172 lines (137 loc) · 5.05 KB

使用 process.mainModule 或 require.main 确定 Node.js 入口脚本.md

File metadata and controls

172 lines (137 loc) · 5.05 KB

使用 process.mainModule 或 require.main 确定 Node.js 入口文件

ES2020import.meta 旨在解决访问模块元信息的问题,如文件当前元素是什么。

<!-- index.html -->
<script src="foo.js"></script>
// foo.js
const currentScript = document.currentScript
currentScript.src // 当前文件地址

// ESM 的获取方式 => 需要在 `script` 标签加上 `type="module"`
import.meta.url

这是我们在浏览器中执行此操作的方式,但在 Node.js 中如何执行此操作?

在 Node.js 中,每个模块和所需文件都包装在所谓的 The module wrapper(模块包装器)中。

;(function (exports, require, module, __filename, __dirname) {
  // 模块代码实际上就写在这里
})

这就是 require 函数、__filename__dirname 等便捷对象的来源。

在 Node.js 中没有 currentScript,而是一个入口文件,它可能引用了数千个其他模块。你现在如何确定文件是否是入口文件

事实证明,有两种方法可以做到这一点:require.mainprocess.mainModule

让我们看看这两个定义是什么。

console.log(require.main)
console.log(process.mainModule)

/*
Module {
  id: '.',
  path: '',
  exports: {},
  parent: null,
  filename: '/www/wtf/foo.js',
  loaded: false,
  children: [
    Module {
      ...
    }
  ],
  paths:  [
    '/www/wtf/node_modules',
    '/www/node_modules',
    '/node_modules'
  ]
}
Module {
  id: '.',
  path: '',
  exports: {},
  parent: null,
  filename: '/www/wtf/foo.js',
  loaded: false,
  children: [
    Module {
      ...
    }
  ],
  paths:  [
    '/www/wtf/node_modules',
    '/www/node_modules',
    '/node_modules'
  ]
*/

可以看到,我们可以通过访问 require.main.filenameprocess.mainModule.filename 来获取入口模块的文件路径,这两个对象还包含更多有用的信息。

为了确定某个模块是否是入口文件,可以对照 module 对象进行检查。

const isEntryScript = require.main === module
const isAlsoEntryScript = process.mainModule === module

但是 require.mainprocess.mainModule 实际上是一样的吗?

console.log(require.main === process.mainModule) // true

运行后,你会发现它返回 true那么两者有什么区别呢?

不同之处在于,如果主模块在运行时发生更改,require.main 可能仍然引用更改发生之前所需模块中的原始主模块。但通常,我们可以安全地假设这两个模块引用同一个模块。

**这是什么意思?**我决定稍微挖掘一下 Node.js 的核心代码。

process.mainModulenode/lib/modules.js 中定义:

Module._load = function (request, parent, isMain) {
  // ...

  if (isMain) {
    process.mainModule = module
    module.id = '.'
  }

  Module._cache[filename] = module

  tryModuleLoad(module, filename)

  return module.exports
}

require.mainnode/lib/internals/modules.js 中定义:

function makeRequireFunction(mod) {
  // ...
  require.main = process.mainModule
  // ...
  return require
}

我没有进一步挖掘内部结构,但我们每天使用的 require 函数实际引用了 process.mainModule。这就是为什么它们实际上是一样的。如果我们改变 process.mainModulerequire.main,现在会发生什么?

// test.js
const bar = require('./foo')
console.log(process.mainModule)
console.log(require.main)

// foo.js
// 更改两个值
process.mainModule = 'A'
require.main = 'B'

/*
A
Module {
  id: '.',
  path: '',
  exports: {},
  parent: null,
  filename: '/www/wtf/foo.js',
  loaded: false,
  children:
   [
    Module {
      ..
    }
  ],
  paths:  [
    '/www/wtf/node_modules',
    '/www/node_modules',
    '/node_modules'
  ]
}
*/

可以看到,如果我们在运行时将 process.mainModule 设置为其他值,require.main 仍然保留对初始主模块的引用。

注意process.mainModule 自 14.x 被废弃,但你仍然可以在 Node.js 内使用它。

扩展import.meta.url 需要在 ESM 环境中使用,这在 CJS 无法使用。如果你现在正在编写项目,需要在 CJS 和 ESM 中支持 __dirnameimport.meta.url,可以看看 antfuIsomorphic __dirname 文章中给出的方法。但通常在构建需要同时支持 CJS 和 ESM 的包中,我们会使用一些工具同时生成它们,antfu 在 Publish ESM and CJS in a single package 中介绍两种便捷的方式。另外,需要获取包根目录,可以看看 Get Package Root