1
- import { dim } from 'https://deno.land/[email protected] /fmt/colors.ts'
2
- import { dirname , join } from 'https://deno.land/[email protected] /path/mod.ts'
3
- import { ensureDir } from 'https://deno.land/[email protected] /fs/ensure_dir.ts'
4
1
// @deno -types="https://deno.land/x/[email protected] /mod.d.ts"
5
2
import { build , stop as stopEsbuild } from 'https://deno.land/x/[email protected] /mod.js'
3
+ import { dim } from 'https://deno.land/[email protected] /fmt/colors.ts'
4
+ import { basename , dirname , join } from 'https://deno.land/[email protected] /path/mod.ts'
5
+ import { ensureDir , } from 'https://deno.land/[email protected] /fs/ensure_dir.ts'
6
6
import { parseExportNames , transform } from '../compiler/mod.ts'
7
7
import { trimModuleExt } from '../framework/core/module.ts'
8
- import { ensureTextFile , existsFileSync , lazyRemove } from '../shared/fs.ts'
8
+ import { ensureTextFile , existsDirSync , existsFileSync , lazyRemove } from '../shared/fs.ts'
9
9
import log from '../shared/log.ts'
10
10
import util from '../shared/util.ts'
11
11
import { VERSION } from '../version.ts'
12
12
import type { Application , Module } from '../server/app.ts'
13
- import { computeHash , getAlephPkgUri } from '../server/helper.ts'
14
13
import { cache } from '../server/cache.ts'
14
+ import { computeHash , getAlephPkgUri } from '../server/helper.ts'
15
+
16
+ const hashShort = 8
17
+ const reHashJS = new RegExp ( `\\.[0-9a-fx]{${ hashShort } }\\.js$` , 'i' )
15
18
16
- export const bundlerRuntimeCode = ( `
19
+ export const bundlerRuntimeCode = `
17
20
window.__ALEPH = {
18
21
basePath: '/',
19
22
pack: {},
@@ -46,22 +49,15 @@ export const bundlerRuntimeCode = (`
46
49
})
47
50
}
48
51
}
49
- ` ) . split ( '\n' )
50
- . map ( l => l . trim ( )
51
- . replaceAll ( ') {' , '){' )
52
- . replace ( / \s * ( [ , : = | + ] { 1 , 2 } ) \s + / g, '$1' )
53
- )
54
- . join ( '' )
52
+ `
55
53
56
54
/** The bundler class for aleph server. */
57
55
export class Bundler {
58
56
#app: Application
59
- #compiledModules: Set < string >
60
57
#bundledFiles: Map < string , string >
61
58
62
59
constructor ( app : Application ) {
63
60
this . #app = app
64
- this . #compiledModules = new Set ( )
65
61
this . #bundledFiles = new Map ( )
66
62
}
67
63
@@ -82,21 +78,21 @@ export class Bundler {
82
78
}
83
79
} )
84
80
85
- await this . createPolyfillBundle ( )
86
- await this . createBundleChunk (
81
+ await this . bundlePolyfillChunck ( )
82
+ await this . bundleChunk (
87
83
'deps' ,
88
84
Array . from ( remoteEntries ) ,
89
85
[ ]
90
86
)
91
87
if ( sharedEntries . size > 0 ) {
92
- await this . createBundleChunk (
88
+ await this . bundleChunk (
93
89
'shared' ,
94
90
Array . from ( sharedEntries ) ,
95
91
Array . from ( remoteEntries )
96
92
)
97
93
}
98
94
for ( const url of entries ) {
99
- await this . createBundleChunk (
95
+ await this . bundleChunk (
100
96
trimModuleExt ( url ) ,
101
97
[ url ] ,
102
98
[
@@ -105,7 +101,12 @@ export class Bundler {
105
101
] . flat ( )
106
102
)
107
103
}
104
+
105
+ // create main.js after all chunks are bundled
108
106
await this . createMainJS ( )
107
+
108
+ // unlike nodejs, Deno doesn't provide the necessary APIs to allow Deno to
109
+ // exit while esbuild's internal child process is still running.
109
110
stopEsbuild ( )
110
111
}
111
112
@@ -128,9 +129,10 @@ export class Bundler {
128
129
}
129
130
130
131
private async compile ( mod : Module , external : string [ ] ) : Promise < string > {
131
- const bundlingFile = util . trimSuffix ( mod . jsFile , '.js' ) + '.bundling.js'
132
+ const hash = mod . deps . length > 0 ? computeHash ( mod . sourceHash + mod . deps . map ( ( { hash } ) => hash ) . join ( '' ) ) : mod . sourceHash
133
+ const bundlingFile = util . trimSuffix ( mod . jsFile , '.js' ) + `.bundling.${ hash . slice ( 0 , hashShort ) } .js`
132
134
133
- if ( this . #compiledModules . has ( mod . url ) ) {
135
+ if ( existsFileSync ( bundlingFile ) ) {
134
136
return bundlingFile
135
137
}
136
138
@@ -179,8 +181,8 @@ export class Bundler {
179
181
}
180
182
}
181
183
184
+ await clearBuildCache ( bundlingFile )
182
185
await ensureTextFile ( bundlingFile , code )
183
- this . #compiledModules. add ( mod . url )
184
186
185
187
return bundlingFile
186
188
}
@@ -194,30 +196,30 @@ export class Bundler {
194
196
} , { } as Record < string , string > )
195
197
const mainJS = `__ALEPH.bundledFiles=${ JSON . stringify ( bundledFiles ) } ;` + this . #app. getMainJS ( true )
196
198
const hash = computeHash ( mainJS )
197
- const bundleFilename = `main.bundle.${ hash . slice ( 0 , 8 ) } .js`
198
- const bundleFile = join ( this . #app. buildDir , bundleFilename )
199
- await Deno . writeTextFile ( bundleFile , mainJS )
199
+ const bundleFilename = `main.bundle.${ hash . slice ( 0 , hashShort ) } .js`
200
+ const bundleFilePath = join ( this . #app. buildDir , bundleFilename )
201
+ await Deno . writeTextFile ( bundleFilePath , mainJS )
200
202
this . #bundledFiles. set ( 'main' , bundleFilename )
201
203
log . info ( ` {} main.js ${ dim ( '• ' + util . formatBytes ( mainJS . length ) ) } ` )
202
204
}
203
205
204
206
/** create polyfill bundle. */
205
- private async createPolyfillBundle ( ) {
207
+ private async bundlePolyfillChunck ( ) {
206
208
const alephPkgUri = getAlephPkgUri ( )
207
209
const { buildTarget } = this . #app. config
208
210
const hash = computeHash ( buildTarget + Deno . version . deno + VERSION )
209
- const bundleFilename = `polyfill.bundle.${ hash . slice ( 0 , 8 ) } .js`
210
- const bundleFile = join ( this . #app. buildDir , bundleFilename )
211
- if ( ! existsFileSync ( bundleFile ) ) {
211
+ const bundleFilename = `polyfill.bundle.${ hash . slice ( 0 , hashShort ) } .js`
212
+ const bundleFilePath = join ( this . #app. buildDir , bundleFilename )
213
+ if ( ! existsFileSync ( bundleFilePath ) ) {
212
214
const rawPolyfillFile = `${ alephPkgUri } /bundler/polyfills/${ buildTarget } /mod.ts`
213
- await this . build ( rawPolyfillFile , bundleFile )
215
+ await this . build ( rawPolyfillFile , bundleFilePath )
214
216
}
215
217
this . #bundledFiles. set ( 'polyfill' , bundleFilename )
216
- log . info ( ` {} polyfill.js (${ buildTarget . toUpperCase ( ) } ) ${ dim ( '• ' + util . formatBytes ( Deno . statSync ( bundleFile ) . size ) ) } ` )
218
+ log . info ( ` {} polyfill.js (${ buildTarget . toUpperCase ( ) } ) ${ dim ( '• ' + util . formatBytes ( Deno . statSync ( bundleFilePath ) . size ) ) } ` )
217
219
}
218
220
219
221
/** create bundle chunk. */
220
- private async createBundleChunk ( name : string , entry : string [ ] , external : string [ ] ) {
222
+ private async bundleChunk ( name : string , entry : string [ ] , external : string [ ] ) {
221
223
const entryCode = ( await Promise . all ( entry . map ( async ( url , i ) => {
222
224
let mod = this . #app. getModule ( url )
223
225
if ( mod && mod . jsFile !== '' ) {
@@ -237,23 +239,23 @@ export class Bundler {
237
239
return [ ]
238
240
} ) ) ) . flat ( ) . join ( '\n' )
239
241
const hash = computeHash ( entryCode + VERSION + Deno . version . deno )
240
- const bundleFilename = `${ name } .bundle.${ hash . slice ( 0 , 8 ) } .js`
242
+ const bundleFilename = `${ name } .bundle.${ hash . slice ( 0 , hashShort ) } .js`
241
243
const bundleEntryFile = join ( this . #app. buildDir , `${ name } .bundle.entry.js` )
242
- const bundleFile = join ( this . #app. buildDir , bundleFilename )
243
- if ( ! existsFileSync ( bundleFile ) ) {
244
+ const bundleFilePath = join ( this . #app. buildDir , bundleFilename )
245
+ if ( ! existsFileSync ( bundleFilePath ) ) {
244
246
await Deno . writeTextFile ( bundleEntryFile , entryCode )
245
- await this . build ( bundleEntryFile , bundleFile )
247
+ await this . build ( bundleEntryFile , bundleFilePath )
246
248
lazyRemove ( bundleEntryFile )
247
249
}
248
250
this . #bundledFiles. set ( name , bundleFilename )
249
- log . info ( ` {} ${ name } .js ${ dim ( '• ' + util . formatBytes ( Deno . statSync ( bundleFile ) . size ) ) } ` )
251
+ log . info ( ` {} ${ name } .js ${ dim ( '• ' + util . formatBytes ( Deno . statSync ( bundleFilePath ) . size ) ) } ` )
250
252
}
251
253
252
254
/** run deno bundle and compress the output using terser. */
253
255
private async build ( entryFile : string , bundleFile : string ) {
254
256
const { buildTarget, browserslist } = this . #app. config
255
257
256
- await clearBundle ( bundleFile )
258
+ await clearBuildCache ( bundleFile )
257
259
await build ( {
258
260
entryPoints : [ entryFile ] ,
259
261
outfile : bundleFile ,
@@ -263,6 +265,8 @@ export class Bundler {
263
265
} ) ) ,
264
266
bundle : true ,
265
267
minify : true ,
268
+ treeShaking : true ,
269
+ sourcemap : false ,
266
270
plugins : [ {
267
271
name : 'http-loader' ,
268
272
setup ( build ) {
@@ -295,6 +299,27 @@ export class Bundler {
295
299
}
296
300
}
297
301
298
- async function clearBundle ( filename : string ) {
302
+ export function simpleJSMinify ( code : string ) {
303
+ return code . split ( '\n' ) . map ( l => l . trim ( )
304
+ . replace ( / \s * ( [ , : = | + ] { 1 , 2 } ) \s + / g, '$1' )
305
+ . replaceAll ( ') {' , '){' )
306
+ ) . join ( '' )
307
+ }
308
+
309
+ async function clearBuildCache ( filename : string ) {
310
+ const dir = dirname ( filename )
311
+ const hashname = basename ( filename )
312
+ if ( ! reHashJS . test ( hashname ) || ! existsDirSync ( dir ) ) {
313
+ return
314
+ }
299
315
316
+ const jsName = hashname . split ( '.' ) . slice ( 0 , - 2 ) . join ( '.' ) + '.js'
317
+ for await ( const entry of Deno . readDir ( dir ) ) {
318
+ if ( entry . isFile && reHashJS . test ( entry . name ) ) {
319
+ const _jsName = entry . name . split ( '.' ) . slice ( 0 , - 2 ) . join ( '.' ) + '.js'
320
+ if ( _jsName === jsName && hashname !== entry . name ) {
321
+ await Deno . remove ( join ( dir , entry . name ) )
322
+ }
323
+ }
324
+ }
300
325
}
0 commit comments