From 65ba0c7228f9917febabc25b19e8497d049eea19 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Fri, 2 Oct 2026 10:00:35 +0900 Subject: [PATCH] fix(react): add a no-op Emotion CacheProvider and redirect its import Refs #689 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .changepacks/changepack_log_emotion_exports.json | 8 ++++++++ bindings/devup-ui-wasm/src/lib.rs | 8 ++++---- libs/css/src/theme_tokens.rs | 2 +- libs/extractor/src/import_alias_visit.rs | 16 ++++++++++++++++ libs/extractor/src/lib.rs | 8 ++++---- libs/extractor/src/tailwind.rs | 4 ++-- .../react/src/compat/__tests__/index.test.ts | 1 + packages/react/src/compat/emotion.d.ts | 1 + packages/react/src/compat/index.ts | 1 + .../src/utils/__tests__/styled-compat.test.ts | 8 ++++++++ packages/react/src/utils/styled-compat.ts | 13 +++++++++++++ 11 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 .changepacks/changepack_log_emotion_exports.json diff --git a/.changepacks/changepack_log_emotion_exports.json b/.changepacks/changepack_log_emotion_exports.json new file mode 100644 index 000000000..88bd30287 --- /dev/null +++ b/.changepacks/changepack_log_emotion_exports.json @@ -0,0 +1,8 @@ +{ + "changes": { + "packages/react/package.json": "Patch", + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "Emotion's CacheProvider is available from the compat entry as a no-op that renders its children, and `import { CacheProvider } from '@emotion/react'` is redirected there instead of keeping @emotion/react as a runtime dependency; the cache it configures holds styles Emotion inserts at runtime, which Devup UI writes at build time", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/bindings/devup-ui-wasm/src/lib.rs b/bindings/devup-ui-wasm/src/lib.rs index 4560410f3..0a2347ac2 100644 --- a/bindings/devup-ui-wasm/src/lib.rs +++ b/bindings/devup-ui-wasm/src/lib.rs @@ -1551,7 +1551,7 @@ mod tests { ); // Test getters - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert_eq!(output.css_file(), Some("devup-ui-0.css".to_string())); assert_eq!(output.map(), Some("//# sourceMappingURL=test".to_string())); assert!(output.css().is_some()); @@ -1951,7 +1951,7 @@ mod tests { assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert!(output.map().is_some()); } @@ -1975,7 +1975,7 @@ mod tests { assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert!(output.map().is_none()); } @@ -2000,7 +2000,7 @@ mod tests { assert!(result.is_err()); if let Err(error) = result { - assert!(!error.is_empty()); + assert_ne!(error, ""); } } diff --git a/libs/css/src/theme_tokens.rs b/libs/css/src/theme_tokens.rs index d8d8fb021..3b2346aed 100644 --- a/libs/css/src/theme_tokens.rs +++ b/libs/css/src/theme_tokens.rs @@ -119,7 +119,7 @@ mod tests { set_typography_keys(vec!["body".to_string(), "title".to_string()]); assert_eq!(get_typography_keys(), vec!["body", "title"]); set_typography_keys(vec![]); - assert!(get_typography_keys().is_empty()); + assert_eq!(get_typography_keys(), Vec::::new()); } #[test] diff --git a/libs/extractor/src/import_alias_visit.rs b/libs/extractor/src/import_alias_visit.rs index 158d0f420..7ede09c58 100644 --- a/libs/extractor/src/import_alias_visit.rs +++ b/libs/extractor/src/import_alias_visit.rs @@ -215,6 +215,7 @@ fn devup_equivalent(source: &str, imported: &str) -> Option (_, "isStyledComponent") => Some(DevupTarget::Compat("isStyledComponent")), (_, "withTheme") => Some(DevupTarget::Compat("withTheme")), (_, "useTheme") => Some(DevupTarget::Compat("useTheme")), + ("@emotion/react", "CacheProvider") => Some(DevupTarget::Compat("CacheProvider")), _ => None, } } @@ -728,6 +729,21 @@ mod tests { )); } + #[test] + fn test_emotion_cache_provider_redirects_to_compat() { + let code = transform_import_aliases( + "import { CacheProvider, Global } from '@emotion/react'", + "test.tsx", + "@devup-ui/react", + &HashMap::from([("@emotion/react".to_string(), ImportAlias::NamedToNamed)]), + ); + assert!( + code.contains("import { CacheProvider, Global } from '@devup-ui/react/compat';"), + "{code}" + ); + assert!(!code.contains("from '@emotion/react'"), "{code}"); + } + #[test] fn test_no_matching_alias() { assert_snapshot!(transform_import_aliases( diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 68981f36c..96c6ca430 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -836,8 +836,8 @@ mod tests { alternate: None, }; - assert!(empty.extract().is_empty()); - assert!(empty.into_extract().is_empty()); + assert_eq!(empty.extract(), vec![]); + assert_eq!(empty.into_extract(), vec![]); } #[test] @@ -13598,7 +13598,7 @@ globalCss({ ); assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code.is_empty()); + assert_ne!(output.code, ""); } #[test] @@ -18778,7 +18778,7 @@ export const k = styled('div')({ color: SIZE });", &memory_resolver(CONSTANT_MODULES), ) .unwrap(); - assert!(without_imports.dependencies.is_empty()); + assert_eq!(without_imports.dependencies.len(), 0); let without_constants = extract_with_modules( "/src/Handler.tsx", "import { Box } from '@devup-ui/react';\nimport { handler } from './handler';\nexport const a = ;", diff --git a/libs/extractor/src/tailwind.rs b/libs/extractor/src/tailwind.rs index 87e9f5178..70643aa7c 100644 --- a/libs/extractor/src/tailwind.rs +++ b/libs/extractor/src/tailwind.rs @@ -284,7 +284,7 @@ pub struct TailwindClass { /// non-overlapping) but mutates the existing buffer instead of allocating a new /// `String`. `needle` must be non-empty. fn remove_all_substr(haystack: &mut String, needle: &str) { - debug_assert!(!needle.is_empty()); + debug_assert_ne!(needle, ""); let mut search_from = 0; while let Some(rel) = haystack[search_from..].find(needle) { let at = search_from + rel; @@ -3934,7 +3934,7 @@ mod tests { #[test] fn test_empty_string() { let styles = parse_tailwind_to_styles(""); - assert!(styles.is_empty()); + assert_eq!(styles, vec![]); } #[test] diff --git a/packages/react/src/compat/__tests__/index.test.ts b/packages/react/src/compat/__tests__/index.test.ts index ad6e86619..067b6c082 100644 --- a/packages/react/src/compat/__tests__/index.test.ts +++ b/packages/react/src/compat/__tests__/index.test.ts @@ -12,6 +12,7 @@ describe('compat entry', () => { useTheme: expect.any(Function), withTheme: expect.any(Function), + CacheProvider: expect.any(Function), isStyledComponent: expect.any(Function), ServerStyleSheet: expect.any(Function), StyleSheetManager: expect.any(Function), diff --git a/packages/react/src/compat/emotion.d.ts b/packages/react/src/compat/emotion.d.ts index 69bdf9176..251b7bd46 100644 --- a/packages/react/src/compat/emotion.d.ts +++ b/packages/react/src/compat/emotion.d.ts @@ -8,6 +8,7 @@ declare module '@emotion/react' { export { css, keyframes } from '@devup-ui/react' export type { StyledTheme as Theme } from '@devup-ui/react/compat' export { + CacheProvider, Global, ThemeProvider, useTheme, diff --git a/packages/react/src/compat/index.ts b/packages/react/src/compat/index.ts index c22aa8eb4..b05d12493 100644 --- a/packages/react/src/compat/index.ts +++ b/packages/react/src/compat/index.ts @@ -12,6 +12,7 @@ export { ThemeProvider } from '../components/ThemeProvider' export { useStyledTheme as useTheme } from '../hooks/use-styled-theme' export { createGlobalStyle } from '../utils/create-global-style' export { + CacheProvider, isStyledComponent, ServerStyleSheet, StyleSheetManager, diff --git a/packages/react/src/utils/__tests__/styled-compat.test.ts b/packages/react/src/utils/__tests__/styled-compat.test.ts index 3a7b15091..0caeb0490 100644 --- a/packages/react/src/utils/__tests__/styled-compat.test.ts +++ b/packages/react/src/utils/__tests__/styled-compat.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'bun:test' import { + CacheProvider, isStyledComponent, ServerStyleSheet, StyleSheetManager, @@ -23,6 +24,13 @@ describe('StyleSheetManager', () => { }) }) +describe('CacheProvider', () => { + it('renders its children untouched', () => { + expect(CacheProvider({ value: {}, children: 'child' })).toBe('child') + expect(CacheProvider({})).toBeUndefined() + }) +}) + describe('isStyledComponent', () => { it('always reports false', () => { expect(isStyledComponent(() => null)).toBe(false) diff --git a/packages/react/src/utils/styled-compat.ts b/packages/react/src/utils/styled-compat.ts index 517ad4d4f..63c346289 100644 --- a/packages/react/src/utils/styled-compat.ts +++ b/packages/react/src/utils/styled-compat.ts @@ -35,6 +35,19 @@ export function StyleSheetManager({ return children } +/** + * Emotion's `CacheProvider`, kept as a no-op: the cache it configures holds + * styles Emotion would insert at runtime, and Devup UI writes them at build time + */ +export function CacheProvider({ + children, +}: { + value?: unknown + children?: ReactNode +}): ReactNode { + return children +} + export function isStyledComponent(_target: unknown): boolean { return false }