js: introduce setting to choose module type

This commit is contained in:
Alexey Andreev 2023-12-20 19:49:14 +01:00
parent 8c344b3812
commit 4049bc529e
32 changed files with 477 additions and 53 deletions

View File

@ -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
}

View File

@ -128,6 +128,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY);
private final Map<String, String> importedModules = new LinkedHashMap<>();
private JavaScriptTemplateFactory templateFactory;
private JSModuleType moduleType = JSModuleType.UMD;
@Override
public List<ClassHolderTransformer> 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;

View File

@ -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())

View File

@ -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();
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
define("testModule.js", [], () => {
define("./testModule.js", [], () => {
return {
foo() {
return 23;

View File

@ -16,7 +16,7 @@
function require(name) {
switch (name) {
case "testModule.js": {
case "./testModule.js": {
return {
foo() {
return 23;

View File

@ -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;
}

View File

@ -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() {

View File

@ -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;
}

View File

@ -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);

View File

@ -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));

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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));

View File

@ -117,6 +117,7 @@ public class TeaVMPlugin implements Plugin<Project> {
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<Project> {
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<Project> {
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<Project> {
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();

View File

@ -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
}

View File

@ -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<Integer> getProcessMemory();
Property<File> getOutputDir();
DirectoryProperty getOutputDir();
}

View File

@ -22,6 +22,8 @@ public interface TeaVMJSConfiguration extends TeaVMWebConfiguration {
Property<Boolean> getStrict();
Property<JSModuleType> getModuleType();
Property<Boolean> getSourceMap();
Property<String> getEntryPointName();

View File

@ -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<Boolean> getStrict();
@Input
@Optional
public abstract Property<JSModuleType> getModuleType();
@Input
@Optional
public abstract Property<Boolean> 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()) {

View File

@ -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<WasmTa
@Override
CompileResult compile(Consumer<TeaVM> additionalProcessing, String baseName,
TeaVMTestConfiguration<WasmTarget> configuration, File path) {
TeaVMTestConfiguration<WasmTarget> configuration, File path, AnnotatedElement element) {
Supplier<WasmTarget> targetSupplier = () -> {
WasmTarget target = new WasmTarget();
target.setRuntimeType(getRuntimeType());

View File

@ -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);
}

View File

@ -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<CTarget> {
@Override
CompileResult compile(Consumer<TeaVM> additionalProcessing, String baseName,
TeaVMTestConfiguration<CTarget> configuration, File path) {
TeaVMTestConfiguration<CTarget> configuration, File path, AnnotatedElement element) {
CompilePostProcessor postBuild = (vm, file) -> {
try {
resourceToFile("teavm-CMakeLists.txt", new File(file.getParent(), "CMakeLists.txt"),

View File

@ -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<JavaScriptTarget> {
@Override
CompileResult compile(Consumer<TeaVM> additionalProcessing, String baseName,
TeaVMTestConfiguration<JavaScriptTarget> configuration, File path) {
TeaVMTestConfiguration<JavaScriptTarget> configuration, File path, AnnotatedElement element) {
boolean decodeStack = Boolean.parseBoolean(System.getProperty(JS_DECODE_STACK, "true"));
var debugEmitter = new DebugInformationBuilder(new ReferenceCache());
Supplier<JavaScriptTarget> targetSupplier = () -> {
@ -109,6 +112,9 @@ class JSPlatformSupport extends TestPlatformSupport<JavaScriptTarget> {
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<JavaScriptTarget> {
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<JavaScriptTarget> {
htmlSingleTestOutput(outputPathForMethod, configuration, "teavm-run-test.html");
}
@Override
void additionalOutputForAllConfigurations(File outputPath, Method method) {
var annotations = new ArrayList<ServeJS>();
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;

View File

@ -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 {
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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<TeaVMTarget>) platform;
var castConfiguration = (TeaVMTestConfiguration<TeaVMTarget>) 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<TeaVMTarget>) platform;
@SuppressWarnings("unchecked")
var castConfig = (TeaVMTestConfiguration<TeaVMTarget>) 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) {

View File

@ -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<T extends TeaVMTarget> {
abstract List<TeaVMTestConfiguration<T>> getConfigurations();
abstract CompileResult compile(Consumer<TeaVM> additionalProcessing, String baseName,
TeaVMTestConfiguration<T> configuration, File path);
TeaVMTestConfiguration<T> configuration, File path, AnnotatedElement element);
abstract boolean usesFileName();
@ -155,6 +157,9 @@ abstract class TestPlatformSupport<T extends TeaVMTarget> {
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());

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);