Skip to content

Commit

Permalink
fix: typescript support
Browse files Browse the repository at this point in the history
- 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 <[email protected]>
  • Loading branch information
sgammon committed Jun 4, 2024
1 parent 7b356b2 commit a9a17c5
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 33 deletions.
1 change: 1 addition & 0 deletions packages/graalvm-ts/api/graalvm-ts.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init> ()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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,53 @@
import java.util.List;
import org.graalvm.polyglot.SandboxPolicy;

/** TBD. */
/**
* TypeScript language implementation for GraalVM, meant for use via Elide.
*
* <p>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.
*
* <p>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<JSRealm> {
public static final String TEXT_MIME_TYPE = "text/typescript";
public static final String APPLICATION_MIME_TYPE = "application/typescript";
public static final String MODULE_MIME_TYPE = "application/typescript+module";
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));
Expand Down Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,35 @@
import java.util.zip.GZIPInputStream;
import org.apache.commons.io.IOUtils;

/** TBD. */
/**
* TypeScript Compiler
*
* <p>Hosts the TypeScript Compiler (tsc) implementation for Elide's support for the TypeScript
* language (see {@link elide.runtime.lang.typescript.TypeScriptLanguage})
*
* <p>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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypeScriptConfig, TypeScript>() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -39,6 +44,7 @@ class TypeScriptLanguageTest {

val ctx = ctx()
ctx.initialize("js")
ctx.initialize("ts")
ctx.enter()
val parsed = ctx.parse(jsSrc)
ctx.leave()
Expand All @@ -61,7 +67,6 @@ class TypeScriptLanguageTest {
assertNotNull(it)
}

ctx.initialize("ts")
ctx.enter()
ctx.parse(src)
ctx.leave()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -79,7 +80,6 @@ import elide.runtime.plugins.js.JavaScriptVersion.*
)

disableOptions(
"js.annex-b",
"js.console",
"js.graal-builtin",
"js.interop-complete-promises",
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions tools/scripts/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const x: number = 42;
console.log(`Hello from TypeScript! The answer is ${x}`);

0 comments on commit a9a17c5

Please sign in to comment.