For the old version of this application, please see here.
This repository provides the pyaco
binary, a Node.js libary, and a Rust crate to help you deal with css stylesheet in a type safe way.
Generates code from any valid css file (this CLI has been tested against complex CSS files generated by Tailwind). Currently supports TypeScript, ReScript, Elm, and PureScript (Rust users: you can see below how to use the css!
macro).
Using cargo
:
cargo install --git https://github.com/scoville/tailwind-generator
Using npm
/yarn
:
npm install https://github.com/scoville/tailwind-generator
or
yarn add https://github.com/scoville/tailwind-generator
Not all platforms are currently supported officially (MacOS M1 / AArch64 for instance).
Nonetheless, for the time being and in order to make this tool as easy as possible to use, when a platform is not recognized the node native "binary" plus an alternative CLI facade in Node.js will be used instead. So all platforms that support Node should work now. Notice that a small performance degradation is to be expected.
Check the /npm
folder and the package.json
at the main path to get a better undertanding of how the package is being installed and run. Long story short, it uses Neon, a toolchain which allows creating a native Node module from the rust code. Behind the scenes, the node js implementation will call the Rust code that you can find in crates such as pyaco-validate.
You'll find the detailed explanation on how to run the compiled binary in the sections below. In order to run it in dev mode you'll have to replace pyaco
with cargo run
. Also, to get more logs in dev mode you need to add RUST_LOG=info
. So in dev mode, the pyaco validate
command would look like this:
RUST_LOG=info cargo run validate -c .styles.css -i './src/**/*.tsx' --capture-regex 'Cn\.c\(\s*\n?"([^"]*)",?\n?\s*\)'
-
Run
cargo build --release
for compiling the binaries on your architecture -
Build for a specific architecture, e.g. for Mac x86 if you are on an arm64 Mac
rustup target add x86_64-apple-darwin
cargo build --release --target x86_64-apple-darwin
- In order to build the binaries cross-platform you need to have an understanding of how a makefile works. Please note there is a Makefile in the main path of this repository. The command to run is
make release
; this will compile the node.js bindings with Neon and build the app cross platform using cargo for Mac and cross for Windows and Linux.
To get help:
pyaco generate --help
pyaco-generate
Generate code from a css input
USAGE:
pyaco generate [FLAGS] [OPTIONS] --input <input> --output-filename <output-filename> --lang <lang>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
-w, --watch Watch for changes in the provided css file and regenarate the code (doesn't
work with URL)
OPTIONS:
-i, --input <input>
CSS file path and/or URL to parse and generate code from
-l, --lang <lang>
Language used in generated code (elm|purescript|rescript|typescript|typescript-type-
1|typescript-type-2)
-o, --output-directory <output-directory> Directory for generated code [default: ./]
-f, --output-filename <output-filename>
Filename (without extension) used for the generated code
pyaco generate
uses env_logger under the hood, so you can prefix your command with RUST_LOG=info
for a more verbose output, the binary is silent by default.
Warning: in PureScript and Elm, the provided filename and directory path will be used as the module name, make sure they follow the name conventions and are capitalized. For example:
pyaco generate -i ./styles.css -l purescript -o ./Foo/Bar -f Baz
Will generate a ./Foo/Bar/Baz.purs
file that defines a module called Foo.Bar.Baz
.
Display the help message:
pyaco generate -h
Generates a TypeScript file called css.ts
in the generated
folder from the Tailwind CDN:
pyaco generate \
-i https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css \
-l typescript \
-f css \
-o generated
Same as above but generated from a local file:
pyaco generate \
-i ./styles.css \
-l typescript \
-f css \
-o generated
Same as above and regenerate code on CSS file change:
pyaco generate \
-i ./styles.css \
-l typescript \
-f css \
-o generated \
-w
Generates a PureScript file and displays logs:
RUST_LOG=info pyaco generate \
-i ./styles.css \
-l purescript \
-f Css
Warning: the -w|--watch
mode is still experimental and might contain some bugs (for instance the file is sometimes generated twice), use with care.
pyaco generate
offers three flavors for TypeScript code generation, let's see and compare the three solutions.
A simple generator for TypeScript, it exports an opaque type CssClass
, a join
function, and a set of CssClass
"objects":
import { join, textBlue100, rounded, border, borderBlue300 } from "./css.ts";
// ...
<div className={join([textBlue100, rounded, border, borderBlue300])}>
Hello
</div>;
Pros:
- Easy to use
- Very flexible
- Compatible with most TypeScript versions
- Safe, you can't pass any string to the
join
function - Autocompletion
Cons:
- Cost at runtime:
CssClass
are JavaScript objects that help ensuring type opacity - Cost at runtime: the array has to be joined into a string
- Imports can be verbose (unless you use
import * as ...
) - Not the "standard" class names,
h-full
becomeshFull
, etc...
This generator doesn't generate any runtime code apart from the join
function.
import { join } from "./css.ts";
// ...
<div className={join("text-blue-100", "rounded", "border", "border-blue-300")}>
Hello
</div>;
Pros:
- Easy to use
- Very flexible
- Compatible with most TypeScript versions
- Safe, you can't pass any string to the
tailwind
function - "Standard" class names
- Light import (you only need the
join
function) - Autocompletion
Cons:
- Cost at runtime: the classes must be "joined" into a string
This generator doesn't generate any runtime code apart from the css
function.
import { css } from "./css.ts";
// ...
<div className={css("text-blue-100 rounded border border-blue-300")}>
Hello
</div>;
Pros:
- Super easy to use
- Safe, you can't pass any string to the
tailwind
function - "Standard" class names
- Light import (you only need the
css
function) - No runtime cost at all
- Partial support for autocompletion
Cons:
- Not as flexible as the 2 other generators
- Compatible with TypeScript > 4.1 only
- Type error can be hard to debug
- Doesn't accept multiple spaces (not necessarily a cons for some)
In PureScript, a CssClass
newtype is exported without its constructor which derives some very useful type classes like Semigroup
or Monoid
offering a lot of flexibility:
- Simple list of css classes:
[ rounded, borderRed100 ]
- Add a class conditionally:
[ if true then textBlue500 else textRed500 ] -- "text-blue-500"
- Add a class only if a condition is met, do nothing otherwise:
[ guard true textBlue500 ] -- "text-blue-500"
[ guard false rounded ] -- ""
- Handle Maybe, and other
Foldable
values:
[ rounded, fold Nothing ] -- "rounded"
[ rounded, fold $ Right wFull ] -- "rounded w-full"
let mClass = Just borderRed100 in
[ rounded, fold mClass ] -- "rounded border-red-100"
Example:
import Css (rounded, borderRed100, join)
css :: String
css = join [ rounded, borderRed100 ]
You can also take a look at this ppx if you want to skip the code generation step. Both approach (code generation and ppx) have pros and cons.
The ppx got deprecated.
In ReScript, 2 files are generated one that contains the code and an interface file.
Additionally to the class variables, 2 functions are exposed:
join
: takes a list ofcssClass
and returns a stringjoinOpt
: takes a list ofoption<cssClass>
and returns a string
open Css
<div className={join([textBlue100, rounded, border, borderBlue300])}>
{"Hello!"->React.string}
</div>
Since ReScript 9.1 we can safely coerce polymorphic variants to strings. This generator leverages this new feature.
It's lighter than the other ReScript generator, and it's possible to get class names autocompletion using the Tailwind IntelliSense plugin.
Example:
<div className={Css.join([#"text-blue-100", #rounded, #border, #"border-blue-300"])}>
{"Hello!"->React.string}
</div>
Additionally to the generated classes, you'll get 2 useful functions:
classes
: takes a list of css classes and returns anHtml.Attribute msg
that can be used with any html elementjoin
: performs a simpleList CssClass -> String
conversion when you need to compute a class name outside of an html element
import Css exposing (classes, textBlue100, rounded, border, borderBlue300);
view _model =
div [ classes [ textBlue100, rounded, border, borderBlue300 ] ]
[ text "Hello!" ]
Some languages allow for more flexibility using macros or another mechanism. Rust, Crystal, or the OCaml languages (Ocaml, ReasonML, and ReScript) are some of these languages, and pyaco
offers support for some of them.
ReScript users: this tool doesn't offer any other support than the generator (see above) yet, in the meantime you can take a look at this ppx.
In Rust, a pyaco.toml
file is required and must be located at the root of your crate. Its content is pretty simple (as of today) and should look like this:
[general]
input = "./styles.css" # or input = {path = "./styles.css"}
Notice that urls are also supported, which can come in handy when testing or developing your application as in that case no files are required:
[general]
input = {url = "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"}
If your config file is valid and the css can be found, you can now use the css!
macro:
use pyaco_macro::css;
// ...
let style = css!(" rounded border px-2 py-1");
// Notice that extra white spaces have been removed at compile time
assert_eq!(style, "rounded border px-2 py-1");
The css class names are validated and cleaned at compile time, duplicates are removed (a compiler warning is emitted if you use Rust nightly) and the whole macro call is replaced by the provided string itself.
Yew users: the css!
macro can be used instead of the classes!
one.
The pyaco validate
command will take a css input (path or URL) and a glob of files to validate. If a class is used in a file but not present in the css input an error is displayed.
pyaco validate
will not force you to change your workflow, nor will it generate files in your project. It's not a macro/ppx either.
Put simply, it's a specialized grep
that will read all the files you want to validate, check for the css class names, and exit. Since it's fast (less than 2 seconds to analyze more than 5000 files that all contained more than 600 lines of code on my pretty old machine, not even half a second on ~500 files projects), it can be integrated easily into your favorite CI tool.
This binary is still experimental as we need to test it out more on larger codebase in TypeScript first, also some much needed quality of life improvements are still being worked on (watch mode, whitelist, configuration file, etc...).
The API is very likely to change soon, please use with care.
When installed with npm
or yarn
you can execute the provided cli or alternatively use pyaco
just like any Node.js module:
import { generate, validate } from "pyaco";
pyaco.generate({
input: "...",
lang: "purescript",
outputDirectory: "...",
watch: false,
outputFilename: "...",
});
pyaco.validate(
{
cssInput: "...",
inputGlob: "...",
captureRegex: "...",
maxOpenedFiles: 128,
splitRegex: "...",
},
// The callback is required
() => {
console.log("All done");
},
);