Skip to content

✨ feat: expose speedy option for createStaticStylesFactory and default instance#214

Open
Innei wants to merge 2 commits into
ant-design:masterfrom
Innei:feat/expose-speedy-option
Open

✨ feat: expose speedy option for createStaticStylesFactory and default instance#214
Innei wants to merge 2 commits into
ant-design:masterfrom
Innei:feat/expose-speedy-option

Conversation

@Innei
Copy link
Copy Markdown

@Innei Innei commented May 23, 2026

Motivation

antd-style currently hardcodes speedy: false in two places that are not user-overridable:

  1. src/factories/createStaticStyles/index.ts — the module-level defaultEmotion = createEmotion({ key, speedy: false }).
  2. src/functions/index.ts — the default styleInstance = createInstance({ key, speedy: false }).

Users importing the top-level createStaticStyles / createStyles thus have no way to opt into emotion's speedy mode, which uses CSSStyleSheet.insertRule() and lets a single <style> tag hold up to 65 000 rules. For large apps generating thousands of style rules at runtime (we hit this in LobeChat), speedy: true removes thousands of DOM insertBefore calls — a measurable win.

createInstance({ speedy: true }) already works for users willing to build a fully custom instance, but that means dropping the convenience of the shared default cache.

Changes

  • CreateStaticStylesOptions: add speedy?: boolean (default false). When no external cache is provided, speedy: true uses a memoized singleton emotion instance to avoid emotion's multi-instance warning on repeated factory calls.

  • Default styleInstance in functions/index.ts: reads its speedy flag from either:

    • process.env.ANTD_STYLE_SPEEDY === 'true' — convenient for bundlers that inline env vars at build time (Vite, webpack, Next, etc.).
    • globalThis.__ANTD_STYLE_SPEEDY__ === true — for callers that prefer not to rely on process.env.

    Default remains false so behavior is unchanged unless one of the opt-in switches is set.

Backwards compatibility

Fully backwards compatible:

  • Callers that don't pass speedy still hit the existing defaultEmotion.cache.
  • The default createInstance resolves to speedy: false unless an opt-in switch is set.

Tests

tests/functions/createStaticStyles.test.tsx covers:

  • speedy default behavior unchanged.
  • speedy: true produces valid styles.
  • Repeated speedy: true factory calls do not emit emotion's multi-instance warning (singleton reuse).
  • speedy: true and speedy: false factory instances can coexist.

I'm open to either of these API choices for the default instance — env var, globalThis flag, or both — happy to drop one if maintainers prefer a single channel. Also happy to split this into two PRs (factory option vs. default-instance switch) if you'd rather review them separately.

Summary by CodeRabbit

  • New Features

    • 新增 Speedy 模式配置项,提供更高效的样式注入途径。
    • 支持通过环境变量或全局开关控制默认 Speedy 行为。
    • 响应式断点扩展,新增更大尺寸的 "xxxl" 断点。
  • Tests

    • 补充 Speedy 模式的测试覆盖,验证并发与兼容场景。
    • 更新响应式相关测试快照以包含 "xxxl"。

Review Change Stack

…t instance

The internal `createStaticStylesFactory` always created its default emotion
cache with `speedy: false`, and `functions/index.ts` hardcoded the default
`createInstance({ speedy: false })`. Users who imported the top-level
`createStaticStyles`/`createStyles` had no way to opt into emotion's
`speedy` mode, which can significantly reduce DOM operations on apps that
generate thousands of style rules at runtime.

This patch:

- Adds a `speedy?: boolean` option to `CreateStaticStylesOptions`.
  When no external `cache` is provided, `speedy: true` makes the factory
  use a memoized singleton emotion instance created with `speedy: true`
  (avoiding emotion's multi-instance warning across repeated factory calls).
- Lets the default `styleInstance` in `functions/index.ts` read its `speedy`
  flag from either `process.env.ANTD_STYLE_SPEEDY === 'true'` (build-time
  inlined by bundlers) or `globalThis.__ANTD_STYLE_SPEEDY__ === true` (set
  before any antd-style import). Default behavior is unchanged (`false`).

The change is fully backwards compatible: callers that omit `speedy` keep
using the existing `defaultEmotion` cache, and the default `createInstance`
still resolves to `speedy: false` unless one of the opt-in switches is set.

Tests cover the new option path including singleton reuse (no multi-instance
warning) and coexistence of speedy / non-speedy factory instances.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

📝 Walkthrough

Walkthrough

为静态样式工厂新增可配置的 speedy 模式(类型、默认值计算、工厂级单例 cache),更新相关测试,并在类型中新增响应式断点 'xxxl'。

变更

Speedy 加速模式与断点扩展

层级 / 文件(s) 说明
Speedy 配置与全局类型声明
src/factories/createStaticStyles/index.ts, src/functions/index.ts
CreateStaticStylesOptions 增加可选字段 speedy?: boolean;在模块中声明 globalThis.__ANTD_STYLE_SPEEDY__ 的类型以支持外部预先设置。
默认 speedy 值计算与默认实例初始化
src/functions/index.ts
新增 defaultSpeedy 计算逻辑:优先读取 process.env.ANTD_STYLE_SPEEDY === 'true',或检测 globalThis.__ANTD_STYLE_SPEEDY__ === true,并将该值用于默认样式实例的 createInstance 参数。
createStaticStylesFactory 的 speedy 单例 cache 与决策逻辑
src/factories/createStaticStyles/index.ts
新增模块级 speedyEmotion 单例变量以在 speedy=true 时懒加载带 speedy: true 的 emotion 实例;重写 factory 内 emotion cache 选择逻辑:优先使用用户提供 cache,次选 speedy 单例 cache(在 speedy=true 时复用),否则回退全局默认 cache。
Speedy 行为测试覆盖
tests/functions/createStaticStyles.test.tsx
新增 speedy 选项测试,验证未传/显式传值的样式生成、speedy=true 多次创建不触发 Emotion 多实例警告,以及 speedy=truespeedy=false 实例并存场景。
响应断点类型与快照更新
src/types/response.ts, tests/hooks/useResponsive.test.ts
Breakpoint 联合类型中新增 'xxxl',并同步更新 useResponsive 的测试快照以包含 "xxxl": false

Mermaid 序列图

sequenceDiagram
  participant App as 应用启动
  participant EnvCheck as 环境读取
  participant DefaultInstance as 默认样式实例
  participant Factory as createStaticStylesFactory
  participant SpeedyCache as speedyEmotion 单例
  participant Emotion as createEmotion

  App->>EnvCheck: 读取 process.env.ANTD_STYLE_SPEEDY 或 globalThis.__ANTD_STYLE_SPEEDY__
  EnvCheck->>DefaultInstance: 计算 defaultSpeedy
  DefaultInstance->>Factory: 初始化默认实例 (speedy: defaultSpeedy)
  Factory->>Factory: 检查传入 cache 参数
  alt 用户提供 cache
    Factory->>Factory: 使用用户 cache
  else speedy=true 且无单例
    Factory->>SpeedyCache: 懒加载 speedyEmotion
    SpeedyCache->>Emotion: createEmotion({ speedy: true })
    Emotion-->>SpeedyCache: 返回 emotion 实例
    SpeedyCache-->>Factory: 提供 emotion.cache
  else 使用全局默认 cache
    Factory->>Factory: 使用全局默认 cache
  end
  Factory->>Factory: createCSS(emotionCache, { hashPriority }) 生成样式
Loading

估算代码审阅工作量

🎯 3 (中等) | ⏱️ ~25 分钟

诗歌

🐰 speedy 模式来报到,
单例缓存不重复,
样式注入更疾跑,
环境标志可控制,
断点再增一笔彩。

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 拉取请求标题准确反映了核心变更内容,清晰表达了为 createStaticStylesFactory 和默认实例公开 speedy 选项的主要目的。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a 'speedy' mode for Emotion, optimizing performance by using CSSStyleSheet.insertRule() to inject styles. Key changes include adding a speedy option to the createStaticStylesFactory, implementing a singleton to manage the speedy Emotion instance, and enabling global configuration through environment variables or global flags. Feedback suggests centralizing the environment detection logic into a shared utility for better consistency and further refining the singleton implementation to ensure the factory and default instances share the same underlying Emotion instances, thereby avoiding potential multi-instance warnings.

Comment thread src/functions/index.ts
Comment on lines +25 to +27
const defaultSpeedy =
(typeof process !== 'undefined' && process.env && process.env.ANTD_STYLE_SPEEDY === 'true') ||
(typeof globalThis !== 'undefined' && globalThis.__ANTD_STYLE_SPEEDY__ === true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for detecting the speedy mode from environment variables and global flags is currently defined locally in this file. This leads to an inconsistency because the createStaticStyles instance exported from src/factories/createStaticStyles/index.ts (via its own defaultInstance) does not respect these flags.

Consider moving this detection logic to a shared utility (e.g., in src/core or a new src/utils/env.ts) so it can be used consistently across all entry points of the library.

Comment on lines +97 to +99
if (!speedyEmotion) {
speedyEmotion = createEmotion({ key: DEFAULT_CSS_PREFIX_KEY, speedy: true });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While this singleton avoids multiple instances within the factory, it may still trigger Emotion's multi-instance warning if the default styleInstance in src/functions/index.ts is also initialized with speedy: true (using the same acss key).

To fully address the motivation of avoiding warnings, the library should ideally share the same underlying Emotion instances between the factory and the default instance for each mode.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/functions/createStaticStyles.test.tsx (2)

351-364: ⚡ Quick win

try/finally 保证 console.error 的 mock 一定被恢复。

当前写法在中途断言失败时不会执行恢复,可能污染后续用例。

💡 建议修改
       it('speedy=true 多次调用应复用同一个 emotion 单例(不应报 multi-instance 警告)', () => {
-        const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
-
-        createStaticStylesFactory({ speedy: true });
-        createStaticStylesFactory({ speedy: true });
-        createStaticStylesFactory({ speedy: true });
-
-        // emotion 多实例警告会通过 console.error 触发
-        const multiInstanceWarning = errorSpy.mock.calls.find((args) =>
-          String(args[0] ?? '').includes('multiple instances of Emotion'),
-        );
-        expect(multiInstanceWarning).toBeUndefined();
-
-        errorSpy.mockRestore();
+        const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
+        try {
+          createStaticStylesFactory({ speedy: true });
+          createStaticStylesFactory({ speedy: true });
+          createStaticStylesFactory({ speedy: true });
+
+          // emotion 多实例警告会通过 console.error 触发
+          const multiInstanceWarning = errorSpy.mock.calls.find((args) =>
+            String(args[0] ?? '').includes('multiple instances of Emotion'),
+          );
+          expect(multiInstanceWarning).toBeUndefined();
+        } finally {
+          errorSpy.mockRestore();
+        }
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/functions/createStaticStyles.test.tsx` around lines 351 - 364, The test
mocks console.error with vi.spyOn into errorSpy but doesn’t guarantee
restoration on assertion failures; wrap the mocking and assertions in a
try/finally so errorSpy.mockRestore() always runs. Specifically, in
tests/functions/createStaticStyles.test.tsx around the createStaticStylesFactory
calls and the multiInstanceWarning assertion, move the mockImplementation setup
before the calls and put the expect(...) logic inside a try block and call
errorSpy.mockRestore() in the finally block to ensure console.error is restored
even if the expect fails.

321-384: ⚡ Quick win

建议补充默认实例 defaultSpeedy 的入口开关测试。

当前新增用例覆盖了 createStaticStylesFactory({ speedy }),但没有覆盖 src/functions/index.ts 里通过 process.env.ANTD_STYLE_SPEEDY / globalThis.__ANTD_STYLE_SPEEDY__ 驱动默认实例的路径。建议补 1~2 个用例,验证这两个入口能真实影响默认导出实例,避免后续回归。

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/functions/createStaticStyles.test.tsx`:
- Around line 351-364: The test mocks console.error with vi.spyOn into errorSpy
but doesn’t guarantee restoration on assertion failures; wrap the mocking and
assertions in a try/finally so errorSpy.mockRestore() always runs. Specifically,
in tests/functions/createStaticStyles.test.tsx around the
createStaticStylesFactory calls and the multiInstanceWarning assertion, move the
mockImplementation setup before the calls and put the expect(...) logic inside a
try block and call errorSpy.mockRestore() in the finally block to ensure
console.error is restored even if the expect fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9cca510a-73f9-4a38-96e3-b2d3121bb13c

📥 Commits

Reviewing files that changed from the base of the PR and between 0231ad6 and dabffea.

📒 Files selected for processing (3)
  • src/factories/createStaticStyles/index.ts
  • src/functions/index.ts
  • tests/functions/createStaticStyles.test.tsx

antd v6.4.x added a new xxxl breakpoint to Grid.useBreakpoint(),
which caused the useResponsive snapshot test to fail. Update the
Breakpoint type and inline snapshot to include the new key.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/types/response.ts`:
- Line 4: Breakpoint 类型新增了 'xxxl' 但映射没有同步:检查并确认目标语义(是否将 `desktop` 对应到
`xxxl`),然后在 `convertBreakpointToResponsive` 中将 `desktop` 的映射从 `breakpoints.xxl`
调整为 `breakpoints.xxxl`(或相反根据确认结果),并同步更新静态映射表(在
`createStaticStyles/responsive`)及所有使用 `Breakpoint` 映射的代码;最后在 PR 描述中说明为何新增
`xxxl`(与 speedy 配置的关系),并在代码中搜索/修正其他引用 `desktop`/`xxl` 的地方以保持一致。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e47dcc22-6979-452d-83a2-2377a55db676

📥 Commits

Reviewing files that changed from the base of the PR and between dabffea and 44d7a2f.

📒 Files selected for processing (2)
  • src/types/response.ts
  • tests/hooks/useResponsive.test.ts

Comment thread src/types/response.ts
import { SerializedStyles } from './css';

export type Breakpoint =
| 'xxxl'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 检查 convertBreakpointToResponsive 中的 desktop 映射定义
rg -n "desktop.*breakpoints\.(xxl|xxxl)" --type ts

# 检查是否有其他地方对 Breakpoint 类型做穷举检查(可能因新增 xxxl 而中断)
rg -n "type.*Breakpoint|switch.*breakpoint" --type ts -A 5

Repository: ant-design/antd-style

Length of output: 1081


补齐/明确 xxxldesktop 映射语义,避免断点范围不一致

  • src/types/response.tsBreakpoint 已加入 'xxxl',但 convertBreakpointToResponsivedesktop 仍映射为 breakpoints.xxlsrc/utils/responsive.ts:10),且静态映射同样未涉及 xxxlsrc/factories/createStaticStyles/responsive.ts:23);需确认 desktop 是否应对应 xxxl,并同步更新相关映射/使用方。
  • 新增断点与当前 PR “暴露 speedy 配置选项”关联不明;若为有意叠加,建议在 PR 描述中说明原因。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/response.ts` at line 4, Breakpoint 类型新增了 'xxxl'
但映射没有同步:检查并确认目标语义(是否将 `desktop` 对应到 `xxxl`),然后在 `convertBreakpointToResponsive`
中将 `desktop` 的映射从 `breakpoints.xxl` 调整为
`breakpoints.xxxl`(或相反根据确认结果),并同步更新静态映射表(在 `createStaticStyles/responsive`)及所有使用
`Breakpoint` 映射的代码;最后在 PR 描述中说明为何新增 `xxxl`(与 speedy 配置的关系),并在代码中搜索/修正其他引用
`desktop`/`xxl` 的地方以保持一致。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant