Compile one Kotlin file to one JS file with kotlinc-js without Gradle

I eschew Gradle and would like something simpler just for testing simple stuff, I have tried so hard to get this to work but I can’t seem to figure out how to compile ONE Kotlin file to ONE JavaScript file. All the information I can find online is for the <2.x compiler, and the documentation at Kotlin compiler options | Kotlin Documentation seems to have gotten out of sync with what’s shown with kotlinc-js -help.

I have one file named Main.kt:

fun main() {
    println("Hello!")
}
$ kotlinc-js Main.kt -ir-output-dir build -ir-output-name Main -module-kind commonjs -main call
Main.kt:2:2: error: unresolved reference 'println'.
	println("Hello!")
 ^^^^^^^
Main.kt:2:10: error: missing stdlib class.
	println("Hello!")
         ^^^^^^^^

Trying to point it to the libraries succeeds

kotlinc-js Main.kt -ir-output-dir build -ir-output-name Main -module-kind commonjs -main call -libraries /home/fish/.sdkman/candidates/kotlin/2.2.21/lib/kotlin-stdlib-js.klib

but then it just generates this zip file full of IR:

build.zip (3.4 KB)

I then tried

kotlinc-js Main.kt -ir-output-dir build -ir-output-name Main -module-kind commonjs -main call -Xinclude=/home/fish/.sdkman/candidates/kotlin/2.2.21/lib/kotlin-stdlib-js.klib

which seemed to maybe almost work, but eventually resulted in

ValidateIrBeforeLowering
UpgradeCallableReferences
JsCodeOutliningLoweringOnSecondStage
LateinitLowering
SharedVariablesLowering
LocalClassesInInlineLambdasPhase
ArrayConstructor
InlineOnlyPrivateFunctions
OuterThisInInlineFunctionsSpecialAccessorLowering
SyntheticAccessorGeneration
IrValidationAfterInliningOnlyPrivateFunctionsPhase
InlineAllFunctions
IrValidationAfterInliningAllFunctionsPhase
ConstEvaluationLowering
CopyInlineFunctionBody
RemoveInlineFunctionsWithReifiedTypeParametersLowering
ReplaceSuspendIntrinsicLowering
PrepareCollectionsToExportLowering
ExcludeSyntheticDeclarationsFromExportLowering
JsStaticLowering
InventNamesForLocalClasses
CollectClassIdentifiersLowering
AnnotationImplementation
ExpectDeclarationsRemoving
StripTypeAliasDeclarations
CreateScriptFunctionsPhase
JsStringConcatenationLowering
PropertyReferenceLowering
CallableReferenceLowering
SingleAbstractMethod
TailrecLowering
EnumClassConstructorLowering
EnumClassConstructorBodyLowering
LocalDelegatedPropertiesLowering
LocalDeclarationsLowering
LocalClassExtractionPhase
InnerClassesLowering
InnerClassesMemberBody
InnerClassConstructorCallsLowering
JsClassUsageInReflectionLowering
PropertiesLowering
PrimaryConstructorLowering
DelegateToSyntheticPrimaryConstructor
AnnotationConstructorLowering
InitializersLowering
InitializersCleanupLowering
KotlinNothingValueException
CollectClassDefaultConstructorsLowering
EnumWhenLowering
EnumEntryInstancesLowering
EnumEntryInstancesBodyLowering
EnumClassCreateInitializerLowering
EnumEntryCreateGetInstancesFunsLowering
EnumSyntheticFunctionsAndPropertiesLowering
EnumUsageLowering
ExternalEnumUsagesLowering
EnumEntryRemovalLowering
SuspendFunctionsLowering
InteropCallableReferenceLowering
JsSuspendArityStoreLowering
AddContinuationToNonLocalSuspendFunctionsLowering
AddContinuationToLocalSuspendFunctionsLowering
AddContinuationToFunctionCallsLowering
JsReturnableBlockLowering
RangeContainsLowering
ForLoopsLowering
PrimitiveCompanionLowering
PropertyLazyInitLowering
RemoveInitializersForLazyProperties
PropertyAccessorInlineLowering
CopyAccessorBodyLowering
BooleanPropertyInExternalLowering
ExternalPropertyOverridingLowering
PrivateMembersLowering
PrivateMemberUsagesLowering
DefaultArgumentStubGenerator
DefaultArgumentsPatchOverrides
DefaultParameterInjector
DefaultParameterCleaner
CaptureStackTraceInThrowables
ThrowableLowering
VarargLowering
MultipleCatchesLowering
BridgesConstruction
TypeOperatorLowering
SecondaryConstructorLoweringPhase
SecondaryFactoryInjectorLoweringPhase
JsClassReferenceLowering
ConstLowering
InlineClassDeclarationLowering
InlineClassUsageLowering
ExpressionBodyTransformer
AutoboxingTransformer
ObjectDeclarationLowering
BlockDecomposerLowering
IntroduceStaticInitializersLowering
ObjectUsageLowering
ES6AddBoxParameterToConstructorsLowering
ES6SyntheticPrimaryConstructorLowering
ES6ConstructorLowering
ES6ConstructorCallLowering
CallsLowering
EscapedIdentifiersLowering
ImplicitlyExportedDeclarationsMarkingLowering
RemoveImplicitExportsFromCollections
MainFunctionCallWrapperLowering
CleanupLowering
ValidateIrAfterLowering
exception: java.lang.NullPointerException
	at org.jetbrains.kotlin.ir.backend.js.KlibKt.getIrModuleInfoForKlib(klib.kt:262)
	at org.jetbrains.kotlin.ir.backend.js.KlibKt.loadIr(klib.kt:197)
	at org.jetbrains.kotlin.ir.backend.js.JsCompilerKt.compile(jsCompiler.kt:55)
	at org.jetbrains.kotlin.ir.backend.js.JsCompilerKt.compile$default(jsCompiler.kt:42)
	at org.jetbrains.kotlin.cli.js.Ir2JsTransformer.lowerIr(K2JsCompilerImpl.kt:86)
	at org.jetbrains.kotlin.cli.js.Ir2JsTransformer.makeJsCodeGenerator(K2JsCompilerImpl.kt:105)
	at org.jetbrains.kotlin.cli.js.Ir2JsTransformer.compileAndTransformIrNew(K2JsCompilerImpl.kt:115)
	at org.jetbrains.kotlin.cli.pipeline.web.js.JsBackendPipelinePhase.compileNonIncrementally$cli_js(JsBackendPipelinePhase.kt:124)
	at org.jetbrains.kotlin.cli.pipeline.web.js.JsBackendPipelinePhase.compileNonIncrementally(JsBackendPipelinePhase.kt:98)
	at org.jetbrains.kotlin.cli.pipeline.web.js.JsBackendPipelinePhase.compileNonIncrementally(JsBackendPipelinePhase.kt:32)
	at org.jetbrains.kotlin.cli.pipeline.web.WebBackendPipelinePhase.executePhase(WebBackendPipelinePhase.kt:121)
	at org.jetbrains.kotlin.cli.pipeline.web.WebBackendPipelinePhase.executePhase(WebBackendPipelinePhase.kt:31)
	at org.jetbrains.kotlin.cli.pipeline.PipelinePhase.phaseBody(PipelinePhase.kt:68)
	at org.jetbrains.kotlin.cli.pipeline.PipelinePhase.phaseBody(PipelinePhase.kt:58)
	at org.jetbrains.kotlin.config.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:102)
	at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:22)
	at org.jetbrains.kotlin.config.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:53)
	at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.runPhasedPipeline(AbstractCliPipeline.kt:109)
	at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.execute(AbstractCliPipeline.kt:68)
	at org.jetbrains.kotlin.cli.js.K2JSCompiler.doExecutePhased(K2JSCompiler.kt:66)
	at org.jetbrains.kotlin.cli.js.K2JSCompiler.doExecutePhased(K2JSCompiler.kt:53)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:90)
	at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:352)
	at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:330)
	at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:294)
	at org.jetbrains.kotlin.cli.common.CLICompiler$Companion.doMainNoExit(CLICompiler.kt:431)
	at org.jetbrains.kotlin.cli.common.CLICompiler$Companion.doMainNoExit$default(CLICompiler.kt:426)
	at org.jetbrains.kotlin.cli.common.CLICompiler$Companion.doMain(CLICompiler.kt:418)
	at org.jetbrains.kotlin.cli.js.K2JSCompiler$Companion.main(K2JSCompiler.kt:321)
	at org.jetbrains.kotlin.cli.js.K2JSCompiler.main(K2JSCompiler.kt)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.jetbrains.kotlin.preloading.Preloader.run(Preloader.java:87)
	at org.jetbrains.kotlin.preloading.Preloader.main(Preloader.java:44)

I don’t know how to do this, and the documentation, CLI, and LLMs all aren’t helping.

Hi there.
Since we don’t pay much attention to the compiler CLI, it’s not quite obvious and intuitive how to do this, but it’s possible.
The compilation process is split into two phases:

  • Compilation of Kotlin sources to KLIB
  • Compilation of KLIB into JS

So, for the first stage you can run CLI with the following arguments:

# First phase
kotlinc-js Main.kt -ir-output-dir build.klib -ir-output-name Main -libraries /home/fish/.sdkman/candidates/kotlin/2.2.21/lib/kotlin-stdlib-js.klib

After that there will be the same zip file as you shared called build.klib, so you can use it to produce JS files.

Note: Per file compilation you’ve mentioned doesn’t work with commonjs, so only ES modules could be produces

# Second phase
kotlinc-js -Xir-produce-js -libraries /home/fish/.sdkman/candidates/kotlin/2.2.21/lib/kotlin-stdlib-js.klib -Xinclude=$PWD/build.klib -module-kind=es -Xir-per-file -ir-output-dir build -ir-output-name main

After that there will be a directory called build with the per-file compiled JS files.