From 906651ec94070df34b2f3ef8d775142ef6191931 Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Mon, 3 Jun 2024 19:52:28 -0700 Subject: [PATCH] fix: typescript support - fix: typescript compiler and js realm/context use - fix: invocation of typescript from entrypoint - fix: module loader requires access to disk Signed-off-by: Sam Gammon --- packages/graalvm-ts/api/graalvm-ts.api | 1 + .../lang/typescript/TypeScriptLanguage.java | 42 ++++++++++++------- .../internals/TypeScriptCompiler.java | 25 ++++++++--- .../runtime/plugins/typescript/TypeScript.kt | 2 +- .../lang/typescript/TypeScriptLanguageTest.kt | 9 +++- .../core/internals/graalvm/GraalVMEngine.kt | 2 +- .../gvm/internals/sqlite/SqliteModule.kt | 8 +--- .../elide/runtime/plugins/js/JavaScript.kt | 4 +- .../runtime/plugins/js/JavaScriptConfig.kt | 2 +- tools/scripts/hello.ts | 2 + 10 files changed, 64 insertions(+), 33 deletions(-) create mode 100644 tools/scripts/hello.ts diff --git a/packages/graalvm-ts/api/graalvm-ts.api b/packages/graalvm-ts/api/graalvm-ts.api index 7c3ff24909..ed744530a0 100644 --- a/packages/graalvm-ts/api/graalvm-ts.api +++ b/packages/graalvm-ts/api/graalvm-ts.api @@ -5,6 +5,7 @@ public class elide/runtime/lang/typescript/TypeScriptLanguage : com/oracle/truff public static final field MODULE_MIME_TYPE Ljava/lang/String; public static final field NAME Ljava/lang/String; public static final field TEXT_MIME_TYPE Ljava/lang/String; + public static final field TYPESCRIPT_VERSION Ljava/lang/String; public fun ()V protected fun createContext (Lcom/oracle/truffle/api/TruffleLanguage$Env;)Lcom/oracle/truffle/js/runtime/JSRealm; protected synthetic fun createContext (Lcom/oracle/truffle/api/TruffleLanguage$Env;)Ljava/lang/Object; diff --git a/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/TypeScriptLanguage.java b/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/TypeScriptLanguage.java index 5860b701da..48c66c0f8f 100644 --- a/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/TypeScriptLanguage.java +++ b/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/TypeScriptLanguage.java @@ -30,18 +30,33 @@ import java.util.List; import org.graalvm.polyglot.SandboxPolicy; -/** TBD. */ +/** + * TypeScript language implementation for GraalVM, meant for use via Elide. + * + *

The TypeScript language implementation uses GraalJs internally, and an embedded version of the + * TypeScript Compiler (known as 'tsc'). The compiler is loaded into a dedicated JavaScript context + * and realm, and granted I/O access in order to load scripts from disk sources. + * + *

At this time, Elide's implementation of TypeScript incurs a penalty to compile the input code + * (or code loaded from modules) through `tsc`; later, this restriction may be lifted. TypeScript + * does not support I/O isolation yet. + */ @Registration( - id = "ts", - name = "TypeScript", - implementationName = "Elide TypeScript", - version = "5.4.5", + id = TypeScriptLanguage.ID, + name = TypeScriptLanguage.NAME, + implementationName = TypeScriptLanguage.IMPLEMENTATION_NAME, + version = TypeScriptLanguage.TYPESCRIPT_VERSION, dependentLanguages = "js", - characterMimeTypes = "application/typescript", + defaultMimeType = TypeScriptLanguage.APPLICATION_MIME_TYPE, website = "https://docs.elide.dev", fileTypeDetectors = TypeScriptFileTypeDetector.class, contextPolicy = ContextPolicy.SHARED, - sandbox = SandboxPolicy.UNTRUSTED) + sandbox = SandboxPolicy.TRUSTED, + characterMimeTypes = { + TypeScriptLanguage.TEXT_MIME_TYPE, + TypeScriptLanguage.APPLICATION_MIME_TYPE, + TypeScriptLanguage.MODULE_MIME_TYPE + }) public class TypeScriptLanguage extends TruffleLanguage { public static final String TEXT_MIME_TYPE = "text/typescript"; public static final String APPLICATION_MIME_TYPE = "application/typescript"; @@ -49,18 +64,19 @@ public class TypeScriptLanguage extends TruffleLanguage { public static final String NAME = "TypeScript"; public static final String IMPLEMENTATION_NAME = "TypeScript"; public static final String ID = "ts"; + public static final String TYPESCRIPT_VERSION = "5.4.5"; private TypeScriptCompiler tsCompiler; private Env env; @Override protected JSRealm createContext(Env env) { CompilerAsserts.neverPartOfCompilation(); - var javaScriptLanguage = JavaScriptLanguage.getCurrentLanguage(); + var js = JavaScriptLanguage.getCurrentLanguage(); LanguageInfo jsInfo = env.getInternalLanguages().get("js"); env.initializeLanguage(jsInfo); var jsEnv = JavaScriptLanguage.getCurrentEnv(); - var ctx = JSEngine.createJSContext(javaScriptLanguage, jsEnv); - tsCompiler = new TypeScriptCompiler(env); + var ctx = JSEngine.createJSContext(js, jsEnv); + tsCompiler = new TypeScriptCompiler(jsEnv); this.env = jsEnv; var realm = ctx.createRealm(jsEnv); JSRealmPatcher.setTSModuleLoader(realm, new TypeScriptModuleLoader(realm, tsCompiler)); @@ -96,10 +112,8 @@ protected TSRootNode(TruffleLanguage language, RootNode delegate) { @Override public Object execute(VirtualFrame frame) { - // @TODO breaks with blocklisted methods - // JSRealm realm = JSRealm.get(delegate); - // JSRealmPatcher.setTSModuleLoader(realm, new TypeScriptModuleLoader(realm, - // tsCompiler)); + JSRealm realm = JSRealm.get(delegate); + JSRealmPatcher.setTSModuleLoader(realm, new TypeScriptModuleLoader(realm, tsCompiler)); return delegate.execute(frame); } } diff --git a/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/internals/TypeScriptCompiler.java b/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/internals/TypeScriptCompiler.java index fa9f477e30..891143d656 100644 --- a/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/internals/TypeScriptCompiler.java +++ b/packages/graalvm-ts/src/main/java/elide/runtime/lang/typescript/internals/TypeScriptCompiler.java @@ -27,20 +27,35 @@ import java.util.zip.GZIPInputStream; import org.apache.commons.io.IOUtils; -/** TBD. */ +/** + * TypeScript Compiler + * + *

Hosts the TypeScript Compiler (tsc) implementation for Elide's support for the TypeScript + * language (see {@link elide.runtime.lang.typescript.TypeScriptLanguage}) + * + *

The TypeScript compiler is hosted in a dedicated JavaScript context, and leverages GraalJs to + * compile user code on-the-fly, either as primary inputs or as a loaded module. + */ public class TypeScriptCompiler implements AutoCloseable { private static final String TYPESCRIPT_COMPILER_PATH = "/META-INF/elide/embedded/tools/tsc/typescript.js.gz"; private static final Source TYPESCRIPT_COMPILER_SOURCE = createTypeScriptCompilerSource(); - // private static final Source TYPESCRIPT_TRANSPILE_FUNCTION_SOURCE = - // createTypeScriptTranspileFunctionSource(); private final TruffleContext context; private final Object transpileFunction; public TypeScriptCompiler(Env env) { - this.context = env.newInnerContextBuilder("js").build(); + context = + env.newInnerContextBuilder("js") + .allowIO(true) // must allow for import of modules during compilation + .option("js.annex-b", "true") // enable Annex B for compatibility with TypeScript + .option("js.ecmascript-version", "2021") // always use a modern ECMA spec + .option( + "js.commonjs-require", "true") // always enable `require()`, the compiler needs it + .option( + "js.commonjs-require-cwd", System.getProperty("user.dir")) // use cwd as import root + .build(); + transpileFunction = context.evalInternal(null, TYPESCRIPT_COMPILER_SOURCE); - // transpileFunction = context.evalInternal(null, TYPESCRIPT_TRANSPILE_FUNCTION_SOURCE); } public String compileToString(CharSequence ts, String name) { diff --git a/packages/graalvm-ts/src/main/kotlin/elide/runtime/plugins/typescript/TypeScript.kt b/packages/graalvm-ts/src/main/kotlin/elide/runtime/plugins/typescript/TypeScript.kt index c897d64b5c..9a62097e63 100644 --- a/packages/graalvm-ts/src/main/kotlin/elide/runtime/plugins/typescript/TypeScript.kt +++ b/packages/graalvm-ts/src/main/kotlin/elide/runtime/plugins/typescript/TypeScript.kt @@ -27,7 +27,7 @@ import elide.runtime.plugins.AbstractLanguagePlugin.LanguagePluginManifest ) { @Suppress("unused", "unused_parameter") private fun configureContext(builder: PolyglotContextBuilder) { - // nothing yet + // nothing at this time } public companion object Plugin : AbstractLanguagePlugin() { diff --git a/packages/graalvm-ts/src/test/kotlin/elide/runtime/lang/typescript/TypeScriptLanguageTest.kt b/packages/graalvm-ts/src/test/kotlin/elide/runtime/lang/typescript/TypeScriptLanguageTest.kt index b097404d38..44aba747d1 100644 --- a/packages/graalvm-ts/src/test/kotlin/elide/runtime/lang/typescript/TypeScriptLanguageTest.kt +++ b/packages/graalvm-ts/src/test/kotlin/elide/runtime/lang/typescript/TypeScriptLanguageTest.kt @@ -1,19 +1,24 @@ package elide.runtime.lang.typescript import org.graalvm.polyglot.Context +import org.graalvm.polyglot.Engine import org.graalvm.polyglot.PolyglotAccess import org.graalvm.polyglot.Source +import org.graalvm.polyglot.io.IOAccess import org.junit.jupiter.api.assertDoesNotThrow import kotlin.test.* /** Basic tests for [TypeScriptLanguage]. */ class TypeScriptLanguageTest { - private fun ctx(): Context = Context.newBuilder() + private val engine: Engine = Engine.newBuilder("js", "ts").build() + private fun ctx(): Context = Context.newBuilder("js", "ts") .allowInnerContextOptions(true) .allowPolyglotAccess(PolyglotAccess.ALL) + .allowIO(IOAccess.ALL) .allowExperimentalOptions(true) .allowValueSharing(true) .allowAllAccess(true) + .engine(engine) .build() private fun language() = TypeScriptLanguage() @@ -39,6 +44,7 @@ class TypeScriptLanguageTest { val ctx = ctx() ctx.initialize("js") + ctx.initialize("ts") ctx.enter() val parsed = ctx.parse(jsSrc) ctx.leave() @@ -61,7 +67,6 @@ class TypeScriptLanguageTest { assertNotNull(it) } - ctx.initialize("ts") ctx.enter() ctx.parse(src) ctx.leave() diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/core/internals/graalvm/GraalVMEngine.kt b/packages/graalvm/src/main/kotlin/elide/runtime/core/internals/graalvm/GraalVMEngine.kt index eea7c659eb..790ed54383 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/core/internals/graalvm/GraalVMEngine.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/core/internals/graalvm/GraalVMEngine.kt @@ -92,7 +92,7 @@ import org.graalvm.polyglot.HostAccess as PolyglotHostAccess .allowPolyglotAccess(PolyglotAccess.ALL) .allowValueSharing(true) .allowHostAccess(contextHostAccess) - .allowInnerContextOptions(false) + .allowInnerContextOptions(true) .allowCreateThread(true) .allowCreateProcess(true) // @TODO(sgammon): needs policy enforcement .allowHostClassLoading(true) diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/sqlite/SqliteModule.kt b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/sqlite/SqliteModule.kt index c1da89ac8e..fc431ae48c 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/sqlite/SqliteModule.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/sqlite/SqliteModule.kt @@ -94,13 +94,7 @@ internal class SqliteModule : SQLiteAPI { private const val SQLITE3_LIBRARY: String = "sqlite" init { - // on SVM, we should load the library directly using our own tools; this is because sqlite3 may be built into the - // binary statically, and loaded via our own facilities at build-time. - if (ImageInfo.inImageCode()) NativeLibraries.resolve(SQLITE3_LIBRARY) { - org.sqlite.SQLiteJDBCLoader.initialize() - } else { - // otherwise, on JVM, we should load the library through regular SQLite3 JDBC mechanisms; this will unpack the - // library to a temporary location, load it, and then clean up after itself. + NativeLibraries.resolve(SQLITE3_LIBRARY) { org.sqlite.SQLiteJDBCLoader.initialize() } } diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScript.kt b/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScript.kt index c930bd9e3c..ceb0312860 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScript.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScript.kt @@ -51,6 +51,7 @@ import elide.runtime.plugins.js.JavaScriptVersion.* private fun configureContext(builder: PolyglotContextBuilder): Unit = with(builder) { enableOptions( "js.allow-eval", + "js.annex-b", "js.async-context", "js.async-iterator-helpers", "js.async-stack-traces", @@ -79,7 +80,6 @@ import elide.runtime.plugins.js.JavaScriptVersion.* ) disableOptions( - "js.annex-b", "js.console", "js.graal-builtin", "js.interop-complete-promises", @@ -96,7 +96,7 @@ import elide.runtime.plugins.js.JavaScriptVersion.* setOptions( "js.charset" to config.charset.name(), - "js.commonjs-require-cwd" to config.npmConfig.modulesPath, + "js.commonjs-require-cwd" to (config.npmConfig.modulesPath?.ifBlank { null } ?: "."), "js.debug-property-name" to DEBUG_GLOBAL, "js.ecmascript-version" to config.language.symbol(), "js.function-constructor-cache-size" to FUNCTION_CONSTRUCTOR_CACHE_SIZE, diff --git a/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScriptConfig.kt b/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScriptConfig.kt index 4f9bfd95c9..f55107da77 100644 --- a/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScriptConfig.kt +++ b/packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/JavaScriptConfig.kt @@ -35,7 +35,7 @@ import elide.runtime.plugins.js.JavaScriptVersion.ES2022 * `/my_modules` *and* `/my_modules/node_modules`, as well as other pre-defined locations, according to * [the Node.js specification](https://nodejs.org/api/modules.html#modules_all_together). */ - public var modulesPath: String = "." + public var modulesPath: String? = null } public inner class BuiltInModulesConfig { diff --git a/tools/scripts/hello.ts b/tools/scripts/hello.ts new file mode 100644 index 0000000000..4c9d7a2e1c --- /dev/null +++ b/tools/scripts/hello.ts @@ -0,0 +1,2 @@ +const x: number = 42; +console.log(`Hello from TypeScript! The answer is ${x}`);