Swiftpack.co - Package - TokamakUI/Tokamak
Tokamak logo

SwiftUI-compatible framework for building browser apps with WebAssembly

CI status

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.

If you'd like to participate in the growing SwiftWasm community, you're also very welcome to join the #webassembly channel in the SwiftPM Slack.

Example code

Tokamak API attempts to resemble SwiftUI API as much as possible. The main difference is that you use import TokamakShim instead of import SwiftUI in your files. The former makes your views compatible with Apple platforms, as well as platforms supported by Tokamak (currently only WebAssembly/WASI with more coming in the future):

import TokamakShim

struct Counter: View {
  @State var count: Int
  let limit: Int

  var body: some View {
    if count < limit {
      VStack {
        Button("Increment") { count += 1 }
        Text("\(count)")
      }
      .onAppear { print("Counter.VStack onAppear") }
      .onDisappear { print("Counter.VStack onDisappear") }
    } else {
      VStack { Text("Limit exceeded") }
    }
  }
}

struct CounterApp: App {
  var body: some Scene {
    WindowGroup("Counter Demo") {
      Counter(count: 5, limit: 15)
    }
  }
}

// @main attribute is not supported in SwiftPM apps.
// See https://bugs.swift.org/browse/SR-12683 for more details.
CounterApp.main()

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:

import JavaScriptKit

_ = 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.

Requirements for app developers

  • macOS 10.15 and Xcode 11.4 or later.
  • Swift 5.2 or later and Ubuntu 18.04 if you'd like to use Linux. Other Linux distributions are currently not supported.

Requirements for app users

Any browser that supports WebAssembly should work, which currently includes:

  • Edge 16+
  • Firefox 53+
  • Chrome 57+
  • (Mobile) Safari 11+

Not all of these were tested though, compatibility reports are very welcome!

Getting started

Tokamak relies on carton as a primary build tool. As a part of these steps you'll install carton via Homebrew on macOS (unfortunately you'll have to build it manually on Linux). Assuming you already have Homebrew installed, you can create a new Tokamak app 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
  1. Create a directory for your project and make it current:
mkdir TokamakApp && cd TokamakApp
  1. Initialize the project from a template with carton:
carton init --template tokamak
  1. Build the project and start the development server, carton dev can be kept running during development:
carton dev
  1. Open http://127.0.0.1:8080/ in your browser to see the app running. 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.

You can also clone this repository and run carton dev in its root directory. This will build the demo app that shows almost all of the currently implemented APIs.

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 modules are TokamakDOM and TokamakStaticHTML, the latter can be used for static websites and server-side rendering. If you'd like to implement your own custom renderer, please refer to our renderers guide for more details.

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":

  1. If a symbol is restricted to a module and has no public access control, no need for an underscore.
  2. 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 from TokamakCore by the renderer module via public typealias.
  3. If a function or a type have public on them only by necessity to make them available in TokamakDOM, 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 TokamakStaticHTML, single-page apps would use TokamakDOM, maybe in conjuction with TokamakStaticHTML 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.

Coding Style

This project uses SwiftFormat and SwiftLint to enforce formatting and coding style. SwiftFormat 0.45.3 and SwiftLint 0.39.2 or later versions are recommended. We encourage you to run SwiftFormat and SwiftLint within a local clone of the repository in whatever way works best for you. You can do that either manually, or automatically with VSCode extensions for SwiftFormat and SwiftLint respectively, or with the Xcode extension, or build phase.

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.

Sponsorship

If this library saved you any amount of time or money, please consider sponsoring the work of its maintainers on their sponsorship pages: @carson-katri, @kateinoigakukun, and @MaxDesiatov. While some of the sponsorship tiers give you priority support or even consulting time, any amount is appreciated and helps in maintaining the project.

Maintainers

Carson Katri, Jed Fox, Max Desiatov.

Acknowledgments

  • Thanks to the Swift community for building one of the best programming languages available!
  • Thanks to everyone who developed React with its reconciler/renderer architecture that inspired Tokamak in the first place.
  • Thanks to the designers of the SwiftUI API who showed us how to write UI apps in Swift declaratively (arguably even in a better way than React did).
  • 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!

SwiftUI is a trademark owned by Apple Inc. Software maintained as a part of the Tokamak project is not affiliated with Apple Inc.

License

Tokamak is available under the Apache 2.0 license. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the LICENSE file for more info.

Github

link
Stars: 797

Used By

Total: 0

Releases

0.4.0 - 2020-09-30 10:24:17

This is mainly a bugfix and compatibility release with a small feature addition. Namely, Slider view is introduced in the DOM renderer, and binding updates for SVG elements are working now. During this development cycle efforts of our team were devoted to recently released JavaScriptKit 0.7 and carton 0.6. Both of those releases are pretty big updates that improve developer experience significantly, and this version of Tokamak requires those as minimum versions.

Many thanks to @j-f1 and @kateinoigakukun for their contributions to these updates!

Closed issues:

  • HTML + Binding (#278)

Merged pull requests:

- 2020-08-19 09:19:35

This release improves compatibility with the SwiftUI API and fixes bugs in our WebAssembly/DOM renderer, included but not limited to:

  • support for App/Scene lifecycle;
  • ColorScheme detection and environment setting;
  • dark mode styles;
  • @StateObject property wrapper implementation;
  • SidebarListStyle, ButtonStyle, GeometryProxy types;
  • NavigationView and GeometryReader views.

Additionally, new TokamakStaticHTML renderer was added that supports rendering stateless views into static HTML that doesn't include any JavaScript or WebAssembly dependencies. This is useful for static websites and in the future could be used together with TokamakDOM for server-side rendering.

Tokamak 0.3.0 now requires 5.3 snapshots of SwiftWasm, which in general should be more stable than the development snapshots that were previously used, and is also compatible with Xcode 12 betas. If you have a .swift-version file in your project, you should specify wasm-5.3-SNAPSHOT-2020-07-27-a in it or a later 5.3 snapshot, otherwise carton 0.5 selects a compatible 5.3 snapshot for you automatically. Allowing carton to select a default snapshot is the recommended approach, so in general we recommend avoiding .swif-version files in projects that use Tokamak.

Many thanks to @carson-katri, @j-f1, and @Outcue for their contributions to this release.

The complete list of changes included in this release is available below.

Closed issues:

  • Command "carton dev" failed (#258)
  • Dark mode detection causes crashes in Safari (#245)
  • Add dark color scheme style (#237)
  • Establish App lifecycle as the only way to start rendering (#224)
  • Runtime issues with dynamic properties in App types (#222)
  • List appearance changes when reloaded (#212)
  • List scrolling does not work on Firefox 78 on macOS (#211)
  • Scrolling broken when List is child of NavigationView (#208)
  • Rectangle frame is not being set properly (#185)
  • Implement SidebarListStyle (#180)
  • Implement GeometryReader/GeometryProxy (#176)
  • @StateObject support (#158)
  • NavigationView/NavigationLink (#129)

Merged pull requests:

WebAssembly support - 2020-07-21 18:49:33

This is the first release that supports WebAssembly and browsers with the new TokamakDOM module. The API now closely follows SwiftUI, while the new React-like API is no longer available. Unfortunately, since older versions of iOS don't support opaque types, and you already can use SwiftUI on iOS versions that do support it, iOS and macOS renderers are no longer available. Many thanks to @carson-katri, @j-f1, @helje5, @hodovani, @Outcue, @filip-sakel and @noontideox for their contributions to this release!

Closed issues:

  • State vars have to be first (#190)
  • Implicit 8 pixel margin added to html body (#188)
  • Unable to set Color value (#186)
  • Crash in protocol conformance code (#167)
  • Extend Path to match the SwiftUI API (#156)
  • Some primitive Views cannot access @Environment (#139)
  • Logo for the project (#132)
  • ZStack? (#111)
  • View has - by default - a Body of type Never. (#110)
  • Getting value of type 'String' has no member 'components' (#108)
  • Does iOS 10 work? (#105)
  • Add Tokamak project linter (#77)
  • Ambiguous reference to member 'node' (#68)

Merged pull requests:

- 2019-03-18 13:19:01

Update example code in README for CocoaPods page.

- 2019-03-18 10:31:04

Update rendered README.md on CocoaPods page.

- 2019-03-18 10:21:07

First release with an iOS renderer for UIKit and a basic macOS renderer for AppKit.