Skip to content

Commit 1c993c0

Browse files
committed
feat: support more types in wasmify and wasi logging
closes #159
1 parent 7ff93a7 commit 1c993c0

File tree

13 files changed

+596
-151
lines changed

13 files changed

+596
-151
lines changed

packages/homestar/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,17 @@
6464
"dependencies": {
6565
"@bytecodealliance/componentize-js": "^0.7.0",
6666
"@fission-codes/channel": "workspace:^",
67+
"@fission-codes/homestar-wit": "workspace:^",
6768
"@ipld/dag-cbor": "^9.2.0",
6869
"@ipld/dag-json": "^10.2.0",
6970
"@multiformats/sha3": "^3.0.2",
7071
"@ts-ast-parser/core": "^0.7.0",
72+
"del": "^7.1.0",
7173
"emittery": "^1.0.3",
7274
"esbuild": "^0.20.1",
75+
"esbuild-plugin-replace-regex": "^0.0.2",
7376
"get-tsconfig": "^4.7.2",
77+
"just-kebab-case": "^4.2.0",
7478
"multiformats": "^13.1.0",
7579
"object-path": "^0.11.8",
7680
"zod": "^3.22.4"

packages/homestar/src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,10 @@ export type HomestarService = Service<
5959
result: Opaque<number[], Schemas.EventNotification>
6060
}
6161
>
62+
63+
export interface WitOptions {
64+
filePath?: string
65+
source?: string
66+
worldName: string
67+
wasiImports?: Set<string>
68+
}
Lines changed: 74 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,104 @@
11
import { writeFile } from 'node:fs/promises'
22
import path from 'path'
3-
import { parseFromFiles } from '@ts-ast-parser/core'
3+
import { createRequire } from 'node:module'
4+
import { base32 } from 'iso-base/rfc4648'
45
import * as esbuild from 'esbuild'
5-
import { getTsconfig } from 'get-tsconfig'
6+
import { deleteAsync } from 'del'
67

78
// @ts-ignore
8-
import { componentize } from '@bytecodealliance/componentize-js'
9-
10-
/**
11-
* @param {import('@ts-ast-parser/core').Type} type
12-
* @returns {string}
13-
*/
14-
function primitiveType(type) {
15-
if (type.text === 'string') {
16-
return 'string'
17-
}
18-
19-
if (type.text === 'boolean') {
20-
return 'bool'
21-
}
22-
23-
if (type.text === 'number') {
24-
return 's64'
25-
}
26-
27-
if (type.text === 'Uint8Array') {
28-
return 'list<u8>'
29-
}
9+
import replacePlugin from 'esbuild-plugin-replace-regex'
3010

31-
if (type.kind === 'Array' && type.elementType) {
32-
return `list<${primitiveType(type.elementType)}>`
33-
}
11+
// @ts-ignore
12+
import { componentize } from '@bytecodealliance/componentize-js'
13+
import { wit } from './wit.js'
3414

35-
throw new Error(`Unknown type: ${JSON.stringify(type)}`)
36-
}
15+
const require = createRequire(import.meta.url)
3716

3817
/**
39-
*
40-
* Generate a WIT file from a TypeScript file
18+
* Bundle the js code with WASI support
4119
*
4220
* @param {string} filePath - Path to a TypeScript file
4321
*/
44-
async function wit(filePath) {
45-
const cfg = getTsconfig(filePath)
46-
if (!cfg) {
47-
throw new Error('No tsconfig found')
48-
}
49-
50-
const { project, errors } = await parseFromFiles([filePath], {
51-
tsConfigFilePath: cfg.path,
52-
})
53-
54-
if (errors.length > 0) {
55-
console.error(errors)
56-
// Handle the errors
57-
58-
// process.exit(1)
59-
}
60-
61-
const result = project?.getModules().map((m) => m.serialize()) ?? []
62-
if (result.length > 0) {
63-
// console.log(
64-
// '🚀 ~ file: cli.js:23 ~ reflectedModules:',
65-
// JSON.stringify(result, null, 2)
66-
// )
67-
const { sourcePath, declarations } = result[0]
68-
const world = path.basename(sourcePath).replace('.ts', '')
69-
const exports = declarations.map((d) => {
70-
if (d.kind === 'Function') {
71-
/** @type {string[]} */
72-
const params = d.signatures[0].parameters
73-
? d.signatures[0].parameters.map(
74-
(p) => `${p.name}: ${primitiveType(p.type)}`
75-
)
76-
: []
77-
const name = d.name
78-
const returnType = primitiveType(d.signatures[0].return.type)
79-
80-
return ` export ${name}: func(${params.join(', ')}) -> ${returnType};`
81-
}
82-
83-
return ''
84-
})
85-
86-
const wit = `
87-
package local:${world};
88-
89-
world ${world} {
90-
${exports.join('\n')}
91-
}
92-
`
93-
// console.log('🚀 ~ WIT World\n\n', wit)
94-
return wit
95-
} else {
96-
throw new Error('No modules found')
22+
async function bundle(filePath) {
23+
const wasiImports = new Set()
24+
/** @type {import('esbuild').Plugin} */
25+
const wasiPlugin = {
26+
name: 'wasi imports',
27+
setup(build) {
28+
// @ts-ignore
29+
build.onResolve({ filter: /^wasi:.*/ }, (args) => {
30+
wasiImports.add(`import ${args.path};`)
31+
})
32+
},
9733
}
98-
}
9934

100-
/**
101-
* @param {string} filePath - Path to a TypeScript file
102-
*/
103-
async function bundle(filePath) {
10435
const result = await esbuild.build({
10536
entryPoints: [filePath],
10637
bundle: true,
10738
format: 'esm',
10839
platform: 'browser',
10940
write: false,
41+
plugins: [
42+
replacePlugin({
43+
filter: /homestar-wit\/src\/logging.js$/,
44+
patterns: [
45+
['let wasiLog', '// let wasiLog'],
46+
[
47+
"// import { log as wasiLog } from 'wasi:logging/logging'",
48+
"import { log as wasiLog } from 'wasi:logging/logging'",
49+
],
50+
],
51+
}),
52+
wasiPlugin,
53+
],
54+
external: ['wasi:*'],
11055
})
111-
// console.log('🚀 ~ bundle ~ result:', result.outputFiles[0].hash)
112-
return result.outputFiles[0].text
56+
return {
57+
src: result.outputFiles[0].text,
58+
hash: result.outputFiles[0].hash,
59+
wasiImports,
60+
}
11361
}
11462

11563
/**
64+
* Generate wasm component from a TypeScript file
65+
*
11666
* @param {string} filePath - Path to a TypeScript file
11767
* @param {string} outDir - Path to a directory to write the Wasm component file
11868
*/
11969
export async function build(filePath, outDir = process.cwd()) {
120-
const outName = path
121-
.basename(filePath)
122-
.replace(path.extname(filePath), '.wasm')
123-
const outPath = path.join(outDir, outName)
124-
125-
const { component } = await componentize(
126-
await bundle(filePath),
127-
await wit(filePath)
128-
)
129-
await writeFile(outPath, component)
130-
131-
return {
132-
outPath,
70+
const pkgPath = require.resolve('@fission-codes/homestar-wit')
71+
const witPath = path.join(pkgPath, '..', '..', 'wit')
72+
let witHash
73+
74+
// Clean up any old WIT files in the wit directory
75+
// componentize process.exit(1) if it fails so we can't clean up after it
76+
await deleteAsync([`${witPath}/*.wit`], { force: true })
77+
78+
try {
79+
const { hash, src, wasiImports } = await bundle(filePath)
80+
witHash = base32.encode(hash).toLowerCase()
81+
82+
// TODO: check the wit hash and only componentize if it has changed
83+
const witSource = await wit({ filePath, worldName: witHash, wasiImports })
84+
const witFile = path.join(witPath, `${witHash}.wit`)
85+
await writeFile(witFile, witSource, 'utf8')
86+
const { component } = await componentize(src, {
87+
witPath,
88+
worldName: witHash,
89+
// debug: true,
90+
sourceName: filePath,
91+
})
92+
const outPath = path.join(outDir, `${witHash}.wasm`)
93+
await writeFile(outPath, component)
94+
95+
return {
96+
outPath,
97+
witHash,
98+
}
99+
} finally {
100+
if (witHash) {
101+
await deleteAsync([path.join(witPath, `${witHash}.wit`)], { force: true })
102+
}
133103
}
134104
}

0 commit comments

Comments
 (0)