GitHub - artpar/jslike: Production-ready JavaScript interpreter with full ES6+ support using Acorn parser

10 min read Original article ↗

Production-ready JavaScript interpreter with full ES6+ support, native JSX parsing, and React integration. JSLike executes real JavaScript code with a custom runtime environment, supporting modern ES6+ features including classes, destructuring, template literals, JSX, and more.

Playground: https://artpar.github.io/jslike/

Features

  • Production-Ready - Handles files of any size, tested with 1000+ tests
  • Full ES6+ JavaScript Support - Classes, destructuring, template literals, spread operator, arrow functions
  • Native JSX Support - Parse and execute JSX without pre-transformation
  • TypeScript & TSX Module Support - Execute JS-compatible TypeScript syntax directly from .ts, .tsx, .mts, and .cts modules
  • React Integration - Import React hooks and components via moduleResolver
  • CSP-Safe - Tree-walking interpreter, no eval() or new Function()
  • ASI (Automatic Semicolon Insertion) - Write JavaScript naturally without mandatory semicolons
  • Acorn Parser - Battle-tested parser used by webpack, ESLint, and major tools
  • Zero Runtime Dependencies - Parser bundled, no npm install needed after build
  • REPL & CLI - Interactive development and direct file execution

Installation

Or for development:

git clone https://github.com/artpar/jslike.git
cd jslike
npm install
npm run build

Quick Start

Programmatic Usage

import { execute, createEnvironment } from 'jslike';

// Simple execution
const result = await execute(`
  const greeting = "Hello";
  const name = "World";
  greeting + ", " + name + "!"
`);
console.log(result); // "Hello, World!"

JSX Support

import { execute } from 'jslike';

const element = await execute(`
  function Button({ label, onClick }) {
    return <button className="btn" onClick={onClick}>{label}</button>;
  }

  <div className="container">
    <h1>Welcome</h1>
    <Button label="Click me" onClick={() => console.log('clicked')} />
  </div>
`);

// element is a React-compatible element object:
// { $$typeof: Symbol(react.element), type: 'div', props: {...}, ... }

React Integration

import { execute, createEnvironment } from 'jslike';
import * as React from 'react';

// Create module resolver for React imports
const moduleResolver = {
  async resolve(modulePath) {
    if (modulePath === 'react') {
      return { exports: React };  // Return native module exports
    }
    return null;
  }
};

// Create environment with React for JSX
const env = createEnvironment();
env.define('React', React);

// Execute with React hooks
const component = await execute(`
  import { useState, useEffect } from 'react';

  function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
      document.title = \`Count: \${count}\`;
    }, [count]);

    return (
      <div>
        <p>Count: {count}</p>
        <button onClick={() => setCount(count + 1)}>+</button>
      </div>
    );
  }

  <Counter />
`, env, { moduleResolver });

CLI Usage

# Run a file
npx jslike myfile.js

# Interactive REPL
npx jslike --repl

Module System

JSLike supports ES6 imports with a flexible module resolver:

Native Module Exports (React, lodash, etc.)

const moduleResolver = {
  async resolve(modulePath) {
    // Return native JavaScript objects directly
    if (modulePath === 'react') {
      return { exports: React };
    }
    if (modulePath === 'lodash') {
      return { exports: _ };
    }
    return null;
  }
};

Code Modules (parsed and executed)

const moduleResolver = {
  async resolve(modulePath, fromPath) {
    if (modulePath === './utils') {
      return {
        path: '/virtual/project/utils.js',
        code: `
          export function double(x) { return x * 2; }
          export const PI = 3.14159;
        `
      };
    }
    return null;
  }
};

fromPath is the path of the importing module. For top-level code, pass sourcePath to execute() so relative imports have a deterministic root. For nested imports, JSLike passes the resolved module path returned by the resolver.

const files = {
  '/virtual/project/main.ts': `
    import { user } from './user.ts';
    user.name
  `,
  '/virtual/project/user.ts': `
    type User = { name: string };
    export const user: User = { name: 'Ada' };
  `
};

const moduleResolver = {
  async resolve(modulePath, fromPath) {
    const base = new URL('.', `file://${fromPath}`).pathname;
    const resolvedPath = new URL(modulePath, `file://${base}`).pathname;

    if (!files[resolvedPath]) return null;
    return {
      path: resolvedPath,
      code: files[resolvedPath]
    };
  }
};

const result = await execute(files['/virtual/project/main.ts'], null, {
  moduleResolver,
  sourcePath: '/virtual/project/main.ts'
});
// "Ada"

Import Styles Supported

import { useState, useEffect } from 'react';     // Named imports
import React from 'react';                        // Default import
import * as Utils from './utils';                 // Namespace import

TypeScript Support

JSLike parses TypeScript and TSX with a bundled @sveltejs/acorn-typescript parser. TypeScript syntax is enabled automatically for sourcePath and resolved module paths ending in .ts, .tsx, .mts, or .cts. You can also force it with typescript: true or tsx: true.

const result = await execute(`
  interface User {
    name: string;
  }

  enum Role {
    Admin,
    Member
  }

  class Account {
    constructor(public user: User, readonly role: Role) {}
  }

  const account = new Account({ name: 'Ada' }, Role.Admin);
  account.user.name + ':' + account.role
`, null, {
  sourcePath: '/virtual/account.ts'
});
// "Ada:0"

Supported TypeScript runtime behavior:

  • Type-only declarations and annotations are erased: type, interface, declare, parameter/return annotations, tuple/readonly annotations.
  • Type-only imports and exports do not trigger runtime module resolution.
  • Type wrappers evaluate their inner expression: as, <T>value, satisfies, non-null !, generic call instantiation.
  • Enums execute as runtime enum objects, including numeric reverse mappings and string enum members.
  • Constructor parameter properties assign to this, including public, private, and readonly.
  • TSX parses and executes JSX in .tsx files or with tsx: true.

Unsupported runtime TypeScript constructs throw explicit errors instead of executing incorrectly. This currently includes namespace, export =, and import x = require(...).

JSX Features

Basic Elements

<div className="container">Hello World</div>
<input type="text" disabled />
<br />

Expressions

const name = "World";
<div>Hello {name}</div>
<div>{1 + 2 + 3}</div>
<div>{items.map(item => <span key={item.id}>{item.name}</span>)}</div>

Attributes

// String attributes
<div className="container" id="main">

// Expression attributes
<div className={isActive ? 'active' : 'inactive'}>

// Spread attributes
const props = { className: 'btn', disabled: true };
<button {...props}>Click</button>

// Boolean attributes
<input disabled />  // Same as disabled={true}

Fragments

<>
  <div>First</div>
  <div>Second</div>
</>

Components

// Function components
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
    </div>
  );
}

// Usage - components are stored as type (React behavior)
<Card title="Welcome">
  <p>Card content here</p>
</Card>

// To render, call component manually or use React renderer
Card({ title: "Welcome", children: <p>Content</p> })

Member Expression Components

const UI = {
  Button: ({ children }) => <button className="ui-btn">{children}</button>,
  Card: ({ children }) => <div className="ui-card">{children}</div>
};

<UI.Button>Click me</UI.Button>

Language Features

Variables

let x = 10;
const name = "Alice";
var isActive = true;

Functions

// Function declaration
function add(a, b) {
  return a + b;
}

// Arrow functions
const multiply = (x, y) => x * y;
const greet = name => `Hello, ${name}`;

// Default parameters
function greet(name = "World") {
  return `Hello, ${name}`;
}

// Rest parameters
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}

Classes

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  speak() {
    return `${this.name} barks`;
  }
}

const dog = new Dog("Rex");
dog.speak(); // "Rex barks"

Destructuring

// Object destructuring
const { name, age } = person;
const { name: userName, age: userAge } = person;

// Array destructuring
const [first, second, ...rest] = array;

// Parameter destructuring
function greet({ name, age }) {
  return `${name} is ${age}`;
}

Template Literals

const name = "World";
const greeting = `Hello, ${name}!`;
const multiline = `
  Line 1
  Line 2
`;

Async/Await

async function fetchData() {
  const response = await fetch(url);
  const data = await response.json();
  return data;
}

// Top-level await supported
const result = await fetchData();

Control Flow

// If-else
if (x > 10) {
  console.log("Greater");
} else if (x === 10) {
  console.log("Equal");
} else {
  console.log("Less");
}

// Ternary
const result = x > 5 ? "yes" : "no";

// Nullish coalescing
const value = input ?? "default";

// Optional chaining
const name = user?.profile?.name;

// Switch
switch (day) {
  case 1: return "Monday";
  case 2: return "Tuesday";
  default: return "Other";
}

Loops

// For loop
for (let i = 0; i < 10; i++) {
  console.log(i);
}

// For-of
for (const item of array) {
  console.log(item);
}

// For-in
for (const key in object) {
  console.log(key, object[key]);
}

// While
while (condition) { /* ... */ }

// Do-while
do { /* ... */ } while (condition);

Built-in Objects & Functions

Standard JavaScript

  • console.log(), console.error(), console.warn()
  • Math.PI, Math.sqrt(), Math.random(), etc.
  • Array, Object, String, Number, Boolean
  • Map, Set, WeakMap, WeakSet
  • RegExp, Symbol
  • JSON.parse(), JSON.stringify()
  • Promise, Date, Error
  • setTimeout(), setInterval()
  • parseInt(), parseFloat(), isNaN(), isFinite()

JSX Runtime

  • createElement(type, props, ...children) - Creates React-compatible elements
  • Fragment - Symbol for React fragments

Wang Standard Library

Utility functions available globally:

// Array operations
sort_by(array, key)      // Sort by key or function
group_by(array, key)     // Group into object by key
unique(array)            // Remove duplicates
chunk(array, size)       // Split into chunks
flatten(array, depth)    // Flatten nested arrays
first(array, n)          // Get first n items
last(array, n)           // Get last n items
range(start, end, step)  // Generate number sequence

// Object operations
keys(obj)                // Object.keys
values(obj)              // Object.values
entries(obj)             // Object.entries
pick(obj, keys)          // Pick specific keys
omit(obj, keys)          // Omit specific keys
merge(...objects)        // Merge objects
get(obj, path, default)  // Deep get with dot notation
clone(obj)               // Deep clone

// String operations
split(str, sep)          // Split string
join(arr, sep)           // Join array
trim(str)                // Trim whitespace
upper(str)               // Uppercase
lower(str)               // Lowercase
capitalize(str)          // Capitalize first letter
truncate(str, len)       // Truncate with ellipsis

// Type checking
is_string(val)           // Check if string
is_number(val)           // Check if number
is_array(val)            // Check if array
is_object(val)           // Check if object
is_function(val)         // Check if function
is_empty(val)            // Check if empty

// Math operations
sum(array)               // Sum of numbers
avg(array)               // Average
min(array)               // Minimum
max(array)               // Maximum
clamp(num, min, max)     // Clamp to range
round(num, decimals)     // Round to decimals

API Reference

execute(code, env?, options?)

Execute JavaScript code and return the result.

const result = await execute(code, env, {
  moduleResolver,        // For import statements
  executionController,   // For pause/resume/abort
  abortSignal,           // For cancellation
  sourcePath,            // Optional importer path for resolving top-level imports
  typescript,            // Parse TypeScript syntax
  tsx                    // Parse TypeScript + JSX syntax
});

createEnvironment()

Create a new execution environment with built-ins.

const env = createEnvironment();
env.define('myVar', 42);
env.define('myFunc', (x) => x * 2);

ModuleResolver

Interface for resolving imports:

const moduleResolver = {
  async resolve(modulePath, fromPath) {
    // Return { exports: object } for native modules
    // Return { code: string } for code modules
    // Return null if not found
  },
  async exists(modulePath, fromPath) {
    // Return boolean
  },
  async list(prefix) {
    // Return string[] of module paths
  }
};

ExecutionController

Control execution flow:

import { execute, ExecutionController } from 'jslike';

const controller = new ExecutionController();

// Start execution
const promise = execute(code, null, { executionController: controller });

// Control execution
controller.pause();
controller.resume();
controller.abort();

// Check state
console.log(controller.state); // 'running' | 'paused' | 'completed' | 'aborted'

Testing

npm test                    # Run all tests
npm test -- tests/jsx.test.js  # Run specific test file

1009 tests covering:

  • ES6+ language features
  • JSX parsing and execution
  • React integration
  • Module imports
  • Error handling
  • Edge cases

Architecture

src/
├── parser.js              - Bundled Acorn + acorn-jsx (~245KB)
├── index.js               - Main API (parse/execute)
├── interpreter/
│   └── interpreter.js     - Tree-walking interpreter (~2500 LOC)
├── runtime/
│   ├── environment.js     - Lexical scoping and closures
│   ├── builtins.js        - Built-in objects and JSX runtime
│   └── execution-controller.js - Pause/resume/abort
└── ast/
    └── nodes.js           - AST node types

Known Limitations

  • Generator functions (function*, yield) not supported
  • Tagged template literals not fully supported
  • Class getters/setters not fully supported
  • Proxies and Reflect API not implemented

License

MIT

Links