repl-sdk 1.6.0 → 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 +3 -3
- 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 +5 -5
- 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.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -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-glimdown": "^2.0.4",
|
|
100
99
|
"codemirror-lang-glimmer": "^2.0.4",
|
|
101
|
-
"codemirror-lang-glimmer-js": "^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"
|
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
|
/**
|
|
@@ -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);
|
|
@@ -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
|
+
}
|