πΈ 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βs get started.
βοΈ Deno Import Map
Letβs start by updating the import_map.json
file in the project root directory:
{
"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:
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:
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 (line8
) takes an updatedstyles
path and processes it with PostCSS, saving the output tostatic/styles
. - Calling
debounceBuildStyles
instead ofbuildStyles
directly, stops the CSS building function from firing off too often. We limit it to run once at most in any 200Β ms period. - Lines
29
βββ36
will run once when we start up the build CSS task (more about that in a moment) and create the outputstatic/styles
directory if it does not yet exist. - Lines
38
βββ44
contain the watch code for file changes in thestyles
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:
{
"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, listing the specific directories Deno will apply the read and write permissions to.
You can open a new Terminal tab and run this new task there:
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:
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:
: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 complete code for this project is in the Rodney Lab GitHub repo. 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 linting your input CSS, consider trying stylelint.
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 consider supporting me through Buy me a Coffee.
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 @askRodney on Twitter, @rodney@toot.community on Mastodon and also the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as Deno. Also, subscribe to the newsletter to keep up-to-date with our latest projects.