|
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