SkillAgentSearch skills...

Optimus

A Ring middleware for frontend performance optimization.

Install / Use

/learn @magnars/Optimus
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<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:

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
  1. Assets are scripts, stylesheets, images, fonts and other static resources your webapp uses.

  2. You can mix and match optimizations.

  3. You can choose different strategies for how you want to serve your assets.

  4. Declare how to get your assets in a function.

  5. It returns a list of assets.

  6. The helpers in optimus.assets load files from a given directory on the classpath (normally in the resources directory). So in this case, the files are loaded from resources/public/.

  7. The name of this bundle is styles.css.

  8. It takes a list of paths. These paths double as URLs to the assets, and paths to the files in the public directory.

  9. The contents are concatenated together in the order specified in the bundle.

  10. You can declare several bundles at once with load-bundles.

  11. 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.js is included first, even tho it is included by the regex. This way you can make sure dependencies are loaded before their dependents.

  12. 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.assets helpers, you don't have to list images and fonts referenced in your CSS files - those are added along with the stylesheet.

  13. Assets don't have to be files on disk. This example creates an asset on the path /init.js that is bundled along with the app.js bundle.

  14. Add optimus/wrap as a Ring middleware.

  15. Pass in the function that loads all your assets.

  16. Pass in the function that optimizes your assets. You can choose from those in optimus.optimizations, or write your own asset transformation functions.

  17. optimizations/none does nothing and returns your assets unharmed.

  18. When you use optimizations/all you get everything that Optimus provides. But you can easily exchange this for a function that executes only the transformations that you need.

  19. Pass in your chosen strategy. Set up properly with environment variables of some kind.

  20. In development you want the assets to be served live. No need to restart the app just to see changes or new files.

  21. 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.

  22. Since Ring comes with content type middleware, Optimus doesn't worry about it. Just make sure to put it after Optimus.

  23. The same goes for responding with 304 Not Modified. Since Optimus adds Last-Modified headers, 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

View on GitHub
GitHub Stars373
CategoryDevelopment
Updated3mo ago
Forks22

Languages

Clojure

Security Score

77/100

Audited on Dec 3, 2025

No findings