A Quarkus extension that integrates quickjs4j - Java bindings for the QuickJS JavaScript engine - into Quarkus applications. This extension enables seamless execution of JavaScript code within your Java applications using CDI beans and compile-time code generation.
- ๐ Compile-time Code Generation: Automatically generates CDI beans and proxy classes for JavaScript interfaces
- ๐ CDI Integration: Full integration with Quarkus dependency injection
- ๐ Flexible Script Loading: Load JavaScript files from classpath, filesystem, or URLs
- ๐ง Context Support: Pass Java objects as context to JavaScript execution
- โก Build-time Optimization: Leverages Quarkus build-time processing for optimal performance
- Java 17+
- Quarkus 3.20.1+
- Maven 3.6.3+
Add the extension to your Quarkus application project:
<dependency>
<groupId>io.quarkiverse.quickjs4j</groupId>
<artifactId>quarkus-quickjs4j</artifactId>
<version>${quarkus-quickjs4j.version}</version>
</dependency>Also enable the quickjs4j annotation processor (required) to enable code generation from annotations:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<release>${maven.compiler.release}</release>
<annotationProcessorPaths>
<path>
<groupId>io.quarkiverse.quickjs4j</groupId>
<artifactId>quarkus-quickjs4j</artifactId>
<version>${quarkus-quickjs4j.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>Create a Java interface annotated with @ScriptInterface and @ScriptImplementation. This
interface represents the contract that will be implemented by your JavaScript code:
package com.example;
import io.roastedroot.quickjs4j.annotations.ScriptInterface;
import io.quarkiverse.quickjs4j.annotations.ScriptImplementation;
@ScriptInterface
@ScriptImplementation(location = "dynamicCalculator.js")
public interface Calculator {
int add(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);
}The @ScriptImplementation annotation is optional and should be used only if you have
the JavaScript implementation of the interface available at build time or at a well known
location at runtime. If you are loading the JavaScript from some other dynamic location,
do not include this annotation and instead using the factory pattern.
Note: you will be able to directly inject the interface in your CDI service beans
only if you have the interface annotated with @ScriptImplementation. Otherwise,
you will need to inject a factory.
Create src/main/resources/dynamicCalculator.js:
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
export {
add, multiply, divide
};Inject and use the calculator in your Quarkus application:
package com.example;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class MathService {
@Inject
Calculator calculator;
public int performCalculation() {
int sum = calculator.add(5, 3); // Returns 8
int product = calculator.multiply(4, 7); // Returns 28
double quotient = calculator.divide(10.0, 2.0); // Returns 5.0
return sum + product + (int) quotient;
}
}You can configure the @ScriptInterface with a Java context class. This context class represents
the set of methods that will be available for your JavaScript code to invoke (JavaScript --> Java):
package com.example;
import io.roastedroot.quickjs4j.annotations.ScriptInterface;
import io.quarkiverse.quickjs4j.annotations.ScriptImplementation;
@ScriptInterface(context = CalculatorContext.class)
@ScriptImplementation(location = "dynamicCalculator.js")
public interface Calculator {
int add(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);
}@ApplicationScoped
public class CalculatorContext {
public void logDivideByZero(int a, int b) {
System.out.println("ERROR>> Divide by zero: " + a + " / " + b);
}
}JavaScript implementation with context (note that quickjs4j will generate a .mjs file for the context functions):
// ../../../target/classes/META-INF/quickjs4j/CalculatorContext_Builtins.mjs
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
CalculatorContext_Builtins.logDivideByZero(a, b);
throw new Error("Division by zero");
}
return a / b;
}
export {
add, multiply, divide
};For more control over script instantiation, you can use the factory pattern:
@ApplicationScoped
public class MathService {
@Inject
CalculatorContext context;
@Inject
ScriptInterfaceFactory<Calculator, CalculatorContext> calculatorFactory;
public void performCalculations() {
// Load the script from some custom location.
String scriptContent = loadCustomScript();
// Use the factory to create a new Calculator instance
Calculator calculator = calculatorFactory.create(scriptContent, context);
// Use the calculator...
int result = calculator.add(10, 20);
}
}The extension supports multiple ways to load JavaScript files:
-
Classpath Resources (recommended):
@ScriptImplementation(location = "scripts/my-script.js")
-
Absolute File Paths:
@ScriptImplementation(location = "/path/to/script.js")
-
Relative File Paths (relative to working directory):
@ScriptImplementation(location = "scripts/my-script.js")
Note: In all cases the script referenced in the @ScriptImplementation interface
must be available at runtime (on the classpath or on the file system). If the
script is available at build time, it should be packaged with the application.
JavaScript errors are propagated as Java exceptions:
try {
double result = calculator.divide(10, 0);
} catch (RuntimeException e) {
// Handle JavaScript errors
logger.error("JavaScript execution failed", e);
}The extension performs build-time code generation, creating:
- CDI Bean Classes:
{InterfaceName}_CDI- Injectable CDI bean - Factory Classes:
{InterfaceName}_Factory- Injectable Factory bean - Proxy Classes:
{InterfaceName}_Proxy- Generated by quickjs4j - Context Builtins:
{InterfaceName}_Builtins- Generated by quickjs4j
These classes are automatically generated during compilation and don't need to be manually created.
Feel free to contribute to this project by submitting issues or pull requests.
- Clone the repository
- Build the project:
mvn clean install - Run tests:
mvn test
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- ๐ Documentation
- ๐ Issue Tracker
Status: Experimental - This extension is in active development and APIs may change.