Skip to content

Replace Tailwind CSS CDN with Vite build#298

Closed
andriytyurnikov wants to merge 1 commit intoredplanetlabs:masterfrom
andriytyurnikov:feature/vite-tailwind
Closed

Replace Tailwind CSS CDN with Vite build#298
andriytyurnikov wants to merge 1 commit intoredplanetlabs:masterfrom
andriytyurnikov:feature/vite-tailwind

Conversation

@andriytyurnikov
Copy link
Copy Markdown
Contributor

Summary

Replaces the @tailwindcss/browser CDN runtime with build-time CSS compilation using Vite and @tailwindcss/vite.

Supersedes #282 (which used custom shadow-cljs build hooks and was reverted in e080856).

Why

The browser runtime generates CSS at page load:

  • FOUC on every page load
  • ~100KB runtime JS downloaded and executed per visit
  • CDN dependency — no offline dev, outage = broken UI
  • No custom CSS — can't use @layer, @apply, or plugins

Approach: Vite instead of shadow-cljs hooks

The previous attempt (#282) used a custom Clojure namespace (tailwind_hook.clj) to shell out to the Tailwind CLI from shadow-cljs build hooks. This required managing child processes, shutdown hooks, and had race conditions on one-shot builds.

This PR uses Vite — an industry-standard build tool with first-class Tailwind support. No custom process management code. The CSS build is a standard npx vite build command, completely independent of shadow-cljs.

#282 (shadow-cljs hooks) This PR (Vite)
Custom Clojure code 97-line tailwind_hook.clj None
Process management Manual (ProcessBuilder, shutdown hooks) Vite handles it
Dev watch Custom watch process lifecycle npx vite build --watch
Build tool npx @tailwindcss/cli via Clojure shelling npx vite build
Manifest format Custom EDN Standard Vite JSON

What changed

File Change
vite.config.js Vite config with @tailwindcss/vite plugin, outputs hashed CSS + JSON manifest to resource/public/
src/cljs/.../ui/css/main.css Tailwind v4 entry: @import "tailwindcss" + @source directive
resource/index.html CDN script replaced by {{MAIN_CSS}} placeholder
server.clj css-tag reads Vite JSON manifest for hashed filename; falls back to CDN when no compiled CSS exists
scripts/build-ui.sh Added npx vite build before shadow-cljs release
package.json Added vite and @tailwindcss/vite dev dependencies
.gitignore Vite CSS output + manifest
shadow-cljs.edn Unchanged — no build hooks needed

CDN fallback for REPL workflow

The server checks for compiled CSS at request time. If found, serves <link> to the hashed file. If not (e.g., REPL started without a prior build), falls back to the original CDN <script> tag. The REPL workflow ((aor/start-ui ipc)) continues to work with zero setup.

Developer workflows

# Dev — two terminals
npx vite build --watch    # CSS (rebuilds on change)
npx shadow-cljs watch :dev  # JS

# Production
scripts/build-ui.sh         # runs both vite build + shadow-cljs release

# REPL only (no build needed)
# lein repl → (aor/start-ui ipc)
# CSS falls back to CDN automatically

Test plan

  • npx vite build — produces main.HASH.css (39KB) + css-manifest.json in 1.9s
  • npx shadow-cljs compile :dev — unaffected, builds normally
  • scripts/build-ui.sh — produces both hashed CSS and JS
  • Server serves built CSS when available
  • Server falls back to CDN when no compiled CSS exists

@andriytyurnikov andriytyurnikov force-pushed the feature/vite-tailwind branch 6 times, most recently from e2b3638 to 48e6f28 Compare March 20, 2026 16:02
Use Vite with @tailwindcss/vite to compile CSS at build time instead
of loading the ~100KB Tailwind browser runtime from CDN on every page.

- vite.config.js: Tailwind plugin, content-hashed CSS output + manifest
- dev/vite_hook.clj: shadow-cljs hook runs `vite build` automatically
- server.clj: reads Vite JSON manifest for hashed CSS filename
- build-ui.sh: runs `npx vite build` before shadow-cljs release

No workflow change — shadow-cljs watch/compile triggers Vite via hook.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant