@@ -160,7 +160,135 @@ export async function dev(base: string, entrypoint: string) {
160
160
161
161
if ( manifestChanged ) await generate ( dir , newManifest ) ;
162
162
163
- await import ( entrypoint ) ;
163
+ if ( await updateNpmSpecifiers ( newManifest , dir ) ) {
164
+ // reset
165
+ await dev ( base , entrypoint ) ;
166
+ } else {
167
+ await import ( entrypoint ) ;
168
+ }
169
+ }
170
+
171
+ async function updateNpmSpecifiers ( _manifest : Manifest , dir : string ) {
172
+ const importMapPath = join ( dir , "import_map.json" ) ;
173
+ const importMapText = await Deno . readTextFile ( importMapPath ) ;
174
+ const importMap = JSON . parse ( importMapText ) ;
175
+ const originalImportMapSnapshot = JSON . stringify ( importMap ) ;
176
+ // todo: don't mutate the provided object in getDenoInfo
177
+ const denoInfo = await getDenoInfo ( dir , importMap ) ;
178
+
179
+ if ( ! denoInfo . npmPackages ) {
180
+ return false ;
181
+ }
182
+
183
+ const foundReferences = new Set < string > ( ) ;
184
+ const npmPackageReferences = [ ] ;
185
+
186
+ // todo: only analyze the tree of islands
187
+ for ( const module of denoInfo . modules ) {
188
+ if ( module . dependencies ) {
189
+ for ( const dep of module . dependencies ) {
190
+ if ( dep . npmPackage != null && ! foundReferences . has ( dep . specifier ) ) {
191
+ npmPackageReferences . push ( {
192
+ specifier : dep . specifier ,
193
+ package : dep . npmPackage ,
194
+ } ) ;
195
+ foundReferences . add ( dep . specifier ) ;
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ for ( const reference of npmPackageReferences ) {
202
+ const pkg = denoInfo . npmPackages ! [ reference . package ] ;
203
+ let esmUrl = `https://esm.sh/${ pkg . name } @${ pkg . version } ` ;
204
+ const deps = pkg . dependencies . map ( ( depId ) => {
205
+ const dep = denoInfo . npmPackages ! [ depId ] ;
206
+ return `${ dep . name } @${ dep . version } ` ;
207
+ } ) ;
208
+ if ( deps . length > 0 ) {
209
+ // very buggy
210
+ // esmUrl += "?deps=" + deps.join(",");
211
+ }
212
+ importMap . imports [ reference . specifier ] = esmUrl ;
213
+ }
214
+ if ( originalImportMapSnapshot === JSON . stringify ( importMap ) ) {
215
+ return false ;
216
+ }
217
+
218
+ // todo: use deno fmt
219
+ await Deno . writeTextFile (
220
+ importMapPath ,
221
+ JSON . stringify ( importMap , undefined , 2 ) + "\n" ,
222
+ ) ;
223
+ return true ;
224
+ }
225
+
226
+ interface DenoInfo {
227
+ modules : DenoInfoModule [ ] ;
228
+ npmPackages ?: Record < string , DenoInfoNpmPackage > ;
229
+ }
230
+
231
+ interface DenoInfoModule {
232
+ specifier : string ;
233
+ dependencies ?: DenoInfoModuleDependency [ ] ;
234
+ }
235
+
236
+ interface DenoInfoModuleDependency {
237
+ specifier : string ;
238
+ code : DenoInfoModuleDependencyCode ;
239
+ npmPackage ?: string ;
240
+ }
241
+
242
+ interface DenoInfoModuleDependencyCode {
243
+ specifier : string ;
244
+ }
245
+
246
+ interface DenoInfoNpmPackage {
247
+ name : string ;
248
+ version : string ;
249
+ dependencies : string [ ] ;
250
+ }
251
+
252
+ async function getDenoInfo ( dir : string , importMap : any ) {
253
+ for ( const key of Object . keys ( importMap . imports ) ) {
254
+ if ( isNpmPackageReference ( key ) ) {
255
+ delete importMap . imports [ key ] ;
256
+ }
257
+ }
258
+
259
+ const importMapUri = `data:application/json;base64,${
260
+ btoa ( JSON . stringify ( importMap ) )
261
+ } `;
262
+ const proc = Deno . run ( {
263
+ cmd : [
264
+ "V:\\deno\\target\\debug\\deno.exe" ,
265
+ "info" ,
266
+ "--import-map" ,
267
+ importMapUri ,
268
+ "--json" ,
269
+ "main.ts" ,
270
+ ] ,
271
+ cwd : dir ,
272
+ stdout : "piped" ,
273
+ stderr : "piped" ,
274
+ } ) ;
275
+ const [ out , stderrOutput ] = await Promise . all ( [
276
+ proc . output ( ) ,
277
+ proc . stderrOutput ( ) ,
278
+ ] ) ;
279
+ const status = await proc . status ( ) ;
280
+ if ( ! status . success ) {
281
+ console . error ( new TextDecoder ( ) . decode ( stderrOutput ) ) ;
282
+ throw new Error ( "Failed getting deno info." ) ;
283
+ }
284
+ proc . close ( ) ;
285
+ const outputText = new TextDecoder ( ) . decode ( out ) ;
286
+ const outputData = JSON . parse ( outputText ) ;
287
+ return outputData as DenoInfo ;
288
+ }
289
+
290
+ function isNpmPackageReference ( rawText : string ) {
291
+ return rawText . startsWith ( "npm:" ) ;
164
292
}
165
293
166
294
function arraysEqual < T > ( a : T [ ] , b : T [ ] ) : boolean {
0 commit comments