1
1
import { minify as terser , ECMA } from 'https://esm.sh/[email protected] '
2
2
import { transform } from '../compiler/mod.ts'
3
- import { colors , path } from '../deps.ts'
3
+ import { colors , ensureDir , path } from '../deps.ts'
4
4
import { defaultReactVersion } from '../shared/constants.ts'
5
5
import { ensureTextFile , existsFileSync , lazyRemove } from '../shared/fs.ts'
6
6
import log from '../shared/log.ts'
@@ -16,9 +16,9 @@ import {
16
16
trimModuleExt
17
17
} from './helper.ts'
18
18
19
- export const createBundlerRuntimeCode = ( ) => minify ( `
19
+ export const bundlerRuntimeCode = `
20
20
window.__ALEPH = {
21
- baseURL: '/'
21
+ baseURL: '/',
22
22
pack: {},
23
23
import: function(src, specifier) {
24
24
var pack = this.pack
@@ -40,14 +40,18 @@ export const createBundlerRuntimeCode = () => minify(`
40
40
})
41
41
}
42
42
}
43
- ` )
43
+ `
44
44
45
45
/** The bundler class for aleph server. */
46
46
export class Bundler {
47
47
#app: Application
48
+ #compiledModules: Set < string >
49
+ #bundledFiles: Map < string , string >
48
50
49
51
constructor ( app : Application ) {
50
52
this . #app = app
53
+ this . #compiledModules = new Set ( )
54
+ this . #bundledFiles = new Map ( )
51
55
}
52
56
53
57
async bundle ( entryMods : Array < { url : string , shared : boolean } > ) {
@@ -67,50 +71,62 @@ export class Bundler {
67
71
}
68
72
} )
69
73
70
- await Promise . all ( [
71
- // this.createPolyfillBundle(),
72
- this . createBundleChunk (
73
- 'deps' ,
74
- remoteEntries ,
75
- [ ]
76
- ) ,
77
- this . createBundleChunk (
74
+ await this . createPolyfillBundle ( )
75
+ await this . createBundleChunk (
76
+ 'deps' ,
77
+ remoteEntries ,
78
+ [ ]
79
+ )
80
+ if ( sharedEntries . length > 0 ) {
81
+ await this . createBundleChunk (
78
82
'shared' ,
79
83
sharedEntries ,
80
84
remoteEntries
81
- ) ,
82
- ...entries . map ( url => {
83
- this . createBundleChunk (
84
- trimModuleExt ( url ) ,
85
- [ url ] ,
86
- [
87
- ...remoteEntries ,
88
- ...sharedEntries
89
- ]
90
- )
91
- } )
85
+ )
86
+ }
87
+ for ( const url of entries ) {
88
+ await this . createBundleChunk (
89
+ trimModuleExt ( url ) ,
90
+ [ url ] ,
91
+ [ remoteEntries , sharedEntries ] . flat ( )
92
+ )
93
+ }
94
+ }
95
+
96
+ getBundledFile ( name : string ) : string | null {
97
+ return this . #bundledFiles. get ( name ) || null
98
+ }
99
+
100
+ async copyDist ( ) {
101
+ await Promise . all ( [
102
+ ...Array . from ( this . #bundledFiles. values ( ) ) . map ( jsFile => this . copyBundleFile ( jsFile ) ) ,
103
+ this . copyMainJS ( ) ,
92
104
] )
93
105
}
94
106
95
- #compiled = new Set < string > ( )
107
+ private async copyMainJS ( ) {
108
+ const mainJS = this . #app. getMainJS ( true )
109
+ const hash = computeHash ( mainJS )
110
+ const jsFilename = `main.bundle.${ hash . slice ( 0 , 8 ) } .js`
111
+ const saveAs = path . join ( this . #app. outputDir , '_aleph' , jsFilename )
112
+ this . #bundledFiles. set ( 'main' , jsFilename )
113
+ await ensureTextFile ( saveAs , mainJS )
114
+ }
115
+
116
+ private async copyBundleFile ( jsFilename : string ) {
117
+ const { buildDir, outputDir } = this . #app
118
+ const bundleFile = path . join ( buildDir , jsFilename )
119
+ const saveAs = path . join ( outputDir , '_aleph' , jsFilename )
120
+ await ensureDir ( path . dirname ( saveAs ) )
121
+ await Deno . copyFile ( bundleFile , saveAs )
122
+ }
96
123
97
- private async compile ( mod : Module , external : string [ ] ) : Promise < [ string , Boolean ] > {
124
+ private async compile ( mod : Module , external : string [ ] ) : Promise < string > {
98
125
const bundlingFile = util . trimSuffix ( mod . jsFile , '.js' ) + '.bundling.js'
99
126
100
- if ( this . #compiled . has ( mod . url ) ) {
101
- return [ bundlingFile , false ]
127
+ if ( this . #compiledModules . has ( mod . url ) ) {
128
+ return bundlingFile
102
129
}
103
- this . #compiled. add ( mod . url )
104
- // let shouldCompile = false
105
- // this.#app.lookupDeps(mod.url, dep => {
106
- // if (external.includes(dep.url)) {
107
- // shouldCompile = true
108
- // return false
109
- // }
110
- // })
111
- // if (!shouldCompile) {
112
- // return [mod.jsFile, false]
113
- // }
114
130
115
131
const { content, contentType } = await this . #app. fetchModule ( mod . url )
116
132
const source = await this . #app. precompile ( mod . url , content , contentType )
@@ -141,8 +157,8 @@ export class Bundler {
141
157
if ( ! dep . url . startsWith ( '#' ) && ! external . includes ( dep . url ) ) {
142
158
const depMod = this . #app. getModule ( dep . url )
143
159
if ( depMod !== null ) {
144
- const [ _ , isBundling ] = await this . compile ( depMod , external )
145
160
const s = `.bundling.js#${ dep . url } @`
161
+ await this . compile ( depMod , external )
146
162
code = code . split ( s ) . map ( ( p , i ) => {
147
163
if ( i > 0 && p . charAt ( 6 ) === '"' ) {
148
164
return dep . hash . slice ( 0 , 6 ) + p . slice ( 6 )
@@ -154,42 +170,9 @@ export class Bundler {
154
170
}
155
171
156
172
await ensureTextFile ( bundlingFile , code )
157
- return [ bundlingFile , true ]
158
- }
173
+ this . #compiledModules. add ( mod . url )
159
174
160
- async copyDist ( ) {
161
- // const pageModules: Module[] = []
162
- // this.#pageRouting.lookup(routes => routes.forEach(({ module: { url } }) => {
163
- // const mod = this.getModule(url)
164
- // if (mod) {
165
- // pageModules.push(mod)
166
- // }
167
- // }))
168
- // await Promise.all([
169
- // (async () => {
170
- // const mainJS = this.getMainJS(true)
171
- // const filename = `main.bundle.${util.shortHash(computeHash(mainJS))}.js`
172
- // const saveAs = path.join(this.outputDir, '_aleph', filename)
173
- // await Deno.writeTextFile(saveAs, mainJS)
174
- // })(),
175
- // ...['deps', 'shared', 'polyfill'].map(async name => {
176
- // const mod = this.#modules.get(`/${name}.js`)
177
- // if (mod) {
178
- // const { hash } = mod
179
- // const bundleFile = path.join(this.buildDir, `${name}.bundle.${util.shortHash(hash)}.js`)
180
- // const saveAs = path.join(this.outputDir, '_aleph', `${name}.bundle.${util.shortHash(hash)}.js`)
181
- // await Deno.copyFile(bundleFile, saveAs)
182
- // }
183
- // }),
184
- // ...pageModules.map(async mod => {
185
- // const { jsFile, hash } = mod
186
- // const pathname = util.trimSuffix(jsFile.replace(reHashJS, ''), '.bundling')
187
- // const bundleFile = pathname + `.bundle.${util.shortHash(hash)}.js`
188
- // const saveAs = path.join(this.outputDir, `/_aleph/`, util.trimPrefix(pathname, this.buildDir) + `.bundle.${util.shortHash(hash)}.js`)
189
- // await ensureDir(path.dirname(saveAs))
190
- // await Deno.copyFile(bundleFile, saveAs)
191
- // })
192
- // ])
175
+ return bundlingFile
193
176
}
194
177
195
178
/** create bundle chunk. */
@@ -203,7 +186,7 @@ export class Bundler {
203
186
`__ALEPH.pack[${ JSON . stringify ( url ) } ] = mod_${ i } `
204
187
]
205
188
} else {
206
- const [ jsFile ] = await this . compile ( mod , external )
189
+ const jsFile = await this . compile ( mod , external )
207
190
return [
208
191
`import * as mod_${ i } from ${ JSON . stringify ( 'file://' + jsFile ) } ` ,
209
192
`__ALEPH.pack[${ JSON . stringify ( url ) } ] = mod_${ i } `
@@ -213,13 +196,15 @@ export class Bundler {
213
196
return [ ]
214
197
} ) ) ) . flat ( ) . join ( '\n' )
215
198
const hash = computeHash ( entryCode + VERSION + Deno . version . deno )
199
+ const bundleFilename = `${ name } .bundle.${ hash . slice ( 0 , 8 ) } .js`
216
200
const bundleEntryFile = path . join ( this . #app. buildDir , `${ name } .bundle.entry.js` )
217
- const bundleFile = path . join ( this . #app. buildDir , ` ${ name } .bundle. ${ hash . slice ( 0 , 8 ) } .js` )
201
+ const bundleFile = path . join ( this . #app. buildDir , bundleFilename )
218
202
if ( ! existsFileSync ( bundleFile ) ) {
219
203
await Deno . writeTextFile ( bundleEntryFile , entryCode )
220
- await this . runDenoBundle ( bundleEntryFile , bundleFile )
204
+ await this . _bundle ( bundleEntryFile , bundleFile )
221
205
lazyRemove ( bundleEntryFile )
222
206
}
207
+ this . #bundledFiles. set ( name , bundleFilename )
223
208
log . info ( ` {} ${ name } ${ colors . dim ( '• ' + util . formatBytes ( Deno . statSync ( bundleFile ) . size ) ) } ` )
224
209
}
225
210
@@ -228,16 +213,18 @@ export class Bundler {
228
213
const alephPkgUri = getAlephPkgUri ( )
229
214
const { buildTarget } = this . #app. config
230
215
const hash = computeHash ( buildTarget + Deno . version . deno + VERSION )
231
- const bundleFile = path . join ( this . #app. buildDir , `polyfill.bundle.${ hash . slice ( 0 , 8 ) } .js` )
216
+ const bundleFilename = `polyfill.bundle.${ hash . slice ( 0 , 8 ) } .js`
217
+ const bundleFile = path . join ( this . #app. buildDir , bundleFilename )
232
218
if ( ! existsFileSync ( bundleFile ) ) {
233
219
const rawPolyfillFile = `${ alephPkgUri } /compiler/polyfills/${ buildTarget } /mod.ts`
234
- await this . runDenoBundle ( rawPolyfillFile , bundleFile )
220
+ await this . _bundle ( rawPolyfillFile , bundleFile )
235
221
}
222
+ this . #bundledFiles. set ( 'polyfill' , bundleFilename )
236
223
log . info ( ` {} polyfill (${ buildTarget . toUpperCase ( ) } ) ${ colors . dim ( '• ' + util . formatBytes ( Deno . statSync ( bundleFile ) . size ) ) } ` )
237
224
}
238
225
239
226
/** run deno bundle and compress the output using terser. */
240
- private async runDenoBundle ( bundleEntryFile : string , bundleFile : string ) {
227
+ private async _bundle ( bundleEntryFile : string , bundleFile : string ) {
241
228
// todo: use Deno.emit()
242
229
const p = Deno . run ( {
243
230
cmd : [ Deno . execPath ( ) , 'bundle' , '--no-check' , bundleEntryFile , bundleFile ] ,
@@ -282,10 +269,11 @@ export class Bundler {
282
269
}
283
270
284
271
async function minify ( code : string , ecma : ECMA = 5 ) {
285
- return ( await terser ( code , {
272
+ const ret = await terser ( code , {
286
273
compress : true ,
287
274
mangle : true ,
288
275
ecma,
289
276
sourceMap : false
290
- } ) ) . code
277
+ } )
278
+ return ret . code
291
279
}
0 commit comments