Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript-ify entrypoint scripts #428

Merged
merged 1 commit into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions Sources/CartonKit/Server/StaticArchive.swift

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Sources/carton-release/HashArchive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ struct HashArchive: AsyncParsableCommand {
try localFileSystem.createDirectory(dotFilesStaticPath, recursive: true)
var hashes: [(String, String)] = []
for entrypoint in ["dev", "bundle", "test", "testNode"] {
let tsFilename = "\(entrypoint).ts"
let filename = "\(entrypoint).js"
var arguments = [
"esbuild", "--bundle", "entrypoint/\(filename)", "--outfile=static/\(filename)",
"esbuild", "--bundle", "entrypoint/\(tsFilename)", "--outfile=static/\(filename)",
]

if entrypoint == "testNode" {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class SwiftRuntime {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaScriptKitの index.mjs があると想定されている場所に、index.d.ts を置いて型付けします。

cartonはJavaScriptKitをユーザが用意する事を想定しているため、
ここで利用するJavaScriptKitのバージョンがcartonに対して固定されません。

そのためここでの型定義は、他のコードと相互作用するために必要な最小限の定義にします。

setInstance(instance: WebAssembly.Instance): void;
readonly wasmImports: ImportedFunctions;
}
export type SwiftRuntimeConstructor = typeof SwiftRuntime;
export interface ImportedFunctions { }
6 changes: 4 additions & 2 deletions entrypoint/bundle.js → entrypoint/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
// limitations under the License.

import { WasmRunner } from "./common.js";
import type { SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaScriptKitはユーザが配布する想定で、実装はcarton側ではバンドルしません。
それを明確に表明するため import type を使います。


const startWasiTask = async () => {
// Fetch our Wasm File
const response = await fetch("REPLACE_THIS_WITH_THE_MAIN_WEBASSEMBLY_MODULE");
const responseArrayBuffer = await response.arrayBuffer();

let runtimeConstructor;
let runtimeConstructor: SwiftRuntimeConstructor | undefined = undefined;
try {
const { SwiftRuntime } = await import(
// @ts-ignore
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScriptが下記のパスを検証して、ファイルが存在しない事のエラーを出しますが、
コンパイル後はまさにこのパスをimportしたいので、エラーを潰しています。

なお、 .../Runtime.../Runtime/index であれば型チェックが通りますが、
そうするとコンパイル後が index.mjs にならないためダメでした。

"./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs"
);
runtimeConstructor = SwiftRuntime;
Expand All @@ -36,7 +38,7 @@ const startWasiTask = async () => {
await wasmRunner.run(wasmBytes);
};

function handleError(e) {
function handleError(e: any) {
console.error(e);
if (e instanceof WebAssembly.RuntimeError) {
console.log(e.stack);
Expand Down
50 changes: 35 additions & 15 deletions entrypoint/common.js → entrypoint/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,34 @@
import { WASI } from "@wasmer/wasi";
import { WasmFs } from "@wasmer/wasmfs";
import * as path from "path-browserify";
import type { SwiftRuntime, SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime";

export const WasmRunner = (rawOptions, SwiftRuntime) => {
const options = defaultRunnerOptions(rawOptions);
export type Options = {
args?: string[];
onStdout?: (text: string) => void;
onStderr?: (text: string) => void;
};

export type WasmRunner = {
run(wasmBytes: ArrayBufferLike, extraWasmImports?: WebAssembly.Imports): Promise<void>
};

export const WasmRunner = (rawOptions: Options | false, SwiftRuntime: SwiftRuntimeConstructor | undefined): WasmRunner => {
const options: Options = defaultRunnerOptions(rawOptions);

let swift;
let swift: SwiftRuntime;
if (SwiftRuntime) {
swift = new SwiftRuntime();
}

const wasmFs = createWasmFS(
(stdout) => {
console.log(stdout);
options.onStdout(stdout);
options.onStdout?.call(undefined, stdout);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onStdout が設定されてない場合はスキップして動くようにしました。

},
(stderr) => {
console.error(stderr);
options.onStderr(stderr);
options.onStderr?.call(undefined, stderr);
}
);

Expand All @@ -50,13 +61,16 @@ export const WasmRunner = (rawOptions, SwiftRuntime) => {
},
});

const createWasmImportObject = (extraWasmImports, wasmModule) => {
const importObject = {
const createWasmImportObject = (
extraWasmImports: WebAssembly.Imports,
wasmModule: WebAssembly.Module
): WebAssembly.Imports => {
const importObject: WebAssembly.Imports = {
wasi_snapshot_preview1: wrapWASI(wasi, wasmModule),
};

if (swift) {
importObject.javascript_kit = swift.wasmImports;
importObject.javascript_kit = swift.wasmImports as unknown as WebAssembly.ModuleImports;
}

if (extraWasmImports) {
Expand All @@ -70,7 +84,7 @@ export const WasmRunner = (rawOptions, SwiftRuntime) => {
};

return {
async run(wasmBytes, extraWasmImports) {
async run(wasmBytes: ArrayBufferLike, extraWasmImports?: WebAssembly.Imports) {
if (!extraWasmImports) {
extraWasmImports = {};
}
Expand All @@ -91,7 +105,7 @@ export const WasmRunner = (rawOptions, SwiftRuntime) => {
wasi.start(instance);

// Initialize and start Reactor
if (instance.exports._initialize) {
if (typeof instance.exports._initialize == "function") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ifの先で関数呼び出しをするために型チェックが必要です。

instance.exports._initialize();
if (typeof instance.exports.main === "function") {
instance.exports.main();
Expand All @@ -104,7 +118,7 @@ export const WasmRunner = (rawOptions, SwiftRuntime) => {
};
};

const defaultRunnerOptions = (options) => {
const defaultRunnerOptions = (options: Options | false): Options => {
if (!options) return defaultRunnerOptions({});
if (!options.onStdout) {
options.onStdout = () => { };
Expand All @@ -118,13 +132,19 @@ const defaultRunnerOptions = (options) => {
return options;
};

const createWasmFS = (onStdout, onStderr) => {
const createWasmFS = (
onStdout: (text: string) => void,
onStderr: (text: string) => void
): WasmFs => {
// Instantiate a new WASI Instance
const wasmFs = new WasmFs();

// Output stdout and stderr to console
const originalWriteSync = wasmFs.fs.writeSync;
wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => {
(wasmFs.fs as any).writeSync = (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メソッドのインターフェースであり、差し替えられるようになってないので、any を経由します。

fd: number, buffer: Buffer | Uint8Array,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここの Buffer を書くために nodejs の型が必要でした。

offset?: number, length?: number, position?: number
): number => {
const text = new TextDecoder("utf-8").decode(buffer);
if (text !== "\n") {
switch (fd) {
Expand All @@ -142,7 +162,7 @@ const createWasmFS = (onStdout, onStderr) => {
return wasmFs;
};

const wrapWASI = (wasiObject, wasmModule) => {
const wrapWASI = (wasiObject: WASI, wasmModule: WebAssembly.Module): WebAssembly.ModuleImports => {
// PATCH: @wasmer-js/wasi@0.x forgets to call `refreshMemory` in `clock_res_get`,
// which writes its result to memory view. Without the refresh the memory view,
// it accesses a detached array buffer if the memory is grown by malloc.
Expand All @@ -154,7 +174,7 @@ const wrapWASI = (wasiObject, wasmModule) => {
// Reference: https://github.com/wasmerio/wasmer-js/blob/55fa8c17c56348c312a8bd23c69054b1aa633891/packages/wasi/src/index.ts#L557
const original_clock_res_get = wasiObject.wasiImport["clock_res_get"];

wasiObject.wasiImport["clock_res_get"] = (clockId, resolution) => {
wasiObject.wasiImport["clock_res_get"] = (clockId: number, resolution: number) => {
wasiObject.refreshMemory();
return original_clock_res_get(clockId, resolution);
};
Expand Down
8 changes: 5 additions & 3 deletions entrypoint/dev.js → entrypoint/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
// limitations under the License.

import ReconnectingWebSocket from "reconnecting-websocket";
import { WasmRunner } from "./common.js";
import { WasmRunner } from "./common";
import type { SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime";

const socket = new ReconnectingWebSocket(`ws://${location.host}/watcher`);

Expand All @@ -28,9 +29,10 @@ const startWasiTask = async () => {
const response = await fetch("/main.wasm");
const responseArrayBuffer = await response.arrayBuffer();

let runtimeConstructor;
let runtimeConstructor: SwiftRuntimeConstructor | undefined = undefined;
try {
const { SwiftRuntime } = await import(
// @ts-ignore
"./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs"
);
runtimeConstructor = SwiftRuntime;
Expand Down Expand Up @@ -62,7 +64,7 @@ const startWasiTask = async () => {
await wasmRunner.run(wasmBytes);
};

function handleError(e) {
function handleError(e: any) {
console.error(e);
if (e instanceof WebAssembly.RuntimeError) {
console.log(e.stack);
Expand Down
8 changes: 5 additions & 3 deletions entrypoint/test.js → entrypoint/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import ReconnectingWebSocket from "reconnecting-websocket";
import { WASIExitError } from "@wasmer/wasi";
import { WasmRunner } from "./common.js";
import type { SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime";

const socket = new ReconnectingWebSocket(`ws://${location.host}/watcher`);
socket.addEventListener("message", (message) => {
Expand All @@ -28,9 +29,10 @@ const startWasiTask = async () => {
const response = await fetch("/main.wasm");
const responseArrayBuffer = await response.arrayBuffer();

let runtimeConstructor;
let runtimeConstructor: SwiftRuntimeConstructor | undefined = undefined;
try {
const { SwiftRuntime } = await import(
// @ts-ignore
"./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs"
);
runtimeConstructor = SwiftRuntime;
Expand Down Expand Up @@ -69,7 +71,7 @@ const startWasiTask = async () => {
// 5. Crash by throwing JS exception synchronously
// 6. Crash by throwing JS exception asynchronously

const handleExitOrError = (error) => {
const handleExitOrError = (error: any) => {
// XCTest always calls `exit` at the end when no crash
if (error instanceof WASIExitError) {
// pass the output to the server in any case
Expand Down Expand Up @@ -105,7 +107,7 @@ const startWasiTask = async () => {
// reachable here without catch (case 3, 4, 6)
};

function handleError(e) {
function handleError(e: any) {
console.error(e);
if (e instanceof WebAssembly.RuntimeError) {
console.log(e.stack);
Expand Down
4 changes: 3 additions & 1 deletion entrypoint/testNode.js → entrypoint/testNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import fs from "fs/promises";
import { WasmRunner } from "./common.js";
import type { SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime";

const args = [...process.argv];
args.shift();
Expand All @@ -27,9 +28,10 @@ if (!wasmFile) {
const startWasiTask = async () => {
const wasmBytes = await fs.readFile(wasmFile);

let runtimeConstructor;
let runtimeConstructor: SwiftRuntimeConstructor | undefined = undefined;
try {
const { SwiftRuntime } = await import(
// @ts-ignore
"./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs"
);

Expand Down
66 changes: 65 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
},
"homepage": "https://github.com/swiftwasm/carton#readme",
"devDependencies": {
"@types/node": "^20.12.7",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Buffer を書くために必要です

"@types/path-browserify": "^1.0.2",
"@wasmer/wasi": "^0.12.0",
"@wasmer/wasmfs": "^0.12.0",
"path-browserify": "^1.0.1",
"esbuild": "^0.14.38",
"npm-run-all": "^4.1.5",
"reconnecting-websocket": "^4.4.0"
"path-browserify": "^1.0.1",
"reconnecting-websocket": "^4.4.0",
"typescript": "^5.4.5"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ローカルで $ npx tsc すると型チェックできます。

}
}
Loading
Loading