Skip to content

Add Hello world tutorial #294

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

Merged
merged 1 commit into from
Mar 14, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ xcuserdata/
.vscode
Examples/*/Bundle
Examples/*/package-lock.json
/Package.resolved
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,9 @@ let package = Package(
),
]
)

if Context.environment["JAVASCRIPTKIT_USE_DOCC_PLUGIN"] != nil {
package.dependencies.append(
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.4.0")
)
}
257 changes: 23 additions & 234 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,262 +4,51 @@

Swift framework to interact with JavaScript through WebAssembly.

## Getting started
## Quick Start

This JavaScript code
Check out the [Hello World](https://swiftpackageindex.com/swiftwasm/JavaScriptKit/main/tutorials/javascriptkit/hello-world) tutorial for a step-by-step guide to getting started.

```javascript
const alert = window.alert;
const document = window.document;
## Overview

const divElement = document.createElement("div");
divElement.innerText = "Hello, world";
const body = document.body;
body.appendChild(divElement);
JavaScriptKit provides a seamless way to interact with JavaScript from Swift code when compiled to WebAssembly. It allows Swift developers to:

const pet = {
age: 3,
owner: {
name: "Mike",
},
};

alert("JavaScript is running on browser!");
```

Can be written in Swift using JavaScriptKit
- Access JavaScript objects and functions
- Create closures that can be called from JavaScript
- Convert between Swift and JavaScript data types
- Use JavaScript promises with Swift's `async/await`
- Work with multi-threading

```swift
import JavaScriptKit

// Access global JavaScript objects
let document = JSObject.global.document

var divElement = document.createElement("div")
divElement.innerText = "Hello, world"
_ = document.body.appendChild(divElement)

struct Owner: Codable {
let name: String
}

struct Pet: Codable {
let age: Int
let owner: Owner
}

let jsPet = JSObject.global.pet
let swiftPet: Pet = try JSValueDecoder().decode(from: jsPet)

_ = JSObject.global.alert!("Swift is running in the browser!")
```

### `async`/`await`

Starting with SwiftWasm 5.5 you can use `async`/`await` with `JSPromise` objects. This requires
a few additional steps though (you can skip these steps if your app depends on
[Tokamak](https://tokamak.dev)):

1. Make sure that your target depends on `JavaScriptEventLoop` in your `Packages.swift`:

```swift
.target(
name: "JavaScriptKitExample",
dependencies: [
"JavaScriptKit",
.product(name: "JavaScriptEventLoop", package: "JavaScriptKit"),
]
)
```

2. Add an explicit import in the code that executes **before* you start using `await` and/or `Task`
APIs (most likely in `main.swift`):

```swift
import JavaScriptEventLoop
```

3. Run this function **before* you start using `await` and/or `Task` APIs (again, most likely in
`main.swift`):

```swift
JavaScriptEventLoop.installGlobalExecutor()
```

Then you can `await` on the `value` property of `JSPromise` instances, like in the example below:

```swift
import JavaScriptKit
import JavaScriptEventLoop

let alert = JSObject.global.alert.function!
let document = JSObject.global.document

private let jsFetch = JSObject.global.fetch.function!
func fetch(_ url: String) -> JSPromise {
JSPromise(jsFetch(url).object!)!
}

JavaScriptEventLoop.installGlobalExecutor()

struct Response: Decodable {
let uuid: String
}

var asyncButtonElement = document.createElement("button")
asyncButtonElement.innerText = "Fetch UUID demo"
asyncButtonElement.onclick = .object(JSClosure { _ in
Task {
do {
let response = try await fetch("https://httpbin.org/uuid").value
let json = try await JSPromise(response.json().object!)!.value
let parsedResponse = try JSValueDecoder().decode(Response.self, from: json)
alert(parsedResponse.uuid)
} catch {
print(error)
}
}
// Create and manipulate DOM elements
var div = document.createElement("div")
div.innerText = "Hello from Swift!"
_ = document.body.appendChild(div)

// Handle events with Swift closures
var button = document.createElement("button")
button.innerText = "Click me"
button.onclick = .object(JSClosure { _ in
JSObject.global.alert!("Button clicked!")
return .undefined
})

_ = document.body.appendChild(asyncButtonElement)
```

### `JavaScriptEventLoop` activation in XCTest suites

If you need to execute Swift async functions that can be resumed by JS event loop in your XCTest suites, please add `JavaScriptEventLoopTestSupport` to your test target dependencies.

```diff
.testTarget(
name: "MyAppTests",
dependencies: [
"MyApp",
+ "JavaScriptEventLoopTestSupport",
]
)
```

Linking this module automatically activates JS event loop based global executor by calling `JavaScriptEventLoop.installGlobalExecutor()`


## Requirements

### For developers

- macOS 11 and Xcode 13.2 or later versions, which support Swift Concurrency back-deployment.
To use earlier versions of Xcode on macOS 11 you'll have to
add `.unsafeFlags(["-Xfrontend", "-disable-availability-checking"])` in `Package.swift` manifest of
your package that depends on JavaScriptKit. You can also use Xcode 13.0 and 13.1 on macOS Monterey,
since this OS does not need back-deployment.
- [Swift 5.5 or later](https://swift.org/download/) and Ubuntu 18.04 if you'd like to use Linux.
Other Linux distributions are currently not supported.

### For users of apps depending on JavaScriptKit

Any recent browser that [supports WebAssembly](https://caniuse.com/#feat=wasm) and required
JavaScript features should work, which currently includes:

- Edge 84+
- Firefox 79+
- Chrome 84+
- Desktop Safari 14.1+
- Mobile Safari 14.8+

If you need to support older browser versions, you'll have to build with
the `JAVASCRIPTKIT_WITHOUT_WEAKREFS` flag, passing `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` flags
when compiling. This should lower browser requirements to these versions:

- Edge 16+
- Firefox 61+
- Chrome 66+
- (Mobile) Safari 12+

Not all of these versions are tested on regular basis though, compatibility reports are very welcome!

## Usage in a browser application

The easiest is to start with [Examples](/Examples) which has JavaScript glue runtime.

Second option is to get started with JavaScriptKit in your browser app is with [the `carton`
bundler](https://carton.dev). Add carton to your swift package dependencies:

```diff
dependencies: [
+ .package(url: "https://github.com/swiftwasm/carton", from: "1.1.3"),
],
```

Now you can activate the package dependency through swift:

```
swift run carton dev
_ = document.body.appendChild(button)
```

If you have multiple products in your package, you can also used the product flag:

```
swift run carton dev --product MyApp
```
Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Examples) for more detailed usage.

> [!WARNING]
> - If you already use `carton` before 0.x.x versions via Homebrew, you can remove it with `brew uninstall carton` and install the new version as a SwiftPM dependency.
> - Also please remove the old `.build` directory before using the new `carton`
## Contributing

<details><summary>Legacy Installation</summary>

---

As a part of these steps
you'll install `carton` via [Homebrew](https://brew.sh/) on macOS (you can also use the
[`ghcr.io/swiftwasm/carton`](https://github.com/orgs/swiftwasm/packages/container/package/carton)
Docker image if you prefer to run the build steps on Linux). Assuming you already have Homebrew
installed, you can create a new app that uses JavaScriptKit by following these steps:

1. Install `carton`:

```
brew install swiftwasm/tap/carton
```

If you had `carton` installed before this, make sure you have version 0.6.1 or greater:

```
carton --version
```

2. Create a directory for your project and make it current:

```
mkdir SwiftWasmApp && cd SwiftWasmApp
```

3. Initialize the project from a template with `carton`:

```
carton init --template basic
```

4. Build the project and start the development server, `carton dev` can be kept running
during development:

```
carton dev
```

---

</details>

Open [http://127.0.0.1:8080/](http://127.0.0.1:8080/) in your browser and a developer console
within it. You'll see `Hello, world!` output in the console. You can edit the app source code in
your favorite editor and save it, `carton` will immediately rebuild the app and reload all
browser tabs that have the app open.
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to the project.

## Sponsoring

[Become a gold or platinum sponsor](https://github.com/sponsors/swiftwasm/) and contact maintainers to add your logo on our README on Github with a link to your site.


<a href="https://www.emergetools.com/">
<img src="https://github.com/swiftwasm/swift/raw/swiftwasm-distribution/assets/sponsors/emergetools.png" width="30%">
</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# JavaScript Environment Requirements

## Required JavaScript Features

The JavaScript package produced by the JavaScriptKit packaging plugin requires the following JavaScript features:

- [`FinalizationRegistry`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry#browser_compatibility)
- [WebAssembly BigInt to i64 conversion in JS API](https://caniuse.com/wasm-bigint)

## Browser Compatibility

These JavaScript features are supported in the following browsers:

- Chrome 85+ (August 2020)
- Firefox 79+ (July 2020)
- Desktop Safari 14.1+ (April 2021)
- Mobile Safari 14.5+ (April 2021)
- Edge 85+ (August 2020)
- Node.js 15.0+ (October 2020)

Older browsers will not be able to run applications built with JavaScriptKit unless polyfills are provided.

## Handling Missing Features

### FinalizationRegistry

When using JavaScriptKit in environments without `FinalizationRegistry` support, you can:

1. Build with the opt-out flag: `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS`
2. Then manually manage memory by calling `release()` on all `JSClosure` instances:

```swift
let closure = JSClosure { args in
// Your code here
return .undefined
}

// Use the closure...

// Then release it when done
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
closure.release()
#endif
```

### WebAssembly BigInt Support

If you need to work with 64-bit integers in JavaScript, ensure your target environment supports WebAssembly BigInt conversions. For environments that don't support this feature, you'll need to avoid importing `JavaScriptBigIntSupport`
Loading