Optimus
A Ring middleware for frontend performance optimization.
Install / Use
/learn @magnars/OptimusREADME
<img align="right" src="optimus.png"> Optimus
A Ring middleware for frontend performance optimization.
It serves your static assets:
- in production: as optimized bundles
- in development: as unchanged, individual files
In other words: Develop with ease. Optimize in production.
Features
Depending on how you use it, Optimus:
- concatenates your JavaScript and CSS files into bundles.
- adds cache-busters to your static asset URLs
- adds far future Expires headers
- minifies your JavaScript with UglifyJS 2
- minifies your CSS with clean-css
- inlines CSS imports while preserving media queries
You might also be interested in:
- optimus-autoprefixer - an asset middleware which uses autoprefixer to add vendor prefixes to CSS files.
- optimus-angular - which comes with a custom asset loader that prepopulates the Angular.JS template cache. It also has Optimus asset middleware that prepares Angular.JS code for minification.
- optimus-less - which adds a custom asset loader for LESS files.
- optimus-sass - which adds a custom asset loader for Sass/SCSS files.
- optimus-coffeescript - which adds a custom asset loader for CoffeeScript files.
- optimus-jsx - which adds a custom asset loader for React JSX files.
- optimus-img-transform - an asset middleware to transform your images' size, quality and rendering methods.
Install
Add [optimus "2025.01.19.2"] to :dependencies in your project.clj.
This project no longer uses Semantic Versioning. Instead we're aiming to never break the API. Feel free to check out the change log.
There were breaking changes in 0.16, 0.17, 0.19 and 2022-02-13. If
you're upgrading, you might want to read more about them.
Usage
Let's look at an example:
(ns my-app.example
(:require [optimus.prime :as optimus]
[optimus.assets :as assets] ;; 1
[optimus.optimizations :as optimizations] ;; 2
[optimus.strategies :as strategies])) ;; 3
(defn get-assets [] ;; 4
(concat ;; 5
(assets/load-bundle "public" ;; 6
"styles.css" ;; 7
["/styles/reset.css" ;; 8
"/styles/main.css"]) ;; 9
(assets/load-bundles "public" ;; 10
{"lib.js" ["/scripts/ext/angular.js"
#"/scripts/ext/.+\.js$"] ;; 11
"app.js" ["/scripts/controllers.js"
"/scripts/directives.js"]})
(assets/load-assets "public" ;; 12
["/images/logo.png"
"/images/photo.jpg"])
[{:path "/init.js" ;; 13
:contents (str "var contextPath = " (:context-path env))
:bundle "app.js"}]))
(-> app
(optimus/wrap ;; 14
get-assets ;; 15
(if (= :dev (:env config)) ;; 16
optimizations/none ;; 17
optimizations/all) ;; 18
(if (= :dev (:env config)) ;; 19
strategies/serve-live-assets ;; 20
strategies/serve-frozen-assets)) ;; 21
(ring.middleware.content-type/wrap-content-type) ;; 22
(ring.middleware.not-modified/wrap-not-modified)) ;; 23
-
Assets are scripts, stylesheets, images, fonts and other static resources your webapp uses.
-
You can mix and match optimizations.
-
You can choose different strategies for how you want to serve your assets.
-
Declare how to get your assets in a function.
-
It returns a list of assets.
-
The helpers in
optimus.assetsload files from a given directory on the classpath (normally in theresourcesdirectory). So in this case, the files are loaded fromresources/public/. -
The name of this bundle is
styles.css. -
It takes a list of paths. These paths double as URLs to the assets, and paths to the files in the public directory.
-
The contents are concatenated together in the order specified in the bundle.
-
You can declare several bundles at once with
load-bundles. -
You can use regexen to find multiple files without specifying each individually. Make sure you're specific enough to avoid including weird things out of other jars on the class path.
Notice that
angular.jsis included first, even tho it is included by the regex. This way you can make sure dependencies are loaded before their dependents. -
You can add individual assets that aren't part of a bundle, but should be optimized and served through Optimus. This is useful to add cache busters and far future Expires headers to images served straight from your HTML.
If you use the
optimus.assetshelpers, you don't have to list images and fonts referenced in your CSS files - those are added along with the stylesheet. -
Assets don't have to be files on disk. This example creates an asset on the path
/init.jsthat is bundled along with theapp.jsbundle. -
Add
optimus/wrapas a Ring middleware. -
Pass in the function that loads all your assets.
-
Pass in the function that optimizes your assets. You can choose from those in
optimus.optimizations, or write your own asset transformation functions. -
optimizations/nonedoes nothing and returns your assets unharmed. -
When you use
optimizations/allyou get everything that Optimus provides. But you can easily exchange this for a function that executes only the transformations that you need. -
Pass in your chosen strategy. Set up properly with environment variables of some kind.
-
In development you want the assets to be served live. No need to restart the app just to see changes or new files.
-
In production you want the assets to be frozen. They're loaded and optimized when the application starts.
Take note: You're free to serve optimized, live assets. It'll be a little slow, but what if your javascript doesn't minify well? How do you reproduce it? It's damn annoying having to restart the server for each change. Here's a way that optimizes just like production, but still serves fresh changes without restarts.
-
Since Ring comes with content type middleware, Optimus doesn't worry about it. Just make sure to put it after Optimus.
-
The same goes for responding with
304 Not Modified. Since Optimus addsLast-Modifiedheaders, Ring handles the rest.
Using the new URLs
Since we're rewriting URLs to include cache busters, we need to access them through Optimus.
Notice that we use map, since there is likely more than one URL in development
mode.
(ns my-app.view
(:require [optimus.link :as link]))
(defn my-page
[request]
(hiccup.core/html
[:html
[:head
(map (fn [url] [:link {:rel "stylesheet" :href url}])
(link/bundle-paths request ["styles.css"]))]
[:body
(map (fn [url] [:script {:src url}])
(link/bundle-paths request ["lib.js" "app.js"]))]]))
There's even some sugar available:
(defn my-page
[request]
(hiccup.core/html
[:html
[:head
(optimus.html/link-to-css-bundles request ["styles.css"])]
[:body
(optimus.html/link-to-js-bundles request ["lib.js" "app.js"])]]))
These link-to-*-bundles will return a string of HTML that includes several
script/link tags in development, and a single tag in production.
Specifying the optimizations
If you want to mix and match optimizations, here's how you do that:
(defn my-optimize [assets options]
(-> assets
(optimizations/minify-js-assets options)
(optimizations/minify-css-assets options)
(optimizations/inline-css-imports)
(optimizations/concatenate-bundles)
(optimizations/add-cache-busted-expires-headers)
(optimizations/add-last-modified-headers)))
(-> app
(optimus/wrap
get-assets
(if (= :dev (:env config))
optimizations/none
my-optimize)
the-strategy))
Just remember that you should always add cache busters after concatenating bundles.
Adding your own asset transformation functions is fair game too. In fact, it's encouraged. Let's say you need to serve all assets from a Content Delivery Network ...
Yeah, we are using a Content Delivery Network. How does that work?
To serve the files from a different host, add a :base-url to the assets:
(defn add-cdn-base-url-to-assets [assets]
(map #(assoc % :base-url "http://cdn.example.com") assets))
(defn my-optimize [assets options]
(-> assets
(optimizations/all options)
(add-cdn-base-url-to-assets)))
This supposes that your CDN will pull assets from your app server on cache misses. If you need to push files to the CDN, you also need to save them to disk. Like this:
(defn export-assets []
(-> (get-assets)
(my-optimize (get-optimisation-options))
(optimus.export/save-assets "./cdn-export/")))
You can even add an alias to your project.clj:
:aliases {"export-assets" ["run" "-m" "my-app.example/export-assets"]}
And run lein export-assets from the command line. Handy.
Hey, I'm serving my app from a sub-directory, how can I avoid referencing it everywhere?
Locally, your app is known as http://localhost:3000/, but sometimes in
production it must share the limelight with others like it. Maybe it'll go live
at `h
Related Skills
node-connect
340.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
84.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
340.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
84.2kCommit, push, and open a PR
