1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 |
x1
x3
x3
x3
x3
x3
x3
x3
x3
x1
x3
x3
x18
x6
x6
x42
x6
x6
x8
x8
x56
x8
x8
x32
x8
x8
x8
x6
x6
x60
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x32
x8
x40
x8
x6
x1
x3
x3
x5
x5
x5
x5
x5
x40
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x5
x15
x15
x21
x21
x21
x15
x17
x17
x17
x17
x17
x17
x68
x17
x17
x15
x5
x5
x5
x5
x25
x5
x5
x5 |
I
I
I
I
|
import { encodeBase64 } from "@std/encoding/base64"
import { bundle as bundle_ts } from "../ts/bundle.ts"
import { assert } from "@std/assert"
import { UntarStream } from "@std/tar/untar-stream"
import { ensureFile } from "@std/fs"
import { basename, dirname, resolve, toFileUrl } from "@std/path"
import { Logger } from "@libs/logger"
import type { record } from "@libs/typing"
import { command } from "@libs/run/command"
export type { record }
export async function bundle(project: string, { bin = "wasm-pack", autoinstall = false, banner, logger: log = new Logger(), env = {} } = {} as { bin?: string; autoinstall?: boolean; banner?: string; logger?: Logger; env?: record<string> }): Promise<void> {
log = log.with({ project })
try {
await command("cargo", ["--version"], { logger: log, env, throw: true })
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
log.error("cargo binary not found in PATH, is it installed?")
log.error("note that even with `autoinstall` option enabled, cargo must be installed manually")
log.error("https://doc.rust-lang.org/cargo/getting-started/installation.html")
throw error
}
}
if (autoinstall) {
log.trace(`checking if ${bin} is installed`)
try {
await command(bin, ["--version"], { logger: log, env, throw: true })
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
log.wdebug(`${bin} not found, installing`)
bin = await install({ log, path: dirname(bin) })
}
}
}
log.debug("building wasm")
const { success, stderr } = await command(bin, ["build", "--release", "--target", "web"], { logger: log, cwd: project, env })
assert(success, `wasm build failed: ${stderr}`)
log.ok("built wasm")
log.debug("injecting base64 wasm to js")
const name = basename(project)
const wasm = await fetch(toFileUrl(resolve(project, `pkg/${name}_bg.wasm`))).then((response) => response.arrayBuffer())
let js = await fetch(toFileUrl(resolve(project, `pkg/${name}.js`))).then((response) => response.text())
js = js.replace(`'${name}_bg.wasm'`, "`data:application/wasm;base64,${source('base64')}`")
js += `export function source(format) {
const b64 = '${encodeBase64(wasm)}'
return format === 'base64' ? b64 : new Uint8Array(Array.from(atob(b64), c => c.charCodeAt(0))).buffer
}`
await Deno.writeTextFile(resolve(project, `./${name}.js`), js)
log.ok("injecting base64 wasm to js")
log.debug("minifying js")
const minified = await bundle_ts(new URL(toFileUrl(resolve(project, `./${name}.js`))), { minify: "terser", banner })
await Deno.writeTextFile(resolve(project, `./${name}.js`), minified)
log.with({ size: `${new Blob([minified]).size}b` }).log()
log.ok("minified js")
}
async function install({ log, path }: { log: Logger; path: string }) {
log.debug("looking for latest release of wasm-pack")
const { tag_name, assets: assets } = await fetch("https://api.github.com/repos/rustwasm/wasm-pack/releases/latest").then((response) => response.json())
log.info(`found version ${tag_name}`)
const packaged = assets
.map(({ name, browser_download_url: url }: { name: string; browser_download_url: string }) => ({ name, url }))
.find(({ name }: { name: string }) => name.includes(Deno.build.os) && name.includes(Deno.build.arch))
assert(packaged, `cannot find suitable release for ${Deno.build.os}-${Deno.build.arch}`)
log.info(`found binary ${packaged.name} for ${Deno.build.os}-${Deno.build.arch}`)
log.debug("downloading release")
const response = await fetch(packaged.url)
log.ok("downloaded release")
log.debug("extracting release")
const entries = response.body!
.pipeThrough(new DecompressionStream("gzip"))
.pipeThrough(new UntarStream())
let found = false
for await (const entry of entries) {
const filename = basename(entry.path)
if (!filename.startsWith("wasm-pack")) {
await entry.readable?.cancel()
continue
}
if (entry.readable === undefined) {
continue
}
found = true
path = resolve(path, filename)
log.trace(`extracted ${path}`)
await ensureFile(path)
using file = await Deno.open(path, { write: true, truncate: true })
await Deno.chmod(path, 0o755).catch(() => null)
await entry.readable.pipeTo(file.writable)
break
}
assert(found, "wasm-pack binary not found in archive")
log.ok("extracted release")
log.debug("checking installation")
const { success } = await command(path, ["--version"], { logger: log })
assert(success, "wasm-pack could not be executed")
return path
}
|