3
3
* SPDX-License-Identifier: GPL-3.0-or-later
4
4
*/
5
5
6
+ // @deno -types="./polyfills/async-disposable-stack.ts"
7
+ import { AsyncDisposableStack } from "./polyfills/async-disposable-stack.js" ;
8
+
6
9
// @ts -ignore
7
10
// @deno -types="./std/semver.ts"
8
11
import { parse , parseRange , satisfies } from "./std/semver.js" ;
@@ -24,23 +27,21 @@ import { SPOTIFY_VERSION } from "./static.js";
24
27
// @deno -types="./transform.ts"
25
28
import { createTransformer , type Transformer } from "./transform.js" ;
26
29
27
- export type IndexMixinFn = ( context : MixinContext ) => void | PromiseLike < void > ;
28
- export type IndexPreloadFn = (
29
- context : PreloadContext ,
30
- ) => void | PromiseLike < void > | PromiseLike < ( ( ) => void ) > | PromiseLike < ( ( ) => PromiseLike < void > ) > ;
31
- export type IndexLoadFn = (
32
- context : LoadContext ,
33
- ) => void | PromiseLike < void > | PromiseLike < ( ( ) => void ) > | PromiseLike < ( ( ) => PromiseLike < void > ) > ;
30
+ export type IndexMixinFn = ( context : MixinContext ) => SyncOrAsync < void > ;
31
+ export type IndexPreloadFn = ( context : PreloadContext ) => SyncOrAsync < void > | PromiseLike < DisposeFn | void > ;
32
+ export type IndexLoadFn = ( context : LoadContext ) => SyncOrAsync < void > | PromiseLike < DisposeFn | void > ;
34
33
35
- export type JSIndex = {
34
+ export interface JSIndex {
36
35
mixin ?: IndexMixinFn ;
37
36
preload ?: IndexPreloadFn ;
38
37
load ?: IndexLoadFn ;
39
- } ;
38
+ disposableStack : AsyncDisposableStack ;
39
+ }
40
40
41
- export type CSSIndex = {
41
+ export interface CSSIndex {
42
42
default : CSSStyleSheet ;
43
- } ;
43
+ disposableStack : AsyncDisposableStack ;
44
+ }
44
45
45
46
export type ModuleIdentifier = string ;
46
47
export type Version = string ;
@@ -431,12 +432,10 @@ export class ModuleInstance extends ModuleInstanceBase<Module> implements MixinL
431
432
432
433
public awaitedMixins = new Array < Promise < void > > ( ) ;
433
434
434
- _unloadJs : ( ( ) => Promise < void > ) | null = null ;
435
- _unloadCss : ( ( ) => void ) | null = null ;
436
435
private mixinsLoaded = false ;
437
436
private loaded = false ;
438
- private jsIndex : JSIndex | null = null ;
439
- private cssIndex : CSSIndex | null = null ;
437
+ _jsIndex : JSIndex | null = null ;
438
+ _cssIndex : CSSIndex | null = null ;
440
439
441
440
public transition = new Transition ( ) ;
442
441
private dependants = new Set < ModuleInstance > ( ) ;
@@ -485,14 +484,14 @@ export class ModuleInstance extends ModuleInstanceBase<Module> implements MixinL
485
484
486
485
async #loadMixins( ) {
487
486
await this . #loadJsIndex( ) ;
488
- if ( ! this . jsIndex ) {
487
+ if ( ! this . _jsIndex ) {
489
488
return ;
490
489
}
491
490
492
491
console . time ( `${ this . getModuleIdentifier ( ) } #loadMixins` ) ;
493
492
try {
494
493
const mixinContext : MixinContext = { module : this , transformer : this . transformer } ;
495
- await this . jsIndex . mixin ?.( mixinContext ) ;
494
+ await this . _jsIndex . mixin ?.( mixinContext ) ;
496
495
} catch ( e ) {
497
496
console . error (
498
497
new Error (
@@ -513,25 +512,20 @@ export class ModuleInstance extends ModuleInstanceBase<Module> implements MixinL
513
512
514
513
async #preloadJs( ) {
515
514
await this . #loadJsIndex( ) ;
516
- if ( ! this . jsIndex ) {
515
+ const index = this . _jsIndex ;
516
+ if ( ! index ) {
517
517
return ;
518
518
}
519
519
520
- this . _unloadJs = async ( ) => {
521
- this . _unloadJs = null ;
522
- } ;
523
-
524
520
console . time ( `${ this . getModuleIdentifier ( ) } #preloadJs` ) ;
525
521
try {
526
522
const preloadContext : PreloadContext = { module : this } ;
527
- const predispose = await this . jsIndex . preload ?.( preloadContext ) ;
528
- const unloadJs = this . _unloadJs ;
529
- this . _unloadJs = async ( ) => {
530
- await predispose ?.( ) ;
531
- await unloadJs ( ) ;
532
- } ;
523
+ const predispose = await index . preload ?.( preloadContext ) ;
524
+ if ( predispose ) {
525
+ index . disposableStack . defer ( predispose ) ;
526
+ }
533
527
} catch ( e ) {
534
- await this . _unloadJs ! ( ) ;
528
+ await index . disposableStack . disposeAsync ( ) ;
535
529
console . error (
536
530
new Error (
537
531
`Error preloading javascript for \`${ this . getModuleIdentifier ( ) } \`` ,
@@ -543,25 +537,20 @@ export class ModuleInstance extends ModuleInstanceBase<Module> implements MixinL
543
537
}
544
538
545
539
async #loadJs( ) {
546
- if ( ! this . jsIndex ) {
547
- return ;
548
- }
549
-
550
- if ( ! this . _unloadJs ) {
540
+ const index = this . _jsIndex ;
541
+ if ( ! index || index . disposableStack . disposed ) {
551
542
return ;
552
543
}
553
544
554
545
console . time ( `${ this . getModuleIdentifier ( ) } #loadJs` ) ;
555
546
try {
556
547
const loadContext : PreloadContext = { module : this } ;
557
- const dispose = await this . jsIndex . load ?.( loadContext ) ;
558
- const predispose = this . _unloadJs ! ;
559
- this . _unloadJs = async ( ) => {
560
- await dispose ?.( ) ;
561
- await predispose ( ) ;
562
- } ;
548
+ const dispose = await index . load ?.( loadContext ) ;
549
+ if ( dispose ) {
550
+ index . disposableStack . defer ( dispose ) ;
551
+ }
563
552
} catch ( e ) {
564
- await this . _unloadJs ! ( ) ;
553
+ await index . disposableStack . disposeAsync ( ) ;
565
554
console . error (
566
555
new Error (
567
556
`Error loading javascript for \`${ this . getModuleIdentifier ( ) } \`` ,
@@ -574,39 +563,56 @@ export class ModuleInstance extends ModuleInstanceBase<Module> implements MixinL
574
563
575
564
async #loadCss( ) {
576
565
await this . #loadCssIndex( ) ;
577
- if ( ! this . cssIndex ) {
566
+ const index = this . _cssIndex ;
567
+ if ( ! index ) {
578
568
return ;
579
569
}
580
570
581
- const styleSheet = this . cssIndex . default ;
582
- document . adoptedStyleSheets . push ( styleSheet ) ;
583
-
584
- this . _unloadCss = ( ) => {
585
- this . _unloadCss = null ;
586
- document . adoptedStyleSheets = document . adoptedStyleSheets . filter ( ( sheet ) => sheet !== styleSheet ) ;
587
- } ;
571
+ console . time ( `${ this . getModuleIdentifier ( ) } #loadCss` ) ;
572
+ try {
573
+ const styleSheet = index . default ;
574
+ document . adoptedStyleSheets . push ( styleSheet ) ;
575
+ index . disposableStack . defer ( ( ) => {
576
+ document . adoptedStyleSheets = document . adoptedStyleSheets . filter ( ( sheet ) => sheet !== styleSheet ) ;
577
+ } ) ;
578
+ } catch ( e ) {
579
+ await index . disposableStack . disposeAsync ( ) ;
580
+ console . error (
581
+ new Error (
582
+ `Error loading css for \`${ this . getModuleIdentifier ( ) } \`` ,
583
+ { cause : e } ,
584
+ ) ,
585
+ ) ;
586
+ }
587
+ console . timeEnd ( `${ this . getModuleIdentifier ( ) } #loadCss` ) ;
588
588
}
589
589
590
590
async #loadJsIndex( ) {
591
+ this . _jsIndex = null ;
591
592
const { js } = this . metadata ?. entries ?? { } ;
592
593
if ( ! js ) {
593
594
return ;
594
595
}
595
596
596
597
const now = Date . now ( ) ;
597
598
const uniqueEntry = `${ this . getRelPath ( js ) ! } ?t=${ now } ` ;
598
- this . jsIndex = await import ( uniqueEntry ) ;
599
+ this . _jsIndex = Object . assign ( { } , await import ( uniqueEntry ) , {
600
+ disposableStack : new AsyncDisposableStack ( ) ,
601
+ } ) ;
599
602
}
600
603
601
604
async #loadCssIndex( ) {
605
+ this . _cssIndex = null ;
602
606
const { css } = this . metadata ?. entries ?? { } ;
603
607
if ( ! css ) {
604
608
return ;
605
609
}
606
610
607
611
const now = Date . now ( ) ;
608
612
const uniqueEntry = `${ this . getRelPath ( css ) ! } ?t=${ now } ` ;
609
- this . cssIndex = await import ( uniqueEntry , { with : { type : "css" } } ) ;
613
+ this . _cssIndex = Object . assign ( { } , await import ( uniqueEntry , { with : { type : "css" } } ) , {
614
+ disposableStack : new AsyncDisposableStack ( ) ,
615
+ } ) ;
610
616
}
611
617
612
618
private canLoadMixinsRecur ( ) {
@@ -718,8 +724,8 @@ export class ModuleInstance extends ModuleInstanceBase<Module> implements MixinL
718
724
Array . from ( this . dependants ) . map ( ( dependant ) => dependant . unloadRecur ( ) ) ,
719
725
) ;
720
726
721
- await this . _unloadJs ?. ( ) ;
722
- await this . _unloadCss ?. ( ) ;
727
+ await this . _jsIndex ?. disposableStack . disposeAsync ( ) ;
728
+ await this . _cssIndex ?. disposableStack . disposeAsync ( ) ;
723
729
724
730
resolve ( ) ;
725
731
}
@@ -1099,30 +1105,47 @@ export const enableAllLoadable = () =>
1099
1105
getLoadableChildrenInstances ( ) . map ( ( instance ) => instance . load ( ) ) ,
1100
1106
) ;
1101
1107
1102
- export type DisposeFn = ( ) => void | PromiseLike < void > ;
1108
+ // ...
1109
+
1110
+ export type ContextPromise =
1111
+ & PromiseWithResolvers < DisposeFn | void >
1112
+ & {
1113
+ wrap : ( promise : Promise < DisposeFn | void > ) => Promise < DisposeFn | void > ;
1114
+ } ;
1115
+
1116
+ function createContextPromise ( ) : ContextPromise {
1117
+ const promise = Promise . withResolvers < DisposeFn | void > ( ) ;
1118
+ return Object . assign ( promise , {
1119
+ wrap : ( $ : Promise < DisposeFn | void > ) => $ . then ( ( v ) => promise . resolve ( v ) , ( e ) => promise . reject ( e ) ) ,
1120
+ } ) ;
1121
+ }
1122
+
1123
+ export type SyncOrAsync < T > = T | PromiseLike < T > ;
1124
+ export type DisposeFn = ( ) => SyncOrAsync < void > ;
1103
1125
export type PreloadContext = { module : ModuleInstance } ;
1104
1126
export type LoadContext = { module : ModuleInstance } ;
1105
1127
export type MixinContext = { module : ModuleInstance ; transformer : Transformer } ;
1106
- export type ContextWithDispose < C extends { } > = C & { dispose : PromiseWithResolvers < DisposeFn > } ;
1128
+ export type ContextWithPromise < C extends { } > = C & { promise : ContextPromise } ;
1107
1129
export const hotwire = < C extends { } > (
1108
1130
meta : ImportMeta ,
1109
1131
url : string ,
1110
1132
_import : ( ) => Promise < any > ,
1111
1133
raw = false ,
1112
1134
) => {
1113
- const p = Promise . withResolvers < ContextWithDispose < C > > ( ) ;
1135
+ const p = Promise . withResolvers < ContextWithPromise < C > > ( ) ;
1114
1136
1115
1137
const nurl = normalizeUrl ( url , meta . url , raw ) ;
1116
1138
globalThis [ "__HOTWIRED__" ] [ nurl ] ?. reject ( `outdated hotwire: ${ nurl } ` ) ;
1117
1139
globalThis [ "__HOTWIRED__" ] [ nurl ] = p ;
1118
1140
1119
- return async ( ctx : C ) : Promise < DisposeFn > => {
1120
- const dispose = Promise . withResolvers < DisposeFn > ( ) ;
1121
- p . resolve ( { ...ctx , dispose } as ContextWithDispose < C > ) ;
1122
- return await Promise . race ( [ dispose . promise , _import ( ) ] ) ;
1141
+ return async < R extends void | DisposeFn > ( ctx : C ) : Promise < R > => {
1142
+ const promise = createContextPromise ( ) ;
1143
+ p . resolve ( { ...ctx , promise } as ContextWithPromise < C > ) ;
1144
+ _import ( ) . catch ( ( e ) => promise . reject ( e ) ) ;
1145
+ return ( await promise . promise ) as R ;
1123
1146
} ;
1124
1147
} ;
1125
- export const hotwired = < C extends { } > ( meta : ImportMeta ) : Promise < ContextWithDispose < C > > => {
1148
+ export const hotwired = < C extends { } > ( meta : ImportMeta ) : Promise < ContextWithPromise < C > > => {
1126
1149
const nurl = normalizeUrl ( meta . url ) ;
1127
1150
const p = globalThis [ "__HOTWIRED__" ] [ nurl ] ;
1128
1151
if ( ! p ) {
@@ -1132,7 +1155,7 @@ export const hotwired = <C extends {}>(meta: ImportMeta): Promise<ContextWithDis
1132
1155
} ;
1133
1156
1134
1157
declare global {
1135
- var __HOTWIRED__ : Record < string , PromiseWithResolvers < ContextWithDispose < any > > > ;
1158
+ var __HOTWIRED__ : Record < string , PromiseWithResolvers < ContextWithPromise < any > > > ;
1136
1159
}
1137
1160
1138
1161
globalThis [ "__HOTWIRED__" ] = { } ;
0 commit comments