@@ -2,6 +2,7 @@ import * as uuid from "@lukeed/uuid";
2
2
import * as solid from "solid-js" ;
3
3
4
4
/** @import * as base from "@jupyter-widgets/base" */
5
+ /** @import { Initialize, Render, AnyModel } from "@anywidget/types" */
5
6
6
7
/**
7
8
* @template T
@@ -10,13 +11,13 @@ import * as solid from "solid-js";
10
11
11
12
/**
12
13
* @typedef AnyWidget
13
- * @prop initialize {import(" @anywidget/types"). Initialize }
14
- * @prop render {import(" @anywidget/types"). Render }
14
+ * @prop initialize {Initialize}
15
+ * @prop render {Render}
15
16
*/
16
17
17
18
/**
18
19
* @typedef AnyWidgetModule
19
- * @prop render {import(" @anywidget/types"). Render= }
20
+ * @prop render {Render=}
20
21
* @prop default {AnyWidget | (() => AnyWidget | Promise<AnyWidget>)=}
21
22
*/
22
23
@@ -102,19 +103,16 @@ async function load_css(css, anywidget_id) {
102
103
103
104
/**
104
105
* @param {string } esm
105
- * @returns {Promise<{ mod: AnyWidgetModule, url: string } > }
106
+ * @returns {Promise<AnyWidgetModule> }
106
107
*/
107
108
async function load_esm ( esm ) {
108
109
if ( is_href ( esm ) ) {
109
- return {
110
- mod : await import ( /* webpackIgnore: true */ /* @vite -ignore */ esm ) ,
111
- url : esm ,
112
- } ;
110
+ return await import ( /* webpackIgnore: true */ /* @vite -ignore */ esm ) ;
113
111
}
114
112
let url = URL . createObjectURL ( new Blob ( [ esm ] , { type : "text/javascript" } ) ) ;
115
113
let mod = await import ( /* webpackIgnore: true */ /* @vite -ignore */ url ) ;
116
114
URL . revokeObjectURL ( url ) ;
117
- return { mod, url } ;
115
+ return mod ;
118
116
}
119
117
120
118
/** @param {string } anywidget_id */
@@ -148,14 +146,13 @@ To learn more, please see: https://github.com/manzt/anywidget/pull/395.
148
146
/**
149
147
* @param {string } esm
150
148
* @param {string } anywidget_id
151
- * @returns {Promise<AnyWidget & { url: string } > }
149
+ * @returns {Promise<AnyWidget> }
152
150
*/
153
151
async function load_widget ( esm , anywidget_id ) {
154
- let { mod, url } = await load_esm ( esm ) ;
152
+ let mod = await load_esm ( esm ) ;
155
153
if ( mod . render ) {
156
154
warn_render_deprecation ( anywidget_id ) ;
157
155
return {
158
- url,
159
156
async initialize ( ) { } ,
160
157
render : mod . render ,
161
158
} ;
@@ -166,7 +163,7 @@ async function load_widget(esm, anywidget_id) {
166
163
) ;
167
164
let widget =
168
165
typeof mod . default === "function" ? await mod . default ( ) : mod . default ;
169
- return { url , ... widget } ;
166
+ return widget ;
170
167
}
171
168
172
169
/**
@@ -219,28 +216,35 @@ async function safe_cleanup(fn, kind) {
219
216
220
217
/**
221
218
* @template T
222
- * @typedef {{ data: T, state: "ok" } | { error: any, state: "error" } } Result
219
+ * @typedef Ready
220
+ * @property {"ready" } status
221
+ * @property {T } data
223
222
*/
224
223
225
- /** @type { <T>(data: T) => Result<T> } */
226
- function ok ( data ) {
227
- return { data , state : "ok" } ;
228
- }
224
+ /**
225
+ * @typedef Pending
226
+ * @property { "pending" } status
227
+ */
229
228
230
- /** @type {(e: any) => Result<any> } */
231
- function error ( e ) {
232
- return { error : e , state : "error" } ;
233
- }
229
+ /**
230
+ * @typedef Errored
231
+ * @property {"error" } status
232
+ * @property {unknown } error
233
+ */
234
+
235
+ /**
236
+ * @template T
237
+ * @typedef {Pending | Ready<T> | Errored } Result
238
+ */
234
239
235
240
/**
236
241
* Cleans up the stack trace at anywidget boundary.
237
242
* You can fully inspect the entire stack trace in the console interactively,
238
243
* but the initial error message is cleaned up to be more user-friendly.
239
244
*
240
245
* @param {unknown } source
241
- * @returns {never }
242
246
*/
243
- function throw_anywidget_error ( source ) {
247
+ function log_anywidget_error ( source ) {
244
248
if ( ! ( source instanceof Error ) ) {
245
249
// Don't know what to do with this.
246
250
throw source ;
@@ -251,7 +255,6 @@ function throw_anywidget_error(source) {
251
255
anywidget_index === - 1 ? lines : lines . slice ( 0 , anywidget_index + 1 ) ;
252
256
source . stack = clean_stack . join ( "\n" ) ;
253
257
console . error ( source ) ;
254
- throw source ;
255
258
}
256
259
257
260
/**
@@ -321,66 +324,86 @@ function promise_with_resolvers() {
321
324
}
322
325
323
326
class Runtime {
324
- /** @type {import(' solid-js').Resource <Result<AnyWidget & { url: string } >> } */
327
+ /** @type {solid.Accessor <Result<AnyWidget>> } */
325
328
// @ts -expect-error - Set synchronously in constructor.
326
329
#widget_result;
327
- /** @type {Promise<void> } */
328
- ready ;
329
330
/** @type {AbortSignal } */
330
331
#signal;
332
+ /** @type {Promise<void> } */
333
+ ready ;
331
334
332
335
/**
333
336
* @param {base.DOMWidgetModel } model
334
337
* @param {{ signal: AbortSignal } } options
335
338
*/
336
339
constructor ( model , options ) {
337
340
/** @type {PromiseWithResolvers<void> } */
338
- const resolvers = promise_with_resolvers ( ) ;
341
+ let resolvers = promise_with_resolvers ( ) ;
339
342
this . ready = resolvers . promise ;
340
343
this . #signal = options . signal ;
341
344
this . #signal. throwIfAborted ( ) ;
342
345
this . #signal. addEventListener ( "abort" , ( ) => dispose ( ) ) ;
346
+
347
+ AbortSignal . timeout ( 2000 ) . addEventListener ( "abort" , ( ) => {
348
+ console . error ( "timed out" ) ;
349
+ resolvers . reject ( new Error ( "[anywidget] Failed to load" ) ) ;
350
+ } ) ;
343
351
let dispose = solid . createRoot ( ( dispose ) => {
352
+ let id = ( ) => model . get ( "_anywidget_id" ) ;
344
353
let [ css , set_css ] = solid . createSignal ( model . get ( "_css" ) ) ;
345
354
model . on ( "change:_css" , ( ) => {
346
- let id = model . get ( "_anywidget_id" ) ;
347
- console . debug ( `[anywidget] css hot updated: ${ id } ` ) ;
355
+ console . debug ( `[anywidget] css hot updated: ${ id ( ) } ` ) ;
348
356
set_css ( model . get ( "_css" ) ) ;
349
357
} ) ;
350
358
solid . createEffect ( ( ) => {
351
- let id = model . get ( "_anywidget_id" ) ;
352
- load_css ( css ( ) , id ) ;
359
+ css ( ) && load_css ( css ( ) , id ( ) ) ;
353
360
} ) ;
354
361
355
- /** @type {import(" solid-js") .Signal<string> } */
362
+ /** @type {solid.Signal<string> } */
356
363
let [ esm , setEsm ] = solid . createSignal ( model . get ( "_esm" ) ) ;
357
364
model . on ( "change:_esm" , async ( ) => {
358
- let id = model . get ( "_anywidget_id" ) ;
359
- console . debug ( `[anywidget] esm hot updated: ${ id } ` ) ;
365
+ console . debug ( `[anywidget] esm hot updated: ${ id ( ) } ` ) ;
360
366
setEsm ( model . get ( "_esm" ) ) ;
361
367
} ) ;
362
- /** @type {void | (() => Awaitable<void>) } */
363
- let cleanup ;
364
- this . #widget_result = solid . createResource ( esm , async ( update ) => {
365
- await safe_cleanup ( cleanup , "initialize" ) ;
366
- try {
367
- model . off ( null , null , INITIALIZE_MARKER ) ;
368
- let widget = await load_widget ( update , model . get ( "_anywidget_id" ) ) ;
369
- resolvers . resolve ( ) ;
370
- cleanup = await widget . initialize ?. ( {
371
- model : model_proxy ( model , INITIALIZE_MARKER ) ,
372
- experimental : {
373
- // @ts -expect-error - bind isn't working
374
- invoke : invoke . bind ( null , model ) ,
375
- } ,
376
- } ) ;
377
- return ok ( widget ) ;
378
- } catch ( e ) {
379
- return error ( e ) ;
380
- }
381
- } ) [ 0 ] ;
368
+
369
+ let [ widget_result , set_widget_result ] = solid . createSignal (
370
+ /** @type {Result<AnyWidget> } */ ( {
371
+ status : "pending" ,
372
+ } ) ,
373
+ ) ;
374
+
375
+ this . #widget_result = widget_result ;
376
+
377
+ solid . createEffect ( ( ) => {
378
+ let controller = new AbortController ( ) ;
379
+ solid . onCleanup ( ( ) => controller . abort ( ) ) ;
380
+ model . off ( null , null , INITIALIZE_MARKER ) ;
381
+ load_widget ( esm ( ) , model . get ( "_anywidget_id" ) )
382
+ . then ( async ( widget ) => {
383
+ if ( controller . signal . aborted ) {
384
+ return ;
385
+ }
386
+ let cleanup = await widget . initialize ?. ( {
387
+ model : model_proxy ( model , INITIALIZE_MARKER ) ,
388
+ experimental : {
389
+ // @ts -expect-error - bind isn't working
390
+ invoke : invoke . bind ( null , model ) ,
391
+ } ,
392
+ } ) ;
393
+ if ( controller . signal . aborted ) {
394
+ safe_cleanup ( cleanup , "esm update" ) ;
395
+ return ;
396
+ }
397
+ controller . signal . addEventListener ( "abort" , ( ) =>
398
+ safe_cleanup ( cleanup , "esm update" ) ,
399
+ ) ;
400
+ set_widget_result ( { status : "ready" , data : widget } ) ;
401
+ resolvers . resolve ( ) ;
402
+ } )
403
+ . catch ( ( error ) => set_widget_result ( { status : "error" , error } ) ) ;
404
+ } ) ;
405
+
382
406
return ( ) => {
383
- cleanup ?. ( ) ;
384
407
model . off ( "change:_css" ) ;
385
408
model . off ( "change:_esm" ) ;
386
409
dispose ( ) ;
@@ -399,42 +422,41 @@ class Runtime {
399
422
signal . throwIfAborted ( ) ;
400
423
signal . addEventListener ( "abort" , ( ) => dispose ( ) ) ;
401
424
let dispose = solid . createRoot ( ( dispose ) => {
402
- /** @type {void | (() => Awaitable<void>) } */
403
- let cleanup ;
404
- let resource = solid . createResource (
405
- this . #widget_result,
406
- async ( widget_result ) => {
407
- cleanup ?. ( ) ;
408
- // Clear all previous event listeners from this hook.
409
- model . off ( null , null , view ) ;
410
- view . $el . empty ( ) ;
411
- if ( widget_result . state === "error" ) {
412
- throw_anywidget_error ( widget_result . error ) ;
413
- }
414
- let widget = widget_result . data ;
415
- try {
416
- cleanup = await widget . render ?. ( {
425
+ solid . createEffect ( ( ) => {
426
+ // Clear all previous event listeners from this hook.
427
+ model . off ( null , null , view ) ;
428
+ view . $el . empty ( ) ;
429
+ let result = this . #widget_result( ) ;
430
+ if ( result . status === "pending" ) {
431
+ return ;
432
+ }
433
+ if ( result . status === "error" ) {
434
+ log_anywidget_error ( result . error ) ;
435
+ return ;
436
+ }
437
+ let controller = new AbortController ( ) ;
438
+ solid . onCleanup ( ( ) => controller . abort ( ) ) ;
439
+ Promise . resolve ( )
440
+ . then ( async ( ) => {
441
+ let cleanup = await result . data . render ?. ( {
417
442
model : model_proxy ( model , view ) ,
418
443
el : view . el ,
419
444
experimental : {
420
445
// @ts -expect-error - bind isn't working
421
446
invoke : invoke . bind ( null , model ) ,
422
447
} ,
423
448
} ) ;
424
- } catch ( e ) {
425
- throw_anywidget_error ( e ) ;
426
- }
427
- } ,
428
- ) [ 0 ] ;
429
- solid . createEffect ( ( ) => {
430
- if ( resource . error ) {
431
- // TODO: Show error in the view?
432
- }
449
+ if ( controller . signal . aborted ) {
450
+ safe_cleanup ( cleanup , "dispose view - already aborted" ) ;
451
+ return ;
452
+ }
453
+ controller . signal . addEventListener ( "abort" , ( ) =>
454
+ safe_cleanup ( cleanup , "dispose view - aborted" ) ,
455
+ ) ;
456
+ } )
457
+ . catch ( ( error ) => log_anywidget_error ( error ) ) ;
433
458
} ) ;
434
- return ( ) => {
435
- dispose ( ) ;
436
- cleanup ?. ( ) ;
437
- } ;
459
+ return ( ) => dispose ( ) ;
438
460
} ) ;
439
461
}
440
462
}
@@ -463,21 +485,20 @@ export default function ({ DOMWidgetModel, DOMWidgetView }) {
463
485
initialize ( ...args ) {
464
486
super . initialize ( ...args ) ;
465
487
let controller = new AbortController ( ) ;
466
- let runtime = new Runtime ( this , { signal : controller . signal } ) ;
467
488
this . once ( "destroy" , ( ) => {
468
- try {
469
- controller . abort ( "[anywidget] Runtime destroyed." ) ;
470
- } finally {
471
- RUNTIMES . delete ( this ) ;
472
- }
489
+ controller . abort ( "[anywidget] Runtime destroyed." ) ;
490
+ RUNTIMES . delete ( this ) ;
473
491
} ) ;
474
- RUNTIMES . set ( this , runtime ) ;
492
+ RUNTIMES . set ( this , new Runtime ( this , { signal : controller . signal } ) ) ;
475
493
}
476
494
477
495
/** @param {Parameters<InstanceType<DOMWidgetModel>["_handle_comm_msg"]> } msg */
478
496
async _handle_comm_msg ( ...msg ) {
479
- const runtime = RUNTIMES . get ( this ) ;
480
- await runtime ?. ready ;
497
+ let runtime = RUNTIMES . get ( this ) ;
498
+ await runtime ?. ready . catch ( ( ) => {
499
+ console . log ( "timed out" ) ;
500
+ console . log ( { msg } ) ;
501
+ } ) ;
481
502
return super . _handle_comm_msg ( ...msg ) ;
482
503
}
483
504
0 commit comments