repl-sdk 1.5.2 → 1.6.1
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/package.json +5 -5
- package/src/compilers/ember/gjs.js +12 -1
- package/src/compilers/ember/gmd.js +7 -0
- package/src/compilers/ember/hbs.js +12 -1
- package/src/compilers/ember.js +3 -1
- package/src/compilers/react.js +23 -0
- package/src/compilers/svelte.js +12 -9
- package/src/compilers/vue.js +6 -1
- package/src/compilers.ember.onUnhandled.test.ts +58 -0
- package/src/index.d.ts +7 -0
- package/src/index.js +11 -7
- package/src/utils.errorMessage.test.ts +45 -0
- package/src/utils.js +33 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "repl-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"prettier": "^3.7.4",
|
|
51
51
|
"publint": "^0.3.16",
|
|
52
52
|
"typescript": "^5.9.3",
|
|
53
|
-
"vite": "^
|
|
53
|
+
"vite": "^8.0.8",
|
|
54
54
|
"vite-plugin-dts": "4.5.4",
|
|
55
55
|
"vitest": "^4.0.18"
|
|
56
56
|
},
|
|
@@ -96,9 +96,9 @@
|
|
|
96
96
|
"unified": "^11.0.5",
|
|
97
97
|
"unist-util-visit": "^5.0.0",
|
|
98
98
|
"vfile": "^6.0.3",
|
|
99
|
-
"codemirror-lang-
|
|
100
|
-
"codemirror-lang-glimmer": "^2.0.
|
|
101
|
-
"codemirror-lang-
|
|
99
|
+
"codemirror-lang-glimmer": "^2.0.4",
|
|
100
|
+
"codemirror-lang-glimmer-js": "^2.0.4",
|
|
101
|
+
"codemirror-lang-glimdown": "^2.0.4"
|
|
102
102
|
},
|
|
103
103
|
"volta": {
|
|
104
104
|
"extends": "../../package.json"
|
|
@@ -170,7 +170,18 @@ export async function compiler(config, api) {
|
|
|
170
170
|
const { renderComponent } = await compiler.tryResolve('@ember/renderer');
|
|
171
171
|
|
|
172
172
|
const owner = makeOwner(config.owner);
|
|
173
|
-
const
|
|
173
|
+
const args = /** @type {Record<string, unknown> | undefined} */ (
|
|
174
|
+
extra && typeof extra === 'object' && 'args' in extra
|
|
175
|
+
? /** @type {Record<string, unknown>} */ (extra).args
|
|
176
|
+
: undefined
|
|
177
|
+
);
|
|
178
|
+
const result = renderComponent(compiled, {
|
|
179
|
+
into: element,
|
|
180
|
+
owner,
|
|
181
|
+
...(args ? { args } : {}),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
compiler.announce('info', 'Ember Island Rendered');
|
|
174
185
|
|
|
175
186
|
return () => result.destroy();
|
|
176
187
|
},
|
|
@@ -98,9 +98,16 @@ export async function compiler(config, api) {
|
|
|
98
98
|
|
|
99
99
|
const { renderComponent } = await compiler.tryResolve('@ember/renderer');
|
|
100
100
|
|
|
101
|
+
const args = /** @type {Record<string, unknown> | undefined} */ (
|
|
102
|
+
extra && typeof extra === 'object' && 'args' in extra
|
|
103
|
+
? /** @type {Record<string, unknown>} */ (extra).args
|
|
104
|
+
: undefined
|
|
105
|
+
);
|
|
106
|
+
|
|
101
107
|
const result = renderComponent(compiled, {
|
|
102
108
|
into: element,
|
|
103
109
|
owner: userOptions.owner,
|
|
110
|
+
...(args ? { args } : {}),
|
|
104
111
|
});
|
|
105
112
|
|
|
106
113
|
const destroy = () => result.destroy();
|
|
@@ -67,7 +67,18 @@ export async function compiler(config, api) {
|
|
|
67
67
|
|
|
68
68
|
const { renderComponent } = await compiler.tryResolve('@ember/renderer');
|
|
69
69
|
const owner = makeOwner(config.owner);
|
|
70
|
-
const
|
|
70
|
+
const args = /** @type {Record<string, unknown> | undefined} */ (
|
|
71
|
+
extra && typeof extra === 'object' && 'args' in extra
|
|
72
|
+
? /** @type {Record<string, unknown>} */ (extra).args
|
|
73
|
+
: undefined
|
|
74
|
+
);
|
|
75
|
+
const result = renderComponent(compiled, {
|
|
76
|
+
into: element,
|
|
77
|
+
owner,
|
|
78
|
+
...(args ? { args } : {}),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
compiler.announce('info', 'Ember Island Rendered');
|
|
71
82
|
|
|
72
83
|
return () => result.destroy();
|
|
73
84
|
},
|
package/src/compilers/ember.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* @typedef {import('../types.ts').CompilerConfig} CompilerConfig
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { errorMessage } from '../utils.js';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Other `@ember` (and `@glimmer`) packages are bundled in ember-source,
|
|
7
9
|
* and typecilaly use a build plugin to resolve from `@ember/*` imports.
|
|
@@ -91,7 +93,7 @@ function resolve(id) {
|
|
|
91
93
|
function onUnhandled(e, handle) {
|
|
92
94
|
if (!e.reason?.message) return;
|
|
93
95
|
|
|
94
|
-
let reason = e.reason
|
|
96
|
+
let reason = errorMessage(e.reason);
|
|
95
97
|
|
|
96
98
|
if (reason.includes('Stack trace for the update:')) {
|
|
97
99
|
reason += ' (see console)';
|
package/src/compilers/react.js
CHANGED
|
@@ -18,6 +18,8 @@ export const jsx = {
|
|
|
18
18
|
return `https://esm.sh/react@19.2.3/es2022/react.development.mjs`;
|
|
19
19
|
case 'react/jsx-dev-runtime':
|
|
20
20
|
return `https://esm.sh/react@19.2.3/es2022/jsx-dev-runtime.development.mjs`;
|
|
21
|
+
case 'react/jsx-runtime':
|
|
22
|
+
return `https://esm.sh/react@19.2.3/es2022/jsx-runtime.mjs`;
|
|
21
23
|
case 'react-dom/client':
|
|
22
24
|
return `https://esm.sh/react-dom@19.2.3/es2022/client.development.mjs`;
|
|
23
25
|
case '@babel/standalone':
|
|
@@ -37,6 +39,18 @@ export const jsx = {
|
|
|
37
39
|
[
|
|
38
40
|
babel.availablePresets.react,
|
|
39
41
|
{
|
|
42
|
+
/**
|
|
43
|
+
* The production automatic runtime (jsx/jsxs from
|
|
44
|
+
* 'react/jsx-runtime') works under both dev- and
|
|
45
|
+
* production-built hosts.
|
|
46
|
+
*
|
|
47
|
+
* The default (development) transform emits jsxDEV from
|
|
48
|
+
* 'react/jsx-dev-runtime', which a production build of react
|
|
49
|
+
* deliberately exports as undefined — every compiled demo
|
|
50
|
+
* then throws "_jsxDEV is not a function" at evaluation.
|
|
51
|
+
*/
|
|
52
|
+
runtime: 'automatic',
|
|
53
|
+
development: false,
|
|
40
54
|
// useBuiltIns: true,
|
|
41
55
|
},
|
|
42
56
|
],
|
|
@@ -53,6 +67,15 @@ export const jsx = {
|
|
|
53
67
|
|
|
54
68
|
// Wait for react-dom to render
|
|
55
69
|
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
70
|
+
|
|
71
|
+
// The return value is this render's destroy handle (see Compiler#render).
|
|
72
|
+
// Without unmounting, every rendered demo leaves a live FiberRootNode
|
|
73
|
+
// pinning its container element -- and through DOM parent/child links,
|
|
74
|
+
// the entire (detached) tree the demo was rendered into. In dev,
|
|
75
|
+
// react-refresh additionally roots every FiberRootNode globally
|
|
76
|
+
// (helpersByRoot), so nothing about an abandoned render is ever
|
|
77
|
+
// garbage-collected.
|
|
78
|
+
return () => root.unmount();
|
|
56
79
|
},
|
|
57
80
|
};
|
|
58
81
|
},
|
package/src/compilers/svelte.js
CHANGED
|
@@ -99,17 +99,20 @@ export const svelte = {
|
|
|
99
99
|
|
|
100
100
|
element.appendChild(div);
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
// @ts-ignore
|
|
104
|
-
svelte.mount(component, {
|
|
105
|
-
target: element,
|
|
106
|
-
props: {
|
|
107
|
-
/* no props */
|
|
108
|
-
},
|
|
109
|
-
});
|
|
102
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
110
103
|
|
|
111
|
-
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
const instance = svelte.mount(component, {
|
|
106
|
+
target: element,
|
|
107
|
+
props: {
|
|
108
|
+
/* no props */
|
|
109
|
+
},
|
|
112
110
|
});
|
|
111
|
+
|
|
112
|
+
api.announce('info', 'Done');
|
|
113
|
+
|
|
114
|
+
// The return value is this render's destroy handle (see Compiler#render).
|
|
115
|
+
return () => svelte.unmount(instance);
|
|
113
116
|
},
|
|
114
117
|
};
|
|
115
118
|
},
|
package/src/compilers/vue.js
CHANGED
|
@@ -50,8 +50,13 @@ export const vue = {
|
|
|
50
50
|
element.appendChild(div);
|
|
51
51
|
element.appendChild(style);
|
|
52
52
|
|
|
53
|
-
createApp(component)
|
|
53
|
+
const app = createApp(component);
|
|
54
|
+
|
|
55
|
+
app.mount(div);
|
|
54
56
|
compiler.announce('info', 'Done');
|
|
57
|
+
|
|
58
|
+
// The return value is this render's destroy handle (see Compiler#render).
|
|
59
|
+
return () => app.unmount();
|
|
55
60
|
},
|
|
56
61
|
};
|
|
57
62
|
},
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { gjs } from './compilers/ember.js';
|
|
4
|
+
|
|
5
|
+
function handleUnhandled(reason: unknown) {
|
|
6
|
+
let handled: string | undefined;
|
|
7
|
+
|
|
8
|
+
gjs.onUnhandled?.({ reason } as PromiseRejectionEvent, (message) => (handled = message));
|
|
9
|
+
|
|
10
|
+
return handled;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* In apps, nothing awaits the compile state's promise, so compile errors
|
|
15
|
+
* also arrive via unhandledrejection — and this handler's announcement is
|
|
16
|
+
* the last one, i.e. the one the UI's error bubble shows.
|
|
17
|
+
*/
|
|
18
|
+
describe('gjs onUnhandled', () => {
|
|
19
|
+
it('ignores reasons without a message', () => {
|
|
20
|
+
expect(handleUnhandled(undefined)).toBe(undefined);
|
|
21
|
+
expect(handleUnhandled('boom')).toBe(undefined);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('uses the message of a normal Error', () => {
|
|
25
|
+
expect(handleUnhandled(new Error('boom'))).toBe('boom');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('includes the source_code of an SWC-style error (as thrown by content-tag)', () => {
|
|
29
|
+
const swcError = Object.assign(new Error('Parse Error at dynamic-repl.js:3:16: 3:33'), {
|
|
30
|
+
source_code:
|
|
31
|
+
" × Expected 'from', got 'string literal'\n" +
|
|
32
|
+
' ╭─[dynamic-repl.js:3:1]\n' +
|
|
33
|
+
" 2 │ import { tracked } from '@glimmer/tracking';\n" +
|
|
34
|
+
" 3 │ import { on } '@ember/modifier';\n" +
|
|
35
|
+
' · ─────────────────\n' +
|
|
36
|
+
' ╰────',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(handleUnhandled(swcError)).toMatchInlineSnapshot(`
|
|
40
|
+
"Parse Error at dynamic-repl.js:3:16: 3:33
|
|
41
|
+
|
|
42
|
+
× Expected 'from', got 'string literal'
|
|
43
|
+
╭─[dynamic-repl.js:3:1]
|
|
44
|
+
2 │ import { tracked } from '@glimmer/tracking';
|
|
45
|
+
3 │ import { on } '@ember/modifier';
|
|
46
|
+
· ─────────────────
|
|
47
|
+
╰────"
|
|
48
|
+
`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('points at the console for backtracking-rerender asserts', () => {
|
|
52
|
+
const message = handleUnhandled(
|
|
53
|
+
new Error('Assertion Failed: ...\n\nStack trace for the update:\n...')
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(message).toContain('(see console)');
|
|
57
|
+
});
|
|
58
|
+
});
|
package/src/index.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ import type { EditorView } from 'codemirror';
|
|
|
3
3
|
|
|
4
4
|
export type { ErrorMessage, InfoMessage, Message, Options } from './types.ts';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Builds the most useful human-readable message from a thrown error,
|
|
8
|
+
* including the explanation and code-frame from SWC / content-tag
|
|
9
|
+
* parse errors (which hide those on a non-standard `source_code` property).
|
|
10
|
+
*/
|
|
11
|
+
export function errorMessage(error: unknown): string;
|
|
12
|
+
|
|
6
13
|
export const defaultFormats: keyof Options['formats'];
|
|
7
14
|
export const defaults: Options;
|
|
8
15
|
|
package/src/index.js
CHANGED
|
@@ -10,10 +10,12 @@ import { compilers } from './compilers.js';
|
|
|
10
10
|
import { STABLE_REFERENCE } from './es-module-shim.js';
|
|
11
11
|
import { getTarRequestId } from './request.js';
|
|
12
12
|
import { getFromTarball } from './tar.js';
|
|
13
|
-
import { assert, nextId, prefix_tgz, tgzPrefix, unzippedPrefix } from './utils.js';
|
|
13
|
+
import { assert, errorMessage, nextId, prefix_tgz, tgzPrefix, unzippedPrefix } from './utils.js';
|
|
14
14
|
|
|
15
15
|
assert(`There is no document. repl-sdk is meant to be ran in a browser`, globalThis.document);
|
|
16
16
|
|
|
17
|
+
export { errorMessage } from './utils.js';
|
|
18
|
+
|
|
17
19
|
export const defaultFormats = Object.keys(compilers);
|
|
18
20
|
|
|
19
21
|
export const defaults = {
|
|
@@ -98,7 +100,7 @@ export class Compiler {
|
|
|
98
100
|
|
|
99
101
|
if (handled) return;
|
|
100
102
|
|
|
101
|
-
this.#announce('error', e.reason);
|
|
103
|
+
this.#announce('error', errorMessage(e.reason));
|
|
102
104
|
};
|
|
103
105
|
|
|
104
106
|
/**
|
|
@@ -345,7 +347,7 @@ export class Compiler {
|
|
|
345
347
|
/**
|
|
346
348
|
* @param {string} format
|
|
347
349
|
* @param {string} text
|
|
348
|
-
* @param {{ fileName?: string, flavor?: string, [key: string]: unknown }} [ options ]
|
|
350
|
+
* @param {{ fileName?: string, flavor?: string, args?: Record<string, unknown>, [key: string]: unknown }} [ options ]
|
|
349
351
|
* @returns {Promise<{ element: HTMLElement, destroy: () => void }>}
|
|
350
352
|
*/
|
|
351
353
|
async compile(format, text, options = {}) {
|
|
@@ -355,9 +357,7 @@ export class Compiler {
|
|
|
355
357
|
return await this.#compile(format, text, options);
|
|
356
358
|
} catch (e) {
|
|
357
359
|
// for on.log usage
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
this.#announce('error', String(message));
|
|
360
|
+
this.#announce('error', errorMessage(e));
|
|
361
361
|
|
|
362
362
|
// Don't hide errors!
|
|
363
363
|
this.#error(e);
|
|
@@ -411,6 +411,7 @@ export class Compiler {
|
|
|
411
411
|
return this.#render(compiler, value, {
|
|
412
412
|
...extras,
|
|
413
413
|
compiled: value,
|
|
414
|
+
...(opts.args ? { args: opts.args } : {}),
|
|
414
415
|
});
|
|
415
416
|
}
|
|
416
417
|
|
|
@@ -421,7 +422,10 @@ export class Compiler {
|
|
|
421
422
|
|
|
422
423
|
this.#log('[compile] preparing to render', defaultExport, extras);
|
|
423
424
|
|
|
424
|
-
return this.#render(compiler, defaultExport,
|
|
425
|
+
return this.#render(compiler, defaultExport, {
|
|
426
|
+
...extras,
|
|
427
|
+
...(opts.args ? { args: opts.args } : {}),
|
|
428
|
+
});
|
|
425
429
|
}
|
|
426
430
|
|
|
427
431
|
#compilerCache = new WeakMap();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { errorMessage } from './utils.js';
|
|
4
|
+
|
|
5
|
+
describe('errorMessage', () => {
|
|
6
|
+
it('handles non-object throwables', () => {
|
|
7
|
+
expect(errorMessage('boom')).toBe('boom');
|
|
8
|
+
expect(errorMessage(undefined)).toBe('undefined');
|
|
9
|
+
expect(errorMessage(null)).toBe('null');
|
|
10
|
+
expect(errorMessage(2)).toBe('2');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('uses the message of a normal Error', () => {
|
|
14
|
+
expect(errorMessage(new Error('boom'))).toBe('boom');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('falls back to String() for objects without message or source_code', () => {
|
|
18
|
+
expect(errorMessage({})).toBe('[object Object]');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('includes the source_code of an SWC-style error (as thrown by content-tag)', () => {
|
|
22
|
+
/**
|
|
23
|
+
* Real shape from content-tag's `Preprocessor#process`:
|
|
24
|
+
* an Error with extra own properties: source_code, source_code_color
|
|
25
|
+
*/
|
|
26
|
+
const swcError = Object.assign(new Error('Parse Error at dynamic-repl.js:1:9: 1:10'), {
|
|
27
|
+
source_code:
|
|
28
|
+
' × Expression expected\n' +
|
|
29
|
+
' ╭─[dynamic-repl.js:1:1]\n' +
|
|
30
|
+
' 1 │ let y = ;\n' +
|
|
31
|
+
' · ─\n' +
|
|
32
|
+
' ╰────',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(errorMessage(swcError)).toMatchInlineSnapshot(`
|
|
36
|
+
"Parse Error at dynamic-repl.js:1:9: 1:10
|
|
37
|
+
|
|
38
|
+
× Expression expected
|
|
39
|
+
╭─[dynamic-repl.js:1:1]
|
|
40
|
+
1 │ let y = ;
|
|
41
|
+
· ─
|
|
42
|
+
╰────"
|
|
43
|
+
`);
|
|
44
|
+
});
|
|
45
|
+
});
|
package/src/utils.js
CHANGED
|
@@ -39,3 +39,36 @@ export function prefix_tgz(url) {
|
|
|
39
39
|
export function isRecord(x) {
|
|
40
40
|
return typeof x === 'object' && x !== null && !Array.isArray(x);
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Builds the most useful human-readable message from a thrown error.
|
|
45
|
+
*
|
|
46
|
+
* SWC (via content-tag) throws Errors whose `message` is only
|
|
47
|
+
* "Parse Error at <file>:<line>:<column>" — the explanation of what's
|
|
48
|
+
* wrong and the code-frame live on a non-standard `source_code` property
|
|
49
|
+
* (and `stack` is nothing but wasm frames).
|
|
50
|
+
*
|
|
51
|
+
* @param {unknown} error
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
export function errorMessage(error) {
|
|
55
|
+
if (!isRecord(error)) {
|
|
56
|
+
return String(error);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const parts = [];
|
|
60
|
+
|
|
61
|
+
if ('message' in error && error.message) {
|
|
62
|
+
parts.push(String(error.message));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if ('source_code' in error && error.source_code) {
|
|
66
|
+
parts.push(String(error.source_code));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (parts.length === 0) {
|
|
70
|
+
return String(error);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return parts.join('\n\n');
|
|
74
|
+
}
|