Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: typescript support #968

Merged
merged 1 commit into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change meant to be part of this PR? Seems related to SQLite instead.

Copy link
Member Author

@sgammon sgammon Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's related -- fixes for native libs

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}`);
Loading