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
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_vanilla_extract_companions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch"
},
"note": "While @vanilla-extract/css is aliased, importing @vanilla-extract/recipes or @vanilla-extract/sprinkles is a build error naming the file, line and column: those packages generate CSS Devup UI does not compile, so their rules used to be silently missing from the stylesheet. The error says to write the styles with css(), or to disable the alias and build with the vanilla-extract plugin. Type-only imports are allowed",
"date": "2026-10-01T00:00:00.000Z"
}
65 changes: 65 additions & 0 deletions libs/extractor/src/import_alias_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,45 @@ pub fn transform_import_aliases<'a>(
transform_import_aliases_with_edits(code, filename, package, import_aliases).0
}

/// Packages generating CSS on top of vanilla-extract's, which Devup UI does not
/// compile
const VANILLA_EXTRACT_COMPANIONS: [&str; 2] =
["@vanilla-extract/recipes", "@vanilla-extract/sprinkles"];

/// The value imports of vanilla-extract's companion packages, as errors at
/// their offsets: while `@vanilla-extract/css` compiles to Devup UI, the
/// rules they generate would never reach the stylesheet
pub fn companion_errors(code: &str, filename: &str) -> Vec<(u32, String)> {
if !VANILLA_EXTRACT_COMPANIONS
.iter()
.any(|package| code.contains(package))
{
return Vec::new();
}
let allocator = Allocator::default();
let source_type = SourceType::from_path(filename).unwrap_or_default();
let program = Parser::new(&allocator, code, source_type).parse().program;
program
.body
.iter()
.filter_map(|statement| match statement {
Statement::ImportDeclaration(import)
if !import.import_kind.is_type()
&& VANILLA_EXTRACT_COMPANIONS.contains(&import.source.value.as_str()) =>
{
Some((
import.span.start,
format!(
"`{}` generates CSS that Devup UI does not compile, so with the `@vanilla-extract/css` alias its rules would never reach the stylesheet. Fix: write the styles with `css()`, or set `importAliases: {{ '@vanilla-extract/css': false }}` and build with the vanilla-extract plugin",
import.source.value
),
))
}
_ => None,
})
.collect()
}

/// A replacement of `code[start..end]` by text of `length` bytes
pub type Edit = (usize, usize, usize);

Expand Down Expand Up @@ -728,6 +767,32 @@ mod tests {
));
}

#[test]
fn test_vanilla_extract_companions_are_reported() {
let errors = companion_errors(
"import type { RecipeVariants } from '@vanilla-extract/recipes'
import { recipe } from '@vanilla-extract/recipes'
import { style } from '@vanilla-extract/css'
import { createSprinkles } from '@vanilla-extract/sprinkles'",
"a.css.ts",
);
assert_eq!(errors.len(), 2, "{errors:?}");
assert!(
errors[0]
.1
.starts_with("`@vanilla-extract/recipes` generates CSS")
);
assert!(
errors[1]
.1
.starts_with("`@vanilla-extract/sprinkles` generates CSS")
);
assert_eq!(
companion_errors("import { style } from '@vanilla-extract/css'", "a.css.ts"),
vec![]
);
}

#[test]
fn test_no_matching_alias() {
assert_snapshot!(transform_import_aliases(
Expand Down
34 changes: 34 additions & 0 deletions libs/extractor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,12 @@ fn extract_source(
source_map: bool,
resolver: Option<&ModuleResolver>,
) -> Result<ExtractOutput, Box<dyn Error>> {
if evaluated.is_none() && option.import_aliases.contains_key("@vanilla-extract/css") {
let errors = import_alias_visit::companion_errors(code, filename);
if !errors.is_empty() {
return Err(located_errors(filename, code, &[], errors).into());
}
}
// Step 1: Transform import aliases
// e.g., `import styled from '@emotion/styled'` → `import { styled } from '@devup-ui/react'`
// e.g., `import { style } from '@vanilla-extract/css'` → `import { style } from '@devup-ui/react'`
Expand Down Expand Up @@ -11201,6 +11207,34 @@ let color = "red";
));
}

#[test]
#[serial]
fn test_vanilla_extract_companion_import_is_an_error() {
let option = |aliased: bool| ExtractOption {
package: "@devup-ui/react".to_string(),
css_dir: "@devup-ui/react".to_string(),
single_css: true,
import_main_css: false,
import_aliases: if aliased {
HashMap::from([(
"@vanilla-extract/css".to_string(),
ImportAlias::NamedToNamed,
)])
} else {
HashMap::new()
},
};
let code = "import { recipe } from '@vanilla-extract/recipes'\nexport const b = recipe({})";
let error = extract("a.css.ts", code, option(true))
.unwrap_err()
.to_string();
assert!(
error.starts_with("a.css.ts:1:1: `@vanilla-extract/recipes`"),
"{error}"
);
assert!(extract("a.css.ts", code, option(false)).is_ok());
}

#[test]
#[serial]
fn test_keyframes_no_args() {
Expand Down
Loading