|
4 | 4 |
|
5 | 5 | Swift framework to interact with JavaScript through WebAssembly.
|
6 | 6 |
|
7 |
| -## Getting started |
| 7 | +## Quick Start |
8 | 8 |
|
9 |
| -This JavaScript code |
| 9 | +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. |
10 | 10 |
|
11 |
| -```javascript |
12 |
| -const alert = window.alert; |
13 |
| -const document = window.document; |
| 11 | +## Overview |
14 | 12 |
|
15 |
| -const divElement = document.createElement("div"); |
16 |
| -divElement.innerText = "Hello, world"; |
17 |
| -const body = document.body; |
18 |
| -body.appendChild(divElement); |
| 13 | +JavaScriptKit provides a seamless way to interact with JavaScript from Swift code when compiled to WebAssembly. It allows Swift developers to: |
19 | 14 |
|
20 |
| -const pet = { |
21 |
| - age: 3, |
22 |
| - owner: { |
23 |
| - name: "Mike", |
24 |
| - }, |
25 |
| -}; |
26 |
| - |
27 |
| -alert("JavaScript is running on browser!"); |
28 |
| -``` |
29 |
| - |
30 |
| -Can be written in Swift using JavaScriptKit |
| 15 | +- Access JavaScript objects and functions |
| 16 | +- Create closures that can be called from JavaScript |
| 17 | +- Convert between Swift and JavaScript data types |
| 18 | +- Use JavaScript promises with Swift's `async/await` |
| 19 | +- Work with multi-threading |
31 | 20 |
|
32 | 21 | ```swift
|
33 | 22 | import JavaScriptKit
|
34 | 23 |
|
| 24 | +// Access global JavaScript objects |
35 | 25 | let document = JSObject.global.document
|
36 | 26 |
|
37 |
| -var divElement = document.createElement("div") |
38 |
| -divElement.innerText = "Hello, world" |
39 |
| -_ = document.body.appendChild(divElement) |
40 |
| - |
41 |
| -struct Owner: Codable { |
42 |
| - let name: String |
43 |
| -} |
44 |
| - |
45 |
| -struct Pet: Codable { |
46 |
| - let age: Int |
47 |
| - let owner: Owner |
48 |
| -} |
49 |
| - |
50 |
| -let jsPet = JSObject.global.pet |
51 |
| -let swiftPet: Pet = try JSValueDecoder().decode(from: jsPet) |
52 |
| - |
53 |
| -_ = JSObject.global.alert!("Swift is running in the browser!") |
54 |
| -``` |
55 |
| - |
56 |
| -### `async`/`await` |
57 |
| - |
58 |
| -Starting with SwiftWasm 5.5 you can use `async`/`await` with `JSPromise` objects. This requires |
59 |
| -a few additional steps though (you can skip these steps if your app depends on |
60 |
| -[Tokamak](https://tokamak.dev)): |
61 |
| - |
62 |
| -1. Make sure that your target depends on `JavaScriptEventLoop` in your `Packages.swift`: |
63 |
| - |
64 |
| -```swift |
65 |
| -.target( |
66 |
| - name: "JavaScriptKitExample", |
67 |
| - dependencies: [ |
68 |
| - "JavaScriptKit", |
69 |
| - .product(name: "JavaScriptEventLoop", package: "JavaScriptKit"), |
70 |
| - ] |
71 |
| -) |
72 |
| -``` |
73 |
| - |
74 |
| -2. Add an explicit import in the code that executes **before* you start using `await` and/or `Task` |
75 |
| -APIs (most likely in `main.swift`): |
76 |
| - |
77 |
| -```swift |
78 |
| -import JavaScriptEventLoop |
79 |
| -``` |
80 |
| - |
81 |
| -3. Run this function **before* you start using `await` and/or `Task` APIs (again, most likely in |
82 |
| -`main.swift`): |
83 |
| - |
84 |
| -```swift |
85 |
| -JavaScriptEventLoop.installGlobalExecutor() |
86 |
| -``` |
87 |
| - |
88 |
| -Then you can `await` on the `value` property of `JSPromise` instances, like in the example below: |
89 |
| - |
90 |
| -```swift |
91 |
| -import JavaScriptKit |
92 |
| -import JavaScriptEventLoop |
93 |
| - |
94 |
| -let alert = JSObject.global.alert.function! |
95 |
| -let document = JSObject.global.document |
96 |
| - |
97 |
| -private let jsFetch = JSObject.global.fetch.function! |
98 |
| -func fetch(_ url: String) -> JSPromise { |
99 |
| - JSPromise(jsFetch(url).object!)! |
100 |
| -} |
101 |
| - |
102 |
| -JavaScriptEventLoop.installGlobalExecutor() |
103 |
| - |
104 |
| -struct Response: Decodable { |
105 |
| - let uuid: String |
106 |
| -} |
107 |
| - |
108 |
| -var asyncButtonElement = document.createElement("button") |
109 |
| -asyncButtonElement.innerText = "Fetch UUID demo" |
110 |
| -asyncButtonElement.onclick = .object(JSClosure { _ in |
111 |
| - Task { |
112 |
| - do { |
113 |
| - let response = try await fetch("https://httpbin.org/uuid").value |
114 |
| - let json = try await JSPromise(response.json().object!)!.value |
115 |
| - let parsedResponse = try JSValueDecoder().decode(Response.self, from: json) |
116 |
| - alert(parsedResponse.uuid) |
117 |
| - } catch { |
118 |
| - print(error) |
119 |
| - } |
120 |
| - } |
| 27 | +// Create and manipulate DOM elements |
| 28 | +var div = document.createElement("div") |
| 29 | +div.innerText = "Hello from Swift!" |
| 30 | +_ = document.body.appendChild(div) |
121 | 31 |
|
| 32 | +// Handle events with Swift closures |
| 33 | +var button = document.createElement("button") |
| 34 | +button.innerText = "Click me" |
| 35 | +button.onclick = .object(JSClosure { _ in |
| 36 | + JSObject.global.alert!("Button clicked!") |
122 | 37 | return .undefined
|
123 | 38 | })
|
124 |
| - |
125 |
| -_ = document.body.appendChild(asyncButtonElement) |
126 |
| -``` |
127 |
| - |
128 |
| -### `JavaScriptEventLoop` activation in XCTest suites |
129 |
| - |
130 |
| -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. |
131 |
| - |
132 |
| -```diff |
133 |
| - .testTarget( |
134 |
| - name: "MyAppTests", |
135 |
| - dependencies: [ |
136 |
| - "MyApp", |
137 |
| -+ "JavaScriptEventLoopTestSupport", |
138 |
| - ] |
139 |
| - ) |
140 |
| -``` |
141 |
| - |
142 |
| -Linking this module automatically activates JS event loop based global executor by calling `JavaScriptEventLoop.installGlobalExecutor()` |
143 |
| - |
144 |
| - |
145 |
| -## Requirements |
146 |
| - |
147 |
| -### For developers |
148 |
| - |
149 |
| -- macOS 11 and Xcode 13.2 or later versions, which support Swift Concurrency back-deployment. |
150 |
| -To use earlier versions of Xcode on macOS 11 you'll have to |
151 |
| -add `.unsafeFlags(["-Xfrontend", "-disable-availability-checking"])` in `Package.swift` manifest of |
152 |
| -your package that depends on JavaScriptKit. You can also use Xcode 13.0 and 13.1 on macOS Monterey, |
153 |
| -since this OS does not need back-deployment. |
154 |
| -- [Swift 5.5 or later](https://swift.org/download/) and Ubuntu 18.04 if you'd like to use Linux. |
155 |
| - Other Linux distributions are currently not supported. |
156 |
| - |
157 |
| -### For users of apps depending on JavaScriptKit |
158 |
| - |
159 |
| -Any recent browser that [supports WebAssembly](https://caniuse.com/#feat=wasm) and required |
160 |
| -JavaScript features should work, which currently includes: |
161 |
| - |
162 |
| -- Edge 84+ |
163 |
| -- Firefox 79+ |
164 |
| -- Chrome 84+ |
165 |
| -- Desktop Safari 14.1+ |
166 |
| -- Mobile Safari 14.8+ |
167 |
| - |
168 |
| -If you need to support older browser versions, you'll have to build with |
169 |
| -the `JAVASCRIPTKIT_WITHOUT_WEAKREFS` flag, passing `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` flags |
170 |
| -when compiling. This should lower browser requirements to these versions: |
171 |
| - |
172 |
| -- Edge 16+ |
173 |
| -- Firefox 61+ |
174 |
| -- Chrome 66+ |
175 |
| -- (Mobile) Safari 12+ |
176 |
| - |
177 |
| -Not all of these versions are tested on regular basis though, compatibility reports are very welcome! |
178 |
| - |
179 |
| -## Usage in a browser application |
180 |
| - |
181 |
| -The easiest is to start with [Examples](/Examples) which has JavaScript glue runtime. |
182 |
| - |
183 |
| -Second option is to get started with JavaScriptKit in your browser app is with [the `carton` |
184 |
| -bundler](https://carton.dev). Add carton to your swift package dependencies: |
185 |
| - |
186 |
| -```diff |
187 |
| -dependencies: [ |
188 |
| -+ .package(url: "https://github.com/swiftwasm/carton", from: "1.1.3"), |
189 |
| -], |
190 |
| -``` |
191 |
| - |
192 |
| -Now you can activate the package dependency through swift: |
193 |
| - |
194 |
| -``` |
195 |
| -swift run carton dev |
| 39 | +_ = document.body.appendChild(button) |
196 | 40 | ```
|
197 | 41 |
|
198 |
| -If you have multiple products in your package, you can also used the product flag: |
199 |
| - |
200 |
| -``` |
201 |
| -swift run carton dev --product MyApp |
202 |
| -``` |
| 42 | +Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Examples) for more detailed usage. |
203 | 43 |
|
204 |
| -> [!WARNING] |
205 |
| -> - 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. |
206 |
| -> - Also please remove the old `.build` directory before using the new `carton` |
| 44 | +## Contributing |
207 | 45 |
|
208 |
| -<details><summary>Legacy Installation</summary> |
209 |
| - |
210 |
| ---- |
211 |
| - |
212 |
| -As a part of these steps |
213 |
| -you'll install `carton` via [Homebrew](https://brew.sh/) on macOS (you can also use the |
214 |
| -[`ghcr.io/swiftwasm/carton`](https://github.com/orgs/swiftwasm/packages/container/package/carton) |
215 |
| -Docker image if you prefer to run the build steps on Linux). Assuming you already have Homebrew |
216 |
| -installed, you can create a new app that uses JavaScriptKit by following these steps: |
217 |
| - |
218 |
| -1. Install `carton`: |
219 |
| - |
220 |
| -``` |
221 |
| -brew install swiftwasm/tap/carton |
222 |
| -``` |
223 |
| - |
224 |
| -If you had `carton` installed before this, make sure you have version 0.6.1 or greater: |
225 |
| - |
226 |
| -``` |
227 |
| -carton --version |
228 |
| -``` |
229 |
| - |
230 |
| -2. Create a directory for your project and make it current: |
231 |
| - |
232 |
| -``` |
233 |
| -mkdir SwiftWasmApp && cd SwiftWasmApp |
234 |
| -``` |
235 |
| - |
236 |
| -3. Initialize the project from a template with `carton`: |
237 |
| - |
238 |
| -``` |
239 |
| -carton init --template basic |
240 |
| -``` |
241 |
| - |
242 |
| -4. Build the project and start the development server, `carton dev` can be kept running |
243 |
| - during development: |
244 |
| - |
245 |
| -``` |
246 |
| -carton dev |
247 |
| -``` |
248 |
| - |
249 |
| ---- |
250 |
| - |
251 |
| -</details> |
252 |
| - |
253 |
| -Open [http://127.0.0.1:8080/](http://127.0.0.1:8080/) in your browser and a developer console |
254 |
| -within it. You'll see `Hello, world!` output in the console. You can edit the app source code in |
255 |
| -your favorite editor and save it, `carton` will immediately rebuild the app and reload all |
256 |
| -browser tabs that have the app open. |
| 46 | +Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to the project. |
257 | 47 |
|
258 | 48 | ## Sponsoring
|
259 | 49 |
|
260 | 50 | [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.
|
261 | 51 |
|
262 |
| - |
263 | 52 | <a href="https://www.emergetools.com/">
|
264 | 53 | <img src="https://github.com/swiftwasm/swift/raw/swiftwasm-distribution/assets/sponsors/emergetools.png" width="30%">
|
265 | 54 | </a>
|
0 commit comments