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

Expose instantiate API from bundled package #500

Merged
merged 2 commits into from
Nov 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
9 changes: 5 additions & 4 deletions Sources/CartonHelpers/StaticArchive.swift

Large diffs are not rendered by default.

97 changes: 92 additions & 5 deletions Sources/carton-frontend-slim/CartonFrontendBundleCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ struct CartonFrontendBundleCommand: AsyncParsableCommand {
topLevelResourcePaths: resources
)

terminal.write("Bundle successfully generated at \(bundleDirectory)\n", inColor: .green, bold: true)
terminal.write(
"Bundle successfully generated at \(bundleDirectory)\n", inColor: .green, bold: true)
}

func optimize(_ inputPath: AbsolutePath, outputPath: AbsolutePath, terminal: InteractiveWriter)
Expand Down Expand Up @@ -171,7 +172,10 @@ struct CartonFrontendBundleCommand: AsyncParsableCommand {
) throws {
// Rename the final binary to use a part of its hash to bust browsers and CDN caches.
let wasmFileHash = try localFileSystem.readFileContents(wasmOutputFilePath).hexChecksum
let mainModuleName = contentHash ? "\(wasmFileHash).wasm" : URL(fileURLWithPath: mainWasmPath).lastPathComponent
let mainModuleBaseName = URL(fileURLWithPath: mainWasmPath).deletingPathExtension()
.lastPathComponent
let mainModuleName =
contentHash ? "\(mainModuleBaseName).\(wasmFileHash).wasm" : "\(mainModuleBaseName).wasm"
let mainModulePath = try AbsolutePath(validating: mainModuleName, relativeTo: bundleDirectory)
try localFileSystem.move(from: wasmOutputFilePath, to: mainModulePath)

Expand All @@ -183,7 +187,7 @@ struct CartonFrontendBundleCommand: AsyncParsableCommand {
with: mainModuleName
)
)
let entrypointName = contentHash ? "\(entrypoint.hexChecksum).js" : "index.js"
let entrypointName = contentHash ? "app.\(entrypoint.hexChecksum).js" : "app.js"
try localFileSystem.writeFileContents(
AbsolutePath(validating: entrypointName, relativeTo: bundleDirectory),
bytes: entrypoint
Expand All @@ -198,7 +202,37 @@ struct CartonFrontendBundleCommand: AsyncParsableCommand {
))
)

for directoryName in try localFileSystem.resourcesDirectoryNames(relativeTo: buildDirectory) {
try localFileSystem.writeFileContents(
AbsolutePath(validating: "intrinsics.js", relativeTo: bundleDirectory),
bytes: ByteString(StaticResource.intrinsics)
)

let resourcesDirectoryNames = try localFileSystem.resourcesDirectoryNames(
relativeTo: buildDirectory)
let hasJavaScriptKitResources = resourcesDirectoryNames.contains(
"JavaScriptKit_JavaScriptKit.resources")

try localFileSystem.writeFileContents(
AbsolutePath(validating: "index.js", relativeTo: bundleDirectory),
bytes: ByteString(
encodingAsUTF8: indexJsContent(
mainModuleName: mainModuleName, hasJavaScriptKitResources: hasJavaScriptKitResources)
)
)

try localFileSystem.writeFileContents(
AbsolutePath(validating: "package.json", relativeTo: bundleDirectory),
bytes: ByteString(
encodingAsUTF8: """
{
"type": "module",
"main": "./index.js"
}
"""
)
)

for directoryName in resourcesDirectoryNames {
let resourcesPath = buildDirectory.appending(component: directoryName)
let targetDirectory = bundleDirectory.appending(component: directoryName)

Expand All @@ -212,7 +246,8 @@ struct CartonFrontendBundleCommand: AsyncParsableCommand {
validating: resourcesPath, relativeTo: localFileSystem.currentWorkingDirectory!)
for file in try localFileSystem.traverseRecursively(resourcesPath) {
let targetPath = bundleDirectory.appending(component: file.basename)
let sourcePath = bundleDirectory.appending(component: resourcesPath.basename).appending(component: file.basename)
let sourcePath = bundleDirectory.appending(component: resourcesPath.basename).appending(
component: file.basename)

guard localFileSystem.exists(sourcePath, followSymlink: true),
!localFileSystem.exists(targetPath, followSymlink: true)
Expand All @@ -223,6 +258,58 @@ struct CartonFrontendBundleCommand: AsyncParsableCommand {
}
}
}

private func indexJsContent(mainModuleName: String, hasJavaScriptKitResources: Bool) -> String {
var content = """
import { instantiate as internalInstantiate } from './intrinsics.js';

"""
if hasJavaScriptKitResources {
content += """
import { SwiftRuntime } from './JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs';

"""
}
content += """
export const wasmFileName = '\(mainModuleName)';

export async function instantiate(options, imports) {
if (!options) {
options = {};
}
const isNodeJs = (typeof process !== 'undefined') && (process.release.name === 'node');
const isWebBrowser = (typeof window !== 'undefined');

if (!options.module) {
if (isNodeJs) {
const module = await import(/* webpackIgnore: true */'node:module');
const importMeta = import.meta;
const require = module.default.createRequire(importMeta.url);
const fs = require('fs/promises');
const url = require('url');
const filePath = import.meta.resolve('./' + wasmFileName);
options.module = await WebAssembly.compile(await fs.readFile(url.fileURLToPath(filePath)));
} else if (isWebBrowser) {
options.module = await WebAssembly.compileStreaming(fetch(wasmFileName));
} else {
throw new Error('Unsupported environment to automatically load the WebAssembly module. Please provide the \"module\" option with the compiled WebAssembly module manually.');
}
}

"""
if hasJavaScriptKitResources {
content += """
options.SwiftRuntime = SwiftRuntime;

"""
}
content += """
return internalInstantiate(options, imports);
}
"""

return content
}
}

extension ByteString {
Expand Down
16 changes: 8 additions & 8 deletions Sources/carton-release/HashArchive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ struct HashArchive: AsyncParsableCommand {
let staticPath = try AbsolutePath(validating: "static", relativeTo: cwd)

var fileContent = """
import Foundation
import Foundation

public enum StaticResource {
public enum StaticResource {

"""
"""

for entrypoint in ["dev", "bundle", "test", "testNode"] {
for entrypoint in ["dev", "bundle", "test", "testNode", "intrinsics"] {
let tsFilename = "\(entrypoint).ts"
let filename = "\(entrypoint).js"
var arguments = [
Expand Down Expand Up @@ -75,15 +75,15 @@ struct HashArchive: AsyncParsableCommand {
$0.base64EncodedString()
}
fileContent += """
public static let \(entrypoint): Data = Data(base64Encoded: \"\(base64Content)\")!
public static let \(entrypoint): Data = Data(base64Encoded: \"\(base64Content)\")!

"""
"""
}

fileContent += """

}
"""
}
"""

try localFileSystem.writeFileContents(
AbsolutePath(
Expand Down
18 changes: 8 additions & 10 deletions entrypoint/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

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

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: SwiftRuntimeConstructor | undefined = undefined;
try {
Expand All @@ -31,22 +30,21 @@ const startWasiTask = async () => {
// JavaScriptKit module not available, running without JavaScriptKit runtime.
}

const wasmRunner = WasmRunner({
// Instantiate the WebAssembly file
await instantiate({
module: await WebAssembly.compileStreaming(response),
onStdoutLine(line) {
console.log(line);
},
onStderrLine(line) {
console.error(line);
}
}, runtimeConstructor);

// Instantiate the WebAssembly file
const wasmBytes = new Uint8Array(responseArrayBuffer).buffer;
await wasmRunner.run(wasmBytes);
},
SwiftRuntime: runtimeConstructor,
});
};

async function main(): Promise<void> {
await startWasiTask();
}

main();
main();
19 changes: 8 additions & 11 deletions entrypoint/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

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

const socket = new ReconnectingWebSocket(`ws://${location.host}/watcher`);
Expand All @@ -27,7 +27,6 @@ socket.addEventListener("message", (message) => {
const startWasiTask = async () => {
// Fetch our Wasm File
const response = await fetch("/main.wasm");
const responseArrayBuffer = await response.arrayBuffer();

let runtimeConstructor: SwiftRuntimeConstructor | undefined = undefined;
try {
Expand All @@ -42,8 +41,10 @@ const startWasiTask = async () => {
);
}

const wasmRunner = WasmRunner(
// Instantiate the WebAssembly file
await instantiate(
{
module: await WebAssembly.compileStreaming(response),
onStdout(chunk) {
const kindBuffer = new ArrayBuffer(2);
new DataView(kindBuffer).setUint16(0, 1001, true);
Expand All @@ -69,14 +70,10 @@ const startWasiTask = async () => {
},
onStderrLine(line) {
console.error(line);
}
},
runtimeConstructor
},
SwiftRuntime: runtimeConstructor,
}
);

// Instantiate the WebAssembly file
const wasmBytes = new Uint8Array(responseArrayBuffer).buffer;
await wasmRunner.run(wasmBytes);
};

function handleError(e: any) {
Expand Down Expand Up @@ -108,4 +105,4 @@ async function main(): Promise<void> {
}
}

main();
main();
Loading