diff --git a/Cargo.lock b/Cargo.lock index b48ed3c10..f4adb17f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1990,6 +1990,7 @@ dependencies = [ "iris-build", "iris-diagnostics", "iris-watch-server", + "javascript", "serde", "serde_json", "url", diff --git a/compiler-backend/functional/src/convert.rs b/compiler-backend/functional/src/convert.rs index 35cc7195f..13e7dfd11 100644 --- a/compiler-backend/functional/src/convert.rs +++ b/compiler-backend/functional/src/convert.rs @@ -203,7 +203,7 @@ fn convert(mut context: Context<'_, impl checking::ExternalQueries>) -> Conversi } } // StyleX consumes the emitted expressions, including literals substituted for local bindings. - context.validate_stylex_uses(&declarations)?; + let stylex_theme_imports = context.validate_stylex_uses(&declarations)?; let dependencies = context.dependencies.iter().map(|(&file_id, dependency)| ModuleDependency { file_id, @@ -220,6 +220,7 @@ fn convert(mut context: Context<'_, impl checking::ExternalQueries>) -> Conversi dependencies: dependencies.into(), surface, declarations: declarations.into(), + stylex_theme_imports, storage: context.storage, }) } diff --git a/compiler-backend/functional/src/convert/stylex.rs b/compiler-backend/functional/src/convert/stylex.rs index d46ad2a5c..9acd93c40 100644 --- a/compiler-backend/functional/src/convert/stylex.rs +++ b/compiler-backend/functional/src/convert/stylex.rs @@ -297,13 +297,14 @@ where Ok(intrinsic) } + /// Returns the imported theme values that StyleX evaluates statically in this module. pub(super) fn validate_stylex_uses( &self, declarations: &[Declaration], - ) -> ConversionResult<()> { + ) -> ConversionResult> { // StyleX intrinsics and expressions only arise from references to the virtual modules. if !self.references_stylex_module && !self.module_is_virtual(self.file_id) { - return Ok(()); + return Ok(FxHashSet::default()); } let mut bindings = StyleXStaticBindings::default(); for declaration in declarations { @@ -334,7 +335,8 @@ where &mut bindings, )?; } - Ok(()) + let theme_imports = bindings.imports.into_iter().filter(|&(_, theme)| theme); + Ok(theme_imports.map(|(global, _)| global).collect()) } fn validate_stylex_expression( @@ -768,13 +770,9 @@ where Ok(matches!( context.storage[expression].kind, ExpressionKind::StyleX(StyleXExpression::Call { - target: StyleXCallTarget::Root( - StyleXRootCall::DefineVars - | StyleXRootCall::DefineConsts - | StyleXRootCall::DefineMarker - ), + target: StyleXCallTarget::Root(call), .. - }) + }) if call.defines_theme_value() )) } diff --git a/compiler-backend/functional/src/stylex.rs b/compiler-backend/functional/src/stylex.rs index d455dea8a..81250b395 100644 --- a/compiler-backend/functional/src/stylex.rs +++ b/compiler-backend/functional/src/stylex.rs @@ -130,6 +130,17 @@ impl StyleXRootCall { StyleXRootCall::FirstThatWorks => "firstThatWorks", } } + + /// StyleX hashes these definitions by file path, so it accepts them only in theme files and + /// resolves references to them only through imports of theme files. + pub fn defines_theme_value(self) -> bool { + matches!( + self, + StyleXRootCall::DefineVars + | StyleXRootCall::DefineConsts + | StyleXRootCall::DefineMarker + ) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/compiler-backend/functional/src/tree.rs b/compiler-backend/functional/src/tree.rs index 2ec0ca8a3..2bd8bf6f2 100644 --- a/compiler-backend/functional/src/tree.rs +++ b/compiler-backend/functional/src/tree.rs @@ -7,6 +7,7 @@ use files::FileId; use indexing::{DeriveId, InstanceId, TermItemId, TypeItemId}; use la_arena::{Arena, Idx}; use lowering::TypeId as SourceTypeId; +use rustc_hash::FxHashSet; use smol_str::SmolStr; use crate::stylex::StyleXExpression; @@ -21,6 +22,9 @@ pub struct Module { pub dependencies: Arc<[ModuleDependency]>, pub surface: ModuleSurface, pub declarations: Arc<[Declaration]>, + /// Imported `defineVars`, `defineConsts`, and `defineMarker` values that StyleX evaluates + /// statically, which it resolves only through imports of the defining module's theme file. + pub stylex_theme_imports: FxHashSet, pub storage: Storage, } diff --git a/compiler-backend/javascript/src/convert/generator/functional/render.rs b/compiler-backend/javascript/src/convert/generator/functional/render.rs index d459e6195..b0fdc835f 100644 --- a/compiler-backend/javascript/src/convert/generator/functional/render.rs +++ b/compiler-backend/javascript/src/convert/generator/functional/render.rs @@ -26,7 +26,9 @@ use smol_str::{SmolStr, format_smolstr}; use super::super::names::NameAllocator; use crate::error::{ModuleDiagnostic, ModuleError, ModuleResult, UnsupportedState}; -use crate::module::{Module, module_filename, runtime_filename}; +use crate::module::{ + Module, STYLEX_THEME_FILENAME, module_filename, runtime_filename, stylex_theme_module_filename, +}; use crate::tree::{BinaryOperator, ExpressionId, ObjectProperty, Tree, UnaryOperator}; use crate::writer::{BindingCallTarget, Writer}; @@ -34,7 +36,7 @@ use self::inline::{is_abstraction, pattern_parameter}; use self::structure::{ collect_module_references, cyclic_instance_initializers, has_local_lazy_initializers, }; -use self::stylex::collect_stylex_references; +use self::stylex::{StyleXTheme, collect_stylex_references, collect_stylex_theme}; use self::syntax::{ binary_expression, combine_conditions, constructor_expression, curried_call_expression, literal_expression, synthesized_evidence_expression, unary_expression, @@ -55,6 +57,7 @@ pub(crate) struct Generator<'m> { external_named_imports: FxHashMap, external_references: Vec, stylex_namespace: Option, + stylex_theme: Option, foreign_import: Option, runtime_namespace: Option, lazy_global_names: FxHashMap, @@ -177,6 +180,14 @@ struct ModuleRenderer<'a, 'm, 't> { generator: &'a Generator<'m>, tree: &'a mut Tree<'t>, writer: &'a mut Writer<'t>, + file: OutputFile, +} + +/// The files generated for a module. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum OutputFile { + Index, + StyleXTheme, } struct FunctionRenderer<'a, 'm, 't> { @@ -309,6 +320,7 @@ impl<'m> Generator<'m> { } let stylex_namespace = has_stylex.then(|| allocator.allocate("$stylex")); + let stylex_theme = if has_stylex { collect_stylex_theme(module) } else { None }; let has_foreign = module .declarations @@ -367,6 +379,7 @@ impl<'m> Generator<'m> { external_named_imports, external_references, stylex_namespace, + stylex_theme, foreign_import, runtime_namespace, lazy_global_names, @@ -381,8 +394,12 @@ impl<'m> Generator<'m> { let mut tree = Tree::new(&allocator); let mut writer = Writer::new(&allocator); let initializer_cycle = { - let mut renderer = - ModuleRenderer { generator: &self, tree: &mut tree, writer: &mut writer }; + let mut renderer = ModuleRenderer { + generator: &self, + tree: &mut tree, + writer: &mut writer, + file: OutputFile::Index, + }; render_imports(&mut renderer); render_constructors(&mut renderer); render_source_functions(&mut renderer)?; @@ -392,6 +409,25 @@ impl<'m> Generator<'m> { render_exports(&mut renderer); initializer_cycle }; + let stylex_theme_source = if self.stylex_theme.is_some() { + let mut writer = Writer::new(&allocator); + let mut renderer = ModuleRenderer { + generator: &self, + tree: &mut tree, + writer: &mut writer, + file: OutputFile::StyleXTheme, + }; + render_imports(&mut renderer); + let initializer_cycle = render_value_declarations(&mut renderer)?; + debug_assert!( + initializer_cycle.is_empty(), + "invariant violated: statically evaluable StyleX theme values form a cycle" + ); + render_exports(&mut renderer); + Some(writer.finish()) + } else { + None + }; let dependencies = self.module.dependencies.iter().map(|dependency| dependency.file_id); let dependencies = dependencies.collect_vec(); @@ -409,6 +445,7 @@ impl<'m> Generator<'m> { dependencies, diagnostics, self.foreign_import.as_ref().map(|foreign_import| foreign_import.kind), + stylex_theme_source, requires_runtime, )) } @@ -424,7 +461,19 @@ impl<'m> Generator<'m> { } fn render_imports(renderer: &mut ModuleRenderer<'_, '_, '_>) { - let ModuleRenderer { generator, writer, .. } = renderer; + let ModuleRenderer { generator, writer, file, .. } = renderer; + if *file == OutputFile::StyleXTheme { + let theme = generator + .stylex_theme + .as_ref() + .expect("invariant violated: StyleX theme file rendered without theme definitions"); + render_named_imports(generator, writer, theme.imports.iter().copied()); + if let Some(namespace) = &generator.stylex_namespace { + writer.import_namespace(namespace, "@stylexjs/stylex"); + } + writer.blank(); + return; + } let mut files = generator .external_module_namespaces .keys() @@ -439,34 +488,19 @@ fn render_imports(renderer: &mut ModuleRenderer<'_, '_, '_>) { let path = format!("../{}", module_filename(module_name)); writer.import_namespace(namespace, &path); } - let mut named_files = generator - .external_named_imports - .keys() - .map(|global| global_file(*global)) - .collect::>() - .into_iter() - .collect_vec(); - named_files.sort_by_key(|file_id| generator.module_dependency(*file_id).module_name.as_str()); - for file_id in named_files { - let dependency = generator.module_dependency(file_id); - let path = format!("../{}", module_filename(&dependency.module_name)); - let bindings = generator - .external_named_imports + render_named_imports(generator, writer, generator.external_named_imports.keys().copied()); + if let Some(theme) = &generator.stylex_theme { + let definitions = generator + .module + .declarations .iter() - .filter(|(global, _)| global_file(**global) == file_id) - .map(|(global, local)| { - let imported = generator - .external_references - .iter() - .find(|reference| reference.id == *global) - .expect("invariant violated: named import has no external reference") - .item_name - .as_str(); - (imported, local.as_str()) + .filter(|declaration| theme.definitions.contains(&declaration.global.id)) + .map(|declaration| { + let local = generator.global_name(declaration.global.id); + (declaration.global.item_name.as_str(), local) }); - let mut bindings = bindings.collect_vec(); - bindings.sort_unstable(); - writer.import_named(&bindings, &path); + let definitions = definitions.collect_vec(); + writer.import_named(&definitions, &format!("./{STYLEX_THEME_FILENAME}")); } if let Some(namespace) = &generator.stylex_namespace { writer.import_namespace(namespace, "@stylexjs/stylex"); @@ -489,8 +523,48 @@ fn render_imports(renderer: &mut ModuleRenderer<'_, '_, '_>) { } } +/// StyleX resolves an imported theme value only when the import names the defining module's +/// theme file. Other values are imported from the module itself, which re-exports its theme. +fn render_named_imports( + generator: &Generator<'_>, + writer: &mut Writer<'_>, + globals: impl Iterator, +) { + let mut groups = FxHashMap::<_, Vec<_>>::default(); + for global in globals { + let local = generator + .external_named_imports + .get(&global) + .expect("invariant violated: imported StyleX value has no named import"); + let imported = generator + .external_references + .iter() + .find(|reference| reference.id == global) + .expect("invariant violated: named import has no external reference") + .item_name + .as_str(); + let theme = generator.module.stylex_theme_imports.contains(&global); + let group = groups.entry((global_file(global), theme)).or_default(); + group.push((imported, local.as_str())); + } + let mut groups = groups.into_iter().collect_vec(); + groups.sort_by_key(|&((file_id, theme), _)| { + (generator.module_dependency(file_id).module_name.as_str(), theme) + }); + for ((file_id, theme), mut bindings) in groups { + let module_name = &generator.module_dependency(file_id).module_name; + let filename = if theme { + stylex_theme_module_filename(module_name) + } else { + module_filename(module_name) + }; + bindings.sort_unstable(); + writer.import_named(&bindings, &format!("../{filename}")); + } +} + fn render_constructors(renderer: &mut ModuleRenderer<'_, '_, '_>) { - let ModuleRenderer { generator, tree, writer } = renderer; + let ModuleRenderer { generator, tree, writer, .. } = renderer; let mut rendered = false; for declaration in generator.module.declarations.iter() { let DeclarationKind::Constructor { arity } = declaration.kind else { @@ -1033,7 +1107,7 @@ impl Generator<'_> { } fn render_foreign_declarations(renderer: &mut ModuleRenderer<'_, '_, '_>) { - let ModuleRenderer { generator, tree, writer } = renderer; + let ModuleRenderer { generator, tree, writer, .. } = renderer; let Some(foreign_import) = &generator.foreign_import else { return; }; @@ -1103,7 +1177,9 @@ fn render_value_declarations( let DeclarationKind::Value(expression) = declaration.kind else { unreachable!("invariant violated: sorted JavaScript declaration is not a value") }; - if is_abstraction(&generator.module.storage[expression].kind) { + if is_abstraction(&generator.module.storage[expression].kind) + || !renderer.renders_value(declaration) + { continue; } @@ -1113,7 +1189,7 @@ fn render_value_declarations( } let name = generator.global_name(declaration.global.id); - let exported = generator.declaration_is_inline_exported(declaration); + let exported = renderer.declaration_is_inline_exported(declaration); if cyclic { initializer_cycle.push(declaration.global.id); @@ -3237,31 +3313,70 @@ fn sorted_value_declarations<'m>(generator: &'m Generator<'_>) -> Vec<(&'m Decla } fn render_exports(renderer: &mut ModuleRenderer<'_, '_, '_>) { - let ModuleRenderer { generator, writer, .. } = renderer; let mut rendered = false; - for declaration in generator.module.declarations.iter() { - if !declaration.exported { - continue; - } - let local = generator.global_name(declaration.global.id); - if local == declaration.global.item_name { + for declaration in renderer.generator.module.declarations.iter() { + if !renderer.exports(declaration) || renderer.declaration_is_inline_exported(declaration) { continue; } - writer.export(local, &declaration.global.item_name); + let local = renderer.generator.global_name(declaration.global.id); + renderer.writer.export(local, &declaration.global.item_name); rendered = true; } - for exports in generator.module.surface.indirect.iter() { - let specifiers = exports.globals.iter().map(|global| global.item_name.to_string()); - let dependency = generator.module_dependency(exports.file_id); - let path = format!("../{}", module_filename(&dependency.module_name)); - writer.re_export(specifiers.collect_vec(), &path); - rendered = true; + let ModuleRenderer { generator, writer, file, .. } = renderer; + if *file == OutputFile::Index { + if let Some(theme) = &generator.stylex_theme { + let definitions = generator + .module + .declarations + .iter() + .filter(|declaration| theme.definitions.contains(&declaration.global.id)) + .map(|declaration| declaration.global.item_name.to_string()); + writer.re_export(definitions.collect_vec(), &format!("./{STYLEX_THEME_FILENAME}")); + rendered = true; + } + for exports in generator.module.surface.indirect.iter() { + let specifiers = exports.globals.iter().map(|global| global.item_name.to_string()); + let dependency = generator.module_dependency(exports.file_id); + let path = format!("../{}", module_filename(&dependency.module_name)); + writer.re_export(specifiers.collect_vec(), &path); + rendered = true; + } } if rendered { writer.blank(); } } +impl ModuleRenderer<'_, '_, '_> { + /// Whether this file renders the declaration, if it is a value: a module's theme definitions + /// render only in its theme file, which renders nothing else except the values they read. + fn renders_value(&self, declaration: &Declaration) -> bool { + let id = declaration.global.id; + match (self.file, &self.generator.stylex_theme) { + (OutputFile::Index, None) => true, + (OutputFile::Index, Some(theme)) => !theme.definitions.contains(&id), + (OutputFile::StyleXTheme, Some(theme)) => theme.declarations.contains(&id), + (OutputFile::StyleXTheme, None) => false, + } + } + + /// A theme file exports only the theme definitions; the values they read stay private to it, + /// while the module's own file re-exports the definitions. + fn exports(&self, declaration: &Declaration) -> bool { + match (self.file, &self.generator.stylex_theme) { + (OutputFile::StyleXTheme, Some(theme)) => { + theme.definitions.contains(&declaration.global.id) + } + _ => declaration.exported && self.renders_value(declaration), + } + } + + fn declaration_is_inline_exported(&self, declaration: &Declaration) -> bool { + let local = self.generator.global_name(declaration.global.id); + self.exports(declaration) && local == declaration.global.item_name + } +} + impl Generator<'_> { fn declaration_is_inline_exported(&self, declaration: &Declaration) -> bool { let local = self.global_name(declaration.global.id); diff --git a/compiler-backend/javascript/src/convert/generator/functional/render/stylex.rs b/compiler-backend/javascript/src/convert/generator/functional/render/stylex.rs index 919726b71..2719823fb 100644 --- a/compiler-backend/javascript/src/convert/generator/functional/render/stylex.rs +++ b/compiler-backend/javascript/src/convert/generator/functional/render/stylex.rs @@ -64,6 +64,63 @@ pub(super) fn collect_stylex_references(module: &Module) -> Vec { references } +/// The declarations rendered into a module's StyleX theme file. +/// +/// StyleX evaluates every import of a theme file as a theme value without reading the file, so +/// the same-module values that the definitions read are rendered again in the theme file rather +/// than imported from it. These values are statically evaluable data, so duplicating them does not +/// change their meaning. +pub(super) struct StyleXTheme { + /// Exported `defineVars`, `defineConsts`, and `defineMarker` values. + pub(super) definitions: FxHashSet, + /// The definitions and the same-module values that they read. + pub(super) declarations: FxHashSet, + /// Other modules' values that the declarations read. + pub(super) imports: FxHashSet, +} + +pub(super) fn collect_stylex_theme(module: &Module) -> Option { + let mut values = FxHashMap::default(); + let mut definitions = FxHashSet::default(); + for declaration in module.declarations.iter() { + let DeclarationKind::Value(expression) = declaration.kind else { continue }; + values.insert(declaration.global.id, expression); + if declaration.exported + && let ExpressionKind::StyleX(StyleXExpression::Call { + target: StyleXCallTarget::Root(call), + .. + }) = module.storage[expression].kind + && call.defines_theme_value() + { + definitions.insert(declaration.global.id); + } + } + if definitions.is_empty() { + return None; + } + let mut declarations = FxHashSet::default(); + let mut imports = FxHashSet::default(); + let mut pending = definitions.iter().copied().collect::>(); + while let Some(id) = pending.pop() { + if !declarations.insert(id) { + continue; + } + let expression = values + .get(&id) + .expect("invariant violated: StyleX theme definition reads a non-value declaration"); + let mut globals = FxHashSet::default(); + super::collect_expression_globals(module, *expression, true, &mut globals); + for global in globals { + if global_file(global) == module.file_id { + pending.push(global); + } else { + imports.insert(global); + } + } + } + Some(StyleXTheme { definitions, declarations, imports }) +} + fn global_file(id: GlobalId) -> files::FileId { match id { GlobalId::Term(file_id, _) | GlobalId::Generated(file_id, _) => file_id, diff --git a/compiler-backend/javascript/src/lib.rs b/compiler-backend/javascript/src/lib.rs index fca3723e0..40cd43228 100644 --- a/compiler-backend/javascript/src/lib.rs +++ b/compiler-backend/javascript/src/lib.rs @@ -15,6 +15,7 @@ pub use convert::convert_module; pub use error::{ModuleDiagnostic, ModuleError, ModuleResult, UnsupportedState}; pub use module::{ Module, foreign_module_filename, module_filename, runtime_filename, runtime_source, + stylex_theme_module_filename, }; use building_types::QueryResult; diff --git a/compiler-backend/javascript/src/module.rs b/compiler-backend/javascript/src/module.rs index 87d54c5a8..2bebf27b0 100644 --- a/compiler-backend/javascript/src/module.rs +++ b/compiler-backend/javascript/src/module.rs @@ -12,6 +12,7 @@ pub struct Module { dependencies: Arc<[FileId]>, diagnostics: Arc<[ModuleDiagnostic]>, foreign_kind: Option, + stylex_theme_source: Option>, requires_runtime: bool, } @@ -23,6 +24,7 @@ impl Module { dependencies: Vec, diagnostics: Vec, foreign_kind: Option, + stylex_theme_source: Option, requires_runtime: bool, ) -> Module { Module { @@ -32,6 +34,7 @@ impl Module { dependencies: dependencies.into(), diagnostics: diagnostics.into(), foreign_kind, + stylex_theme_source: stylex_theme_source.map(Arc::from), requires_runtime, } } @@ -64,6 +67,11 @@ impl Module { self.foreign_kind } + /// The module's StyleX theme definitions, written to [`stylex_theme_module_filename`]. + pub fn stylex_theme_source(&self) -> Option<&str> { + self.stylex_theme_source.as_deref() + } + pub fn requires_runtime(&self) -> bool { self.requires_runtime } @@ -81,6 +89,14 @@ pub fn module_filename(module_name: &str) -> String { format!("{module_name}/index.js") } +/// StyleX accepts `defineVars`, `defineConsts`, and `defineMarker` only in files named +/// `*.stylex.js`, which its default module resolution recognizes without configuration. +pub fn stylex_theme_module_filename(module_name: &str) -> String { + format!("{module_name}/{STYLEX_THEME_FILENAME}") +} + +pub(crate) const STYLEX_THEME_FILENAME: &str = "index.stylex.js"; + pub fn foreign_module_filename(module_name: &str, kind: ForeignSourceKind) -> String { format!("{module_name}/foreign.{}", kind.extension()) } diff --git a/compiler-executable/iris-build/src/compile.rs b/compiler-executable/iris-build/src/compile.rs index 1b136da57..ec7456df0 100644 --- a/compiler-executable/iris-build/src/compile.rs +++ b/compiler-executable/iris-build/src/compile.rs @@ -484,6 +484,13 @@ fn write_module( return ModuleWrite { outputs: Vec::new(), result: Err(error.into()) }; } let mut outputs = vec![output_path]; + if let Some(source) = module.stylex_theme_source() { + let output_path = output.join(javascript::stylex_theme_module_filename(module.name())); + if let Err(error) = write_if_changed(&output_path, source.as_bytes()) { + return ModuleWrite { outputs, result: Err(error.into()) }; + } + outputs.push(output_path); + } if let Some(kind) = module.foreign_kind() { let output_path = output.join(javascript::foreign_module_filename(module.name(), kind)); let foreign = compilation diff --git a/compiler-executable/iris-cli/skills/stylex/SKILL.md b/compiler-executable/iris-cli/skills/stylex/SKILL.md index d014251cf..9d0bbe4c1 100644 --- a/compiler-executable/iris-cli/skills/stylex/SKILL.md +++ b/compiler-executable/iris-cli/skills/stylex/SKILL.md @@ -50,7 +50,7 @@ import Iris.StyleX.Types as Types | `stylex.firstThatWorks('sticky', 'fixed')` | `StyleX.firstThatWorks [ "sticky", "fixed" ]` | | `stylex.types.color('red')` | `Types.color "red"` | | `stylex.keyframes`, `defineVars`, `defineConsts`, `createTheme`, `viewTransitionClass`, `positionTry`, `attrs` | The same name under `StyleX` | -| `tokens.stylex.js` exporting `defineVars` or `defineConsts` | An exported top-level value in any module, with the bundler setting below | +| `tokens.stylex.js` exporting `defineVars` or `defineConsts` | An exported top-level value in any module; Iris writes it to the module's theme file | - Quote record labels that are not PureScript identifiers: `":hover"`, `"::placeholder"`, `"@media (min-width: 768px)"`, `"WebkitBackdropFilter"`. @@ -155,23 +155,18 @@ as well as the Iris build. Follow StyleX's installation guide for the bundler, then add what Iris output needs: - Generated modules import `@stylexjs/stylex`, so the project depends on it at runtime. -- Iris writes each module to `output//index.js`, but the plugin accepts `defineVars`, - `defineConsts`, `defineMarker`, and `createTheme` only in theme files, `*.stylex.js` by default. - Mark Iris output as theme files; `@stylexjs/unplugin` forwards this option to the Babel plugin: - - ```js - unstable_moduleResolution: { - type: "commonJS", - rootDir: process.cwd(), - themeFileExtension: "index", - }, - ``` - - Without it, those calls fail with `Unable to generate hash` or `Only static values are allowed`, - and imports between modules in static calls fail with `Could not resolve the path to the - imported file`. When calling the Babel plugin yourself, pass real file paths: the plugin - resolves imports through symlinks, so a symlinked filename gives a variable a different hash in - the module that imports it. +- Iris writes each module to `output//index.js`. The plugin accepts `defineVars`, + `defineConsts`, and `defineMarker` only in theme files named `*.stylex.js`, so Iris writes a + module's exported definitions to `output//index.stylex.js`, which `index.js` + re-exports. Generated modules import these values from the theme file, which the plugin's + default module resolution follows. `@stylexjs/unplugin` needs no resolution options; when + calling the Babel plugin yourself, pass `unstable_moduleResolution: { type: "commonJS", + rootDir }`, since the plugin cannot hash these definitions without it. Remove any + `themeFileExtension: "index"` setting from older Iris versions: it no longer matches the + generated files. +- When calling the Babel plugin yourself, also transform each `index.stylex.js` and pass real + file paths: the plugin resolves imports through symlinks, so a symlinked filename gives a + variable a different hash in the module that imports it. - Run `iris watch` beside the dev server, with the same `--output` the bundler imports from. - Vite may miss `@stylexjs/stylex` in its first scan of generated modules; add it to `optimizeDeps.include` if the dev server reloads on first navigation. diff --git a/compiler-executable/iris-watch-query/Cargo.toml b/compiler-executable/iris-watch-query/Cargo.toml index 612b5737d..745cba242 100644 --- a/compiler-executable/iris-watch-query/Cargo.toml +++ b/compiler-executable/iris-watch-query/Cargo.toml @@ -14,6 +14,7 @@ iris-analysis = { version = "0.1.0", path = "../../compiler-services/iris-analys iris-build = { version = "0.1.0", path = "../iris-build" } iris-diagnostics = { version = "0.1.0", path = "../../compiler-services/iris-diagnostics" } iris-watch-server = { version = "0.1.0", path = "../iris-watch-server" } +javascript = { version = "0.1.0", path = "../../compiler-backend/javascript" } lsp-types.workspace = true serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" diff --git a/compiler-executable/iris-watch-query/src/lib.rs b/compiler-executable/iris-watch-query/src/lib.rs index 2f114a577..a4a4044e2 100644 --- a/compiler-executable/iris-watch-query/src/lib.rs +++ b/compiler-executable/iris-watch-query/src/lib.rs @@ -164,6 +164,7 @@ impl DiagnosticSeverity { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct JavascriptAnswer { pub source: String, + pub stylex_theme_source: Option, } /// Serializes an answer for the protocol. @@ -292,9 +293,16 @@ pub fn render(query: &Query, value: Value) -> Result }); diagnostics.collect::>().join("\n") } - Query::Javascript { .. } => { - let JavascriptAnswer { source } = serde_json::from_value(value)?; - source + Query::Javascript { name } => { + let JavascriptAnswer { source, stylex_theme_source } = serde_json::from_value(value)?; + match stylex_theme_source { + Some(stylex_theme_source) => { + let filename = javascript::module_filename(name); + let theme_filename = javascript::stylex_theme_module_filename(name); + format!("// {filename}\n{source}\n// {theme_filename}\n{stylex_theme_source}") + } + None => source, + } } }; Ok(text) diff --git a/compiler-executable/iris-watch-query/src/lookup.rs b/compiler-executable/iris-watch-query/src/lookup.rs index 649b6175d..6f8ab3e8e 100644 --- a/compiler-executable/iris-watch-query/src/lookup.rs +++ b/compiler-executable/iris-watch-query/src/lookup.rs @@ -221,7 +221,10 @@ pub(crate) fn javascript( ))); } match context.engine.javascript(file_id)? { - Ok(module) => Ok(JavascriptAnswer { source: module.source().to_string() }), + Ok(module) => Ok(JavascriptAnswer { + source: module.source().to_string(), + stylex_theme_source: module.stylex_theme_source().map(str::to_string), + }), Err(_) => Err(QueryFailure::Failed(format!("no JavaScript was generated for {name}"))), } } diff --git a/tests-e2e/tests/stylex.rs b/tests-e2e/tests/stylex.rs index dbf475256..81a31a49e 100644 --- a/tests-e2e/tests/stylex.rs +++ b/tests-e2e/tests/stylex.rs @@ -19,19 +19,25 @@ package: ); workspace.write( "src/Tokens.purs", - r#"module Tokens (await, constants, rowMarker, variables) where + r#"module Tokens (await, constants, gapProps, rowMarker, variables) where import Iris.StyleX as StyleX await :: StyleX.Style await = StyleX.defaultMarker -constants = StyleX.defineConsts { spacing: "13px", small: "@media (max-width: 600px)" } +gap = "21px" + +constants = StyleX.defineConsts { spacing: "13px", small: "@media (max-width: 600px)", gap } variables = StyleX.defineVars { accent: "blue" } rowMarker :: StyleX.Marker rowMarker = StyleX.defineMarker + +gapStyles = StyleX.create { root: { margin: gap, color: variables.accent } } + +gapProps = StyleX.props gapStyles.root "#, ); workspace.write( @@ -106,8 +112,8 @@ conditionalProps enabled = StyleX.props assert_success(&output); let generated = workspace.read("output/Main/index.js"); assert!( - generated - .contains("import { \"await\" as Tokens_await, constants as Tokens_constants, rowMarker as Tokens_rowMarker, variables as Tokens_variables }"), + generated.contains("import { \"await\" as Tokens_await } from \"../Tokens/index.js\";") + && generated.contains("import { constants as Tokens_constants, rowMarker as Tokens_rowMarker, variables as Tokens_variables } from \"../Tokens/index.stylex.js\";"), "unexpected generated JavaScript:\n{generated}" ); diff --git a/tests-e2e/tools/verify-stylex.mjs b/tests-e2e/tools/verify-stylex.mjs index 453ac039d..d3cdf17d4 100644 --- a/tests-e2e/tools/verify-stylex.mjs +++ b/tests-e2e/tools/verify-stylex.mjs @@ -12,12 +12,12 @@ import * as stylex from "@stylexjs/stylex"; // The plugin resolves imports to real paths and hashes them relative to `rootDir`, so a // symlinked temporary directory (macOS `/var`) must be canonical on both sides. const outputRoot = fs.realpathSync(path.resolve(process.argv[2])); -const modules = ["Tokens", "Main"]; +const files = ["Tokens/index.stylex.js", "Tokens/index.js", "Main/index.js"]; const styles = []; const transformed = new Map(); -for (const moduleName of modules) { - const filename = path.join(outputRoot, moduleName, "index.js"); +for (const file of files) { + const filename = path.join(outputRoot, file); const source = fs.readFileSync(filename, "utf8"); const result = transformSync(source, { filename, @@ -28,11 +28,8 @@ for (const moduleName of modules) { stylexPlugin, { dev: false, - unstable_moduleResolution: { - type: "commonJS", - rootDir: outputRoot, - themeFileExtension: "index", - }, + // The default that `@stylexjs/unplugin` passes; theme files need no further setup. + unstable_moduleResolution: { type: "commonJS", rootDir: outputRoot }, }, ], ], @@ -49,7 +46,7 @@ for (const moduleName of modules) { "viewTransitionClass", ]; if (staticCalls.some((call) => result.code.includes(`$stylex.${call}(`))) { - throw new Error(`${moduleName} retains uncompiled static StyleX calls`); + throw new Error(`${file} retains uncompiled static StyleX calls`); } styles.push(...result.metadata.stylex); transformed.set(filename, result.code); @@ -73,6 +70,7 @@ for (const expected of [ "padding:8px", "@media (max-width: 600px)", "padding:4px", + "margin:21px", ]) { if (!css.includes(expected)) { throw new Error(`StyleX CSS does not contain ${JSON.stringify(expected)}:\n${css}`); @@ -81,8 +79,12 @@ for (const expected of [ // A cross-module reference must hash to the variable its defining module declares. const accent = css.match(/(--[\w-]+):blue/)?.[1]; -if (accent === undefined || !css.includes(`border-color:var(${accent})`)) { - throw new Error(`border-color does not use the variable Tokens defines:\n${css}`); +if ( + accent === undefined || + !css.includes(`border-color:var(${accent})`) || + !css.includes(`{color:var(${accent})`) +) { + throw new Error(`Styles do not use the variable Tokens defines:\n${css}`); } for (const [filename, code] of transformed) { @@ -101,7 +103,14 @@ registerHooks({ const tokens = await import(pathToFileURL(path.join(outputRoot, "Tokens/index.js"))); const main = await import(pathToFileURL(path.join(outputRoot, "Main/index.js"))); +const theme = await import(pathToFileURL(path.join(outputRoot, "Tokens/index.stylex.js"))); +assert.equal(tokens.variables, theme.variables); const classNames = (value) => new Set(value.trim().split(/\s+/)); +const gapClasses = classNames(tokens.gapProps.className); +assert.equal(gapClasses.size, 2); +for (const gapClass of gapClasses) { + assert.ok(css.includes(`.${gapClass}`)); +} const expected = classNames(stylex.props(main.styles.row, tokens.rowMarker).className); const markerClass = stylex.props(tokens.rowMarker).className; assert.ok(markerClass.length > 0); diff --git a/tests-integration/fixtures/compiler/1788370980_style_x_extended_intrinsics/output/Main/index.js b/tests-integration/fixtures/compiler/1788370980_style_x_extended_intrinsics/output/Main/index.js index 3de04296b..913c1d0a8 100644 --- a/tests-integration/fixtures/compiler/1788370980_style_x_extended_intrinsics/output/Main/index.js +++ b/tests-integration/fixtures/compiler/1788370980_style_x_extended_intrinsics/output/Main/index.js @@ -1,28 +1,9 @@ +import { constants, variables, rowMarker } from "./index.stylex.js"; import * as $stylex from "@stylexjs/stylex"; -export const constants = $stylex.defineConsts({ - compact: "@media (max-width: 40rem)", - columns: 12 | 0 -}); -export const variables = $stylex.defineVars({ - accent: $stylex.types.color("royalblue"), - angle: $stylex.types.angle("45deg"), - image: $stylex.types.image("linear-gradient(red, blue)"), - integer: $stylex.types.integer(1 | 0), - length: $stylex.types.length("8px"), - lengthPercentage: $stylex.types.lengthPercentage("10%"), - number: $stylex.types.number(.5), - percentage: $stylex.types.percentage("50%"), - resolution: $stylex.types.resolution("2dppx"), - time: $stylex.types.time("200ms"), - transformFunction: $stylex.types.transformFunction("scale(1)"), - transformList: $stylex.types.transformList("scale(1) rotate(2deg)"), - url: $stylex.types.url("url(image.png)") -}); export const theme = $stylex.createTheme(variables, { accent: $stylex.types.color("white"), length: $stylex.types.length("12px") }); -export const rowMarker = $stylex.defineMarker(); export const styles = $stylex.create({ root: { color: { default: "blue", @@ -51,3 +32,4 @@ export const fallback = $stylex.positionTry({ positionArea: "block-start", margin: 8 | 0 }); +export { constants, variables, rowMarker } from "./index.stylex.js"; diff --git a/tests-integration/fixtures/compiler/1788370980_style_x_extended_intrinsics/output/Main/index.stylex.js b/tests-integration/fixtures/compiler/1788370980_style_x_extended_intrinsics/output/Main/index.stylex.js new file mode 100644 index 000000000..7e5ef6f34 --- /dev/null +++ b/tests-integration/fixtures/compiler/1788370980_style_x_extended_intrinsics/output/Main/index.stylex.js @@ -0,0 +1,21 @@ +import * as $stylex from "@stylexjs/stylex"; +export const constants = $stylex.defineConsts({ + compact: "@media (max-width: 40rem)", + columns: 12 | 0 +}); +export const variables = $stylex.defineVars({ + accent: $stylex.types.color("royalblue"), + angle: $stylex.types.angle("45deg"), + image: $stylex.types.image("linear-gradient(red, blue)"), + integer: $stylex.types.integer(1 | 0), + length: $stylex.types.length("8px"), + lengthPercentage: $stylex.types.lengthPercentage("10%"), + number: $stylex.types.number(.5), + percentage: $stylex.types.percentage("50%"), + resolution: $stylex.types.resolution("2dppx"), + time: $stylex.types.time("200ms"), + transformFunction: $stylex.types.transformFunction("scale(1)"), + transformList: $stylex.types.transformList("scale(1) rotate(2deg)"), + url: $stylex.types.url("url(image.png)") +}); +export const rowMarker = $stylex.defineMarker(); diff --git a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.checking.snap b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.checking.snap index 34f64006c..0c367db10 100644 --- a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.checking.snap +++ b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.checking.snap @@ -1,8 +1,10 @@ --- source: tests-integration/src/fixtures.rs +assertion_line: 311 --- Terms theme :: Iris.StyleX.Style styles :: { root :: Iris.StyleX.Style } +awaitProps :: { className :: Prim.String } Types diff --git a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.functional.snap b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.functional.snap index 3e82b4993..87a966567 100644 --- a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.functional.snap +++ b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.functional.snap @@ -1,8 +1,15 @@ --- source: tests-integration/src/fixtures.rs +assertion_line: 314 --- @export theme = stylex.createTheme variables { accent: "white" } @export styles = stylex.create { - root: { color: stylex.conditionalValue "blue" [stylex.when.ancestor(":hover", rowMarker, "red")] } + root: + { + color: stylex.conditionalValue "blue" [stylex.when.ancestor(":hover", rowMarker, "red")], + padding: spacing.gap + } } + +@export awaitProps = stylex.props await diff --git a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.purs b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.purs index 206de808d..b6cec2666 100644 --- a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.purs +++ b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.purs @@ -2,7 +2,7 @@ module Main where import Iris.StyleX as StyleX import Iris.StyleX.When as When -import Tokens (rowMarker, variables) +import Tokens (await, rowMarker, spacing, variables) theme = StyleX.createTheme variables { accent: "white" } @@ -10,5 +10,8 @@ styles = StyleX.create { root: { color: StyleX.conditionalValue "blue" [ When.ancestorMarker ":hover" rowMarker "red" ] + , padding: spacing.gap } } + +awaitProps = StyleX.props await diff --git a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.semantic.snap b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.semantic.snap index 49d65d24a..c19e57bfb 100644 --- a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.semantic.snap +++ b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Main.semantic.snap @@ -1,5 +1,6 @@ --- source: tests-integration/src/fixtures.rs +assertion_line: 313 --- theme :: Iris.StyleX.Style theme = @@ -10,4 +11,8 @@ theme = styles :: { root :: Iris.StyleX.Style } styles = create {compileStyles {trivial} {compileStyleListConsTypeRecord {compileStyleListNilTypeRow} {trivial}}} - { root: { color: conditionalValue "blue" [ancestorMarker ":hover" rowMarker "red"] } } + { root: { color: conditionalValue "blue" + [ancestorMarker ":hover" rowMarker "red"], padding: spacing.gap } } + +awaitProps :: { className :: Prim.String } +awaitProps = props {propsInputStyleProps} await diff --git a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Tokens.purs b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Tokens.purs index 6c15b0c7b..a89695c0c 100644 --- a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Tokens.purs +++ b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/Tokens.purs @@ -1,8 +1,19 @@ -module Tokens (rowMarker, variables) where +module Tokens (await, gapProps, rowMarker, spacing, variables) where import Iris.StyleX as StyleX +await :: StyleX.Style +await = StyleX.defaultMarker + +gap = "21px" + +spacing = StyleX.defineConsts { gap } + variables = StyleX.defineVars { accent: "blue" } rowMarker :: StyleX.Marker rowMarker = StyleX.defineMarker + +gapStyles = StyleX.create { root: { margin: gap, color: variables.accent } } + +gapProps = StyleX.props gapStyles.root diff --git a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Main/index.js b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Main/index.js index 8936b4b9e..cc61e5d8f 100644 --- a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Main/index.js +++ b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Main/index.js @@ -1,7 +1,12 @@ -import { rowMarker as Tokens_rowMarker, variables as Tokens_variables } from "../Tokens/index.js"; +import { "await" as Tokens_await } from "../Tokens/index.js"; +import { rowMarker as Tokens_rowMarker, spacing as Tokens_spacing, variables as Tokens_variables } from "../Tokens/index.stylex.js"; import * as $stylex from "@stylexjs/stylex"; export const theme = $stylex.createTheme(Tokens_variables, { accent: "white" }); -export const styles = $stylex.create({ root: { color: { - default: "blue", - [$stylex.when.ancestor(":hover", Tokens_rowMarker)]: "red" -} } }); +export const styles = $stylex.create({ root: { + color: { + default: "blue", + [$stylex.when.ancestor(":hover", Tokens_rowMarker)]: "red" + }, + padding: Tokens_spacing.gap +} }); +export const awaitProps = $stylex.props(Tokens_await); diff --git a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Tokens/index.js b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Tokens/index.js index 90e9a2671..99e84dc10 100644 --- a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Tokens/index.js +++ b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Tokens/index.js @@ -1,3 +1,11 @@ +import { spacing, variables, rowMarker } from "./index.stylex.js"; import * as $stylex from "@stylexjs/stylex"; -export const variables = $stylex.defineVars({ accent: "blue" }); -export const rowMarker = $stylex.defineMarker(); +const $await = $stylex.defaultMarker(); +const gap = "21px"; +const gapStyles = $stylex.create({ root: { + margin: gap, + color: variables.accent +} }); +export const gapProps = $stylex.props(gapStyles.root); +export { $await as "await" }; +export { spacing, variables, rowMarker } from "./index.stylex.js"; diff --git a/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Tokens/index.stylex.js b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Tokens/index.stylex.js new file mode 100644 index 000000000..688a0db23 --- /dev/null +++ b/tests-integration/fixtures/compiler/1788371460_style_x_cross_module_values/output/Tokens/index.stylex.js @@ -0,0 +1,5 @@ +import * as $stylex from "@stylexjs/stylex"; +const gap = "21px"; +export const spacing = $stylex.defineConsts({ gap }); +export const variables = $stylex.defineVars({ accent: "blue" }); +export const rowMarker = $stylex.defineMarker(); diff --git a/tests-integration/fixtures/compiler/1788371940_style_x_theme_overrides/output/Main/index.js b/tests-integration/fixtures/compiler/1788371940_style_x_theme_overrides/output/Main/index.js index 5d621fa89..a6c4f0f7c 100644 --- a/tests-integration/fixtures/compiler/1788371940_style_x_theme_overrides/output/Main/index.js +++ b/tests-integration/fixtures/compiler/1788371940_style_x_theme_overrides/output/Main/index.js @@ -1,11 +1,6 @@ +import { variables, invalidInteger, invalidNumber, invalidColor } from "./index.stylex.js"; import * as $stylex from "@stylexjs/stylex"; -export const variables = $stylex.defineVars({ - accent: $stylex.types.color("blue"), - spacing: $stylex.types.length("8px") -}); export const valid = $stylex.createTheme(variables, { accent: $stylex.types.color("white") }); export const wrongType = $stylex.createTheme(variables, { accent: $stylex.types.length("12px") }); export const unknown = $stylex.createTheme(variables, { missing: $stylex.types.color("red") }); -export const invalidInteger = $stylex.defineVars({ value: $stylex.types.integer(true) }); -export const invalidNumber = $stylex.defineVars({ value: $stylex.types.number("1") }); -export const invalidColor = $stylex.defineVars({ value: $stylex.types.color(42 | 0) }); +export { variables, invalidInteger, invalidNumber, invalidColor } from "./index.stylex.js"; diff --git a/tests-integration/fixtures/compiler/1788371940_style_x_theme_overrides/output/Main/index.stylex.js b/tests-integration/fixtures/compiler/1788371940_style_x_theme_overrides/output/Main/index.stylex.js new file mode 100644 index 000000000..9296984f7 --- /dev/null +++ b/tests-integration/fixtures/compiler/1788371940_style_x_theme_overrides/output/Main/index.stylex.js @@ -0,0 +1,8 @@ +import * as $stylex from "@stylexjs/stylex"; +export const variables = $stylex.defineVars({ + accent: $stylex.types.color("blue"), + spacing: $stylex.types.length("8px") +}); +export const invalidInteger = $stylex.defineVars({ value: $stylex.types.integer(true) }); +export const invalidNumber = $stylex.defineVars({ value: $stylex.types.number("1") }); +export const invalidColor = $stylex.defineVars({ value: $stylex.types.color(42 | 0) }); diff --git a/tests-integration/fixtures/compiler/1791340800_stylex_marker_composition/output/Main/index.js b/tests-integration/fixtures/compiler/1791340800_stylex_marker_composition/output/Main/index.js index defc86a33..0aa974889 100644 --- a/tests-integration/fixtures/compiler/1791340800_stylex_marker_composition/output/Main/index.js +++ b/tests-integration/fixtures/compiler/1791340800_stylex_marker_composition/output/Main/index.js @@ -1,5 +1,6 @@ +import { rowMarker } from "./index.stylex.js"; import * as $stylex from "@stylexjs/stylex"; -export const rowMarker = $stylex.defineMarker(); export const styles = $stylex.create({ row: { color: "red" } }); export const rowProps = $stylex.props([styles.row, rowMarker]); export const rowAttrs = $stylex.attrs([rowMarker, styles.row]); +export { rowMarker } from "./index.stylex.js"; diff --git a/tests-integration/fixtures/compiler/1791340800_stylex_marker_composition/output/Main/index.stylex.js b/tests-integration/fixtures/compiler/1791340800_stylex_marker_composition/output/Main/index.stylex.js new file mode 100644 index 000000000..98cb887db --- /dev/null +++ b/tests-integration/fixtures/compiler/1791340800_stylex_marker_composition/output/Main/index.stylex.js @@ -0,0 +1,2 @@ +import * as $stylex from "@stylexjs/stylex"; +export const rowMarker = $stylex.defineMarker(); diff --git a/tests-integration/fixtures/compiler/1791340920_stylex_marker_style_in_marker_case/output/Main/index.js b/tests-integration/fixtures/compiler/1791340920_stylex_marker_style_in_marker_case/output/Main/index.js index c5e9940e3..f85f60f02 100644 --- a/tests-integration/fixtures/compiler/1791340920_stylex_marker_style_in_marker_case/output/Main/index.js +++ b/tests-integration/fixtures/compiler/1791340920_stylex_marker_style_in_marker_case/output/Main/index.js @@ -1,6 +1,7 @@ +import { rowMarker } from "./index.stylex.js"; import * as $stylex from "@stylexjs/stylex"; -export const rowMarker = $stylex.defineMarker(); export const styles = $stylex.create({ row: { color: { default: "blue", [$stylex.when.ancestor(":hover", rowMarker)]: "red" } } }); +export { rowMarker } from "./index.stylex.js"; diff --git a/tests-integration/fixtures/compiler/1791340920_stylex_marker_style_in_marker_case/output/Main/index.stylex.js b/tests-integration/fixtures/compiler/1791340920_stylex_marker_style_in_marker_case/output/Main/index.stylex.js new file mode 100644 index 000000000..98cb887db --- /dev/null +++ b/tests-integration/fixtures/compiler/1791340920_stylex_marker_style_in_marker_case/output/Main/index.stylex.js @@ -0,0 +1,2 @@ +import * as $stylex from "@stylexjs/stylex"; +export const rowMarker = $stylex.defineMarker(); diff --git a/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Main/index.js b/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Main/index.js index 6bf788fb1..530adc260 100644 --- a/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Main/index.js +++ b/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Main/index.js @@ -1,4 +1,4 @@ -import { breakpoints as Tokens_breakpoints } from "../Tokens/index.js"; +import { breakpoints as Tokens_breakpoints } from "../Tokens/index.stylex.js"; import * as $stylex from "@stylexjs/stylex"; export const print = "@media print"; export const styles = $stylex.create({ root: { padding: { diff --git a/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Tokens/index.js b/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Tokens/index.js index a7ccd99bb..227361386 100644 --- a/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Tokens/index.js +++ b/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Tokens/index.js @@ -1,2 +1,3 @@ +import { breakpoints } from "./index.stylex.js"; import * as $stylex from "@stylexjs/stylex"; -export const breakpoints = $stylex.defineConsts({ small: "@media (max-width: 600px)" }); +export { breakpoints } from "./index.stylex.js"; diff --git a/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Tokens/index.stylex.js b/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Tokens/index.stylex.js new file mode 100644 index 000000000..a7ccd99bb --- /dev/null +++ b/tests-integration/fixtures/compiler/1791347580_stylex_conditional_case/output/Tokens/index.stylex.js @@ -0,0 +1,2 @@ +import * as $stylex from "@stylexjs/stylex"; +export const breakpoints = $stylex.defineConsts({ small: "@media (max-width: 600px)" }); diff --git a/tests-integration/src/fixtures.rs b/tests-integration/src/fixtures.rs index edc048cae..5d1504a6b 100644 --- a/tests-integration/src/fixtures.rs +++ b/tests-integration/src/fixtures.rs @@ -66,6 +66,11 @@ impl JavaScriptModules { let output_parent = output_path.parent().expect("module filename has no parent"); std::fs::create_dir_all(output_parent)?; std::fs::write(output_path, module.source())?; + if let Some(source) = module.stylex_theme_source() { + let output_path = + output.join(javascript::stylex_theme_module_filename(module.name())); + std::fs::write(output_path, source)?; + } if let Some(kind) = module.foreign_kind() { let source_url = Url::parse(&files.path(module.file_id()))?; let source_path = source_url.to_file_path().map_err(|()| { @@ -322,6 +327,11 @@ pub fn compiler(path: &Path) -> FixtureResult { continue; } expected_paths.insert(PathBuf::from(module.filename())); + if module.stylex_theme_source().is_some() { + expected_paths.insert(PathBuf::from(javascript::stylex_theme_module_filename( + module.name(), + ))); + } if let Some(kind) = module.foreign_kind() { expected_paths.insert(PathBuf::from(javascript::foreign_module_filename( module.name(),