Web geliştirme dünyası çok geniştir ve her gün ortaya çıkan yeni teknolojilerin sürekli akışında kaybolmuş gibi hissetmek kolaydır. Bu yeni teknolojilerin çoğu JavaScript veya TypeScript kullanılarak oluşturulmuştur. Ancak bu makalede, doğrudan tarayıcınızın içinde yerel Swift kullanarak web geliştirmeyi size tanıtacağım ve bunun sizi etkileyeceğinden eminim. Bu nasıl mümkün olaiblir? Swift'in web sayfasında yerel olarak çalışması için önce WebAssembly bayt koduna derlenmesi gerekir, ardından JavaScript bu kodu sayfaya yükleyebilir. Özel araç zincirini kullanmamız ve yardımcı dosyalar oluşturmamız gerektiğinden tüm derleme süreci biraz çetrefilli; bu nedenle yardımcı CLI araçları mevcut: Carton ve Webber. Üçüncü taraf bir araç zinciri kullanmak uygun mudur? topluluğu, orijinal Swift araç zincirini yamalayarak Swift'in WebAssembly'de derlenmesini mümkün kılmak için muazzam miktarda çalışma yaptı. Her gün orijinal takım zincirindeki değişiklikleri otomatik olarak alarak ve testler başarısız olursa çatallarını düzelterek takım zincirini güncellerler. Hedefleri resmi alet zincirinin bir parçası olmak ve bunun yakın gelecekte gerçekleşmesini umuyorlar. SwiftWasm Karton mu yoksa Webber mi? , SwiftWasm topluluğu tarafından yapılmıştır ve size SwiftUI kullanarak web siteleri yazma yeteneği veren bir çerçeve olan Tokamak projeleri için kullanılabilir. Carton SwifWeb projeleri için tasarlandı. SwifWeb, tüm HTML ve CSS standartlarının yanı sıra tüm web API'lerini sarması bakımından farklıdır. Webber, Her ne kadar kod tutarlılığı için SwiftUI kullanarak web uygulamaları yazmayı tercih etseniz de bunun yanlış bir yaklaşım olduğuna inanıyorum çünkü web geliştirme doğası gereği farklıdır ve SwiftUI ile aynı şekilde ele alınamaz. Bu yüzden size HTML, CSS ve web API'lerinin tüm gücünü doğrudan Swift'den kullanma olanağı veren, otomatik tamamlama ve dokümantasyon içeren güzel sözdizimini kullanan SwifWeb'i yarattım. Ve Webber aracını oluşturdum çünkü Carton, kendisi için yaratılmadığından SwifWeb uygulamalarını doğru şekilde derleyemiyor, hata ayıklayamıyor ve dağıtamıyor. Adım Mikhail Isaev ve SwifWeb'in yazarıyım. Bu yazıda size SwifWeb'i kullanarak bir web sitesi oluşturmaya nasıl başlayacağınızı göstereceğim. Gerekli araçlar Süratli Swift'in kurulu olması gerekiyor, bunu yapmanın en kolay yolu: macOS'ta Xcode'u yüklemektir Linux veya Windows'ta (WSL2) komut dosyasını kullanmaktır Swiftlang.xyz'deki Diğer durumlarda bakın resmi web sitesindeki kurulum talimatlarına Webber CLI Uygulamalarınızı oluşturmanıza, hata ayıklamanıza ve dağıtmanıza yardımcı olmak için Webber'ı yarattım. MacOS'ta HomeBrew ile kurulumu kolaydır ( sitesinden yükleyin) web brew install swifweb/tap/webber daha sonra en son sürüme güncellemek için çalıştırmanız yeterli brew upgrade webber Ubuntu veya Windows'ta (WSL2'de Ubuntu) Webber'ı manuel olarak kopyalayın ve derleyin 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 son sürüme güncellemek için daha sonra çalıştırın cd /opt/webber sudo git pull sudo swift build -c release dal her zaman kararlı kod içerir, bu nedenle güncellemeleri ondan almaktan çekinmeyin ana Yeni proje oluşturma Terminali açın ve çalıştırın webber new İnteraktif menüde veya seçin ve proje adını girin. pwa spa Dizini yeni oluşturulan projeyle değiştirin ve komutunu çalıştırın. webber serve Bu komut, projenizi WebAssembly'de derleyecek, gerekli tüm dosyaları özel bir klasöründe paketleyecek ve varsayılan olarak bağlantı noktasını kullanarak projenizi tüm arayüzlerde sunmaya başlayacaktır. .webber 8888 için ek argümanlar webber serve Uygulama türü Aşamalı Web Uygulaması için -t pwa Tek Web Uygulaması için -t spa Hizmet çalışanı hedefinin adı (genellikle PWA projesinde olarak adlandırılır) Service -s Service Uygulama hedefinin adı (Varsayılan olarak ) App -a App Konsolda daha fazla bilgi yazdırın -v Webber sunucusunun bağlantı noktası (varsayılan ) 8888 -p 8080 Gerçek SSL gibi test etmek için kullanın (izin verilen kendinden imzalı SSL ayarıyla) -p 443 Otomatik olarak başlatılacak hedef tarayıcı adı veya --browser safari --browser chrome Hizmet çalışanlarının hatalarını ayıklamak için izin verilen kendinden imzalı SSL ayarına sahip ek tarayıcı örneği --browser-self-signed Gizli modda ek tarayıcı örneği --browser-incognito Başvuru Uygulama başlar 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() } } Yaşam döngüsü İOS benzeri bir şekilde çalışır: yeni başlatıldığında başlatılıyor didFinishLaunching uygulama sona erdiğinde willTerminate pencere etkin olmadığında willResignActive pencere aktif olduğunda didBecomeActive pencere arka plana geçtiğinde didEnterBackground pencere ön plana geçtiğinde willEnterForeground Buradaki en kullanışlı yöntem çünkü uygulamayı yapılandırmak için harika bir yerdir. Gerçekten bir iOS uygulaması gibi hissettirdiğini görüyorsunuz! 😀 didFinishLaunching İşte yararlı kolaylık yöntemleri içerir: app PWA hizmet çalışanını kaydetmek için çağrı registerServiceWorker(“serviceName“) göreceli veya harici komut dosyası eklemek için çağrısı addScript(“path/to/script.js“) göreceli veya harici stil eklemek için çağrısı addStylesheet(“path/to/style.css“) göreli veya harici yazı tipi eklemek için çağrı, isteğe bağlı olarak türü ayarlayın addFont(“path/to/font.woff”, type:) simge ekleme çağrısı, isteğe bağlı olarak türü ve rengi ayarlayın addIcon(“path/to/icon“, type:color:) Ayrıca , , vb. gibi ek kitaplıkların yapılandırılacağı yerdir. Autolayout Bootstrap Materialize Rotalar Geçerli URL'ye göre uygun sayfayı göstermek için yönlendirme gereklidir. Yönlendirmenin nasıl kullanılacağını anlamak için URL'nin ne olduğunu anlamalısınız. https://website.com/hello/world - işte /hello/world yol Gördüğünüz gibi başlangıçta App sınıfında tüm üst düzey rotaları bildirmemiz gerekiyor. Üst düzey, bu rotalarda bildirilen sayfaların penceredeki tüm alanı kaplayacağı anlamına gelir. Tamam, örneğin kök rota üç şekilde ayarlanabilir Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() } Bence en güzeli sonuncusu 🙂 Giriş veya kayıt rotaları bu şekilde ayarlanabilir Page("login") { LoginPage() } Page("registration") { RegistrationPage() } Parametreyle ilgili rotalar Page("article/:id") { ArticlePage() } Yukarıdaki örnekte rotanın dinamik bir parçasıdır. Kendisiyle ilişkili makaleyi görüntülemek için bu tanımlayıcıyı sınıfından alabiliriz. :id ArticlePage class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } } Yolda birden fazla parametreniz olabilir. Hepsini aynı şekilde geri alın. Sorguları Yoldaki bir sonraki ilginç şey, kullanımı da çok kolay olan . Örneğin, arama ve sorgusu parametrelerine sahip olmasını bekleyen rotasını ele alalım. sorgudur text age /search https://website.com/search**?text=Alex&age=19** - son kısım sorgudur Arama rotasını bildirmeniz yeterli Page("search") { SearchPage() } Ve sınıfındaki sorgu verilerini şu şekilde alın 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)") } } } Herhangi bir şey Bunun gibi belirli yol kısmındaki herhangi bir şeyi kabul eden rotayı bildirmek için komutunu da kullanabilirsiniz. * Page("foo", "*", "bar") { SearchPage() } Yukarıdaki rota foo ve bar arasındaki her şeyi kabul edecektir, örneğin /foo/aaa/bar, /foo/bbb/bar, vb. Hepsini yakala işaretiyle, belirli bir yoldaki diğer rotalarla eşleşmeyen her şeyi ele alacak özel bir tümünü kapsayan rota ayarlayabilirsiniz. ** Global 404 rotası oluşturmak için kullanın Page("**") { NotFoundPage() } veya belirli bir yol için, örneğin kullanıcı bulunamadığında Page("user", "**") { UserNotFoundPage() } Yukarıda belirtilen rotalarla durumları netleştirelim /user/1 - eğer /user/:id için bir rota varsa o zaman döndürür. Aksi halde düşecek… UserPage değerini Kullanıcı BulunamadıSayfa /user/1/hello - eğer /user/:id/hello için bir rota varsa o zaman düşecektir UserNotFoundPage'e /something - /something için bir rota yoksa düşecektir NotFoundPage'e İç içe yönlendirme Bir sonraki rota için sayfadaki içeriğin tamamını değil, yalnızca belirli blokları değiştirmek isteyebiliriz. kullanışlı olduğu yer burasıdır! FragmentRouter'ın sayfasında sekmelerimizin olduğunu düşünelim. Her sekme bir alt rotadır ve alt rotadaki değişikliklere kullanarak tepki vermek istiyoruz. /user FragmentRouter sınıfında üst düzey rotayı bildirin App Page("user") { UserPage() } Ve sınıfında ilan edin 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() } } } } Yukarıdaki örnekte, ve alt rotalarını yönetir ve bunu altında işler, böylece sayfa hiçbir zaman içeriğin tamamını yeniden yüklemez, yalnızca belirli parçaları yeniden yükler. FragmentRouter /user/profile /user/friends Navbar Aynı veya farklı alt rotalara sahip birden fazla parça da bildirilebilir ve hepsi birlikte sihir gibi çalışacaktır! Btw bir ve onu arayarak yapılandırabilirsiniz. FragmentRouter Div'dir FragmentRouter(self) .configure { div in // do anything you want with the div } Stil sayfaları Geleneksel CSS dosyalarını kullanabilirsiniz, ancak aynı zamanda Swift'de yazılmış bir stil sayfasını kullanma gibi yeni, sihirli bir yeteneğe de sahipsiniz! Temel bilgiler Swift kullanarak bir CSS kuralı bildirmek için nesnesine sahibiz. Rule Yöntemlerini çağırarak bildirimsel olarak oluşturulabilir. Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto) veya @resultBuilder kullanarak SwiftUI benzeri bir yol Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) } Her iki yol da eşittir ancak ben yazdıktan hemen sonra otomatik tamamlama nedeniyle ilkini tercih ediyorum 😀 . MDN'de açıklanan tüm CSS yöntemleri mevcuttur. Dahası, tarayıcı öneklerini otomatik olarak yönetir! Ancak bazı özel durumlarda özel özelliği bu şekilde ayarlayabilirsiniz. Rule(...selector...) .custom("customKey", "customValue") Seçici Kuralın hangi unsurları etkileyeceğini belirlemek için bir seçici ayarlamamız gerekir. Seçiciyi veritabanındaki sorgu olarak görüyorum, ancak bu seçici sorgusunun bazı kısımlarını işaretçi olarak adlandırıyorum. Bir işaretçi oluşturmanın en kolay yolu, onu ham dizeyi kullanarak başlatmaktır. Pointer("a") Ancak doğru hızlı yol, gerekli HTML etiketinde bu şekilde çağırarak bunu oluşturmaktır. .pointer H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId Temel işaretçilerle ilgilidir, ancak aynı zamanda vb. gibi değiştiricileri de vardır. :hover :first :first-child H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover Mevcut herhangi bir değiştiriciyi bildirebilirsiniz, hepsi mevcuttur. Eğer bir şey eksikse onu eklemek için uzantı yapmaktan çekinmeyin! Herkese eklemek için github'a çekme isteği göndermeyi unutmayın. Ayrıca işaretçileri birleştirebilirsiniz 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 seçici nasıl kullanılır? Kuraldaki Rule(Pointer("a")) // or Rule(A.pointer) birden fazla seçici nasıl kullanılır? Kuralda Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer)) Aşağıdaki CSS kodunu üretir a, h1#myId, div > p { } Reaktivite Uygulamamız için koyu ve açık stilleri tanımlayalım, daha sonra bunlar arasında kolayca geçiş yapabileceğiz. 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 }) } } ve ayrı dosyalarda veya örneğin App.swift'te bildirilebilir. 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) } } Ve sonra bir sayfanın kullanıcı arayüzünde bir yerde arayın App.current.theme = .light // to switch to light theme // or App.current.theme = .dark // to switch to dark theme Ve ilgili stil sayfalarını etkinleştirecek veya devre dışı bırakacaktır! Çok hoş değil mi? 😎 Ancak stilleri CSS yerine Swift'de tanımlamanın daha zor olduğunu söyleyebilirsiniz, o zaman ne anlamı var? Ana nokta tepkiselliktir! @State'i CSS özellikleriyle kullanabilir ve değerleri anında değiştirebiliriz! Sadece bir göz atın, bazı reaktif özelliklere sahip bir sınıf oluşturabilir ve onu çalışma zamanında istediğimiz zaman değiştirebiliriz, böylece ekrandaki bu sınıfı kullanan herhangi bir öğe güncellenecektir! Birçok öğe için sınıf değiştirmekten çok daha etkilidir! 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) } } Daha sonra kodun herhangi bir yerinden aramanız yeterli App.current.reactiveColor = .yellow // or any color you want ve Stil Sayfasındaki ve onu kullanan tüm öğelerdeki rengi güncelleyecektir 😜 Ayrıca bir Stil Sayfasına ham CSS eklemek de mümkündür 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; } """ } } ham CSS dizelerini gerektiği kadar karıştırabilirsiniz Sayfalar Yönlendirici her rotadaki sayfaları oluşturuyor. Page, miras alınan herhangi bir sınıftır. PageController'dan , kullanıcı arayüzü yöntemleri ve gibi yaşam döngüsü yöntemleri ve HTML öğeleri için özellik sarmalayıcı değişkeni bulunur. PageController'da willLoad didLoad willUnload didUnload buildUI body Teknik olarak yalnızca bir Div'dir ve özelliklerini yönteminde ayarlayabilirsiniz. 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 // 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") } } } Sayfanız küçükse bu kadar kısa yoldan bile ilan edebilirsiniz. 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 } Güzel ve özlü değil mi? 🥲 Bonus kolaylık yöntemleri - doğrudan JS yöntemi alert(message: String) alert - URL yolunu değiştirme changePath(to: String) HTML Öğeleri Son olarak size HTML öğelerini nasıl(!) oluşturup kullanacağınızı anlatacağım! Tüm HTML öğeleri, öznitelikleriyle birlikte Swift'te mevcuttur, tam liste örneğin . MDN'dedir HTML öğelerinin yalnızca örnek bir kısa listesi: SwifWeb kodu HTML Kodu 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”> Gördüğünüz gibi Swift'de herhangi bir HTML etiketine erişmek çok kolaydır çünkü girişler dışında hepsi aynı adla temsil edilir. Bunun nedeni, farklı girdi türlerinin farklı yöntemlere sahip olmasıdır ve ben bunları karıştırmak istemedim. Basit Div Div() bunun gibi tüm niteliklerine ve stil özelliklerine erişebiliriz 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;"> Alt sınıflandırma Stili önceden tanımlamak veya çok sayıda önceden tanımlanmış alt öğe ve dışarıda mevcut bazı uygun yöntemler ile bileşik bir öğe oluşturmak veya ve gibi yaşam döngüsü olaylarını elde etmek için alt sınıf HTML öğesi. didAddToDOM didRemoveFromDOM Sadece bir olan ancak önceden tanımlanmış sınıfına sahip bir öğesi oluşturalım Div .divider 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) } } Alt sınıflandırma yaparken süper yöntemleri çağırmak çok önemlidir. Bu olmadan beklenmedik davranışlarla karşılaşabilirsiniz. DOM'a ekleme Öğe, DOM'sine veya hemen veya daha sonra eklenebilir. PageController'ın HTML öğesine Derhal Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } } Veya daha sonra kullanarak lazy var lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 } Böylece bir önceden bildirebilir ve daha sonra istediğiniz zaman DOM'a ekleyebilirsiniz! HTML öğesini DOM'dan kaldırılıyor lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove() Üst öğeye erişme Herhangi bir HTML öğesinin, DOM'a eklenmesi durumunda üst öğeye erişim sağlayan isteğe bağlı bir denetleme özelliği vardır. Div().superview?.backgroundColor(.red) if/else koşulları Genellikle öğeleri yalnızca belirli koşullarda göstermemiz gerekir; bunun için kullanalım. 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") } } Fakat reaktif değildir. olarak ayarlamaya çalışırsanız hiçbir şey olmuyor. showDiv2 false Reaktif örnek 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 } Neden $showDiv2.map {…} kullanmalıyız ? Sırayla: çünkü SwiftUI değil. Kesinlikle. Aşağıda @State hakkında daha fazla bilgi bulabilirsiniz . Ham HTML Ayrıca sayfaya veya HTML öğesine ham HTML eklemeniz gerekebilir ve bu kolayca mümkündür. Div { """ <a href="https://google.com">Go to Google</a> """ } Her biri için Statik örnek 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) } } Dinamik örnek @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 Yukarıdaki örneklerle aynı, ancak da mevcut 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 } } Aşağıdaki örnekte bir değeri gibi bazı değerleri yalnızca bir kez hesaplamak için döngülerinde kullanabilirsiniz. delay ForEach BuilderFunction 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) } } Aynı zamanda bir argüman olarak da işlev görebilir BuilderFunction(calculate) { calculatedValue in // CSS rule or DOM element } func calculate() -> Int { return 1 + 1 } HTML öğeleri için de kullanılabilir :) BuilderFunction @State ile Reaktivite günümüzde bildirimsel için en çok arzu edilen şeydir. @State programlama Yukarıda da söylediğim gibi: SwiftUI değil, dolayısıyla her şeyi takip eden ve yeniden çizen bir küresel durum makinesi yok. Ve HTML öğeleri geçici yapılar değil sınıflardır, yani bunlar gerçek nesnelerdir ve onlara doğrudan erişebilirsiniz. Çok daha iyi ve esnektir, tüm kontrol sizdedir. Kaputun altında ne var? Değişiklikleri tüm abonelere bildiren bir özellik sarmalayıcıdır. Değişikliklere nasıl abone olunur? 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)") } HTML öğeleri değişikliklere nasıl tepki verebilir? Basit metin örneği @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 Basit sayı örneği @State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div Basit boole örneği @State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div Haritalama örneği @State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" }) İki eyaletin haritalanması @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 }) İkiden fazla eyaletin haritalanması @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 }) Tüm HTML ve CSS özellikleri değerlerini işleyebilir @State Uzantılar HTML öğelerini genişlet Div gibi somut elemanlara bazı kullanışlı yöntemler ekleyebilirsiniz. extension Div { func makeItBeautiful() {} } Veya ebeveyn biliyorsanız öğe grupları. class Az sayıda ebeveyn sınıfı vardır. - , vb. gibi dizeyle başlatılabilen öğeler içindir. BaseActiveStringElement a h1 - , vb. gibi içinde içerik bulundurabilen tüm öğeler içindir. BaseContentElement div ul - tüm öğeler içindir BaseElement Yani tüm elemanların uzantısı bu şekilde yazılabilir extension BaseElement { func doSomething() {} } Renkleri beyan edin sınıfı renklerden sorumludur. Önceden tanımlanmış HTML renkleri vardır, ancak kendi renklerinize de sahip olabilirsiniz. Renk 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) } Daha sonra gibi kullanın. H1(“Text“).color(.myColor1) Sınıfları bildirin extension Class { var my: Class { "my" } } Sonra bunu gibi kullanın Div().class(.my) Kimlikleri bildir extension Id { var myId: Id { "my" } } Daha sonra gibi kullanın Div().id(.my) Web API'leri Pencere nesnesi tamamen sarılmıştır ve değişkeni aracılığıyla erişilebilir. window App.current.window Tam referans mevcuttur. MDN'de Aşağıda kısa bir genel bakış yapalım Ön Plan Bayrağı Bunu veya doğrudan bu şekilde dinleyebilirsiniz. App.swift Lifecycle App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed } veya istediğiniz zaman istediğiniz yerde okuyun if App.current.window.isInForeground { // do somethign } veya HTML öğesiyle tepki verin Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white }) Aktif Bayrak Ön Plan bayrağıyla aynıdır ancak aracılığıyla erişilebilir App.current.window.isActive Bir kullanıcının pencerenin içinde hâlâ etkileşimde olup olmadığını tespit eder. Çevrimiçi durum Ön Plan bayrağıyla aynıdır ancak aracılığıyla erişilebilir App.current.window.isOnline Bir kullanıcının hala internete erişimi olup olmadığını tespit eder. Karanlık mod durumu Ön Plan bayrağıyla aynıdır ancak aracılığıyla erişilebilir App.current.window.isDark Bir kullanıcının tarayıcısının veya işletim sisteminin karanlık modda olup olmadığını tespit eder. İç ölçü Kaydırma çubukları da dahil olmak üzere pencerenin içerik alanının (görüntü alanı) boyutu , içindeki ve değerleri içindeki nesnesidir. App.current.window.innerSize width height Size değişkeni olarak da mevcuttur. @State Dış Boyut Araç çubukları/kaydırma çubukları da dahil olmak üzere tarayıcı penceresinin boyutu. , içindeki ve değerleri içindeki nesnesidir. App.current.window.outerSize width height Size değişkeni olarak da mevcuttur. @State Ekran Geçerli pencerenin oluşturulduğu ekranın özelliklerini denetlemek için özel nesne. aracılığıyla kullanılabilir. App.current.window.screen En ilginç özellik genellikle . pixelRatio Tarih Kullanıcı tarafından ziyaret edilen URL'leri içerir (tarayıcı penceresi içinde). veya sadece aracılığıyla edinilebilir. App.current.window.history History.shared değişkeni olarak erişilebilir olduğundan, gerekirse değişikliklerini dinleyebilirsiniz. @State App.current.window.$history.listen { history in // read history properties } Basit değişken olarak da erişilebilir 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 Daha fazla ayrıntıyı bulabilirsiniz. MDN'de Konum Geçerli URL ile ilgili bilgileri içerir. veya yalnızca aracılığıyla kullanılabilir. App.current.window.location Location.shared değişkeni olarak erişilebilir olduğundan, gerekirse değişikliklerini dinleyebilirsiniz. @State Örneğin yönlendirici bu şekilde çalışır. App.current.window.$location.listen { location in // read location properties } Basit bir değişken olarak da erişilebilir 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 Daha fazla ayrıntıyı bulabilirsiniz. MDN'de Gezgin Tarayıcı hakkında bilgi içerir. veya sadece aracılığıyla edinilebilir App.current.window.navigator Navigator.shared En ilginç özellikler genellikle . userAgent platform language cookieEnabled Yerel depolama Anahtar/değer çiftlerinin bir web tarayıcısına kaydedilmesine olanak tanır. Son kullanma tarihi olmayan verileri saklar. veya yalnızca olarak mevcuttur. 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() Değişiklikleri izleme LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") } Tüm öğelerin kaldırılmasını takip etme LocalStorage.onClear { print("LocalStorage: all items has been removed") } Oturum Depolama Anahtar/değer çiftlerinin bir web tarayıcısına kaydedilmesine olanak tanır. Yalnızca bir oturuma ait verileri depolar. veya yalnızca olarak mevcuttur. App.current.window.sessionStorage SessionStorage.shared API, yukarıda açıklanan ile tamamen aynıdır. LocalStorage Belge Tarayıcıya yüklenen herhangi bir web sayfasını temsil eder ve web sayfasının içeriğine giriş noktası görevi görür. aracılığıyla edinilebilir. 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] Yerelleştirme Statik yerelleştirme Klasik yerelleştirme otomatiktir ve kullanıcının sistem diline bağlıdır Nasıl kullanılır H1(String( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは"))) Dinamik yerelleştirme Ekrandaki yerelleştirilmiş dizeleri anında değiştirmek istiyorsanız (sayfayı yeniden yüklemeden) Mevcut dili arayarak değiştirebilirsiniz. Localization.current = .es Kullanıcının dilini çerezlerde veya yerel depolamada bir yere kaydettiyseniz, bunu uygulama başlatılırken ayarlamanız gerekir. Lifecycle.didFinishLaunching { Localization.current = .es } Nasıl kullanılır H1(LString( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは"))) Gelişmiş örnek 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 } Gidip getirmek 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")) } Konsol Basit JavaScript'teki ile eşdeğerdir print(“Hello world“) console.log('Hello world') yöntemleri de sevgiyle sarılmış ❤️ Konsol Console.dir(...) Console.error(...) Console.warning(...) Console.clear() Canlı önizleme Canlı önizlemenin çalışmasını sağlamak için istediğiniz her dosyada WebPreview sınıfını bildirin. 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 Lütfen talimatları okuyun. Zor ama tamamen işe yarayan bir çözüm 😎 depo sayfasındaki VSCode içindeki gidin ve arayın. VSCode Uzantılar'a Webber'ı Kurulduktan sonra tuşlarına basın (veya Linux/Windows'ta ) Cmd+Shift+P Ctrl+Shift+P bulun ve başlatın. Webber Live Preview Sağ tarafta canlı önizleme penceresini göreceksiniz ve sınıfını içeren dosyayı her kaydettiğinizde yenilenir. WebPreview JavaScript'e erişim temeli olan aracılığıyla edinilebilir. SwifWeb'in JavaScriptKit nasıl yapılacağını okuyun. Resmi depoda Kaynaklar Projenin içine , , , ve diğer statik kaynakları ekleyebilirsiniz. css js png jpg Ancak bunları veya son dosyalarında kullanılabilir hale getirmek için hepsini bu şekilde bildirmeniz gerekir. hata ayıklama sırasında sürüm Package.swift'te .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") ]), Daha sonra bunlara örneğin şu şekilde erişebileceksiniz Img().src(“/images/logo.png“) Hata ayıklama aşağıdaki şekilde başlatın Webber'ı sadece hızlı bir şekilde başlatmak için webber serve PWA modunda başlatmak için webber serve -t pwa -s Service Ek parametreler Hata ayıklama amacıyla konsolda daha fazla bilgi göstermek için veya -v --verbose veya webber sunucusunu varsayılan 8888 yerine 443 bağlantı noktasında başlatmak için -p 443 --port 443 istenen tarayıcıyı otomatik olarak açmak için, varsayılan olarak herhangi bir tarayıcıyı açmaz --browser chrome/safari hizmet çalışanlarının yerel hatalarını ayıklamak için gerekli, aksi halde çalışmazlar --browser-self-signed gizli modda ek tarayıcı örneğini açmak için, yalnızca Chrome ile çalışır --browser-incognito Uygulamanızı hata ayıklama modunda oluşturmak için, uygulamayı otomatik olarak Chrome'da açın ve herhangi bir dosyayı değiştirdiğinizde tarayıcıyı otomatik olarak yenileyin, uygulamayı bu şekilde başlatın SPA için webber serve --browser chrome gerçek PWA testi için webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito İlk uygulama yükleme İlk yükleme sürecini iyileştirmek isteyebilirsiniz. Bunun için proje içindeki klasörünü açın ve dosyasını düzenleyin. .webber/entrypoint/dev index.html Çok kullanışlı dinleyicilere sahip başlangıç HTML kodunu içerir: . WASMLoadingStarted WASMLoadingStartedWithoutProgress WASMLoadingProgress WASMLoadingError Bu kodu, özel stilinizi uygulamak istediğiniz şekilde düzenlemekte özgürsünüz 🔥 Yeni uygulamayı tamamladığınızda, aynısını klasörüne kaydetmeyi unutmayın. .webber/entrypoint/release Bina Yayını PWA için veya çalıştırmanız yeterlidir. webber release webber release -t pwa -s Service Daha sonra derlenmiş dosyaları klasöründen alın ve sunucunuza yükleyin. .webber/release Nasıl dağıtılır Dosyalarınızı herhangi bir statik hostinge yükleyebilirsiniz. Hosting, dosyaları için doğru İçerik Türünü sağlamalıdır! wasm Evet, dosyaları için doğru başlığına sahip olmak çok önemlidir, aksi halde ne yazık ki tarayıcı WebAssembly uygulamanızı yükleyemeyecektir. wasm Content-Type: application/wasm Örneğin dosyaları için doğru İçerik Türünü sağlamadığından ne yazık ki üzerinde WebAssembly sitelerini barındırmak imkansızdır. GithubPages, wasm Nginx Nginx ile kendi sunucunuzu kullanıyorsanız açın ve kayıt. Eğer evet ise o zaman gitmeye hazırsınız! /etc/nginx/mime.types application/wasm wasm; Çözüm Umarım bugün sizi şaşırtmışımdır ve en azından SwifWeb'i deneyecek ve en fazla bir sonraki büyük web projeniz için kullanmaya başlayacaksınız! Lütfen herhangi bir katkıda bulunmaktan ve ayrıca hepsine ⭐️ yıldız eklemekten çekinmeyin! SwifWeb kütüphanesine var! Seni aramızda görmek harika olurdu! Discord'da büyük bir destek bulabileceğiniz, küçük eğitimler okuyabileceğiniz ve gelecek güncellemelerden ilk önce haberdar olabileceğiniz harika bir SwiftStream topluluğum Bu sadece başlangıç; SwifWeb hakkında daha fazla makale için bizi takip etmeye devam edin! Arkadaşlarına söyle!