@@ -7,13 +7,14 @@ import { existsDir, existsFile } from "../lib/fs.ts";
7
7
import { parseHtmlLinks } from "./html.ts" ;
8
8
import log from "../lib/log.ts" ;
9
9
import util from "../lib/util.ts" ;
10
- import { DependencyGraph } from "./graph.ts" ;
10
+ import type { DependencyGraph } from "./graph.ts" ;
11
11
import {
12
12
builtinModuleExts ,
13
13
getAlephPkgUri ,
14
14
initModuleLoaders ,
15
15
loadImportMap ,
16
16
loadJSXConfig ,
17
+ restoreUrl ,
17
18
toLocalPath ,
18
19
} from "./helpers.ts" ;
19
20
import { initRoutes } from "./routing.ts" ;
@@ -33,6 +34,7 @@ export async function build(serverEntry?: string) {
33
34
const moduleLoaders = await initModuleLoaders ( importMap ) ;
34
35
const config : AlephConfig | undefined = Reflect . get ( globalThis , "__ALEPH_CONFIG" ) ;
35
36
const platform = config ?. build ?. platform ?? "deno" ;
37
+ const target = config ?. build ?. target ?? "es2015" ;
36
38
const outputDir = join ( workingDir , config ?. build ?. outputDir ?? "dist" ) ;
37
39
38
40
if ( platform === "cloudflare" || platform === "vercel" ) {
@@ -57,6 +59,7 @@ export async function build(serverEntry?: string) {
57
59
return [ filename , exportNames ] ;
58
60
} ) ) ;
59
61
}
62
+
60
63
const modulesProxyPort = Deno . env . get ( "ALEPH_MODULES_PROXY_PORT" ) ;
61
64
const serverEntryCode = [
62
65
`import { DependencyGraph } from "${ alephPkgUri } /server/graph.ts";` ,
@@ -162,7 +165,7 @@ export async function build(serverEntry?: string) {
162
165
build . onResolve ( { filter : / .* / } , ( args ) => {
163
166
let importUrl = args . path ;
164
167
if ( importUrl in importMap . imports ) {
165
- // since deno deploy doesn't support importMap, we need to resolve the 'react' import
168
+ // since deno deploy doesn't support importMap yet , we need to resolve the 'react' import
166
169
importUrl = importMap . imports [ importUrl ] ;
167
170
}
168
171
@@ -230,8 +233,10 @@ export async function build(serverEntry?: string) {
230
233
} ] ,
231
234
} ) ;
232
235
233
- // create server_dependency_graph.js
234
236
const serverDependencyGraph : DependencyGraph | undefined = Reflect . get ( globalThis , "serverDependencyGraph" ) ;
237
+ const clientDependencyGraph : DependencyGraph | undefined = Reflect . get ( globalThis , "clientDependencyGraph" ) ;
238
+
239
+ // create server_dependency_graph.js
235
240
if ( serverDependencyGraph ) {
236
241
// deno-lint-ignore no-unused-vars
237
242
const modules = serverDependencyGraph . modules . map ( ( { sourceCode, ...ret } ) => ret ) ;
@@ -246,26 +251,26 @@ export async function build(serverEntry?: string) {
246
251
if ( await existsFile ( join ( workingDir , "index.html" ) ) ) {
247
252
const html = await Deno . readFile ( join ( workingDir , "index.html" ) ) ;
248
253
const links = await parseHtmlLinks ( html ) ;
249
- for ( const link of links ) {
250
- if ( ! util . isLikelyHttpURL ( link ) ) {
251
- const ext = extname ( link ) ;
252
- if ( ext === ". css" || builtinModuleExts . includes ( ext . slice ( 1 ) ) ) {
253
- const specifier = "." + util . cleanPath ( link ) ;
254
+ for ( const src of links ) {
255
+ if ( ! util . isLikelyHttpURL ( src ) ) {
256
+ const ext = extname ( util . splitBy ( src , "?" ) [ 0 ] ) . slice ( 1 ) ;
257
+ if ( ext === "css" || builtinModuleExts . includes ( ext ) ) {
258
+ const specifier = "." + util . cleanPath ( src ) ;
254
259
tasks . push ( specifier ) ;
255
260
}
256
261
}
257
262
}
258
263
}
259
- tasks . push ( `${ alephPkgUri } /framework/core/style.ts` ) ;
264
+
265
+ const entryModules = new Set ( tasks ) ;
266
+ const allModules = new Set < string > ( ) ;
260
267
261
268
// transform client modules
262
269
const serverHandler : FetchHandler | undefined = Reflect . get ( globalThis , "__ALEPH_SERVER" ) ?. handler ;
263
- const clientModules = new Set < string > ( ) ;
264
270
if ( serverHandler ) {
265
271
while ( tasks . length > 0 ) {
266
272
const deps = new Set < string > ( ) ;
267
273
await Promise . all ( tasks . map ( async ( specifier ) => {
268
- clientModules . add ( specifier ) ;
269
274
const url = new URL ( util . isLikelyHttpURL ( specifier ) ? toLocalPath ( specifier ) : specifier , "http://localhost" ) ;
270
275
const isCSS = url . pathname . endsWith ( ".css" ) ;
271
276
const req = new Request ( url . toString ( ) ) ;
@@ -282,20 +287,151 @@ export async function build(serverEntry?: string) {
282
287
] ) ;
283
288
await res . body ?. pipeTo ( file . writable ) ;
284
289
if ( ! isCSS ) {
285
- const clientDependencyGraph : DependencyGraph | undefined = Reflect . get ( globalThis , "clientDependencyGraph" ) ;
286
- clientDependencyGraph ?. get ( specifier ) ?. deps ?. forEach ( ( { specifier } ) => {
290
+ clientDependencyGraph ?. get ( specifier ) ?. deps ?. forEach ( ( { specifier, dynamic } ) => {
291
+ if ( dynamic ) {
292
+ entryModules . add ( specifier ) ;
293
+ }
287
294
if ( specifier . endsWith ( ".css" ) ) {
288
295
deps . add ( specifier + "?module" ) ;
289
296
} else {
290
297
deps . add ( specifier ) ;
291
298
}
292
299
} ) ;
300
+ } else if ( url . searchParams . has ( "module" ) ) {
301
+ deps . add ( `${ alephPkgUri } /framework/core/style.ts` ) ;
293
302
}
303
+ allModules . add ( specifier ) ;
294
304
} ) ) ;
295
- tasks = Array . from ( deps ) . filter ( ( specifier ) => ! clientModules . has ( specifier ) ) ;
305
+ tasks = Array . from ( deps ) . filter ( ( specifier ) => ! allModules . has ( specifier ) ) ;
296
306
}
297
307
}
298
308
309
+ // count client module refs
310
+ const refs = new Map < string , Set < string > > ( ) ;
311
+ for ( const name of entryModules ) {
312
+ clientDependencyGraph ?. walk ( name , ( { specifier } , importer ) => {
313
+ if ( importer ) {
314
+ let set = refs . get ( specifier ) ;
315
+ if ( ! set ) {
316
+ set = new Set < string > ( ) ;
317
+ refs . set ( specifier , set ) ;
318
+ }
319
+ set . add ( importer . specifier ) ;
320
+ }
321
+ } ) ;
322
+ }
323
+
324
+ // hygiene 1
325
+ /*
326
+ B(1) <-
327
+ A <- <- <- D(1+) :: A <- D(1)
328
+ C(1) <-
329
+ */
330
+ refs . forEach ( ( counter , specifier ) => {
331
+ if ( counter . size > 1 ) {
332
+ const a = Array . from ( counter ) . filter ( ( specifier ) => {
333
+ const set = refs . get ( specifier ) ;
334
+ if ( set ?. size === 1 ) {
335
+ const name = set . values ( ) . next ( ) . value ;
336
+ if ( name && counter . has ( name ) ) {
337
+ return false ;
338
+ }
339
+ }
340
+ return true ;
341
+ } ) ;
342
+ refs . set ( specifier , new Set ( a ) ) ;
343
+ }
344
+ } ) ;
345
+
346
+ // hygiene 2 (twice)
347
+ /*
348
+ B(1) <-
349
+ A <- C(1) <- E(1+) :: A <- E(1)
350
+ D(1) <-
351
+ */
352
+ for ( let i = 0 ; i < 2 ; i ++ ) {
353
+ refs . forEach ( ( counter , specifier ) => {
354
+ if ( counter . size > 0 ) {
355
+ const a = Array . from ( counter ) ;
356
+ if (
357
+ a . every ( ( specifier ) => {
358
+ const set = refs . get ( specifier ) ;
359
+ return set ?. size === 1 ;
360
+ } )
361
+ ) {
362
+ const set = new Set ( a . map ( ( specifier ) => {
363
+ const set = refs . get ( specifier ) ;
364
+ return set ?. values ( ) . next ( ) . value ;
365
+ } ) ) ;
366
+ if ( set . size === 1 ) {
367
+ refs . set ( specifier , set ) ;
368
+ }
369
+ }
370
+ }
371
+ } ) ;
372
+ }
373
+
374
+ // find client modules
375
+ const clientModules = new Set < string > ( entryModules ) ;
376
+ refs . forEach ( ( counter , specifier ) => {
377
+ if ( counter . size > 1 ) {
378
+ clientModules . add ( specifier ) ;
379
+ }
380
+ console . log ( `${ specifier } is referenced by \n - ${ Array . from ( counter ) . join ( "\n - " ) } ` ) ;
381
+ } ) ;
382
+
383
+ // bundle client modules
384
+ const bundling = new Set < string > ( ) ;
385
+ clientModules . forEach ( ( specifier ) => {
386
+ if (
387
+ clientDependencyGraph ?. get ( specifier ) ?. deps ?. some ( ( { specifier } ) => ! clientModules . has ( specifier ) ) &&
388
+ ! util . splitBy ( specifier , "?" ) [ 0 ] . endsWith ( ".css" )
389
+ ) {
390
+ bundling . add ( specifier ) ;
391
+ }
392
+ } ) ;
393
+ await Promise . all (
394
+ Array . from ( bundling ) . map ( async ( entryPoint ) => {
395
+ const url = new URL ( util . isLikelyHttpURL ( entryPoint ) ? toLocalPath ( entryPoint ) : entryPoint , "http://localhost" ) ;
396
+ let jsFile = join ( outputDir , url . pathname ) ;
397
+ if ( entryPoint . startsWith ( "https://esm.sh/" ) ) {
398
+ jsFile += ".js" ;
399
+ }
400
+ await esbuild ( {
401
+ entryPoints : [ jsFile ] ,
402
+ outfile : jsFile ,
403
+ allowOverwrite : true ,
404
+ platform : "browser" ,
405
+ format : "esm" ,
406
+ target : [ target ] ,
407
+ bundle : true ,
408
+ minify : true ,
409
+ treeShaking : true ,
410
+ sourcemap : false ,
411
+ plugins : [ {
412
+ name : "aleph-esbuild-plugin" ,
413
+ setup ( build ) {
414
+ build . onResolve ( { filter : / .* / } , ( args ) => {
415
+ const path = util . trimPrefix ( args . path , outputDir ) ;
416
+ let specifier = "." + path ;
417
+ if ( args . path . startsWith ( "/-/" ) ) {
418
+ specifier = restoreUrl ( path ) ;
419
+ }
420
+ if ( clientModules . has ( specifier ) && specifier !== entryPoint ) {
421
+ return { path : args . path , external : true } ;
422
+ }
423
+ let jsFile = join ( outputDir , path ) ;
424
+ if ( specifier . startsWith ( "https://esm.sh/" ) ) {
425
+ jsFile += ".js" ;
426
+ }
427
+ return { path : jsFile } ;
428
+ } ) ;
429
+ } ,
430
+ } ] ,
431
+ } ) ;
432
+ } ) ,
433
+ ) ;
434
+
299
435
// clean up then exit
300
436
if ( jsxShimFile ) {
301
437
await Deno . remove ( jsxShimFile ) ;
0 commit comments