diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java index 0973c2345b8..4a7352703c2 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2021 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. */ @@ -516,7 +516,7 @@ public class K2JSCompiler extends CLICompiler { } @NotNull - private static String calculateSourceMapSourceRoot( + static String calculateSourceMapSourceRoot( @NotNull MessageCollector messageCollector, @NotNull K2JSCompilerArguments arguments ) { diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 02434c03820..7e645c9e928 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -284,10 +284,10 @@ class K2JsIrCompiler : CLICompiler() { ), ) - val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.outputsAfterDce!! else compiledModule.outputs!! - outputFile.writeText(jsCode.jsCode) - jsCode.dependencies.forEach { (name, content) -> - outputFile.resolveSibling("$name.js").writeText(content) + val outputs = if (arguments.irDce && !arguments.irDceDriven) compiledModule.outputsAfterDce!! else compiledModule.outputs!! + outputFile.write(outputs) + outputs.dependencies.forEach { (name, content) -> + outputFile.resolveSibling("$name.js").write(content) } if (arguments.generateDts) { val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!! @@ -298,6 +298,15 @@ class K2JsIrCompiler : CLICompiler() { return OK } + private fun File.write(outputs: CompilationOutputs) { + writeText(outputs.jsCode) + outputs.sourceMap?.let { + val mapFile = resolveSibling("$name.map") + appendText("\n//# sourceMappingURL=${mapFile.name}") + mapFile.writeText(it) + } + } + override fun setupPlatformSpecificArgumentsAndServices( configuration: CompilerConfiguration, arguments: K2JSCompilerArguments, @@ -310,9 +319,22 @@ class K2JsIrCompiler : CLICompiler() { } configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.defaultVersion()) - // TODO: Support source maps if (arguments.sourceMap) { - messageCollector.report(WARNING, "source-map argument is not supported yet", null) + configuration.put(JSConfigurationKeys.SOURCE_MAP, true) + if (arguments.sourceMapPrefix != null) { + configuration.put(JSConfigurationKeys.SOURCE_MAP_PREFIX, arguments.sourceMapPrefix!!) + } + + var sourceMapSourceRoots = arguments.sourceMapBaseDirs + if (sourceMapSourceRoots == null && StringUtil.isNotEmpty(arguments.sourceMapPrefix)) { + sourceMapSourceRoots = K2JSCompiler.calculateSourceMapSourceRoot(messageCollector, arguments) + } + + if (sourceMapSourceRoots != null) { + val sourceMapSourceRootList = StringUtil.split(sourceMapSourceRoots, File.pathSeparator) + configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceMapSourceRootList) + } + } else { if (arguments.sourceMapPrefix != null) { messageCollector.report(WARNING, "source-map-prefix argument has no effect without source map", null) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index 703553003be..0790fc4db96 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -32,7 +32,11 @@ class CompilerResult( val tsDefinitions: String? = null ) -class CompilationOutputs(val jsCode: String, val dependencies: Iterable> = emptyList()) +class CompilationOutputs( + val jsCode: String, + val sourceMap: String? = null, + val dependencies: Iterable> = emptyList() +) fun compile( project: Project, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index cb7326a8eda..ad2efbe884b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -5,8 +5,8 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs -import org.jetbrains.kotlin.ir.backend.js.CompilerResult import org.jetbrains.kotlin.ir.backend.js.CompilationOutputs +import org.jetbrains.kotlin.ir.backend.js.CompilerResult import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator @@ -19,9 +19,17 @@ import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.isInterface +import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor +import org.jetbrains.kotlin.js.backend.NoOpSourceLocationConsumer import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding +import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver +import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder +import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilderConsumer +import org.jetbrains.kotlin.js.util.TextOutputImpl import org.jetbrains.kotlin.utils.DFS +import java.io.File class IrModuleToJsTransformer( private val backendContext: JsIrBackendContext, @@ -104,16 +112,14 @@ class IrModuleToJsTransformer( ) }.reversed() - return CompilationOutputs(mainModule, dependencies) + return CompilationOutputs(mainModule.jsCode, mainModule.sourceMap, dependencies) } else { - return CompilationOutputs( - generateWrappedModuleBody2( - modules, - emptyList(), - exportedModule, - namer, - EmptyCrossModuleReferenceInfo - ) + return generateWrappedModuleBody2( + modules, + emptyList(), + exportedModule, + namer, + EmptyCrossModuleReferenceInfo ) } } @@ -124,7 +130,7 @@ class IrModuleToJsTransformer( exportedModule: ExportedModule, namer: NameTables, refInfo: CrossModuleReferenceInfo - ): String { + ): CompilationOutputs { val nameGenerator = refInfo.withReferenceTracking( IrNamerImpl(newNameTables = namer, backendContext), @@ -188,7 +194,41 @@ class IrModuleToJsTransformer( ) } - return program.toString() + val jsCode = TextOutputImpl() + + val configuration = backendContext.configuration + val sourceMapPrefix = configuration.get(JSConfigurationKeys.SOURCE_MAP_PREFIX, "") + val sourceMapsEnabled = configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP) + + val sourceMapBuilder = SourceMap3Builder(null, jsCode, sourceMapPrefix) + val sourceMapBuilderConsumer = + if (sourceMapsEnabled) { + val sourceRoots = configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, emptyList()).map(::File) + val generateRelativePathsInSourceMap = sourceMapPrefix.isEmpty() && sourceRoots.isEmpty() + val outputDir = if (generateRelativePathsInSourceMap) configuration.get(JSConfigurationKeys.OUTPUT_DIR) else null + + val pathResolver = SourceFilePathResolver(sourceRoots, outputDir) + + val sourceMapContentEmbedding = + configuration.get(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, SourceMapSourceEmbedding.INLINING) + + SourceMapBuilderConsumer( + File("."), + sourceMapBuilder, + pathResolver, + sourceMapContentEmbedding == SourceMapSourceEmbedding.ALWAYS, + sourceMapContentEmbedding != SourceMapSourceEmbedding.NEVER + ) + } else { + null + } + + program.accept(JsToStringGenerationVisitor(jsCode, sourceMapBuilderConsumer ?: NoOpSourceLocationConsumer)) + + return CompilationOutputs( + jsCode.toString(), + if(sourceMapsEnabled) sourceMapBuilder.build() else null + ) } private fun IrModuleFragment.externalModuleName(): String { diff --git a/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.java b/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.java index e9ccf129e7d..028f425a875 100644 --- a/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.java +++ b/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.java @@ -62,7 +62,7 @@ public class SourceMap3Builder implements SourceMapBuilder { @SuppressWarnings("unchecked") JsonObject json = new JsonObject(); json.getProperties().put("version", new JsonNumber(3)); - json.getProperties().put("file", new JsonString(generatedFile.getName())); + if (generatedFile != null) json.getProperties().put("file", new JsonString(generatedFile.getName())); appendSources(json); appendSourcesContent(json); json.getProperties().put("names", new JsonArray()); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt index 8499f8bcabe..1a6e4160098 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt @@ -230,8 +230,8 @@ abstract class BasicIrBoxTest( val dependencyPaths = mutableListOf() - dependencies.forEach { (moduleId, code) -> - val wrappedCode = wrapWithModuleEmulationMarkers(code, config.moduleKind, moduleId) + dependencies.forEach { (moduleId, outputs) -> + val wrappedCode = wrapWithModuleEmulationMarkers(outputs.jsCode, config.moduleKind, moduleId) val dependencyPath = outputFile.absolutePath.replace("_v5.js", "-${moduleId}_v5.js") dependencyPaths += dependencyPath File(dependencyPath).write(wrappedCode)