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 |
x1
x2
x2
x2
x2
x2
x2
x2
x2
x2
x1
x2
x2
x15
x5
x5
x35
x5
x5
x7
x7
x49
x7
x7
x28
x7
x7
x7
x5
x5
x50
x7
x7
x7
x7
x7
x7
x7
x7
x7
x7
x7
x7
x7
x7
x7
x28
x7
x35
x7
x5
x1
x2
x2
x4
x4
x4
x4
x4
x36
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x14
x14
x20
x20
x14
x16
x16
x16
x16
x16
x16
x64
x16
x16
x14
x4
x4
x4
x4
x20
x4
x4
x4 |
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 { Untar } from "@std/archive/untar"
import { copy, readerFromStreamReader } from "@std/io"
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 reader = readerFromStreamReader(await fetch(packaged.url).then((response) => response.body!.pipeThrough(new DecompressionStream("gzip")).getReader()))
log.ok("downloaded release")
log.debug("extracting release")
const untar = new Untar(reader)
let found = false
for await (const entry of untar) {
const filename = basename(entry.fileName)
if (!filename.startsWith("wasm-pack")) {
continue
}
if (entry.type !== "file") {
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 copy(entry, file)
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
}
|