Skip to content

Commit 72242b6

Browse files
committed
Replace @tootallnate/quickjs-emscripten with quickjs-wasi
Migrate degenerator, pac-resolver, and pac-proxy-agent from @tootallnate/quickjs-emscripten to quickjs-wasi, a lighter-weight QuickJS WASM runtime built on quickjs-ng with WASI reactor mode. Key changes: - degenerator: compile() now takes a QuickJS instance directly instead of QuickJSWASMModule (no more qjs.newContext() — the QuickJS instance IS the context). Host callbacks receive _this as first arg. resolvePromise() returns {value}|{error} instead of VmCallResult. Promise bridging calls executePendingJobs() after resolve/reject. - pac-resolver: createPacResolver() takes QuickJS instead of QuickJSWASMModule. Updated workspace dependency on degenerator. - pac-proxy-agent: QuickJS.create() replaces getQuickJS(). quickjs-wasi is now a runtime dependency (replaces @tootallnate/quickjs-emscripten). Updated workspace dependency on pac-resolver. - Tests: Updated error message assertion for quickjs-ng which formats 'process is not defined' without quotes around the identifier.
1 parent 5d3f71a commit 72242b6

10 files changed

Lines changed: 67 additions & 61 deletions

File tree

packages/degenerator/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@
3434
"esprima": "^4.0.1"
3535
},
3636
"devDependencies": {
37-
"@tootallnate/quickjs-emscripten": "catalog:",
3837
"@types/escodegen": "^0.0.7",
3938
"@types/esprima": "^4.0.3",
4039
"@types/node": "catalog:",
40+
"quickjs-wasi": "catalog:",
4141
"tsconfig": "workspace:*",
4242
"typescript": "catalog:"
4343
}

packages/degenerator/src/compile.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { types } from 'util';
22
import { degenerator } from './degenerator.js';
33
import type { Context } from 'vm';
4-
import type {
5-
QuickJSContext,
6-
QuickJSHandle,
7-
QuickJSWASMModule,
8-
} from '@tootallnate/quickjs-emscripten';
4+
import type { QuickJS, JSValueHandle } from 'quickjs-wasi';
95
import type { DegeneratorNames } from './degenerator.js';
106

117
export interface CompileOptions {
@@ -15,15 +11,13 @@ export interface CompileOptions {
1511
}
1612

1713
export function compile<R = unknown, A extends unknown[] = []>(
18-
qjs: QuickJSWASMModule,
14+
vm: QuickJS,
1915
code: string,
2016
returnName: string,
2117
options: CompileOptions = {}
2218
): (...args: A) => Promise<R> {
2319
const compiled = degenerator(code, options.names ?? []);
2420

25-
const vm = qjs.newContext();
26-
2721
// Add functions to global
2822
if (options.sandbox) {
2923
for (const [name, value] of Object.entries(options.sandbox)) {
@@ -32,11 +26,11 @@ export function compile<R = unknown, A extends unknown[] = []>(
3226
`Expected a "function" for sandbox property \`${name}\`, but got "${typeof value}"`
3327
);
3428
}
35-
const fnHandle = vm.newFunction(name, (...args) => {
29+
const fnHandle = vm.newFunction(name, (_this, ...args) => {
3630
const result = value(
37-
...args.map((arg) => quickJSHandleToHost(vm, arg))
31+
...args.map((arg) => vm.dump(arg))
3832
);
39-
vm.runtime.executePendingJobs();
33+
vm.executePendingJobs();
4034
return hostToQuickJSHandle(vm, result);
4135
});
4236
fnHandle.consume((handle) => vm.setProp(vm.global, name, handle));
@@ -53,8 +47,8 @@ export function compile<R = unknown, A extends unknown[] = []>(
5347
);
5448
}
5549
const r = async function (...args: A): Promise<R> {
56-
let promiseHandle: QuickJSHandle | undefined;
57-
let resolvedHandle: QuickJSHandle | undefined;
50+
let promiseHandle: JSValueHandle | undefined;
51+
let resolvedHandle: JSValueHandle | undefined;
5852
try {
5953
const result = vm.callFunction(
6054
fn,
@@ -63,10 +57,23 @@ export function compile<R = unknown, A extends unknown[] = []>(
6357
);
6458
promiseHandle = vm.unwrapResult(result);
6559
const resolvedResultP = vm.resolvePromise(promiseHandle);
66-
vm.runtime.executePendingJobs();
60+
vm.executePendingJobs();
6761
const resolvedResult = await resolvedResultP;
68-
resolvedHandle = vm.unwrapResult(resolvedResult);
69-
return quickJSHandleToHost(vm, resolvedHandle);
62+
if ('error' in resolvedResult) {
63+
const dumped = vm.dump(resolvedResult.error);
64+
resolvedResult.error.dispose();
65+
if (dumped instanceof Error) {
66+
// QuickJS Error `stack` does not include the name +
67+
// message, so patch those in to behave more like V8
68+
if (dumped.stack && !dumped.stack.startsWith(dumped.name)) {
69+
dumped.stack = `${dumped.name}: ${dumped.message}\n${dumped.stack}`;
70+
}
71+
throw dumped;
72+
}
73+
throw new Error(String(dumped));
74+
}
75+
resolvedHandle = resolvedResult.value;
76+
return vm.dump(resolvedHandle) as R;
7077
} catch (err: unknown) {
7178
if (err && typeof err === 'object' && 'cause' in err && err.cause) {
7279
if (
@@ -97,11 +104,7 @@ export function compile<R = unknown, A extends unknown[] = []>(
97104
return r;
98105
}
99106

100-
function quickJSHandleToHost(vm: QuickJSContext, val: QuickJSHandle) {
101-
return vm.dump(val);
102-
}
103-
104-
function hostToQuickJSHandle(vm: QuickJSContext, val: unknown): QuickJSHandle {
107+
function hostToQuickJSHandle(vm: QuickJS, val: unknown): JSValueHandle {
105108
if (typeof val === 'undefined') {
106109
return vm.undefined;
107110
} else if (val === null) {
@@ -116,13 +119,14 @@ function hostToQuickJSHandle(vm: QuickJSContext, val: unknown): QuickJSHandle {
116119
return val ? vm.true : vm.false;
117120
} else if (types.isPromise(val)) {
118121
const promise = vm.newPromise();
119-
promise.settled.then(vm.runtime.executePendingJobs);
120122
val.then(
121123
(r: unknown) => {
122124
promise.resolve(hostToQuickJSHandle(vm, r));
125+
vm.executePendingJobs();
123126
},
124127
(err: unknown) => {
125128
promise.reject(hostToQuickJSHandle(vm, err));
129+
vm.executePendingJobs();
126130
}
127131
);
128132
return promise.handle;

packages/degenerator/test/test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import fs from 'fs';
22
import path from 'path';
33
import assert from 'assert';
44
import { degenerator, compile } from '../src';
5-
import {
6-
getQuickJS,
7-
type QuickJSWASMModule,
8-
} from '@tootallnate/quickjs-emscripten';
5+
import { QuickJS } from 'quickjs-wasi';
96

107
describe('degenerator()', () => {
118
it('should support "async" output functions', () => {
@@ -68,10 +65,14 @@ describe('degenerator()', () => {
6865
});
6966

7067
describe('`compile()`', () => {
71-
let qjs: QuickJSWASMModule;
68+
let qjs: QuickJS;
7269

7370
beforeAll(async () => {
74-
qjs = await getQuickJS();
71+
qjs = await QuickJS.create();
72+
});
73+
74+
afterAll(() => {
75+
qjs?.dispose(false);
7576
});
7677

7778
it('should compile code into an invocable async function', async () => {
@@ -173,7 +174,7 @@ describe('degenerator()', () => {
173174
err = _err as Error;
174175
}
175176
assert(err);
176-
assert.equal(err.message, "'process' is not defined");
177+
assert(err.message.includes('process is not defined'));
177178
});
178179
it('should allow to return synchronous undefined', async () => {
179180
function u() {

packages/pac-proxy-agent/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@
3636
"author": "Nathan Rajlich <[email protected]> (http://n8.io/)",
3737
"license": "MIT",
3838
"dependencies": {
39-
"@tootallnate/quickjs-emscripten": "catalog:",
4039
"agent-base": "workspace:*",
4140
"debug": "catalog:",
4241
"get-uri": "workspace:*",
4342
"http-proxy-agent": "workspace:*",
4443
"https-proxy-agent": "workspace:*",
4544
"pac-resolver": "workspace:*",
45+
"quickjs-wasi": "catalog:",
4646
"socks-proxy-agent": "workspace:*"
4747
},
4848
"devDependencies": {

packages/pac-proxy-agent/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
FindProxyForURL,
2121
PacResolverOptions,
2222
} from 'pac-resolver';
23-
import { getQuickJS } from '@tootallnate/quickjs-emscripten';
23+
import { QuickJS } from 'quickjs-wasi';
2424

2525
const debug = createDebug('pac-proxy-agent');
2626

@@ -134,7 +134,7 @@ export class PacProxyAgent<Uri extends string> extends Agent {
134134
try {
135135
// (Re)load the contents of the PAC file URI
136136
const [qjs, code] = await Promise.all([
137-
getQuickJS(),
137+
QuickJS.create(),
138138
this.loadPacFile(),
139139
]);
140140

packages/pac-resolver/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
"netmask": "^2.0.2"
1818
},
1919
"devDependencies": {
20-
"@tootallnate/quickjs-emscripten": "catalog:",
2120
"@types/netmask": "^1.0.30",
2221
"@types/node": "catalog:",
22+
"quickjs-wasi": "catalog:",
2323
"tsconfig": "workspace:*",
2424
"typescript": "catalog:"
2525
},

packages/pac-resolver/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ import myIpAddress from './myIpAddress.js';
1616
import shExpMatch from './shExpMatch.js';
1717
import timeRange from './timeRange.js';
1818
import weekdayRange from './weekdayRange.js';
19-
import type { QuickJSWASMModule } from '@tootallnate/quickjs-emscripten';
19+
import type { QuickJS } from 'quickjs-wasi';
2020

2121
/**
2222
* Returns an asynchronous `FindProxyForURL()` function
2323
* from the given JS string (from a PAC file).
2424
*/
2525
export function createPacResolver(
26-
qjs: QuickJSWASMModule,
26+
qjs: QuickJS,
2727
_str: string | Buffer,
2828
_opts: PacResolverOptions = {}
2929
) {

packages/pac-resolver/test/test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ import assert from 'assert';
22
import { resolve } from 'path';
33
import { readFileSync } from 'fs';
44
import { createPacResolver } from '../src';
5-
import {
6-
getQuickJS,
7-
type QuickJSWASMModule,
8-
} from '@tootallnate/quickjs-emscripten';
5+
import { QuickJS } from 'quickjs-wasi';
96

107
type FindProxyForURLFn = ReturnType<typeof createPacResolver>;
118

129
describe('FindProxyForURL', () => {
13-
let qjs: QuickJSWASMModule;
10+
let qjs: QuickJS;
1411

1512
beforeAll(async () => {
16-
qjs = await getQuickJS();
13+
qjs = await QuickJS.create();
14+
});
15+
16+
afterAll(() => {
17+
qjs?.dispose(false);
1718
});
1819

1920
it('should return `undefined` by default', async () => {
@@ -73,7 +74,7 @@ describe('FindProxyForURL', () => {
7374
err = _err as Error;
7475
}
7576
assert(err);
76-
expect(err.message).toEqual("'process' is not defined");
77+
expect(err.message).toContain('process is not defined');
7778
});
7879

7980
describe('official docs Example #1', () => {

pnpm-lock.yaml

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ packages:
33
- 'packages/*'
44

55
catalog:
6-
'@tootallnate/quickjs-emscripten': ^0.23.0
6+
quickjs-wasi: ^0.0.1
77
'@types/async-retry': ^1.4.5
88
'@types/debug': ^4.1.7
99
'@types/node': ^22.13.0

0 commit comments

Comments
 (0)