Skip to content

Css to * code generator and validator

Notifications You must be signed in to change notification settings

gaku-sei/pyaco

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

70 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pyaco (Polyglot tYpe sAfe Css tOolbox)

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.

Pyaco generate: a type safe CSS to "*" code generator, tailored for Tailwind

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).

Installation

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

Unsupported platforms

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.

Commands:

To get help:

pyaco generate --help
pyaco-generate

Generate code from a css input

USAGE:
    pyaco generate [OPTIONS] --input <INPUT> --output-filename <OUTPUT_FILENAME> --lang <LANG>

OPTIONS:
    -f, --output-filename <OUTPUT_FILENAME>
            Filename (without extension) used for the generated code

    -h, --help
            Print help information

    -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: ./]

    -w, --watch
            Watch for changes in the provided css file and regenarate the code (doesn't work with
            URL)

        --watch-debounce-duration <WATCH_DEBOUNCE_DURATION>
            Watch debounce duration (in ms), if files are validated twice after saving the css file,
            you should try to increase this value [default: 10]

Warning: the -w|--watch mode is still experimental and might contain some bugs, use with care.

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.

Examples

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

Generators

TypeScript

pyaco generate offers three flavors for TypeScript code generation, let's see and compare the three solutions.

TypeScript (typescript)

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 becomes hFull, etc...

TypeScript type 1 (typescript-type-1) (recommended)

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

TypeScript type 2 (typescript-type-2)

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)

PureScript (purescript)

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 ]

ReScript (rescript)

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 of cssClass and returns a string
  • joinOpt: takes a list of option<cssClass> and returns a string
open Css

<div className={join([textBlue100, rounded, border, borderBlue300])}>
  {"Hello!"->React.string}
</div>

ReScript type (rescript-type)

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>

Elm (elm)

Additionally to the generated classes, you'll get 2 useful functions:

  • classes: takes a list of css classes and returns an Html.Attribute msg that can be used with any html element
  • join: performs a simple List 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!" ]

No generators

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.

Rust

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.

Pyaco validate: A type safe CSS / code validator Experimental

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...).

To get help:

pyaco validate --help
pyaco-validate

Validate code against a css input

USAGE:
    pyaco validate [OPTIONS] --css-input <CSS_INPUT> --input-glob <INPUT_GLOB>

OPTIONS:
    -c, --css-input <CSS_INPUT>
            CSS file path or URL used for code verification

        --capture-regex <CAPTURE_REGEX>
            Classes matcher regex, must include a capture containing all the classes [default:
            class="([^"]+)"]

    -h, --help
            Print help information

    -i, --input-glob <INPUT_GLOB>
            Glob pointing to the files to validate

        --max-opened-files <MAX_OPENED_FILES>
            How many files can be read concurrently at most, setting this value to a big number
            might break depending on your system [default: 128]

        --split-regex <SPLIT_REGEX>
            Classes splitter regex, will split the string captured with the `capture_regex` argument
            and split it into classes [default: \s+]

    -w, --watch
            Watch for changes in the provided css file and files and revalidate the code (doesn't
            work with URL)

        --watch-debounce-duration <WATCH_DEBOUNCE_DURATION>
            Watch debounce duration (in ms), if files are validated twice after saving a file, you
            should try to increase this value [default: 10]

Warning: the -w|--watch mode is still experimental and might contain some bugs, use with care.

The Node.js API

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";

generate({
  input: "...",
  lang: "purescript",
  outputDirectory: "...",
  watch: false,
  watchDebounceDuration: 0,
  outputFilename: "...",
});

validate({
  cssInput: "...",
  inputGlob: "...",
  captureRegex: "...",
  maxOpenedFiles: 128,
  splitRegex: "...",
  watch: false,
  watchDebounceDuration: 0,
}).then(() => {
  console.log("Done");
});

About

Css to * code generator and validator

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Rust 86.1%
  • JavaScript 11.5%
  • Makefile 2.4%