Sie müssen Swift installiert haben. Der einfachste Weg, es zu installieren:
brew install swifweb/tap/webber
Um später auf die neueste Version zu aktualisieren, führen Sie einfach aus brew upgrade webber
sudo apt-get install binaryen curl //get.wasmer.io -sSfL | sh apt-get install npm cd /opt sudo git clone //github.com/swifweb/webber cd webber sudo swift build -c release sudo ln -s /opt/webber/.build/release/Webber /usr/local/bin/webber
um später auf die letzte Version zu aktualisieren cd /opt/webber sudo git pull sudo swift build -c release
Der Hauptzweig enthält immer stabilen Code, Sie können also jederzeit Updates daraus abrufen
webber new
Wählen Sie im interaktiven Menü pwa
oder spa
und geben Sie den Projektnamen ein.
Wechseln Sie in das Verzeichnis des neu erstellten Projekts und führen Sie webber serve
aus.
Dieser Befehl kompiliert Ihr Projekt in WebAssembly, packt alle erforderlichen Dateien in einen speziellen .webber
Ordner und beginnt mit der Bereitstellung Ihres Projekts auf allen Schnittstellen, die standardmäßig Port 8888
verwenden.
Zusätzliche Argumente für webber serve
-t pwa
für Progressive Web App
-t spa
für eine einzelne Web-App
Name des Service-Worker-Ziels (im PWA-Projekt normalerweise Service
genannt)
-s Service
Name des App-Ziels (standardmäßig App
)
-a App
-v
Port für den Webber-Server (Standard ist 8888
)
-p 8080
Verwenden Sie -p 443
um wie echtes SSL zu testen (mit erlaubter selbstsignierter SSL-Einstellung).
--browser safari
oder --browser chrome
--browser-self-signed
--browser-incognito
Die App beginnt 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() } }
Es funktioniert auf eine iOS-ähnliche Weise:
didFinishLaunching
, als die App gerade gestartet wurde
willTerminate
, wenn die App sterben wird
willResignActive
wenn das Fenster inaktiv sein soll
didBecomeActive
wenn das Fenster aktiv ist
didEnterBackground
, wenn das Fenster in den Hintergrund wechselt
willEnterForeground
, wenn das Fenster in den Vordergrund geht
Die nützlichste Methode ist hier didFinishLaunching
, da sie sich hervorragend zum Konfigurieren der App eignet. Sie sehen, es fühlt sich wirklich wie eine iOS-App an! 😀
Diese app
enthält nützliche Komfortmethoden:
registerServiceWorker(“serviceName“)
-Aufruf zum Registrieren des PWA-Servicemitarbeiters
addScript(“path/to/script.js“)
Aufruf zum Hinzufügen eines relativen oder externen Skripts
addStylesheet(“path/to/style.css“)
Aufruf zum Hinzufügen eines relativen oder externen Stils
addFont(“path/to/font.woff”, type:)
Aufruf zum Hinzufügen einer relativen oder externen Schriftart, optional Festlegen des Typs
addIcon(“path/to/icon“, type:color:)
Aufruf zum Hinzufügen eines Symbols, optional Festlegung von Typ und Farbe
//website.com/hello/world – hier ist /hello/world der Pfad
Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() }
Ich denke, das letzte ist das schönste 🙂
Page("login") { LoginPage() } Page("registration") { RegistrationPage() }
Parameterbezogene Routen
Page("article/:id") { ArticlePage() }
Die :id im obigen Beispiel ist ein dynamischer Teil der Route. Wir können diesen Bezeichner in der ArticlePage- Klasse abrufen, um den damit verbundenen Artikel anzuzeigen.
class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } }
Das nächste interessante Element im Pfad ist die Abfrage , die ebenfalls sehr einfach zu verwenden ist. Betrachten wir beispielsweise die Route /search , die die Abfrageparameter „ text
“ und age
erwartet.
//website.com/search**?text=Alex&age=19** – der letzte Teil ist die Abfrage
Page("search") { SearchPage() }
Und rufen Sie Abfragedaten in der SearchPage- Klasse wie folgt ab
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)") } } }
Sie können *
auch verwenden, um eine Route zu deklarieren, die alles im spezifischen Pfadteil wie diesen akzeptiert
Page("foo", "*", "bar") { SearchPage() }
Die obige Route akzeptiert alles zwischen foo und bar, z. B. /foo/aaa/bar, /foo/bbb/bar usw. Mit dem **
Zeichen können Sie eine spezielle Catch-All-Route festlegen, die alles verarbeitet, was nicht mit anderen Routen auf einem bestimmten Pfad abgeglichen wurde.
Page("**") { NotFoundPage() }
oder für einen bestimmten Pfad, z. B. wenn der Benutzer nicht gefunden wurde Page("user", "**") { UserNotFoundPage() }
/user/1 – wenn es eine Route für /user/:id gibt, wird UserPage zurückgegeben. Andernfalls fällt es in ...
UserNotFoundPage
/user/1/hello – wenn es eine Route für /user/:id/hello gibt, wird sie in UserNotFoundPage fallen
/something – wenn es keine Route für /something gibt, wird es in NotFoundPage abgelegt
Möglicherweise möchten wir für die nächste Route nicht den gesamten Inhalt der Seite ersetzen, sondern nur bestimmte Blöcke. Hier kommt der FragmentRouter zum Einsatz!
Nehmen wir an, dass wir auf der Seite /user Registerkarten haben. Jede Registerkarte ist eine Unterroute, und wir möchten mit FragmentRouter auf Änderungen in der Unterroute reagieren.
Deklarieren Sie die Route der obersten Ebene in der App- Klasse
Page("user") { UserPage() }
Und deklarieren Sie FragmentRouter in der UserPage- Klasse
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() } } } }
Im obigen Beispiel verarbeitet FragmentRouter die Unterrouten /user/profile und /user/friends und stellt sie unter der Navbar dar, sodass die Seite nie den gesamten Inhalt neu lädt, sondern nur bestimmte Fragmente.
Übrigens ist FragmentRouter ein Div und Sie können es durch Aufrufen konfigurieren
FragmentRouter(self) .configure { div in // do anything you want with the div }
Um eine CSS-Regel mit Swift zu deklarieren, haben wir das Rule- Objekt.
Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto)
oder SwiftUI-ähnliche Weise mit @resultBuilder Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) }
Beide Möglichkeiten sind gleich, ich bevorzuge jedoch die erste wegen der automatischen Vervollständigung direkt nach der Eingabe .
😀
Rule(...selector...) .custom("customKey", "customValue")
Pointer("a")
Aber der richtige und schnelle Weg besteht darin, es zu erstellen, indem man .pointer
auf das benötigte HTML-Tag wie dieses aufruft
H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId
Es geht um grundlegende Zeiger, aber sie haben auch Modifikatoren wie :hover
:first
:first-child
usw.
H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover
Sie können jeden vorhandenen Modifikator deklarieren, sie sind alle verfügbar. 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
So verwenden Sie den Selektor in der Regel
Rule(Pointer("a")) // or Rule(A.pointer)
So verwenden Sie mehr als einen Selektor in der Regel
Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer))
Es erzeugt den folgenden CSS-Code a, h1#myId, div > p { }
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 }) } }
LightStyle und DarkStyle können in separaten Dateien oder z. B. in der App.swift deklariert werden
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) } }
App.current.theme = .light // to switch to light theme // or App.current.theme = .dark // to switch to dark theme
Und es aktiviert oder deaktiviert die zugehörigen Stylesheets! Ist das nicht cool? 😎
Aber Sie sagen vielleicht, dass die Beschreibung von Stilen in Swift statt in CSS schwieriger ist, wozu also?
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) } }
App.current.reactiveColor = .yellow // or any color you want
und es aktualisiert die Farbe im Stylesheet und in allen Elementen, die es verwenden 😜
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; } """ } }
Sie können rohe CSS-Strings so oft wie nötig einmischenDer Router rendert Seiten auf jeder Route. Page ist eine beliebige vom PageController geerbte Klasse.
PageController verfügt über Lebenszyklusmethoden wie willLoad
didLoad
willUnload
didUnload
, UI-Methoden buildUI
und body
sowie eine Eigenschafts-Wrapper-Variable für HTML-Elemente.
Technisch gesehen ist PageController nur ein Div und Sie können alle seine Eigenschaften in buildUI
Methode festlegen.
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 // eg 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") } } }
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 }
Ist es nicht schön und lakonisch? 🥲
Bonus-Komfortmethoden
alert(message: String)
– direkte JS- alert
changePath(to: String)
– URL-Pfad wechseln
SwifWeb-Code | HTML Quelltext |
---|---|
| |
| |
| |
| |
| |
| |
Einfache Div
Div()
Auf diese Weise können wir auf alle seine Attribute und Stileigenschaften zugreifen 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;">
Unterklassen Sie ein HTML-Element, um den Stil dafür vorzudefinieren, oder um ein zusammengesetztes Element mit vielen vordefinierten untergeordneten Elementen und einigen praktischen Methoden außerhalb verfügbar zu machen, oder um Lebenszyklusereignisse wie didAddToDOM
und didRemoveFromDOM
zu erreichen.
Lassen Sie uns ein Divider
Element erstellen, das nur ein Div
ist, aber eine vordefinierte .divider
Klasse hat
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) } }
Das Element kann sofort oder später an das DOM des PageControllers oder HTML-Elements angehängt werden.
Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } }
Oder später mit lazy var
lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 }
Sie können also ein HTML-Element im Voraus deklarieren und es später jederzeit zum DOM hinzufügen!
lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove()
Div().superview?.backgroundColor(.red)
Wir müssen Elemente oft nur unter bestimmten Bedingungen anzeigen, also verwenden wir dafür 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") } }
Aber es ist nicht reaktiv. Wenn Sie versuchen, showDiv2
auf false
zu setzen, passiert nichts.
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 }
Warum sollten wir $showDiv2.map {…}
verwenden ?
Lesen Sie unten mehr über @State
.
Div { """ <a href="//google.com">Go to Google</a> """ }
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) } }
@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"] }
Wie in den obigen Beispielen, aber auch BuilderFunction
ist verfügbar
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 } }
Sie können BuilderFunction
in ForEach
Schleifen verwenden, um einen Wert nur einmal zu berechnen, wie im folgenden Beispiel einen 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) } }
BuilderFunction(calculate) { calculatedValue in // CSS rule or DOM element } func calculate() -> Int { return 1 + 1 }
BuilderFunction ist auch für HTML-Elemente verfügbar :)
@State
ist heutzutage das wünschenswerteste Element für die deklarative Programmierung .
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)") }
@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
Einfaches Zahlenbeispiel @State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div
Einfaches boolesches Beispiel @State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div
Mapping-Beispiel @State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" })
Zwei Zustände abbilden @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 })
Kartierung von mehr als zwei Staaten @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 })
Alle HTML- und CSS-Eigenschaften können @State
Werte verarbeiten
extension Div { func makeItBeautiful() {} }
Oder Gruppen von Elementen, wenn Sie deren übergeordnete class
kennen.
Es gibt nur wenige Elternklassen.
BaseActiveStringElement
– ist für Elemente, die mit String initialisiert werden können, wie a
, h1
usw.
BaseContentElement
– gilt für alle Elemente, die Inhalte enthalten können, wie div
, ul
usw.
BaseElement
– gilt für alle Elemente
extension BaseElement { func doSomething() {} }
Die Farbklasse ist für die Farben verantwortlich. Es verfügt über vordefinierte HTML-Farben, Sie können jedoch auch eigene verwenden
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) }
Dann verwenden Sie es wie H1(“Text“).color(.myColor1)
extension Class { var my: Class { "my" } }
Dann verwenden Sie es wie Div().class(.my)
extension Id { var myId: Id { "my" } }
Dann verwenden Sie es wie Div().id(.my)
window
ist vollständig umschlossen und über die Variable App.current.window
zugänglich.
Sie können es in Lifecycle
im App.swift
oder direkt hier anhören
App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed }
oder lesen Sie es einfach jederzeit und überall if App.current.window.isInForeground { // do somethign }
oder mit einem HTML-Element darauf reagieren Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white })
Es ist dasselbe wie das Foreground-Flag, ist aber über App.current.window.isActive
zugänglich
Identisch mit der Vordergrundflagge, aber über App.current.window.isOnline
zugänglich
Identisch mit der Foreground-Flagge, aber zugänglich über App.current.window.isDark
App.current.window.innerSize
ist das Größenobjekt innerhalb width
und height
im Inneren.
Auch als @State
Variable verfügbar.
App.current.window.outerSize
ist das Größenobjekt innerhalb width
und height
im Inneren.
Auch als @State
Variable verfügbar.
Spezielles Objekt zum Überprüfen der Eigenschaften des Bildschirms, auf dem das aktuelle Fenster gerendert wird. Verfügbar über App.current.window.screen
.
Die interessanteste Eigenschaft ist normalerweise pixelRatio
.
Verfügbar über App.current.window.history
oder einfach History.shared
.
Sie ist als @State
Variable zugänglich, sodass Sie bei Bedarf auf Änderungen warten können.
App.current.window.$history.listen { history in // read history properties }
Es ist auch als einfache Variable zugänglich 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
Weitere Einzelheiten finden Sie auf . Verfügbar über App.current.window.location
oder einfach Location.shared
.
Sie ist als @State
Variable zugänglich, sodass Sie bei Bedarf auf Änderungen warten können.
App.current.window.$location.listen { location in // read location properties }
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
Weitere Einzelheiten finden Sie auf . Verfügbar über App.current.window.navigator
oder einfach Navigator.shared
Die interessantesten Eigenschaften sind normalerweise language
platform
userAgent
cookieEnabled
.
Verfügbar als App.current.window.localStorage
oder einfach 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()
Änderungen verfolgen LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") }
Verfolgung der Entfernung aller Gegenstände LocalStorage.onClear { print("LocalStorage: all items has been removed") }
Verfügbar als App.current.window.sessionStorage
oder einfach SessionStorage.shared
.
Die API ist absolut dieselbe wie in LocalStorage, wie oben beschrieben.
Verfügbar über 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]
H1(String( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))
Localization.current = .es
Wenn Sie die Sprache des Benutzers irgendwo in Cookies oder im lokalen Speicher gespeichert haben, müssen Sie sie beim Start der App festlegen Lifecycle.didFinishLaunching { Localization.current = .es }
H1(LString( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))
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 }
import FetchAPI Fetch("//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)") } } } }
import XMLHttpRequest XMLHttpRequest() .open(method: "GET", url: "//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()
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")) }
Einfaches print(“Hello world“)
entspricht console.log('Hello world')
in JavaScript
Konsolenmethoden werden ebenfalls mit Liebe verpackt ❤️
Console.dir(...) Console.error(...) Console.warning(...) Console.clear()
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() } }
Gehen Sie in VSCode zu „Erweiterungen“ und suchen Sie nach „Webber“ .
Sobald es installiert ist, drücken Sie Cmd+Shift+P
(oder Ctrl+Shift+P
unter Linux/Windows).
Suchen und starten Sie Webber Live Preview
.
Auf der rechten Seite sehen Sie das Live-Vorschaufenster, das jedes Mal aktualisiert wird, wenn Sie die Datei speichern, die die WebPreview- Klasse enthält.
Es ist über JavaScriptKit verfügbar, das die Grundlage von SwifWeb bildet.
Lesen Sie, wie es Ihnen geht, im . Sie können css
, js
, png
, jpg
und alle anderen statischen Ressourcen innerhalb des Projekts hinzufügen.
Damit sie jedoch während des Debuggens oder in endgültigen Release- Dateien verfügbar sind, müssen Sie sie alle wie folgt in Package.swift deklarieren
.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") ]),
Später können Sie darauf zugreifen, z. B. so Img().src(“/images/logo.png“)
Starten Sie Webber auf folgende Weise
webber serve
nur dazu, es schnell zu starten
webber serve -t pwa -s Service
, um ihn im PWA-Modus zu starten
-v
oder --verbose
, um zu Debugzwecken weitere Informationen in der Konsole anzuzeigen
-p 443
oder --port 443
, um den Webber-Server auf Port 443 statt auf dem Standardport 8888 zu starten
--browser chrome/safari
, um den gewünschten Browser automatisch zu öffnen, standardmäßig wird keiner geöffnet
--browser-self-signed
wird benötigt, um Servicemitarbeiter lokal zu debuggen, andernfalls funktionieren sie nicht
--browser-incognito
zum Öffnen einer zusätzlichen Browserinstanz im Inkognito-Modus, funktioniert nur mit Chrome
webber serve --browser chrome
webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito
Öffnen Sie dazu einfach den Ordner .webber/entrypoint/dev
im Projekt und bearbeiten Sie die Datei index.html
.
Es enthält anfänglichen HTML-Code mit sehr nützlichen Listenern: WASMLoadingStarted
WASMLoadingStartedWithoutProgress
WASMLoadingProgress
WASMLoadingError
.
Wenn Sie die neue Implementierung abgeschlossen haben, vergessen Sie nicht, diese im Ordner .webber/entrypoint/release
zu speichern
Führen Sie einfach webber release
oder webber release -t pwa -s Service
für PWA aus.
Dann holen Sie sich die kompilierten Dateien aus dem Ordner .webber/release
und laden Sie sie auf Ihren Server hoch.
Das Hosting sollte den richtigen Inhaltstyp für WASM- Dateien bereitstellen!
Ja, es ist sehr wichtig, den richtigen Header Content-Type: application/wasm
für WASM- Dateien zu haben, andernfalls kann der Browser Ihre WebAssembly-Anwendung leider nicht laden.
GithubPages bietet beispielsweise nicht den richtigen Inhaltstyp für WASM- Dateien, sodass es leider nicht möglich ist, WebAssembly-Sites darauf zu hosten.
Wenn Sie Ihren eigenen Server mit Nginx verwenden, öffnen Sie /etc/nginx/mime.types
und prüfen Sie, ob es application/wasm wasm;
enthält. aufzeichnen. Wenn ja, dann können Sie loslegen!
Bitte zögern Sie nicht, zu einer beizutragen und sie alle zu markieren!