Zero-runtime CSS-in-JS tool inspired by Linaria.
Try on StackBlitz or check out example.
⚠ Work in progress.
pnpm i -D vite-plugin-toad
/*@toad-ext .scss*/ // This sets extension for corresponding output file.
// You can set in plugin config globally or for each file.
import { render } from 'solid-js/web';
import { modularScale, hiDPI } from 'polished';
import { css } from 'vite-plugin-toad/css';
import Constants from './constants';
import logo from './logo.svg';
css`/*global*/ /* mark style as global */
body {background-color: ${Constants.BACKGROUND_COLOR};} /* Use some static variables. Works when 'ssr.evaluate = true' */
@keyframes logo-spin {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
`;
const App = () => (
<div
class={css`
/*@toad-debug wrapper*/ // <- this adds "wrapper" to output class
max-width: 800px;
background-color: #dadada;
font-size: ${modularScale(2)};
${hiDPI(1.5)} {
font-size: ${modularScale(2.5)};
}
`}
>
<img
src={logo}
class={css`
animation: logo-spin infinite 10s linear;
height: 40vmin;
pointer-events: none;
& ~ p {
$variable: blue;
color: #{$variable}; // This code will work as we set .scss extension
}
`}
/>
<p>Edit <code>app.tsx</code> and save to reload</p>
<a href="https://github.com/solidjs/solid" target="_blank" > Learn solid </a>
</div>
);
if (!import.meta.env.SSR) {
render(App, document.body);
}
All CSS transforms are handled by Vite, so it will work with SASS, LightningCSS, PostCSS and other tools.
There is also way to write your CSS-in-JS separately by using babel-plugin-css-attribute
. Here it is an example from my project with UnoCSS, preset attributify and vite-plugin-toad:
<a
m-b-8
m-t-16px
class="block cursor-pointer text-center c-blue-500"
css={css`@container (height < 400px) { margin-bottom: 0; }`}
>
My link
</a>
⬇️vite-plguin-toad⬇️
<a class={"block cursor-pointer text-center c-blue-500 m-b-8 m-t-16px" + "login_page-1okjy9f"}
>
My link
</a>
To use this feature, set transformCssAttribute: true
or use Babel plugin, example with vite-plugin-solid
:
import ViteSolid from "@foxpro/vite-plugin-solid"
import BabelPluginCssAttrs from "vite-plugin-toad/babel-plugin-css-attribute"
// ...vite configurations and plugins...
ViteSolid({
hot: dev,
dev: dev,
typescript: {
onlyRemoveTypeImports: true
},
babel: {
plugins: [[BabelPluginCssAttrs, {}]]
}
})
⚠️ To achieve better better performance, usebabel-plugin-css-attribute
directly if possible, because Toad will use additionally:
@babel/preset-typescript
@babel/plugin-syntax-jsx
This can slightly affect build performance.
You can customize babel options by usingbabel
option.
For more advanced documentation, please refer to typescript JSDoc comments.
I found following way of writing components can be convinient:
<TextField.Input
ref={numberInputRef}
inputmode="decimal"
css="
background-color: var(--grey-000);
border: 1px solid blue; border-radius: 8px;
&:focus {
outline-offset: -5px; outline-color: var(--grey-000);
}
color: var(--black-900); line-height: 3.5rem; font-weight: 500;
"
h-12 w-full p-l-10px
/>
CSS-in-JS for creating component styles, and Atomic CSS with attributify for positioning component in layout.
I found this way keeps code more clean and readable. It avoids mess of long atomic classes with ?#[]@
symbols and decoupling of styles as it used to with BEM or CSS modules.
I'm planning to add handling of css=""
attribute.
Make sure you modules with CSS-in-JS don't use top-level DOM API if you are using ssr: { eval: true }
. You can wrap it like in the example:
if (!import.meta.env.SSR) {
render(App, document.body)
}
Some plugins don't respect Vite ssr: true
option when using ssrLoadModule
, so they need to be processed separately if you're want to use variables in template literals.
You can process it in customSSRTransformer
. Make sure to output SSR-ready code.
ViteToad({
outputExtension: '.scss',
exclude: [/node_modules/, /\.s?css/], // better to exclude all styles
ssr: {
eval: true,
async customSSRTransformer(code, ctx, server, _c, url) {
solidOptions.solid.generate = 'ssr'
const result = await server.transformRequest(skipToadForUrl(url), { ssr: true })
// Or, if you know what you're doing:
// const solidPlugin = server.config.plugins.find(p => p.name === 'solid')
// const result = await solidPlugin.transform(code, skipToadForUrl(url), { ssr: true })
return {
result,
// this will be called when we will transform all dependencies
cb: () => {
solidOptions.solid.generate = 'dom'
}
}
},
},
})
Currently, it may not work if styles are co-located with some legacy dependencies / or dependencies that are not intended to be used in SSR environment. What you can do about it:
- Split your code so your component with styles are not in the same module with bad dependency
- Wrap your dependency in lazy
import()
inside your component near usage place, or inif(!import.meta.env.SSR)
. - Try another SSR-friendly library instead
- Make a simple Vite plugin just in your configuration to skip dependency: return empty string in Vite
load()
hook. - Play with Vite
ssr.external
configuration. It's possible that I will implement some kind of tree-shaking through SWC, so all unused in styling deps will be omitted, as Linaria do with their shaker.
You can avoid using import { css } from 'vite-plugin-toad/css
by adding "vite-plugin-toad/css"
entry to tsconfig.json
compilerOptions.types
array.