@@ -227,113 +227,125 @@ export type ComponentHandle = ReturnType<typeof createComponent>
227227 * @returns Component runtime helpers used by the reconciler.
228228 */
229229export function createComponent < C = NoContext > ( config : ComponentConfig ) {
230- let taskQueue : Task [ ] = [ ]
231- let renderCtrl : AbortController | null = null
232- let connectedCtrl : AbortController | null = null
233- let contextValue : C | undefined = undefined
234-
235- function getConnectedSignal ( ) {
236- if ( ! connectedCtrl ) connectedCtrl = new AbortController ( )
237- return connectedCtrl . signal
238- }
239-
240- let getContent : null | RenderFn = null
241- let scheduleUpdate : ( ) => void = ( ) => {
242- throw new Error ( 'scheduleUpdate not implemented' )
243- }
244- let props = { } as ElementProps
230+ return new ComponentRuntime < C > ( config )
231+ }
245232
246- let context : Context < C > = {
247- set : ( value : C ) => {
248- contextValue = value
249- } ,
250- get : ( type : Component ) => config . getContext ( type ) ,
251- }
233+ class ComponentRuntime < C = NoContext > {
234+ frame : FrameHandle
252235
253- let handle : Handle < ElementProps , C > = {
254- id : config . id ,
255- props,
256- update : ( ) =>
257- new Promise ( ( resolve ) => {
258- taskQueue . push ( ( signal ) => resolve ( signal ) )
259- scheduleUpdate ( )
260- } ) ,
261- queueTask : ( task : Task ) => {
262- taskQueue . push ( task )
263- } ,
264- frame : config . frame ,
265- frames : {
266- get top ( ) {
267- return config . getTopFrame ?.( ) ?? config . frame
268- } ,
269- get ( name : string ) {
270- return config . getFrameByName ( name )
271- } ,
272- } ,
273- context : context ,
274- get signal ( ) {
275- return config . signal ?? getConnectedSignal ( )
276- } ,
236+ #config: ComponentConfig
237+ #connectedController: AbortController | undefined
238+ #contextValue: C | undefined
239+ #handle: Handle < ElementProps , C >
240+ #props = { } as ElementProps
241+ #renderController: AbortController | undefined
242+ #renderFn: RenderFn | undefined
243+ #scheduleUpdate: ( ) => void = ( ) => {
244+ throw new Error ( 'scheduleUpdate not implemented' )
277245 }
246+ #tasks: Task [ ] = [ ]
278247
279- function dequeueTasks ( ) : ( ( ) => void ) [ ] {
280- // Only create render controller if any task expects a signal (has length >= 1)
281- let needsSignal = taskQueue . some ( ( task ) => task . length >= 1 )
282- if ( needsSignal && ! renderCtrl ) {
283- renderCtrl = new AbortController ( )
284- }
285- let signal = renderCtrl ?. signal
286- return taskQueue . splice ( 0 , taskQueue . length ) . map ( ( task ) => ( ) => task ( signal ! ) )
248+ constructor ( config : ComponentConfig ) {
249+ this . #config = config
250+ this . frame = config . frame
251+ this . #handle = this . #createHandle( )
287252 }
288253
289- function render ( props : ElementProps ) : [ RemixNode , Array < ( ) => void > ] {
290- if ( connectedCtrl ?. signal . aborted ) {
254+ render = ( nextProps : ElementProps ) : [ RemixNode , Array < ( ) => void > ] => {
255+ if ( this . #connectedController ?. signal . aborted ) {
291256 console . warn ( 'render called after component was removed, potential application memory leak' )
292257 return [ null , [ ] ]
293258 }
294259
295- // Only abort render controller if it was initialized
296- if ( renderCtrl ) {
297- renderCtrl . abort ( )
298- renderCtrl = null
299- }
260+ this . #abortRenderSignal( )
261+ syncProps ( this . #props, nextProps )
300262
301- syncProps ( handle . props , props )
263+ let renderFn = this . #renderFn
264+
265+ if ( renderFn === undefined ) {
266+ let result = this . #config. type ( this . #handle)
302267
303- let renderContent = getContent
304- if ( ! renderContent ) {
305- let result = config . type ( handle )
306268 if ( typeof result !== 'function' ) {
307- let name = config . type . name || 'Anonymous'
269+ let name = this . # config. type . name || 'Anonymous'
308270 throw new Error ( `${ name } must return a render function, received ${ typeof result } ` )
309- } else {
310- getContent = result
311- renderContent = result
312271 }
313- }
314- if ( ! renderContent ) {
315- throw new Error ( 'component render function was not initialized' )
272+
273+ renderFn = result as RenderFn
274+ this . #renderFn = renderFn
316275 }
317276
318- let node = renderContent ( handle . props )
319- return [ node , dequeueTasks ( ) ]
277+ return [ renderFn ( this . #props) , this . #dequeueTasks( ) ]
320278 }
321279
322- function remove ( ) : ( ( ) => void ) [ ] {
323- connectedCtrl ?. abort ( )
324- renderCtrl ?. abort ( )
325- return dequeueTasks ( )
280+ remove = ( ) : Array < ( ) => void > => {
281+ this . #connectedController ?. abort ( )
282+ this . #abortRenderSignal ( )
283+ return this . # dequeueTasks( )
326284 }
327285
328- function setScheduleUpdate ( nextScheduleUpdate : ( ) => void ) {
329- scheduleUpdate = nextScheduleUpdate
286+ setScheduleUpdate = ( nextScheduleUpdate : ( ) => void ) : void => {
287+ this . # scheduleUpdate = nextScheduleUpdate
330288 }
331289
332- function getContextValue ( ) : C | undefined {
333- return contextValue
290+ getContextValue = ( ) : C | undefined => this . #contextValue
291+
292+ #createHandle( ) : Handle < ElementProps , C > {
293+ let component = this
294+ let context : Context < C > = {
295+ set : ( value : C ) => {
296+ this . #contextValue = value
297+ } ,
298+ get : ( type : ElementType | symbol ) => this . #config. getContext ( type as Component ) ,
299+ }
300+
301+ return {
302+ id : this . #config. id ,
303+ props : this . #props,
304+ update : ( ) =>
305+ new Promise ( ( resolve ) => {
306+ this . #tasks. push ( ( signal ) => resolve ( signal ) )
307+ this . #scheduleUpdate( )
308+ } ) ,
309+ queueTask : ( task : Task ) => {
310+ this . #tasks. push ( task )
311+ } ,
312+ frame : this . #config. frame ,
313+ frames : {
314+ get top ( ) {
315+ return component . #config. getTopFrame ?.( ) ?? component . #config. frame
316+ } ,
317+ get ( name : string ) {
318+ return component . #config. getFrameByName ( name )
319+ } ,
320+ } ,
321+ context,
322+ get signal ( ) {
323+ return component . #config. signal ?? component . #connectedSignal( )
324+ } ,
325+ }
334326 }
335327
336- return { render, remove, setScheduleUpdate, frame : config . frame , getContextValue }
328+ #connectedSignal( ) : AbortSignal {
329+ this . #connectedController ??= new AbortController ( )
330+ return this . #connectedController. signal
331+ }
332+
333+ #abortRenderSignal( ) : void {
334+ this . #renderController?. abort ( )
335+ this . #renderController = undefined
336+ }
337+
338+ #dequeueTasks( ) : Array < ( ) => void > {
339+ let needsSignal = this . #tasks. some ( ( task ) => task . length >= 1 )
340+
341+ if ( needsSignal ) {
342+ this . #renderController ??= new AbortController ( )
343+ }
344+
345+ let signal = this . #renderController?. signal
346+ let tasks = this . #tasks. splice ( 0 , this . #tasks. length )
347+ return tasks . map ( ( task ) => ( ) => task ( signal ! ) )
348+ }
337349}
338350
339351function syncProps ( target : ElementProps , next : ElementProps ) : void {
0 commit comments