Read this in Chinese: README.zh-CN.md
SWC JVM binding in Kotlin.
- swc-binding
implementation("dev.yidafu.swc:swc-binding:0.8.0")| swc-binding | Rust SWC | @swc/types | Notes |
|---|---|---|---|
| 0.8.0 | swc_core 48.0.4 | 0.1.25 | Latest stable release with swc_core migration |
| 0.7.0 | 43.0.0 | 0.1.25 | Previous stable release |
| 0.6.0 | 0.270.25 | 0.1.5 | Legacy version with individual swc crates |
- API Documentation - Kotlin Doc
- Project Wiki - Complete Guide - Auto-generated from repo wiki, includes architecture, usage guides, and detailed explanations
val swc = SwcNative()
val output = swc.transformSync(
code = "const x: number = 42",
isModule = true,
options = Options().apply {
jsc = jscConfig {
parser = ParserConfig().apply { syntax = "typescript" }
}
}
)
println(output.code)val swc = SwcNative()
val res = swc.transformSync(
"""
import x from './test.js';
class Foo {
bar: string
}
""".trimIndent(),
false,
Options().apply {
jsc = jscConfig {
parser = ParserConfig().apply {
syntax = "ecmascript"
}
}
}
)val ast = SwcNative().parseSync(
"""
import x from './test.js';
class Foo {
bar: string
}
""".trimIndent(),
ParseOptions().apply { syntax = "typescript" },
"temp.js"
)All SWC methods now support asynchronous execution using Kotlin coroutines. Async methods run in background threads and don't block the calling thread.
import kotlinx.coroutines.*
// Async parse
suspend fun parseCode() {
val swc = SwcNative()
val options = ParserConfig(
syntax = Syntax.Typescript(TsSyntax(tsx = true)),
target = JscTarget.Es2020
)
val ast = swc.parseAsync(
code = "const x: number = 42;",
options = options,
filename = "example.ts"
)
println("Parsed: ${ast.type}")
}
// Parallel parsing
suspend fun parseMultiple() = coroutineScope {
val swc = SwcNative()
val options = ParserConfig(/* ... */)
val results = listOf("code1.ts", "code2.ts", "code3.ts")
.map { file ->
async { swc.parseFileAsync(file, options) }
}
.awaitAll()
}val swc = SwcNative()
swc.parseAsync(
code = "const x: number = 42;",
options = parseOptions,
filename = "example.ts",
onSuccess = { ast -> println("Success: ${ast.type}") },
onError = { error -> println("Error: $error") }
)All synchronous methods have async counterparts:
parseAsync()- Asynchronously parse codeparseFileAsync()- Asynchronously parse filetransformAsync()- Asynchronously transform codetransformFileAsync()- Asynchronously transform fileprintAsync()- Asynchronously print ASTminifyAsync()- Asynchronously minify code
- Async methods immediately return and execute work in background threads
- Callbacks are invoked from Rust background threads
- Use coroutines for structured concurrency and easy thread management
- No blocking of the calling thread
See AsyncSamples.kt for more examples.
parsing React source code
see swc#parseSync
Kotlin DSL Wrapper method
@Throws(RuntimeException::class)
fun parseSync(code: String, options: ParserConfig, filename: String?): Program Native method
@Throws(RuntimeException::class)
fun parseSync(code: String, options: String, filename: String?): StringKotlin DSL Wrapper method
@Throws(RuntimeException::class)
fun parseFileSync(filepath: String, options: ParserConfig): Program Native method
@Throws(RuntimeException::class)
fun parseFileSync(filepath: String, options: String): StringKotlin DSL Wrapper method
@Throws(RuntimeException::class)
fun transformSync(code: String, isModule: Boolean, options: Options): TransformOutputNative method
@Throws(RuntimeException::class)
fun transformSync(code: String, isModule: Boolean, options: String): StringKotlin DSL Wrapper method
@Throws(RuntimeException::class)
fun transformFileSync(filepath: String, isModule: Boolean, options: Options): TransformOutputNative method
@Throws(RuntimeException::class)
fun transformFileSync(filepath: String, isModule: Boolean, options: String): StringKotlin DSL Wrapper method
@Throws(RuntimeException::class)
fun printSync(program: Program, options: Options): TransformOutputNative method
@Throws(RuntimeException::class)
fun printSync(program: String, options: String): StringKotlin DSL Wrapper method
@Throws(RuntimeException::class)
fun minifySync(program: Program, options: Options): TransformOutputNative method
@Throws(RuntimeException::class)
fun minifySync(program: String, options: String): StringTo build the entire project:
./gradlew buildRun all tests:
./gradlew testRun tests for a specific module:
./gradlew :swc-binding:testThis project uses the NMCP (New Maven Central Publisher) plugin for publishing to Maven Central.
- Maven Central Account: Create an account at Maven Central Portal
- Namespace Verification: Verify your namespace (
dev.yidafu.swc) in the portal - GPG Key: Set up a GPG key for signing artifacts
Create a local.properties file in the project root:
# Maven Central Portal credentials
centralUsername=your-portal-username
centralPassword=your-portal-password
# GPG signing credentials
signing.key=your-gpg-private-key
signing.password=your-gpg-passwordOr use environment variables:
export CENTRAL_USERNAME=your-portal-username
export CENTRAL_PASSWORD=your-portal-password
export SIGNING_KEY=your-gpg-private-key
export SIGNING_PASSWORD=your-gpg-passwordTo publish to Maven Central Portal:
./gradlew :swc-binding:publishSonatypePublicationToCentralPortalOr publish all publications:
./gradlew :swc-binding:publishAllPublicationsToCentralPortalFor testing, you can publish to your local Maven repository:
./gradlew :swc-binding:publishToMavenLocal- Version Requirements: Central Portal does NOT support SNAPSHOT versions. Only release versions are allowed
- Publication Type: Currently configured as
USER_MANAGED, which requires manual approval in the Central Portal UI - Namespace: Make sure your namespace (
dev.yidafu.swc) is verified in the Central Portal before publishing
import x from './test.js';
class Foo {
bar: string
}The JS code above is equivalent to the Kotlin AST DSL below.
module {
body = arrayOf(
importDeclaration {
specifiers = arrayOf(
importDefaultSpecifier {
local = createIdentifier {
span = emptySpan()
value = "x"
}
}
)
source = stringLiteral {
value= "./test.js"
raw = "./test.js"
span = emptySpan()
}
typeOnly = false
span = emptySpan()
},
classDeclaration {
identifier = createIdentifier { }
span = emptySpan()
body = arrayOf(
classProperty {
span = emptySpan()
typeAnnotation = tsTypeAnnotation {
span = emptySpan()
typeAnnotation = tsKeywordType {
span = emptySpan()
kind = TsKeywordTypeKind.STRING
}
}
}
)
}
)
}If you want to create an AST segment, call the createXxx function to create the segment.
createVariableDeclaration {
span = span(0, 17, 0)
kind = 'const'
declare = false
declarations = arrayOf(
variableDeclaratorImpl {
span = span(6, 17, 0)
id = identifier {
span = span(5, 9, 0)
value = "foo"
}
init = stringLiteral {
span = span(12,17, 0)
value = "bar"
raw = "'bar'"
}
}
)
}Some SWC options accept a union type boolean | T, for example:
export interface Config {
sourceMaps?: boolean | "inline";
}You can express this directly using Union.U2<Boolean, T>:
import dev.yidafu.swc.Union
options {
sourceMaps = Union.U2<Boolean, String>(b = "inline")
// or
sourceMaps = Union.U2<Boolean, String>(a = false)
}Union.U2 can also be applied to properties like configFile, isModule, and lazy, providing a general way to support union types such as boolean | Array<T> and boolean | MatchPattern[].
How to implement SWC JVM binding -- English translation -- 中文原文
When using externalHelpers = false, SWC should inline helper functions directly into the output code instead of importing them from @swc/helpers. However, the current Rust implementation of SWC used in this project does not respect this configuration correctly.
Current Behavior:
- With
externalHelpers = false: SWC still generates imports from@swc/helpers - With
externalHelpers = true: SWC generates imports from@swc/helpers
Expected Behavior:
- With
externalHelpers = false: SWC should inline helper functions (no imports from@swc/helpers) - With
externalHelpers = true: SWC should generate imports from@swc/helpers
This is a known difference between the Rust and Node.js versions of SWC. The configuration is correctly parsed and passed to SWC, but the Rust implementation does not inline helpers even when external_helpers is set to false.
Workaround: For now, tests have been updated to match the actual Rust behavior rather than the expected behavior. This issue may be addressed in a future update when the Rust implementation is fixed or when a workaround is identified.
MIT