From 4049bc529ed9ec1d5f90ea85df7c3a52e602b730 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 20 Dec 2023 19:49:14 +0100 Subject: [PATCH] js: introduce setting to choose module type --- .../backend/javascript/JSModuleType.java | 23 ++++ .../backend/javascript/JavaScriptTarget.java | 127 ++++++++++++++++-- tests/build.gradle.kts | 2 +- .../org/teavm/jso/test/ImportModuleTest.java | 11 +- .../resources/org/teavm/jso/test/amdModule.js | 2 +- .../resources/org/teavm/jso/test/commonjs.js | 2 +- .../resources/org/teavm/jso/test/es2015.js | 19 +++ .../main/java/org/teavm/cli/TeaVMRunner.java | 32 ++++- .../java/org/teavm/tooling/TeaVMTool.java | 7 + .../teavm/tooling/builder/BuildStrategy.java | 3 + .../builder/InProcessBuildStrategy.java | 8 ++ .../tooling/builder/RemoteBuildStrategy.java | 6 + .../org/teavm/tooling/daemon/BuildDaemon.java | 1 + .../tooling/daemon/RemoteBuildRequest.java | 2 + .../org/teavm/gradle/TeaVMExtensionImpl.java | 5 +- .../java/org/teavm/gradle/TeaVMPlugin.java | 7 +- .../org/teavm/gradle/api/JSModuleType.java | 23 ++++ .../gradle/api/TeaVMCommonConfiguration.java | 4 +- .../gradle/api/TeaVMJSConfiguration.java | 2 + .../gradle/tasks/GenerateJavaScriptTask.java | 20 +++ .../junit/BaseWebAssemblyPlatformSupport.java | 3 +- .../org/teavm/junit/BrowserRunStrategy.java | 11 +- .../org/teavm/junit/CPlatformSupport.java | 3 +- .../org/teavm/junit/JSPlatformSupport.java | 44 +++++- .../java/org/teavm/junit/JsModuleTest.java | 26 ++++ .../main/java/org/teavm/junit/ServeJS.java | 31 +++++ .../java/org/teavm/junit/ServeJSList.java | 27 ++++ .../java/org/teavm/junit/TeaVMTestRunner.java | 21 ++- .../org/teavm/junit/TestPlatformSupport.java | 7 +- .../main/java/org/teavm/junit/TestRun.java | 8 +- .../src/main/resources/test-server/frame.js | 38 +++--- .../org/teavm/maven/TeaVMCompileMojo.java | 5 + 32 files changed, 477 insertions(+), 53 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/javascript/JSModuleType.java create mode 100644 tests/src/test/resources/org/teavm/jso/test/es2015.js create mode 100644 tools/gradle/src/main/java/org/teavm/gradle/api/JSModuleType.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/JsModuleTest.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/ServeJS.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/ServeJSList.java diff --git a/core/src/main/java/org/teavm/backend/javascript/JSModuleType.java b/core/src/main/java/org/teavm/backend/javascript/JSModuleType.java new file mode 100644 index 000000000..20cc92c1c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/JSModuleType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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.teavm.backend.javascript; + +public enum JSModuleType { + COMMON_JS, + UMD, + NONE, + ES2015 +} diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index fc6c00848..8f4db001e 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -128,6 +128,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY); private final Map importedModules = new LinkedHashMap<>(); private JavaScriptTemplateFactory templateFactory; + private JSModuleType moduleType = JSModuleType.UMD; @Override public List getTransformers() { @@ -214,6 +215,10 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { this.strict = strict; } + public void setModuleType(JSModuleType moduleType) { + this.moduleType = moduleType; + } + @Override public boolean requiresRegisterAllocation() { return true; @@ -403,11 +408,12 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { renderer.renderStringConstants(); renderer.renderCompatibilityStubs(); for (var entry : controller.getEntryPoints().entrySet()) { - rememberingWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).ws().append("=").ws(); + var alias = "$rt_export_" + entry.getKey(); var ref = entry.getValue().getMethod(); - rememberingWriter.appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref); + rememberingWriter.append("let ").appendFunction(alias).ws().append("=").ws() + .appendFunction("$rt_mainStarter").append("(").appendMethodBody(ref); rememberingWriter.append(");").newLine(); - rememberingWriter.appendFunction("$rt_exports").append(".").append(entry.getKey()).append(".") + rememberingWriter.appendFunction(alias).append(".") .append("javaException").ws().append("=").ws().appendFunction("$rt_javaException") .append(";").newLine(); } @@ -450,18 +456,30 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private void printWrapperStart(SourceWriter writer) { writer.append("\"use strict\";").newLine(); - printUmdStart(writer); - writer.append("function(").appendFunction("$rt_exports"); - for (var moduleName : importedModules.values()) { - writer.append(",").ws().appendFunction(moduleName); - } - writer.append(")").appendBlockStart(); + printModuleStart(writer); } private String importModule(String name) { return importedModules.computeIfAbsent(name, n -> "$rt_imported_" + importedModules.size()); } + private void printModuleStart(SourceWriter writer) { + switch (moduleType) { + case UMD: + printUmdStart(writer); + break; + case COMMON_JS: + printCommonJsStart(writer); + break; + case NONE: + printIIFStart(writer); + break; + case ES2015: + printES2015Start(writer); + break; + } + } + private void printUmdStart(SourceWriter writer) { writer.append("(function(module)").appendBlockStart(); writer.appendIf().append("typeof define").ws().append("===").ws().append("'function'") @@ -501,12 +519,103 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { writer.append(");").softNewLine(); writer.appendBlockEnd(); writer.outdent().append("}("); + + writer.append("function(").appendFunction("$rt_exports"); + for (var moduleName : importedModules.values()) { + writer.append(",").ws().appendFunction(moduleName); + } + writer.append(")").appendBlockStart(); + } + + private void printIIFStart(SourceWriter writer) { + for (var exportedName : controller.getEntryPoints().keySet()) { + writer.append("var ").appendGlobal(exportedName).append(";").softNewLine(); + } + writer.append("(function()").appendBlockStart(); + + for (var entry : importedModules.entrySet()) { + var moduleName = entry.getKey(); + var alias = entry.getValue(); + writer.append("let ").appendFunction(alias).ws().append('=').ws().append("this[\"") + .append(RenderingUtil.escapeString(moduleName)).append("\"];").softNewLine(); + } + } + + private void printCommonJsStart(SourceWriter writer) { + for (var entry : importedModules.entrySet()) { + var moduleName = entry.getKey(); + var alias = entry.getValue(); + writer.append("let ").appendFunction(alias).ws().append('=').ws().append("require(\"") + .append(RenderingUtil.escapeString(moduleName)).append("\");").softNewLine(); + } + } + + private void printES2015Start(SourceWriter writer) { + for (var entry : importedModules.entrySet()) { + var moduleName = entry.getKey(); + var alias = entry.getValue(); + writer.append("import").ws().append("*").ws().append("as ").appendFunction(alias) + .append(" from").ws().append("\"") + .append(RenderingUtil.escapeString(moduleName)).append("\";").softNewLine(); + } } private void printWrapperEnd(SourceWriter writer) { + switch (moduleType) { + case UMD: + printUmdEnd(writer); + break; + case COMMON_JS: + printCommonJsEnd(writer); + break; + case NONE: + printIFFEnd(writer); + break; + case ES2015: + printES2015End(writer); + break; + } + } + + private void printUmdEnd(SourceWriter writer) { + for (var export : controller.getEntryPoints().keySet()) { + writer.appendFunction("$rt_exports").append(".").append(export).ws().append("=").ws() + .appendFunction("$rt_export_" + export); + } writer.outdent().append("}));").newLine(); } + private void printCommonJsEnd(SourceWriter writer) { + for (var export : controller.getEntryPoints().keySet()) { + writer.appendFunction("exports.").append(export).ws().append("=").ws() + .appendFunction("$rt_export_" + export).append(";").softNewLine(); + } + } + + private void printIFFEnd(SourceWriter writer) { + for (var exportedName : controller.getEntryPoints().keySet()) { + writer.appendGlobal(exportedName).ws().append("=").ws().appendFunction("$rt_export_" + exportedName) + .append(";").softNewLine(); + } + writer.outdent().append("})();"); + } + + private void printES2015End(SourceWriter writer) { + if (controller.getEntryPoints().isEmpty()) { + return; + } + writer.append("export").ws().append("{").ws(); + var first = true; + for (var exportedName : controller.getEntryPoints().keySet()) { + if (!first) { + writer.append(",").ws(); + } + first = false; + writer.appendFunction("$rt_export_" + exportedName).append(" as ").append(exportedName); + } + writer.ws().append("};").softNewLine(); + } + private void printStats(OutputSourceWriter writer, int totalSize) { if (!Boolean.parseBoolean(System.getProperty("teavm.js.stats", "false"))) { return; diff --git a/tests/build.gradle.kts b/tests/build.gradle.kts index 196402d66..89d6f6f98 100644 --- a/tests/build.gradle.kts +++ b/tests/build.gradle.kts @@ -50,7 +50,7 @@ tasks.test { systemProperty("teavm.junit.js", providers.gradleProperty("teavm.tests.js").orElse("true").get()) systemProperty("teavm.junit.js.runner", browser) - systemProperty("teavm.junit.minified", providers.gradleProperty("teavm.tests.minified").orElse("false").get()) + systemProperty("teavm.junit.minified", providers.gradleProperty("teavm.tests.minified").orElse("true").get()) systemProperty("teavm.junit.optimized", providers.gradleProperty("teavm.tests.optimized").orElse("true").get()) systemProperty("teavm.junit.js.decodeStack", providers.gradleProperty("teavm.tests.decodeStack") .orElse("false").get()) diff --git a/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java b/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java index 93908c3a5..46ddd2151 100644 --- a/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java @@ -22,7 +22,9 @@ import org.teavm.jso.JSBody; import org.teavm.jso.JSBodyImport; import org.teavm.junit.AttachJavaScript; import org.teavm.junit.EachTestCompiledSeparately; +import org.teavm.junit.JsModuleTest; import org.teavm.junit.OnlyPlatform; +import org.teavm.junit.ServeJS; import org.teavm.junit.SkipJVM; import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TestPlatform; @@ -47,9 +49,16 @@ public class ImportModuleTest { assertEquals(23, runTestFunction()); } + @Test + @JsModuleTest + @ServeJS(from = "org/teavm/jso/test/es2015.js", as = "testModule.js") + public void es2015() { + assertEquals(23, runTestFunction()); + } + @JSBody( script = "return testModule.foo();", - imports = @JSBodyImport(alias = "testModule", fromModule = "testModule.js") + imports = @JSBodyImport(alias = "testModule", fromModule = "./testModule.js") ) private static native int runTestFunction(); } diff --git a/tests/src/test/resources/org/teavm/jso/test/amdModule.js b/tests/src/test/resources/org/teavm/jso/test/amdModule.js index f3558cc3d..ebd4ffc64 100644 --- a/tests/src/test/resources/org/teavm/jso/test/amdModule.js +++ b/tests/src/test/resources/org/teavm/jso/test/amdModule.js @@ -14,7 +14,7 @@ * limitations under the License. */ -define("testModule.js", [], () => { +define("./testModule.js", [], () => { return { foo() { return 23; diff --git a/tests/src/test/resources/org/teavm/jso/test/commonjs.js b/tests/src/test/resources/org/teavm/jso/test/commonjs.js index a0df8790e..14f80383a 100644 --- a/tests/src/test/resources/org/teavm/jso/test/commonjs.js +++ b/tests/src/test/resources/org/teavm/jso/test/commonjs.js @@ -16,7 +16,7 @@ function require(name) { switch (name) { - case "testModule.js": { + case "./testModule.js": { return { foo() { return 23; diff --git a/tests/src/test/resources/org/teavm/jso/test/es2015.js b/tests/src/test/resources/org/teavm/jso/test/es2015.js new file mode 100644 index 000000000..6ec512589 --- /dev/null +++ b/tests/src/test/resources/org/teavm/jso/test/es2015.js @@ -0,0 +1,19 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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. + */ + +export function foo() { + return 23; +} \ No newline at end of file diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java index 4dca4e594..78186d5b6 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -28,6 +28,7 @@ import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.tooling.ConsoleTeaVMToolLog; import org.teavm.tooling.TeaVMProblemRenderer; @@ -145,11 +146,10 @@ public final class TeaVMRunner { .desc("Maximum heap size in megabytes (for C and WebAssembly)") .build()); options.addOption(Option.builder() - .longOpt("max-toplevel-names") - .argName("number") + .longOpt("js-module-type") + .argName("module-type") .hasArg() - .desc("Maximum number of names kept in top-level scope (" - + "other will be put in a separate object. 10000 by default.") + .desc("JavaScript module type (umd, common-js, none, es2015).") .build()); } @@ -240,6 +240,30 @@ public final class TeaVMRunner { private void parseGenerationOptions() { tool.setObfuscated(commandLine.hasOption("m")); tool.setStrict(commandLine.hasOption("strict")); + parseJsModuleOption(); + } + + private void parseJsModuleOption() { + if (!commandLine.hasOption("js-module-type")) { + return; + } + switch (commandLine.getOptionValue("js-module-type")) { + case "umd": + tool.setJsModuleType(JSModuleType.UMD); + break; + case "common-js": + tool.setJsModuleType(JSModuleType.COMMON_JS); + break; + case "none": + tool.setJsModuleType(JSModuleType.NONE); + break; + case "es2015": + tool.setJsModuleType(JSModuleType.ES2015); + break; + default: + System.err.print("Wrong JS module type level"); + printUsage(); + } } private void parseDebugOptions() { diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 8a764ae01..2c3946b43 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -36,6 +36,7 @@ import org.teavm.backend.c.CTarget; import org.teavm.backend.c.generate.CNameProvider; import org.teavm.backend.c.generate.ShorteningFileNameProvider; import org.teavm.backend.c.generate.SimpleFileNameProvider; +import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.javascript.JavaScriptTarget; import org.teavm.backend.wasm.WasmRuntimeType; import org.teavm.backend.wasm.WasmTarget; @@ -75,6 +76,7 @@ public class TeaVMTool { private TeaVMTargetType targetType = TeaVMTargetType.JAVASCRIPT; private String targetFileName = ""; private boolean obfuscated = true; + private JSModuleType jsModuleType = JSModuleType.UMD; private boolean strict; private String mainClass; private String entryPointName = "main"; @@ -129,6 +131,10 @@ public class TeaVMTool { this.obfuscated = obfuscated; } + public void setJsModuleType(JSModuleType jsModuleType) { + this.jsModuleType = jsModuleType; + } + public void setStrict(boolean strict) { this.strict = strict; } @@ -334,6 +340,7 @@ public class TeaVMTool { debugEmitter = debugInformationGenerated || sourceMapsFileGenerated ? new DebugInformationBuilder(referenceCache) : null; javaScriptTarget.setDebugEmitter(debugEmitter); + javaScriptTarget.setModuleType(jsModuleType); return javaScriptTarget; } diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java index f1ade6f28..3e4861740 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java @@ -17,6 +17,7 @@ package org.teavm.tooling.builder; import java.util.List; import java.util.Properties; +import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.tooling.TeaVMSourceFilePolicy; import org.teavm.tooling.TeaVMTargetType; @@ -60,6 +61,8 @@ public interface BuildStrategy { void setStrict(boolean strict); + void setJsModuleType(JSModuleType jsModuleType); + void setProperties(Properties properties); void setTransformers(String[] transformers); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java index adf192b81..c8e1cfb94 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java @@ -26,6 +26,7 @@ import java.util.Collection; import java.util.List; import java.util.Properties; import java.util.stream.Collectors; +import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.callgraph.CallGraph; import org.teavm.diagnostics.ProblemProvider; @@ -53,6 +54,7 @@ public class InProcessBuildStrategy implements BuildStrategy { private TeaVMOptimizationLevel optimizationLevel = TeaVMOptimizationLevel.ADVANCED; private boolean fastDependencyAnalysis; private boolean obfuscated; + private JSModuleType jsModuleType; private boolean strict; private boolean sourceMapsFileGenerated; private boolean debugInformationGenerated; @@ -167,6 +169,11 @@ public class InProcessBuildStrategy implements BuildStrategy { this.strict = strict; } + @Override + public void setJsModuleType(JSModuleType jsModuleType) { + this.jsModuleType = jsModuleType; + } + @Override public void setTransformers(String[] transformers) { this.transformers = transformers.clone(); @@ -247,6 +254,7 @@ public class InProcessBuildStrategy implements BuildStrategy { tool.setSourceFilePolicy(sourceMapsSourcePolicy); tool.setObfuscated(obfuscated); + tool.setJsModuleType(jsModuleType); tool.setStrict(strict); tool.setIncremental(incremental); tool.getTransformers().addAll(Arrays.asList(transformers)); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java index 9c42307c9..b3de32f97 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java @@ -20,6 +20,7 @@ import java.rmi.server.UnicastRemoteObject; import java.util.Collection; import java.util.List; import java.util.Properties; +import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.callgraph.CallGraph; import org.teavm.diagnostics.Problem; @@ -142,6 +143,11 @@ public class RemoteBuildStrategy implements BuildStrategy { request.strict = strict; } + @Override + public void setJsModuleType(JSModuleType jsModuleType) { + request.jsModuleType = jsModuleType; + } + @Override public void setTransformers(String[] transformers) { request.transformers = transformers.clone(); diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java index 9ce2b78f1..77c3d383e 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/BuildDaemon.java @@ -158,6 +158,7 @@ public class BuildDaemon extends UnicastRemoteObject implements RemoteBuildServi tool.setOptimizationLevel(request.optimizationLevel); tool.setFastDependencyAnalysis(request.fastDependencyAnalysis); tool.setObfuscated(request.obfuscated); + tool.setJsModuleType(request.jsModuleType); tool.setStrict(request.strict); tool.setWasmVersion(request.wasmVersion); tool.setMinHeapSize(request.minHeapSize); diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java index 0c87169af..9e58df4e9 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java @@ -19,6 +19,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Properties; +import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.tooling.TeaVMSourceFilePolicy; import org.teavm.tooling.TeaVMTargetType; @@ -42,6 +43,7 @@ public class RemoteBuildRequest implements Serializable { public String cacheDirectory; public boolean obfuscated; public boolean strict; + public JSModuleType jsModuleType; public Properties properties; public TeaVMOptimizationLevel optimizationLevel; public boolean fastDependencyAnalysis; diff --git a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java index 3a98012dc..418e6c7ae 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java @@ -16,10 +16,10 @@ package org.teavm.gradle; import groovy.lang.Closure; -import java.io.File; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.model.ObjectFactory; +import org.teavm.gradle.api.JSModuleType; import org.teavm.gradle.api.OptimizationLevel; import org.teavm.gradle.api.SourceFilePolicy; import org.teavm.gradle.api.TeaVMCConfiguration; @@ -63,6 +63,7 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio js.getObfuscated().convention(property("js.obfuscated").map(Boolean::parseBoolean).orElse(true)); js.getSourceMap().convention(property("js.sourceMap").map(Boolean::parseBoolean).orElse(false)); js.getStrict().convention(property("js.strict").map(Boolean::parseBoolean).orElse(false)); + js.getModuleType().convention(property("js.moduleType").map(JSModuleType::valueOf).orElse(JSModuleType.UMD)); js.getEntryPointName().convention("main"); js.getTargetFileName().convention(project.provider(() -> project.getName() + ".js")); js.getAddedToWebApp().convention(property("js.addedToWebApp").map(Boolean::parseBoolean).orElse(false)); @@ -104,7 +105,7 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio } private void setupAllDefaults() { - all.getOutputDir().convention(new File(project.getBuildDir(), "generated/teavm")); + all.getOutputDir().convention(project.getLayout().getBuildDirectory().dir("generated/teavm")); all.getDebugInformation().convention(property("debugInformation").map(Boolean::parseBoolean).orElse(false)); all.getOptimization().convention(OptimizationLevel.BALANCED); all.getFastGlobalAnalysis().convention(property("fastGlobalAnalysis").map(Boolean::parseBoolean).orElse(false)); diff --git a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java index 234f59206..549ac28a3 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java @@ -117,6 +117,7 @@ public class TeaVMPlugin implements Plugin { var js = extension.getJs(); applyToTask(js, task, configuration); task.getObfuscated().convention(js.getObfuscated()); + task.getModuleType().convention(js.getModuleType()); task.getSourceMap().convention(js.getSourceMap()); task.getTargetFileName().convention(js.getTargetFileName()); task.getStrict().convention(js.getStrict()); @@ -230,7 +231,7 @@ public class TeaVMPlugin implements Plugin { var relPath = extension.getJs().getRelativePathInOutputDir(); task.with(project.copySpec(spec -> { spec.into(relPath); - spec.from(project.files(outDir.map(dir -> new File(dir, relPath.get())))); + spec.from(project.files(outDir.map(dir -> new File(dir.getAsFile(), relPath.get())))); spec.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE); })); } @@ -240,7 +241,7 @@ public class TeaVMPlugin implements Plugin { var relPath = extension.getWasm().getRelativePathInOutputDir(); task.with(project.copySpec(spec -> { spec.into(relPath); - spec.from(project.files(outDir.map(dir -> new File(dir, relPath.get())))); + spec.from(project.files(outDir.map(dir -> new File(dir.getAsFile(), relPath.get())))); })); } } @@ -259,7 +260,7 @@ public class TeaVMPlugin implements Plugin { task.getProperties().putAll(configuration.getProperties()); task.getDaemonClasspath().from(toolsConfiguration); task.getOutputDir().convention(configuration.getOutputDir().map( - d -> new File(d, configuration.getRelativePathInOutputDir().get()))); + d -> new File(d.getAsFile(), configuration.getRelativePathInOutputDir().get()))); var project = task.getProject(); diff --git a/tools/gradle/src/main/java/org/teavm/gradle/api/JSModuleType.java b/tools/gradle/src/main/java/org/teavm/gradle/api/JSModuleType.java new file mode 100644 index 000000000..194ee92c7 --- /dev/null +++ b/tools/gradle/src/main/java/org/teavm/gradle/api/JSModuleType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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.teavm.gradle.api; + +public enum JSModuleType { + COMMON_JS, + UMD, + NONE, + ES2015 +} diff --git a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMCommonConfiguration.java b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMCommonConfiguration.java index 9316f03d8..0ff87591d 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMCommonConfiguration.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMCommonConfiguration.java @@ -15,7 +15,7 @@ */ package org.teavm.gradle.api; -import java.io.File; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; @@ -37,5 +37,5 @@ public interface TeaVMCommonConfiguration { Property getProcessMemory(); - Property getOutputDir(); + DirectoryProperty getOutputDir(); } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMJSConfiguration.java b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMJSConfiguration.java index 257c8b11d..ccfa02dfa 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMJSConfiguration.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMJSConfiguration.java @@ -22,6 +22,8 @@ public interface TeaVMJSConfiguration extends TeaVMWebConfiguration { Property getStrict(); + Property getModuleType(); + Property getSourceMap(); Property getEntryPointName(); diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java index e96689cf3..0fde2ce9b 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java @@ -20,6 +20,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; +import org.teavm.gradle.api.JSModuleType; import org.teavm.gradle.api.SourceFilePolicy; import org.teavm.tooling.TeaVMSourceFilePolicy; import org.teavm.tooling.TeaVMTargetType; @@ -29,6 +30,7 @@ public abstract class GenerateJavaScriptTask extends TeaVMTask { public GenerateJavaScriptTask() { getObfuscated().convention(true); getStrict().convention(false); + getModuleType().convention(JSModuleType.UMD); getSourceMap().convention(false); getSourceFilePolicy().convention(SourceFilePolicy.DO_NOTHING); getEntryPointName().convention("main"); @@ -42,6 +44,10 @@ public abstract class GenerateJavaScriptTask extends TeaVMTask { @Optional public abstract Property getStrict(); + @Input + @Optional + public abstract Property getModuleType(); + @Input @Optional public abstract Property getSourceMap(); @@ -62,6 +68,20 @@ public abstract class GenerateJavaScriptTask extends TeaVMTask { builder.setTargetType(TeaVMTargetType.JAVASCRIPT); builder.setObfuscated(getObfuscated().get()); builder.setStrict(getStrict().get()); + switch (getModuleType().get()) { + case UMD: + builder.setJsModuleType(org.teavm.backend.javascript.JSModuleType.UMD); + break; + case COMMON_JS: + builder.setJsModuleType(org.teavm.backend.javascript.JSModuleType.COMMON_JS); + break; + case NONE: + builder.setJsModuleType(org.teavm.backend.javascript.JSModuleType.NONE); + break; + case ES2015: + builder.setJsModuleType(org.teavm.backend.javascript.JSModuleType.ES2015); + break; + } builder.setSourceMapsFileGenerated(getSourceMap().get()); builder.setEntryPointName(getEntryPointName().get()); for (var file : getSourceFiles()) { diff --git a/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java index 2dc3682bb..25881ccbb 100644 --- a/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java @@ -17,6 +17,7 @@ package org.teavm.junit; import static org.teavm.junit.PropertyNames.SOURCE_DIRS; import java.io.File; +import java.lang.reflect.AnnotatedElement; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.function.Consumer; @@ -42,7 +43,7 @@ abstract class BaseWebAssemblyPlatformSupport extends TestPlatformSupport additionalProcessing, String baseName, - TeaVMTestConfiguration configuration, File path) { + TeaVMTestConfiguration configuration, File path, AnnotatedElement element) { Supplier targetSupplier = () -> { WasmTarget target = new WasmTarget(); target.setRuntimeType(getRuntimeType()); diff --git a/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java index 6b850ed97..9d0dc54bd 100644 --- a/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java @@ -181,13 +181,20 @@ class BrowserRunStrategy implements TestRunStrategy { ObjectNode testNode = nf.objectNode(); testNode.set("type", nf.textNode(type)); testNode.set("name", nf.textNode(run.getFileName())); - testNode.set("file", nf.textNode("tests/" + relPath)); + + var fileNode = nf.objectNode(); + fileNode.set("path", nf.textNode("tests/" + relPath)); + fileNode.set("type", nf.textNode(run.isModule() ? "module" : "regular")); + testNode.set("file", fileNode); var additionalJs = additionalJs(run); if (additionalJs.length > 0) { var additionalJsJson = nf.arrayNode(); for (var additionalFile : additionalJs) { - additionalJsJson.add("resources/" + additionalFile); + var additionFileObj = nf.objectNode(); + additionFileObj.set("path", nf.textNode("resources/" + additionalFile)); + additionFileObj.set("type", nf.textNode("regular")); + additionalJsJson.add(additionFileObj); } testNode.set("additionalFiles", additionalJsJson); } diff --git a/tools/junit/src/main/java/org/teavm/junit/CPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/CPlatformSupport.java index 9f18a5131..cd69488a6 100644 --- a/tools/junit/src/main/java/org/teavm/junit/CPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/CPlatformSupport.java @@ -22,6 +22,7 @@ import static org.teavm.junit.PropertyNames.OPTIMIZED; import static org.teavm.junit.TestUtil.resourceToFile; import java.io.File; import java.io.IOException; +import java.lang.reflect.AnnotatedElement; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -78,7 +79,7 @@ class CPlatformSupport extends TestPlatformSupport { @Override CompileResult compile(Consumer additionalProcessing, String baseName, - TeaVMTestConfiguration configuration, File path) { + TeaVMTestConfiguration configuration, File path, AnnotatedElement element) { CompilePostProcessor postBuild = (vm, file) -> { try { resourceToFile("teavm-CMakeLists.txt", new File(file.getParent(), "CMakeLists.txt"), diff --git a/tools/junit/src/main/java/org/teavm/junit/JSPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/JSPlatformSupport.java index 8479aa84f..275d7201a 100644 --- a/tools/junit/src/main/java/org/teavm/junit/JSPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/JSPlatformSupport.java @@ -27,10 +27,13 @@ import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; +import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.javascript.JavaScriptTarget; import org.teavm.debugging.information.DebugInformation; import org.teavm.debugging.information.DebugInformationBuilder; @@ -99,7 +102,7 @@ class JSPlatformSupport extends TestPlatformSupport { @Override CompileResult compile(Consumer additionalProcessing, String baseName, - TeaVMTestConfiguration configuration, File path) { + TeaVMTestConfiguration configuration, File path, AnnotatedElement element) { boolean decodeStack = Boolean.parseBoolean(System.getProperty(JS_DECODE_STACK, "true")); var debugEmitter = new DebugInformationBuilder(new ReferenceCache()); Supplier targetSupplier = () -> { @@ -109,6 +112,9 @@ class JSPlatformSupport extends TestPlatformSupport { target.setDebugEmitter(debugEmitter); target.setStackTraceIncluded(true); } + if (isModule(element)) { + target.setModuleType(JSModuleType.ES2015); + } return target; }; CompilePostProcessor postBuild = null; @@ -139,6 +145,19 @@ class JSPlatformSupport extends TestPlatformSupport { postBuild, additionalProcessing, baseName); } + private boolean isModule(AnnotatedElement element) { + if (element.isAnnotationPresent(JsModuleTest.class)) { + return true; + } + if (element instanceof Method) { + var cls = ((Method) element).getDeclaringClass(); + if (cls.isAnnotationPresent(JsModuleTest.class)) { + return true; + } + } + return false; + } + @Override void additionalOutput(File outputPath, File outputPathForMethod, TeaVMTestConfiguration configuration, MethodReference reference) { @@ -151,6 +170,29 @@ class JSPlatformSupport extends TestPlatformSupport { htmlSingleTestOutput(outputPathForMethod, configuration, "teavm-run-test.html"); } + @Override + void additionalOutputForAllConfigurations(File outputPath, Method method) { + var annotations = new ArrayList(); + var list = method.getAnnotation(ServeJSList.class); + if (list != null) { + annotations.addAll(List.of(list.value())); + } + var single = method.getAnnotation(ServeJS.class); + if (single != null) { + annotations.add(single); + } + var loader = JSPlatformSupport.class.getClassLoader(); + for (var item : annotations) { + var outputFile = new File(outputPath, item.as()); + try (var input = loader.getResourceAsStream(item.from()); + var output = new FileOutputStream(outputFile)) { + input.transferTo(output); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + @Override boolean usesFileName() { return true; diff --git a/tools/junit/src/main/java/org/teavm/junit/JsModuleTest.java b/tools/junit/src/main/java/org/teavm/junit/JsModuleTest.java new file mode 100644 index 000000000..dcb203921 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/JsModuleTest.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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.teavm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface JsModuleTest { +} diff --git a/tools/junit/src/main/java/org/teavm/junit/ServeJS.java b/tools/junit/src/main/java/org/teavm/junit/ServeJS.java new file mode 100644 index 000000000..855d3104d --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/ServeJS.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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.teavm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(ServeJSList.class) +public @interface ServeJS { + String from(); + + String as(); +} diff --git a/tools/junit/src/main/java/org/teavm/junit/ServeJSList.java b/tools/junit/src/main/java/org/teavm/junit/ServeJSList.java new file mode 100644 index 000000000..57702eb91 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/ServeJSList.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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.teavm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ServeJSList { + ServeJS[] value(); +} diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index c2f3ea3a9..1028149a8 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -133,7 +133,6 @@ public class TeaVMTestRunner extends Runner implements Filterable { this.testClass = testClass; } - @Override public Description getDescription() { if (suiteDescription == null) { @@ -234,7 +233,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { var castPlatform = (TestPlatformSupport) platform; var castConfiguration = (TeaVMTestConfiguration) configuration; var result = castPlatform.compile(wholeClass(children, platform.getPlatform()), "classTest", - castConfiguration, path); + castConfiguration, path, testClass); if (!result.success) { notifier.fireTestFailure(createFailure(description, result)); return false; @@ -344,9 +343,10 @@ public class TeaVMTestRunner extends Runner implements Filterable { var testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, platform.getExtension()); runs.add(createTestRun(configuration, testPath, child, platform.getPlatform(), - reference.toString())); + reference.toString(), isModule(child))); platform.additionalOutput(outputPath, outputPathForMethod, configuration, reference); } + platform.additionalOutputForAllConfigurations(outputPath, child); } } } @@ -364,13 +364,15 @@ public class TeaVMTestRunner extends Runner implements Filterable { var castPlatform = (TestPlatformSupport) platform; @SuppressWarnings("unchecked") var castConfig = (TeaVMTestConfiguration) configuration; - var compileResult = castPlatform.compile(singleTest(child), "test", castConfig, outputPath); + var compileResult = castPlatform.compile(singleTest(child), "test", castConfig, outputPath, + child); var run = prepareRun(configuration, child, compileResult, notifier, platform.getPlatform()); if (run != null) { runs.add(run); platform.additionalSingleTestOutput(outputPath, configuration, reference); } } + platform.additionalOutputForAllConfigurations(outputPath, child); } } } catch (Throwable e) { @@ -711,13 +713,18 @@ public class TeaVMTestRunner extends Runner implements Filterable { return null; } - return createTestRun(configuration, result.file, child, kind, null); + return createTestRun(configuration, result.file, child, kind, null, isModule(child)); + } + + private boolean isModule(Method method) { + return method.isAnnotationPresent(JsModuleTest.class) + || method.getDeclaringClass().isAnnotationPresent(JsModuleTest.class); } private TestRun createTestRun(TeaVMTestConfiguration configuration, File file, Method child, TestPlatform kind, - String argument) { + String argument, boolean module) { return new TestRun(generateName(child.getName(), configuration), file.getParentFile(), child, - file.getName(), kind, argument); + file.getName(), kind, argument, module); } private String generateName(String baseName, TeaVMTestConfiguration configuration) { diff --git a/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java index 132f00156..fcc315039 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java @@ -18,6 +18,8 @@ package org.teavm.junit; import static org.teavm.junit.TestUtil.resourceToFile; import java.io.File; import java.io.IOException; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -58,7 +60,7 @@ abstract class TestPlatformSupport { abstract List> getConfigurations(); abstract CompileResult compile(Consumer additionalProcessing, String baseName, - TeaVMTestConfiguration configuration, File path); + TeaVMTestConfiguration configuration, File path, AnnotatedElement element); abstract boolean usesFileName(); @@ -155,6 +157,9 @@ abstract class TestPlatformSupport { MethodReference reference) { } + void additionalOutputForAllConfigurations(File outputPath, Method method) { + } + protected final void htmlOutput(File outputPath, File outputPathForMethod, TeaVMTestConfiguration configuration, MethodReference reference, String template) { var testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), getExtension()); diff --git a/tools/junit/src/main/java/org/teavm/junit/TestRun.java b/tools/junit/src/main/java/org/teavm/junit/TestRun.java index 88e0fa9ad..33a32956c 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestRun.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestRun.java @@ -25,15 +25,17 @@ class TestRun { private String fileName; private TestPlatform kind; private String argument; + private boolean module; TestRun(String name, File baseDirectory, Method method, String fileName, TestPlatform kind, - String argument) { + String argument, boolean module) { this.name = name; this.baseDirectory = baseDirectory; this.method = method; this.fileName = fileName; this.kind = kind; this.argument = argument; + this.module = module; } public String getName() { @@ -59,4 +61,8 @@ class TestRun { public String getArgument() { return argument; } + + public boolean isModule() { + return module; + } } diff --git a/tools/junit/src/main/resources/test-server/frame.js b/tools/junit/src/main/resources/test-server/frame.js index e8323a897..b5a699f38 100644 --- a/tools/junit/src/main/resources/test-server/frame.js +++ b/tools/junit/src/main/resources/test-server/frame.js @@ -31,8 +31,8 @@ window.addEventListener("message", event => { break; case "WASM": - const runtimeFile = request.file + "-runtime.js"; - appendFiles([runtimeFile], 0, () => { + const runtimeFile = request.file.path + "-runtime.js"; + appendFiles([{ path: runtimeFile, type: "regular" }], 0, () => { launchWasmTest(request.file, request.argument, response => { event.source.postMessage(response, "*"); }); @@ -47,21 +47,29 @@ function appendFiles(files, index, callback, errorCallback) { if (index === files.length) { callback(); } else { - let fileName = files[index]; - let script = document.createElement("script"); - script.onload = () => { - appendFiles(files, index + 1, callback, errorCallback); - }; - script.onerror = () => { - errorCallback("failed to load script " + fileName); - }; - script.src = fileName; - document.body.appendChild(script); + let file = files[index]; + if (file.type === "module") { + import("./" + file.path).then(module => { + window.main = module.main; + appendFiles(files, index + 1, callback, errorCallback); + }); + } else { + let script = document.createElement("script"); + script.onload = () => { + appendFiles(files, index + 1, callback, errorCallback); + }; + script.onerror = () => { + errorCallback("failed to load script " + file.path); + }; + script.src = file.path; + document.body.appendChild(script); + } } } function launchTest(argument, callback) { - main(argument ? [argument] : [], result => { + let m = typeof main === "undefined" ? window.main : main; + m(argument ? [argument] : [], result => { if (result instanceof Error) { callback(wrapResponse({ status: "failed", @@ -88,7 +96,7 @@ function launchTest(argument, callback) { } } -function launchWasmTest(path, argument, callback) { +function launchWasmTest(file, argument, callback) { let output = []; let outputBuffer = ""; let outputBufferStderr = ""; @@ -142,7 +150,7 @@ function launchWasmTest(path, argument, callback) { let instance = null; - TeaVM.wasm.load(path, { + TeaVM.wasm.load(file.path, { installImports: function(o) { o.teavm.putwcharsOut = (chars, count) => putwchars(instance, chars, count); o.teavm.putwcharsErr = (chars, count) => putwcharsStderr(instance, chars, count); diff --git a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java index 684b8d92b..23383057a 100644 --- a/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java +++ b/tools/maven/plugin/src/main/java/org/teavm/maven/TeaVMCompileMojo.java @@ -37,6 +37,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.repository.RepositorySystem; +import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.tooling.TeaVMProblemRenderer; import org.teavm.tooling.TeaVMSourceFilePolicy; @@ -83,6 +84,9 @@ public class TeaVMCompileMojo extends AbstractMojo { @Parameter(property = "teavm.strict", defaultValue = "false") private boolean strict; + @Parameter(property = "teavm.jsModuleType", defaultValue = "UMD") + private JSModuleType jsModuleType; + @Parameter private Properties properties; @@ -164,6 +168,7 @@ public class TeaVMCompileMojo extends AbstractMojo { builder.setClassPathEntries(prepareClassPath()); builder.setObfuscated(minifying); builder.setStrict(strict); + builder.setJsModuleType(jsModuleType); builder.setTargetDirectory(targetDirectory.getAbsolutePath()); if (transformers != null) { builder.setTransformers(transformers);