Ahead-of-Time Code Generation with BridgeJS
Learn how to improve build times by generating BridgeJS code ahead of time.
Overview
The BridgeJS build plugin automatically processes @JS
annotations and TypeScript definitions during each build. While convenient, this can significantly increase build times for larger projects. To address this, JavaScriptKit provides a command plugin that lets you generate the bridge code ahead of time.
Using the Command Plugin
The swift package plugin bridge-js
command provides an alternative to the build plugin approach. By generating code once and committing it to your repository, you can:
Reduce build times: Skip code generation during normal builds
Inspect generated code: Review and version control the generated Swift code
Create reproducible builds: Ensure consistent builds across different environments
Step 1: Configure Your Package
Configure your package to use JavaScriptKit, but without including the BridgeJS build plugin:
// swift-tools-version:6.0
import PackageDescription
let package = Package(
name: "MyApp",
dependencies: [
.package(url: "https://github.com/swiftwasm/JavaScriptKit.git", branch: "main")
],
targets: [
.executableTarget(
name: "MyApp",
dependencies: ["JavaScriptKit"],
swiftSettings: [
// Still required for the generated code
.enableExperimentalFeature("Extern")
]
// Notice we DON'T include the BridgeJS build plugin here
)
]
)
Step 2: Create Your Swift Code with @JS Annotations
Write your Swift code with @JS
annotations as usual:
import JavaScriptKit
@JS public func calculateTotal(price: Double, quantity: Int) -> Double {
return price * Double(quantity)
}
@JS class Counter {
private var count = 0
@JS init() {}
@JS func increment() {
count += 1
}
@JS func getValue() -> Int {
return count
}
}
Step 3: Create Your TypeScript Definitions
If you’re importing JavaScript APIs, create your bridge.d.ts
file as usual:
// Sources/MyApp/bridge.d.ts
export function consoleLog(message: string): void;
export interface Document {
title: string;
getElementById(id: string): HTMLElement;
}
export function getDocument(): Document;
Step 4: Generate the Bridge Code
Run the command plugin to generate the bridge code:
swift package plugin bridge-js
This command will:
Process all Swift files with
@JS
annotationsProcess any TypeScript definition files
Generate Swift binding code in a
Generated
directory within your source folder
For example, with a target named “MyApp”, it will create:
Sources/MyApp/Generated/ExportSwift.swift # Generated code for Swift exports
Sources/MyApp/Generated/ImportTS.swift # Generated code for TypeScript imports
Sources/MyApp/Generated/JavaScript/ # Generated JSON skeletons
Step 5: Add Generated Files to Version Control
Add these generated files to your version control system:
git add Sources/MyApp/Generated
git commit -m "Add generated BridgeJS code"
Step 6: Build Your Package
Now you can build your package as usual:
swift package --swift-sdk $SWIFT_SDK_ID js
Since the bridge code is already generated, the build will be faster.
Options for Selective Code Generation
The command plugin supports targeting specific modules in your package:
# Generate bridge code only for the specified target
swift package plugin bridge-js --target MyApp
Updating Generated Code
When you change your Swift code or TypeScript definitions, you’ll need to regenerate the bridge code:
# Regenerate bridge code
swift package plugin bridge-js
git add Sources/MyApp/Generated
git commit -m "Update generated BridgeJS code"
When to Use Each Approach
Use the build plugin when:
You’re developing a small project or prototype
You frequently change your API boundaries
You want the simplest setup
Use the command plugin when:
You’re developing a larger project
Build time is a concern
You want to inspect and version control the generated code
You’re working in a team and want to ensure consistent builds
Best Practices
Consistency: Choose either the build plugin or the command plugin approach for your project
Version Control: Always commit the generated files if using the command plugin
API Boundaries: Try to stabilize your API boundaries to minimize regeneration
Documentation: Document your approach in your project README
CI/CD: If using the command plugin, consider verifying that generated code is up-to-date in CI