The web development world is vast, and it's easy to feel lost in the constant stream of new technologies emerging every day. Most of these new technologies are built using JavaScript or TypeScript. However, in this article, I will introduce you to web development using native Swift, directly inside your browser, and I'm confident it will impress you. How is it possible? For Swift to work natively on the web page it needs to be compiled to WebAssembly byte-code first and then JavaScript could load that code onto the page. The whole process of compilation is a bit tricky since we need to use the special toolchain and build helper files, that’s why there are helper CLI-tools available: Carton and Webber. Is it ok to use a third-party toolchain? The community has done a tremendous amount of work to make it possible to compile Swift into WebAssembly by patching the original Swift toolchain. They update the toolchain by automatically pulling changes from the original toolchain every day and fixing their fork if tests fail. Their goal is to become a part of the official toolchain and they hope that it will happen in the near future. SwiftWasm Carton or Webber? is made by the SwiftWasm community and can be used for Tokamak projects, which is a framework that gives you the ability to write websites using SwiftUI. Carton is made for SwifWeb projects. SwifWeb is different in that it wraps the entire HTML and CSS standards, as well as all of the web APIs. Webber Although you may prefer to write web apps using SwiftUI for code consistency, I believe this is the wrong approach because web development is inherently different and cannot be approached the same way as SwiftUI. That's why I created SwifWeb, which gives you the ability to use all the power of HTML, CSS, and web APIs directly from Swift, using its beautiful syntax with auto-completion and documentation. And I created the Webber tool because Carton is not able to compile, debug and deploy SwifWeb apps the right way because it wasn’t created for it. My name is Mikhail Isaev, and I am the author of SwifWeb. In this article, I will show you how to start building a website using the SwifWeb. Required tools Swift You need to have Swift installed, the easiest way to have it: on macOS is to install Xcode on Linux or Windows(WSL2) is to use script from swiftlang.xyz In other cases take a look at installation instructions on the official website Webber CLI I created Webber to help you to build, debug and deploy your apps. On macOS it is easy to install with HomeBrew (install it from their ) website brew install swifweb/tap/webber to update to the latest version later just run brew upgrade webber On Ubuntu or Windows(Ubuntu in WSL2) clone and compile Webber manually sudo apt-get install binaryen curl https://get.wasmer.io -sSfL | sh apt-get install npm cd /opt sudo git clone https://github.com/swifweb/webber cd webber sudo swift build -c release sudo ln -s /opt/webber/.build/release/Webber /usr/local/bin/webber to update to the last version later run cd /opt/webber sudo git pull sudo swift build -c release branch always contains stable code so feel free to pull updates from it main Creating new project Open the terminal and execute webber new In the interactive menu choose or , and enter the project name. pwa spa Change the directory to the newly created project and execute . webber serve This command will compile your project into WebAssembly, package all necessary files inside a special folder, and start serving your project on all interfaces using port by default. .webber 8888 Additional arguments for webber serve App type for Progressive Web App -t pwa for Single Web App -t spa Name of service worker target (usually named in PWA project) Service -s Service Name of app target ( by default) App -a App Print more info in console -v Port for the Webber server (default is ) 8888 -p 8080 Use to test like real SSL (with allowed self-signed SSL setting) -p 443 Destination browser name to automatically launch in or --browser safari --browser chrome Additional instance of browser with allowed self-signed SSL setting to debug service-workers --browser-self-signed Additional instance of browser in incognito mode --browser-incognito Application The app begins in Sources/App/App.swift import Web @main class App: WebApp { @AppBuilder override var app: Configuration { Lifecycle.didFinishLaunching { app in app.registerServiceWorker("service") } Routes { Page { IndexPage() } Page("login") { LoginPage() } Page("article/:id") { ArticlePage() } Page("**") { NotFoundPage() } } MainStyle() } } Lifecycle It works in an iOS-like manner: when the app just started didFinishLaunching when the app is going to die willTerminate when the window is going to be inactive willResignActive when the window is active didBecomeActive when the window is going into the background didEnterBackground when the window is going into the foreground willEnterForeground The most useful method here is because it is a great place to configure the app. You see it feels really like an iOS app! 😀 didFinishLaunching Here contains useful convenience methods: app call to register PWA service worker registerServiceWorker(“serviceName“) call to add relative or external script addScript(“path/to/script.js“) call to add relative or external style addStylesheet(“path/to/style.css“) call to add relative or external font, optionally set type addFont(“path/to/font.woff”, type:) call to add icon, optionally set type and color addIcon(“path/to/icon“, type:color:) Also, it is the place to configure additional libraries like , , , etc. Autolayout Bootstrap Materialize Routes Routing is needed to show the appropriate page based on the current URL. To understand how to use routing you have to understand what URL is https://website.com/hello/world - here is the /hello/world path As you have seen, in the beginning, in the App class we should declare all the top-level routes. Top-level means that pages declared in these routes will take the whole space in the window. Ok, so for example the root route can be set three ways Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() } I think the last one is the most beautiful 🙂 Login or registration routes can be set like this Page("login") { LoginPage() } Page("registration") { RegistrationPage() } Parameter-related routes Page("article/:id") { ArticlePage() } The in the example above is a dynamic part of the route. We can retrieve this identifier in the class to display the article associated with it. :id ArticlePage class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } } You can have more than one parameter in the path. Retrieve all of them in the same way. Queries The next interesting thing in the path is the , which is also very easy to use. For example, let's consider the route, which expects to have the search and query parameters. query /search text age https://website.com/search**?text=Alex&age=19** - the last part is the query Simply declare the search route Page("search") { SearchPage() } And retrieve query data in the class like this SearchPage class SearchPage: PageController { struct Query: Decodable { let text: String? let age: Int? } override func didLoad(with req: PageRequest) { do { let query = try req.query.decode(Query.self) // use optional query.text and query.age // to query search results } catch { print("Can't decode query: \(error)") } } } Anything You also can use to declare route which accept anything in the specific path part like this * Page("foo", "*", "bar") { SearchPage() } The route above will accept anything between foo and bar, e.g. /foo/aaa/bar, /foo/bbb/bar, etc. Catch-all With sign you may set a special catch-all route that will handle anything that hasn’t been matched to other routes at a specific path. ** Use it to either make global 404 route Page("**") { NotFoundPage() } or for specific path e.g. when user not found Page("user", "**") { UserNotFoundPage() } Let’s clarify situations with routes declared above /user/1 - if there is a route for /user/:id then it will return . Otherwise, it will fall into… UserPage UserNotFoundPage /user/1/hello - if there are route for /user/:id/hello then it will fall into UserNotFoundPage /something - if there are no route for /something then it will fall into NotFoundPage Nested routing We may not want to replace the entire content on the page for the next route, but only certain blocks. This is where the comes in handy! FragmentRouter Let's consider that we have tabs on the page. Each tab is a subroute, and we want to react to changes in the subroute using the . /user FragmentRouter Declare the top-level route in the class App Page("user") { UserPage() } And declare in the class FragmentRouter UserPage class UserPage: PageController { @DOM override var body: DOM.Content { // NavBar is from Materialize library :) Navbar() .item("Profile") { self.changePath(to: "/user/profile") } .item("Friends") { self.changePath(to: "/user/friends") } FragmentRouter(self) .routes { Page("profile") { UserProfilePage() } Page("friends") { UserFriendsPage() } } } } In the example above, handles and subroutes and renders it under the , so the page never reloads the whole content but only specific fragments. FragmentRouter /user/profile /user/friends Navbar There are also may be declared more than one fragment with the same or different subroutes and they all will just work together like magic! Btw is a and you may configure it by calling FragmentRouter Div FragmentRouter(self) .configure { div in // do anything you want with the div } Stylesheets You can use traditional CSS files, but you also have the new, magical ability to use a stylesheet written in Swift! Basics To declare a CSS rule using Swift we have object. Rule It can be built declaratively by calling its methods Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto) or SwiftUI-like way using @resultBuilder Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) } Both ways are equal, however, I prefer the first one because of the autocompletion right after I type 😀 . All CSS methods described on MDN are available. More than that it handles browser prefixes automatically! However, you can set custom property this way in some specific case Rule(...selector...) .custom("customKey", "customValue") Selector To set which elements Rule should affect we have to set a selector. I see the selector as the query in the database, but parts of that selector query I call pointers. The easiest way to build a pointer is to initialize it using the raw string Pointer("a") But the right swifty way is to build it by calling at needed HTML tag like this .pointer H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId It is about basic pointers, but they also have modifiers like etc. :hover :first :first-child H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover You can declare any existing modifier, they are all available. If something is missing don’t hesitate to make an extension to add it! And don’t forget to send pull request on github to add it for everyone. You also can concatenate pointers H1.class(.myClass) // h1.myClass H1.id(.myId) // h1#myId H1.id(.myId).disabled // h1#myId:disabled Div.pointer.inside(P.pointer) // div p Div.pointer.parent(P.pointer) // div > p Div.pointer.immediatedlyAfter(P.pointer) // Div + p P.pointer.precededBy(Ul.pointer) // p ~ ul How to use the selector in the Rule Rule(Pointer("a")) // or Rule(A.pointer) How to use more than one selector in the Rule Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer)) It produces the following CSS code a, h1#myId, div > p { } Reactivity Let’s declare dark and light styles for our app, and later, we will be able to easily switch between them. import Web @main class App: WebApp { enum Theme { case light, dark } @State var theme: Theme = .light @AppBuilder override var app: Configuration { // ... Lifecycle, Routes ... LightStyle().disabled($theme.map { $0 != .happy }) DarkStyle().disabled($theme.map { $0 != .sad }) } } and may be declared in separate files or e.g. in the App.swift LightStyle DarkStyle class LightStyle: Stylesheet { @Rules override var rules: Rules.Content { Rule(Body.pointer).backgroundColor(.white) Rule(H1.pointer).color(.black) } } class DarkStyle: Stylesheet { @Rules override var rules: Rules.Content { Rule(Body.pointer).backgroundColor(.black) Rule(H1.pointer).color(.white) } } And then somewhere in the UI of some page just call App.current.theme = .light // to switch to light theme // or App.current.theme = .dark // to switch to dark theme And it will activate or deactivate the related stylesheets! Isn't that cool? 😎 But you may say that describing styles in Swift instead of CSS is harder, so what’s the point? The main point is reactivity! We can use @State with CSS properties and change values on the fly! Just take a look, we can create a class with some reactive property and change it anytime in runtime, so any element on the screen which uses that class will be updated! It is much more effective than switching classes for many elements! import Web @main class App: WebApp { @State var reactiveColor = Color.cyan @AppBuilder override var app: Configuration { // ... Lifecycle, Routes ... MainStyle() } } extension Class { static var somethingCool: Class { "somethingCool" } } class MainStyle: Stylesheet { @Rules override var rules: Rules.Content { // for all elements with `somethingCool` class Rule(Class.hello.pointer) .color(App.current.$reactiveColor) // for H1 and H2 elements with `somethingCool` class Rule(H1.class(.hello), H2.class(.hello)) .color(App.current.$reactiveColor) } } Later from any place in the code just call App.current.reactiveColor = .yellow // or any color you want and it will update the color in the Stylesheet and in all the elements that uses it 😜 Also, it is possible to add raw CSS into a Stylesheet class MainStyle: Stylesheet { @Rules override var rules: Rules.Content { // for all elements with `somethingCool` class Rule(Class.hello.pointer) .color(App.current.$reactiveColor) // for H1 and H2 elements with `somethingCool` class Rule(H1.class(.hello), H2.class(.hello)) .color(App.current.$reactiveColor) """ /* Raw CSS goes here */ body { margin: 0; padding: 0; } """ } } you can mix-in raw CSS strings as many times as needed Pages Router is rendering pages on each route. Page is any class inherited from the . PageController has lifecycle methods like , UI methods and , and property wrapper variable for HTML elements. PageController willLoad didLoad willUnload didUnload buildUI body Technically is just a Div and you may set any its properties in method. PageController buildUI class IndexPage: PageController { // MARK: - Lifecycle override func willLoad(with req: PageRequest) { super.willLoad(with: req) } override func didLoad(with req: PageRequest) { super.didLoad(with: req) // set page title and metaDescription self.title = "My Index Page" self.metaDescription = "..." // also parse query and hash here } override func willUnload() { super.willUnload() } override func didUnload() { super.didUnload() } // MARK: - UI override func buildUI() { super.buildUI() // access any properties of the page's div here // e.g. self.backgroundcolor(.lightGrey) // optionally call body method here to add child HTML elements body { P("Hello world") } // or alternatively self.appendChild(P("Hello world")) } // the best place to declare any child HTML elements @DOM override var body: DOM.Content { H1("Hello world") P("Text under title") Button("Click me") { self.alert("Click!") print("button clicked") } } } If your page is tiny little you may declare it even this short way PageController { page in H1("Hello world") P("Text under title") Button("Click me") { page.alert("Click!") print("button clicked") } } .backgroundcolor(.lightGrey) .onWillLoad { page in } .onDidLoad { page in } .onWillUnload { page in } .onDidUnload { page in } Isn’t it beautiful and laconic? 🥲 Bonus convenience methods - direct JS method alert(message: String) alert - switching URL path changePath(to: String) HTML Elements Finally, I will tell you how(!) to build and use HTML elements! All HTML elements with their attributes are available in Swift, the full list is e.g. on . MDN Just an example short list of HTML elements: SwifWeb code HTML Code Div() <div></div> H1(“text“) <h1>text</h1> A(“Click me“).href(““).target(.blank) <a href=”” target=”_blank”>Click me</a> Button(“Click“).onClick { print(“click“) } <button onclick=”…”>Click</button> InputText($text).placeholder("Title") <input type=”text” placeholder=”title”> InputCheckbox($checked) <input type=”checkbox”> As you can see, it is very easy to access any HTML tag in Swift because they are all represented under the same name, except for inputs. This is because different input types have different methods, and I didn't want to mix them. Simple Div Div() we can access all its attributes and style properties like this Div().class(.myDivs) // <div class="myDivs"> .id(.myDiv) // <div id="myDiv"> .backgroundColor(.green) // <div style="background-color: green;"> .onClick { // adds `onClick` listener directly to the DOM element print("Clicked on div") } .attribute("key", "value") // <div key="value"> .attribute("boolKey", true, .trueFalse) // <div boolKey="true"> .attribute("boolKey", true, .yesNo) // <div boolKey="yes"> .attribute("checked", true, .keyAsValue) // <div checked="checked"> .attribute("muted", true, .keyWithoutValue) // <div muted> .custom("border", "2px solid red") // <div style="border: 2px solid red;"> Subclassing Subclass HTML element to predefine style for it, or to make a composite element with a lot of predefined child elements and some convenient methods available outside, or to achieve lifecycle events like and . didAddToDOM didRemoveFromDOM Let’s create a element which is just a but with predefined class Divider Div .divider public class Divider: Div { // it is very important to override the name // because otherwise it will be <divider> in HTML open class override var name: String { "\(Div.self)".lowercased() } required public init() { super.init() } // this method executes immediately after any init method public override func postInit() { super.postInit() // here we are adding `divider` class self.class(.divider) } } It is very important to call super methods when subclassing. Without it you may experience unexpected behaviour. Appending to DOM The Element can be appended to the DOM of or right away or later. PageController HTML element Right away Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } } Or later using lazy var lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 } So you can declare an in advance and add it to the DOM any time later! HTML element Removing from DOM lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove() Access parent element Any HTML element has an optional superview property which gives access to its parent if it is added to the DOM Div().superview?.backgroundColor(.red) if/else conditions We often need to show elements only in certain conditions so let’s use for that if/else lazy var myDiv1 = Div() lazy var myDiv2 = Div() lazy var myDiv3 = Div() var myDiv4: Div? var showDiv2 = true Div { myDiv1 if showDiv2 { myDiv2 } else { myDiv3 } if let myDiv4 = myDiv4 { myDiv4 } else { P("Div 4 was nil") } } But it is not reactive. If you try to set to nothing happens. showDiv2 false Reactive example lazy var myDiv1 = Div() lazy var myDiv2 = Div() lazy var myDiv3 = Div() @State var showDiv2 = true Div { myDiv1 myDiv2.hidden($showDiv2.map { !$0 }) // shows myDiv2 if showDiv2 == true myDiv3.hidden($showDiv2.map { $0 }) // shows myDiv3 if showDiv2 == false } Why should we use $showDiv2.map {…} ? In sort: because it is not SwiftUI. At all. Read more about @State below. Raw HTML You also may need to add raw HTML into the page or HTML element and it is easily possible Div { """ <a href="https://google.com">Go to Google</a> """ } ForEach Static example let names = ["Bob", "John", "Annie"] Div { ForEach(names) { name in Div(name) } // or ForEach(names) { index, name in Div("\(index). \(name)") } // or with range ForEach(1...20) { index in Div() } // and even like this 20.times { Div().class(.shootingStar) } } Dynamic example @State var names = ["Bob", "John", "Annie"] Div { ForEach($names) { name in Div(name) } // or with index ForEach($names) { index, name in Div("\(index). \(name)") } } Button("Change 1").onClick { // this will append new Div with name automatically self.names.append("George") } Button("Change 2").onClick { // this will replace and update Divs with names automatically self.names = ["Bob", "Peppa", "George"] } CSS Same as in examples above, but also is available BuilderFunction Stylesheet { ForEach(1...20) { index in CSSRule(Div.pointer.nthChild("\(index)")) // set rule properties depending on index } 20.times { index in CSSRule(Div.pointer.nthChild("\(index)")) // set rule properties depending on index } } You can use in loops to calculate some value one time only like a value in the following example BuilderFunction ForEach delay ForEach(1...20) { index in BuilderFunction(9999.asRandomMax()) { delay in CSSRule(Pointer(".shooting_star").nthChild("\(index)")) .custom("top", "calc(50% - (\(400.asRandomMax() - 200)px))") .custom("left", "calc(50% - (\(300.asRandomMax() + 300)px))") .animationDelay(delay.ms) CSSRule(Pointer(".shooting_star").nthChild("\(index)").before) .animationDelay(delay.ms) CSSRule(Pointer(".shooting_star").nthChild("\(index)").after) .animationDelay(delay.ms) } } It can also take function as an argument BuilderFunction(calculate) { calculatedValue in // CSS rule or DOM element } func calculate() -> Int { return 1 + 1 } is available for HTML elements too :) BuilderFunction Reactivity with @State is the most desirable thing nowadays for declarative . @State programming As I told you above: it is not SwiftUI, so there is no global state machine that tracks and redraws everything. And HTML elements are not temporary structs but classes, so they are real objects and you have direct access to them. It is much better and flexible, you have all the control. What is it under the hood? It is a property wrapper which notifies all the subscribers about its changes. How to subscribe to changes? enum Countries { case usa, australia, mexico } @State var selectedCounty: Countries = .usa $selectedCounty.listen { print("country changed") } $selectedCounty.listen { newValue in print("country changed to \(newValue)") } $selectedCounty.listen { oldValue, newValue in print("country changed from \(oldValue) to \(newValue)") } How HTML elements can react to changes? Simple text example @State var text = "Hello world!" H1($text) // whenever text changes it updates inner-text in H1 InputText($text) // while user is typing text it updates $text which updates H1 Simple number example @State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div Simple boolean example @State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div Mapping example @State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" }) Mapping two states @State var one = true @State var two = true Div().display($one.and($two).map { one, two in // returns .block if both one and two are true one && two ? .block : .none }) Mapping more than two states @State var one = true @State var two = true @State var three = 15 Div().display($one.and($two).map { one, two in // returns true if both one and two are true one && two }.and($three).map { oneTwo, three in // here oneTwo is a result of the previous mapping // returns .block if oneTwo is true and three is 15 oneTwo && three == 15 ? .block : .none }) All HTML and CSS properties can handle values @State Extensions Extend HTML elements You can add some convenient methods to concrete elements like Div extension Div { func makeItBeautiful() {} } Or groups of elements if you know their parent . class There are few parent classes. - is for elements which can be initialized with string, like , , etc. BaseActiveStringElement a h1 - is for all elements that can have content inside of it, like , , etc. BaseContentElement div ul - is for all elements BaseElement So extension for all elements can be written this way extension BaseElement { func doSomething() {} } Declare colors class is responsible for colors. It has predefined HTML-colors, but you can have your own Color extension Color { var myColor1: Color { .hex(0xf1f1f1) } // which is 0xF1F1F1 var myColor2: Color { .hsl(60, 60, 60) } // which is hsl(60, 60, 60) var myColor3: Color { .hsla(60, 60, 60, 0.8) } // which is hsla(60, 60, 60, 0.8) var myColor4: Color { .rgb(60, 60, 60) } // which is rgb(60, 60, 60) var myColor5: Color { .rgba(60, 60, 60, 0.8) } // which is rgba(60, 60, 60, 0.8) } Then use it like H1(“Text“).color(.myColor1) Declare classes extension Class { var my: Class { "my" } } Then use it like Div().class(.my) Declare ids extension Id { var myId: Id { "my" } } Then use it like Div().id(.my) Web APIs Window object is fully wrapped and accessible via variable. window App.current.window Full reference is available on . MDN Let’s do the short overview down below Foreground Flag You can listen for it in in the or directly this way Lifecycle App.swift App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed } or just read it anytime anywhere if App.current.window.isInForeground { // do somethign } or react on it with HTML element Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white }) Active Flag It is the same as Foreground flag, but accessible via App.current.window.isActive It detects if a user is still interacting inside the window. Online status Same as the Foreground flag, but accessible via App.current.window.isOnline It detects if a user still has access to the internet. Darkmode status Same as the Foreground flag, but accessible via App.current.window.isDark It detects if a user’s browser or operating system is in dark mode. Inner Size The size of the window's content area (viewport) including scrollbars is object within and values inside. App.current.window.innerSize Size width height Also available as variable. @State Outer Size The size of the browser window, including toolbars/scrollbars. is object within and values inside. App.current.window.outerSize Size width height Also available as variable. @State Screen Special object for inspecting properties of the screen on which the current window is being rendered. Available via . App.current.window.screen The most interesting property is usually . pixelRatio History Contains the URLs visited by the user (within a browser window). Available via or just . App.current.window.history History.shared It is accessible as variable, so you can listen for its changes if needed. @State App.current.window.$history.listen { history in // read history properties } It is also accessible as simple variable History.shared.length // size of the history stack History.shared.back() // to go back in history stack History.shared.forward() // to go forward in history stack History.shared.go(offset:) // going to specific index in history stack More details are available on . MDN Location Contains information about the current URL. Available via or just . App.current.window.location Location.shared It is accessible as variable, so you can listen for its changes if needed. @State This is how router works for example. App.current.window.$location.listen { location in // read location properties } It is also accessible as a simple variable Location.shared.href // also $href Location.shared.host // also $host Location.shared.port // also $port Location.shared.pathname // also $pathname Location.shared.search // also $search Location.shared.hash // also $hash More details are available on . MDN Navigator Contains information about the browser. Available via or just App.current.window.navigator Navigator.shared The most interesting properties usually . userAgent platform language cookieEnabled LocalStorage Allows to save key/value pairs in a web browser. Stores data with no expiration date. Available as or just . App.current.window.localStorage LocalStorage.shared // You can save any value that can be represented in JavaScript LocalStorage.shared.set("key", "value") // saves String LocalStorage.shared.set("key", 123) // saves Int LocalStorage.shared.set("key", 0.8) // saves Double LocalStorage.shared.set("key", ["key":"value"]) // saves Dictionary LocalStorage.shared.set("key", ["v1", "v2"]) // saves Array // Getting values back LocalStorage.shared.string(forKey: "key") // returns String? LocalStorage.shared.integer(forKey: "key") // returns Int? LocalStorage.shared.string(forKey: "key") // returns String? LocalStorage.shared.value(forKey: "key") // returns JSValue? // Removing item LocalStorage.shared.removeItem(forKey: "key") // Removing all items LocalStorage.shared.clear() Tracking changes LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") } Tracking all items removal LocalStorage.onClear { print("LocalStorage: all items has been removed") } SessionStorage Allows to save key/value pairs in a web browser. Stores data for only one session. Available as or just . App.current.window.sessionStorage SessionStorage.shared API is absolutely same as in described above. LocalStorage Document Represents any web page loaded in the browser and serves as an entry point into the web page's content. Available via . App.current.window.document App.current.window.document.title // also $title App.current.window.document.metaDescription // also $metaDescription App.current.window.document.head // <head> element App.current.window.document.body // <body> element App.current.window.documentquerySelector("#my") // returns BaseElement? App.current.window.document.querySelectorAll(".my") // returns [BaseElement] Localization Static localization Classic localization is automatic and depends on the user system language How to use H1(String( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは"))) Dynamic localization If you want to change localized strings on the screen on-the-fly (without page reloading) You could change the current language by calling Localization.current = .es If you saved the user's language somewhere in cookies or localstorage then you have to set it on the app launch Lifecycle.didFinishLaunching { Localization.current = .es } How to use H1(LString( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは"))) Advanced example H1(Localization.currentState.map { "Curent language: \($0.rawValue)" }) H2(LString(.en("English string"), .es("Hilo Español"))) Button("change lang").onClick { Localization.current = Localization.current.rawValue.contains("en") ? .es : .en } Fetch import FetchAPI Fetch("https://jsonplaceholder.typicode.com/todos/1") { switch $0 { case .failure: break case .success(let response): print("response.code: \(response.status)") print("response.statusText: \(response.statusText)") print("response.ok: \(response.ok)") print("response.redirected: \(response.redirected)") print("response.headers: \(response.headers.dictionary)") struct Todo: Decodable { let id, userId: Int let title: String let completed: Bool } response.json(as: Todo.self) { switch $0 { case .failure(let error): break case .success(let todo): print("decoded todo: \(todo)") } } } } XMLHttpRequest import XMLHttpRequest XMLHttpRequest() .open(method: "GET", url: "https://jsonplaceholder.typicode.com/todos/1") .onAbort { print("XHR onAbort") }.onLoad { print("XHR onLoad") }.onError { print("XHR onError") }.onTimeout { print("XHR onTimeout") }.onProgress{ progress in print("XHR onProgress") }.onLoadEnd { print("XHR onLoadEnd") }.onLoadStart { print("XHR onLoadStart") }.onReadyStateChange { readyState in print("XHR onReadyStateChange") } .send() WebSocket import WebSocket let webSocket = WebSocket("wss://echo.websocket.org").onOpen { print("ws connected") }.onClose { (closeEvent: CloseEvent) in print("ws disconnected code: \(closeEvent.code) reason: \(closeEvent.reason)") }.onError { print("ws error") }.onMessage { message in print("ws message: \(message)") switch message.data { case .arrayBuffer(let arrayBuffer): break case .blob(let blob): break case .text(let text): break case .unknown(let jsValue): break } } Dispatch.asyncAfter(2) { // send as simple string webSocket.send("Hello from SwifWeb") // send as Blob webSocket.send(Blob("Hello from SwifWeb")) } Console Simple is equivalent of in JavaScript print(“Hello world“) console.log(‘Hello world‘) methods are also wrapped with love ❤️ Console Console.dir(...) Console.error(...) Console.warning(...) Console.clear() LivePreview To make live preview work declare WebPreview class in each file you want. class IndexPage: PageController {} class Welcome_Preview: WebPreview { @Preview override class var content: Preview.Content { Language.en Title("Initial page") Size(640, 480) // add here as many elements as needed IndexPage() } } Xcode Please read the instruction on . It is tricky but fully working solution 😎 repository page VSCode Go to inside and search . Extensions VSCode Webber Once it is installed press (or on Linux/Windows) Cmd+Shift+P Ctrl+Shift+P Find and launch . Webber Live Preview On the right side, you will see the live preview window and it refreshes whenever you save the file which contains class. WebPreview Access to JavaScript It is available through which is the foundation of the . JavaScriptKit SwifWeb Read how to you is in . the official repository Resources You can add , , , , and any other static resources inside of the project. css js png jpg But to have them available or in final files you have to declare them all in the like this during debug release Package.swift .executableTarget(name: "App", dependencies: [ .product(name: "Web", package: "web") ], resources: [ .copy("css/*.css"), .copy("css"), .copy("images/*.jpg"), .copy("images/*.png"), .copy("images/*.svg"), .copy("images"), .copy("fonts/*.woff2"), .copy("fonts") ]), Later you will be able to access them e.g. like this Img().src(“/images/logo.png“) Debugging Launch the following way Webber just to quickly launch it webber serve to launch it in PWA mode webber serve -t pwa -s Service Additional parameters or to show more info in console for debugging purposes -v --verbose or to start webber server on 443 port instead of default 8888 -p 443 --port 443 to automatically open desired browser, by default it doesn't open any --browser chrome/safari needed to debug service workers locally, otherwise they doesn't work --browser-self-signed to open additional instance of browser in incognito mode, works only with chrome --browser-incognito So to build your app in debug mode, automatically open it in Chrome and refresh browser automatically whenever you change any file launch it this way for SPA webber serve --browser chrome for real PWA testing webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito Initial app loading You may want to improve the initial loading process. For that just open folder inside of the project and edit file. .webber/entrypoint/dev index.html It contains initial HTML code with very useful listeners: . WASMLoadingStarted WASMLoadingStartedWithoutProgress WASMLoadingProgress WASMLoadingError You are free to edit that code to whatever you want to implement your custom style 🔥 When you finish the new implementation don’t forget to save same into folder .webber/entrypoint/release Building Release Simply execute or for PWA. webber release webber release -t pwa -s Service Then grab compiled files from the folder and upload them to your server. .webber/release How to deploy You can upload your files to any static hosting. Hosting should provide the correct Content-Type for files! wasm Yes, it is very important to have the correct header for files, otherwise unfortunately browser will not be able to load your WebAssembly application. Content-Type: application/wasm wasm For example doesn’t provide the correct Content-Type for files so so unfortunately it is impossible to host WebAssembly sites on it. GithubPages wasm Nginx If you use your own server with nginx, then open and check if it contains record. If yes then you are good to go! /etc/nginx/mime.types application/wasm wasm; Conclusion I hope I amazed you today and you will at least give a try to SwifWeb and at max will start using it for your next big web project! Please feel free to contribute to any of and also to star ⭐️them all! SwifWeb libraries I have a great where you can find huge support, read little tutorials and be notified first about any upcoming updates! Would be cool to see you with us! SwiftStream community in Discord It is just the very beginning, so stay tuned for more articles about SwifWeb! Tell your friends!