Writing your own extension
You add a compiler for another kind of source by shipping an extension, on the same contract the built-in extensions use, so a custom extension reads exactly like a shipped one.
This page builds a Svelte extension as a complete example. Svelte is a component framework whose .svelte files compile to JavaScript at build time. The bundler doesn't handle that source on its own, so consuming a Svelte component from webforJ means teaching the build to compile it, which is exactly what an extension does. That makes it a fitting example: a new source type, taught to the build by the extension you write, then bound to a class the same way any other entry is.
The extension API is experimental and may change between releases while it settles. The mechanism itself is here to stay, the built-in extensions are built on the same contract, but its method signatures may shift, so expect to adjust a custom extension when you upgrade webforJ.
The build-time dependency
To write an extension you compile against the bundler API. Add it with provided scope, since the build supplies it and you don't ship it:
<dependency>
<groupId>com.webforj</groupId>
<artifactId>webforj-bundle-bun</artifactId>
<version>${webforj.version}</version>
<scope>provided</scope>
</dependency>
The plugin wrapper
An extension contributes a Bun plugin. Keep the plugin in a small .mjs resource. It default-exports a factory that the build calls with the options you set for this extension, and returns the Bun plugin:
import { SveltePlugin } from 'bun-plugin-svelte';
export default (options) => SveltePlugin({ forceSide: 'client', ...options });
The extension
Implement BundleExtension. It names itself with an id, decides when it activates, and in onWillBundle declares its packages and contributes the wrapper:
public class SvelteExtension implements BundleExtension {
@Override
public String getId() {
return "svelte";
}
@Override
public boolean isEnabledByDefault(BundleContext context) {
return context.getSourceExtensions().contains("svelte");
}
@Override
public void onWillBundle(BundleContext context) {
context.addPackage(new BundlePackageDeclaration()
.setName("bun-plugin-svelte")
.setVersion("^0.0.6")
.setDev(true));
context.addPackage(new BundlePackageDeclaration()
.setName("svelte")
.setVersion("^5.56.2")
.setDev(true));
context.addPlugin(getId(), readWrapper("/frontend-extensions/svelte.mjs"));
}
private String readWrapper(String path) {
try (InputStream in = getClass().getResourceAsStream(path)) {
if (in == null) {
throw new IOException("missing wrapper " + path);
}
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
In this example:
getId()returns the id used to turn the extension on or off and to key its options, the same rolewebforj-scssplays for the SCSS extension.isEnabledByDefaultactivates the extension when a.sveltesource is present, the activate by file type rule the curated extensions follow.onWillBundledeclares the build dependencies the compiler needs and contributes the Bun plugin under the extension's id.
Registering the extension
The build discovers an extension as a service on the classpath. Add a service file named for BundleExtension, with the fully qualified class name:
com.example.SvelteExtension
With the service file in place, a .svelte source now compiles, and the view consumes the element the component registers.
Options
The extension forwards the options you set under its id straight to the Bun plugin. Set them in bun.config.ts:
export const options = {
'svelte': { /* bun-plugin-svelte options */ }
};