Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changepacks/changepack_log_stylex_unsupported_api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch",
"bindings/devup-ui-binding/package.json": "Patch"
},
"note": "A StyleX API Devup UI does not compile is a located build error naming it, instead of being dropped from the compiled set while its calls stay StyleX calls that throw at runtime. New build error: an export of `@stylexjs/stylex` outside `create`, `props`, `attrs`, `keyframes`, `defineVars`, `createTheme`, `createThemeContract`, `defineConsts`, `positionTry`, `viewTransitionClass`, `firstThatWorks`, `include` and `types` (such as `when`, `env` or `defaultMarker`), imported by name or read as a member of the package imported whole",
"date": "2026-11-15T00:00:00.000Z"
}
4 changes: 4 additions & 0 deletions apps/landing/src/app/(detail)/docs/migration/stylex/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,7 @@ const transition = stylex.viewTransitionClass({ animationDuration: '300ms' })
## Value helpers

`firstThatWorks()` emits its fallbacks in CSS order, `types.*()` unwraps to the inner value, and `include()` merges another namespace's classes. Values may be plain literals, condition objects (`{ default, ':hover', '@media ...' }`), or arrow functions, which become CSS variables bound at the call site.

## Unsupported APIs

Devup UI compiles `create`, `props`, `attrs`, `keyframes`, `defineVars`, `createTheme`, `createThemeContract`, `defineConsts`, `positionTry`, `viewTransitionClass`, `firstThatWorks`, `include` and `types`. Any other export of `@stylexjs/stylex` (`when`, `env`, `defaultMarker`, ...) would stay a StyleX call that throws at runtime, so importing it or reading it from the package is a build error naming it.
51 changes: 37 additions & 14 deletions libs/extractor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17902,6 +17902,30 @@ const styles = create({ base: { color: 'green' } });",
));
}

// A StyleX API Devup UI does not compile would stay a StyleX call, which
// throws at runtime, so it is a located error naming it
#[test]
#[serial]
fn test_stylex_unsupported_api_is_located_error() {
let errors: Vec<(&str, Option<String>)> = [
"import { create, when, type StyleXStyles } from '@stylexjs/stylex';\nexport const a = when.ancestor(':hover');",
"import { env as e } from '@stylexjs/stylex';\nexport const a = e.tokens;",
"import stylex from '@stylexjs/stylex';\nexport const a = stylex.when.ancestor(':hover');\nexport const b = stylex.env;\nexport const c = stylex.defaultMarker();",
"import * as sx from '@stylexjs/stylex';\nconst styles = sx.create({ a: { color: { default: 'red', [sx.when.ancestor(':hover')]: 'blue' } } });\nexport const p = sx.props(styles.a);",
]
.into_iter()
.map(|code| {
reset_class_map();
reset_file_map();
let error = extract("test.tsx", code, ExtractOption::default())
.err()
.map(|error| error.to_string());
(code, error)
})
.collect();
assert_debug_snapshot!(errors);
}

#[test]
#[serial]
fn test_stylex_create_single_namespace_static() {
Expand Down Expand Up @@ -19626,21 +19650,20 @@ const composed = stylex.create({ fancy: { ...stylex.include(base.dynamic), backg
fn test_stylex_import_unknown_named() {
reset_class_map();
reset_file_map();
assert_debug_snapshot!(ToBTreeSet::from(
extract(
"test.tsx",
r"import { create, unknownFunction } from '@stylexjs/stylex';
// It stayed imported from `@stylexjs/stylex`, which then ran at runtime
let error = extract(
"test.tsx",
r"import { create, unknownFunction } from '@stylexjs/stylex';
const styles = create({ base: { color: 'red' } });",
ExtractOption {
package: "@devup-ui/react".to_string(),
css_dir: "@devup-ui/react".to_string(),
single_css: true,
import_main_css: false,
import_aliases: HashMap::new()
},
)
.unwrap()
));
ExtractOption::default(),
)
.unwrap_err()
.to_string();
assert!(
error
.starts_with("test.tsx:1:18: `@stylexjs/stylex` `unknownFunction` is not compiled"),
"{error}"
);
}

// ==========================================
Expand Down
6 changes: 6 additions & 0 deletions libs/extractor/src/scope/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ impl Bindings {
}
}

/// The member of the `StyleX` package imported whole that `expression`
/// reads, as `when` in `stylex.when`
pub fn stylex_member<'e>(&self, expression: &'e Expression<'_>) -> Option<&'e str> {
self.member_of(&self.stylex_namespaces, expression)
}

/// The bindings the names `object` reads all stand for, by name
pub fn visible(&self, object: &ObjectExpression<'_>) -> FxHashMap<String, SymbolId> {
let mut reads = visits::Names::new(self);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
source: libs/extractor/src/lib.rs
expression: errors
---
[
(
"import { create, when, type StyleXStyles } from '@stylexjs/stylex';\nexport const a = when.ancestor(':hover');",
Some(
"test.tsx:1:18: `@stylexjs/stylex` `when` is not compiled by Devup UI, so it would stay a StyleX call at runtime, which throws with no StyleX compiler run. Fix: use only `create`, `props`, `attrs`, `keyframes`, `defineVars`, `createTheme`, `createThemeContract`, `defineConsts`, `positionTry`, `viewTransitionClass`, `firstThatWorks`, `include`, `types` from `@stylexjs/stylex`",
),
),
(
"import { env as e } from '@stylexjs/stylex';\nexport const a = e.tokens;",
Some(
"test.tsx:1:10: `@stylexjs/stylex` `env` is not compiled by Devup UI, so it would stay a StyleX call at runtime, which throws with no StyleX compiler run. Fix: use only `create`, `props`, `attrs`, `keyframes`, `defineVars`, `createTheme`, `createThemeContract`, `defineConsts`, `positionTry`, `viewTransitionClass`, `firstThatWorks`, `include`, `types` from `@stylexjs/stylex`",
),
),
(
"import stylex from '@stylexjs/stylex';\nexport const a = stylex.when.ancestor(':hover');\nexport const b = stylex.env;\nexport const c = stylex.defaultMarker();",
Some(
"test.tsx:2:18: `@stylexjs/stylex` `when` is not compiled by Devup UI, so it would stay a StyleX call at runtime, which throws with no StyleX compiler run. Fix: use only `create`, `props`, `attrs`, `keyframes`, `defineVars`, `createTheme`, `createThemeContract`, `defineConsts`, `positionTry`, `viewTransitionClass`, `firstThatWorks`, `include`, `types` from `@stylexjs/stylex`\ntest.tsx:3:18: `@stylexjs/stylex` `env` is not compiled by Devup UI, so it would stay a StyleX call at runtime, which throws with no StyleX compiler run. Fix: use only `create`, `props`, `attrs`, `keyframes`, `defineVars`, `createTheme`, `createThemeContract`, `defineConsts`, `positionTry`, `viewTransitionClass`, `firstThatWorks`, `include`, `types` from `@stylexjs/stylex`\ntest.tsx:4:18: `@stylexjs/stylex` `defaultMarker` is not compiled by Devup UI, so it would stay a StyleX call at runtime, which throws with no StyleX compiler run. Fix: use only `create`, `props`, `attrs`, `keyframes`, `defineVars`, `createTheme`, `createThemeContract`, `defineConsts`, `positionTry`, `viewTransitionClass`, `firstThatWorks`, `include`, `types` from `@stylexjs/stylex`",
),
),
(
"import * as sx from '@stylexjs/stylex';\nconst styles = sx.create({ a: { color: { default: 'red', [sx.when.ancestor(':hover')]: 'blue' } } });\nexport const p = sx.props(styles.a);",
Some(
"test.tsx:2:59: `@stylexjs/stylex` `when` is not compiled by Devup UI, so it would stay a StyleX call at runtime, which throws with no StyleX compiler run. Fix: use only `create`, `props`, `attrs`, `keyframes`, `defineVars`, `createTheme`, `createThemeContract`, `defineConsts`, `positionTry`, `viewTransitionClass`, `firstThatWorks`, `include`, `types` from `@stylexjs/stylex`\ntest.tsx:2:59: `stylex.create()` cannot use `[sx.when.ancestor(\":hover\")]` at build time: its keys must be known",
),
),
]
15 changes: 15 additions & 0 deletions libs/extractor/src/stylex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ impl StylexFunction {
}
}

/// `name`, an export of `@stylexjs/stylex` Devup UI does not compile, as
/// `when` or `env`: it would stay a `StyleX` call at runtime, which throws
/// with no `StyleX` compiler run
#[must_use]
pub fn unsupported_stylex_error(name: &str) -> String {
let supported: Vec<String> = STYLEX_EXPORTS
.iter()
.map(|(name, _)| format!("`{name}`"))
.collect();
format!(
"`@stylexjs/stylex` `{name}` is not compiled by Devup UI, so it would stay a StyleX call at runtime, which throws with no StyleX compiler run. Fix: use only {} from `@stylexjs/stylex`",
supported.join(", ")
)
}

/// The custom property `stylex.defineVars()` in `filename` declares for `key`;
/// a module importing it computes the same name
#[must_use]
Expand Down
23 changes: 22 additions & 1 deletion libs/extractor/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ use crate::scope::{Bindings, NAMESPACE_STYLED};
use crate::style_collector::StyleCollector;
use crate::stylex::{
StylexDynamicInfo, StylexFunction, StylexNamespaceValue, create_theme_class,
css_variable_block, css_variable_rules, define_vars_variable, variable_values,
css_variable_block, css_variable_rules, define_vars_variable, unsupported_stylex_error,
variable_values,
};
use crate::util_type::UtilType;
use crate::{ExtractStyleProp, ExtractStyleValue};
Expand Down Expand Up @@ -3363,6 +3364,12 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> {
if self.compile_own_rules(it) {
return;
}
if let Some(name) = self.bindings.stylex_member(it)
&& StylexFunction::from_export_name(name).is_none()
{
self.errors
.push((it.span().start, unsupported_stylex_error(name)));
}
if let Expression::CallExpression(call) = &*it
&& self.is_css(&call.callee)
{
Expand Down Expand Up @@ -5143,6 +5150,11 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> {
if let Some(func) = StylexFunction::from_export_name(&imported) {
self.bindings
.stylex_import(named_spec.local.symbol_id.get(), func);
} else {
self.errors.push((
named_spec.span.start,
unsupported_stylex_error(&imported),
));
}
}
ImportSpecifier(_) => {}
Expand Down Expand Up @@ -5785,6 +5797,15 @@ mod tests {
);
assert!(code.contains("export const animation = \""), "{code}");
assert!(code.contains("import value from \"other\";"), "{code}");
// `unknown` would stay a StyleX call at runtime
assert_eq!(visitor.errors.len(), 1, "{:?}", visitor.errors);
assert!(
visitor.errors[0]
.1
.starts_with("`@stylexjs/stylex` `unknown` is not compiled"),
"{:?}",
visitor.errors
);
}

#[test]
Expand Down
Loading