Skip to content

Add plugin system for bun#2020

Open
wout wants to merge 20 commits intoluckyframework:mainfrom
wout:add-plugin-system-for-bun
Open

Add plugin system for bun#2020
wout wants to merge 20 commits intoluckyframework:mainfrom
wout:add-plugin-system-for-bun

Conversation

@wout
Copy link
Contributor

@wout wout commented Mar 1, 2026

Purpose

This PR adds a plugin system for Bun, glob functionality for CSS and JS imports, and a few tweaks to Lucky (see also #2019).

Description

The plugin system hooks in to the JS and CSS transformation chains. Three plugins are included now and activated by default.

1. cssAliases

This plugin was already present but was hardcoded. It resolves $ root aliases in CSS url() references, so you can avoid the use of relative paths for images and fonts. For example: url('$/images/foo.png')url('/absolute/src/images/foo.png')

2. cssGlobs

This is a new plugin to allow glob patterns in CSS imports:

@import 'global/**/*';

This will be intercepted by the plugin and expanded to a list of imports for individual files in alphabetical order.

3. jsGlobs

In Bun there's no such thing like import.meta.glob in Vite. This plugin enables a similar thing with a special syntax for imports:

import components from 'glob:./components/**/*.js'

Which will be expanded to something like:

import _glob_components_theme from './components/theme.js'
import _glob_components_shared_tooltip from './components/shared/tooltip.js'
const components = {
 'components/theme': _glob_components_theme,
  'components/shared/tooltip': _glob_components_shared_tooltip
}

The components object can then be used to for example load and register a directory of Alpine.js components or Stimulus.js controllers.

Custom CSS/JS transform plugins

Plugins to hook into the CSS and JS transformation chains look as follows:

export default function luckyPlugin(context) {
  return (content, args) => content.replace(/\$.../g, 'something')
}

Or async:

export default function luckyPlugin(context) {
  return async (content, args) => {
    const result = await someAsyncWork(content)
    return result
  }
}

The context argument in the plugin factory function contains the following properties:

  • context.root (string) the Lucky project's root directory
  • context.config (object) the fully resolved config from bun.json
  • context.dev (boolean) whether the --dev flag is used or not
  • context.prod (boolean) whether the --prod flag is used or not
  • context.manifest (object) the asset manifest being built

Important

The CSS/JS transform plugins above are bundled together into one Bun plugin per type. This is necessary because Bun can only register one loader for a given path matcher.

Bun plugins

Raw Bun plugins can be added as well:

export default function bunPlugin(context) {
  return {
    name: 'my-bun-plugin',
    setup(build) {
      build.onLoad({filter: /\.xyz$/}, async args => {
        // full Bun plugin API
      })
    }
  }
}

Registering plugins

Custom plugins can be registered in the bun.json. By default, the three built-in plugins are loaded and custom plugins can be added using the full path relative to Lucky's root:

{
  "entryPoints": {
    "js": ["src/js/main.js"],
    "css": ["src/css/main.css"]
  },
  "plugins": {
    "css": ["cssAliases", "cssGlobs", "config/bun/plugins/luckyCssPlugin.js"],
    "js": ["jsGlobs"]
  },
  ...
}

Lucky::DevAssetCacheHandler

This is a new handler to disable browser caching for static assets in development. Currently I added it to the handlers listin AppServer like this:

  def middleware : Array(HTTP::Handler)
    [
      # ...
      Lucky::DevAssetCacheHandler.new(enabled: LuckyEnv.development?),
      # ...
    ] of HTTP::Handler
  end

But I wonder if that is the most elegant way to do it. Essentially this will just continue to the next handler if it's not enabled, so it's an empty call. This could also be an option of course:

    [
      # ...
      (Lucky::DevAssetCacheHandler.new if LuckyEnv.development?),
      # ...
    ].select(HTTP::Handler)

Not unrelated, in the upload implementation we'll need to do a similar thing with a Lucky::StaticFileHandler in development, so users can render images from a local upload directory if they choose to use file system uploads. This handler will also need to be added only in environments where a FileSystem storage is used. So I think it's a good idea to formalise a way to enable some handlers in certain environments.

Checklist

  • - An issue already exists detailing the issue/or feature request that this PR fixes
  • - All specs are formatted with crystal tool format spec src
  • - Inline documentation has been added and/or updated
  • - Lucky builds on docker with ./script/setup
  • - All builds and specs pass on docker with ./script/test

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