# @ctrl/dom 中文文档

规范地址：https://npmcn.com/package/%40ctrl/dom/

版本：0\.2\.0
npm 元数据更新时间：2026-11-10T00:45:35.820Z
中文译文更新时间：2026-11-11T04:18:09.305Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40ctrl/dom
原文对照：https://npmcn.com/package/%40ctrl/dom/?lang=original
项目主页：https://github.com/scttcper/dom#readme
源码仓库：https://github.com/scttcper/dom

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：2.7 MB，392 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=26
- 运行依赖：3 个
- peer 依赖：jest \>=30\.0\.0，react ^19\.0\.0，vitest \>=5\.0\.0，react-dom ^19\.0\.0，@types/react ^19\.0\.0，@jest/globals \>=30\.0\.0，pretty-format ^27\.0\.2，@types/react-dom ^19\.0\.0
- 首次发布：2026-10-26T01:47:24.926Z
- 最新版本发布：2026-11-10T00:45:35.413Z
- 近 30 天下载量：3209（2026-10-11 至 2026-11-09，npm 官方下载统计）
- 已发布版本数：18（近 12 个月 18 个）

## 中文简介

用于测试 React 组件的快取 DOM : jsdom 和 happy-dom 的倒置替换, 包含测试库, 用户事件和快取dom 的构建.

## 安装

```sh
npm install @ctrl/dom
```

也可以使用：`pnpm add @ctrl/dom`、`yarn add @ctrl/dom`、`bun add @ctrl/dom`

## README

# @ ctrl/ dom

用于测试 React 组件的 DOM. 它取代了Jsdom或快乐的地盘, 并进行自己的测试 图书馆,用户活动 和笑柄建在它。 一个100个文件的回放套房 以3.6s的速度运行 与14.4 s在jsdom和9.3 s在快乐度, 相比,记忆力更弱.

-   **滴入来.** 与 jsdom 同为全局, 测试库执行 导出、行为、错误消息和 `prettyDOM` 输出为 `@testing-library/dom`, (中文(简体) ), `/react`, (中文(简体) ), `/user-event` 财务报告和审定财务报表 `/jest-dom`只交换DOM,或者整个堆栈,没有触摸测试.
-   **快点.** 查询类 `getByRole` 取微秒而不是毫秒, 用户事件 不等待计时器 在键盘中,和闲置的时间在 `findBy*`, (中文(简体) ), `waitFor` 应用 在虚拟时钟上跳出跳出而不是等待出场([关掉](https://npmcn.com/package/%40ctrl/dom/#readme-time-skipping)) (中文(简体) ).
-   **为组件测试所建.** 没有布局、脚本执行、导航、网络; react 组件触碰的所有其他东西都像浏览器一样,或者像测试时的jsdom 套房取决于它.

|  | 支助 |
| --- | --- |
| 节点 | 26岁以上 |
| 测试运行器 | Vest 5+, Jest 30+, 节点: test, Bun |
| 反应 | 19(续)`@ctrl/dom/react` 用途 `React.act`(中文(简体) ) |

节点: test and Bun: [其他试跑者](https://npmcn.com/package/%40ctrl/dom/#readme-other-test-runners)。 。 。 .

## 维特

整个堆栈:环境加上您现有的 `@testing-library/*` 进口到 `@ctrl/dom`。 。 。 .

```sh
pnpm add -D @ctrl/dom
```

```ts
// vitest.config.ts
import { defineConfig } from "vitest/config";
import { ctrlDom } from "@ctrl/dom/vitest";

export default defineConfig({
  plugins: [ctrlDom()],
  // Optional and faster: each test file gets a fresh context in a reused worker instead of a
  // new worker, so per-file setup drops from about 23 ms to about 2 ms.
  test: { pool: "vmThreads" },
});
```

DOM( 库存测试库软件包继续工作 ) :

```ts
export default defineConfig({ plugins: [ctrlDom({ testingLibrary: false })] });
```

选项转到插件 : `ctrlDom({ url: "http://localhost:3000/", width: 1280 })`与 安装了插件, 文件也可以选择环境本身 : `// @vitest-environment ctrl-dom`。 。 。 . 一个 `environment` 您在配置中设置了其它 `"ctrl-dom"` 保留项目的默认值.

插件还为Vitest 的员工添加了节点旗, 因为默认池开始每次测试一次 文件:随着它的测试库,它会随着每个工人的开始打开编译缓存,所以Vitest的 不仅包括DOM系统.

## 欢乐

```sh
pnpm add -D @ctrl/dom @ctrl/dom-jest-environment
```

```js
// jest.config.js
module.exports = {
  preset: "@ctrl/dom", // the environment + @testing-library/* routed to @ctrl/dom
  setupFilesAfterEnv: ["@ctrl/dom/jest-dom"], // instead of "@testing-library/jest-dom"
};
```

仅限DOM: `testEnvironment: "@ctrl/dom-jest-environment"` (股票测试库继续工作). 选项进入 `testEnvironmentOptions`。每个文件 : `/** @jest-environment @ctrl/dom-jest-environment */`。 。 。 .

## 其他试跑者

`@ctrl/dom/register` 为没有 DOM 环境的跑者安装一个窗口作为全局, 如 快乐之地 `@happy-dom/global-registrator`,随着Vitest环境的默认值: `jsdom-30` 简介, `http://localhost:3000/`,空闲时间在虚拟时钟上跳过,以及例外 被未通过运行测试的事件听众所抛出 。 测试库的自动 `cleanup()` 之后 下方的两只跑道,每只试验 `@testing-library/react` 包含.

### 节点:测试

```sh
node --import @ctrl/dom/register --test
```

或者说 `import "@ctrl/dom/register";` 作为测试文件的第一次导入。 节点:测试的钩子不是 环球,所以它暴露为 `beforeAll`, (中文(简体) ), `afterAll`, (中文(简体) ), `beforeEach` 财务报告和审定财务报表 `afterEach` (只有名称) jest 和 Bun 的方法: 测试库正在寻找。 之后 最后测试它关闭了窗口并放下了等待的定时器,所以被遗忘了 `setInterval` 没有 保持文件进程运行 。 文件自节点起每个窗口都获得新窗口: 测试运行每个文件 其自己的进程 `--test-isolation=none` 他们分享一个.

笑柄匹配者需要一个 `expect`. 杰斯特的独立 [`expect`](https://www.npmjs.com/package/expect) 把它们当作是 `node:assert`,对 DOM 属性进行断言:

```ts
// signup.test.ts
import assert from "node:assert/strict";
import { test } from "node:test";
import { expect } from "expect";
import * as matchers from "@ctrl/dom/jest-dom/matchers";
import { createElement } from "react";
import { render, screen } from "@ctrl/dom/react";
import userEvent from "@ctrl/dom/user-event";
import { Signup } from "./signup.ts";

expect.extend(matchers);

test("signs up", async () => {
  const user = userEvent.setup();
  render(createElement(Signup));
  assert.equal((screen.getByRole("button", { name: "Sign up" }) as HTMLButtonElement).disabled, true);
  await user.type(screen.getByLabelText("Name"), "Ada");
  expect(screen.getByRole("textbox", { name: "Name" })).toHaveValue("Ada");
});
```

节点条 类型脚本但不编译 JSX: 写入 `.ts` 文件为 `createElement`,或 添加加载器, 如 [tx](https://www.npmjs.com/package/tsx) 页:1 `.tsx` 文件 (单位:千美元)`node --import tsx --import @ctrl/dom/register --test`,与 `"jsx": "react-jsx"` 在你身边 (原始内容存档于2018-09-26). tsconfig.json.

### 阿宝

```toml
# bunfig.toml
[test]
preload = ["@ctrl/dom/register"]
```

```tsx
// signup.test.tsx
import { expect, test } from "bun:test";
import * as matchers from "@ctrl/dom/jest-dom/matchers";
import { render, screen } from "@ctrl/dom/react";
import userEvent from "@ctrl/dom/user-event";
import { Signup } from "./signup.tsx";

expect.extend(matchers);

test("signs up", async () => {
  const user = userEvent.setup();
  render(<Signup />);
  expect(screen.getByRole("button", { name: "Sign up" })).toBeDisabled();
  await user.type(screen.getByLabelText("Name"), "Ada");
  expect(screen.getByRole("textbox", { name: "Name" })).toHaveValue("Ada");
});
```

`bun test` 在一个全局中运行每个文件, 因此文件共享窗口( 如 Vitest 和 `isolate: false`(三); `bun test --isolate` (或 `--parallel`)给每个文件一个新的全局和 窗口。 Bun自己的测试钩已经是全球性的了.

### 配置

预装不需争辩,所以读作 `CTRL_DOM_URL`, (中文(简体) ), `CTRL_DOM_COMPAT` (单位:千美元)`jsdom-26`, (中文(简体) ), `jsdom-30`, (中文(简体) ), `modern`, (中文(简体) ), `CTRL_DOM_TIME_SKIP` (单位:千美元)`0`编号 :`1`和 `CTRL_DOM_TEST_HOOKS=0` (让跑者独活). 其他一切都会通过API,它 `@ctrl/dom/register/pure` 未登记的出口 导入时 :

```ts
import { register, unregister } from "@ctrl/dom/register/pure";

const { window } = register({ url: "http://localhost:5173/", compat: "modern", width: 1280 });
// ...
unregister(); // closes the window and restores every global it replaced
```

`register(options)` 拿着 [环境选项](https://npmcn.com/package/%40ctrl/dom/#readme-options) (除外) `compileCache` 财务报告和审定财务报表 `nodeFetch`+) 加上 (中文(简体) ) `testHooks` (默认) `false` 在这里, `true` 页:1 `@ctrl/dom/register`页:1 任何以前的登记.

### 故事书

[便携式故事](https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest) 运行 在ctrl-dom Vitest环境中,在节点下:测试或装有 `@ctrl/dom/register`数字 : `composeStories` 故事渲染为 `@ctrl/dom/react`是因为 `render`,以及 `await Story.run()` 挂载 故事并运行它 `play` 函数( 故事本 `storybook/test` 带来自己的测试库 和用户活动,运行在同一DOM上).

```tsx
import { composeStories } from "@storybook/react";
import { render, screen } from "@ctrl/dom/react";
import * as stories from "./Signup.stories.tsx";

const { Empty, Filled } = composeStories(stories);

test("renders", () => {
  render(<Empty />);
  expect(screen.getByRole("button", { name: "Sign up" })).toBeDisabled();
});

test("plays", async () => {
  await Filled.run();
  expect(screen.getByRole("status")).toHaveTextContent("Hi, Ada!");
});
```

`run()` 如jsdom:测试库 `cleanup()` 不删除, 因此同一文件中的稍后测试会看到两者( 重复) `id`s 断开标签 抬头看. 测试钩子是全局的( Jest, Bun, 节点: 有寄存器的测试), 导入 测试库的 React 条目将 React 的行为环境打开, React 然后警告 `play` 函数外部的状态更新 `act(...)`; 测试仍然通过.

## 时间跳过

计时器运行在虚拟时钟上. 当测试无所事事时,只能等待(无待决) 承诺, `setImmediate` 回调或I/O,时钟跳到下个计时器,以及 `Date`, (中文(简体) ), `performance.now()` 事件时间戳随它而移动。 A 类 `findBy*` 等待800多米的弹出 以微秒计返回;计时器仍然按顺序起火,以及假计时器(`vi.useFakeTimers()`, (中文(简体) ), `jest.useFakeTimers()`)接任如常.

测试需要实时通过时关闭:来自 `node:timers`,或从图书馆 抓住那个 `setTimeout` 在环境开始前, 播放页面的计时器; 或测试 对照过程外的时钟测量时间。 真正的I/O(口袋、文件、儿童流程) 已经持有时钟, 因此一个真正的服务器的空闲度 永远不会被跳过 .

| 范围 | 怎么样 |
| --- | --- |
| Vest, 每个文件 | `ctrlDom({ timeSkip: false })` |
| Vest, 一个文件 | `// @vitest-environment-options {"timeSkip": false}` 作为第一行 |
| 欢乐,每个文件 | `testEnvironmentOptions: { timeSkip: false }` |
| Jest, 一个文件 | `/** @jest-environment-options {"timeSkip": false} */` 在顶端 |
| 节点:测试, Bun | `register({ timeSkip: false })` 从 `@ctrl/dom/register/pure` |
| 任何跑者,一个跑者 | `CTRL_DOM_TIME_SKIP=0`,它优先于选项 |

## 直接进口

| 改为 | 导入 |
| --- | --- |
| `@testing-library/react` | `@ctrl/dom/react` (另见: `/react/pure`, (中文(简体) ), `/react/dont-cleanup-after-each`(中文(简体) ) |
| `@testing-library/dom` | `@ctrl/dom/testing-library` |
| `@testing-library/user-event` | `@ctrl/dom/user-event` |
| `@testing-library/jest-dom` | `@ctrl/dom/jest-dom` (另见: `/jest-dom/vitest`, (中文(简体) ), `/jest-dom/matchers`, (中文(简体) ), `/jest-dom/jest-globals`(中文(简体) ) |
| `aria-query` + 键 `dom-accessibility-api` | `@ctrl/dom/aria` |
| `jsdom` 编号 : `happy-dom` | `@ctrl/dom` (单位:千美元)`createWindow()`, (中文(简体) ), `installGlobals(window)`(中文(简体) ) |
| `@happy-dom/global-registrator` | `@ctrl/dom/register` (单位:千美元)`register()`, (中文(简体) ), `unregister()` 从 `/register/pure`(中文(简体) ) |

在任何一种环境下, `@ctrl/dom` 每一个子路径共享环境的 DOM 实例, 和每个游泳池(包括 `vmThreads`编号 :`vmForks`),这样 `instanceof` 对照 `@ctrl/dom`班级都坚持住 窗户也一样 `@ctrl/dom/register` 安装.

## 选项

环境选项`environmentOptions["ctrl-dom"]` 在韦斯特, `testEnvironmentOptions` 内 贾斯特:

| 选项 | 默认 |  |
| --- | --- | --- |
| `url` | `http://localhost:3000/` (视频), `http://localhost/` (贾斯特) | 文档 URL |
| `width`, (中文(简体) ), `height` | 1024×768 (中文(简体) ) | `innerWidth` 编号 : `innerHeight`驱动器 `matchMedia` |
| `userAgent`, (中文(简体) ), `platform`, (中文(简体) ), `languages`, (中文(简体) ), `devicePixelRatio`, (中文(简体) ), `referrer` | 类似 jsdom 的 | 导航/窗口值 |
| `timeSkip` | `true` | 跳过虚拟时钟上的闲置时间; `false` 保留实时计时器. `CTRL_DOM_TIME_SKIP=0/1` 覆盖 |
| `compat` | `"jsdom-30"` (视频), `"jsdom-26"` (贾斯特) | 超过jsdom的 API 被曝光 : |
| `layout` | 关 | `"lite"`: 几何学的大致框模型(`getBoundingClientRect`, (中文(简体) ), `offset*`, (中文(简体) ), `client*`, (中文(简体) ), `scroll*`, 调整观察员大小),因此虚拟化器(TanStack Virtual,反应-窗口)和定位库在没有手写模拟的情况下工作. 关闭,几何是零,就像jsdom。 细节 : [文档/推出](https://github.com/scttcper/dom/blob/main/docs/layout.md) |
| `compileCache` (声音) | `true` 带有插件的测试库, 否则 `false` | 允许节点在导入 DOM( 插件: 每个工人开始时) 之前为每个工人编译缓存 。 它保留在工人负荷的所有东西上,这只支付大文件的费用(库存测试库的许多小文件的负荷较慢). `CTRL_DOM_COMPILE_CACHE=0/1` 覆盖 |
| `nodeFetch` (贾斯特) | `false` | 保持节点 `fetch`编号 :`Request`编号 :`Response` (在笑话-环境-jsdom下缺席) |

仅Vitest 插件的选项( E)`ctrlDom({ ... })`:

| 选项 | 默认 |  |
| --- | --- | --- |
| `testingLibrary` | `true` | 路线储存 `@testing-library/*` 导入到 @ctrl/ dom 的执行; `false` 只交换 DOM |
| `jsxRuntime` | `true` | 页:1 `react/jsx-runtime` 财务报告和审定财务报表 `react/jsx-dev-runtime` 给ES模块的复制件。 病毒手测试和应用代码 一个代理代号 相对于一个普通JS依赖, 所以每个JSX元素 否则会通过代理 `get` 陷阱;没有它,钩子重的渲染速度会快8%. 当项目解决运行时间到任何结果时, 独自一人 |
| `sharedWorkers` | `false` | 在同一领域逐一运行工人的测试文件,而不是启动每个文件的工人:见下文 |
| `debug` | 关 | `"aria"` 或者说 `"compact"`数字 : [用编码代理调试](https://npmcn.com/package/%40ctrl/dom/#readme-debugging-with-coding-agents) |

### 共有工人

```ts
export default defineConfig({ plugins: [ctrlDom({ sharedWorkers: true })] });
```

大部分很多短测试文件的成本 不是测试: 每个文件开始一个工人, 它的靴子节点, 装入维特,反应和反应度, 运行所有它冷. `sharedWorkers` 套装 维特的 `isolate: false`,所以一个工人 不停地从文档到文档, 并放回大多数 什么是 新生提供了每个文件。 基础UI的9,199测试 采用35 s隔离 和7 -8 这样 同样的结果.

每个测试文件前 :

-   重新评价第一当事方模块(`vi.resetModules()`),因此模块状态和 `vi.mock()` 重新开始;
-   窗口被重置: 仍然挂载的树被卸载, 等待计时器和动画框 抛出、空出、聚焦、存储、饼干和放回URL;
-   测试库的清理登记为文件(库存测试库登记为 只有工人的第一个文件, 这就是为什么模式需要 `testingLibrary`) (中文(简体) ).

文件仍然能从早先的文件中看到什么,为什么模式是选入的 :

-   模块依赖状态。 有毒负载 `node_modules` 通过节点,每个工人一次: React,一个CSS-in-JS缓存,一个依赖性中模块范围创建的数据牵引客户端;
-   `<head>`,事件听众打开 `window` 财务报告和审定财务报表 `document`,和补丁原型,这些 模块可依赖;
-   记忆。 维特保持每一个测试,它运行 达到工人的生命(柴记 每个 `expect.extend` vitest每次测试一次),并以此为文件模块:a 工人的堆积由文件的模块图每个文件的大小所增长,2.3 MB在Base UI上. 再来 工人意味着每个文件更少.

自愿流动资金库`pool: "vmThreads"`已经重复使用每个文件有新上下文的工人,并且 不受影响.

## 兼容配置文件

用于 jsdom 特征检测的应用程序: 找到的库 `PointerEvent`, (中文(简体) ), `matchMedia` 或者说 `ResizeObserver` 取取其浏览器路径, 以及仅在 API 时安装自身的多填充 离开我们的地方 而不是它的模拟. `compat` 选择测试全球曝光的内容:

| `compat` | 匹配 | Jsdom以外的展览 30 |
| --- | --- | --- |
| `"jsdom-26"` | jest- environment- jsdom 30 (jsdom 26.1); 游戏默认值 | 没有 `PointerEvent` 或者说 `onpointer*` 处理者 |
| `"jsdom-30"` | Vitest 的 jsdom 环境; Vitest 默认 | 无库特性检测 |
| `"modern"` | 所有@ctrl/ dom 执行 | `matchMedia`, (中文(简体) ), `ResizeObserver`,则 `inert` IDL属性, `visualViewport` |

[文件/索引](https://github.com/scttcper/dom/blob/main/docs/deviations.md) 列出每个 API 中 每个剖面图,有原因.

## 用编码代理调试

`screen.debug()` 和失败的查询将整个 DOM 打印为 HTML, 费用为千位代理 的符号和SVG路径。 运行测试 `CTRL_DOM_DEBUG=aria` (或 `ctrlDom({ debug: "aria" })`在 Playwright 的 aria 快照中打印无障碍树 格式,另加最接近的匹配 `*ByRole`, (中文(简体) ), `*ByText` 或者说 `*ByLabelText` 查询 失败 :

```
Unable to find an accessible element with the role "button" and name "Submit"

Closest matches:
- text: "Submit"  # in <div>, which has no role: give it role="button" or use getByText
- button "Pay now"  # same role, other name

Accessibility snapshot of <body> (CTRL_DOM_DEBUG=aria):
- main:
  - heading "Checkout" [level=1]
  - form "Payment":
    - textbox "Email address"
    - button "Pay now"
    - text: Submit
```

该错误为468个字符;上游印出2,476个同为小字形,约437,000个 一个大页. `ariaSnapshot(el?)` 编号 : `screen.ariaSnapshot()`, (中文(简体) ), `prettyDOM(el, max, { format: "aria" })` 财务报告和审定财务报表 `diffAriaSnapshots(before, after)` 直接提供. `CTRL_DOM_DEBUG=compact` 指纹 HTML没有类/类属性和SVG内部. 没有变量,输出是相同的 到测试图书馆.

在同一模式下, **每次测试失败** (失败) `toBe`,一个抛出的错误,而不仅仅是查询)结束 并附有一页的快照,当它失败时, 在清理之前, 加上自 测试的前一个快照。 杰斯特的环境是靠自己完成的 与Vitest, `ctrlDom()` 添加 设置文件,或添加 `"@ctrl/dom/vitest-environment/failure-snapshot"` 改为 `setupFiles` 与 裸露的环境.

`npx ctrl-dom debug <test file> [-t name]` 在一个命令中,它能发现维特和杰斯特, 以代理模式运行文件,并打印一个紧凑的报表(其快照和 最接近的匹配,一个堆放框,控制台输出,随测试状态退出.

为了说明快照的打印方式 滑稽的动作 `expect(el).toMatchAriaSnapshot(template)`,与 [剧作家](https://playwright.dev/docs/aria-snapshots) 部分匹配 :

```ts
expect(screen.getByRole("form")).toMatchAriaSnapshot(`
  - textbox "Email"
  - button /Pay \\d+ €/ [disabled]
`);
```

给你的一条线 `CLAUDE.md` 编号 : `AGENTS.md`数字 :

> 当组件测试失败时, 请重新运行 `npx ctrl-dom debug <file> [-t <name>]` 阅读 无障碍快照和最接近的匹配,而不是添加 `screen.debug()`。 。 。 .

细节 : [文件/代理调试](https://github.com/scttcper/dom/blob/main/docs/agent-debugging.md)。 。 。 .

## 哨兵

[`@ctrl/sentry-tests`](https://www.npmjs.com/package/@ctrl/sentry-tests) 在哨兵中进行测试: 测试失败 与失败的行, 测试所做的面包屑和页面 附加的片状试验 `FlakyTest` 以及每个测试文件 作为其描述块的痕迹, 测试、钩子和测试功能。 在@ctrl/dom上,痕迹还包含每个测试库查询, `waitFor`、用户活动动作和渲染(`findByRole("option", …) (40 checks)`),失败则得到 页面的无障碍快照.

```ts
plugins: [ctrlDom(), sentryTests()]                // Vitest, sentryTests from @ctrl/sentry-tests/vitest
```

Vitest 和 Jest 的设置 : [其读物](https://github.com/scttcper/dom/blob/main/packages/sentry-tests/README.md)。 。 。 .

## 与jsdom的区别

没有脚本执行 没有导航 没有网络 像jsdom和Happy -dom一样 没有 除非打开布局 `layout: "lite"` (其他几何为零,如jsdom). 每个 故意差别在以下列出: [文件/索引](https://github.com/scttcper/dom/blob/main/docs/deviations.md)。 。 。 .

## 许可证

麻省理工学院. 包括从测试库移植的代码,dom-access-api,aria-query, Playwright, jsdom及其他人;他们的版权通知和许可证在包裹中 `NOTICE` 文档.

## 常见问题

### @ctrl/dom 是什么？

用于测试 React 组件的快取 DOM : jsdom 和 happy-dom 的倒置替换, 包含测试库, 用户事件和快取dom 的构建\.

### 如何安装 @ctrl/dom？

在项目目录运行 npm install @ctrl/dom，或使用 pnpm add / yarn add / bun add。它还提供命令行工具 ctrl-dom，可以用 npx ctrl-dom 直接运行。

### @ctrl/dom 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @ctrl/dom 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @ctrl/dom 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=26。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @ctrl/dom 可以商用吗？

@ctrl/dom 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @ctrl/dom 的下载量有多少？

根据 npm 官方下载统计，@ctrl/dom 在 2026/10/11 – 2026/11/09 这 30 天内被下载 3,209 次，日均约 107 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @ctrl/dom 还在维护吗？

最新版本 0\.2\.0 发布于 2026/11/10（1 天前）；至今共发布 18 个版本，其中过去 12 个月发布了 18 个；首次发布于 2026/10/26。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

