[JS IR] Re-export all JS-exports from main module in multi-module mode
Add an implicit transitive re-export through main module for all other module's JS-exports.
This commit is contained in:
committed by
Space
parent
d52fa8dd9b
commit
b40227b66e
+5
-5
@@ -154,7 +154,7 @@ class IrModuleToJsTransformer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val moduleBody = generateModuleBody(modules, staticContext)
|
val moduleBody = generateModuleBody(modules, staticContext)
|
||||||
val internalModuleName = JsName("_", false)
|
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||||
val globalNames = NameTable<String>(namer.globalNames)
|
val globalNames = NameTable<String>(namer.globalNames)
|
||||||
val exportStatements = ExportModelToJsStatements(nameGenerator) { globalNames.declareFreshName(it, it) }
|
val exportStatements = ExportModelToJsStatements(nameGenerator) { globalNames.declareFreshName(it, it) }
|
||||||
.generateModuleExport(exportedModule, internalModuleName)
|
.generateModuleExport(exportedModule, internalModuleName)
|
||||||
@@ -256,7 +256,7 @@ class IrModuleToJsTransformer(
|
|||||||
modules += JsImportedModule(module.externalModuleName(), moduleName, null, relativeRequirePath)
|
modules += JsImportedModule(module.externalModuleName(), moduleName, null, relativeRequirePath)
|
||||||
|
|
||||||
names.forEach {
|
names.forEach {
|
||||||
imports += JsVars(JsVars.JsVar(JsName(it, false), JsNameRef(it, JsNameRef("\$crossModule\$", moduleName.makeRef()))))
|
imports += JsVars(JsVars.JsVar(JsName(it, false), JsNameRef(it, ReservedJsNames.makeCrossModuleNameRef(moduleName))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,15 +271,15 @@ class IrModuleToJsTransformer(
|
|||||||
return modules.flatMap {
|
return modules.flatMap {
|
||||||
refInfo.exports(it).map {
|
refInfo.exports(it).map {
|
||||||
jsAssignment(
|
jsAssignment(
|
||||||
JsNameRef(it, JsNameRef("\$crossModule\$", internalModuleName.makeRef())),
|
JsNameRef(it, ReservedJsNames.makeCrossModuleNameRef(internalModuleName)),
|
||||||
JsNameRef(it)
|
JsNameRef(it)
|
||||||
).makeStmt()
|
).makeStmt()
|
||||||
}
|
}
|
||||||
}.let {
|
}.let {
|
||||||
if (!it.isEmpty()) {
|
if (!it.isEmpty()) {
|
||||||
val createExportBlock = jsAssignment(
|
val createExportBlock = jsAssignment(
|
||||||
JsNameRef("\$crossModule\$", internalModuleName.makeRef()),
|
ReservedJsNames.makeCrossModuleNameRef(internalModuleName),
|
||||||
JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral())
|
JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral())
|
||||||
).makeStmt()
|
).makeStmt()
|
||||||
return listOf(createExportBlock) + it
|
return listOf(createExportBlock) + it
|
||||||
} else it
|
} else it
|
||||||
|
|||||||
+3
-3
@@ -173,7 +173,7 @@ class IrModuleToJsTransformerTmp(
|
|||||||
|
|
||||||
val result = JsIrProgramFragment(file.fqName.asString())
|
val result = JsIrProgramFragment(file.fqName.asString())
|
||||||
|
|
||||||
val internalModuleName = JsName("_", false)
|
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||||
val globalNames = NameTable<String>(globalNameScope)
|
val globalNames = NameTable<String>(globalNameScope)
|
||||||
val exportStatements =
|
val exportStatements =
|
||||||
ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport(
|
ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport(
|
||||||
@@ -316,8 +316,8 @@ fun generateWrappedModuleBody(
|
|||||||
|
|
||||||
val moduleToRef = program.crossModuleDependencies(relativeRequirePath)
|
val moduleToRef = program.crossModuleDependencies(relativeRequirePath)
|
||||||
|
|
||||||
val main = program.modules.last()
|
val main = program.mainModule
|
||||||
val others = program.modules.dropLast(1)
|
val others = program.otherModules
|
||||||
|
|
||||||
val mainModule = generateSingleWrappedModuleBody(
|
val mainModule = generateSingleWrappedModuleBody(
|
||||||
mainModuleName,
|
mainModuleName,
|
||||||
|
|||||||
+11
-3
@@ -26,11 +26,14 @@ class JsIrProgramFragment(val packageFqn: String) {
|
|||||||
class JsIrModule(val moduleName: String, val externalModuleName: String, val fragments: List<JsIrProgramFragment>)
|
class JsIrModule(val moduleName: String, val externalModuleName: String, val fragments: List<JsIrProgramFragment>)
|
||||||
|
|
||||||
class JsIrProgram(val modules: List<JsIrModule>) {
|
class JsIrProgram(val modules: List<JsIrModule>) {
|
||||||
|
val mainModule = modules.last()
|
||||||
|
val otherModules = modules.dropLast(1)
|
||||||
|
|
||||||
fun crossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModule, CrossModuleReferences> {
|
fun crossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModule, CrossModuleReferences> {
|
||||||
val moduleToBuilder = modules.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) }
|
val moduleToBuilder = modules.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) }
|
||||||
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>()
|
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>()
|
||||||
|
|
||||||
|
moduleToBuilder[mainModule]!!.transitiveJsExportFrom = otherModules
|
||||||
for (module in modules) {
|
for (module in modules) {
|
||||||
val moduleBuilder = moduleToBuilder[module]!!
|
val moduleBuilder = moduleToBuilder[module]!!
|
||||||
for (fragment in module.fragments) {
|
for (fragment in module.fragments) {
|
||||||
@@ -65,6 +68,7 @@ private class CrossModuleRef(val module: JsIrModuleCrossModuleReferecenceBuilder
|
|||||||
private class JsIrModuleCrossModuleReferecenceBuilder(val module: JsIrModule, val relativeRequirePath: Boolean) {
|
private class JsIrModuleCrossModuleReferecenceBuilder(val module: JsIrModule, val relativeRequirePath: Boolean) {
|
||||||
val imports = mutableListOf<CrossModuleRef>()
|
val imports = mutableListOf<CrossModuleRef>()
|
||||||
val exports = mutableSetOf<String>()
|
val exports = mutableSetOf<String>()
|
||||||
|
var transitiveJsExportFrom = emptyList<JsIrModule>()
|
||||||
|
|
||||||
private lateinit var exportNames: Map<String, String> // tag -> name
|
private lateinit var exportNames: Map<String, String> // tag -> name
|
||||||
|
|
||||||
@@ -108,12 +112,15 @@ private class JsIrModuleCrossModuleReferecenceBuilder(val module: JsIrModule, va
|
|||||||
val importedAs = tagToName[tag]!!
|
val importedAs = tagToName[tag]!!
|
||||||
val moduleName = it.module.module.import()
|
val moduleName = it.module.module.import()
|
||||||
|
|
||||||
val importStatement = JsVars.JsVar(importedAs, JsNameRef(exportedAs, JsNameRef("\$crossModule\$", moduleName.makeRef())))
|
val importStatement = JsVars.JsVar(importedAs, JsNameRef(exportedAs, ReservedJsNames.makeCrossModuleNameRef(moduleName)))
|
||||||
|
|
||||||
tag to importStatement
|
tag to importStatement
|
||||||
}
|
}
|
||||||
|
|
||||||
return CrossModuleReferences(importedModules.values.toList(), resultImports, exportNames)
|
val transitiveExport = transitiveJsExportFrom.mapNotNull {
|
||||||
|
it.fragments.find { f -> !f.exports.isEmpty }?.run { it.import() }
|
||||||
|
}
|
||||||
|
return CrossModuleReferences(importedModules.values.toList(), resultImports, exportNames, transitiveExport)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +128,9 @@ class CrossModuleReferences(
|
|||||||
val importedModules: List<JsImportedModule>, // additional Kotlin imported modules
|
val importedModules: List<JsImportedModule>, // additional Kotlin imported modules
|
||||||
val imports: Map<String, JsVars.JsVar>, // tag -> import statement
|
val imports: Map<String, JsVars.JsVar>, // tag -> import statement
|
||||||
val exports: Map<String, String>, // tag -> name
|
val exports: Map<String, String>, // tag -> name
|
||||||
|
val transitiveJsExportFrom: List<JsName> // the list of modules which provide their js exports for transitive export
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val Empty = CrossModuleReferences(listOf(), emptyMap(), emptyMap())
|
val Empty = CrossModuleReferences(listOf(), emptyMap(), emptyMap(), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+37
-10
@@ -63,20 +63,18 @@ class Merger(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (crossModuleReferences.exports.isNotEmpty()) {
|
if (crossModuleReferences.exports.isNotEmpty()) {
|
||||||
val internalModuleName = JsName("_", false)
|
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||||
|
|
||||||
val createExportBlock = jsAssignment(
|
val createExportBlock = jsAssignment(
|
||||||
JsNameRef("\$crossModule\$", internalModuleName.makeRef()),
|
ReservedJsNames.makeCrossModuleNameRef(internalModuleName),
|
||||||
JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral())
|
JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral())
|
||||||
).makeStmt()
|
).makeStmt()
|
||||||
additionalExports += createExportBlock
|
additionalExports += createExportBlock
|
||||||
|
|
||||||
crossModuleReferences.exports.entries.forEach { (tag, name) ->
|
crossModuleReferences.exports.entries.forEach { (tag, name) ->
|
||||||
val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'")
|
val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'")
|
||||||
additionalExports += jsAssignment(
|
val crossModuleRef = ReservedJsNames.makeCrossModuleNameRef(ReservedJsNames.makeInternalModuleName())
|
||||||
JsNameRef(name, JsNameRef("\$crossModule\$", JsName("_", false).makeRef())),
|
additionalExports += jsAssignment(JsNameRef(name, crossModuleRef), JsNameRef(internalName)).makeStmt()
|
||||||
JsNameRef(internalName)
|
|
||||||
).makeStmt()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,6 +124,36 @@ class Merger(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun declareAndCallJsExporter(): List<JsStatement> {
|
||||||
|
val exportBody = JsBlock(fragments.flatMap { it.exports.statements })
|
||||||
|
if (exportBody.isEmpty) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||||
|
val exporterName = ReservedJsNames.makeJsExporterName()
|
||||||
|
val jsExporterFunction = JsFunction(emptyScope, "js exporter function").apply {
|
||||||
|
body = exportBody
|
||||||
|
name = exporterName
|
||||||
|
parameters.add(JsParameter(internalModuleName))
|
||||||
|
}
|
||||||
|
val jsExporterCall = JsInvocation(exporterName.makeRef(), internalModuleName.makeRef())
|
||||||
|
val result = mutableListOf(jsExporterFunction.makeStmt(), jsExporterCall.makeStmt())
|
||||||
|
if (!generateCallToMain) {
|
||||||
|
val exportExporter = jsAssignment(JsNameRef(exporterName, internalModuleName.makeRef()), exporterName.makeRef())
|
||||||
|
result += exportExporter.makeStmt()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transitiveJsExport(): List<JsStatement> {
|
||||||
|
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||||
|
val exporterName = ReservedJsNames.makeJsExporterName()
|
||||||
|
return crossModuleReferences.transitiveJsExportFrom.map {
|
||||||
|
JsInvocation(JsNameRef(exporterName, it.makeRef()), internalModuleName.makeRef()).makeStmt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun merge(): JsProgram {
|
fun merge(): JsProgram {
|
||||||
assertSingleDefinition()
|
assertSingleDefinition()
|
||||||
|
|
||||||
@@ -176,13 +204,11 @@ class Merger(
|
|||||||
|
|
||||||
val callToMain = fragments.sortedBy { it.packageFqn }.firstNotNullOfOrNull { it.mainFunction }
|
val callToMain = fragments.sortedBy { it.packageFqn }.firstNotNullOfOrNull { it.mainFunction }
|
||||||
|
|
||||||
val exportStatements = fragments.flatMap { it.exports.statements } + additionalExports
|
val exportStatements = declareAndCallJsExporter() + additionalExports + transitiveJsExport()
|
||||||
|
|
||||||
val importedJsModules = this.importedModulesMap.values.toList() + this.crossModuleReferences.importedModules
|
val importedJsModules = this.importedModulesMap.values.toList() + this.crossModuleReferences.importedModules
|
||||||
val importStatements = this.importStatements.values.toList()
|
val importStatements = this.importStatements.values.toList()
|
||||||
|
|
||||||
val internalModuleName = JsName("_", false)
|
|
||||||
|
|
||||||
val program = JsProgram()
|
val program = JsProgram()
|
||||||
|
|
||||||
if (generateScriptModule) {
|
if (generateScriptModule) {
|
||||||
@@ -192,6 +218,7 @@ class Merger(
|
|||||||
this.statements.addWithComment("block: exports", exportStatements)
|
this.statements.addWithComment("block: exports", exportStatements)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||||
val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply {
|
val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply {
|
||||||
parameters += JsParameter(internalModuleName)
|
parameters += JsParameter(internalModuleName)
|
||||||
parameters += (importedJsModules).map { JsParameter(it.internalName) }
|
parameters += (importedJsModules).map { JsParameter(it.internalName) }
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.JsName
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.JsNameRef
|
||||||
|
|
||||||
|
class ReservedJsNames {
|
||||||
|
companion object {
|
||||||
|
fun makeInternalModuleName() = JsName("_", false)
|
||||||
|
fun makeJsExporterName() = JsName("\$jsExportAll\$", false)
|
||||||
|
fun makeCrossModuleNameRef(moduleName: JsName) = JsNameRef("\$crossModule\$", moduleName.makeRef())
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -7258,6 +7258,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/multiModule/clashedInternalDeclarations.kt");
|
runTest("js/js.translator/testData/box/multiModule/clashedInternalDeclarations.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("exportFromModules.kt")
|
||||||
|
public void testExportFromModules() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/multiModule/exportFromModules.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("interfaceMethodWithDefaultParameter.kt")
|
@TestMetadata("interfaceMethodWithDefaultParameter.kt")
|
||||||
public void testInterfaceMethodWithDefaultParameter() throws Exception {
|
public void testInterfaceMethodWithDefaultParameter() throws Exception {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
$kotlin_test_internal$.beginModule();
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
var demoPackage = require("main").demoPackage
|
||||||
|
|
||||||
|
var a1 = demoPackage.a1.ModuleA1Class
|
||||||
|
var a2 = demoPackage.a2.moduleA2Function
|
||||||
|
var bb = demoPackage.b.moduleBFunction
|
||||||
|
|
||||||
|
return {
|
||||||
|
"moduleA1": (new a1()).toString(),
|
||||||
|
"moduleA2": a2().toString(),
|
||||||
|
"moduleB": bb()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$kotlin_test_internal$.endModule("lib");
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// DONT_TARGET_EXACT_BACKEND: JS
|
||||||
|
// KJS_WITH_FULL_RUNTIME
|
||||||
|
|
||||||
|
// MODULE: ModuleA1
|
||||||
|
// FILE: modulea1.kt
|
||||||
|
package demoPackage.a1
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
data class ModuleA1Class(private val string: String = "A1") {
|
||||||
|
override fun toString(): String = string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODULE: ModuleA2(ModuleA1)
|
||||||
|
// FILE: modulea2.kt
|
||||||
|
package demoPackage.a2
|
||||||
|
|
||||||
|
import demoPackage.a1.ModuleA1Class
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
fun moduleA2Function() = ModuleA1Class("A2")
|
||||||
|
|
||||||
|
// MODULE: ModuleB
|
||||||
|
// FILE: moduleb.kt
|
||||||
|
package demoPackage.b
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
fun moduleBFunction() = "B"
|
||||||
|
|
||||||
|
// MODULE: main(ModuleA2, ModuleB)
|
||||||
|
// MODULE_KIND: COMMON_JS
|
||||||
|
// FILE: main.kt
|
||||||
|
|
||||||
|
package mainPackage
|
||||||
|
|
||||||
|
import demoPackage.*
|
||||||
|
|
||||||
|
external interface JsResult {
|
||||||
|
val moduleA1: String
|
||||||
|
val moduleA2: String
|
||||||
|
val moduleB: String
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsModule("lib")
|
||||||
|
external fun jsBox(): JsResult
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
assertEquals(demoPackage.b.moduleBFunction(), "B")
|
||||||
|
|
||||||
|
val res = jsBox()
|
||||||
|
assertEquals(res.moduleA1, "A1")
|
||||||
|
assertEquals(res.moduleA2, "A2")
|
||||||
|
assertEquals(res.moduleB, "B")
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user