React CSS
Compile-time "CSS in Haxe" library, allowing you to write your CSS in a per component basis, only include the ones you actually use, and generate a good ol' CSS file at compile time. No runtime CSS generation, no inline CSS.
Note that this lib is still in early alpha status. If you get errors from something that should work in your opinion, please let me know by opening an issue.
Installation
Only tested (for now) with latest Haxe version, and using react-next. This should work with haxe-react too, or only with minor adjustments (PR welcome).
Version >= 0.9.0 of css-types is needed, unless you're using plain string CSS in your components (see below).
Install latest release from haxelib:
haxelib install react-css
This lib is still on heavy developpement, you might want to use git version if latest haxelib release is outdated:
haxelib git react-css git@github.com:kLabz/haxe-react-css.git
# Or with https:
haxelib git react-css https://github.com/kLabz/haxe-react-css.git
Basic setup
First, make sure you include this lib in your hxml with -lib react-css.
css-types being an optional dependency, add it too if you intend to
use object declaration syntax.
Configuration is made with defines:
-D react.css.out=out/styles.css
-D react.css.base=base.css- Use
react.css.outdefine to set the ouput file for generated CSS react.css.basedefine is optional and points to a CSS file you want to include at the beginning of generated CSS file.
Other defines are available, see Advanced usage.
For convenience, I can add those to your import.hx when working with css-types:
import css.GlobalValue;
import css.GlobalValue.Var;
import react.ReactComponent;
import react.css.Stylesheet;
Usage
There are several ways to declare your components' styles:
External CSS file
Inline CSS as String inside metadata
Inline object declaration inside metadata, using css-types
Object declaration as a static field, using css-types; this is
the suggested way since it allows compile-time checks and completion
See below for details about each way. You can also find some examples in tests.
Once your component's styles are defined, you can use className field anywhere
in your component (or from outside, it's public static):
override function render():ReactFragment {
return jsx(<div className={className}>My component</div>);
}
Whatever way you are using to define your components' styles, you can use this in your CSS selectors to map to generated classnames:
-
_is a shortcut to reference current component's generated classname -
$SomeComponentwill resolve toSomeComponent's classname (SomeComponentneeds to be resolvable from current component, either by import or classic type resolution; fully qualified identifiers are not supported atm)
For example, this "plain CSS":
_ {
color: red;
}
_.selected + $SomeComponent {
color: yellow;
}
_$SomeComponent$OtherComponent {
color: blue;
}
Will generate:
.MyComponent-abc123 {
color: red;
}
.MyComponent-abc123.selected + .SomeComponent-def456 {
color: yellow;
}
.MyComponent-abc123.SomeComponent-def456.OtherComponent-ghi789 {
color: blue;
}
External CSS file
Using an external CSS file for your component is possible with
@:css(something.css) meta on your component. Path resolution will be relative
to your component. You can omit quotes around path if it's simple enough (no
dashes, etc.).
@:css(MyComponent.css)
class MyComponent extends ReactComponent {
override function render():ReactFragment {
return <div className={className}>My component</div>;
}
}
Plain CSS in metadata
You can also inline plain CSS inside the meta instead:
@:css('
_ {
color: orange;
}
')
class MyComponent extends ReactComponent {
override function render():ReactFragment {
return <div className={className}>My component</div>;
}
}
CSS object declaration in metadata
Using css-types, you can use an object declaration inside the metadata, similar to what can be done with material-ui's JSS. Note that completion won't work there.
@:css({
'_': {
color: 'red',
position: Relative,
fontSize: Inherit,
padding: 42
}
})
class MyComponent extends ReactComponent {
override function render():ReactFragment {
return <div className={className}>My component</div>;
}
}
CSS object declaration as static field
Last but definitely not least, you can declare a styles field with an object
declaration, using css-types:
@:css
class MyComponent extends ReactComponent {
static var styles:Stylesheet = {
'_': {
color: 'red',
textAlign: Center,
padding: 4,
margin: [0, "0.3em"]
},
'_::before': {
content: '"[before] "'
},
'$TestComponent + $OtherComponent': {
position: Relative,
'--blargh': 42,
zIndex: Var('blargh')
}
};
override function render():ReactFragment {
return <div className={className}>My component</div>;
}
}
Completion will work in there, for components identifiers and enum values. You
might want to use the import.hx example above for maximum convenience.
styles field will be removed during compilation, unless you explicitely tell
the build macro not to by adding a @:keep meta to it.
Advanced usage
Media queries
When using plain CSS, you can write your media queries like you would do in CSS. Using CSS object declaration as static field with css-types, you need to add a separate field for media queries:
@:css
class MyComponent extends ReactComponent {
static var styles:Stylesheet = {
'_': {
margin: '2em'
}
};
static var mediaQueries:Dynamic<Stylesheet> = {
'max-width: 799px': {
'_': {
margin: '0.5em'
}
}
};
override function render():ReactFragment {
return <div className={className}>My component</div>;
}
}
Handle components order in generated CSS
CSS declaration order matters, and even if your CSS is defined on a per-component basis, you can end up shadowing things you don't want to.
This should not happen a lot, but if you want to determine output order, add
@:css.priority(X) meta to your component(s), where X is any number. Higher
priority means that this component will be included later in output CSS.
Use a salt to change all hashes
Hashes are calculated from your components path (and avoid clashes). If you want
a new set of hashes for some reason, you can define a salt with react.css.salt
define:
-D react.css.salt=Aidohx7e
Remap react-css meta/defines/field names
If meta/defines/field names used by this lib don't suit your needs for some reason (clash with another lib, etc.), you can redefine them at compile time.
You can do that by redefining any of these in an init macro:
package react.css;
// ...
class ReactCSSMacro {
// ...
public static var META_NAME = ':css';
public static var PRIORITY_META_NAME = ':css.priority';
public static var STYLES_FIELD = 'styles';
public static var MEDIA_QUERIES_FIELD = 'mediaQueries';
public static var CLASSNAME_FIELD = 'className';
public static var BASE_DEFINE = 'react.css.base';
public static var OUT_DEFINE = 'react.css.out';
public static var SALT_DEFINE = 'react.css.salt';
public static var SOURCEMAP_DEFINE = 'react.css.sourcemap';
// ...
}
Usage with classnames lib
Using classnames lib, you can do this:
// I usually do this in import.hx
import classnames.ClassNames.fastNull as classNames;
// ...
override function render():ReactFragment {
var classes = classNames({
'$className': true,
'selected': state.selected,
'active': state.active,
});
return <div className={classes}>My component</div>;
}
Which will generate, with a state of {selected: true, active: false}:
<div class="MyComponent-abc123 selected">My component</div>
Limitations, roadmap
- I intend to try to generate some sourcemaps for generated CSS, see #1
- Currently, identifiers are not allowed as values, see #2
- Similarily, string interpolation is not applied in values, see #3
This lib is still in early alpha status. If you get errors from something that should work in your opinion, please let me know by opening an issue.