@@ -46,11 +46,82 @@ export async function write_all_types(config, manifest_data) {
46
46
}
47
47
}
48
48
49
+ // Read/write meta data on each invocation, not once per node process,
50
+ // it could be invoked by another process in the meantime.
51
+ const meta_data_file = `${ types_dir } /route_meta_data.json` ;
52
+ const has_meta_data = fs . existsSync ( meta_data_file ) ;
53
+ let meta_data = has_meta_data
54
+ ? /** @type {Record<string, string[]> } */ ( JSON . parse ( fs . readFileSync ( meta_data_file , 'utf-8' ) ) )
55
+ : { } ;
49
56
const routes_map = create_routes_map ( manifest_data ) ;
50
57
// For each directory, write $types.d.ts
51
58
for ( const route of manifest_data . routes ) {
52
- update_types ( config , routes_map , route ) ;
59
+ if ( ! route . leaf && ! route . layout && ! route . endpoint ) continue ; // nothing to do
60
+
61
+ const outdir = path . join ( config . kit . outDir , 'types' , routes_dir , route . id ) ;
62
+
63
+ // check if the types are out of date
64
+ /** @type {string[] } */
65
+ const input_files = [ ] ;
66
+
67
+ /** @type {import('types').PageNode | null } */
68
+ let node = route . leaf ;
69
+ while ( node ) {
70
+ if ( node . shared ) input_files . push ( node . shared ) ;
71
+ if ( node . server ) input_files . push ( node . server ) ;
72
+ node = node . parent ?? null ;
73
+ }
74
+
75
+ /** @type {import('types').PageNode | null } */
76
+ node = route . layout ;
77
+ while ( node ) {
78
+ if ( node . shared ) input_files . push ( node . shared ) ;
79
+ if ( node . server ) input_files . push ( node . server ) ;
80
+ node = node . parent ?? null ;
81
+ }
82
+
83
+ if ( route . endpoint ) {
84
+ input_files . push ( route . endpoint . file ) ;
85
+ }
86
+
87
+ try {
88
+ fs . mkdirSync ( outdir , { recursive : true } ) ;
89
+ } catch { }
90
+
91
+ const output_files = compact (
92
+ fs . readdirSync ( outdir ) . map ( ( name ) => {
93
+ const stats = fs . statSync ( path . join ( outdir , name ) ) ;
94
+ if ( stats . isDirectory ( ) ) return ;
95
+ return {
96
+ name,
97
+ updated : stats . mtimeMs
98
+ } ;
99
+ } )
100
+ ) ;
101
+
102
+ const source_last_updated = Math . max (
103
+ // ctimeMs includes move operations whereas mtimeMs does not
104
+ ...input_files . map ( ( file ) => fs . statSync ( file ) . ctimeMs )
105
+ ) ;
106
+ const types_last_updated = Math . max ( ...output_files . map ( ( file ) => file . updated ) ) ;
107
+
108
+ const should_generate =
109
+ // source files were generated more recently than the types
110
+ source_last_updated > types_last_updated ||
111
+ // no meta data file exists yet
112
+ ! has_meta_data ||
113
+ // some file was deleted
114
+ ! meta_data [ route . id ] ?. every ( ( file ) => input_files . includes ( file ) ) ;
115
+
116
+ if ( should_generate ) {
117
+ // track which old files end up being surplus to requirements
118
+ const to_delete = new Set ( output_files . map ( ( file ) => file . name ) ) ;
119
+ update_types ( config , routes_map , route , to_delete ) ;
120
+ meta_data [ route . id ] = input_files ;
121
+ }
53
122
}
123
+
124
+ fs . writeFileSync ( meta_data_file , JSON . stringify ( meta_data , null , '\t' ) ) ;
54
125
}
55
126
56
127
/**
@@ -72,6 +143,7 @@ export async function write_types(config, manifest_data, file) {
72
143
73
144
const route = manifest_data . routes . find ( ( route ) => route . id === id ) ;
74
145
if ( ! route ) return ; // this shouldn't ever happen
146
+ if ( ! route . leaf && ! route . layout && ! route . endpoint ) return ; // nothing to do
75
147
76
148
update_types ( config , create_routes_map ( manifest_data ) , route ) ;
77
149
}
@@ -96,60 +168,12 @@ function create_routes_map(manifest_data) {
96
168
* @param {import('types').ValidatedConfig } config
97
169
* @param {Map<import('types').PageNode, import('types').RouteData> } routes
98
170
* @param {import('types').RouteData } route
171
+ * @param {Set<string> } [to_delete]
99
172
*/
100
- function update_types ( config , routes , route ) {
101
- if ( ! route . leaf && ! route . layout && ! route . endpoint ) return ; // nothing to do
102
-
173
+ function update_types ( config , routes , route , to_delete = new Set ( ) ) {
103
174
const routes_dir = posixify ( path . relative ( '.' , config . kit . files . routes ) ) ;
104
175
const outdir = path . join ( config . kit . outDir , 'types' , routes_dir , route . id ) ;
105
176
106
- // first, check if the types are out of date
107
- const input_files = [ ] ;
108
-
109
- /** @type {import('types').PageNode | null } */
110
- let node = route . leaf ;
111
- while ( node ) {
112
- if ( node . shared ) input_files . push ( node . shared ) ;
113
- if ( node . server ) input_files . push ( node . server ) ;
114
- node = node . parent ?? null ;
115
- }
116
-
117
- /** @type {import('types').PageNode | null } */
118
- node = route . layout ;
119
- while ( node ) {
120
- if ( node . shared ) input_files . push ( node . shared ) ;
121
- if ( node . server ) input_files . push ( node . server ) ;
122
- node = node . parent ?? null ;
123
- }
124
-
125
- if ( route . endpoint ) {
126
- input_files . push ( route . endpoint . file ) ;
127
- }
128
-
129
- try {
130
- fs . mkdirSync ( outdir , { recursive : true } ) ;
131
- } catch { }
132
-
133
- const output_files = compact (
134
- fs . readdirSync ( outdir ) . map ( ( name ) => {
135
- const stats = fs . statSync ( path . join ( outdir , name ) ) ;
136
- if ( stats . isDirectory ( ) ) return ;
137
- return {
138
- name,
139
- updated : stats . mtimeMs
140
- } ;
141
- } )
142
- ) ;
143
-
144
- const source_last_updated = Math . max ( ...input_files . map ( ( file ) => fs . statSync ( file ) . mtimeMs ) ) ;
145
- const types_last_updated = Math . max ( ...output_files . map ( ( file ) => file ?. updated ) ) ;
146
-
147
- // types were generated more recently than the source files, so don't regenerate
148
- if ( types_last_updated > source_last_updated ) return ;
149
-
150
- // track which old files end up being surplus to requirements
151
- const to_delete = new Set ( output_files . map ( ( file ) => file . name ) ) ;
152
-
153
177
// now generate new types
154
178
const imports = [ `import type * as Kit from '@sveltejs/kit';` ] ;
155
179
@@ -180,7 +204,7 @@ function update_types(config, routes, route) {
180
204
`type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>`
181
205
) ;
182
206
// null & {} == null, we need to prevent that in some situations
183
- declarations . push ( `type EnsureParentData <T> = T extends null | undefined ? {} : T;` ) ;
207
+ declarations . push ( `type EnsureDefined <T> = T extends null | undefined ? {} : T;` ) ;
184
208
}
185
209
186
210
if ( route . leaf ) {
@@ -329,9 +353,13 @@ function process_node(node, outdir, is_page, all_pages_have_load = true) {
329
353
written_proxies . push ( `proxy${ path . basename ( node . shared ) } ` ) ;
330
354
}
331
355
332
- const type = get_data_type ( node . shared , `${ parent_type } & ${ prefix } ServerData` , proxy ) ;
356
+ const type = get_data_type (
357
+ node . shared ,
358
+ `${ parent_type } & EnsureDefined<${ prefix } ServerData>` ,
359
+ proxy
360
+ ) ;
333
361
334
- data = `Expand<Omit<${ parent_type } , keyof ${ type } > & ${ type } >` ;
362
+ data = `Expand<Omit<${ parent_type } , keyof ${ type } > & EnsureDefined< ${ type } > >` ;
335
363
336
364
const output_data_shape =
337
365
! is_page && all_pages_have_load
@@ -345,7 +373,7 @@ function process_node(node, outdir, is_page, all_pages_have_load = true) {
345
373
} else if ( server_data === 'null' ) {
346
374
data = `Expand<${ parent_type } >` ;
347
375
} else {
348
- data = `Expand<Omit<${ parent_type } , keyof ${ prefix } ServerData> & ${ prefix } ServerData>` ;
376
+ data = `Expand<Omit<${ parent_type } , keyof ${ prefix } ServerData> & EnsureDefined< ${ prefix } ServerData> >` ;
349
377
}
350
378
351
379
exports . push ( `export type ${ prefix } Data = ${ data } ;` ) ;
@@ -396,14 +424,14 @@ function get_parent_type(node, type) {
396
424
parent = parent . parent ;
397
425
}
398
426
399
- let parent_str = `EnsureParentData <${ parent_imports [ 0 ] || '{}' } >` ;
427
+ let parent_str = `EnsureDefined <${ parent_imports [ 0 ] || '{}' } >` ;
400
428
for ( let i = 1 ; i < parent_imports . length ; i ++ ) {
401
429
// Omit is necessary because a parent could have a property with the same key which would
402
430
// cause a type conflict. At runtime the child overwrites the parent property in this case,
403
431
// so reflect that in the type definition.
404
- // EnsureParentData is necessary because {something: string} & null becomes null.
432
+ // EnsureDefined is necessary because {something: string} & null becomes null.
405
433
// Output types of server loads can be null but when passed in through the `parent` parameter they are the empty object instead.
406
- parent_str = `Omit<${ parent_str } , keyof ${ parent_imports [ i ] } > & EnsureParentData <${ parent_imports [ i ] } >` ;
434
+ parent_str = `Omit<${ parent_str } , keyof ${ parent_imports [ i ] } > & EnsureDefined <${ parent_imports [ i ] } >` ;
407
435
}
408
436
return parent_str ;
409
437
}
0 commit comments