[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 internalModuleName = JsName("_", false)
|
||||
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||
val globalNames = NameTable<String>(namer.globalNames)
|
||||
val exportStatements = ExportModelToJsStatements(nameGenerator) { globalNames.declareFreshName(it, it) }
|
||||
.generateModuleExport(exportedModule, internalModuleName)
|
||||
@@ -256,7 +256,7 @@ class IrModuleToJsTransformer(
|
||||
modules += JsImportedModule(module.externalModuleName(), moduleName, null, relativeRequirePath)
|
||||
|
||||
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 {
|
||||
refInfo.exports(it).map {
|
||||
jsAssignment(
|
||||
JsNameRef(it, JsNameRef("\$crossModule\$", internalModuleName.makeRef())),
|
||||
JsNameRef(it, ReservedJsNames.makeCrossModuleNameRef(internalModuleName)),
|
||||
JsNameRef(it)
|
||||
).makeStmt()
|
||||
}
|
||||
}.let {
|
||||
if (!it.isEmpty()) {
|
||||
val createExportBlock = jsAssignment(
|
||||
JsNameRef("\$crossModule\$", internalModuleName.makeRef()),
|
||||
JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral())
|
||||
ReservedJsNames.makeCrossModuleNameRef(internalModuleName),
|
||||
JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral())
|
||||
).makeStmt()
|
||||
return listOf(createExportBlock) + it
|
||||
} else it
|
||||
|
||||
+3
-3
@@ -173,7 +173,7 @@ class IrModuleToJsTransformerTmp(
|
||||
|
||||
val result = JsIrProgramFragment(file.fqName.asString())
|
||||
|
||||
val internalModuleName = JsName("_", false)
|
||||
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||
val globalNames = NameTable<String>(globalNameScope)
|
||||
val exportStatements =
|
||||
ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport(
|
||||
@@ -316,8 +316,8 @@ fun generateWrappedModuleBody(
|
||||
|
||||
val moduleToRef = program.crossModuleDependencies(relativeRequirePath)
|
||||
|
||||
val main = program.modules.last()
|
||||
val others = program.modules.dropLast(1)
|
||||
val main = program.mainModule
|
||||
val others = program.otherModules
|
||||
|
||||
val mainModule = generateSingleWrappedModuleBody(
|
||||
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 JsIrProgram(val modules: List<JsIrModule>) {
|
||||
val mainModule = modules.last()
|
||||
val otherModules = modules.dropLast(1)
|
||||
|
||||
fun crossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModule, CrossModuleReferences> {
|
||||
val moduleToBuilder = modules.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) }
|
||||
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>()
|
||||
|
||||
moduleToBuilder[mainModule]!!.transitiveJsExportFrom = otherModules
|
||||
for (module in modules) {
|
||||
val moduleBuilder = moduleToBuilder[module]!!
|
||||
for (fragment in module.fragments) {
|
||||
@@ -65,6 +68,7 @@ private class CrossModuleRef(val module: JsIrModuleCrossModuleReferecenceBuilder
|
||||
private class JsIrModuleCrossModuleReferecenceBuilder(val module: JsIrModule, val relativeRequirePath: Boolean) {
|
||||
val imports = mutableListOf<CrossModuleRef>()
|
||||
val exports = mutableSetOf<String>()
|
||||
var transitiveJsExportFrom = emptyList<JsIrModule>()
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
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 imports: Map<String, JsVars.JsVar>, // tag -> import statement
|
||||
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 {
|
||||
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()) {
|
||||
val internalModuleName = JsName("_", false)
|
||||
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||
|
||||
val createExportBlock = jsAssignment(
|
||||
JsNameRef("\$crossModule\$", internalModuleName.makeRef()),
|
||||
JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral())
|
||||
ReservedJsNames.makeCrossModuleNameRef(internalModuleName),
|
||||
JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral())
|
||||
).makeStmt()
|
||||
additionalExports += createExportBlock
|
||||
|
||||
crossModuleReferences.exports.entries.forEach { (tag, name) ->
|
||||
val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'")
|
||||
additionalExports += jsAssignment(
|
||||
JsNameRef(name, JsNameRef("\$crossModule\$", JsName("_", false).makeRef())),
|
||||
JsNameRef(internalName)
|
||||
).makeStmt()
|
||||
val crossModuleRef = ReservedJsNames.makeCrossModuleNameRef(ReservedJsNames.makeInternalModuleName())
|
||||
additionalExports += jsAssignment(JsNameRef(name, crossModuleRef), 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 {
|
||||
assertSingleDefinition()
|
||||
|
||||
@@ -176,13 +204,11 @@ class Merger(
|
||||
|
||||
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 importStatements = this.importStatements.values.toList()
|
||||
|
||||
val internalModuleName = JsName("_", false)
|
||||
|
||||
val program = JsProgram()
|
||||
|
||||
if (generateScriptModule) {
|
||||
@@ -192,6 +218,7 @@ class Merger(
|
||||
this.statements.addWithComment("block: exports", exportStatements)
|
||||
}
|
||||
} else {
|
||||
val internalModuleName = ReservedJsNames.makeInternalModuleName()
|
||||
val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply {
|
||||
parameters += JsParameter(internalModuleName)
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("exportFromModules.kt")
|
||||
public void testExportFromModules() throws Exception {
|
||||
runTest("js/js.translator/testData/box/multiModule/exportFromModules.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("interfaceMethodWithDefaultParameter.kt")
|
||||
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