[CLI] Extract :compiler:cli-js from :compiler:cli
diff --git a/compiler/cli/cli-js/build.gradle.kts b/compiler/cli/cli-js/build.gradle.kts
new file mode 100644
index 0000000..d7b6ae2
--- /dev/null
+++ b/compiler/cli/cli-js/build.gradle.kts
@@ -0,0 +1,37 @@
+plugins {
+    kotlin("jvm")
+    id("jps-compatible")
+}
+
+jvmTarget = "1.8"
+
+dependencies {
+    compile(project(":compiler:util"))
+    compile(project(":compiler:cli-common"))
+    compile(project(":compiler:cli"))
+    compile(project(":compiler:frontend"))
+    compile(project(":compiler:backend-common"))
+    compile(project(":compiler:backend"))
+    compile(project(":compiler:ir.backend.common"))
+    compile(project(":compiler:ir.serialization.js"))
+    compile(project(":compiler:backend.js"))
+    compile(project(":js:js.translator"))
+    compile(project(":js:js.serializer"))
+    compile(project(":js:js.dce"))
+
+    testCompile(project(":compiler:backend"))
+    testCompile(project(":compiler:cli"))
+    testCompile(projectTests(":compiler:tests-common"))
+    testCompile(commonDep("junit:junit"))
+}
+
+sourceSets {
+    "main" { projectDefault() }
+    "test" { projectDefault() }
+}
+
+testsJar {}
+
+projectTest {
+    workingDir = rootDir
+}
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
new file mode 100644
index 0000000..e466922
--- /dev/null
+++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.cli.js;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ExceptionUtil;
+import com.intellij.util.SmartList;
+import kotlin.collections.ArraysKt;
+import kotlin.collections.CollectionsKt;
+import kotlin.collections.SetsKt;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.kotlin.analyzer.AnalysisResult;
+import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
+import org.jetbrains.kotlin.cli.common.CLICompiler;
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
+import org.jetbrains.kotlin.cli.common.CommonCompilerPerformanceManager;
+import org.jetbrains.kotlin.cli.common.ExitCode;
+import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments;
+import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants;
+import org.jetbrains.kotlin.cli.common.config.ContentRootsKt;
+import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
+import org.jetbrains.kotlin.cli.common.messages.MessageUtil;
+import org.jetbrains.kotlin.cli.common.output.OutputUtilsKt;
+import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
+import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser;
+import org.jetbrains.kotlin.config.*;
+import org.jetbrains.kotlin.incremental.components.ExpectActualTracker;
+import org.jetbrains.kotlin.incremental.components.LookupTracker;
+import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider;
+import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer;
+import org.jetbrains.kotlin.incremental.js.TranslationResultValue;
+import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS;
+import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult;
+import org.jetbrains.kotlin.js.config.EcmaVersion;
+import org.jetbrains.kotlin.js.config.JSConfigurationKeys;
+import org.jetbrains.kotlin.js.config.JsConfig;
+import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding;
+import org.jetbrains.kotlin.js.facade.K2JSTranslator;
+import org.jetbrains.kotlin.js.facade.MainCallParameters;
+import org.jetbrains.kotlin.js.facade.TranslationResult;
+import org.jetbrains.kotlin.js.facade.TranslationUnit;
+import org.jetbrains.kotlin.js.facade.exceptions.TranslationException;
+import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
+import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion;
+import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
+import org.jetbrains.kotlin.psi.KtFile;
+import org.jetbrains.kotlin.serialization.js.ModuleKind;
+import org.jetbrains.kotlin.utils.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+import static org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR;
+import static org.jetbrains.kotlin.cli.common.ExitCode.OK;
+import static org.jetbrains.kotlin.cli.common.UtilsKt.checkKotlinPackageUsage;
+import static org.jetbrains.kotlin.cli.common.UtilsKt.getLibraryFromHome;
+import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*;
+
+public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
+    private static final Map<String, ModuleKind> moduleKindMap = new HashMap<>();
+    private static final Map<String, SourceMapSourceEmbedding> sourceMapContentEmbeddingMap = new LinkedHashMap<>();
+
+    private K2JsIrCompiler irCompiler = null;
+
+    @NotNull
+    private K2JsIrCompiler getIrCompiler() {
+        if (irCompiler == null)
+            irCompiler = new K2JsIrCompiler();
+        return irCompiler;
+    }
+
+    static {
+        moduleKindMap.put(K2JsArgumentConstants.MODULE_PLAIN, ModuleKind.PLAIN);
+        moduleKindMap.put(K2JsArgumentConstants.MODULE_COMMONJS, ModuleKind.COMMON_JS);
+        moduleKindMap.put(K2JsArgumentConstants.MODULE_AMD, ModuleKind.AMD);
+        moduleKindMap.put(K2JsArgumentConstants.MODULE_UMD, ModuleKind.UMD);
+
+        sourceMapContentEmbeddingMap.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_ALWAYS, SourceMapSourceEmbedding.ALWAYS);
+        sourceMapContentEmbeddingMap.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_NEVER, SourceMapSourceEmbedding.NEVER);
+        sourceMapContentEmbeddingMap.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_INLINING, SourceMapSourceEmbedding.INLINING);
+    }
+
+    public static void main(String... args) {
+        doMain(new K2JSCompiler(), args);
+    }
+
+    private final K2JSCompilerPerformanceManager performanceManager = new K2JSCompilerPerformanceManager();
+
+    @NotNull
+    @Override
+    public K2JSCompilerArguments createArguments() {
+        return new K2JSCompilerArguments();
+    }
+
+    @NotNull
+    private static TranslationResult translate(
+            @NotNull JsConfig.Reporter reporter,
+            @NotNull List<KtFile> allKotlinFiles,
+            @NotNull JsAnalysisResult jsAnalysisResult,
+            @NotNull MainCallParameters mainCallParameters,
+            @NotNull JsConfig config
+    ) throws TranslationException {
+        K2JSTranslator translator = new K2JSTranslator(config);
+        IncrementalDataProvider incrementalDataProvider = config.getConfiguration().get(JSConfigurationKeys.INCREMENTAL_DATA_PROVIDER);
+        if (incrementalDataProvider != null) {
+            Map<File, KtFile> nonCompiledSources = new HashMap<>(allKotlinFiles.size());
+            for (KtFile ktFile : allKotlinFiles) {
+                nonCompiledSources.put(VfsUtilCore.virtualToIoFile(ktFile.getVirtualFile()), ktFile);
+            }
+
+            Map<File, TranslationResultValue> compiledParts = incrementalDataProvider.getCompiledPackageParts();
+
+            File[] allSources = new File[compiledParts.size() + allKotlinFiles.size()];
+            int i = 0;
+            for (File file : compiledParts.keySet()) {
+                allSources[i++] = file;
+            }
+            for (File file : nonCompiledSources.keySet()) {
+                allSources[i++] = file;
+            }
+            Arrays.sort(allSources);
+
+            List<TranslationUnit> translationUnits = new ArrayList<>();
+            for (i = 0; i < allSources.length; i++) {
+                KtFile nonCompiled = nonCompiledSources.get(allSources[i]);
+                if (nonCompiled != null) {
+                    translationUnits.add(new TranslationUnit.SourceFile(nonCompiled));
+                }
+                else {
+                    TranslationResultValue translatedValue = compiledParts.get(allSources[i]);
+                    translationUnits.add(new TranslationUnit.BinaryAst(translatedValue.getBinaryAst(), translatedValue.getInlineData()));
+                }
+            }
+            return translator.translateUnits(reporter, translationUnits, mainCallParameters, jsAnalysisResult);
+        }
+
+        CollectionsKt.sortBy(allKotlinFiles, ktFile -> VfsUtilCore.virtualToIoFile(ktFile.getVirtualFile()));
+        return translator.translate(reporter, allKotlinFiles, mainCallParameters, jsAnalysisResult);
+    }
+
+    @NotNull
+    @Override
+    protected ExitCode doExecute(
+            @NotNull K2JSCompilerArguments arguments,
+            @NotNull CompilerConfiguration configuration,
+            @NotNull Disposable rootDisposable,
+            @Nullable KotlinPaths paths
+    ) {
+        MessageCollector messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
+
+        if (arguments.getIrBackend()) {
+            return getIrCompiler().doExecute(arguments, configuration, rootDisposable, paths);
+        }
+
+        if (arguments.getFreeArgs().isEmpty() && !IncrementalCompilation.isEnabledForJs()) {
+            if (arguments.getVersion()) {
+                return OK;
+            }
+            messageCollector.report(ERROR, "Specify at least one source file or directory", null);
+            return COMPILATION_ERROR;
+        }
+
+        ExitCode pluginLoadResult =
+                PluginCliParser.loadPluginsSafe(arguments.getPluginClasspaths(), arguments.getPluginOptions(), configuration);
+        if (pluginLoadResult != ExitCode.OK) return pluginLoadResult;
+
+        configuration.put(JSConfigurationKeys.LIBRARIES, configureLibraries(arguments, paths, messageCollector));
+
+        String[] commonSourcesArray = arguments.getCommonSources();
+        Set<String> commonSources = commonSourcesArray == null ? Collections.emptySet() : SetsKt.setOf(commonSourcesArray);
+        for (String arg : arguments.getFreeArgs()) {
+            ContentRootsKt.addKotlinSourceRoot(configuration, arg, commonSources.contains(arg));
+        }
+
+        KotlinCoreEnvironment environmentForJS =
+                KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JS_CONFIG_FILES);
+
+        Project project = environmentForJS.getProject();
+        List<KtFile> sourcesFiles = environmentForJS.getSourceFiles();
+
+        environmentForJS.getConfiguration().put(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE, arguments.getAllowKotlinPackage());
+
+        if (!checkKotlinPackageUsage(environmentForJS, sourcesFiles)) return ExitCode.COMPILATION_ERROR;
+
+        if (arguments.getOutputFile() == null) {
+            messageCollector.report(ERROR, "Specify output file via -output", null);
+            return ExitCode.COMPILATION_ERROR;
+        }
+
+        if (messageCollector.hasErrors()) {
+            return ExitCode.COMPILATION_ERROR;
+        }
+
+        if (sourcesFiles.isEmpty() && !IncrementalCompilation.isEnabledForJs()) {
+            messageCollector.report(ERROR, "No source files", null);
+            return COMPILATION_ERROR;
+        }
+
+        if (arguments.getVerbose()) {
+            reportCompiledSourcesList(messageCollector, sourcesFiles);
+        }
+
+        File outputFile = new File(arguments.getOutputFile());
+
+        configuration.put(CommonConfigurationKeys.MODULE_NAME, FileUtil.getNameWithoutExtension(outputFile));
+
+        JsConfig config = new JsConfig(project, configuration);
+        JsConfig.Reporter reporter = new JsConfig.Reporter() {
+            @Override
+            public void error(@NotNull String message) {
+                messageCollector.report(ERROR, message, null);
+            }
+
+            @Override
+            public void warning(@NotNull String message) {
+                messageCollector.report(STRONG_WARNING, message, null);
+            }
+        };
+        if (config.checkLibFilesAndReportErrors(reporter)) {
+            return COMPILATION_ERROR;
+        }
+
+        AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
+                messageCollector, CommonConfigurationKeysKt.getLanguageVersionSettings(configuration)
+        );
+        analyzerWithCompilerReport.analyzeAndReport(sourcesFiles, () -> TopDownAnalyzerFacadeForJS.analyzeFiles(sourcesFiles, config));
+        if (analyzerWithCompilerReport.hasErrors()) {
+            return COMPILATION_ERROR;
+        }
+
+        ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
+
+        AnalysisResult analysisResult = analyzerWithCompilerReport.getAnalysisResult();
+        assert analysisResult instanceof JsAnalysisResult : "analysisResult should be instance of JsAnalysisResult, but " + analysisResult;
+        JsAnalysisResult jsAnalysisResult = (JsAnalysisResult) analysisResult;
+
+        File outputPrefixFile = null;
+        if (arguments.getOutputPrefix() != null) {
+            outputPrefixFile = new File(arguments.getOutputPrefix());
+            if (!outputPrefixFile.exists()) {
+                messageCollector.report(ERROR, "Output prefix file '" + arguments.getOutputPrefix() + "' not found", null);
+                return ExitCode.COMPILATION_ERROR;
+            }
+        }
+
+        File outputPostfixFile = null;
+        if (arguments.getOutputPostfix() != null) {
+            outputPostfixFile = new File(arguments.getOutputPostfix());
+            if (!outputPostfixFile.exists()) {
+                messageCollector.report(ERROR, "Output postfix file '" + arguments.getOutputPostfix() + "' not found", null);
+                return ExitCode.COMPILATION_ERROR;
+            }
+        }
+
+        File outputDir = outputFile.getParentFile();
+        if (outputDir == null) {
+            outputDir = outputFile.getAbsoluteFile().getParentFile();
+        }
+        try {
+            config.getConfiguration().put(JSConfigurationKeys.OUTPUT_DIR, outputDir.getCanonicalFile());
+        }
+        catch (IOException e) {
+            messageCollector.report(ERROR, "Could not resolve output directory", null);
+            return ExitCode.COMPILATION_ERROR;
+        }
+
+        if (config.getConfiguration().getBoolean(JSConfigurationKeys.SOURCE_MAP)) {
+            checkDuplicateSourceFileNames(messageCollector, sourcesFiles, config);
+        }
+
+        MainCallParameters mainCallParameters = createMainCallParameters(arguments.getMain());
+        TranslationResult translationResult;
+
+        try {
+            //noinspection unchecked
+            translationResult = translate(reporter, sourcesFiles, jsAnalysisResult, mainCallParameters, config);
+        }
+        catch (Exception e) {
+            throw ExceptionUtilsKt.rethrow(e);
+        }
+
+        ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
+
+        AnalyzerWithCompilerReport.Companion.reportDiagnostics(translationResult.getDiagnostics(), messageCollector);
+
+        if (!(translationResult instanceof TranslationResult.Success)) return ExitCode.COMPILATION_ERROR;
+
+        TranslationResult.Success successResult = (TranslationResult.Success) translationResult;
+        OutputFileCollection outputFiles = successResult.getOutputFiles(outputFile, outputPrefixFile, outputPostfixFile);
+
+        if (outputFile.isDirectory()) {
+            messageCollector.report(ERROR, "Cannot open output file '" + outputFile.getPath() + "': is a directory", null);
+            return ExitCode.COMPILATION_ERROR;
+        }
+
+        ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
+
+        OutputUtilsKt.writeAll(outputFiles, outputDir, messageCollector,
+                               configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES));
+
+        return OK;
+    }
+
+    private static void checkDuplicateSourceFileNames(
+            @NotNull MessageCollector log,
+            @NotNull List<KtFile> sourceFiles,
+            @NotNull JsConfig config
+    ) {
+        if (config.getSourceMapRoots().isEmpty()) return;
+
+        SourceFilePathResolver pathResolver = SourceFilePathResolver.create(config);
+        Map<String, String> pathMap = new HashMap<>();
+        Set<String> duplicatePaths = new HashSet<>();
+
+        try {
+            for (KtFile sourceFile : sourceFiles) {
+                String path = sourceFile.getVirtualFile().getPath();
+                String relativePath = pathResolver.getPathRelativeToSourceRoots(new File(sourceFile.getVirtualFile().getPath()));
+
+                String existingPath = pathMap.get(relativePath);
+                if (existingPath != null) {
+                    if (duplicatePaths.add(relativePath)) {
+                        log.report(WARNING, "There are files with same path '" + relativePath + "', relative to source roots: " +
+                                            "'" + path + "' and '" + existingPath + "'. " +
+                                            "This will likely cause problems with debugger", null);
+                    }
+                }
+                else {
+                    pathMap.put(relativePath, path);
+                }
+            }
+        }
+        catch (IOException e) {
+            log.report(ERROR, "IO error occurred validating source path:\n" + ExceptionUtil.getThrowableText(e), null);
+        }
+    }
+
+    private static void reportCompiledSourcesList(@NotNull MessageCollector messageCollector, @NotNull List<KtFile> sourceFiles) {
+        Iterable<String> fileNames = CollectionsKt.map(sourceFiles, file -> {
+            VirtualFile virtualFile = file.getVirtualFile();
+            if (virtualFile != null) {
+                return MessageUtil.virtualFileToPath(virtualFile);
+            }
+            return file.getName() + " (no virtual file)";
+        });
+        messageCollector.report(LOGGING, "Compiling source files: " + StringsKt.join(fileNames, ", "), null);
+    }
+
+    @Override
+    protected void setupPlatformSpecificArgumentsAndServices(
+            @NotNull CompilerConfiguration configuration, @NotNull K2JSCompilerArguments arguments,
+            @NotNull Services services
+    ) {
+        if (arguments.getIrBackend()) {
+            getIrCompiler().setupPlatformSpecificArgumentsAndServices(configuration, arguments, services);
+            return;
+        }
+
+        MessageCollector messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
+
+        if (arguments.getTarget() != null) {
+            assert "v5".equals(arguments.getTarget()) : "Unsupported ECMA version: " + arguments.getTarget();
+        }
+        configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.defaultVersion());
+
+        if (arguments.getSourceMap()) {
+            configuration.put(JSConfigurationKeys.SOURCE_MAP, true);
+            if (arguments.getSourceMapPrefix() != null) {
+                configuration.put(JSConfigurationKeys.SOURCE_MAP_PREFIX, arguments.getSourceMapPrefix());
+            }
+
+            String sourceMapSourceRoots = arguments.getSourceMapBaseDirs();
+            if (sourceMapSourceRoots == null && StringUtil.isNotEmpty(arguments.getSourceMapPrefix())) {
+                sourceMapSourceRoots = calculateSourceMapSourceRoot(messageCollector, arguments);
+            }
+
+            if (sourceMapSourceRoots != null) {
+                List<String> sourceMapSourceRootList = StringUtil.split(sourceMapSourceRoots, File.pathSeparator);
+                configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceMapSourceRootList);
+            }
+        }
+        else {
+            if (arguments.getSourceMapPrefix() != null) {
+                messageCollector.report(WARNING, "source-map-prefix argument has no effect without source map", null);
+            }
+            if (arguments.getSourceMapBaseDirs() != null) {
+                messageCollector.report(WARNING, "source-map-source-root argument has no effect without source map", null);
+            }
+        }
+        if (arguments.getMetaInfo()) {
+            configuration.put(JSConfigurationKeys.META_INFO, true);
+        }
+
+        configuration.put(JSConfigurationKeys.TYPED_ARRAYS_ENABLED, arguments.getTypedArrays());
+
+        configuration.put(JSConfigurationKeys.FRIEND_PATHS_DISABLED, arguments.getFriendModulesDisabled());
+
+        if (!arguments.getFriendModulesDisabled() && arguments.getFriendModules() != null) {
+            List<String> friendPaths = ArraysKt.filterNot(arguments.getFriendModules().split(File.pathSeparator), String::isEmpty);
+            configuration.put(JSConfigurationKeys.FRIEND_PATHS, friendPaths);
+        }
+
+        String moduleKindName = arguments.getModuleKind();
+        ModuleKind moduleKind = moduleKindName != null ? moduleKindMap.get(moduleKindName) : ModuleKind.PLAIN;
+        if (moduleKind == null) {
+            messageCollector.report(
+                    ERROR, "Unknown module kind: " + moduleKindName + ". Valid values are: plain, amd, commonjs, umd", null
+            );
+            moduleKind = ModuleKind.PLAIN;
+        }
+        configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind);
+
+        IncrementalDataProvider incrementalDataProvider = services.get(IncrementalDataProvider.class);
+        if (incrementalDataProvider != null) {
+            configuration.put(JSConfigurationKeys.INCREMENTAL_DATA_PROVIDER, incrementalDataProvider);
+        }
+
+        IncrementalResultsConsumer incrementalResultsConsumer = services.get(IncrementalResultsConsumer.class);
+        if (incrementalResultsConsumer != null) {
+            configuration.put(JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER, incrementalResultsConsumer);
+        }
+
+        LookupTracker lookupTracker = services.get(LookupTracker.class);
+        if (lookupTracker != null) {
+            configuration.put(CommonConfigurationKeys.LOOKUP_TRACKER, lookupTracker);
+        }
+
+        ExpectActualTracker expectActualTracker = services.get(ExpectActualTracker.class);
+        if (expectActualTracker != null) {
+            configuration.put(CommonConfigurationKeys.EXPECT_ACTUAL_TRACKER, expectActualTracker);
+        }
+
+        String sourceMapEmbedContentString = arguments.getSourceMapEmbedSources();
+        SourceMapSourceEmbedding sourceMapContentEmbedding = sourceMapEmbedContentString != null ?
+                                                             sourceMapContentEmbeddingMap.get(sourceMapEmbedContentString) :
+                                                             SourceMapSourceEmbedding.INLINING;
+        if (sourceMapContentEmbedding == null) {
+            String message = "Unknown source map source embedding mode: " + sourceMapEmbedContentString + ". Valid values are: " +
+                             StringUtil.join(sourceMapContentEmbeddingMap.keySet(), ", ");
+            messageCollector.report(ERROR, message, null);
+            sourceMapContentEmbedding = SourceMapSourceEmbedding.INLINING;
+        }
+        configuration.put(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, sourceMapContentEmbedding);
+
+        if (!arguments.getSourceMap() && sourceMapEmbedContentString != null) {
+            messageCollector.report(WARNING, "source-map-embed-sources argument has no effect without source map", null);
+        }
+    }
+
+    @NotNull
+    private static List<String> configureLibraries(
+            @NotNull K2JSCompilerArguments arguments,
+            @Nullable KotlinPaths paths,
+            @NotNull MessageCollector messageCollector
+    ) {
+        List<String> libraries = new SmartList<>();
+        if (!arguments.getNoStdlib()) {
+            File stdlibJar = getLibraryFromHome(
+                    paths, KotlinPaths::getJsStdLibJarPath, PathUtil.JS_LIB_JAR_NAME, messageCollector, "'-no-stdlib'");
+            if (stdlibJar != null) {
+                libraries.add(stdlibJar.getAbsolutePath());
+            }
+        }
+
+        if (arguments.getLibraries() != null) {
+            libraries.addAll(ArraysKt.filterNot(arguments.getLibraries().split(File.pathSeparator), String::isEmpty));
+        }
+        return libraries;
+    }
+
+    @NotNull
+    private static String calculateSourceMapSourceRoot(
+            @NotNull MessageCollector messageCollector,
+            @NotNull K2JSCompilerArguments arguments
+    ) {
+        File commonPath = null;
+        List<File> pathToRoot = new ArrayList<>();
+        Map<File, Integer> pathToRootIndexes = new HashMap<>();
+
+        try {
+            for (String path : arguments.getFreeArgs()) {
+                File file = new File(path).getCanonicalFile();
+                if (commonPath == null) {
+                    commonPath = file;
+
+                    while (file != null) {
+                        pathToRoot.add(file);
+                        file = file.getParentFile();
+                    }
+                    Collections.reverse(pathToRoot);
+
+                    for (int i = 0; i < pathToRoot.size(); ++i) {
+                        pathToRootIndexes.put(pathToRoot.get(i), i);
+                    }
+                }
+                else {
+                    while (file != null) {
+                        Integer existingIndex = pathToRootIndexes.get(file);
+                        if (existingIndex != null) {
+                            existingIndex = Math.min(existingIndex, pathToRoot.size() - 1);
+                            pathToRoot.subList(existingIndex + 1, pathToRoot.size()).clear();
+                            commonPath = pathToRoot.get(pathToRoot.size() - 1);
+                            break;
+                        }
+                        file = file.getParentFile();
+                    }
+                    if (file == null) {
+                        break;
+                    }
+                }
+            }
+        }
+        catch (IOException e) {
+            String text = ExceptionUtil.getThrowableText(e);
+            messageCollector.report(CompilerMessageSeverity.ERROR, "IO error occurred calculating source root:\n" + text, null);
+            return ".";
+        }
+
+        return commonPath != null ? commonPath.getPath() : ".";
+    }
+
+    @NotNull
+    @Override
+    protected CommonCompilerPerformanceManager getPerformanceManager() {
+        return performanceManager;
+    }
+
+    private static MainCallParameters createMainCallParameters(String main) {
+        if (K2JsArgumentConstants.NO_CALL.equals(main)) {
+            return MainCallParameters.noCall();
+        }
+        else {
+            return MainCallParameters.mainWithoutArguments();
+        }
+    }
+
+    @NotNull
+    @Override
+    public String executableScriptFileName() {
+        return "kotlinc-js";
+    }
+
+    @NotNull
+    @Override
+    protected BinaryVersion createMetadataVersion(@NotNull int[] versionArray) {
+        return new JsMetadataVersion(versionArray);
+    }
+
+    private static final class K2JSCompilerPerformanceManager extends CommonCompilerPerformanceManager {
+        public K2JSCompilerPerformanceManager() {
+            super("Kotlin to JS Compiler");
+        }
+    }
+}
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
new file mode 100644
index 0000000..7a9a4b9
--- /dev/null
+++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt
@@ -0,0 +1,399 @@
+/*
+ * Copyright 2010-2019 JetBrains s.r.o. 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.cli.js
+
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.util.text.StringUtil
+import org.jetbrains.kotlin.cli.common.*
+import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR
+import org.jetbrains.kotlin.cli.common.ExitCode.OK
+import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
+import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants
+import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.common.messages.MessageUtil
+import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser
+import org.jetbrains.kotlin.config.CommonConfigurationKeys
+import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.jetbrains.kotlin.config.IncrementalCompilation
+import org.jetbrains.kotlin.config.Services
+import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
+import org.jetbrains.kotlin.incremental.components.LookupTracker
+import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider
+import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
+import org.jetbrains.kotlin.ir.backend.js.KlibModuleRef
+import org.jetbrains.kotlin.ir.backend.js.generateKLib
+import org.jetbrains.kotlin.ir.backend.js.compile
+import org.jetbrains.kotlin.js.config.EcmaVersion
+import org.jetbrains.kotlin.js.config.JSConfigurationKeys
+import org.jetbrains.kotlin.js.config.JsConfig
+import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding
+import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.serialization.js.ModuleKind
+import org.jetbrains.kotlin.utils.JsMetadataVersion
+import org.jetbrains.kotlin.utils.KotlinPaths
+import org.jetbrains.kotlin.utils.join
+import java.io.File
+import java.io.IOException
+import java.util.zip.ZipFile
+
+enum class ProduceKind {
+    DEFAULT,  // Determine what to produce based on js-v1 options
+    JS,
+    KLIB
+}
+
+class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
+
+    override val performanceManager: CommonCompilerPerformanceManager =
+        object : CommonCompilerPerformanceManager("Kotlin to JS (IR) Compiler") {}
+
+    override fun createArguments(): K2JSCompilerArguments {
+        return K2JSCompilerArguments()
+    }
+
+    private fun extractKlibFromZip(library: String, messageCollector: MessageCollector): File? {
+        val zipLib = ZipFile(library)
+        val tempDir = createTempDir(File(library).name, "klibjar")
+        tempDir.deleteOnExit()
+        var extractedKlibDir: File? = null
+        for (entry in zipLib.entries()) {
+            if (!entry.isDirectory) {
+                zipLib.getInputStream(entry).use { input ->
+                    val outputEntryFile = File(tempDir, entry.name)
+                    outputEntryFile.parentFile.mkdirs()
+                    outputEntryFile.outputStream().use { output ->
+                        input.copyTo(output)
+                    }
+                }
+            } else {
+                if (entry.name.endsWith("KLIB/")) {
+                    extractedKlibDir = File(tempDir, entry.name)
+                    messageCollector.report(INFO, "Klib $library is extracted into $extractedKlibDir")
+                }
+            }
+        }
+
+        return extractedKlibDir
+    }
+
+    private fun loadIrLibrary(library: String, messageCollector: MessageCollector): KlibModuleRef? {
+        val libraryFile = File(library)
+        var klibDir = when {
+            FileUtil.isJarOrZip(libraryFile) -> {
+                extractKlibFromZip(library, messageCollector) ?: return null
+            }
+            !libraryFile.isDirectory -> {
+                messageCollector.report(ERROR, "Klib $library must be a directory")
+                return null
+            }
+            else ->
+                libraryFile
+        }
+
+        var klibFiles = klibDir.listFiles()
+        if (klibFiles.isEmpty()) {
+            messageCollector.report(STRONG_WARNING, "Klib $library directory is empty")
+            return null
+        }
+
+        // Sometines gradle gives us a directory with klib directory inside
+        val klibDirInsideDir = klibFiles.find { it.name.endsWith("KLIB") }
+        if (klibDirInsideDir != null) {
+            klibDir = klibDirInsideDir
+            klibFiles = klibDir.listFiles()
+        }
+
+        val metadataFile = klibFiles.find { it.extension == "klm" }
+
+        if (metadataFile == null) {
+            messageCollector.report(STRONG_WARNING, "No metadata file (.klm) for klib: $klibDir")
+            return null
+        }
+
+        return KlibModuleRef(metadataFile.nameWithoutExtension, klibDir.absolutePath)
+    }
+
+    override fun doExecute(
+        arguments: K2JSCompilerArguments,
+        configuration: CompilerConfiguration,
+        rootDisposable: Disposable,
+        paths: KotlinPaths?
+    ): ExitCode {
+        val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
+
+        if (arguments.freeArgs.isEmpty() && !IncrementalCompilation.isEnabledForJs()) {
+            if (arguments.version) {
+                return OK
+            }
+            messageCollector.report(ERROR, "Specify at least one source file or directory", null)
+            return COMPILATION_ERROR
+        }
+
+        val pluginLoadResult = PluginCliParser.loadPluginsSafe(
+            arguments.pluginClasspaths,
+            arguments.pluginOptions,
+            configuration
+        )
+        if (pluginLoadResult != ExitCode.OK) return pluginLoadResult
+
+        val libraries: List<String> = configureLibraries(arguments.libraries)
+        val friendLibraries: List<String> = configureLibraries(arguments.friendModules)
+
+        configuration.put(JSConfigurationKeys.LIBRARIES, libraries)
+        configuration.put(JSConfigurationKeys.TRANSITIVE_LIBRARIES, libraries)
+
+        val commonSourcesArray = arguments.commonSources
+        val commonSources = commonSourcesArray?.toSet() ?: emptySet()
+        for (arg in arguments.freeArgs) {
+            configuration.addKotlinSourceRoot(arg, commonSources.contains(arg))
+        }
+
+        val environmentForJS =
+            KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JS_CONFIG_FILES)
+
+        val project = environmentForJS.project
+        val sourcesFiles = environmentForJS.getSourceFiles()
+
+        environmentForJS.configuration.put(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE, arguments.allowKotlinPackage)
+
+        if (!checkKotlinPackageUsage(environmentForJS, sourcesFiles)) return ExitCode.COMPILATION_ERROR
+
+        val outputFilePath = arguments.outputFile
+        if (outputFilePath == null) {
+            messageCollector.report(ERROR, "IR: Specify output file via -output", null)
+            return ExitCode.COMPILATION_ERROR
+        }
+
+        if (messageCollector.hasErrors()) {
+            return ExitCode.COMPILATION_ERROR
+        }
+
+        if (sourcesFiles.isEmpty() && !IncrementalCompilation.isEnabledForJs()) {
+            messageCollector.report(ERROR, "No source files", null)
+            return COMPILATION_ERROR
+        }
+
+        if (arguments.verbose) {
+            reportCompiledSourcesList(messageCollector, sourcesFiles)
+        }
+
+        val outputFile = File(outputFilePath)
+
+        configuration.put(CommonConfigurationKeys.MODULE_NAME, FileUtil.getNameWithoutExtension(outputFile))
+
+        val config = JsConfig(project, configuration)
+        val outputDir: File = outputFile.parentFile ?: outputFile.absoluteFile.parentFile!!
+        try {
+            config.configuration.put(JSConfigurationKeys.OUTPUT_DIR, outputDir.canonicalFile)
+        } catch (e: IOException) {
+            messageCollector.report(ERROR, "Could not resolve output directory", null)
+            return ExitCode.COMPILATION_ERROR
+        }
+
+        // TODO: Handle non-empty main call arguments
+        val mainCallArguments = if (K2JsArgumentConstants.NO_CALL == arguments.main) null else emptyList<String>()
+
+        val loadedLibrariesNames = mutableSetOf<String>()
+        val dependencies = mutableListOf<KlibModuleRef>()
+        val friendDependencies = mutableListOf<KlibModuleRef>()
+
+        for (library in libraries) {
+            val irLib = loadIrLibrary(library, messageCollector) ?: continue
+            if (irLib.moduleName !in loadedLibrariesNames) {
+                dependencies.add(irLib)
+                loadedLibrariesNames.add(irLib.moduleName)
+                if (library in friendLibraries) {
+                    friendDependencies.add(irLib)
+                }
+            }
+        }
+
+        val produceKind = produceMap[arguments.irProduceOnly]
+        if (produceKind == null) {
+            messageCollector.report(ERROR, "Unknown produce kind: ${arguments.irProduceOnly}. Valid values are: js, klib")
+        }
+
+        if (produceKind == ProduceKind.JS || produceKind == ProduceKind.DEFAULT) {
+            val compiledModule = compile(
+                project,
+                sourcesFiles,
+                configuration,
+                immediateDependencies = dependencies,
+                allDependencies = dependencies,
+                friendDependencies = friendDependencies,
+                mainArguments = mainCallArguments
+            )
+
+            outputFile.writeText(compiledModule)
+        }
+
+        if (produceKind == ProduceKind.KLIB || (produceKind == ProduceKind.DEFAULT && arguments.metaInfo)) {
+            val outputKlibPath = "$outputFilePath.KLIB"
+            generateKLib(
+                project = config.project,
+                files = sourcesFiles,
+                configuration = config.configuration,
+                immediateDependencies = dependencies,
+                allDependencies = dependencies,
+                friendDependencies = friendDependencies,
+                outputKlibPath = outputKlibPath
+            )
+        }
+
+        return OK
+    }
+
+    override fun setupPlatformSpecificArgumentsAndServices(
+        configuration: CompilerConfiguration,
+        arguments: K2JSCompilerArguments,
+        services: Services
+    ) {
+        val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
+
+        if (arguments.target != null) {
+            assert("v5" == arguments.target) { "Unsupported ECMA version: " + arguments.target!! }
+        }
+        configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.defaultVersion())
+
+        // TODO: Support source maps
+        if (arguments.sourceMap) {
+            messageCollector.report(WARNING, "source-map argument is not supported yet", null)
+        } else {
+            if (arguments.sourceMapPrefix != null) {
+                messageCollector.report(WARNING, "source-map-prefix argument has no effect without source map", null)
+            }
+            if (arguments.sourceMapBaseDirs != null) {
+                messageCollector.report(WARNING, "source-map-source-root argument has no effect without source map", null)
+            }
+        }
+
+        if (arguments.metaInfo) {
+            configuration.put(JSConfigurationKeys.META_INFO, true)
+        }
+
+        configuration.put(JSConfigurationKeys.TYPED_ARRAYS_ENABLED, arguments.typedArrays)
+
+        configuration.put(JSConfigurationKeys.FRIEND_PATHS_DISABLED, arguments.friendModulesDisabled)
+
+        val friendModules = arguments.friendModules
+        if (!arguments.friendModulesDisabled && friendModules != null) {
+            val friendPaths = friendModules
+                .split(File.pathSeparator.toRegex())
+                .dropLastWhile { it.isEmpty() }
+                .filterNot { it.isEmpty() }
+
+            configuration.put(JSConfigurationKeys.FRIEND_PATHS, friendPaths)
+        }
+
+        val moduleKindName = arguments.moduleKind
+        var moduleKind: ModuleKind? = if (moduleKindName != null) moduleKindMap[moduleKindName] else ModuleKind.PLAIN
+        if (moduleKind == null) {
+            messageCollector.report(
+                ERROR, "Unknown module kind: $moduleKindName. Valid values are: plain, amd, commonjs, umd", null
+            )
+            moduleKind = ModuleKind.PLAIN
+        }
+        configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind)
+
+        val incrementalDataProvider = services[IncrementalDataProvider::class.java]
+        if (incrementalDataProvider != null) {
+            configuration.put(JSConfigurationKeys.INCREMENTAL_DATA_PROVIDER, incrementalDataProvider)
+        }
+
+        val incrementalResultsConsumer = services[IncrementalResultsConsumer::class.java]
+        if (incrementalResultsConsumer != null) {
+            configuration.put(JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER, incrementalResultsConsumer)
+        }
+
+        val lookupTracker = services[LookupTracker::class.java]
+        if (lookupTracker != null) {
+            configuration.put(CommonConfigurationKeys.LOOKUP_TRACKER, lookupTracker)
+        }
+
+        val expectActualTracker = services[ExpectActualTracker::class.java]
+        if (expectActualTracker != null) {
+            configuration.put(CommonConfigurationKeys.EXPECT_ACTUAL_TRACKER, expectActualTracker)
+        }
+
+        val sourceMapEmbedContentString = arguments.sourceMapEmbedSources
+        var sourceMapContentEmbedding: SourceMapSourceEmbedding? = if (sourceMapEmbedContentString != null)
+            sourceMapContentEmbeddingMap[sourceMapEmbedContentString]
+        else
+            SourceMapSourceEmbedding.INLINING
+        if (sourceMapContentEmbedding == null) {
+            val message = "Unknown source map source embedding mode: " + sourceMapEmbedContentString + ". Valid values are: " +
+                    StringUtil.join(sourceMapContentEmbeddingMap.keys, ", ")
+            messageCollector.report(ERROR, message, null)
+            sourceMapContentEmbedding = SourceMapSourceEmbedding.INLINING
+        }
+        configuration.put(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, sourceMapContentEmbedding)
+
+        if (!arguments.sourceMap && sourceMapEmbedContentString != null) {
+            messageCollector.report(WARNING, "source-map-embed-sources argument has no effect without source map", null)
+        }
+    }
+
+    override fun executableScriptFileName(): String {
+        return "kotlinc-js -Xir"
+    }
+
+    override fun createMetadataVersion(versionArray: IntArray): BinaryVersion {
+        // TODO: Support metadata versions for klibs
+        return JsMetadataVersion(*versionArray)
+    }
+
+    companion object {
+        private val moduleKindMap = mapOf(
+            K2JsArgumentConstants.MODULE_PLAIN to ModuleKind.PLAIN,
+            K2JsArgumentConstants.MODULE_COMMONJS to ModuleKind.COMMON_JS,
+            K2JsArgumentConstants.MODULE_AMD to ModuleKind.AMD,
+            K2JsArgumentConstants.MODULE_UMD to ModuleKind.UMD
+        )
+        private val sourceMapContentEmbeddingMap = mapOf(
+            K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_ALWAYS to SourceMapSourceEmbedding.ALWAYS,
+            K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_NEVER to SourceMapSourceEmbedding.NEVER,
+            K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_INLINING to SourceMapSourceEmbedding.INLINING
+        )
+        private val produceMap = mapOf(
+            null to ProduceKind.DEFAULT,
+            "js" to ProduceKind.JS,
+            "klib" to ProduceKind.KLIB
+        )
+
+        @JvmStatic
+        fun main(args: Array<String>) {
+            CLITool.doMain(K2JsIrCompiler(), args)
+        }
+
+        private fun reportCompiledSourcesList(messageCollector: MessageCollector, sourceFiles: List<KtFile>) {
+            val fileNames = sourceFiles.map { file ->
+                val virtualFile = file.virtualFile
+                if (virtualFile != null) {
+                    MessageUtil.virtualFileToPath(virtualFile)
+                } else {
+                    file.name + " (no virtual file)"
+                }
+            }
+            messageCollector.report(LOGGING, "Compiling source files: " + join(fileNames, ", "), null)
+        }
+
+        private fun configureLibraries(libraryString: String?): List<String> =
+            libraryString?.splitByPathSeparator() ?: emptyList()
+
+        private fun String.splitByPathSeparator(): List<String> {
+            return this.split(File.pathSeparator.toRegex())
+                .dropLastWhile { it.isEmpty() }
+                .toTypedArray()
+                .filterNot { it.isEmpty() }
+        }
+    }
+}
diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/dce/K2JSDce.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/dce/K2JSDce.kt
new file mode 100644
index 0000000..5f2c508
--- /dev/null
+++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/dce/K2JSDce.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.cli.js.dce
+
+import org.jetbrains.kotlin.cli.common.CLITool
+import org.jetbrains.kotlin.cli.common.ExitCode
+import org.jetbrains.kotlin.cli.common.arguments.K2JSDceArguments
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.config.Services
+import org.jetbrains.kotlin.js.dce.*
+import org.jetbrains.kotlin.js.inline.util.RelativePathCalculator
+import org.jetbrains.kotlin.js.parser.sourcemaps.*
+import java.io.*
+import java.util.zip.ZipFile
+
+class K2JSDce : CLITool<K2JSDceArguments>() {
+    override fun createArguments(): K2JSDceArguments = K2JSDceArguments()
+
+    override fun execImpl(messageCollector: MessageCollector, services: Services, arguments: K2JSDceArguments): ExitCode {
+        val baseDir = File(arguments.outputDirectory ?: "min")
+        val files = arguments.freeArgs.flatMap { arg ->
+            collectInputFiles(baseDir, arg, messageCollector)
+        }
+
+        if (messageCollector.hasErrors()) return ExitCode.COMPILATION_ERROR
+
+        if (files.isEmpty() && !arguments.version) {
+            messageCollector.report(CompilerMessageSeverity.ERROR, "no source files")
+            return ExitCode.COMPILATION_ERROR
+        }
+
+        val existingFiles = mutableMapOf<String, InputFile>()
+        for (file in files) {
+            existingFiles[file.outputPath]?.let {
+                messageCollector.report(
+                    CompilerMessageSeverity.ERROR,
+                    "duplicate target file will be created for '${file.resource.name}' and '${it.resource.name}'"
+                )
+                return ExitCode.COMPILATION_ERROR
+            }
+            existingFiles[file.outputPath] = file
+            if (File(file.outputPath).isDirectory) {
+                messageCollector.report(CompilerMessageSeverity.ERROR, "cannot open output file '${file.outputPath}': it is a directory")
+                return ExitCode.COMPILATION_ERROR
+            }
+        }
+
+        return if (!arguments.devMode) {
+            performDce(files, arguments, messageCollector)
+        } else {
+            copyFiles(files)
+            ExitCode.OK
+        }
+    }
+
+    private fun performDce(files: List<InputFile>, arguments: K2JSDceArguments, messageCollector: MessageCollector): ExitCode {
+        val includedDeclarations = arguments.declarationsToKeep.orEmpty().toSet()
+
+        val logConsumer = { level: DCELogLevel, message: String ->
+            val severity = when (level) {
+                DCELogLevel.ERROR -> CompilerMessageSeverity.ERROR
+                DCELogLevel.WARN -> CompilerMessageSeverity.WARNING
+                DCELogLevel.INFO -> CompilerMessageSeverity.LOGGING
+            }
+            messageCollector.report(severity, message)
+        }
+
+        val dceResult = DeadCodeElimination.run(files, includedDeclarations, logConsumer)
+        if (dceResult.status == DeadCodeEliminationStatus.FAILED) return ExitCode.COMPILATION_ERROR
+        val nodes = dceResult.reachableNodes.filterTo(mutableSetOf()) { it.reachable }
+
+        val reachabilitySeverity = if (arguments.printReachabilityInfo) CompilerMessageSeverity.INFO else CompilerMessageSeverity.LOGGING
+        messageCollector.report(reachabilitySeverity, "")
+        for (node in nodes.extractRoots()) {
+            printTree(
+                node, { messageCollector.report(reachabilitySeverity, it) },
+                printNestedMembers = false, showLocations = true
+            )
+        }
+
+        return ExitCode.OK
+    }
+
+    private fun copyFiles(files: List<InputFile>) {
+        for (file in files) {
+            copyResource(file.resource, File(file.outputPath))
+            file.sourceMapResource?.let { sourceMap ->
+                val sourceMapTarget = File(file.outputPath + ".map")
+                val inputFile = File(sourceMap.name)
+                if (!inputFile.exists() || !mapSourcePaths(inputFile, sourceMapTarget)) {
+                    copyResource(sourceMap, sourceMapTarget)
+                }
+            }
+        }
+    }
+
+    private fun copyResource(resource: InputResource, targetFile: File) {
+        if (targetFile.exists() && resource.lastModified() < targetFile.lastModified()) return
+
+        targetFile.parentFile.mkdirs()
+        resource.reader().use { input ->
+            FileOutputStream(targetFile).use { output ->
+                input.copyTo(output)
+            }
+        }
+    }
+
+    private fun mapSourcePaths(inputFile: File, targetFile: File): Boolean {
+        val json = try {
+            InputStreamReader(FileInputStream(inputFile), "UTF-8").use { parseJson(it) }
+        } catch (e: JsonSyntaxException) {
+            return false
+        }
+
+        val sourcesArray = (json as? JsonObject)?.properties?.get("sources") as? JsonArray ?: return false
+        val sources = sourcesArray.elements.map {
+            (it as? JsonString)?.value ?: return false
+        }
+
+        val pathCalculator = RelativePathCalculator(targetFile.parentFile)
+        val mappedSources = sources.map {
+            val result = pathCalculator.calculateRelativePathTo(File(inputFile.parentFile, it))
+            if (result != null) {
+                if (File(targetFile.parentFile, result).exists()) {
+                    result
+                } else {
+                    it
+                }
+            } else {
+                it
+            }
+        }
+
+        if (mappedSources == sources) return false
+
+        json.properties["sources"] = JsonArray(*mappedSources.map { JsonString(it) }.toTypedArray())
+
+        targetFile.parentFile.mkdirs()
+        OutputStreamWriter(FileOutputStream(targetFile), "UTF-8").use { it.write(json.toString()) }
+
+        return true
+    }
+
+    private fun collectInputFiles(baseDir: File, fileName: String, messageCollector: MessageCollector): List<InputFile> {
+        val file = File(fileName)
+        return when {
+            file.isDirectory -> {
+                collectInputFilesFromDirectory(baseDir, fileName)
+            }
+            file.isFile -> {
+                when {
+                    fileName.endsWith(".js") -> {
+                        listOf(singleInputFile(baseDir, fileName))
+                    }
+                    fileName.endsWith(".zip") || fileName.endsWith(".jar") -> {
+                        collectInputFilesFromZip(baseDir, fileName)
+                    }
+                    else -> {
+                        messageCollector.report(
+                            CompilerMessageSeverity.WARNING,
+                            "invalid file name '${file.absolutePath}'; must end either with '.js', '.zip' or '.jar'"
+                        )
+                        emptyList()
+                    }
+                }
+            }
+            else -> {
+                messageCollector.report(CompilerMessageSeverity.ERROR, "source file or directory not found: $fileName")
+                emptyList()
+            }
+        }
+    }
+
+    private fun singleInputFile(baseDir: File, path: String): InputFile {
+        val moduleName = getModuleNameFromPath(path)
+        val pathToSourceMapCandidate = "$path.map"
+        val pathToSourceMap = if (File(pathToSourceMapCandidate).exists()) pathToSourceMapCandidate else null
+        return InputFile(
+            InputResource.file(path), pathToSourceMap?.let { InputResource.file(it) },
+            File(baseDir, "$moduleName.js").absolutePath, moduleName
+        )
+    }
+
+    private fun collectInputFilesFromZip(baseDir: File, path: String): List<InputFile> {
+        return ZipFile(path).use { zipFile ->
+            zipFile.entries().asSequence()
+                .filter { !it.isDirectory }
+                .filter { it.name.endsWith(".js") }
+                .filter { zipFile.getEntry(it.name.metaJs()) != null }
+                .distinctBy { it.name }
+                .map { entry ->
+                    val moduleName = getModuleNameFromPath(entry.name)
+                    val pathToSourceMapCandidate = "${entry.name}.map"
+                    val pathToSourceMap = if (zipFile.getEntry(pathToSourceMapCandidate) != null) pathToSourceMapCandidate else null
+                    InputFile(
+                        InputResource.zipFile(path, entry.name), pathToSourceMap?.let { InputResource.zipFile(path, it) },
+                        File(baseDir, "$moduleName.js").absolutePath, moduleName
+                    )
+                }
+                .toList()
+        }
+    }
+
+    private fun collectInputFilesFromDirectory(baseDir: File, path: String): List<InputFile> {
+        return File(path).walkTopDown().asSequence()
+            .filter { !it.isDirectory }
+            .filter { it.name.endsWith(".js") }
+            .filter { File(it.path.metaJs()).exists() }
+            .map { entry ->
+                val moduleName = getModuleNameFromPath(entry.name)
+                val pathToSourceMapCandidate = "${entry.path}.map"
+                val pathToSourceMap = if (File(pathToSourceMapCandidate).exists()) pathToSourceMapCandidate else null
+                InputFile(
+                    InputResource.file(entry.path), pathToSourceMap?.let { InputResource.file(it) },
+                    File(baseDir, "$moduleName.js").absolutePath, moduleName
+                )
+            }
+            .toList()
+    }
+
+    private fun String.metaJs() = removeSuffix(".js") + ".meta.js"
+
+    private fun getModuleNameFromPath(path: String): String {
+        val dotIndex = path.lastIndexOf('.')
+        val slashIndex = maxOf(path.lastIndexOf('/'), path.lastIndexOf('\\'))
+        return path.substring(slashIndex + 1, if (dotIndex < 0) path.length else dotIndex)
+    }
+
+    override fun executableScriptFileName(): String = "kotlin-dce-js"
+
+    companion object {
+        @JvmStatic
+        fun main(args: Array<String>) {
+            CLITool.doMain(K2JSDce(), args)
+        }
+    }
+}
diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/internal/JSStdlibLinker.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/internal/JSStdlibLinker.kt
new file mode 100644
index 0000000..efb4d2c
--- /dev/null
+++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/internal/JSStdlibLinker.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2019 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.
+ */
+
+@file:JvmName("JSStdlibLinker")
+package org.jetbrains.kotlin.cli.js.internal
+
+import com.google.gwt.dev.js.ThrowExceptionOnErrorReporter
+import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
+import org.jetbrains.kotlin.js.backend.ast.*
+import org.jetbrains.kotlin.js.facade.SourceMapBuilderConsumer
+import org.jetbrains.kotlin.js.inline.util.fixForwardNameReferences
+import org.jetbrains.kotlin.js.parser.parse
+import org.jetbrains.kotlin.js.parser.sourcemaps.*
+import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
+import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
+import org.jetbrains.kotlin.js.util.TextOutputImpl
+import java.io.File
+import java.io.StringReader
+import kotlin.system.exitProcess
+
+fun main(args: Array<String>) {
+    val outputFile = File(args[0])
+    val baseDir = File(args[1]).canonicalFile
+    val wrapperFile = File(args[2])
+
+    val inputPaths = args.drop(3).map { File(it) }
+    mergeStdlibParts(outputFile, wrapperFile, baseDir, inputPaths)
+}
+
+/**
+ * Combines several JS input files, that comprise Kotlin JS Standard Library,
+ * into a single JS module.
+ * The source maps of these files are combined into a single source map.
+ */
+private fun mergeStdlibParts(outputFile: File, wrapperFile: File, baseDir: File, inputPaths: List<File>) {
+    val program = JsProgram()
+
+    fun File.relativizeIfNecessary(): String = canonicalFile.toRelativeString(baseDir)
+
+    val wrapper = parse(wrapperFile.readText(), ThrowExceptionOnErrorReporter, program.scope, wrapperFile.relativizeIfNecessary())!!
+    val insertionPlace = wrapper.createInsertionPlace()
+
+    val allFiles = mutableListOf<File>()
+    inputPaths.forEach { collectFiles(it, allFiles) }
+
+    for (file in allFiles) {
+        val statements = parse(file.readText(), ThrowExceptionOnErrorReporter, program.scope, file.relativizeIfNecessary())!!
+        val block = JsBlock(statements)
+        block.fixForwardNameReferences()
+
+        val sourceMapFile = File(file.parent, file.name + ".map")
+        if (sourceMapFile.exists()) {
+            val sourceMapParse = sourceMapFile.reader().use { SourceMapParser.parse(it) }
+            when (sourceMapParse) {
+                is SourceMapError -> {
+                    System.err.println("Error parsing source map file $sourceMapFile: ${sourceMapParse.message}")
+                    exitProcess(1)
+                }
+                is SourceMapSuccess -> {
+                    val sourceMap = sourceMapParse.value
+                    val remapper = SourceMapLocationRemapper(sourceMap)
+                    remapper.remap(block)
+                }
+            }
+        }
+
+        insertionPlace.statements += statements
+    }
+
+    program.globalBlock.statements += wrapper
+
+    val sourceMapFile = File(outputFile.parentFile, outputFile.name + ".map")
+    val textOutput = TextOutputImpl()
+    val sourceMapBuilder = SourceMap3Builder(outputFile, textOutput, "")
+    val consumer = SourceMapBuilderConsumer(File("."), sourceMapBuilder, SourceFilePathResolver(mutableListOf()), true, true)
+    program.globalBlock.accept(JsToStringGenerationVisitor(textOutput, consumer))
+    val sourceMapContent = sourceMapBuilder.build()
+
+    val programText = textOutput.toString()
+
+    outputFile.writeText(programText + "\n//# sourceMappingURL=${sourceMapFile.name}\n")
+
+    val sourceMapJson = StringReader(sourceMapContent).use { parseJson(it) }
+    val sources = (sourceMapJson as JsonObject).properties["sources"] as JsonArray
+
+    sourceMapJson.properties["sourcesContent"] = JsonArray(*sources.elements.map { sourcePath ->
+        val sourceFile = File((sourcePath as JsonString).value)
+        if (sourceFile.exists()) {
+            JsonString(sourceFile.readText())
+        } else {
+            JsonNull
+        }
+    }.toTypedArray())
+
+    sourceMapFile.writeText(sourceMapJson.toString())
+}
+
+private fun List<JsStatement>.createInsertionPlace(): JsBlock {
+    val block = JsGlobalBlock()
+
+    val visitor = object : JsVisitorWithContextImpl() {
+        override fun visit(x: JsExpressionStatement, ctx: JsContext<in JsStatement>): Boolean {
+            if (isInsertionPlace(x.expression)) {
+                ctx.replaceMe(block)
+                return false
+            } else {
+                return super.visit(x, ctx)
+            }
+        }
+
+        private fun isInsertionPlace(expression: JsExpression): Boolean {
+            if (expression !is JsInvocation || !expression.arguments.isEmpty()) return false
+
+            val qualifier = expression.qualifier
+            if (qualifier !is JsNameRef || qualifier.qualifier != null) return false
+            return qualifier.ident == "insertContent"
+        }
+    }
+
+    for (statement in this) {
+        visitor.accept(statement)
+    }
+    return block
+}
+
+private fun collectFiles(rootFile: File, target: MutableList<File>) {
+    if (rootFile.isDirectory) {
+        for (child in rootFile.listFiles().sorted()) {
+            collectFiles(child, target)
+        }
+    } else if (rootFile.extension == "js") {
+        target += rootFile
+    }
+}
\ No newline at end of file