Vous devez avoir Swift installé, le moyen le plus simple de l'avoir :
brew install swifweb/tap/webber
pour mettre à jour vers la dernière version plus tard, exécutez simplement 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
pour mettre à jour vers la dernière version exécutée ultérieurement cd /opt/webber sudo git pull sudo swift build -c release
la branche principale contient toujours du code stable, alors n'hésitez pas à en extraire les mises à jour
webber new
Dans le menu interactif, choisissez pwa
ou spa
et entrez le nom du projet.
Changez le répertoire pour le projet nouvellement créé et exécutez webber serve
.
Cette commande compilera votre projet dans WebAssembly, regroupera tous les fichiers nécessaires dans un dossier .webber
spécial et commencera à servir votre projet sur toutes les interfaces en utilisant le port 8888
par défaut.
Arguments supplémentaires pour webber serve
-t pwa
pour l'application Web progressive
-t spa
pour une seule application Web
Nom de la cible du service worker (généralement nommé Service
dans le projet PWA)
-s Service
Nom de la cible de l'application ( App
par défaut)
-a App
-v
Port pour le serveur Webber (la valeur par défaut est 8888
)
-p 8080
Utilisez -p 443
pour tester comme le vrai SSL (avec le paramètre SSL auto-signé autorisé)
--browser safari
ou --browser chrome
--browser-self-signed
--browser-incognito
L'application commence dans 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() } }
Cela fonctionne à la manière d'iOS :
didFinishLaunching
lorsque l'application vient de démarrer
willTerminate
lorsque l'application va mourir
willResignActive
lorsque la fenêtre va être inactive
didBecomeActive
lorsque la fenêtre est active
didEnterBackground
lorsque la fenêtre passe en arrière-plan
willEnterForeground
lorsque la fenêtre passe au premier plan
La méthode la plus utile ici est didFinishLaunching
car c'est un endroit idéal pour configurer l'application. Vous voyez, cela ressemble vraiment à une application iOS ! 😀
Ici, app
contient des méthodes de commodité utiles :
registerServiceWorker(“serviceName“)
appel pour enregistrer le service worker PWA
addScript(“path/to/script.js“)
appel pour ajouter un script relatif ou externe
addStylesheet(“path/to/style.css“)
appel pour ajouter un style relatif ou externe
addFont(“path/to/font.woff”, type:)
appel pour ajouter une police relative ou externe, éventuellement définir le type
addIcon(“path/to/icon“, type:color:)
appel pour ajouter une icône, éventuellement définir le type et la couleur
//website.com/hello/world - ici /hello/world est le chemin
Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() }
Je pense que la dernière est la plus belle 🙂
Page("login") { LoginPage() } Page("registration") { RegistrationPage() }
Itinéraires liés aux paramètres
Page("article/:id") { ArticlePage() }
Le :id dans l'exemple ci-dessus est une partie dynamique de la route. On peut récupérer cet identifiant dans la classe ArticlePage pour afficher l'article qui lui est associé.
class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } }
La prochaine chose intéressante dans le chemin est la requête , qui est également très facile à utiliser. Par exemple, considérons la route /search , qui s'attend à avoir le text
de recherche et les paramètres de requête age
.
//website.com/search**?text=Alex&age=19** - la dernière partie est la requête
Page("search") { SearchPage() }
Et récupérez les données de requête dans la classe SearchPage comme ceci
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)") } } }
Vous pouvez également utiliser *
pour déclarer une route qui accepte n'importe quoi dans la partie de chemin spécifique comme celle-ci
Page("foo", "*", "bar") { SearchPage() }
La route ci-dessus acceptera tout ce qui se trouve entre foo et bar, par exemple /foo/aaa/bar, /foo/bbb/bar, etc. Avec le signe **
vous pouvez définir un itinéraire fourre-tout spécial qui gérera tout ce qui n'a pas été mis en correspondance avec d'autres itinéraires sur un chemin spécifique.
Page("**") { NotFoundPage() }
ou pour un chemin spécifique, par exemple lorsque l'utilisateur est introuvable Page("user", "**") { UserNotFoundPage() }
/user/1 - s'il existe une route pour /user/:id, il renverra UserPage . Sinon, il tombera dans…
UserNotFoundPage
/user/1/hello - s'il existe une route pour /user/:id/hello alors il tombera dans UserNotFoundPage
/something - s'il n'y a pas de route pour /something alors il tombera dans NotFoundPage
Nous ne voulons peut-être pas remplacer tout le contenu de la page pour le prochain itinéraire, mais seulement certains blocs. C'est là que le FragmentRouter devient utile !
Considérons que nous avons des onglets sur la page /user . Chaque onglet est un sous-itinéraire, et nous voulons réagir aux changements dans le sous-itinéraire en utilisant le FragmentRouter .
Déclarez la route de niveau supérieur dans la classe App
Page("user") { UserPage() }
Et déclarez FragmentRouter dans la classe 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() } } } }
Dans l'exemple ci-dessus, FragmentRouter gère les sous-routes /user/profile et /user/friends et les restitue sous la barre de navigation , de sorte que la page ne recharge jamais tout le contenu mais uniquement des fragments spécifiques.
Btw FragmentRouter est un Div et vous pouvez le configurer en appelant
FragmentRouter(self) .configure { div in // do anything you want with the div }
Pour déclarer une règle CSS à l'aide de Swift, nous avons l'objet Rule .
Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto)
ou de manière similaire à SwiftUI en utilisant @resultBuilder Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) }
Les deux manières sont égales, cependant, je préfère la première à cause de la saisie semi-automatique juste après avoir tapé .
😀
Rule(...selector...) .custom("customKey", "customValue")
Pointer("a")
Mais la bonne façon rapide est de le construire en appelant .pointer
à la balise HTML nécessaire comme celle-ci
H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId
Il s'agit de pointeurs de base, mais ils ont aussi des modificateurs comme :hover
:first
:first-child
etc.
H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover
Vous pouvez déclarer n'importe quel modificateur existant, ils sont tous disponibles. 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
Comment utiliser le sélecteur dans la règle
Rule(Pointer("a")) // or Rule(A.pointer)
Comment utiliser plusieurs sélecteurs dans la règle
Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer))
Il produit le code CSS suivant 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 et DarkStyle peuvent être déclarés dans des fichiers séparés ou par exemple dans l'App.swift
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
Et cela activera ou désactivera les feuilles de style associées ! N'est-ce pas cool ? 😎
Mais vous pouvez dire que décrire des styles en Swift au lieu de CSS est plus difficile, alors à quoi ça sert ?
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
et il mettra à jour la couleur dans la feuille de style et dans tous les éléments qui l'utilisent 😜
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; } """ } }
vous pouvez mélanger des chaînes CSS brutes autant de fois que nécessaireLe routeur affiche des pages sur chaque route. Page est une classe héritée du PageController .
PageController a des méthodes de cycle de vie comme willLoad
didLoad
willUnload
didUnload
, les méthodes d'interface utilisateur buildUI
et body
, et une variable wrapper de propriété pour les éléments HTML.
Techniquement, PageController n'est qu'un Div et vous pouvez définir toutes ses propriétés dans la méthode 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 // 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 }
N'est-ce pas beau et laconique ? 🥲
Méthodes de commodité bonus
alert(message: String)
- méthode alert
JS directe
changePath(to: String)
- changement de chemin d'URL
Code SwifWeb | Code HTML |
---|---|
| |
| |
| |
| |
| |
| |
Div
simple
Div()
nous pouvons accéder à tous ses attributs et propriétés de style comme celui-ci 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;">
Sous-classe de l'élément HTML pour prédéfinir son style, ou pour créer un élément composite avec de nombreux éléments enfants prédéfinis et des méthodes pratiques disponibles à l'extérieur, ou pour réaliser des événements de cycle de vie comme didAddToDOM
et didRemoveFromDOM
.
Créons un élément Divider
qui est juste un Div
mais avec une classe .divider
prédéfinie
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) } }
L'élément peut être ajouté au DOM de PageController ou à l'élément HTML immédiatement ou ultérieurement.
Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } }
Ou plus tard en utilisant lazy var
lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 }
Vous pouvez donc déclarer un élément HTML à l'avance et l'ajouter au DOM à tout moment par la suite !
lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove()
Div().superview?.backgroundColor(.red)
Nous avons souvent besoin de montrer des éléments uniquement dans certaines conditions, alors utilisons if/else
pour cela
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") } }
Mais ce n'est pas réactif. Si vous essayez de définir showDiv2
sur false
, rien ne se passe.
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 }
Pourquoi devrions-nous utiliser $showDiv2.map {…}
?
En savoir plus sur @State
ci-dessous.
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"] }
Comme dans les exemples ci-dessus, mais BuilderFunction
est également disponible
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 } }
Vous pouvez utiliser BuilderFunction
dans les boucles ForEach
pour calculer une valeur une seule fois comme une valeur delay
dans l'exemple suivant
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 est également disponible pour les éléments HTML :)
@State
est la chose la plus souhaitable de nos jours pour la programmation déclarative.
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
Exemple de nombre simple @State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div
Exemple booléen simple @State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div
Exemple de mappage @State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" })
Cartographier deux états @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 })
Cartographier plus de deux états @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 })
Toutes les propriétés HTML et CSS peuvent gérer les valeurs @State
extension Div { func makeItBeautiful() {} }
Ou des groupes d'éléments si vous connaissez leur class
parente.
Il y a peu de classes parentales.
BaseActiveStringElement
- est pour les éléments qui peuvent être initialisés avec une chaîne, comme a
, h1
, etc.
BaseContentElement
- est pour tous les éléments qui peuvent avoir du contenu à l'intérieur, comme div
, ul
, etc.
BaseElement
- est pour tous les éléments
extension BaseElement { func doSomething() {} }
La classe de couleur est responsable des couleurs. Il a des couleurs HTML prédéfinies, mais vous pouvez avoir les vôtres
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) }
Ensuite, utilisez-le comme H1(“Text“).color(.myColor1)
extension Class { var my: Class { "my" } }
Ensuite, utilisez-le comme Div().class(.my)
extension Id { var myId: Id { "my" } }
Ensuite, utilisez-le comme Div().id(.my)
L'objet window
est entièrement encapsulé et accessible via la variable App.current.window
.
Vous pouvez l'écouter dans Lifecycle
dans l' App.swift
ou directement de cette façon
App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed }
ou juste le lire n'importe quand n'importe où if App.current.window.isInForeground { // do somethign }
ou réagissez dessus avec l'élément HTML Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white })
C'est la même chose que le drapeau de premier plan, mais accessible via App.current.window.isActive
Identique au drapeau de premier plan, mais accessible via App.current.window.isOnline
Identique au drapeau de premier plan, mais accessible via App.current.window.isDark
App.current.window.innerSize
est un objet Size dans les valeurs width
et height
à l'intérieur.
Également disponible en tant que variable @State
.
App.current.window.outerSize
est un objet Size dans les valeurs width
et height
à l'intérieur.
Également disponible en tant que variable @State
.
Objet spécial pour inspecter les propriétés de l'écran sur lequel la fenêtre actuelle est rendue. Disponible via App.current.window.screen
.
La propriété la plus intéressante est généralement pixelRatio
.
Disponible via App.current.window.history
ou simplement History.shared
.
Il est accessible en tant que variable @State
, vous pouvez donc écouter ses modifications si nécessaire.
App.current.window.$history.listen { history in // read history properties }
Elle est également accessible en 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
Plus de détails sont disponibles sur . Disponible via App.current.window.location
ou simplement Location.shared
.
Il est accessible en tant que variable @State
, vous pouvez donc écouter ses modifications si nécessaire.
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
Plus de détails sont disponibles sur . Disponible via App.current.window.navigator
ou simplement Navigator.shared
Les propriétés les plus intéressantes sont généralement language
platform
userAgent
cookieEnabled
.
Disponible en tant que App.current.window.localStorage
ou simplement 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()
Suivi des modifications LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") }
Suivi de la suppression de tous les éléments LocalStorage.onClear { print("LocalStorage: all items has been removed") }
Disponible en tant que App.current.window.sessionStorage
ou simplement SessionStorage.shared
.
L'API est absolument la même que dans LocalStorage décrit ci-dessus.
Disponible 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]
H1(String( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))
Localization.current = .es
Si vous avez enregistré la langue de l'utilisateur quelque part dans les cookies ou le stockage local, vous devez la définir au lancement de l'application 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")) }
Simple print(“Hello world“)
est équivalent à console.log('Hello world')
en JavaScript
Les méthodes de la console sont également emballées avec amour ❤️
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() } }
Accédez à Extensions dans VSCode et recherchez Webber .
Une fois installé, appuyez sur Cmd+Shift+P
(ou Ctrl+Shift+P
sous Linux/Windows)
Recherchez et lancez Webber Live Preview
.
Sur le côté droit, vous verrez la fenêtre d'aperçu en direct et elle s'actualise chaque fois que vous enregistrez le fichier contenant la classe WebPreview .
Il est disponible via JavaScriptKit qui est la base du SwifWeb .
Lisez comment vous est dans . Vous pouvez ajouter css
, js
, png
, jpg
et toute autre ressource statique à l'intérieur du projet.
Mais pour les avoir disponibles pendant le débogage ou dans les fichiers de version finale, vous devez tous les déclarer dans le Package.swift comme ceci
.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") ]),
Plus tard, vous pourrez y accéder, par exemple comme ceci Img().src(“/images/logo.png“)
Lancez Webber de la manière suivante
webber serve
juste à le lancer rapidement
webber serve -t pwa -s Service
pour le lancer en mode PWA
-v
ou --verbose
pour afficher plus d'informations dans la console à des fins de débogage
-p 443
ou --port 443
pour démarrer le serveur webber sur le port 443 au lieu du 8888 par défaut
--browser chrome/safari
pour ouvrir automatiquement le navigateur souhaité, par défaut il n'en ouvre aucun
--browser-self-signed
nécessaire pour déboguer les service workers localement, sinon ils ne fonctionnent pas
--browser-incognito
pour ouvrir une instance supplémentaire du navigateur en mode incognito, fonctionne uniquement avec chrome
webber serve --browser chrome
webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito
Pour cela, ouvrez simplement le dossier .webber/entrypoint/dev
à l'intérieur du projet et modifiez le fichier index.html
.
Il contient le code HTML initial avec des écouteurs très utiles : WASMLoadingStarted
WASMLoadingStartedWithoutProgress
WASMLoadingProgress
WASMLoadingError
.
Lorsque vous avez terminé la nouvelle implémentation, n'oubliez pas de l'enregistrer dans le dossier .webber/entrypoint/release
Exécutez simplement webber release
ou webber release -t pwa -s Service
for PWA.
Ensuite, récupérez les fichiers compilés du dossier .webber/release
et téléchargez-les sur votre serveur.
L'hébergement doit fournir le type de contenu correct pour les fichiers wasm !
Oui, il est très important d'avoir le bon en-tête Content-Type: application/wasm
pour les fichiers wasm , sinon malheureusement le navigateur ne pourra pas charger votre application WebAssembly.
Par exemple , GithubPages ne fournit pas le type de contenu correct pour les fichiers wasm , il est donc malheureusement impossible d'y héberger des sites WebAssembly.
Si vous utilisez votre propre serveur avec nginx, ouvrez /etc/nginx/mime.types
et vérifiez s'il contient application/wasm wasm;
enregistrer. Si oui, vous êtes prêt à partir !
N'hésitez pas à contribuer à l'une des et également à les mettre toutes en vedette !