Use JSKit runtime from SwiftPM resources (#335)

* Use JSKit runtime from SwiftPM resources

* Fix Node.js test runner

* Remove unused webpack npm packages

* Update Swift version in `.swiftformat`

* Fix browser and Node.js CJS/ESM handling

* Fix one of the tests, add CI time limit

* Use Tokamak `update-jskit` branch to fix tests

* Use latest Vapor with `.mjs` content-type fix

* Use dynamic import to detect JSKit presence

* Fix missing `runtimeConstructor` reference

* Update `StaticArchive.swift`

* Reduce the diff

* Address PR feedback

* Fix Node.js <-> JSKit integration test

* Update SwiftPM dependencies

* Fix comment typo in `testNode.js`

* Reuse `__stack_sanitizer` across entrypoints
This commit is contained in:
Max Desiatov 2022-05-20 13:12:45 +01:00 committed by GitHub
parent b9dab08e86
commit 1ba7dd5d82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 916 additions and 2437 deletions

View File

@ -13,13 +13,14 @@ jobs:
include:
- os: macos-12
swift_version: 5.6
xcode: /Applications/Xcode_13.3.app/Contents/Developer
xcode: /Applications/Xcode_13.4.app/Contents/Developer
- os: macos-11
swift_version: 5.5
xcode: /Applications/Xcode_13.2.1.app/Contents/Developer
- os: ubuntu-20.04
swift_version: 5.5
name: Build on ${{ matrix.os }} with Swift ${{ matrix.swift_version }}
timeout-minutes: 40
runs-on: ${{ matrix.os }}
steps:

View File

@ -6,5 +6,5 @@
--ifdef noindent
--stripunusedargs closure-only
--disable andOperator
--swiftversion 5.2
--maxwidth 100
--swiftversion 5.5
--maxwidth 120

View File

@ -15,8 +15,8 @@
"repositoryURL": "https://github.com/vapor/async-kit.git",
"state": {
"branch": null,
"revision": "e2f741640364c1d271405da637029ea6a33f754e",
"version": "1.11.1"
"revision": "017dc7da68c1ec9f0f46fcd1a8002d14a5662732",
"version": "1.12.0"
}
},
{
@ -24,8 +24,8 @@
"repositoryURL": "https://github.com/vapor/console-kit.git",
"state": {
"branch": null,
"revision": "75ea3b627d88221440b878e5dfccc73fd06842ed",
"version": "4.2.7"
"revision": "98d6e72ea7e756da9f87184c7239650223f95800",
"version": "4.3.0"
}
},
{
@ -33,8 +33,8 @@
"repositoryURL": "https://github.com/vapor/multipart-kit.git",
"state": {
"branch": null,
"revision": "2dd9368a3c9580792b77c7ef364f3735909d9996",
"version": "4.5.1"
"revision": "0d55c35e788451ee27222783c7d363cb88092fab",
"version": "4.5.2"
}
},
{
@ -42,8 +42,8 @@
"repositoryURL": "https://github.com/vapor/routing-kit.git",
"state": {
"branch": null,
"revision": "5603b81ceb744b8318feab1e60943704977a866b",
"version": "4.3.1"
"revision": "9e181d685a3dec1eef1fc6dacf606af364f86d68",
"version": "4.5.0"
}
},
{
@ -73,6 +73,15 @@
"version": "1.3.1"
}
},
{
"package": "swift-collections",
"repositoryURL": "https://github.com/apple/swift-collections.git",
"state": {
"branch": null,
"revision": "48254824bb4248676bf7ce56014ff57b142b77eb",
"version": "1.0.2"
}
},
{
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto.git",
@ -186,8 +195,8 @@
"repositoryURL": "https://github.com/vapor/vapor.git",
"state": {
"branch": null,
"revision": "b05266f863ee93eeff4b6d075364ccc8f81bbc1e",
"version": "4.57.0"
"revision": "ca823e66f8efd4dec7f5b996776d0ccc4cb34585",
"version": "4.60.0"
}
},
{

View File

@ -33,7 +33,7 @@ let package = Package(
url: "https://github.com/apple/swift-tools-support-core.git",
.branch("release/5.6")
),
.package(url: "https://github.com/vapor/vapor.git", from: "4.53.0"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.57.1"),
.package(url: "https://github.com/apple/swift-crypto.git", from: "1.1.0"),
.package(url: "https://github.com/JohnSundell/Splash.git", from: "0.16.0"),
.package(

View File

@ -1,4 +1,4 @@
// Copyright 2020 Carton contributors
// Copyright 2022 Carton contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -21,6 +21,7 @@ private enum Constants {
static let entrypoint = Entrypoint(fileName: "testNode.js", sha256: testNodeEntrypointSHA256)
}
/// Test runner for Node.js.
struct NodeTestRunner: TestRunner {
let testFilePath: AbsolutePath
let listTestCases: Bool
@ -28,9 +29,31 @@ struct NodeTestRunner: TestRunner {
let terminal: InteractiveWriter
func run() async throws {
terminal.write("\nRunning the test bundle with NodeJS:\n", inColor: .yellow)
let (_, _, filePath) = Constants.entrypoint.paths(on: localFileSystem)
var nodeArguments = ["node", filePath.pathString, testFilePath.pathString]
terminal.write("\nRunning the test bundle with Node.js:\n", inColor: .yellow)
let (_, _, entrypointPath) = Constants.entrypoint.paths(on: localFileSystem)
// Allow Node.js to resolve modules from resource directories by making them relative to the entrypoint path.
let buildDirectory = testFilePath.parentDirectory
let staticDirectory = entrypointPath.parentDirectory
// Clean up existing symlinks before creating new ones.
for existingSymlink in try localFileSystem.resourcesDirectoryNames(relativeTo: staticDirectory) {
try localFileSystem.removeFileTree(staticDirectory.appending(component: existingSymlink))
}
let resourceDirectories = try localFileSystem.resourcesDirectoryNames(relativeTo: buildDirectory)
// Create new symlink for each resource directory.
for resourcesDirectoryName in resourceDirectories {
try localFileSystem.createSymbolicLink(
staticDirectory.appending(component: resourcesDirectoryName),
pointingAt: buildDirectory.appending(component: resourcesDirectoryName),
relative: false
)
}
var nodeArguments = ["node", entrypointPath.pathString, testFilePath.pathString]
if listTestCases {
nodeArguments.append(contentsOf: ["--", "-l"])
} else if !testCases.isEmpty {
@ -39,5 +62,4 @@ struct NodeTestRunner: TestRunner {
}
try await Process.run(nodeArguments, terminal)
}
}

View File

@ -65,6 +65,7 @@ public extension TSCBasic.Process {
// swiftlint:disable:next function_body_length
static func run(
_ arguments: [String],
environment: [String: String] = [:],
loadingMessage: String = "Running...",
parser: ProcessOutputParser? = nil,
_ terminal: InteractiveWriter
@ -72,6 +73,10 @@ public extension TSCBasic.Process {
terminal.clearLine()
terminal.write("\(loadingMessage)\n", inColor: .yellow)
if !environment.isEmpty {
terminal.write(environment.map { "\($0)=\($1)" }.joined(separator: " ") + " ")
}
let processName = arguments[0].first == "/" ?
AbsolutePath(arguments[0]).basename : arguments[0]
@ -98,6 +103,7 @@ public extension TSCBasic.Process {
let process = Process(
arguments: arguments,
environment: ProcessEnv.vars.merging(environment) { _, new in new },
outputRedirection: .stream(stdout: stdout, stderr: stderr),
verbose: true,
startNewProcessGroup: true

View File

@ -60,22 +60,27 @@ extension Application {
get("main.wasm") {
// stream the file
$0.eventLoop
.makeSucceededFuture($0.fileio.streamFile(at: configuration.mainWasmPath.pathString))
$0.fileio.streamFile(at: configuration.mainWasmPath.pathString)
}
// Serve resources for all targets at their respective paths.
let buildDirectory = configuration.mainWasmPath.parentDirectory
for directoryName in try localFileSystem.resourcesDirectoryNames(relativeTo: buildDirectory) {
get(.constant(directoryName), "**") {
$0.eventLoop.makeSucceededFuture($0.fileio.streamFile(at: AbsolutePath(
func requestHandler(_ directoryName: String) -> ((Request) -> Response) {
{ (request: Request) -> Response in
request.fileio.streamFile(
at: AbsolutePath(
buildDirectory.appending(component: directoryName),
$0.parameters.getCatchall().joined(separator: "/")
).pathString))
request.parameters.getCatchall().joined(separator: "/")
).pathString
)
}
}
for directoryName in try localFileSystem.resourcesDirectoryNames(relativeTo: buildDirectory) {
get(.constant(directoryName), "**", use: requestHandler(directoryName))
}
let inferredMainTarget = configuration.manifest.targets.first {
configuration.product?.targets.contains($0.name) == true
}
@ -84,11 +89,6 @@ extension Application {
guard let mainTarget = inferredMainTarget else { return }
let resourcesPath = configuration.manifest.resourcesPath(for: mainTarget)
get("**") {
$0.eventLoop.makeSucceededFuture($0.fileio.streamFile(at: AbsolutePath(
buildDirectory.appending(component: resourcesPath),
$0.parameters.getCatchall().joined(separator: "/")
).pathString))
}
get("**", use: requestHandler(resourcesPath))
}
}

View File

@ -53,7 +53,7 @@ extension HTML: ResponseEncodable {
}
public static func indexPage(customContent: String?, entrypointName: String) -> String {
let scriptTag = #"<script type="text/javascript" src="\#(entrypointName)"></script>"#
let scriptTag = #"<script type="module" src="\#(entrypointName)"></script>"#
if let customContent = customContent {
return customContent.replacingOccurrences(
of: "</head>",

View File

@ -229,7 +229,7 @@ public actor Server {
/// Blocking function that starts the HTTP server.
public nonisolated func run() async throws {
// Explicitly hop to another thread to avoid blocking the thread that is running the actor executor
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<(), Error>) in
Thread {
Task {
do {

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@ import TSCBasic
import TSCUtility
import WasmTransformer
public let compatibleJSKitVersion = Version(0, 14, 0)
public let compatibleJSKitVersion = Version(0, 15, 0)
enum ToolchainError: Error, CustomStringConvertible {
case directoryDoesNotExist(AbsolutePath)
@ -86,10 +86,10 @@ extension PackageDependency {
default: break
}
if let exactVersion = exactVersion {
return exactVersion == compatibleJSKitVersion
return exactVersion >= compatibleJSKitVersion
}
if let versionRange = versionRange {
return versionRange.upperBound >= compatibleJSKitVersion
return versionRange.lowerBound >= compatibleJSKitVersion
}
return false
}
@ -127,7 +127,8 @@ public final class Toolchain {
self.terminal = terminal
if let workingDirectory = fileSystem.currentWorkingDirectory {
let swiftc = swiftPath.parentDirectory.appending(component: "swiftc")
manifest = await Result { try await Manifest.from(path: workingDirectory, swiftc: swiftc, fileSystem: fileSystem, terminal: terminal)
manifest = await Result {
try await Manifest.from(path: workingDirectory, swiftc: swiftc, fileSystem: fileSystem, terminal: terminal)
}
} else {
manifest = .failure(ToolchainError.noWorkingDirectory)
@ -147,7 +148,7 @@ public final class Toolchain {
}
private func inferDevProduct(hint: String?) throws -> ProductDescription? {
let manifest = try self.manifest.get()
let manifest = try manifest.get()
var candidateProducts = manifest.products
.filter { $0.type == .executable }
@ -201,7 +202,7 @@ public final class Toolchain {
}
public func inferSourcesPaths() throws -> [AbsolutePath] {
let manifest = try self.manifest.get()
let manifest = try manifest.get()
let targetPaths = manifest.targets.compactMap { target -> String? in
@ -224,7 +225,7 @@ public final class Toolchain {
}
private func emitJSKitWarningIfNeeded() throws {
let manifest = try self.manifest.get()
let manifest = try manifest.get()
guard let jsKit = manifest.dependencies.first(where: {
$0.nameForTargetDependencyResolutionOnly == "JavaScriptKit"
}) else {
@ -305,7 +306,7 @@ public final class Toolchain {
public func buildTestBundle(
flavor: BuildFlavor
) async throws -> AbsolutePath {
let manifest = try self.manifest.get()
let manifest = try manifest.get()
let binPath = try inferBinPath(isRelease: flavor.isRelease)
let testProductName = "\(manifest.displayName)PackageTests"
let testBundlePath = binPath.appending(component: "\(testProductName).wasm")

View File

@ -45,10 +45,28 @@ struct HashArchive: AsyncParsableCommand {
)
try localFileSystem.createDirectory(dotFilesStaticPath, recursive: true)
let hashes = try await ["dev", "bundle", "test", "testNode"].asyncMap { entrypoint -> (String, String) in
try await Process.run(["npm", "run", entrypoint], terminal)
let entrypointPath = AbsolutePath(staticPath, "\(entrypoint).js")
let dotFilesEntrypointPath = dotFilesStaticPath.appending(component: "\(entrypoint).js")
let hashes = try await (["dev", "bundle", "test", "testNode"])
.asyncMap { entrypoint -> (String, String) in
let filename = "\(entrypoint).js"
var arguments = [
"npx", "esbuild", "--bundle", "entrypoint/\(filename)", "--outfile=static/\(filename)",
]
if entrypoint == "testNode" {
arguments.append(contentsOf: [
"--format=cjs", "--platform=node",
"--external:./JavaScriptKit_JavaScriptKit.resources/Runtime/index.js",
])
} else {
arguments.append(contentsOf: [
"--format=esm",
"--external:./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs",
])
}
try await Process.run(arguments, terminal)
let entrypointPath = AbsolutePath(staticPath, filename)
let dotFilesEntrypointPath = dotFilesStaticPath.appending(component: filename)
try localFileSystem.removeFileTree(dotFilesEntrypointPath)
try localFileSystem.copy(from: entrypointPath, to: dotFilesEntrypointPath)
@ -83,10 +101,8 @@ struct HashArchive: AsyncParsableCommand {
public let \($0)EntrypointSHA256 = ByteString([
\(arrayString(from: $1))
])
"""
}.joined())
}.joined(separator: "\n\n"))
public let staticArchiveContents = "\(staticArchiveContents.withData { $0.base64EncodedString() })"
"""

View File

@ -30,7 +30,7 @@ final class DevCommandTests: XCTestCase {
client = nil
}
#if os(macOS)
#if os(macOS)
func testWithNoArguments() throws {
let url = "http://127.0.0.1:8080"
@ -88,7 +88,7 @@ final class DevCommandTests: XCTestCase {
// clean up
do { try packageDirectory.appending(component: ".build").delete() } catch {}
}
#endif
#endif
func checkForExpectedContent(at url: String) {
// client time out for connecting and responding
@ -106,7 +106,7 @@ final class DevCommandTests: XCTestCase {
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="text/javascript" src="dev.js"></script>
<script type="module" src="dev.js"></script>
</head>
<body>
</body>

View File

@ -24,7 +24,8 @@ import XCTest
extension TestCommandTests: Testable {}
private enum Constants {
static let anyPackageName = "TestApp"
static let testAppPackageName = "TestApp"
static let nodeJSKitPackageName = "NodeJSKitTest"
}
final class TestCommandTests: XCTestCase {
@ -36,7 +37,7 @@ final class TestCommandTests: XCTestCase {
}
func testWithNoArguments() throws {
let packageDirectory = givenAPackageTestDirectory(Constants.anyPackageName)
let packageDirectory = givenAPackageTestDirectory(Constants.testAppPackageName)
AssertExecuteCommand(
command: "carton test",
@ -45,8 +46,18 @@ final class TestCommandTests: XCTestCase {
)
}
func testEnvironmentNode() throws {
let packageDirectory = givenAPackageTestDirectory(Constants.anyPackageName)
func testEnvironmentNodeNoJSKit() throws {
let packageDirectory = givenAPackageTestDirectory(Constants.testAppPackageName)
AssertExecuteCommand(
command: "carton test --environment node",
cwd: packageDirectory.url,
debug: true
)
}
func testEnvironmentNodeJSKit() throws {
let packageDirectory = givenAPackageTestDirectory(Constants.nodeJSKitPackageName)
AssertExecuteCommand(
command: "carton test --environment node",
@ -80,7 +91,7 @@ final class TestCommandTests: XCTestCase {
}
#endif
private func givenAPackageTestDirectory(_ name: String = Constants.anyPackageName)
private func givenAPackageTestDirectory(_ name: String = Constants.testAppPackageName)
-> TestDirectory
{
let packageDirectory = TestDirectory(testFixturesDirectory, name)
@ -89,7 +100,6 @@ final class TestCommandTests: XCTestCase {
return packageDirectory
}
}
private class TestDirectory {
@ -99,7 +109,7 @@ private class TestDirectory {
var exists: Bool { directory.exists }
init(_ testDirectory: AbsolutePath, _ dirName: String) {
self.directory = testDirectory.appending(components: dirName)
directory = testDirectory.appending(components: dirName)
cleanBuildDir()
}

View File

@ -1,61 +1,59 @@
{
"object": {
"pins": [
"pins" : [
{
"package": "JavaScriptKit",
"repositoryURL": "https://github.com/swiftwasm/JavaScriptKit.git",
"state": {
"branch": null,
"revision": "9a3b7d5f316e6d1709bd25e3e0392efca357f525",
"version": "0.14.0"
"identity" : "javascriptkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftwasm/JavaScriptKit.git",
"state" : {
"revision" : "2d7bc960eed438dce7355710ece43fa004bbb3ac",
"version" : "0.15.0"
}
},
{
"package": "OpenCombine",
"repositoryURL": "https://github.com/OpenCombine/OpenCombine.git",
"state": {
"branch": null,
"revision": "28993ae57de5a4ea7e164787636cafad442d568c",
"version": "0.12.0"
"identity" : "opencombine",
"kind" : "remoteSourceControl",
"location" : "https://github.com/OpenCombine/OpenCombine.git",
"state" : {
"revision" : "9cf67e363738dbab61b47fb5eaed78d3db31e5ee",
"version" : "0.13.0"
}
},
{
"package": "OpenCombineJS",
"repositoryURL": "https://github.com/swiftwasm/OpenCombineJS.git",
"state": {
"branch": null,
"revision": "f1f1799ddbb9876a0ef8c5700a3b78d352d0b969",
"version": "0.1.2"
"identity" : "opencombinejs",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftwasm/OpenCombineJS.git",
"state" : {
"revision" : "e574e418ba468ff5c2d4c499eb56f108aeb4d2ba",
"version" : "0.2.0"
}
},
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "83b23d940471b313427da226196661856f6ba3e0",
"version": "0.4.4"
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "f3c9084a71ef4376f2fabbdf1d3d90a49f1fabdb",
"version" : "1.1.2"
}
},
{
"package": "Benchmark",
"repositoryURL": "https://github.com/google/swift-benchmark",
"state": {
"branch": null,
"revision": "8e0ef8bb7482ab97dcd2cd1d6855bd38921c345d",
"version": "0.1.0"
"identity" : "swift-benchmark",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/swift-benchmark",
"state" : {
"revision" : "8163295f6fe82356b0bcf8e1ab991645de17d096",
"version" : "0.1.2"
}
},
{
"package": "Tokamak",
"repositoryURL": "https://github.com/TokamakUI/Tokamak",
"state": {
"branch": null,
"revision": "6905fdff1910df163f9f58c7a51e97b66fe59f40",
"version": "0.10.0"
"identity" : "tokamak",
"kind" : "remoteSourceControl",
"location" : "https://github.com/TokamakUI/Tokamak",
"state" : {
"branch" : "main",
"revision" : "337b80a4c408db814e5e9e61bd232b4f96251308"
}
}
]
},
"version": 1
],
"version" : 2
}

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.6
import PackageDescription
let package = Package(
name: "Milk",
@ -7,10 +7,10 @@ let package = Package(
.executable(name: "Milk", targets: ["Milk"]),
],
dependencies: [
.package(name: "Tokamak", url: "https://github.com/TokamakUI/Tokamak", from: "0.10.0"),
.package(url: "https://github.com/TokamakUI/Tokamak", branch: "main"),
],
targets: [
.target(
.executableTarget(
name: "Milk",
dependencies: [
.product(name: "TokamakShim", package: "Tokamak"),

View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,14 @@
{
"pins" : [
{
"identity" : "javascriptkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftwasm/JavaScriptKit",
"state" : {
"revision" : "2d7bc960eed438dce7355710ece43fa004bbb3ac",
"version" : "0.15.0"
}
}
],
"version" : 2
}

View File

@ -0,0 +1,23 @@
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "NodeJSKitTest",
dependencies: [
.package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.15.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.executableTarget(
name: "NodeJSKitTest",
dependencies: []
),
.testTarget(
name: "NodeJSKitTestTests",
dependencies: ["JavaScriptKit"]
),
]
)

View File

@ -0,0 +1 @@
print("Hello, world!")

View File

@ -0,0 +1,15 @@
import JavaScriptKit
import XCTest
final class NodeJSKitTestTests: XCTestCase {
func testExample() throws {
let require = JSObject.global.require.function!
let Buffer = require("buffer").object!.Buffer.function!
let testString = "Hello"
let base64String = Buffer.from!(testString).toString("base64")
let decodedString = Buffer.from!(base64String, "base64").toString("ascii").string!
XCTAssertEqual(decodedString, testString)
}
}

View File

@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/swiftwasm/JavaScriptKit",
"state": {
"branch": null,
"revision": "9a3b7d5f316e6d1709bd25e3e0392efca357f525",
"version": "0.14.0"
"revision": "2d7bc960eed438dce7355710ece43fa004bbb3ac",
"version": "0.15.0"
}
}
]

View File

@ -9,7 +9,7 @@ let package = Package(
.executable(name: "TestApp", targets: ["TestApp"]),
],
dependencies: [
.package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.14.0"),
.package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.15.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test

View File

@ -1,11 +0,0 @@
const path = require("path");
const outputPath = path.resolve(__dirname, "../static");
module.exports = {
entry: "./entrypoint/bundle.js",
mode: "production",
output: {
filename: "bundle.js",
path: outputPath,
},
};

View File

@ -12,18 +12,28 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { WasmRunner } from './common'
const wasmRunner = WasmRunner()
import { WasmRunner } from "./common.js";
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;
try {
const { SwiftRuntime } = await import(
"./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs"
);
runtimeConstructor = SwiftRuntime;
} catch {
// JavaScriptKit module not available, running without JavaScriptKit runtime.
}
const wasmRunner = WasmRunner(false, runtimeConstructor);
// Instantiate the WebAssembly file
const wasmBytes = new Uint8Array(responseArrayBuffer).buffer;
await wasmRunner.run(wasmBytes)
await wasmRunner.run(wasmBytes);
};
function handleError(e) {

View File

@ -12,14 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { SwiftRuntime } from "javascript-kit-swift";
import { WASI } from "@wasmer/wasi";
import { WasmFs } from "@wasmer/wasmfs";
export const WasmRunner = (rawOptions) => {
export const WasmRunner = (rawOptions, SwiftRuntime) => {
const options = defaultRunnerOptions(rawOptions);
const swift = new SwiftRuntime();
let swift;
if (SwiftRuntime) {
swift = new SwiftRuntime();
}
const wasmFs = createWasmFS(
(stdout) => {
@ -44,9 +46,12 @@ export const WasmRunner = (rawOptions) => {
const createWasmImportObject = (extraWasmImports) => {
const importObject = {
wasi_snapshot_preview1: wrapWASI(wasi),
javascript_kit: swift.wasmImports,
};
if (swift) {
importObject.javascript_kit = swift.wasmImports;
}
if (extraWasmImports) {
// Shallow clone
for (const key in extraWasmImports) {
@ -59,13 +64,21 @@ export const WasmRunner = (rawOptions) => {
return {
async run(wasmBytes, extraWasmImports) {
if (!extraWasmImports) {
extraWasmImports = {};
}
extraWasmImports.__stack_sanitizer = {
report_stack_overflow: () => {
throw new Error("Detected stack buffer overflow.");
},
};
const importObject = createWasmImportObject(extraWasmImports);
const module = await WebAssembly.instantiate(wasmBytes, importObject);
// Node support
const instance = "instance" in module ? module.instance : module;
if (instance.exports.swjs_library_version) {
if (swift && instance.exports.swjs_library_version) {
swift.setInstance(instance);
}

View File

@ -1,11 +0,0 @@
const path = require("path");
const outputPath = path.resolve(__dirname, "../static");
module.exports = {
entry: "./entrypoint/dev.js",
mode: "development",
output: {
filename: "dev.js",
path: outputPath,
},
};

View File

@ -13,7 +13,7 @@
// limitations under the License.
import ReconnectingWebSocket from "reconnecting-websocket";
import { WasmRunner } from "./common"
import { WasmRunner } from "./common.js";
const socket = new ReconnectingWebSocket(`ws://${location.host}/watcher`);
@ -23,10 +23,28 @@ socket.addEventListener("message", (message) => {
}
});
const wasmRunner = WasmRunner({
const startWasiTask = async () => {
// Fetch our Wasm File
const response = await fetch("/main.wasm");
const responseArrayBuffer = await response.arrayBuffer();
let runtimeConstructor;
try {
const { SwiftRuntime } = await import(
"./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs"
);
runtimeConstructor = SwiftRuntime;
} catch {
console.log(
"JavaScriptKit module not available, running without JavaScriptKit runtime."
);
}
const wasmRunner = WasmRunner(
{
onStderr() {
const prevLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 1000
Error.stackTraceLimit = 1000;
socket.send(
JSON.stringify({
kind: "stackTrace",
@ -34,23 +52,14 @@ const wasmRunner = WasmRunner({
})
);
Error.stackTraceLimit = prevLimit;
}
})
const startWasiTask = async () => {
// Fetch our Wasm File
const response = await fetch("/main.wasm");
const responseArrayBuffer = await response.arrayBuffer();
},
},
runtimeConstructor
);
// Instantiate the WebAssembly file
const wasmBytes = new Uint8Array(responseArrayBuffer).buffer;
await wasmRunner.run(wasmBytes, {
__stack_sanitizer: {
report_stack_overflow: () => {
throw new Error("Detected stack-buffer-overflow.")
}
}
})
await wasmRunner.run(wasmBytes);
};
function handleError(e) {

View File

@ -1,11 +0,0 @@
const path = require("path");
const outputPath = path.resolve(__dirname, "../static");
module.exports = {
entry: "./entrypoint/test.js",
mode: "development",
output: {
filename: "test.js",
path: outputPath,
},
};

View File

@ -14,7 +14,7 @@
import ReconnectingWebSocket from "reconnecting-websocket";
import { WASIExitError } from "@wasmer/wasi";
import { WasmRunner } from "./common";
import { WasmRunner } from "./common.js";
const socket = new ReconnectingWebSocket(`ws://${location.host}/watcher`);
socket.addEventListener("message", (message) => {
@ -23,8 +23,26 @@ socket.addEventListener("message", (message) => {
}
});
let testRunOutput = "";
const wasmRunner = WasmRunner({
const startWasiTask = async () => {
// Fetch our Wasm File
const response = await fetch("/main.wasm");
const responseArrayBuffer = await response.arrayBuffer();
let runtimeConstructor;
try {
const { SwiftRuntime } = await import(
"./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs"
);
runtimeConstructor = SwiftRuntime;
} catch {
console.log(
"JavaScriptKit module not available, running without JavaScriptKit runtime."
);
}
let testRunOutput = "";
const wasmRunner = WasmRunner(
{
onStdout: (text) => {
testRunOutput += text + "\n";
},
@ -36,25 +54,15 @@ const wasmRunner = WasmRunner({
})
);
},
});
const startWasiTask = async () => {
// Fetch our Wasm File
const response = await fetch("/main.wasm");
const responseArrayBuffer = await response.arrayBuffer();
},
runtimeConstructor
);
// Instantiate the WebAssembly file
const wasmBytes = new Uint8Array(responseArrayBuffer).buffer;
// Start the WebAssembly WASI instance
try {
await wasmRunner.run(wasmBytes, {
__stack_sanitizer: {
report_stack_overflow: () => {
throw new Error("Detected stack-buffer-overflow.");
},
},
});
await wasmRunner.run(wasmBytes);
} catch (error) {
if (!(error instanceof WASIExitError) || error.code != 0) {
throw error; // not a successful test run, rethrow

View File

@ -1,20 +0,0 @@
const path = require("path");
const outputPath = path.resolve(__dirname, "../static");
module.exports = {
entry: "./entrypoint/testNode.js",
mode: "development",
target: "node",
output: {
filename: "testNode.js",
path: outputPath,
libraryTarget: "commonjs"
},
node: {
__dirname: false,
__filename: false,
},
resolve: {
mainFields: ["main", "module"],
},
};

View File

@ -1,4 +1,4 @@
// Copyright 2020 Carton contributors
// Copyright 2022 Carton contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -13,7 +13,7 @@
// limitations under the License.
import fs from "fs/promises";
import { WasmRunner } from "./common";
import { WasmRunner } from "./common.js";
const args = [...process.argv];
args.shift();
@ -24,18 +24,27 @@ if (!wasmFile) {
throw Error("No WASM test file specified, can not run tests");
}
const wasmRunner = WasmRunner({ args: testArgs });
const startWasiTask = async () => {
const wasmBytes = await fs.readFile(wasmFile);
await wasmRunner.run(wasmBytes, {
__stack_sanitizer: {
report_stack_overflow: () => {
throw new Error("Detected stack-buffer-overflow.");
},
},
});
let runtimeConstructor;
try {
const { SwiftRuntime } = await import(
"./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs"
);
runtimeConstructor = SwiftRuntime;
// Make `require` function available in the Swift environment. By default it's only available in the local scope,
// but not on the `global` object.
global.require = require;
} catch {
// No JavaScriptKit module found, run the Wasm module without JSKit
}
const wasmRunner = WasmRunner({ args: testArgs }, runtimeConstructor);
await wasmRunner.run(wasmBytes);
};
startWasiTask().catch((e) => {

2671
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,6 @@
"version": "0.14.2",
"description": "📦 Watcher, bundler, and test runner for your SwiftWasm apps ",
"main": "index.js",
"scripts": {
"dev": "webpack --config entrypoint/dev.config.js",
"bundle": "webpack --config entrypoint/bundle.config.js",
"test": "webpack --config entrypoint/test.config.js",
"testNode": "webpack --config entrypoint/testNode.config.js",
"build": "run-p dev bundle test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/swiftwasm/carton.git"
@ -29,10 +22,8 @@
"devDependencies": {
"@wasmer/wasi": "^0.12.0",
"@wasmer/wasmfs": "^0.12.0",
"javascript-kit-swift": "^0.14.0",
"esbuild": "^0.14.38",
"npm-run-all": "^4.1.5",
"reconnecting-websocket": "^4.4.0",
"webpack": "^5.64.2",
"webpack-cli": "^4.9.1"
"reconnecting-websocket": "^4.4.0"
}
}