Node.js · 原创实验
同一个包、同一个版本,为什么读到了两份状态?
自建一个 0.0.0 包,分别从 require 和 import 进入。计数器确实分成了两份;让两个入口指向同一内核后,再对照对象身份与计数。
记录时间: (北京时间) · Node v22.22.3 · darwin 27.0.1 / arm64
文章语言: English
一处调用已经把计数器加到 1,另一处读取时还是 0。实验目录里只有一个包,版本号也只有一个:0.0.0。差异出在这两处调用最终加载的文件。
这里自建了两个小包。第一个为 require 和 import 各写一份状态;第二个保留两个入口,但把状态交给同一份 CommonJS 内核。每个例子启动一个新 Node 进程,两轮结果一致。
两轮实际结果
| 情况 | 第 1 轮 | 第 2 轮 |
|---|---|---|
| 重复 require 同一个入口 | 同一对象:true;require 侧 1;另一侧 1 | 同一对象:true;require 侧 1;另一侧 1 |
| require 与 import 各走一个入口 | 同一对象:false;require 侧 1;另一侧 0 | 同一对象:false;require 侧 1;另一侧 0 |
| 两个入口共用 core.cjs | 同一对象:true;require 侧 2;另一侧 2 | 同一对象:true;require 侧 2;另一侧 2 |
一个安装目录里可以有两份实现
lab-dual 的导出表让 require 进入 index.cjs,让 import 进入 index.mjs。这两个文件各自创建了一个 store。对其中一份调用 increment,只会修改那份对象。
混合加载的实际结果是 sameStore=false,require 侧 count=1,import 侧 count=0。这里没有第二个安装版本。只看包名和版本,无法替代对最终入口及其状态来源的检查。
{
"name": "lab-dual",
"version": "0.0.0",
"exports": {
"import": "./index.mjs",
"require": "./index.cjs"
}
}先排除重复 require 本身
第一组在同一个进程里 require 两次 lab-dual,再从第一处增加计数。两个调用拿到的 store 是同一个对象,读数都是 1。
把这组与混合加载放在一起,才能把问题缩小到不同入口。否则很容易把任何重复初始化都归因于“Node 缓存失效”,忽略包自己创建了两份状态。
const a = require('lab-dual');
const b = await import('lab-dual');
a.increment();
console.log(a.store === b.store, a.store.count, b.store.count);
// 本次混合加载输出:false 1 0给两个入口接上同一个内核
lab-shared 的 require 条件直接指向 core.cjs。ESM 入口 wrapper.mjs 先导入这个内核,再导出它的 store 和 increment。两边使用同一个对象,各增加一次后,两处读数都为 2。
这个包装器只验证共享对象内部 count 的变化。若内核以后把整个 store 属性重新赋值,包装器里保存的引用会怎样,需要另写例子。不要把这次对象共享的结果推广成所有导出形式都等价。
// wrapper.mjs
import core from './core.cjs';
export const store = core.store;
export const increment = core.increment;排查时把入口一起带上
收集最小复现时,除了精确版本,还应写出调用端使用 require 还是 import,以及 exports 为这两个条件选中了哪些文件。再沿着文件找状态到底在哪里创建。
NPMBot 的兼容性检查能帮助追踪声明入口;对象是否真的共享,要运行像这次这样的用例。六次独立子进程只覆盖这些自建文件,没有经过打包器,也没有加载任何真实 npm 包。
下载脚本,在自己的机器上复跑
使用 Node.js 22 或更高版本,不需要安装依赖。脚本只使用 Node 内置模块;每次执行都会跑两轮并检查断言。记录路径必须尚不存在,避免覆盖上次结果。
node --version
node run.mjs
node run.mjs --record my-results.json实验在同一 Node 进程内比较对象身份;两轮各使用新目录,三组各使用新进程。它不验证跨进程共享、打包产物去重或真实库的单例设计。完整自建源码和摘要随记录提供。
这是同一操作者在本机完成的两轮记录,尚无独立读者复跑反馈。换环境后请保留自己的输出;断言失败会终止脚本。
核对脚本 SHA-256
0e3096e8f2163b919c68e1974f5149d9842edeadaa8cbb0f3b9d5ba0fb7326a1
摘要对应页面提供的脚本文件内容。完整运行数据在 verification.json。