Skip to content

Commit

Permalink
Remove quickjs-emscripten (#1820)
Browse files Browse the repository at this point in the history
  • Loading branch information
Janpot authored Mar 29, 2023
1 parent 66391b6 commit 666ecab
Show file tree
Hide file tree
Showing 5 changed files with 18 additions and 135 deletions.
9 changes: 0 additions & 9 deletions packages/toolpad-app/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,6 @@ export default withBundleAnalyzer({
* @param {import('webpack').Configuration} config
*/
webpack: (config, options) => {
config.resolve ??= {};
config.resolve.fallback = {
...config.resolve.fallback,
// We need these because quickjs-emscripten doesn't export pure browser compatible modules yet
// https://github.com/justjake/quickjs-emscripten/issues/33
fs: false,
path: false,
};

config.module ??= {};
config.module.strictExportPresence = true;

Expand Down
1 change: 0 additions & 1 deletion packages/toolpad-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@
"pino-elasticsearch": "^6.3.0",
"prettier": "^2.8.7",
"pretty-bytes": "^6.1.0",
"quickjs-emscripten": "^0.22.0",
"react": "^18.2.0",
"react-dev-utils": "^12.0.1",
"react-devtools-inline": "^4.27.4",
Expand Down
13 changes: 5 additions & 8 deletions packages/toolpad-app/src/toolpad/AppEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import { styled } from '@mui/material';
import { useNavigate, useLocation } from 'react-router-dom';
import { JsRuntimeProvider } from '@mui/toolpad-core/jsServerRuntime';
import PageEditor from './PageEditor';
import DomProvider, { useAppState } from '../AppState';
import AppEditorShell from './AppEditorShell';
Expand Down Expand Up @@ -63,12 +62,10 @@ function FileEditor() {

export default function Editor() {
return (
<JsRuntimeProvider>
<DomProvider>
<EditorRoot>
<FileEditor />
</EditorRoot>
</DomProvider>
</JsRuntimeProvider>
<DomProvider>
<EditorRoot>
<FileEditor />
</EditorRoot>
</DomProvider>
);
}
13 changes: 6 additions & 7 deletions packages/toolpad-app/src/toolpadDataSources/rest/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
BindableAttrValue,
BindableAttrValues,
JsRuntime,
Serializable,
SerializedError,
} from '@mui/toolpad-core';
import { evaluateBindable } from '@mui/toolpad-core/jsRuntime';
Expand Down Expand Up @@ -57,7 +56,7 @@ export function parseBaseUrl(baseUrl: string): URL {
function resolveBindable(
jsRuntime: JsRuntime,
bindable: BindableAttrValue<string>,
scope: Record<string, Serializable>,
scope: Record<string, unknown>,
): any {
const { value, error } = evaluateBindable(jsRuntime, bindable, scope);
if (error) {
Expand All @@ -69,15 +68,15 @@ function resolveBindable(
function resolveBindableEntries(
jsRuntime: JsRuntime,
entries: BindableAttrEntries,
scope: Record<string, Serializable>,
scope: Record<string, unknown>,
): [string, any][] {
return entries.map(([key, value]) => [key, resolveBindable(jsRuntime, value, scope)]);
}

function resolveBindables<P>(
jsRuntime: JsRuntime,
obj: BindableAttrValues<P>,
scope: Record<string, Serializable>,
scope: Record<string, unknown>,
): P {
return Object.fromEntries(
resolveBindableEntries(jsRuntime, Object.entries(obj) as BindableAttrEntries, scope),
Expand All @@ -102,7 +101,7 @@ interface ResolvedRawBody {
function resolveRawBody(
jsRuntime: JsRuntime,
body: RawBody,
scope: Record<string, Serializable>,
scope: Record<string, unknown>,
): ResolvedRawBody {
const { content, contentType } = resolveBindables(
jsRuntime,
Expand All @@ -127,15 +126,15 @@ interface ResolveUrlEncodedBodyBody {
function resolveUrlEncodedBody(
jsRuntime: JsRuntime,
body: UrlEncodedBody,
scope: Record<string, Serializable>,
scope: Record<string, unknown>,
): ResolveUrlEncodedBodyBody {
return {
kind: 'urlEncoded',
content: resolveBindableEntries(jsRuntime, body.content, scope),
};
}

function resolveBody(jsRuntime: JsRuntime, body: Body, scope: Record<string, Serializable>) {
function resolveBody(jsRuntime: JsRuntime, body: Body, scope: Record<string, unknown>) {
switch (body.kind) {
case 'raw':
return resolveRawBody(jsRuntime, body, scope);
Expand Down
117 changes: 7 additions & 110 deletions packages/toolpad-core/src/jsServerRuntime.tsx
Original file line number Diff line number Diff line change
@@ -1,134 +1,31 @@
import invariant from 'invariant';
import {
getQuickJS,
QuickJSHandle,
QuickJSContext,
RuntimeOptions,
QuickJSRuntime,
} from 'quickjs-emscripten';
import * as vm from 'vm';
import * as React from 'react';
import { BindingEvaluationResult, JsRuntime, Serializable } from './types.js';
import { BindingEvaluationResult, JsRuntime } from './types.js';
import { errorFrom } from './utils/errors.js';

const JsRuntimeContext = React.createContext<QuickJSRuntime | null>(null);

export interface JsRuntimeProviderProps {
options?: RuntimeOptions;
children?: React.ReactNode;
}

export const JsRuntimeProvider = React.lazy(async () => {
const quickJs = await getQuickJS();
function Context(props: JsRuntimeProviderProps) {
const [runtime, setRuntime] = React.useState(() => quickJs.newRuntime(props.options));

// Make sure to dispose of runtime when it changes or unmounts
React.useEffect(() => {
return () => {
if (runtime.alive) {
runtime.dispose();
}
};
}, [runtime]);

React.useEffect(() => setRuntime(quickJs.newRuntime(props.options)), [props.options]);

return <JsRuntimeContext.Provider value={runtime} {...props} />;
}
Context.displayName = 'JsRuntimeProvider';
return { default: Context };
});

function useQuickJsRuntime(): QuickJSRuntime {
const runtime = React.useContext(JsRuntimeContext);

if (!runtime) {
throw new Error(`No JsRuntime context found`);
}

return runtime;
}

function newSerializable(ctx: QuickJSContext, json: unknown): QuickJSHandle {
switch (typeof json) {
case 'string':
return ctx.newString(json);
case 'number':
return ctx.newNumber(json);
case 'boolean':
return json ? ctx.true : ctx.false;
case 'object': {
if (!json) {
return ctx.null;
}
if (Array.isArray(json)) {
const result = ctx.newArray();
Object.values(json).forEach((value, i) => {
const valueHandle = newSerializable(ctx, value);
ctx.setProp(result, i, valueHandle);
valueHandle.dispose();
});
return result;
}
const result = ctx.newObject();
Object.entries(json).forEach(([key, value]) => {
const valueHandle = newSerializable(ctx, value);
ctx.setProp(result, key, valueHandle);
valueHandle.dispose();
});
return result;
}
case 'function': {
const result = ctx.newFunction('anonymous', (...args) => {
const dumpedArgs: Serializable[] = args.map((arg) => ctx.dump(arg));
const fnResult = json(...dumpedArgs);
return newSerializable(ctx, fnResult);
});
return result;
}
case 'undefined':
return ctx.undefined;
default:
return invariant(false, `invalid value: ${json}`);
}
}

function evalExpressionInContext(
ctx: QuickJSContext,
expression: string,
globalScope: Record<string, unknown> = {},
): BindingEvaluationResult {
try {
Object.entries(globalScope).forEach(([key, value]) => {
const valueHandle = newSerializable(ctx, value);
ctx.setProp(ctx.global, key, valueHandle);
valueHandle.dispose();
});

const result = ctx.unwrapResult(ctx.evalCode(expression));
const resultValue = ctx.dump(result);
result.dispose();
return { value: resultValue };
const value = vm.runInNewContext(expression, globalScope);
return { value };
} catch (rawError) {
return { error: errorFrom(rawError) };
}
}

export async function createServerJsRuntime(): Promise<JsRuntime> {
const quickJs = await getQuickJS();
const ctx = quickJs.newContext();
return {
evaluateExpression: (code, globalScope) => evalExpressionInContext(ctx, code, globalScope),
evaluateExpression: (code, globalScope) => evalExpressionInContext(code, globalScope),
};
}

export function useServerJsRuntime(): JsRuntime {
const quickJs = useQuickJsRuntime();
const ctx = quickJs.newContext();
return React.useMemo(
() => ({
evaluateExpression: (code, globalScope) => evalExpressionInContext(ctx, code, globalScope),
evaluateExpression: (code, globalScope) => evalExpressionInContext(code, globalScope),
}),
[ctx],
[],
);
}

0 comments on commit 666ecab

Please sign in to comment.