Skip to content
This repository was archived by the owner on Nov 29, 2025. It is now read-only.
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
6 changes: 3 additions & 3 deletions Sources/CartonHelpers/StaticArchive.swift

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Sources/carton-frontend-slim/BundleLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct BundleLayout {
<body>
<script type="module">
import { testBrowser } from "./index.js";
testBrowser([], true);
testBrowser([], {}, true);
</script>
</body>

Expand Down Expand Up @@ -209,8 +209,8 @@ struct BundleLayout {
return internalInstantiate(options, imports);
}

export async function testBrowser(args, inPage = false) {
await internalTestBrowser(instantiate, wasmFileName, args, import.meta.url, inPage);
export async function testBrowser(args, options, inPage = false) {
await internalTestBrowser(instantiate, wasmFileName, args, import.meta.url, options, inPage);
}

export async function testNode(args) {
Expand Down
3 changes: 2 additions & 1 deletion Sources/carton-release/HashArchive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ struct HashArchive: AsyncParsableCommand {
let arguments = [
"esbuild", "--bundle", "entrypoint/\(tsFilename)", "--outfile=static/\(filename)",
"--external:node:url", "--external:node:path",
"--external:node:module", "--external:node:fs/promises",
"--external:node:module", "--external:node:http",
"--external:node:fs/promises", "--external:node:fs",
"--external:playwright",
"--format=esm",
"--external:./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export class SwiftRuntime {
constructor({ sharedMemory: booleam });
setInstance(instance: WebAssembly.Instance): void;
main?(): void;
readonly wasmImports: ImportedFunctions;
Expand Down
71 changes: 55 additions & 16 deletions entrypoint/intrinsics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { WASI, File, OpenFile, ConsoleStdout, PreopenDirectory, WASIProcExit, In
import type { SwiftRuntime, SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime/index";
import { polyfill as polyfillWebAssemblyTypeReflection } from "wasm-imports-parser/polyfill";
import type { ImportEntry } from "wasm-imports-parser";
import { AddressInfo } from "node:net";

// Apply polyfill for WebAssembly Type Reflection JS API to inspect imported memory info.
// https://github.com/WebAssembly/js-types/blob/main/proposals/js-types/Overview.md
Expand Down Expand Up @@ -65,7 +66,14 @@ export async function instantiate(rawOptions: InstantiationOptions, extraWasmImp

let swift: SwiftRuntime | undefined = options.swift;
if (!swift && options.SwiftRuntime) {
swift = new options.SwiftRuntime();
let sharedMemory = false;
for (const importEntry of WebAssembly.Module.imports(options.module)) {
if (importEntry.module === "env" && importEntry.name === "memory" && importEntry.kind === "memory") {
sharedMemory = true;
break;
}
}
swift = new options.SwiftRuntime({ sharedMemory });
}

let stdoutLine: LineDecoder | undefined = undefined;
Expand Down Expand Up @@ -242,10 +250,54 @@ async function extractAndSaveFile(rootFs: Map<string, Inode>, path: string): Pro
return false;
}

export async function testBrowser(instantiate: Instantiate, wasmFileName: string, args: string[], indexJsUrl: string, inPage: boolean) {
export async function testBrowser(
instantiate: Instantiate,
wasmFileName: string,
args: string[],
indexJsUrl: string,
options: { contentTypes?: (fileName: string) => string } = {},
inPage: boolean = false
) {
if (inPage) {
return await testBrowserInPage(instantiate, wasmFileName, args);
}

const { fileURLToPath } = await import("node:url");
const path = await import("node:path");
const fs = await import("node:fs/promises");
const { existsSync } = await import("node:fs");
const indexJsPath = fileURLToPath(indexJsUrl);
const webRoot = path.dirname(indexJsPath);

const http = await import("node:http");
const defaultContentTypes: Record<string, string> = {
".html": "text/html",
".js": "text/javascript",
".mjs": "text/javascript",
".wasm": "application/wasm",
};
const server = http.createServer(async (req, res) => {
const url = new URL(req.url!, `http://${req.headers.host}`);
const pathname = url.pathname;
const filePath = path.join(webRoot, pathname);
if (existsSync(filePath) && (await fs.stat(filePath)).isFile()) {
const data = await fs.readFile(filePath);
const ext = pathname.slice(pathname.lastIndexOf("."));
const contentType = options.contentTypes?.(pathname) || defaultContentTypes[ext] || "text/plain";
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
} else if (pathname === "/process-info.json") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ env: process.env }));
} else {
res.writeHead(404);
res.end();
}
});

await new Promise<void>((resolve) => server.listen({ host: "localhost", port: 0 }, () => resolve()));
const address = server.address() as AddressInfo;

const playwright = await (async () => {
try {
// @ts-ignore
Expand All @@ -263,29 +315,16 @@ Please run the following command to install it:
const browser = await playwright.chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
const { fileURLToPath } = await import("node:url");
const path = await import("node:path");
const indexJsPath = fileURLToPath(indexJsUrl);
const webRoot = path.dirname(indexJsPath);

// Forward console messages in the page to the Node.js console
page.on("console", (message: any) => {
console.log(message.text());
});

await page.route("http://example.com/**/*", async (route: any) => {
const url = route.request().url();
const urlPath = new URL(url).pathname;
if (urlPath === "/process-info.json") {
route.fulfill({ body: JSON.stringify({ env: process.env }) });
return;
}
route.fulfill({ path: path.join(webRoot, urlPath.slice(1)) });
});
const onExit = new Promise<number>((resolve) => {
page.exposeFunction("exitTest", resolve);
});
await page.goto("http://example.com/test.browser.html");
await page.goto(`http://localhost:${address.port}/test.browser.html`);
const exitCode = await onExit;
await browser.close();
process.exit(exitCode);
Expand Down