Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 454571e

Browse files
author
ije
committed
refactor(server): [wip] rewrite bundler
1 parent 80d43fc commit 454571e

File tree

2 files changed

+91
-93
lines changed

2 files changed

+91
-93
lines changed

server/app.ts

Lines changed: 52 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ImportMap, SourceType, TransformOptions } from '../compiler/mod.ts'
2-
import { buildChecksum, transform } from '../compiler/mod.ts'
1+
import { buildChecksum, ImportMap, SourceType, transform, TransformOptions } from '../compiler/mod.ts'
32
import { colors, createHash, ensureDir, path, walk } from '../deps.ts'
43
import { EventEmitter } from '../framework/core/events.ts'
54
import type { RouteModule } from '../framework/core/routing.ts'
@@ -18,10 +17,13 @@ import log from '../shared/log.ts'
1817
import util from '../shared/util.ts'
1918
import type {
2019
Config,
21-
LoaderPlugin,
20+
21+
22+
23+
DependencyDescriptor, LoaderPlugin,
2224
LoaderTransformResult,
2325
Module,
24-
DependencyDescriptor,
26+
2527
RouterURL,
2628
ServerApplication,
2729
TransformFn
@@ -33,8 +35,9 @@ import {
3335
computeHash,
3436
formatBytesWithColor,
3537
getAlephPkgUri,
36-
getRelativePath,
37-
getDenoDir,
38+
39+
getDenoDir, getRelativePath,
40+
3841
isLoaderPlugin,
3942
reFullVersion,
4043
toLocalUrl,
@@ -432,26 +435,6 @@ export class Application implements ServerApplication {
432435
return [status, html]
433436
}
434437

435-
getSSRHTMLScripts() {
436-
const { baseUrl } = this.config
437-
438-
if (this.isDev) {
439-
return [
440-
{ src: util.cleanPath(`${baseUrl}/_aleph/main.js`), type: 'module' },
441-
{ src: util.cleanPath(`${baseUrl}/_aleph/-/deno.land/x/aleph/nomodule.js`), nomodule: true },
442-
]
443-
}
444-
445-
return [
446-
bundlerRuntimeCode,
447-
...['polyfill', 'deps', 'shared', 'main']
448-
.filter(name => this.#bundler.getBundledFile(name) !== null)
449-
.map(name => ({
450-
src: util.cleanPath(`${baseUrl}/_aleph/${this.#bundler.getBundledFile(name)}`)
451-
}))
452-
]
453-
}
454-
455438
createFSWatcher(): EventEmitter {
456439
const e = new EventEmitter()
457440
this.#fsWatchListeners.push(e)
@@ -550,6 +533,27 @@ export class Application implements ServerApplication {
550533
return code
551534
}
552535

536+
/** get ssr html scripts */
537+
getSSRHTMLScripts() {
538+
const { baseUrl } = this.config
539+
540+
if (this.isDev) {
541+
return [
542+
{ src: util.cleanPath(`${baseUrl}/_aleph/main.js`), type: 'module' },
543+
{ src: util.cleanPath(`${baseUrl}/_aleph/-/deno.land/x/aleph/nomodule.js`), nomodule: true },
544+
]
545+
}
546+
547+
return [
548+
bundlerRuntimeCode,
549+
...['polyfill', 'deps', 'shared', 'main']
550+
.filter(name => this.#bundler.getBundledFile(name) !== null)
551+
.map(name => ({
552+
src: util.cleanPath(`${baseUrl}/_aleph/${this.#bundler.getBundledFile(name)}`)
553+
}))
554+
]
555+
}
556+
553557
/** fetch module content */
554558
async fetchModule(url: string): Promise<{ content: Uint8Array, contentType: string | null }> {
555559
if (!util.isLikelyHttpURL(url)) {
@@ -1015,7 +1019,7 @@ export class Application implements ServerApplication {
10151019
private async bundle() {
10161020
const sharedEntryMods = new Set<string>()
10171021
const entryMods = new Map<string[], boolean>()
1018-
const refCounter = new Map<string, Set<string>>()
1022+
const refCounter = new Set<string>()
10191023
const concatAllEntries = () => [
10201024
Array.from(entryMods.entries()).map(([urls, shared]) => urls.map(url => ({ url, shared }))),
10211025
Array.from(sharedEntryMods).map(url => ({ url, shared: true })),
@@ -1027,55 +1031,41 @@ export class Application implements ServerApplication {
10271031
true
10281032
)
10291033

1034+
// add app/404 modules as shared entry
10301035
entryMods.set(Array.from(this.#modules.keys()).filter(url => ['/app', '/404'].includes(trimModuleExt(url))), true)
10311036

1037+
// add page module entries
1038+
this.#pageRouting.lookup(routes => {
1039+
routes.forEach(({ module: { url } }) => entryMods.set([url], false))
1040+
})
1041+
1042+
// add dynamic imported module as entry
10321043
this.#modules.forEach(mod => {
10331044
mod.deps.forEach(({ url, isDynamic }) => {
10341045
if (isDynamic) {
1035-
// add dynamic imported module as entry
10361046
entryMods.set([url], false)
10371047
}
10381048
return url
10391049
})
1040-
mod.deps.forEach(({ url }) => {
1041-
if (!url.startsWith('#')) {
1042-
if (refCounter.has(url)) {
1043-
refCounter.get(url)!.add(mod.url)
1044-
} else {
1045-
refCounter.set(url, new Set([mod.url]))
1046-
}
1047-
}
1048-
})
10491050
})
10501051

1051-
// add page module entries
1052-
this.#pageRouting.lookup(routes => {
1053-
routes.forEach(({ module: { url } }) => entryMods.set([url], false))
1054-
})
1055-
1056-
refCounter.forEach((refers, url) => {
1057-
if (refers.size > 1) {
1058-
let shared = 0
1059-
for (const mods of entryMods.keys()) {
1060-
const some = mods.some(u => {
1061-
let scoped = false
1062-
this.lookupDeps(u, dep => {
1063-
if (!dep.isDynamic && refers.has(dep.url)) {
1064-
scoped = true
1065-
return false
1066-
}
1067-
})
1068-
return scoped
1069-
})
1070-
if (some) {
1071-
shared++
1052+
for (const mods of entryMods.keys()) {
1053+
const deps = new Set<string>()
1054+
mods.forEach(url => {
1055+
this.lookupDeps(url, dep => {
1056+
if (!dep.isDynamic) {
1057+
deps.add(dep.url)
10721058
}
1073-
}
1074-
if (shared > 1) {
1059+
})
1060+
})
1061+
deps.forEach(url => {
1062+
if (refCounter.has(url)) {
10751063
sharedEntryMods.add(url)
1064+
} else {
1065+
refCounter.add(url)
10761066
}
1077-
}
1078-
})
1067+
})
1068+
}
10791069

10801070
log.info('- bundle')
10811071
await this.#bundler.bundle(concatAllEntries())

server/bundler.ts

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { minify as terser, ECMA } from 'https://esm.sh/[email protected]'
1+
import { ECMA, minify as terser } from 'https://esm.sh/[email protected]'
22
import { transform } from '../compiler/mod.ts'
33
import { colors, ensureDir, path } from '../deps.ts'
44
import { defaultReactVersion } from '../shared/constants.ts'
@@ -20,6 +20,7 @@ export const bundlerRuntimeCode = `
2020
window.__ALEPH = {
2121
baseURL: '/',
2222
pack: {},
23+
bundledFiles: {},
2324
import: function(src, specifier) {
2425
var pack = this.pack
2526
return new Promise(function(resolve, reject) {
@@ -86,31 +87,22 @@ export class Bundler {
8687
}
8788
for (const url of entries) {
8889
await this.createBundleChunk(
89-
trimModuleExt(url),
90+
util.trimPrefix(trimModuleExt(url), '/'),
9091
[url],
9192
[remoteEntries, sharedEntries].flat()
9293
)
9394
}
95+
await this.createMainJS()
9496
}
9597

9698
getBundledFile(name: string): string | null {
9799
return this.#bundledFiles.get(name) || null
98100
}
99101

100102
async copyDist() {
101-
await Promise.all([
102-
...Array.from(this.#bundledFiles.values()).map(jsFile => this.copyBundleFile(jsFile)),
103-
this.copyMainJS(),
104-
])
105-
}
106-
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)
103+
await Promise.all(
104+
Array.from(this.#bundledFiles.keys()).map(jsFile => this.copyBundleFile(jsFile))
105+
)
114106
}
115107

116108
private async copyBundleFile(jsFilename: string) {
@@ -175,6 +167,37 @@ export class Bundler {
175167
return bundlingFile
176168
}
177169

170+
private async createMainJS() {
171+
const bundledFiles = Array.from(this.#bundledFiles.entries())
172+
.filter(([name]) => !['polyfill', 'deps', 'shared'].includes(name))
173+
.reduce((r, [name, filename]) => {
174+
r[name] = filename
175+
return r
176+
}, {} as Record<string, string>)
177+
const mainJS = `__ALEPH.bundledFiles=${JSON.stringify(bundledFiles)};` + this.#app.getMainJS(true)
178+
const hash = computeHash(mainJS)
179+
const bundleFilename = `main.bundle.${hash.slice(0, 8)}.js`
180+
const bundleFile = path.join(this.#app.buildDir, bundleFilename)
181+
await Deno.writeTextFile(bundleFile, mainJS)
182+
this.#bundledFiles.set('main', bundleFilename)
183+
log.info(` {} main.js ${colors.dim('• ' + util.formatBytes(mainJS.length))}`)
184+
}
185+
186+
/** create polyfill bundle. */
187+
private async createPolyfillBundle() {
188+
const alephPkgUri = getAlephPkgUri()
189+
const { buildTarget } = this.#app.config
190+
const hash = computeHash(buildTarget + Deno.version.deno + VERSION)
191+
const bundleFilename = `polyfill.bundle.${hash.slice(0, 8)}.js`
192+
const bundleFile = path.join(this.#app.buildDir, bundleFilename)
193+
if (!existsFileSync(bundleFile)) {
194+
const rawPolyfillFile = `${alephPkgUri}/compiler/polyfills/${buildTarget}/mod.ts`
195+
await this._bundle(rawPolyfillFile, bundleFile)
196+
}
197+
this.#bundledFiles.set('polyfill', bundleFilename)
198+
log.info(` {} polyfill.js (${buildTarget.toUpperCase()}) ${colors.dim('• ' + util.formatBytes(Deno.statSync(bundleFile).size))}`)
199+
}
200+
178201
/** create bundle chunk. */
179202
private async createBundleChunk(name: string, entry: string[], external: string[]) {
180203
const entryCode = (await Promise.all(entry.map(async (url, i) => {
@@ -205,22 +228,7 @@ export class Bundler {
205228
lazyRemove(bundleEntryFile)
206229
}
207230
this.#bundledFiles.set(name, bundleFilename)
208-
log.info(` {} ${name} ${colors.dim('• ' + util.formatBytes(Deno.statSync(bundleFile).size))}`)
209-
}
210-
211-
/** create polyfill bundle. */
212-
private async createPolyfillBundle() {
213-
const alephPkgUri = getAlephPkgUri()
214-
const { buildTarget } = this.#app.config
215-
const hash = computeHash(buildTarget + Deno.version.deno + VERSION)
216-
const bundleFilename = `polyfill.bundle.${hash.slice(0, 8)}.js`
217-
const bundleFile = path.join(this.#app.buildDir, bundleFilename)
218-
if (!existsFileSync(bundleFile)) {
219-
const rawPolyfillFile = `${alephPkgUri}/compiler/polyfills/${buildTarget}/mod.ts`
220-
await this._bundle(rawPolyfillFile, bundleFile)
221-
}
222-
this.#bundledFiles.set('polyfill', bundleFilename)
223-
log.info(` {} polyfill (${buildTarget.toUpperCase()}) ${colors.dim('• ' + util.formatBytes(Deno.statSync(bundleFile).size))}`)
231+
log.info(` {} ${name}.js ${colors.dim('• ' + util.formatBytes(Deno.statSync(bundleFile).size))}`)
224232
}
225233

226234
/** run deno bundle and compress the output using terser. */

0 commit comments

Comments
 (0)