525cefe6bc | ||
---|---|---|
.github | ||
.vscode | ||
Sources | ||
Tests | ||
docs | ||
.editorconfig | ||
.gitignore | ||
.pre-commit-config.yaml | ||
.swift-version | ||
.swiftformat | ||
.swiftlint.yml | ||
Brewfile | ||
CHANGELOG.md | ||
CODE_OF_CONDUCT.md | ||
Dangerfile.swift | ||
LICENSE | ||
Package.resolved | ||
Package.swift | ||
README.md | ||
codecov.sh | ||
codecov.yml | ||
test.sh |
README.md
SwiftUI-compatible framework for building browser apps with WebAssembly
At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM renderer supports
a few view types and modifiers (you can check the current list in the progress document),
and a new HTML
view for constructing arbitrary HTML. The long-term goal of Tokamak is to implement
as much of SwiftUI API as possible and to provide a few more helpful additions that simplify HTML
and CSS interactions.
If there's some SwiftUI API that's missing but you'd like to use it, please review the existing
issues and PRs
to get more details about the current status, or create a new issue
to let us prioritize the development based on the demand. We also try to make the development of
views and modifiers easier (with the help from the HTML
view, see the example
below), so pull requests are very welcome! Don't
forget to check the "Contributing" section first.
Getting started
Tokamak relies on carton
as a primary build tool. Please follow
installation instructions for carton
first.
After carton
is successfully installed, type carton dev --product TokamakDemo
in the
root directory of the cloned Tokamak repository. This will build the demo project and its
dependencies and launch a development HTTP server. You can then open
http://127.0.0.1:8080/ in your browser to interact with the demo.
Example code
Tokamak API attempts to resemble SwiftUI API as much as possible. The main difference is
that you add import TokamakDOM
instead of import SwiftUI
in your files:
#if os(WASI)
import TokamakDOM
#else
import SwiftUI
#endif
struct Counter: View {
@State var count: Int
let limit: Int
var body: some View {
count < limit ?
AnyView(
VStack {
Button("Increment") { count += 1 }
Text("\(count)")
}
) : AnyView(
VStack { Text("Limit exceeded") }
)
}
}
You can then render your view in any DOM node captured with
JavaScriptKit, just
pass it as an argument to the DOMRenderer
initializer together with your view:
import JavaScriptKit
import TokamakDOM
let document = JSObjectRef.global.document.object!
let divElement = document.createElement!("div").object!
let renderer = DOMRenderer(Counter(count: 5, limit: 15), divElement)
let body = document.body.object!
_ = body.appendChild!(divElement)
Arbitrary HTML
With the HTML
view you can also render any HTML you want, including inline SVG:
struct SVGCircle: View {
var body: some View {
HTML("svg", ["width": "100", "height": "100"]) {
HTML("circle", [
"cx": "50", "cy": "50", "r": "40",
"stroke": "green", "stroke-width": "4", "fill": "yellow",
])
}
}
}
Arbitrary styles and scripts
While JavaScriptKit
is a great option for occasional interactions with JavaScript,
sometimes you need to inject arbitrary scripts or styles, which can be done through direct
DOM access:
_ = document.head.object!.insertAdjacentHTML!("beforeend", #"""
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.27.0/moment.min.js"></script>
"""#)
_ = document.head.object!.insertAdjacentHTML!("beforeend", #"""
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css">
"""#)
This way both Semantic UI styles and moment.js localized date formatting (or any arbitrary style/script/font added that way) are available in your app.
Contributing
Modular structure
Tokamak is built with modularity in mind, providing a cross-platform TokamakCore
module and
separate modules for platform-specific renderers. Currently, the only available renderer module
is TokamakDOM
, but we intend to provide other renderers in the future, such as TokamakHTML
for static websites and server-side rendering. Tokamak users only need to import a renderer module
they would like to use, while TokamakCore
is hidden as an "internal" Tokamak
package target.
Unfortunately, Swift does not allow us to specify that certain symbols in TokamakCore
are private
to a package, but they need to stay public
for renderer modules to get access to them. Thus, the
current workaround is to mark those symbols with underscores in their names to indicate this. It
can be formulated as these "rules":
- If a symbol is restricted to a module and has no
public
access control, no need for an underscore. - If a symbol is part of a public renderer module API (e.g.
TokamakDOM
), no need for an underscore, users may use those symbols directly, and it is re-exported fromTokamakCore
by the renderer module viapublic typealias
. - If a function or a type have
public
on them only by necessity to make them available inTokamakDOM
, but unavailable to users (or not intended for public use), underscore is needed to indicate that.
The benefit of separate modules is that they allow us to provide separate renderers for different platforms.
Users can pick and choose what they want to use, e.g. purely static websites would use only TokamakHTML
,
single-page apps would use TokamakDOM
, maybe in conjuction with TokamakHTML
for pre-rendering. As we'd
like to try to implement a native renderer for Android at some point, probably in a separate TokamakAndroid
module, Android apps would use TokamakAndroid
with no need to be aware of any of the web modules.
Sponsorship
If this library saved you any amount of time or money, please consider sponsoring the work of its maintainer. While some of the sponsorship tiers give you priority support or even consulting time, any amount is appreciated and helps in maintaining the project.
Coding Style
This project uses SwiftFormat and SwiftLint to enforce formatting and coding style. We encourage you to run SwiftFormat within a local clone of the repository in whatever way works best for you either manually or automatically via an Xcode extension, build phase or git pre-commit hook etc.
To guarantee that these tools run before you commit your changes on macOS, you're encouraged to run this once to set up the pre-commit hook:
brew bundle # installs SwiftLint, SwiftFormat and pre-commit
pre-commit install # installs pre-commit hook to run checks before you commit
Refer to the pre-commit documentation page for more details and installation instructions for other platforms.
SwiftFormat and SwiftLint also run on CI for every PR and thus a CI build can fail with inconsistent formatting or style. We require CI builds to pass for all PRs before merging.
Code of Conduct
This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to conduct@tokamak.dev.
Maintainers
Carson Katri, Jed Fox, Max Desiatov.
Acknowledgments
- Thanks to the Swift community for building one of the best programming languages available!
- Thanks to SwiftWebUI for reverse-engineering some of the bits of SwiftUI and kickstarting the front-end Swift ecosystem for the web.
- Thanks to Render, ReSwift, Katana UI and Komponents for inspiration!
License
Tokamak is available under the Apache 2.0 license. See the LICENSE file for more info.