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.
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
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.out
define to set the output file for generated CSS react.css.base
define 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, you 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;
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 -
$SomeComponent
will resolve toSomeComponent
's classname (SomeComponent
needs 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;
}
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>;
}
}
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>;
}
}
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>;
}
}
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.
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>;
}
}
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.
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
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';
// ...
}
haxe-react-css/src/react/css/ReactCSSMacro.macro.hx
Lines 33 to 43 in 51bae1b
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>
- 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.