# @entur/uniformen 中文文档

规范地址：https://npmcn.com/package/%40entur/uniformen/

版本：0\.21\.0
npm 元数据更新时间：2026-11-12T07:09:32.590Z
中文译文更新时间：2026-11-14T00:42:25.093Z
软件包许可：EUPL-1\.2

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

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

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

- 包体积（解压后）：72 kB，13 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：1 个
- peer 依赖：react \>=18
- 首次发布：2026-06-10T12:18:38.122Z
- 最新版本发布：2026-11-12T07:09:32.021Z
- 近 30 天下载量：2699（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：23（近 12 个月 23 个）

## 中文简介

Entur apps的页眉和页脚通过Unixen布局服务.

## 安装

```sh
npm install @entur/uniformen
```

也可以使用：`pnpm add @entur/uniformen`、`yarn add @entur/uniformen`、`bun add @entur/uniformen`

## README

# @entur/uniformen (法语)

统一版式服务的客户端库. 它从 Unforden 获取共享头,页脚和头部资产,它提供 React 组件,使其在您的应用程序中实现.

服务使布局, 更改页眉和页脚到达您的应用程序 没有新发布此软件包。 这些变动列于 [服务更改日志](https://github.com/entur/uniformen/blob/main/CHANGELOG.md)。 。 。 .

## 它会做什么

Unfunden是Entur门户网站应用程序的共享头和脚印. 此软件包从 Uniforden 服务中获取 SSR 版式, 并给予您输入的数据和 React 组件以添加到您的应用程序中 .

获取的布局包含:

-   **`headerHtml`** – 为顶端导航提供了 HTML
-   **`footerHtml`** — 为页脚提供 HTML
-   **`headAssets`** ——生 HTML (中文(简体) )`<link>`编号 :`<style>` 标记)将添加到 `<head>`
-   **`scripts`** - 未加工 `<script>` 要添加的 HTML `</body>`
-   **`csp`** ——每个指令的CSP来源(例如: `{ "style-src": [...], "script-src": [...] }`)合并到您页面的内容-安全政策标题中

## 安装

```sh
npm install @entur/uniformen
# or
bun add @entur/uniformen
```

该套件需要回放18或后作同级依赖.

## 使用量

### 获取布局

```ts
import { fetchUniformenLayout } from "@entur/uniformen";

const layout = await fetchUniformenLayout({
  token, // the signed-in user's Auth0 access token
  environment: "dev", // the Uniformen of the environment this app runs in
  params: {
    app: "partner",
    sidebar: true,
    locale: "nb-NO",
    availableLocales: ["nb-NO", "en-GB"],
    loginUrl: "/auth/login", // where the login link points when there is no token
    logoutUrl: "/auth/logout", // where the user menu's logout row points
  },
});

export default function app() {
  return html`
    <html lang="nb-NO">
      <head>
        ${layout.headAssets}
      </head>
      <body>
        ${layout.headerHtml}
        <main>{/* your app */}</main>
        ${layout.footerHtml}
        ${layout.scripts}
      </body>
    </html>
`;
```

将选项传递为一个对象 :

```ts
const layout = await fetchUniformenLayout({
  token, // Auth0 access token; adds the user to the nav
  environment: "dev", // "local" | "dev" | "staging" | "production" (default)
  params: { app: "partner", sidebar: true, locale: "nb-NO" }, // query params for the SSR endpoint
  timeoutMs: 1000, // how long to wait for the service (default)
});
```

`timeoutMs` (单位:千美元)`number`默认值 `1000`)是等待时间的几毫秒 服务,包括重试。 超时时,电话处理它像每一个 其他失败。 见 [缓存和故障](https://npmcn.com/package/%40entur/uniformen/#readme-caching-and-failures)您的页面可以 而不是等待他们.

`params.app` 是要求布局的门户应用程序。 上行显示Entur标志旁边的应用名称. 应用程序切换器将此应用程序标记为当前应用程序,除非该应用程序未列出. 未知值使服务得到答案 `400`,然后调用返回 `null`。 。 。 .

带有参数 `undefined` 值不在查询字符串中。 矩阵参数为 以重复键发送( e)`?key=a&key=b`) (中文(简体) ).

应用程序切换器只显示给登录用户. 它链接到另一个门户 您从环境中获取了应用程序的布局。 例如,a `dev` 与 dev 实例的布局链接,绝不是生产.

标识旁边的环境徽章只在 Entur 中向用户显示 组织。 你不需要发送任何信息 因为服务内容 它来自你传递的标志。 其他用户看到上行没有徽章,但他们 仍然可以看到沿着顶部的彩色条纹显示页面不是生产. 何时 `app` 设置, 您的应用程序部署在环境的布局中 从,徽章是一个按钮。 它打开了您应用程序的链接列表 它被部署到的每一个环境。 否则,徽章是一个简单的标签.

`params.sidebar` (单位:千美元)`boolean`默认值 `false`)在最远处显示一个倒塌按钮 左侧的上边栏。 只有在您的应用程序有侧向导航时才设置它 。 校正让 按钮,而您的应用程序会让侧边栏。 见 [边栏](https://npmcn.com/package/%40entur/uniformen/#readme-sidebar) 详细情况.

`params.simple` (单位:千美元)`boolean`默认值 `false`隐藏应用程序切换器,通知, 边栏按钮和"Mine tigganger"。 顶端栏显示标志,应用程序 名称和登录链接 `params.loginUrl`,或者使用菜单的用户名 只有一个 `params.logoutUrl` 排队 它能覆盖 `params.sidebar`脚印 不变.

`params.hideNotifications` (单位:千美元)`boolean`默认值 `false`)使顶端栏没有 通知按钮,且服务不查询用户的通知. 在显示自己的通知的应用程序中使用, 因此页面没有两个 列表。 与 `params.simple` 设置时,按钮已被隐藏.

`params.loginUrl` (单位:千美元)`string`)是上行站点中的"洛格客栈"链接路径 改为无时 `token` 通过。 如果您将其删除, 则不显示登录链接 。 这一定是 一条你本源的路 绝对URL, `//host` 或者说 `javascript:` 制作 服务答案 `400`,然后调用返回 `null`。 。 。 .

`params.logoutUrl` (单位:千美元)`string`)是用户菜单点中"Logg ut"行的路径 当一个 `token` 通过。 如果您不显示, 则不显示登录行 。 这一定是 一条你本源的路 绝对URL, `//host` 或者说 `javascript:` 制作 服务答案 `400`,然后调用返回 `null`。 。 。 .

`params.locale` (BCP 47: (中文(简体) ) `"nb-NO" | "nn-NO" | "en-GB"`默认值 `"nb-NO"`这是 页眉和页脚的语言。 应用程序名称和环境标签不是 译出. 标签必须准确匹配。 譬如说, `"nb"` 提供服务答案 `400`设置 `<html lang>` 以同样价值计算.

`params.availableLocales` (单位:千美元)`Locale[]`) 显示一个完全带有这些语言切换器 标记,按此顺序。 如果将其删除或通过空列表,则不显示切换器 . 见 [语言](https://npmcn.com/package/%40entur/uniformen/#readme-language) 用于显示和接受哪些价值观.

同样的选项适用于: `fetchUniformenComponents` 见下文.

### 反应适配器

```tsx
import { fetchUniformenComponents } from "@entur/uniformen/react";

const { HeadAssets, Header, Footer, Scripts, csp } = await fetchUniformenComponents({
  token,
  environment: "dev",
  params: { app: "partner", sidebar: true, locale: "nb-NO" },
});

export default function App() {
  return (
    <>
      <head>
        <HeadAssets />
      </head>
      <body>
        <Header />
        <main>{/* your app */}</main>
        <Footer />
        <Scripts />
      </body>
    </>
  );
}
```

如果布局抓取失败, 所有组件都会不产生任何 `csp` 实值 `{}`。 。 。 .

适配器将布局 HTML 解析为真实的 React 元素.

`csp` 一样 `Record<string, string[]>` 那个 `fetchUniformenLayout` 返回,则 您只需要一个获取即可设置 `Content-Security-Policy` 头曰. 合并这些 输入您自己的指令.

-   地点 `<HeadAssets />` 内部 `<head>`。 。 。 .
-   地点 `<Header />` 内部 `<body>` 在此之前 `<main>`。 。 。 .
-   必要时,地点 `<Footer />` 内部 `<body>` 之后 `<main>`。 。 。 .
-   地点 `<Scripts />` 内部 `<body>` 作为最后一个元素.

### 与 Next.js 一起使用

与下一位.js,通过 `Script` 作为 `loader` 向 `<HeadAssets>` 财务报告和审定财务报表 `<Scripts>`。 。 。 . 否则Next.js可以改变脚本运行的顺序,因此脚本和组件的加载顺序错误.

```tsx
import Script from "next/script";

// The head script sets the sidebar state before the first paint, so it must be in
// the initial HTML. The default Next.js strategy adds scripts after hydration, and
// the user would see the sidebar jump.
const BeforeInteractiveScript: typeof Script = (props) => <Script {...props} strategy="beforeInteractive" />;

<head>
  <HeadAssets loader={BeforeInteractiveScript} />
</head>
<body>
  <Header />
  <main>{/* your app */}</main>
  <Scripts loader={Script} />
</body>;
```

## 缓存和故障

`fetchUniformenLayout` 保留服务器程序中的内存布局。 每个 组合 `environment` 财务报告和审定财务报表 `params` 拥有自己的条目.

-   匿名布局( 无) `token`)从内存中使用,只要服务 `Cache-Control: max-age` 允许。 今天是60秒钟。 之后,下一个电话 又取来.
-   签入的布局包含用户名,因此在每个呼叫中都会取出. 这个 软件包将每个符号的最后一个保存一个小时,并只在 获取失败 。 它储存着一种SHA -256散列的代币,而不是代币本身.
-   如果请求失败时出现网络错误或 `5xx` 状态, 软件包试一次 更在同一范围内 `timeoutMs`A级 `4xx` 状态没有重审.
-   如果抓取失败, 调用会返回它为相同选项获得的最后一个布局, 对于一个签名的用户来说 也一样 保存此的匿名布局 一天时间。 如果没有这样的布局, 调用返回 `null`。 。 。 .
-   使用相同的选项打电话, 对于一个签名的用户, 同样的代号, 运行 同时,向Unixen提出一个请求。 一个加入运行的电话 请求还使用该请求的 `timeoutMs`。 。 。 .
-   内存最多持有100个匿名和100个签入版式. 当其中之一 它们是完整的,最近使用的布局被删除。 过期布局 每次存储新的布局都会被移除,所以签入的布局很快就消失了 时间过后,即使用户不回来.
-   你得到的每个布局都是你自己的复制件,所以你可以将自己的来源添加到 `layout.csp` 不更改缓存布局.

要立即获取新布局, 例如在 unifen 部署后, 呼叫 `clearUniformenLayoutCache()`。 。 。 .

每个请求都发出一个 `X-Uniformen-Client` 带有软件包名称和 例如,版本 `@entur/uniformen@0.15.0`。服务使用它来查看 版本正在使用中.

## 边栏

过 `params.sidebar: true` 显示顶端最左边的倒塌按钮 栏。 unifen不使侧边栏本身.

状态只存储于根元素上的此属性 :

```html
<html data-uniformen-sidebar="expanded | collapsed"></html>
```

侧边栏代码在 `headAssets`这样 `<HeadAssets />` 必须是在 `<head>`。 在第一次涂料之前设置存储状态, 因此崩溃的侧边栏不会跳出 . 它也开始在您自己的脚本运行前观看属性,所以它可以看到每个 写到属性,甚至早期.

**基于属性的侧边栏样式 .** 那么你不需要JavaScript状态, 而侧边栏在第一个相框上是正确的. 这在服务器上也能操作 应用程序,如果服务器不知道用户的偏好:

```css
:root[data-uniformen-sidebar="collapsed"] .my-sidebar {
  width: 0;
  /* Also hide it from screen readers and the tab order. With only width: 0, the
     links can still get focus and screen readers still read them. */
  visibility: hidden;
}
```

如果崩溃是动画, 请在 `visibility` 一、导 言 宽度( n)`transition: width 150ms ease, visibility 150ms ease`) (中文(简体) ). 那内容就留下 在动画完成前可见 .

如果你的侧边栏 在一个阴影根 `:root[…]` 选择器无法到达 . 在根上设置自定义属性 。 自定义属性继承到 阴影根,所以里面的样式可以使用值:

```css
:root {
  --my-sidebar-width: 240px;
}
:root[data-uniformen-sidebar="collapsed"] {
  --my-sidebar-width: 0px;
}
```

**要使用不同的默认**时 `<html data-uniformen-sidebar="collapsed">` 打开 服务器。 校正只写用户存储的属性 偏好,或者当属性没有设置时,所以您的值被保留.

Unifen 将您提供的值作为起始状态,而不是改变。 没错 未保存到 `localStorage`,没有 `uniformen:sidebar` 事件为它起火。 当 用户第一次崩溃或扩展侧边栏,该选择 _实值_ 已经保存, 从那时起,它就被使用,而不是你所作的默认。 如果你的应用商店 服务器上的首选项,并想要使用它自己的值,清除 `uniformen:sidebar` 当它保存自己的副本时键.

**把它从其他地方砸倒**,例如边栏中的关闭按钮,a 键盘快捷键或路由更改,写出相同的属性。 上行按钮 执行相同的:

```ts
document.documentElement.dataset.uniformenSidebar = "collapsed";
```

**对脚本变化的反应**听到这个事件。 当您使用上面的 CSS 时 能够。 从按钮或你的应用软件中, 事件会引发任何变化, 并拥有 新状态 :

```ts
window.addEventListener("uniformen:sidebar", (event) => {
  setCollapsed(event.detail.collapsed);
});
```

**如果一个组件需要JavaScript中的状态**,例如边栏组件 带有 `collapsed` 道克,读取属性并观看. A 类 `MutationObserver` 看见了 每写到属性。 与事件不同的是,它也在一个网页上工作 校正头脚本缺失, 例如布局抓取失败 。 礛 您的侧边栏和顶边栏一样工作。 在反应中 :

```tsx
import { useCallback, useSyncExternalStore } from "react";

const ATTRIBUTE = "data-uniformen-sidebar";
const root = () => document.documentElement;

function subscribe(onChange: () => void) {
  const observer = new MutationObserver(onChange);
  observer.observe(root(), { attributeFilter: [ATTRIBUTE] });
  return () => observer.disconnect();
}

export function useSidebarCollapsed() {
  const collapsed = useSyncExternalStore(
    subscribe,
    () => root().getAttribute(ATTRIBUTE) === "collapsed",
    // The server does not know the user's preference.
    () => false,
  );
  const setCollapsed = useCallback((next: boolean) => {
    root().setAttribute(ATTRIBUTE, next ? "collapsed" : "expanded");
  }, []);
  return [collapsed, setCollapsed] as const;
}
```

在服务器发送的应用程序中,服务器会使扩展的侧边栏. 如果用户 偏好崩溃,水合后侧边栏会改变,用户会看到 跳下来。 CSS方法没有这个问题, 所以把它用于所有 CSS可以做到这一点。 在一个仅在浏览器中制作的单页应用程序中,状态是 从第一个角度来说是正确的.

`event.detail` 输入。 这个事件和 `uniformen:locale` 于 `WindowEventMap`,因此,在一个从软件包中导入的项目中,每个文件都得到 输入时没有额外输入或显示.

校正从属性中更新其余: 按钮,其 `aria-expanded`,以及它保存的偏好 `localStorage` 下级 `uniformen:sidebar`。 。 。 .

## 语言

`params.locale` 是服务所提供语言. `params.availableLocales` 添加用户可以更改语言的控制 :

```ts
const layout = await fetchUniformenLayout({
  token,
  params: { app: "partner", locale, availableLocales: ["nb-NO", "en-GB"] },
});
```

这些语言是一组广播节目。 服务器使 `locale` 作为 选中一个, 因此没有客户端状态, 标签从开始就是正确的 . 每个语言都以自己的语言来命名("Norsk bokmål","英语"). 控制权 总是被贴上"Språk / Language"的标签,不管是什么 `locale` 是,所以用户可以找到它 即使他们不能读取当前语言.

**显示控件的地方取决于上行.** 它只显示在一个 地点 :

-   当一个用户被签入时,它是用户菜单中的"Språk / Language"一栏, 在登录行上方。 它呆在那里,当 `params.simple` 已经设定.
-   在无菜单的匿名上行是左侧的单独控制 输入链接。 它显示一个地球和当前语言。 在小屏幕上 文字是隐藏的,地球表有一个 `aria-label` 与语言名称.

只列出您自己的应用程序的标签 。 制服支持三个,但 提供您页面所没有的语言比不提供更糟糕。 你选择 命令。 命令。 如果列表有重复的标签, 未知的标签或不包含 `locale`,服务答案 `400` 调用返回 `null`。 。 。 .

**您的应用程序必须处理选择.** 当用户选择语言时, 顶端栏 发送一个事件,而无所作为。 它没有设置饼干,写给 `localStorage`,重新装入或更改栏中的标签:

```ts
import { writeLocaleCookie } from "@entur/uniformen";

// The tags your app is translated into. Send them as `params.availableLocales`.
const SUPPORTED = ["nb-NO", "en-GB"] as const;

window.addEventListener("uniformen:locale", (event) => {
  writeLocaleCookie(event.detail.locale);
  window.location.reload();
});
```

重装会改变语言. 服务器使信头使用一种语言, ,模块装入时会加载自己的文本。 如果顶部栏变了 没有重新装入的标签,其余的页面仍将使用旧语言.

如果用户选择已经显示的语言,则没有发送事件,所以 页面不重新装入.

### 选择服务器上的语言

在下一次渲染时,将存储的选择发送为 `params.locale` 设置 `<html lang>` 以同样价值计算. `readLocaleCookie` 财务报告和审定财务报表 `resolveLocale` 用简单的绳子,所以他们 与任何服务器框架一起工作:

```ts
import { fetchUniformenLayout, readLocaleCookie, resolveLocale } from "@entur/uniformen";

const SUPPORTED = ["nb-NO", "en-GB"] as const;

const locale = resolveLocale({
  chosen: readLocaleCookie(request.headers.get("cookie")),
  acceptLanguage: request.headers.get("accept-language"),
  supported: SUPPORTED,
  fallback: "nb-NO",
});

const layout = await fetchUniformenLayout({
  token,
  params: { app: "partner", locale, availableLocales: [...SUPPORTED] },
});
// Render <html lang={locale}> with the layout.
```

`resolveLocale` 使用您的应用程序所支持的第一个 :

1.  `chosen`,用户选择的语言。 您的应用程序不支持的值是 忽略, 因此旧的或已更改的饼干无法打破页面 .
2.  语文 `acceptLanguage`,按其分类 `q` 数值。 标记为 `q=0` 财务报告和审定财务报表 `*` 被排除在外.
3.  `fallback`。 。 。 .

当标签是同一标记时, 或当其语言部分时, 标签匹配 匹配 : `en-US` 给 `en-GB`,以及 `nb` 财务报告和审定财务报表 `no` 给 `nb-NO`。浏览器经常发送 仅这些短或区域标记,所以仅一个精确的匹配就会错过许多用户.

返回类型是您的类型 `supported` 列表,因此可以使用结果 您的应用程序期望它自己的语言类型.

**在浏览器中使用相同的功能** 如果你自己的文本选择他们的语言 在这里。 过 `navigator.languages`,按其自身顺序使用。 浏览器 正在建设 `Accept-Language` 从同一个列表,所以页面和页眉得到 同样的语言,同样在饼干设定前的第一次访问中:

```ts
const locale = resolveLocale({
  chosen: readLocaleCookie(document.cookie),
  acceptLanguage: navigator.languages,
  supported: SUPPORTED,
  fallback: "nb-NO",
});
```

### 这个 `entur_locale` 饼干

`writeLocaleCookie` 写入 `entur_locale` 饼干`LOCALE_COOKIE`整个 为期一年 `SameSite=Lax`,并与 `Secure` 关于HTTPS。 只有你的应用 读它。 制服服务没有。 使用此名称可以让 Entur 应用程序全部存储 同样的选择.

默认饼干属于设定它的主机. 过 `domain`,例如 `writeLocaleCookie(locale, { domain: "entur.org" })`,与其他选择分享 应用程序在同一域. **某个域上的所有应用程序都必须这样做.** 如果一个应用 将饼干设定为 `domain` 和另一个没有,浏览器发送两个饼干与 同样的名字, 和它们之间的顺序是不可靠的. `readLocaleCookie` 返回时 第一个. `domain` 必须是页面的主机或它的母机,所以无法工作 打开 `localhost`。在另一个域中,浏览器忽略了 cookie 没有出错.

如果您将应用程序从主机饼干移动到 `domain`,删除旧主机 在你写新书之前先吃饼干: `document.cookie = "entur_locale=; path=/; max-age=0"`。 。 。 .

## 项目结构

```
packages/uniformen/
├── src/
│   ├── index.ts               # Public API: fetchUniformenLayout, the locale helpers and the types
│   ├── types.ts               # UniformenLayout type definition
│   ├── index.test.ts          # Tests for fetchUniformenLayout
│   ├── locale.test.ts         # Tests for the locale helpers
│   ├── reactAdapter.tsx       # React adapter: fetchUniformenComponents
│   ├── version.ts             # Package version, updated by release-please
│   └── lib/
│       ├── fetchUniformenLayout.ts  # Fetches layout from the Uniformen SSR endpoint
│       ├── locale.ts                # Locale cookie and resolveLocale
│       └── layoutCache.ts           # In-memory cache of fetched layouts
├── dist/                      # Compiled output (generated by bun run build)
├── tsconfig.json              # TypeScript config for type checking and local dev
├── tsconfig.build.json        # TypeScript config for emitting .d.ts declaration files
└── package.json
```

## 大楼

```sh
bun run build
```

## 出版

释放 - 请创建所有释放。 不要用手释放: 不要运行 `bun publish` 或者说 `npm publish` ,不编辑 `version` 内 `package.json` 或者说 [变动情况](https://npmcn.com/package/%40entur/uniformen/CHANGELOG.md)。 。 。 .

每一次推进 `main`,请释放 - 请收集更改的承诺 `packages/uniformen/`。它使下个版本的释放拉动请求打开 生成的更改日志 。 合并后,释放被标记 [发布工作流程](https://npmcn.com/package/.github/workflows/release.yml) 建造、测试和出版 `@entur/uniformen` {\\fn黑体\\fs22\\bord1\\shad0\\3ahbe\\4ah00\\fscx67\\fscy66\\2chffffff\\3ch808080}请说.

该版本基于承诺主题,所以它们必须使用 [常规武器委员会](https://www.conventionalcommits.org/)数字 :

```text
feat(uniformen): add loginUrl to the render options
```

| 类型 | 对已公布版本的影响 |
| --- | --- |
| `feat` | 轻微起落 |
| `fix` | 补丁凸起 |
| `feat!`,或带有 `BREAKING CHANGE:` 页脚 | 重大冲击 |
| `chore`, (中文(简体) ), `docs`, (中文(简体) ), `refactor`, (中文(简体) ), `test`, (中文(简体) ), `build`, (中文(简体) ), `ci` | 无释放 |

见 [捐款额](https://npmcn.com/package/CONTRIBUTING.md) 完全承诺和释放规则.

## 常见问题

### @entur/uniformen 是什么？

Entur apps的页眉和页脚通过Unixen布局服务\.

### 如何安装 @entur/uniformen？

在项目目录运行 npm install @entur/uniformen，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：react（\>=18）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @entur/uniformen 支持 TypeScript 吗？

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

### @entur/uniformen 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @entur/uniformen 需要什么版本的 Node\.js？

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

### @entur/uniformen 可以商用吗？

@entur/uniformen 使用 EUPL-1\.2 许可。声明的许可为 EUPL-1\.2，请阅读许可原文确认使用条件。以上为概括，不构成法律意见，请以许可原文为准。

### @entur/uniformen 的下载量有多少？

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

### @entur/uniformen 还在维护吗？

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

