Introducing a new build system for plugins

david
Wed, 18 Mar 2026 12:14:37 +0000
Development

Over the last few months, we’ve been working on a new build system for plugin JavaScript code. This will bring plugins up-to-date with the changes we made to the theme build system back in July 2025, which lean on more modern browser technologies and JS build tooling.

This change is largely backwards-compatible. Most plugin authors won’t need to do anything.

Benefits

As well as the behind-the-scenes modernization, this change will bring a number of functional benefits to Discourse developers & hosters:

1. Plugin assets are heavily cached, and are keyed on a per-plugin basis. This means that restarting your server in development will not have to rebuild all plugins from scratch.

2. We can start to include precompiled code for popular plugins in our existing asset bundles. During a rebuild, only changed/new plugins will need to be built. This should be especially useful for resource-constrained machines

3. Plugin code will be transpiled to a native ES module. This brings much simpler syntax, faster execution in the browser, and unlocks future use of things like import() for bundle-splitting in future.

Testing / Timeline

We have been testing this system internally for some time, and have verified it’s functionality on hundreds of official plugins. Meta has been running with the new system for weeks.

If you’d like to try it out yourself, you can set the ROLLUP_PLUGIN_COMPILER=1 environment variable.

We plan to change the default very soon. There will be a brief period where the new system can be disabled using ROLLUP_PLUGIN_COMPILER=0, in case of any surprises, but we intend to keep the transition period to a minimum.

Possible issues with complex plugins

For more complex plugins, there are a few things which the new system is more strict about:

1. Importing admin modules from non-admin code will now raise an exception. This has always been recommended against, and would cause surprising errors. Now it is detected & blocked more deliberately.

If you run into this problem, you should consider whether your plugin code would be better located in the admin-js directory (admin/assets/javascripts/...). If you really do need to conditionally import admin modules, then you should use core’s optionalRequire helper to achieve that.

2. Cross-plugin imports are still supported. However, similar to the admin modules, importing modules from a plugin which is not installed/enabled will now raise a much more obvious error. If the cross-plugin dependency is intended to be optional, then you should use core’s optionalRequire helper

3. Subtle timing changes can cause issues in rare cases. Now that plugin code is being compiled to native ES modules, anything in the module scope is being run eagerly. If you had any unusual logic in module scope, it may need to be moved into the runtime code (e.g. inside a class constructor, or similar)

If you run into any of these issues, and need a hand resolving them, don’t hesitate to post below.

CORS / Access-Control-Allow-Origin Errors

If you experience CORS errors after the update, and you use a CDN, then you should run a full CLI rebuild (./launcher rebuild app) to pull in this change to the NGINX configuration.

If you use S3 (or S3 compatible) storage for assets, then you need to configure your CDN to add the Access-Control-Allow-Origin: * response header to all assets.

5 posts - 2 participants