JavaScript 모듈
JavaScript 모듈
여러 인기 모듈 시스템을 대상으로 Kotlin 프로젝트를 JavaScript 모듈로 컴파일할 수 있어요. 현재 JavaScript 모듈에 대해 다음 구성을 지원하고 있어요.
- ES Modules — JavaScript에서 모듈을 선언하는 표준 방식이에요 (
import/exportJavaScript 문법을 사용해요).target이es2015로 설정된 경우 기본값으로 사용돼요. - Unified Module Definitions (UMD) —
AMD와CommonJS둘 다와 호환돼요. UMD 모듈은 임포트하지 않거나 모듈 시스템이 없어도 실행할 수 있어요.browser와nodejs타깃의 기본 옵션이에요. - Asynchronous Module Definitions (AMD) — 특히 RequireJS 라이브러리에서 사용돼요.
- CommonJS — Node.js/npm에서 널리 사용돼요 (
require함수와module.exports객체). - Plain — 어떤 모듈 시스템으로도 컴파일하지 않아요. 전역 스코프에서 모듈 이름으로 접근할 수 있어요.
브라우저 타깃
웹 브라우저 환경에서 코드를 실행하려 하고 UMD가 아닌 다른 모듈 시스템을 쓰고 싶다면, webpackTask 구성 블록에서 원하는 모듈 타입을 지정할 수 있어요. 예를 들어 CommonJS로 전환하려면 이렇게 해요:
kotlin {
js {
browser {
webpackTask {
output.libraryTarget = "commonjs2"
}
}
binaries.executable()
}
}
Webpack은 commonjs와 commonjs2 두 가지 CommonJS 맛을 제공하는데, 이는 선언을 어떻게 공개할지에 영향을 줘요. 대부분의 경우 생성된 라이브러리에 module.exports 문법을 추가해 주는 commonjs2를 원할 거예요. 또는 CommonJS 명세를 엄격히 따르는 commonjs 옵션을 선택할 수도 있어요. commonjs와 commonjs2의 차이가 궁금하다면 Webpack 저장소를 참고하세요.
JavaScript 라이브러리와 Node.js 파일
JavaScript나 Node.js 환경에서 쓸 라이브러리를 만들면서 다른 모듈 시스템을 사용하려면, 방법이 조금 달라져요.
타깃 모듈 시스템 선택하기
타깃 모듈 시스템을 선택하려면 Gradle 빌드 스크립트에서 moduleKind 컴파일러 옵션을 설정해요:
tasks.withType<org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink> {
compilerOptions.moduleKind.set(org.jetbrains.kotlin.gradle.dsl.JsModuleKind.MODULE_COMMONJS)
}
compileKotlinJs.compilerOptions.moduleKind = org.jetbrains.kotlin.gradle.dsl.JsModuleKind.MODULE_COMMONJS
사용 가능한 값은 umd(기본값), es, commonjs, amd, plain이에요.
이는 webpackTask.output.libraryTarget을 조정하는 것과는 달라요. 라이브러리 타깃은 (코드가 이미 컴파일된 뒤) webpack이 생성하는 출력을 바꾸고, compilerOptions.moduleKind는 Kotlin 컴파일러가 생성하는 출력을 바꿔요.
Kotlin Gradle DSL에는 CommonJS와 ESM 모듈 종류를 설정하는 단축 메서드도 있어요:
kotlin {
js {
useCommonJs()
// OR
useEsModules()
// ...
}
}
@JsModule 어노테이션
external 클래스, 패키지, 함수, 프로퍼티가 JavaScript 모듈임을 Kotlin에 알려주려면 @JsModule 어노테이션을 사용해요. "hello"라는 CommonJS 모듈이 있다고 가정해 볼게요:
module.exports.sayHello = function (name) { alert("Hello, " + name); }
Kotlin에서는 이렇게 선언해야 해요:
@JsModule("hello")
external fun sayHello(name: String)
@JsModule을 패키지에 적용하기
일부 JavaScript 라이브러리는 함수나 클래스 대신 패키지(네임스페이스)를 내보내요. JavaScript 관점에서 보면 멤버가 클래스, 함수, 프로퍼티인 객체예요. 이런 패키지를 Kotlin 객체로 임포트하는 건 종종 어색해 보여요. 컴파일러는 다음 표기법을 사용해 임포트한 JavaScript 패키지를 Kotlin 패키지로 매핑할 수 있어요:
@file:JsModule("extModule")
package ext.jspackage.name
external fun foo()
external class C
그리고 이에 대응하는 JavaScript 모듈은 이렇게 선언돼 있어요:
module.exports = {
foo: { /* some code here */ },
C: { /* some code here */ }
}
@file:JsModule 어노테이션이 붙은 파일은 non-external 멤버를 선언할 수 없어요. 아래 예제는 컴파일 타임 에러를 만들어 내요:
@file:JsModule("extModule")
package ext.jspackage.name
external fun foo()
fun bar() = "!" + foo() + "!" // error here
더 깊은 패키지 계층 임포트하기
앞선 예제에서는 JavaScript 모듈이 단일 패키지를 내보냈어요. 하지만 일부 JavaScript 라이브러리는 모듈 안에서 여러 패키지를 내보내요. Kotlin도 이 경우를 지원하지만, 임포트하는 각 패키지마다 새 .kt 파일을 선언해야 해요.
예를 들어 예제를 조금 더 복잡하게 만들어 볼게요:
module.exports = {
mylib: {
pkg1: {
foo: function () { /* some code here */ },
bar: function () { /* some code here */ }
},
pkg2: {
baz: function () { /* some code here */ }
}
}
}
이 모듈을 Kotlin에서 임포트하려면 Kotlin 소스 파일 두 개를 작성해야 해요:
@file:JsModule("extModule")
@file:JsQualifier("mylib.pkg1")
package extlib.pkg1
external fun foo()
external fun bar()
그리고:
@file:JsModule("extModule")
@file:JsQualifier("mylib.pkg2")
package extlib.pkg2
external fun baz()
@JsNonModule 어노테이션
선언이 @JsModule로 표시되면, JavaScript 모듈로 컴파일하지 않는 한 Kotlin 코드에서 사용할 수 없어요. 보통 개발자들은 라이브러리를 JavaScript 모듈과, 프로젝트의 정적 리소스에 복사해서 <script> 태그로 포함할 수 있는 다운로드형 .js 파일, 두 형태로 배포해요. @JsModule 선언을 non-module 환경에서 사용해도 괜찮다고 Kotlin에 알려주려면 @JsNonModule 어노테이션을 추가해요. 예를 들어 다음 JavaScript 코드를 생각해 볼게요:
function topLevelSayHello (name) { alert("Hello, " + name); }
if (module && module.exports) {
module.exports = topLevelSayHello;
}
Kotlin에서는 다음과 같이 표현할 수 있어요:
@JsModule("hello")
@JsNonModule
@JsName("topLevelSayHello")
external fun sayHello(name: String)
Kotlin 표준 라이브러리가 사용하는 모듈 시스템
Kotlin은 단일 파일 형태의 Kotlin/JS 표준 라이브러리와 함께 배포되는데, 이 파일 자체가 UMD 모듈로 컴파일돼 있어서 위에서 설명한 어떤 모듈 시스템과도 함께 쓸 수 있어요. 대부분의 Kotlin/JS 사용 사례에서는 kotlin-stdlib-js에 대한 Gradle 의존성을 사용하는 걸 권장하며, NPM에서도 kotlin 패키지로 제공되고 있어요.