Integrating UnoCSS with Yew
Tags: rust, node, webdev, tailwindcss
Published: 2023-01-23 00:55:11

If you'd like to just use a template repository to get started faster, I've prepared one [here](https://github.com/ap-1/yew-with-unocss).

## Intro

If you're anything like me, you often find yourself experimenting with the latest web technologies. Unfortunately, a lot of these up-and-coming frameworks aren't compatible with each other. My latest project is a Jupyter Notebook clone, written in Rust, that runs entirely on the browser. Naturally, I looked toward [Yew](https://yew.rs/) to create the web application. 

Even using Rust, though, I didn't want to give up my [Atomic CSS](https://antfu.me/posts/reimagine-atomic-css) classes. I'm accustomed to using Tailwind CSS with frameworks like Next and Astro, but recently I've replaced it with [UnoCSS](https://uno.antfu.me/). It has a preset for Tailwind, but it's significantly faster.

This guide aims to provide step-by-step instructions for integrating UnoCSS and Yew.

## Setting up a Yew Project

To start off, we can create a new Yew project: [![asciicast](https://asciinema.org/a/553521.svg)](https://asciinema.org/a/553521)

1. I won't go [into depth](https://rustwasm.github.io/docs/book/reference/add-wasm-support-to-crate.html) here, but you need to enable the wasm32-unknown-unknown compilation target for WebAssembly to work.
2. Trunk is a bundler for Yew. You can install it using `cargo`, but I opted for a prebuilt binary. I used `brew`, but there are a [number of methods](https://trunkrs.dev/#install) available.
3. Create a new cargo project and add Yew as a dependency. You need to enable the “csr” (short for client-side rendering) feature, because we're building a web application.

If you did everything correctly, you should have a `Cargo.toml` with the following:
```toml
[dependencies]
yew = { version = "0.20.0", features = ["csr"] }
```
Note that your version number may be different.

Now, we just have to add a few things to get a functional Yew app. Start by creating a boilerplate `index.html` file in the root of your project:
```html
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body></body>
</html>
```

When we run Yew, it will create static files for us in `./dist`. We don't want to keep these under version control, so we can add `/dist` to our `.gitignore`.

### Editor setup

This next step is optional, but if you're using VSCode, you might find it useful to enable HTML Intellisense for Rust files. Create `.vscode/settings.json` in the project root with the following content:
```json
{
	"emmet.includeLanguages": {
		"rust": "html"
	}
}
```

### Starting the App

From there, you can populate `main.rs` with a dummy Yew app:
```rust
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    html! {
        <p class="text-5xl">{ "Hello World!" }</p>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

Try running your app now to see if everything's working correctly. You'll need to use `trunk serve`.

![Initial preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x7563rnvdkn7yoaaqa0t.png)

## Installing UnoCSS

See that [`text-5xl`](https://uno.antfu.me/?s=text-5xl) class there? That doesn't do anything yet. The next stage is to install UnoCSS: [![asciicast](https://asciinema.org/a/553523.svg)](https://asciinema.org/a/553523)

1. UnoCSS runs on JavaScript, so we'll need to create a Node project. I prefer `pnpm`, but you can also use `npm` or `yarn`.
2. We're going to add two devDependencies: `@unocss/cli` and `concurrently`.

If you did that correctly, you should now have a `package.json` with the following:
```json
"devDependencies": {
    "@unocss/cli": "^0.48.4",
    "concurrently": "^7.6.0"
}
```

Again, your version numbers may vary. Now is a good point to pop `node_modules` into your `.gitignore`.

This is also optional, but since I'm using `pnpm`, I'm going to add a [special little script](https://pnpm.io/only-allow-pnpm) to my `package.json`:
```json
"scripts": {
    ...
    "preinstall": "npx only-allow pnpm"
},
```

## Tying it Together

Now that the setup is out of the way, we can properly integrate UnoCSS with Yew. The reason we used `@unocss/cli` was because we're going to use the `unocss` command to scan our Rust files and generate the necessary classes. Then, all we have to do is link the generated CSS to Yew's `index.html`.

Create a new script in your `package.json`:
```json
"uno": "unocss src/**/*.rs index.html --out-file static/uno.css",
```

1. We're telling UnoCSS to search through `index.html` as well as every Rust file in `src`.
2. It's going to spit out the generated CSS file (`uno.css`) in the `static` directory.

### Linking `uno.css`

To link this file in our HTML, we can add the following line to the `<head>` of the `index.html` file in our project root:
```html
<link data-trunk rel="css" href="./static/uno.css" />
```

1. This is a special `<link>`. `data-trunk` is how we tell Trunk to [process assets](https://trunkrs.dev/assets/). 
2. Trunk supports a few types of assets. The `rel="{type}"` we're looking for here is `css`.
3. We're setting the `href` attribute to the `uno.css` file from earlier.

### Adding a CSS Reset

While we're at it, we should also implement one of the [UnoCSS resets](https://github.com/unocss/unocss/blob/main/packages/reset/README.md). You can pick whichever one you want, but I'm going with `antfu.css`, which is an extension of Tailwind CSS's [Preflight](https://tailwindcss.com/docs/preflight). You have a couple options for linking them:

1. [Directly from the `unocss` repository](https://raw.githubusercontent.com/unocss/unocss/main/packages/reset/antfu.css)
2. [From jsDelivr (what I recommend)](https://cdn.jsdelivr.net/npm/@unocss/reset@0.48.4/antfu.css).

Here's all we needed to add to `index.html`:
```html
<link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/@unocss/reset@0.48.4/antfu.css"
/>
<link data-trunk rel="css" href="./static/uno.css" />
```

Finally, let's add `/static/uno.css` to our `.gitignore`. We'll keep a `.gitkeep` file in there for now, since its only file is being gitignored.

### Running UnoCSS with Trunk

Remember when we installed `concurrently` as a devDependency? That's how we're going to run UnoCSS alongside Yew. Let's first create a “dev” script:
```json
"dev": "concurrently 'trunk serve' 'npm run uno -- --watch'",
```

Here, we're still using `trunk serve` to start our Yew app. We're going to run UnoCSS in watch mode so that it automatically updates `./static/uno.css` when you update `index.html` or a Rust file in `src`. The extra set of dashes is necessary to pass the `--watch` argument. 

Next, we can create a “build” script for production:
```json
"build": "concurrently 'trunk build --release' 'npm run uno'",
```

Now, we need neither UnoCSS nor Trunk in watch mode, so we can adjust both commands accordingly.

## Testing our App

Now, the command to run our Yew app is `pnpm run dev` (you can modify this with `npm` or `yarn` if you're using those). Let's try it:

![Items generated in our Yew project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y2imusw2oieldzzr9rzp.png)

1. It worked! An `uno.css` file was generated in the `static` directory.
2. We can see that trunk successfully processed the asset, too. If you check `./dist/index.html`, you'll notice that it even updated the import to the CSS file.

![Finished preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmjedik3lbp36btwkl25.png)

Try changing `text-5xl` to other UnoCSS classes. You'll see the styles get refreshed automatically.

The full source code is available [here](https://github.com/ap-1/yew-with-unocss).