Lilijs is a runtime engine dedicated to running a subset of TypeScript efficiently on server.
On the backend side, an increasing number of developers are adopting TypeScript to typecheck their applications.
TypeScript enforces stricter semantics compared to JavaScript/ECMAScript.
For instance, this snippet {} + 3 is valid in JavaScript but rejected by TypeScript,
which requires the left operand to be either a number or a BigInt:
The core concept behind LiliJS is achieving superior performance compared to popular engines like V8 (Chrome), JavaScriptCore (Safari), or SpiderMonkey (Firefox) by:
- Focusing exclusively on TypeScript execution
- Supporting only a carefully selected subset of TypeScript (dubbed "TypeScript the Good Parts") optimized for performance
- Running on the Java Virtual Machine, which trades slightly longer startup times (still under 1 second) for enhanced peak performance compared to mainstream JavaScript engines
LiliJS includes an integrated linter based on SWC that can be run independently to verify your code's compatibility with "TypeScript the Good Parts." If your code falls outside this subset, LiliJS will throw a runtime error during the execution.
This is a kind of "superstrict mode" that does not support syntax/constructions of EcmaScript that are hard to optimize AND for which there is a better/simpler solution.
Here are a description of the restrictions to TypeScript Lilijs uses.
any is not allowed.
No enum declarations, namespaces and modules with runtime code, parameter properties in classes, Non-ECMAScript import = and export = assignments.
see --erasableSyntaxOnly for more information.
- no
var letandconstonly support reading a binding, not writing one so less memory leaks, and no Temporal Dead Zone for captured variables.
-
local functions are defined as
letinstead ofvar- you can not access a local function value before its definition
The following code is not valid with Lilijs.
function foo() { f = 5; function f() { } }
- you can not redeclare a local function
function foo() { function f() { } function f() { } }
- you can not access a local function value before its definition
The following code is not valid with Lilijs.
-
async functions are supported but the returned
Promiseis always completed (or on failure). The state not completed/in flight does not exist. In terms of implementation, we rely on the virtual threads (think go routine) of the Java Platform. -
generators are not supported, we do not know how to make them fast, this may change in the future.
-
argumentsinside a function is not supported (strict mode), uses...instead.
- you can not catch errors, errors are exceptional paths
- you can still use a Promise as a way to react to errors
- inheritance and super calls are not supported, you can use TypeScript interface instead.
- adding a field dynamically is not supported (not compatible with TypeScript).
We do not support BigInt at the moment, this restriction may be lifted in the future
The project has just begun, I hope to finish most of it in one year.
- local variables
- function, local function, arrow function and calls
- optional parameter, default parameter
- basic numeric ops
- basic control flow
- compound operations +=, -=, &=, etc
- label, break, continue in loop
- throw exception
- switch
- && and ||
- destructuring
- class
- local class, class with captured values
- private properties
- static method and static block
- typeof
- instanceof
- field, '.' and '?.' access
- method calls
- object literal
- array literal
- regex literal
- async functions and Promise
- Object & Function
- Math
- JSON
- Errors
- Symbol ??
- imports ??
- inlining cache (IC) for function definitions
- IC for call
- IC for numeric ops
- more inlining caches :)