✨ feat: expose speedy option for createStaticStylesFactory and default instance#214
✨ feat: expose speedy option for createStaticStylesFactory and default instance#214Innei wants to merge 2 commits into
Conversation
…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.
📝 WalkthroughWalkthrough为静态样式工厂新增可配置的 speedy 模式(类型、默认值计算、工厂级单例 cache),更新相关测试,并在类型中新增响应式断点 'xxxl'。 变更Speedy 加速模式与断点扩展
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 }) 生成样式
估算代码审阅工作量🎯 3 (中等) | ⏱️ ~25 分钟 诗歌
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.
| const defaultSpeedy = | ||
| (typeof process !== 'undefined' && process.env && process.env.ANTD_STYLE_SPEEDY === 'true') || | ||
| (typeof globalThis !== 'undefined' && globalThis.__ANTD_STYLE_SPEEDY__ === true); |
There was a problem hiding this comment.
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.
| if (!speedyEmotion) { | ||
| speedyEmotion = createEmotion({ key: DEFAULT_CSS_PREFIX_KEY, speedy: true }); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
🧹 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
📒 Files selected for processing (3)
src/factories/createStaticStyles/index.tssrc/functions/index.tstests/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.
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
src/types/response.tstests/hooks/useResponsive.test.ts
| import { SerializedStyles } from './css'; | ||
|
|
||
| export type Breakpoint = | ||
| | 'xxxl' |
There was a problem hiding this comment.
🧩 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 5Repository: ant-design/antd-style
Length of output: 1081
补齐/明确 xxxl 与 desktop 映射语义,避免断点范围不一致
src/types/response.ts的Breakpoint已加入'xxxl',但convertBreakpointToResponsive中desktop仍映射为breakpoints.xxl(src/utils/responsive.ts:10),且静态映射同样未涉及xxxl(src/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` 的地方以保持一致。
Motivation
antd-stylecurrently hardcodesspeedy: falsein two places that are not user-overridable:src/factories/createStaticStyles/index.ts— the module-leveldefaultEmotion = createEmotion({ key, speedy: false }).src/functions/index.ts— the defaultstyleInstance = createInstance({ key, speedy: false }).Users importing the top-level
createStaticStyles/createStylesthus have no way to opt into emotion'sspeedymode, which usesCSSStyleSheet.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: trueremoves thousands of DOMinsertBeforecalls — 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: addspeedy?: boolean(defaultfalse). When no externalcacheis provided,speedy: trueuses a memoized singleton emotion instance to avoid emotion's multi-instance warning on repeated factory calls.Default
styleInstanceinfunctions/index.ts: reads itsspeedyflag 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 onprocess.env.Default remains
falseso behavior is unchanged unless one of the opt-in switches is set.Backwards compatibility
Fully backwards compatible:
speedystill hit the existingdefaultEmotion.cache.createInstanceresolves tospeedy: falseunless an opt-in switch is set.Tests
tests/functions/createStaticStyles.test.tsxcovers:speedydefault behavior unchanged.speedy: trueproduces valid styles.speedy: truefactory calls do not emit emotion's multi-instance warning (singleton reuse).speedy: trueandspeedy: falsefactory 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
Tests