Deno Fresh PostCSS: Future CSS with Deno
Tags: webdev, deno, css
Published: 2023-04-13 08:00:38

---
title: "Deno Fresh PostCSS: Future CSS with Deno"
published: "true"
description: "Deno Fresh PostCSS 💅 how to use vanilla CSS with 🍋 Deno Fresh and add CSS minification tooling or polyfill future CSS like media ranges."
tags: "webdev, deno, css"
canonical_url: "https://rodneylab.com/deno-fresh-postcss/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h39gogd3fx0bzbq3ltbp.png"
---

## 🛸 Using PostCSS & Future CSS with Deno Fresh

In this post on Deno Fresh PostCSS, we take a look at setting up some vanilla CSS tooling for Deno Fresh.  Deno Fresh is an SSR site builder. It has instant deploys with **no build step**.  The absence of a build step sets Deno Fresh apart from other site builders like Astro or SvelteKit.  Both of those process CSS, **minify** and handle some transformations for you.  Although Deno Fresh lacks those features, the upside is that you have more control over your CSS; you make the decisions, not the tooling. That said, you need to do a little setup work to use popular tooling like **PostCSS**.  That is what we look at here.

PostCSS tooling transforms input CSS.  You can use it to generate prefixes automatically for legacy browsers or to **minify the CSS**.  Minification just **removes white-space and comments** from CSS, optimizing the CSS for delivery to a browser.  With our setup, we can keep the original CSS file (with comments and white-space). Each time we save it, under the hood, PostCSS will generate the shipped version.  We do not just look at minification here. We also add some PostCSS processing to handle modern or **future CSS**, which is not yet fully integrated into the **CSS Specification**.

## 🧱 What are we Building?

Rather than build a project from scratch, we will just look at the setup we need to add to a Deno Fresh project to use PostCSS.  We will see:

- how you can configure PostCSS itself in Deno Fresh
- how you can automatically generate processed CSS each time you save a CSS input file
- a way to cache your static CSS assets with Deno Fresh

If that sounds good, then let&rsquo;s get started.

## ⚙️ Deno Import Map

Let&rsquo;s start by updating the `import_map.json` file in the project root directory:

```json
{
  "imports": {
    "@/": "./",
    "$fresh/": "https://deno.land/x/fresh@1.1.3/",
    "preact": "https://esm.sh/preact@10.11.0",
    "preact/": "https://esm.sh/preact@10.11.0/",
    "preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4",
    "@preact/signals": "https://esm.sh/*@preact/signals@1.0.3",
    "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.0.1",
    "$std/": "https://deno.land/std@0.178.0/",
    "postcss/": "https://deno.land/x/postcss@8.4.16/"
  }
}
```

We added an import path alias in line `3` which we will use later.  We also added the Deno Standard Library and a Deno PostCSS package.

## 💅 Post CSS Config

The config itself will not be too different to what you are already familiar with from Node-based tooling.  The main difference will be that we use an `npm:` prefix on imports.  Create `postcss.config.ts` in the project root directory with the following content:

```typescript
import autoprefixer from "npm:autoprefixer";
import csso from "npm:postcss-csso";
import customMediaPlugin from "npm:postcss-custom-media";
import postcssPresetEnv from "npm:postcss-preset-env";
 
export const config = {
  plugins: [
    customMediaPlugin(),
    postcssPresetEnv({
      stage: 3,
      features: {
        "nesting-rules": true,
        "custom-media-queries": true,
        "media-query-ranges": true,
      },
    }),
    autoprefixer(),
    csso(),
  ],
};
```

## 👀 Watch Script

The last main piece is the watch script.  This watches the CSS input folder for changes and runs PostCSS each time you save a file there. We will use a `styles` directory in the project root folder as the CSS input directory.  When we save a CSS file there, the watch script will output the result to `static/styles`.  `static` is for content which you want Deno Fresh to serve statically from your deployed project.

Create `build-css.ts` in the project root directory with the content below:


```typescript
import { debounce } from "$std/async/mod.ts";
import { relative, resolve } from "$std/path/mod.ts";
import { config } from "@/postcss.config.ts";
import postcss from "postcss/mod.js";
 
const STYLES_INPUT_DIRECTORY = "styles";
 
async function buildStyles(path: string) {
  try {
    const css = await Deno.readTextFile(path);

    const { css: outputCss } = await postcss(config.plugins).process(css, {
      from: undefined,
    });

    const __dirname = resolve();
    const outputPath = `./static/${relative(__dirname, path)}`;
    console.log(`Updating styles for ${outputPath}`);
    await Deno.writeTextFile(outputPath, outputCss);
  } catch (error: unknown) {
    console.error(`Error building styles for path ${path}: ${error as string}`);
  }
}
 
const debouncedBuildStyles = debounce(async (path: string) => {
  await buildStyles(path);
}, 200);
 
const stylesOutputDirectory = `static/${STYLES_INPUT_DIRECTORY}`;
try {
  Deno.statSync(stylesOutputDirectory);
} catch (error: unknown) {
  if (error instanceof Deno.errors.NotFound) {
    Deno.mkdir(stylesOutputDirectory);
  }
}
 
const watcher = Deno.watchFs([`./${STYLES_INPUT_DIRECTORY}`]);
for await (const event of watcher) {
  const { paths } = event;
  paths.forEach((path) => {
    debouncedBuildStyles(path);
  });
}
```

- The `buildStyles` function (line `8`) takes an updated `styles` path and processes it with PostCSS, saving the output to `static/styles`.
- Calling `debounceBuildStyles` instead of `buildStyles` directly, stops the CSS building function from firing off too often.  We limit it to run once at most in any 200&nbsp;ms period.
- Lines `29`&thinsp;&ndash;&thinsp;`36` will run once when we start up the build CSS task (more about that in a moment) and create the output `static/styles` directory if it does not yet exist.
- Lines `38`&thinsp;&ndash;&thinsp;`44` contain the watch code for file changes in the `styles` directory.  If you added new files before firing up the build CSS task, just save them again once the task is running to make sure PostCSS processes them.


## 💄 Build CSS Task

Next, we can create a Deno task to run the watch script created in the last section.  Update the `deno.json` file in the project root directory to add a `build:css` task:

```json
{
  "tasks": {
    "build:css": "deno run --allow-env=DENO_ENV --allow-read --allow-write build-css.ts",
    "start": "deno run -A --watch=static/,routes/ dev.ts"
  },
  "importMap": "./import_map.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}
```

We give the task just the permissions it needs here.  These are read-write access and access to the `DENO_ENV` environment variable.  You can be more restrictive if you prefer, <a href="https://deno.land/manual@v1.31.1/tools/task_runner">listing the specific directories</a> Deno will apply the read and write permissions to.

You can open a new Terminal tab and run this new task there:

```shell
mkdir styles # create a `styles` directory if you don't yet have one
deno task build:css
```

Then in your previous Terminal tab, start up the Deno Fresh server (as usual) by running `deno task start`.  Try creating a new CSS file at `styles/global.css` for example.  If you open up `static/styles/global.css` you should see a minified version of this CSS which PostCSS has generated.

## 💯 Deno Fresh PostCSS: Testing

Add a few more CSS files and try using new features like range media queries.  You can include the CSS in a `rel` tag in one of your Preact markup files:


```typescript
import { asset, Head } from "$fresh/runtime.ts";

export default function Home() {
  return (
    <Fragment>
      <Head>
        <link rel="stylesheet" href={asset("/styles/fonts.css")} />
        <link rel="stylesheet" href={asset("/styles/global.css")} />
				<!-- TRUNCATED... -->
      </Head>
      <main className="wrapper">
        <BannerImage />
        <h1 className="heading">FRESH</h1>
        <p className="feature">Fresh 🍋 new framework!</p>
        <Video />
      </main>
    </Fragment>
  );
}
```

Here I wrapped the `href` for the stylesheets in the `asset` function.  This is helpful for cache busting.  Deno Fresh will hash the content of the files and append the calculated hash to the filename.  When the CSS changes, the hash and consequently file name change, so no outdated CSS cached in-browser or in a CDN will be applied.

Take a look in `static/styles`.  Your file should contain minified CSS and look something like this:

```css
:root{--colour-dark:hsl(242deg 94% 7%);--colour-light:hsl(260deg 43% 97%);--colour-brand:hsl(50deg 100% 56%);--colour-theme:hsl(204deg 69% 50%);--colour-alt:hsl(100deg 100% 33%);--spacing-0:0;--spacing-1:0.25rem; /* TRUNCATED...*/
```

## 🙌🏽 Deno Fresh PostCSS: Wrapping Up

We had an introduction to how you can set up PostCSS with Deno Fresh.  In particular, you saw:
- Deno code for triggering a function when a file changes
- how to be selective in Deno task permissions
- some Deno file APIs

The <a href="https://github.com/rodneylab/deno/tree/main/demos/deno-fresh-postcss">complete code for this project is in the Rodney Lab GitHub repo</a>.  I do hope the post has either helped you with an existing project or provided some inspiration for a new one.  As an extension, you can add all your favourite future CSS rules to the PostCSS config.  Beyond PostCSS for <a aria-label="linting your input C S S, consider trying style lint" href="https://stylelint.io/">linting your input CSS, consider trying stylelint</a>.

Get in touch if you have some questions about this content or suggestions for improvements.  You can add a comment below if you prefer.  Also, reach out with ideas for fresh content on Deno or other topics!

## 🙏🏽 Deno Fresh PostCSS: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, then please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter, <a href="https://toot.community/@rodney">@rodney@toot.community</a> on Mastodon and also the <a aria-label="Join the Rodney Lab Element Matrix room" href="https://matrix.to/#/%23rodney:matrix.org">#rodney</a> Element Matrix room. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on Astro" href="https://rodneylab.com/tags/astro/">Astro</a> as well as <a aria-label="See posts on deno" href="https://rodneylab.com/tags/deno/">Deno</a>. Also, <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://newsletter.rodneylab.com/issue/latest-issue">subscribe to the newsletter to keep up-to-date</a> with our latest projects.