styletakeout.macro
Lets you pull CSS out of CSS-in-JS into an external CSS file. Similar to
styled-components
and csz
but at compile time instead of run time.
It's 350 lines of TypeScript in a single file and still fully featured. The web ecosystem loves over engineering and complexity; this pushes against that.
/src/components/Button.ts:
; ;;
Becomes:
/build/components/Button.ts
;;
/build/takeout.css
API
css`...`
: CSS which is wrapped in a class and moved to the takeout file. In source code, the tag template is replaced with a string of the classname.injectGlobal`...`
: Global CSS which is directly moved to the takeout file without a class. In source code the tag template is removed entirely....variables
: Any other imports are treated as variables and looked up in your Babel config. See this file, and this real file as examples.
The names css and injectGlobal are used by other CSS-in-JS libraries like styled-components. This means editors like VSCode can provide syntax highlighting, linting, and autocomplete out of the box.
All CSS is processed with Stylis and beautified with CSSBeautify. This can be configured below.
Options
For your Babel config (.babelrc.json
or similar). Default values are shown:
Minimal example:
See this local file, and this other project's file as larger more complex examples.
Typings for variables (TS/JS/Intellisense)
You'll likely want autocomplete for the variables you've set. To support this, use module augmentation. For the minimal example Babelrc from above, you might use this:
declare // Use the type that works for you. The real value is in the JSON config. // You could easily use '' or `string`. Anything to help you remember. }
Now def
and colour
will be valid imports with full type-support. You can
import them.
You'll find this example in sandbox/index.ts. Notice that the object values
don't matter. You can use string
but a branded type (as shown above) will
provide a helpful tooltip in your editor to hint the type.
Here's a more complicated example copying TailwindCSS colours: d.ts and .babelrc.json
Classname structure
CSS classnames are written as ${prefix}${name}+${count}:${line}:${column}
:
-
Prefix defaults to
css-
. Listed in options -
Name is a filename (basename with extension) unless it's an index file and option "classUseFolder" is true, then it's the folder name.
-
Count is for conflict resolution as same-name files are encountered throughout the project. It increments from 0. This is an alternative to hashing, which styled-components and friends often use.
Note there was an attempt to use the shortest conflict-free file path but isn't possible due to a limtation from Babel; see the design notes.
Examples
You can see the sandbox/ directory of this repo for some test cases, or see how it's used in an application at https://github.com/heyheyhello/stayknit
Pitfalls
At compile time, there is no runtime to understand JS. This is probably why nearly all CSS-in-JS libraries operate are at runtime (in browser).
So keep that in mind. Below are some common pitfalls where the compiler won't understand your code:
Macro export references
All imports are processed and understood in isolation by the compiler.
This means any kind of renaming or modification won't work. In the example
below, the macro only sees the line about ... = css
and then immediately
throws an error since it's not a tag template like css`...`
:
;;;
Variable usage
You can't do complex variables like you can in JS. You can read values but not
objects; there's no intermidiate readings. Here's some examples assuming you've
defined a decl
variable.
// Assuming you've set `decl.blue` to #ABCDEF. This is OK.// However, below is NOT OK.css` color: ;`;// Neither is this, assuming `decl.colours` is an object; // Error;
Remember that each macro is processed and understood in isolation.
-
The
decl
macro will change= decl.blue
to= "#ABCDEF"
. OK! -
The
css
block will see that the tag template expression${blue}
is not a known variable in the JSON config and will throw an error. It only knows how to lookup defined values, nothing about JS. -
Lastly
colour
, presumably is an object with colours in it, will not be serialized to JSON - it'll throw an error:"decl.colour" is an object
No code evaluation
The macro is removed at compile time in-place. This doesn't work:
;for of Object.entriestextSizes
The css
macro is replaced entirely with the classname. No code is ever run.
There's no JS runtime. The macro is not aware of the for-loop it's in - it
only sees the exact css`...`
line and replaces it.
The result:
for of Object.entriestextSizes
It's not complicated. Just look sideways a bit to get the hang of it.