jslike 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,77 +1,246 @@
1
1
  # JSLike
2
2
 
3
- **Production-ready JavaScript interpreter** with full ES6+ support using Acorn parser. JSLike executes real JavaScript code with a custom runtime environment, supporting modern ES6+ features including classes, destructuring, template literals, and more.
3
+ **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.
4
4
 
5
5
  ## Features
6
6
 
7
- - **✅ Production-Ready** - Handles files of any size, tested on 100+ line programs
7
+ - **Production-Ready** - Handles files of any size, tested with 1000+ tests
8
8
  - **Full ES6+ JavaScript Support** - Classes, destructuring, template literals, spread operator, arrow functions
9
+ - **Native JSX Support** - Parse and execute JSX without pre-transformation
10
+ - **React Integration** - Import React hooks and components via moduleResolver
11
+ - **CSP-Safe** - Tree-walking interpreter, no eval() or new Function()
9
12
  - **ASI (Automatic Semicolon Insertion)** - Write JavaScript naturally without mandatory semicolons
10
13
  - **Acorn Parser** - Battle-tested parser used by webpack, ESLint, and major tools
11
- - **Custom Interpreter** - ~2000 LOC tree-walking interpreter with proper scoping
12
- - **Zero Runtime Dependencies** - Only requires Node.js to run
13
- - **REPL** - Interactive development environment
14
- - **CLI** - Run `.js` files directly
14
+ - **Zero Runtime Dependencies** - Parser bundled, no npm install needed after build
15
+ - **REPL & CLI** - Interactive development and direct file execution
15
16
 
16
17
  ## Installation
17
18
 
18
19
  ```bash
19
- # For development (includes acorn for building)
20
+ npm install jslike
21
+ ```
22
+
23
+ Or for development:
24
+
25
+ ```bash
26
+ git clone https://github.com/artpar/jslike.git
27
+ cd jslike
20
28
  npm install
21
29
  npm run build
30
+ ```
22
31
 
23
- # For end users - zero runtime dependencies!
24
- # Just copy the src/ and bin/ directories and run:
25
- node bin/jslike.js examples/hello.js
32
+ ## Quick Start
33
+
34
+ ### Programmatic Usage
35
+
36
+ ```javascript
37
+ import { execute, createEnvironment } from 'jslike';
38
+
39
+ // Simple execution
40
+ const result = await execute(`
41
+ const greeting = "Hello";
42
+ const name = "World";
43
+ greeting + ", " + name + "!"
44
+ `);
45
+ console.log(result); // "Hello, World!"
26
46
  ```
27
47
 
28
- ### For End Users
48
+ ### JSX Support
49
+
50
+ ```javascript
51
+ import { execute } from 'jslike';
29
52
 
30
- The built project has **zero runtime dependencies**! The Acorn parser (~225KB) is bundled into `src/parser.js`.
53
+ const element = await execute(`
54
+ function Button({ label, onClick }) {
55
+ return <button className="btn" onClick={onClick}>{label}</button>;
56
+ }
31
57
 
32
- ```bash
33
- # No npm install needed after build!
34
- node bin/jslike.js myfile.js
58
+ <div className="container">
59
+ <h1>Welcome</h1>
60
+ <Button label="Click me" onClick={() => console.log('clicked')} />
61
+ </div>
62
+ `);
63
+
64
+ // element is a React-compatible element object:
65
+ // { $$typeof: Symbol(react.element), type: 'div', props: {...}, ... }
35
66
  ```
36
67
 
37
- Or install globally:
68
+ ### React Integration
69
+
70
+ ```javascript
71
+ import { execute, createEnvironment } from 'jslike';
72
+ import * as React from 'react';
73
+
74
+ // Create module resolver for React imports
75
+ const moduleResolver = {
76
+ async resolve(modulePath) {
77
+ if (modulePath === 'react') {
78
+ return { exports: React }; // Return native module exports
79
+ }
80
+ return null;
81
+ }
82
+ };
83
+
84
+ // Create environment with React for JSX
85
+ const env = createEnvironment();
86
+ env.define('React', React);
87
+
88
+ // Execute with React hooks
89
+ const component = await execute(`
90
+ import { useState, useEffect } from 'react';
91
+
92
+ function Counter() {
93
+ const [count, setCount] = useState(0);
94
+
95
+ useEffect(() => {
96
+ document.title = \`Count: \${count}\`;
97
+ }, [count]);
98
+
99
+ return (
100
+ <div>
101
+ <p>Count: {count}</p>
102
+ <button onClick={() => setCount(count + 1)}>+</button>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ <Counter />
108
+ `, env, { moduleResolver });
109
+ ```
110
+
111
+ ### CLI Usage
112
+
38
113
  ```bash
39
- npm install -g jslike
40
- jslike myfile.js
114
+ # Run a file
115
+ npx jslike myfile.js
116
+
117
+ # Interactive REPL
118
+ npx jslike --repl
41
119
  ```
42
120
 
43
- ## Usage
121
+ ## Module System
44
122
 
45
- ### Running a file
123
+ JSLike supports ES6 imports with a flexible module resolver:
46
124
 
47
- ```bash
48
- npm start examples/hello.js
49
- # or
50
- ./bin/jslike.js examples/hello.js
125
+ ### Native Module Exports (React, lodash, etc.)
126
+
127
+ ```javascript
128
+ const moduleResolver = {
129
+ async resolve(modulePath) {
130
+ // Return native JavaScript objects directly
131
+ if (modulePath === 'react') {
132
+ return { exports: React };
133
+ }
134
+ if (modulePath === 'lodash') {
135
+ return { exports: _ };
136
+ }
137
+ return null;
138
+ }
139
+ };
51
140
  ```
52
141
 
53
- ### Interactive REPL
142
+ ### Code Modules (parsed and executed)
54
143
 
55
- ```bash
56
- npm run repl
57
- # or
58
- ./bin/repl.js
144
+ ```javascript
145
+ const moduleResolver = {
146
+ async resolve(modulePath) {
147
+ if (modulePath === './utils') {
148
+ return {
149
+ code: `
150
+ export function double(x) { return x * 2; }
151
+ export const PI = 3.14159;
152
+ `
153
+ };
154
+ }
155
+ return null;
156
+ }
157
+ };
59
158
  ```
60
159
 
61
- ### Example REPL session
160
+ ### Import Styles Supported
62
161
 
162
+ ```javascript
163
+ import { useState, useEffect } from 'react'; // Named imports
164
+ import React from 'react'; // Default import
165
+ import * as Utils from './utils'; // Namespace import
63
166
  ```
64
- JSLike REPL v1.0.0
65
- Type JavaScript-like code and press Enter to execute
66
- Press Ctrl+C or Ctrl+D to exit
67
-
68
- > const x = 10;
69
- > const y = 20;
70
- > x + y
71
- 30
72
- > function greet(name) { return "Hello, " + name; }
73
- > greet("World")
74
- Hello, World
167
+
168
+ ## JSX Features
169
+
170
+ ### Basic Elements
171
+
172
+ ```javascript
173
+ <div className="container">Hello World</div>
174
+ <input type="text" disabled />
175
+ <br />
176
+ ```
177
+
178
+ ### Expressions
179
+
180
+ ```javascript
181
+ const name = "World";
182
+ <div>Hello {name}</div>
183
+ <div>{1 + 2 + 3}</div>
184
+ <div>{items.map(item => <span key={item.id}>{item.name}</span>)}</div>
185
+ ```
186
+
187
+ ### Attributes
188
+
189
+ ```javascript
190
+ // String attributes
191
+ <div className="container" id="main">
192
+
193
+ // Expression attributes
194
+ <div className={isActive ? 'active' : 'inactive'}>
195
+
196
+ // Spread attributes
197
+ const props = { className: 'btn', disabled: true };
198
+ <button {...props}>Click</button>
199
+
200
+ // Boolean attributes
201
+ <input disabled /> // Same as disabled={true}
202
+ ```
203
+
204
+ ### Fragments
205
+
206
+ ```javascript
207
+ <>
208
+ <div>First</div>
209
+ <div>Second</div>
210
+ </>
211
+ ```
212
+
213
+ ### Components
214
+
215
+ ```javascript
216
+ // Function components
217
+ function Card({ title, children }) {
218
+ return (
219
+ <div className="card">
220
+ <h2>{title}</h2>
221
+ <div className="card-body">{children}</div>
222
+ </div>
223
+ );
224
+ }
225
+
226
+ // Usage - components are stored as type (React behavior)
227
+ <Card title="Welcome">
228
+ <p>Card content here</p>
229
+ </Card>
230
+
231
+ // To render, call component manually or use React renderer
232
+ Card({ title: "Welcome", children: <p>Content</p> })
233
+ ```
234
+
235
+ ### Member Expression Components
236
+
237
+ ```javascript
238
+ const UI = {
239
+ Button: ({ children }) => <button className="ui-btn">{children}</button>,
240
+ Card: ({ children }) => <div className="ui-card">{children}</div>
241
+ };
242
+
243
+ <UI.Button>Click me</UI.Button>
75
244
  ```
76
245
 
77
246
  ## Language Features
@@ -94,30 +263,80 @@ function add(a, b) {
94
263
 
95
264
  // Arrow functions
96
265
  const multiply = (x, y) => x * y;
97
- const greet = (name) => {
98
- return "Hello, " + name;
99
- };
266
+ const greet = name => `Hello, ${name}`;
267
+
268
+ // Default parameters
269
+ function greet(name = "World") {
270
+ return `Hello, ${name}`;
271
+ }
272
+
273
+ // Rest parameters
274
+ function sum(...numbers) {
275
+ return numbers.reduce((a, b) => a + b, 0);
276
+ }
100
277
  ```
101
278
 
102
- ### Arrays
279
+ ### Classes
103
280
 
104
281
  ```javascript
105
- const numbers = [1, 2, 3, 4, 5];
106
- console.log(numbers[0]); // 1
107
- numbers[5] = 6;
282
+ class Animal {
283
+ constructor(name) {
284
+ this.name = name;
285
+ }
286
+
287
+ speak() {
288
+ return `${this.name} makes a sound`;
289
+ }
290
+ }
291
+
292
+ class Dog extends Animal {
293
+ speak() {
294
+ return `${this.name} barks`;
295
+ }
296
+ }
297
+
298
+ const dog = new Dog("Rex");
299
+ dog.speak(); // "Rex barks"
108
300
  ```
109
301
 
110
- ### Objects
302
+ ### Destructuring
111
303
 
112
304
  ```javascript
113
- const person = {
114
- name: "Bob",
115
- age: 30,
116
- greet: () => "Hello!"
117
- };
305
+ // Object destructuring
306
+ const { name, age } = person;
307
+ const { name: userName, age: userAge } = person;
308
+
309
+ // Array destructuring
310
+ const [first, second, ...rest] = array;
311
+
312
+ // Parameter destructuring
313
+ function greet({ name, age }) {
314
+ return `${name} is ${age}`;
315
+ }
316
+ ```
317
+
318
+ ### Template Literals
319
+
320
+ ```javascript
321
+ const name = "World";
322
+ const greeting = `Hello, ${name}!`;
323
+ const multiline = `
324
+ Line 1
325
+ Line 2
326
+ `;
327
+ ```
328
+
329
+ ### Async/Await
330
+
331
+ ```javascript
332
+ async function fetchData() {
333
+ const response = await fetch(url);
334
+ const data = await response.json();
335
+ return data;
336
+ }
118
337
 
119
- console.log(person.name); // Bob
120
- console.log(person["age"]); // 30
338
+ // Top-level await supported
339
+ const result = await fetchData();
121
340
  ```
122
341
 
123
342
  ### Control Flow
@@ -126,20 +345,26 @@ console.log(person["age"]); // 30
126
345
  // If-else
127
346
  if (x > 10) {
128
347
  console.log("Greater");
348
+ } else if (x === 10) {
349
+ console.log("Equal");
129
350
  } else {
130
- console.log("Smaller or equal");
351
+ console.log("Less");
131
352
  }
132
353
 
133
354
  // Ternary
134
355
  const result = x > 5 ? "yes" : "no";
135
356
 
357
+ // Nullish coalescing
358
+ const value = input ?? "default";
359
+
360
+ // Optional chaining
361
+ const name = user?.profile?.name;
362
+
136
363
  // Switch
137
364
  switch (day) {
138
- case 1:
139
- console.log("Monday");
140
- break;
141
- default:
142
- console.log("Other day");
365
+ case 1: return "Monday";
366
+ case 2: return "Tuesday";
367
+ default: return "Other";
143
368
  }
144
369
  ```
145
370
 
@@ -147,53 +372,30 @@ switch (day) {
147
372
 
148
373
  ```javascript
149
374
  // For loop
150
- for (let i = 0; i < 10; i = i + 1) {
375
+ for (let i = 0; i < 10; i++) {
151
376
  console.log(i);
152
377
  }
153
378
 
154
- // While loop
155
- while (condition) {
156
- // code
379
+ // For-of
380
+ for (const item of array) {
381
+ console.log(item);
157
382
  }
158
383
 
159
- // Do-while
160
- do {
161
- // code
162
- } while (condition);
163
-
164
384
  // For-in
165
- for (let key in object) {
166
- console.log(key);
385
+ for (const key in object) {
386
+ console.log(key, object[key]);
167
387
  }
168
388
 
169
- // For-of
170
- for (let value of array) {
171
- console.log(value);
172
- }
173
- ```
389
+ // While
390
+ while (condition) { /* ... */ }
174
391
 
175
- ### Error Handling
176
-
177
- ```javascript
178
- try {
179
- throw "Error message";
180
- } catch (error) {
181
- console.log("Caught:", error);
182
- } finally {
183
- console.log("Cleanup");
184
- }
392
+ // Do-while
393
+ do { /* ... */ } while (condition);
185
394
  ```
186
395
 
187
- ### Operators
396
+ ## Built-in Objects & Functions
188
397
 
189
- - **Arithmetic**: `+`, `-`, `*`, `/`, `%`, `**`
190
- - **Comparison**: `<`, `>`, `<=`, `>=`, `==`, `!=`, `===`, `!==`
191
- - **Logical**: `&&`, `||`, `!`, `??` (nullish coalescing)
192
- - **Bitwise**: `&`, `|`, `^`, `~`, `<<`, `>>`, `>>>`
193
- - **Assignment**: `=`, `+=`, `-=`, `*=`, `/=`, `%=`
194
- - **Update**: `++`, `--`
195
-
196
- ### Built-in Objects
398
+ ### Standard JavaScript
197
399
 
198
400
  - `console.log()`, `console.error()`, `console.warn()`
199
401
  - `Math.PI`, `Math.sqrt()`, `Math.random()`, etc.
@@ -201,148 +403,174 @@ try {
201
403
  - `Map`, `Set`, `WeakMap`, `WeakSet`
202
404
  - `RegExp`, `Symbol`
203
405
  - `JSON.parse()`, `JSON.stringify()`
406
+ - `Promise`, `Date`, `Error`
204
407
  - `setTimeout()`, `setInterval()`
205
408
  - `parseInt()`, `parseFloat()`, `isNaN()`, `isFinite()`
206
- - `Promise`, `Date`, `Error`
207
409
 
208
- ## Examples
410
+ ### JSX Runtime
411
+
412
+ - `createElement(type, props, ...children)` - Creates React-compatible elements
413
+ - `Fragment` - Symbol for React fragments
209
414
 
210
- See the `examples/` directory for more code samples:
415
+ ### Wang Standard Library
211
416
 
212
- - `hello.js` - Basic hello world
213
- - `functions.js` - Function examples including closures
214
- - `loops.js` - All loop types
215
- - `arrays-objects.js` - Arrays and objects
216
- - `algorithms.js` - Factorial, Fibonacci, prime numbers
217
- - `control-flow.js` - If-else, switch, try-catch
218
- - `comprehensive.js` - Complete feature demonstration
417
+ Utility functions available globally:
219
418
 
220
- ## Development
419
+ ```javascript
420
+ // Array operations
421
+ sort_by(array, key) // Sort by key or function
422
+ group_by(array, key) // Group into object by key
423
+ unique(array) // Remove duplicates
424
+ chunk(array, size) // Split into chunks
425
+ flatten(array, depth) // Flatten nested arrays
426
+ first(array, n) // Get first n items
427
+ last(array, n) // Get last n items
428
+ range(start, end, step) // Generate number sequence
429
+
430
+ // Object operations
431
+ keys(obj) // Object.keys
432
+ values(obj) // Object.values
433
+ entries(obj) // Object.entries
434
+ pick(obj, keys) // Pick specific keys
435
+ omit(obj, keys) // Omit specific keys
436
+ merge(...objects) // Merge objects
437
+ get(obj, path, default) // Deep get with dot notation
438
+ clone(obj) // Deep clone
439
+
440
+ // String operations
441
+ split(str, sep) // Split string
442
+ join(arr, sep) // Join array
443
+ trim(str) // Trim whitespace
444
+ upper(str) // Uppercase
445
+ lower(str) // Lowercase
446
+ capitalize(str) // Capitalize first letter
447
+ truncate(str, len) // Truncate with ellipsis
448
+
449
+ // Type checking
450
+ is_string(val) // Check if string
451
+ is_number(val) // Check if number
452
+ is_array(val) // Check if array
453
+ is_object(val) // Check if object
454
+ is_function(val) // Check if function
455
+ is_empty(val) // Check if empty
456
+
457
+ // Math operations
458
+ sum(array) // Sum of numbers
459
+ avg(array) // Average
460
+ min(array) // Minimum
461
+ max(array) // Maximum
462
+ clamp(num, min, max) // Clamp to range
463
+ round(num, decimals) // Round to decimals
464
+ ```
221
465
 
222
- ### Build Process
466
+ ## API Reference
223
467
 
224
- The build process bundles the Acorn parser into `src/parser.js`:
468
+ ### execute(code, env?, options?)
225
469
 
226
- ```bash
227
- # Bundle Acorn parser (creates self-contained parser.js)
228
- npm run build
470
+ Execute JavaScript code and return the result.
471
+
472
+ ```javascript
473
+ const result = await execute(code, env, {
474
+ moduleResolver, // For import statements
475
+ executionController, // For pause/resume/abort
476
+ abortSignal // For cancellation
477
+ });
229
478
  ```
230
479
 
231
- This creates `src/parser.js` (~225KB) which includes:
232
- - Complete Acorn parser
233
- - No external dependencies at runtime
234
- - ES6 module format
480
+ ### createEnvironment()
235
481
 
236
- ### Debug mode
482
+ Create a new execution environment with built-ins.
237
483
 
238
- ```bash
239
- DEBUG=1 ./bin/jslike.js examples/hello.js
484
+ ```javascript
485
+ const env = createEnvironment();
486
+ env.define('myVar', 42);
487
+ env.define('myFunc', (x) => x * 2);
240
488
  ```
241
489
 
242
- ### Architecture
490
+ ### ModuleResolver
243
491
 
244
- ```
245
- src/
246
- ├── parser.js - Bundled Acorn parser (~225KB, self-contained)
247
- ├── index.js - Main API (parse/execute functions)
248
- ├── interpreter/
249
- │ └── interpreter.js - Tree-walking interpreter (~2000 LOC)
250
- ├── runtime/
251
- │ ├── environment.js - Lexical scoping and closures
252
- │ └── builtins.js - Built-in objects (console, Math, JSON, etc.)
253
- └── ast/
254
- └── nodes.js - AST node constructors (ESTree format)
492
+ Interface for resolving imports:
493
+
494
+ ```javascript
495
+ const moduleResolver = {
496
+ async resolve(modulePath, fromPath) {
497
+ // Return { exports: object } for native modules
498
+ // Return { code: string } for code modules
499
+ // Return null if not found
500
+ },
501
+ async exists(modulePath, fromPath) {
502
+ // Return boolean
503
+ },
504
+ async list(prefix) {
505
+ // Return string[] of module paths
506
+ }
507
+ };
255
508
  ```
256
509
 
257
- ## How It Works
258
-
259
- 1. **Parsing**: Acorn parser processes JavaScript code and builds an ESTree-compatible AST
260
- 2. **Evaluation**: The interpreter walks the AST and executes each node
261
- 3. **Runtime**: Environment manages variable scopes (lexical scoping with closures)
262
- 4. **Built-ins**: Native JavaScript objects (console, Math, JSON, etc.) integrated into runtime
263
-
264
- ## Current Status
265
-
266
- ### ✅ Production-Ready & Fully Implemented
267
-
268
- **ES6+ Language Features**:
269
- - ✅ Variables (var, let, const)
270
- - ✅ Functions (declarations, expressions, arrow functions)
271
- - ✅ **Classes** with constructors, methods, and inheritance
272
- - ✅ **Destructuring** (objects and arrays)
273
- - ✅ **Template literals** with interpolation
274
- - ✅ **Spread operator** for arrays and objects
275
- - ✅ Rest parameters `(...rest)`
276
- - ✅ Object/array shorthand syntax
277
- - ✅ Method shorthand in objects
278
- - ✅ **ASI (Automatic Semicolon Insertion)** - semicolons optional!
279
- - ✅ All operators (arithmetic, logical, bitwise, comparison)
280
- - ✅ Control flow (if/else, switch, try/catch)
281
- - ✅ Loops (for, while, do-while, for-in, for-of)
282
- - ✅ Closures and higher-order functions
283
- - ✅ Proper `this` binding in classes and methods
284
- - ✅ Comments (single-line //, multi-line /* */)
285
- - ✅ **Async/await** with Promises
286
- - ✅ **Regular expressions** (literals and RegExp constructor)
287
- - ✅ Enhanced error messages with suggestions
288
-
289
- **Infrastructure**:
290
- - ✅ Acorn parser - production-grade ES2020 support
291
- - ✅ Tree-walking interpreter (~2000 LOC)
292
- - ✅ Runtime environment with proper lexical scoping
293
- - ✅ Built-in objects (console, Math, JSON, Array, Object, etc.)
294
- - ✅ CLI for running .js files
295
- - ✅ Interactive REPL
296
- - ✅ Handles files of **any size** (tested on 100+ line programs)
297
- - ✅ **No file size limitations** - O(n) parsing performance
298
-
299
- ### Performance
300
-
301
- - ✅ **Fast parsing**: O(n) linear time complexity
302
- - ✅ **Handles large files**: No OOM errors, no timeouts
303
- - ✅ **Production-tested parser**: Acorn is used by webpack, ESLint, Rollup
304
- - ✅ Tested on complex programs with 100+ lines
305
-
306
- ### Not Yet Implemented
307
-
308
- - Generators (function* and yield)
309
- - Proxies and Reflect API
310
- - Module system (import/export between files)
510
+ ### ExecutionController
311
511
 
312
- ## Testing
512
+ Control execution flow:
313
513
 
314
- All programs work, including complex multi-file applications:
514
+ ```javascript
515
+ import { execute, ExecutionController } from 'jslike';
516
+
517
+ const controller = new ExecutionController();
518
+
519
+ // Start execution
520
+ const promise = execute(code, null, { executionController: controller });
521
+
522
+ // Control execution
523
+ controller.pause();
524
+ controller.resume();
525
+ controller.abort();
526
+
527
+ // Check state
528
+ console.log(controller.state); // 'running' | 'paused' | 'completed' | 'aborted'
529
+ ```
530
+
531
+ ## Testing
315
532
 
316
533
  ```bash
317
- # Run test suite
318
- npm test
534
+ npm test # Run all tests
535
+ npm test -- tests/jsx.test.js # Run specific test file
536
+ ```
319
537
 
320
- # Test simple programs
321
- node bin/jslike.js examples/hello.js
322
- node bin/jslike.js examples/functions.js
538
+ **1009 tests** covering:
539
+ - ES6+ language features
540
+ - JSX parsing and execution
541
+ - React integration
542
+ - Module imports
543
+ - Error handling
544
+ - Edge cases
323
545
 
324
- # Test ES6 features
325
- node bin/jslike.js examples/es6-test.js
326
- node bin/jslike.js examples/class-simple.js
546
+ ## Architecture
327
547
 
328
- # Test complex algorithms (100+ lines)
329
- node bin/jslike.js examples/algorithms.js
330
548
  ```
549
+ src/
550
+ ├── parser.js - Bundled Acorn + acorn-jsx (~245KB)
551
+ ├── index.js - Main API (parse/execute)
552
+ ├── interpreter/
553
+ │ └── interpreter.js - Tree-walking interpreter (~2500 LOC)
554
+ ├── runtime/
555
+ │ ├── environment.js - Lexical scoping and closures
556
+ │ ├── builtins.js - Built-in objects and JSX runtime
557
+ │ └── execution-controller.js - Pause/resume/abort
558
+ └── ast/
559
+ └── nodes.js - AST node types
560
+ ```
561
+
562
+ ## Known Limitations
331
563
 
332
- **All 698 tests pass**, covering:
333
- - Variables (const, let, var)
334
- - Function declarations and arrow functions
335
- - Object and array literals
336
- - Destructuring (objects, arrays, parameters)
337
- - Control flow and loops
338
- - Closures and higher-order functions
339
- - Classes, inheritance, and constructors
340
- - Template literals
341
- - Async/await and Promises
342
- - Regular expressions
343
- - Native method calls
344
- - Error handling and edge cases
564
+ - Generator functions (`function*`, `yield`) not supported
565
+ - Tagged template literals not fully supported
566
+ - Class getters/setters not fully supported
567
+ - Proxies and Reflect API not implemented
345
568
 
346
569
  ## License
347
570
 
348
571
  MIT
572
+
573
+ ## Links
574
+
575
+ - [npm package](https://www.npmjs.com/package/jslike)
576
+ - [GitHub repository](https://github.com/artpar/jslike)
package/dist/esm/index.js CHANGED
@@ -133,17 +133,29 @@ export async function execute(code, env = null, options = {}) {
133
133
  controller != null;
134
134
 
135
135
  if (needsAsync) {
136
- const result = await interpreter.evaluateAsync(ast, env);
136
+ let result = await interpreter.evaluateAsync(ast, env);
137
137
  if (controller) {
138
138
  controller._complete();
139
139
  }
140
- return result instanceof ReturnValue ? result.value : result;
140
+ result = result instanceof ReturnValue ? result.value : result;
141
+
142
+ // If result is undefined and code has module declarations, return default export
143
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
144
+ return interpreter.moduleExports.default;
145
+ }
146
+ return result;
141
147
  } else {
142
- const result = interpreter.evaluate(ast, env);
148
+ let result = interpreter.evaluate(ast, env);
143
149
  if (controller) {
144
150
  controller._complete();
145
151
  }
146
- return result instanceof ReturnValue ? result.value : result;
152
+ result = result instanceof ReturnValue ? result.value : result;
153
+
154
+ // If result is undefined and code has module declarations, return default export
155
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
156
+ return interpreter.moduleExports.default;
157
+ }
158
+ return result;
147
159
  }
148
160
  } catch (e) {
149
161
  // Mark as aborted if that's the error type
@@ -1671,29 +1671,36 @@ export class Interpreter {
1671
1671
  throw new Error(`Cannot find module '${modulePath}'`);
1672
1672
  }
1673
1673
 
1674
- // Handle both old (string) and new (ModuleResolution) formats
1675
- const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
1676
-
1677
- // Parse and execute module in its own environment
1678
- const moduleAst = acornParse(moduleCode, {
1679
- ecmaVersion: 2020,
1680
- sourceType: 'module',
1681
- locations: false
1682
- });
1683
- const moduleEnv = new Environment(this.globalEnv);
1684
-
1685
- // Create a new interpreter for the module with shared module cache
1686
- const moduleInterpreter = new Interpreter(this.globalEnv, {
1687
- moduleResolver: this.moduleResolver
1688
- });
1689
- moduleInterpreter.moduleCache = this.moduleCache; // Share cache
1690
-
1691
- // Execute module and collect exports
1692
- await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
1693
-
1694
- // Cache the module exports
1695
- moduleExports = moduleInterpreter.moduleExports;
1696
- this.moduleCache.set(modulePath, moduleExports);
1674
+ // Handle native module exports (for libraries like React)
1675
+ // If resolution has 'exports' property, use it directly without parsing
1676
+ if (resolution.exports) {
1677
+ moduleExports = resolution.exports;
1678
+ this.moduleCache.set(modulePath, moduleExports);
1679
+ } else {
1680
+ // Handle both old (string) and new (ModuleResolution) formats
1681
+ const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
1682
+
1683
+ // Parse and execute module in its own environment
1684
+ const moduleAst = acornParse(moduleCode, {
1685
+ ecmaVersion: 2020,
1686
+ sourceType: 'module',
1687
+ locations: false
1688
+ });
1689
+ const moduleEnv = new Environment(this.globalEnv);
1690
+
1691
+ // Create a new interpreter for the module with shared module cache
1692
+ const moduleInterpreter = new Interpreter(this.globalEnv, {
1693
+ moduleResolver: this.moduleResolver
1694
+ });
1695
+ moduleInterpreter.moduleCache = this.moduleCache; // Share cache
1696
+
1697
+ // Execute module and collect exports
1698
+ await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
1699
+
1700
+ // Cache the module exports
1701
+ moduleExports = moduleInterpreter.moduleExports;
1702
+ this.moduleCache.set(modulePath, moduleExports);
1703
+ }
1697
1704
  }
1698
1705
 
1699
1706
  // Import specified bindings into current environment
package/dist/index.cjs CHANGED
@@ -7765,20 +7765,25 @@ var Interpreter = class _Interpreter {
7765
7765
  if (!resolution) {
7766
7766
  throw new Error(`Cannot find module '${modulePath}'`);
7767
7767
  }
7768
- const moduleCode = typeof resolution === "string" ? resolution : resolution.code;
7769
- const moduleAst = jsxParse(moduleCode, {
7770
- ecmaVersion: 2020,
7771
- sourceType: "module",
7772
- locations: false
7773
- });
7774
- const moduleEnv = new Environment(this.globalEnv);
7775
- const moduleInterpreter = new _Interpreter(this.globalEnv, {
7776
- moduleResolver: this.moduleResolver
7777
- });
7778
- moduleInterpreter.moduleCache = this.moduleCache;
7779
- await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
7780
- moduleExports = moduleInterpreter.moduleExports;
7781
- this.moduleCache.set(modulePath, moduleExports);
7768
+ if (resolution.exports) {
7769
+ moduleExports = resolution.exports;
7770
+ this.moduleCache.set(modulePath, moduleExports);
7771
+ } else {
7772
+ const moduleCode = typeof resolution === "string" ? resolution : resolution.code;
7773
+ const moduleAst = jsxParse(moduleCode, {
7774
+ ecmaVersion: 2020,
7775
+ sourceType: "module",
7776
+ locations: false
7777
+ });
7778
+ const moduleEnv = new Environment(this.globalEnv);
7779
+ const moduleInterpreter = new _Interpreter(this.globalEnv, {
7780
+ moduleResolver: this.moduleResolver
7781
+ });
7782
+ moduleInterpreter.moduleCache = this.moduleCache;
7783
+ await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
7784
+ moduleExports = moduleInterpreter.moduleExports;
7785
+ this.moduleCache.set(modulePath, moduleExports);
7786
+ }
7782
7787
  }
7783
7788
  for (const specifier of node.specifiers) {
7784
7789
  if (specifier.type === "ImportSpecifier") {
@@ -9210,6 +9215,7 @@ function containsTopLevelAwait(node) {
9210
9215
  return false;
9211
9216
  }
9212
9217
  async function execute(code, env = null, options = {}) {
9218
+ var _a, _b;
9213
9219
  const controller = options.executionController;
9214
9220
  if (controller) {
9215
9221
  controller._start();
@@ -9226,17 +9232,25 @@ async function execute(code, env = null, options = {}) {
9226
9232
  });
9227
9233
  const needsAsync = options.sourceType === "module" || containsModuleDeclarations(ast) || containsTopLevelAwait(ast) || controller != null;
9228
9234
  if (needsAsync) {
9229
- const result = await interpreter.evaluateAsync(ast, env);
9235
+ let result = await interpreter.evaluateAsync(ast, env);
9230
9236
  if (controller) {
9231
9237
  controller._complete();
9232
9238
  }
9233
- return result instanceof ReturnValue ? result.value : result;
9239
+ result = result instanceof ReturnValue ? result.value : result;
9240
+ if (result === void 0 && ((_a = interpreter.moduleExports) == null ? void 0 : _a.default) !== void 0) {
9241
+ return interpreter.moduleExports.default;
9242
+ }
9243
+ return result;
9234
9244
  } else {
9235
- const result = interpreter.evaluate(ast, env);
9245
+ let result = interpreter.evaluate(ast, env);
9236
9246
  if (controller) {
9237
9247
  controller._complete();
9238
9248
  }
9239
- return result instanceof ReturnValue ? result.value : result;
9249
+ result = result instanceof ReturnValue ? result.value : result;
9250
+ if (result === void 0 && ((_b = interpreter.moduleExports) == null ? void 0 : _b.default) !== void 0) {
9251
+ return interpreter.moduleExports.default;
9252
+ }
9253
+ return result;
9240
9254
  }
9241
9255
  } catch (e) {
9242
9256
  if (controller && e.name === "AbortError") {
package/dist/index.d.cts CHANGED
@@ -8836,29 +8836,36 @@ class Interpreter {
8836
8836
  throw new Error(`Cannot find module '${modulePath}'`);
8837
8837
  }
8838
8838
 
8839
- // Handle both old (string) and new (ModuleResolution) formats
8840
- const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
8841
-
8842
- // Parse and execute module in its own environment
8843
- const moduleAst = jsxParse(moduleCode, {
8844
- ecmaVersion: 2020,
8845
- sourceType: 'module',
8846
- locations: false
8847
- });
8848
- const moduleEnv = new Environment(this.globalEnv);
8839
+ // Handle native module exports (for libraries like React)
8840
+ // If resolution has 'exports' property, use it directly without parsing
8841
+ if (resolution.exports) {
8842
+ moduleExports = resolution.exports;
8843
+ this.moduleCache.set(modulePath, moduleExports);
8844
+ } else {
8845
+ // Handle both old (string) and new (ModuleResolution) formats
8846
+ const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
8847
+
8848
+ // Parse and execute module in its own environment
8849
+ const moduleAst = jsxParse(moduleCode, {
8850
+ ecmaVersion: 2020,
8851
+ sourceType: 'module',
8852
+ locations: false
8853
+ });
8854
+ const moduleEnv = new Environment(this.globalEnv);
8849
8855
 
8850
- // Create a new interpreter for the module with shared module cache
8851
- const moduleInterpreter = new Interpreter(this.globalEnv, {
8852
- moduleResolver: this.moduleResolver
8853
- });
8854
- moduleInterpreter.moduleCache = this.moduleCache; // Share cache
8856
+ // Create a new interpreter for the module with shared module cache
8857
+ const moduleInterpreter = new Interpreter(this.globalEnv, {
8858
+ moduleResolver: this.moduleResolver
8859
+ });
8860
+ moduleInterpreter.moduleCache = this.moduleCache; // Share cache
8855
8861
 
8856
- // Execute module and collect exports
8857
- await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
8862
+ // Execute module and collect exports
8863
+ await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
8858
8864
 
8859
- // Cache the module exports
8860
- moduleExports = moduleInterpreter.moduleExports;
8861
- this.moduleCache.set(modulePath, moduleExports);
8865
+ // Cache the module exports
8866
+ moduleExports = moduleInterpreter.moduleExports;
8867
+ this.moduleCache.set(modulePath, moduleExports);
8868
+ }
8862
8869
  }
8863
8870
 
8864
8871
  // Import specified bindings into current environment
@@ -10764,17 +10771,29 @@ async function execute(code, env = null, options = {}) {
10764
10771
  controller != null;
10765
10772
 
10766
10773
  if (needsAsync) {
10767
- const result = await interpreter.evaluateAsync(ast, env);
10774
+ let result = await interpreter.evaluateAsync(ast, env);
10768
10775
  if (controller) {
10769
10776
  controller._complete();
10770
10777
  }
10771
- return result instanceof ReturnValue ? result.value : result;
10778
+ result = result instanceof ReturnValue ? result.value : result;
10779
+
10780
+ // If result is undefined and code has module declarations, return default export
10781
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
10782
+ return interpreter.moduleExports.default;
10783
+ }
10784
+ return result;
10772
10785
  } else {
10773
- const result = interpreter.evaluate(ast, env);
10786
+ let result = interpreter.evaluate(ast, env);
10774
10787
  if (controller) {
10775
10788
  controller._complete();
10776
10789
  }
10777
- return result instanceof ReturnValue ? result.value : result;
10790
+ result = result instanceof ReturnValue ? result.value : result;
10791
+
10792
+ // If result is undefined and code has module declarations, return default export
10793
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
10794
+ return interpreter.moduleExports.default;
10795
+ }
10796
+ return result;
10778
10797
  }
10779
10798
  } catch (e) {
10780
10799
  // Mark as aborted if that's the error type
package/dist/index.d.ts CHANGED
@@ -8836,29 +8836,36 @@ class Interpreter {
8836
8836
  throw new Error(`Cannot find module '${modulePath}'`);
8837
8837
  }
8838
8838
 
8839
- // Handle both old (string) and new (ModuleResolution) formats
8840
- const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
8841
-
8842
- // Parse and execute module in its own environment
8843
- const moduleAst = jsxParse(moduleCode, {
8844
- ecmaVersion: 2020,
8845
- sourceType: 'module',
8846
- locations: false
8847
- });
8848
- const moduleEnv = new Environment(this.globalEnv);
8839
+ // Handle native module exports (for libraries like React)
8840
+ // If resolution has 'exports' property, use it directly without parsing
8841
+ if (resolution.exports) {
8842
+ moduleExports = resolution.exports;
8843
+ this.moduleCache.set(modulePath, moduleExports);
8844
+ } else {
8845
+ // Handle both old (string) and new (ModuleResolution) formats
8846
+ const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
8847
+
8848
+ // Parse and execute module in its own environment
8849
+ const moduleAst = jsxParse(moduleCode, {
8850
+ ecmaVersion: 2020,
8851
+ sourceType: 'module',
8852
+ locations: false
8853
+ });
8854
+ const moduleEnv = new Environment(this.globalEnv);
8849
8855
 
8850
- // Create a new interpreter for the module with shared module cache
8851
- const moduleInterpreter = new Interpreter(this.globalEnv, {
8852
- moduleResolver: this.moduleResolver
8853
- });
8854
- moduleInterpreter.moduleCache = this.moduleCache; // Share cache
8856
+ // Create a new interpreter for the module with shared module cache
8857
+ const moduleInterpreter = new Interpreter(this.globalEnv, {
8858
+ moduleResolver: this.moduleResolver
8859
+ });
8860
+ moduleInterpreter.moduleCache = this.moduleCache; // Share cache
8855
8861
 
8856
- // Execute module and collect exports
8857
- await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
8862
+ // Execute module and collect exports
8863
+ await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
8858
8864
 
8859
- // Cache the module exports
8860
- moduleExports = moduleInterpreter.moduleExports;
8861
- this.moduleCache.set(modulePath, moduleExports);
8865
+ // Cache the module exports
8866
+ moduleExports = moduleInterpreter.moduleExports;
8867
+ this.moduleCache.set(modulePath, moduleExports);
8868
+ }
8862
8869
  }
8863
8870
 
8864
8871
  // Import specified bindings into current environment
@@ -10764,17 +10771,29 @@ async function execute(code, env = null, options = {}) {
10764
10771
  controller != null;
10765
10772
 
10766
10773
  if (needsAsync) {
10767
- const result = await interpreter.evaluateAsync(ast, env);
10774
+ let result = await interpreter.evaluateAsync(ast, env);
10768
10775
  if (controller) {
10769
10776
  controller._complete();
10770
10777
  }
10771
- return result instanceof ReturnValue ? result.value : result;
10778
+ result = result instanceof ReturnValue ? result.value : result;
10779
+
10780
+ // If result is undefined and code has module declarations, return default export
10781
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
10782
+ return interpreter.moduleExports.default;
10783
+ }
10784
+ return result;
10772
10785
  } else {
10773
- const result = interpreter.evaluate(ast, env);
10786
+ let result = interpreter.evaluate(ast, env);
10774
10787
  if (controller) {
10775
10788
  controller._complete();
10776
10789
  }
10777
- return result instanceof ReturnValue ? result.value : result;
10790
+ result = result instanceof ReturnValue ? result.value : result;
10791
+
10792
+ // If result is undefined and code has module declarations, return default export
10793
+ if (result === undefined && interpreter.moduleExports?.default !== undefined) {
10794
+ return interpreter.moduleExports.default;
10795
+ }
10796
+ return result;
10778
10797
  }
10779
10798
  } catch (e) {
10780
10799
  // Mark as aborted if that's the error type
package/dist/index.js CHANGED
@@ -7731,20 +7731,25 @@ var Interpreter = class _Interpreter {
7731
7731
  if (!resolution) {
7732
7732
  throw new Error(`Cannot find module '${modulePath}'`);
7733
7733
  }
7734
- const moduleCode = typeof resolution === "string" ? resolution : resolution.code;
7735
- const moduleAst = jsxParse(moduleCode, {
7736
- ecmaVersion: 2020,
7737
- sourceType: "module",
7738
- locations: false
7739
- });
7740
- const moduleEnv = new Environment(this.globalEnv);
7741
- const moduleInterpreter = new _Interpreter(this.globalEnv, {
7742
- moduleResolver: this.moduleResolver
7743
- });
7744
- moduleInterpreter.moduleCache = this.moduleCache;
7745
- await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
7746
- moduleExports = moduleInterpreter.moduleExports;
7747
- this.moduleCache.set(modulePath, moduleExports);
7734
+ if (resolution.exports) {
7735
+ moduleExports = resolution.exports;
7736
+ this.moduleCache.set(modulePath, moduleExports);
7737
+ } else {
7738
+ const moduleCode = typeof resolution === "string" ? resolution : resolution.code;
7739
+ const moduleAst = jsxParse(moduleCode, {
7740
+ ecmaVersion: 2020,
7741
+ sourceType: "module",
7742
+ locations: false
7743
+ });
7744
+ const moduleEnv = new Environment(this.globalEnv);
7745
+ const moduleInterpreter = new _Interpreter(this.globalEnv, {
7746
+ moduleResolver: this.moduleResolver
7747
+ });
7748
+ moduleInterpreter.moduleCache = this.moduleCache;
7749
+ await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
7750
+ moduleExports = moduleInterpreter.moduleExports;
7751
+ this.moduleCache.set(modulePath, moduleExports);
7752
+ }
7748
7753
  }
7749
7754
  for (const specifier of node.specifiers) {
7750
7755
  if (specifier.type === "ImportSpecifier") {
@@ -9176,6 +9181,7 @@ function containsTopLevelAwait(node) {
9176
9181
  return false;
9177
9182
  }
9178
9183
  async function execute(code, env = null, options = {}) {
9184
+ var _a, _b;
9179
9185
  const controller = options.executionController;
9180
9186
  if (controller) {
9181
9187
  controller._start();
@@ -9192,17 +9198,25 @@ async function execute(code, env = null, options = {}) {
9192
9198
  });
9193
9199
  const needsAsync = options.sourceType === "module" || containsModuleDeclarations(ast) || containsTopLevelAwait(ast) || controller != null;
9194
9200
  if (needsAsync) {
9195
- const result = await interpreter.evaluateAsync(ast, env);
9201
+ let result = await interpreter.evaluateAsync(ast, env);
9196
9202
  if (controller) {
9197
9203
  controller._complete();
9198
9204
  }
9199
- return result instanceof ReturnValue ? result.value : result;
9205
+ result = result instanceof ReturnValue ? result.value : result;
9206
+ if (result === void 0 && ((_a = interpreter.moduleExports) == null ? void 0 : _a.default) !== void 0) {
9207
+ return interpreter.moduleExports.default;
9208
+ }
9209
+ return result;
9200
9210
  } else {
9201
- const result = interpreter.evaluate(ast, env);
9211
+ let result = interpreter.evaluate(ast, env);
9202
9212
  if (controller) {
9203
9213
  controller._complete();
9204
9214
  }
9205
- return result instanceof ReturnValue ? result.value : result;
9215
+ result = result instanceof ReturnValue ? result.value : result;
9216
+ if (result === void 0 && ((_b = interpreter.moduleExports) == null ? void 0 : _b.default) !== void 0) {
9217
+ return interpreter.moduleExports.default;
9218
+ }
9219
+ return result;
9206
9220
  }
9207
9221
  } catch (e) {
9208
9222
  if (controller && e.name === "AbortError") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jslike",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Production-ready JavaScript interpreter with full ES6+ support using Acorn parser",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",