paint-brush
Cách sử dụng Swift để phát triển webtừ tác giả@imike
16,604 lượt đọc
16,604 lượt đọc

Cách sử dụng Swift để phát triển web

từ tác giả Mikhail Isaev33m2023/03/20
Read on Terminal Reader

dài quá đọc không nổi

SwifWeb là một khung cung cấp cho bạn khả năng viết trang web bằng SwiftUI. Nó gói gọn toàn bộ các tiêu chuẩn HTML và CSS, cũng như tất cả các API web. Trong bài viết này, tôi sẽ chỉ cho bạn cách bắt đầu xây dựng một trang web bằng khung SwifWeb.
featured image - Cách sử dụng Swift để phát triển web
Mikhail Isaev HackerNoon profile picture
0-item
1-item

Thế giới phát triển web rất rộng lớn và thật dễ dàng để cảm thấy lạc lõng trong dòng công nghệ mới liên tục xuất hiện mỗi ngày. Hầu hết các công nghệ mới này được xây dựng bằng JavaScript hoặc TypeScript. Tuy nhiên, trong bài viết này, tôi sẽ giới thiệu với bạn cách phát triển web bằng Swift gốc, trực tiếp bên trong trình duyệt của bạn và tôi tin rằng nó sẽ gây ấn tượng với bạn.


Làm thế nào là nó có thể?

Để Swift hoạt động tự nhiên trên trang web, trước tiên, nó cần được biên dịch thành mã byte WebAssembly và sau đó JavaScript có thể tải mã đó lên trang. Toàn bộ quá trình biên dịch hơi phức tạp một chút vì chúng ta cần sử dụng chuỗi công cụ đặc biệt và xây dựng các tệp trợ giúp, đó là lý do tại sao có sẵn các công cụ CLI trợ giúp: Carton và Webber.

Sử dụng chuỗi công cụ của bên thứ ba có ổn không?

Cộng đồng SwiftWasm đã thực hiện rất nhiều công việc để có thể biên dịch Swift thành WebAssugging bằng cách vá chuỗi công cụ Swift gốc. Họ cập nhật chuỗi công cụ bằng cách tự động lấy các thay đổi từ chuỗi công cụ ban đầu mỗi ngày và sửa bản fork của họ nếu thử nghiệm không thành công. Mục tiêu của họ là trở thành một phần của chuỗi công cụ chính thức và họ hy vọng điều đó sẽ xảy ra trong tương lai gần.

Thùng giấy hay Webber?

Thùng carton được tạo bởi cộng đồng SwiftWasm và có thể được sử dụng cho các dự án Tokamak, đây là một khung cung cấp cho bạn khả năng viết trang web bằng SwiftUI.


Webber được tạo cho các dự án SwifWeb. SwifWeb khác ở chỗ nó bao hàm toàn bộ các tiêu chuẩn HTML và CSS, cũng như tất cả các API web.


Mặc dù bạn có thể thích viết các ứng dụng web bằng SwiftUI để đảm bảo tính nhất quán của mã, nhưng tôi tin rằng đây là cách tiếp cận sai lầm vì quá trình phát triển web vốn dĩ đã khác và không thể tiếp cận theo cách giống như SwiftUI.


Đó là lý do tại sao tôi đã tạo SwifWeb, cung cấp cho bạn khả năng sử dụng tất cả sức mạnh của HTML, CSS và API web trực tiếp từ Swift, sử dụng cú pháp đẹp mắt với tính năng tự động hoàn thành và tài liệu. Và tôi đã tạo công cụ Webber vì Carton không thể biên dịch, gỡ lỗi và triển khai các ứng dụng SwifWeb đúng cách vì công cụ này không được tạo cho nó.


Tên tôi là Mikhail Isaev và tôi là tác giả của SwifWeb. Trong bài viết này, tôi sẽ chỉ cho bạn cách bắt đầu xây dựng một trang web bằng SwifWeb.

Công cụ bắt buộc


Nhanh

Bạn cần cài đặt Swift, cách dễ nhất để có nó:

  • trên macOS là cài Xcode
  • trên Linux hoặc Windows (WSL2) là sử dụng tập lệnh từ swiftlang.xyz


Trong các trường hợp khác, hãy xem hướng dẫn cài đặt trên trang web chính thức

CLI webber

Tôi đã tạo Webber để giúp bạn xây dựng, gỡ lỗi và triển khai các ứng dụng của mình.


Trên macOS, thật dễ dàng để cài đặt với HomeBrew (cài đặt nó từ trang web của họ)

 brew install swifweb/tap/webber

để cập nhật lên phiên bản mới nhất sau đó chỉ cần chạy

 brew upgrade webber


Trên Ubuntu hoặc Windows (Ubuntu trong WSL2) sao chép và biên dịch thủ công Webber

 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

để cập nhật lên phiên bản cuối cùng sau đó chạy

 cd /opt/webber sudo git pull sudo swift build -c release

nhánh chính luôn chứa mã ổn định, vì vậy hãy lấy các bản cập nhật từ nó

Tạo dự án mới

Mở terminal và thực thi

 webber new

Trong menu tương tác, chọn pwa hoặc spa và nhập tên dự án.


Thay đổi thư mục thành dự án mới tạo và thực thi webber serve .

Lệnh này sẽ biên dịch dự án của bạn thành WebAssugging, đóng gói tất cả các tệp cần thiết bên trong một thư mục .webber đặc biệt và bắt đầu phục vụ dự án của bạn trên tất cả các giao diện sử dụng cổng 8888 theo mặc định.


Đối số bổ sung cho webber serve

  • loại ứng dụng

    -t pwa cho Ứng dụng web lũy tiến

    -t spa cho Ứng dụng web đơn

  • Tên của mục tiêu nhân viên dịch vụ (thường được đặt tên là Service trong dự án PWA)

    -s Service

  • Tên của mục tiêu ứng dụng ( App theo mặc định)

    -a App

  • In thêm thông tin trong bảng điều khiển

    -v

  • Cổng cho máy chủ Webber (mặc định là 8888 )

    -p 8080

Sử dụng -p 443 để kiểm tra giống như SSL thực (với cài đặt SSL tự ký được phép)

  • Tên trình duyệt đích để tự động khởi chạy trong

    --browser safari hoặc --browser chrome

  • Phiên bản bổ sung của trình duyệt với cài đặt SSL tự ký được phép để gỡ lỗi nhân viên dịch vụ

    --browser-self-signed

  • Phiên bản bổ sung của trình duyệt ở chế độ ẩn danh

    --browser-incognito

Ứng dụng

Ứng dụng bắt đầu trong 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() } }

Vòng đời

Nó hoạt động theo cách giống như iOS:

didFinishLaunching khi ứng dụng mới bắt đầu

willTerminate khi ứng dụng sắp chết

willResignActive khi cửa sổ sắp không hoạt động

didBecomeActive khi cửa sổ đang hoạt động

didEnterBackground khi cửa sổ đang ở chế độ nền

willEnterForeground khi cửa sổ đi vào nền trước


Phương pháp hữu ích nhất ở đây là didFinishLaunching vì đây là nơi tuyệt vời để định cấu hình ứng dụng. Bạn thấy nó thực sự giống như một ứng dụng iOS! 😀


app này chứa các phương pháp tiện lợi hữu ích:

registerServiceWorker(“serviceName“) gọi để đăng ký nhân viên dịch vụ PWA

lệnh gọi addScript(“path/to/script.js“) để thêm tập lệnh tương đối hoặc bên ngoài

addStylesheet(“path/to/style.css“) gọi để thêm kiểu tương đối hoặc kiểu bên ngoài

addFont(“path/to/font.woff”, type:) gọi để thêm phông chữ tương đối hoặc phông chữ bên ngoài, tùy chọn đặt loại

addIcon(“path/to/icon“, type:color:) gọi để thêm biểu tượng, tùy chọn đặt loại và màu


Ngoài ra, nó còn là nơi để cấu hình các thư viện bổ sung như Autolayout , Bootstrap , Materialize , v.v.

tuyến đường

Định tuyến là cần thiết để hiển thị trang thích hợp dựa trên URL hiện tại.


Để hiểu cách sử dụng định tuyến, bạn phải hiểu URL là gì

https://website.com/hello/world - đây /hello/worldđường dẫn

Như bạn đã thấy, ban đầu, trong lớp Ứng dụng, chúng ta nên khai báo tất cả các tuyến cấp cao nhất.

Cấp cao nhất có nghĩa là các trang được khai báo trong các tuyến này sẽ chiếm toàn bộ không gian trong cửa sổ.


Ok, ví dụ, tuyến gốc có thể được đặt theo ba cách

 Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() }

Mình thấy tấm cuối đẹp nhất 🙂


Các tuyến đăng nhập hoặc đăng ký có thể được đặt như thế này

 Page("login") { LoginPage() } Page("registration") { RegistrationPage() }


Các tuyến liên quan đến tham số

 Page("article/:id") { ArticlePage() }

:id trong ví dụ trên là một phần động của route. Chúng ta có thể truy xuất mã định danh này trong lớp ArticlePage để hiển thị bài viết được liên kết với nó.

 class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } }

Bạn có thể có nhiều tham số trong đường dẫn. Truy xuất tất cả chúng theo cùng một cách.

Truy vấn

Điều thú vị tiếp theo trong đường dẫn là truy vấn , cũng rất dễ sử dụng. Ví dụ: hãy xem xét tuyến đường /search , dự kiến sẽ có các tham số truy vấn agetext tìm kiếm.

https://website.com/search**?text=Alex&age=19** - phần cuối cùng là truy vấn


Chỉ cần khai báo lộ trình tìm kiếm

 Page("search") { SearchPage() }

Và lấy dữ liệu truy vấn trong lớp SearchPage như thế này

 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)") } } }

Bất cứ điều gì

Bạn cũng có thể sử dụng * để khai báo tuyến đường chấp nhận mọi thứ trong phần đường dẫn cụ thể như thế này

 Page("foo", "*", "bar") { SearchPage() }

Tuyến đường trên sẽ chấp nhận mọi thứ giữa foo và bar, ví dụ: /foo/aaa/bar, /foo/bbb/bar, v.v.

Bắt hết

Với dấu ** bạn có thể đặt một tuyến đường bắt tất cả đặc biệt sẽ xử lý mọi thứ chưa khớp với các tuyến đường khác tại một đường dẫn cụ thể.


Sử dụng nó để tạo tuyến đường 404 toàn cầu

 Page("**") { NotFoundPage() }

hoặc cho đường dẫn cụ thể, ví dụ: khi người dùng không tìm thấy

 Page("user", "**") { UserNotFoundPage() }


Hãy làm rõ các tình huống với các route được khai báo ở trên

/user/1 - nếu có một lộ trình cho /user/:id thì nó sẽ trả về UserPage . Nếu không sẽ rơi vào…


Người dùngNotFoundPage

/user/1/hello - nếu có route cho /user/:id/hello thì nó sẽ rơi vào UserNotFoundPage

/something - nếu không có route cho /something thì nó sẽ rơi vào NotFoundPage

Định tuyến lồng nhau

Chúng tôi có thể không muốn thay thế toàn bộ nội dung trên trang cho lộ trình tiếp theo mà chỉ một số khối nhất định. Đây là nơi mà FragmentRouter có ích!


Hãy xem xét rằng chúng ta có các tab trên trang /user . Mỗi tab là một tuyến con và chúng tôi muốn phản ứng với những thay đổi trong tuyến con bằng cách sử dụng FragmentRouter .


Khai báo top-level route trong lớp App

 Page("user") { UserPage() }

Và khai báo FragmentRouter trong lớp 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() } } } }


Trong ví dụ trên, FragmentRouter xử lý các định tuyến con /user/profile/user/friends và hiển thị nó bên dưới Thanh điều hướng , vì vậy trang không bao giờ tải lại toàn bộ nội dung mà chỉ tải lại các đoạn cụ thể.


Cũng có thể được khai báo nhiều hơn một đoạn với các chương trình con giống hoặc khác nhau và tất cả chúng sẽ hoạt động cùng nhau như ma thuật!


Btw FragmentRouter là một Div và bạn có thể định cấu hình nó bằng cách gọi

 FragmentRouter(self) .configure { div in // do anything you want with the div }

biểu định kiểu

Bạn có thể sử dụng các tệp CSS truyền thống, nhưng bạn cũng có khả năng kỳ diệu mới để sử dụng biểu định kiểu được viết bằng Swift!

Khái niệm cơ bản

Để khai báo quy tắc CSS bằng Swift, chúng ta có đối tượng Quy tắc .


Nó có thể được xây dựng một cách khai báo bằng cách gọi các phương thức của nó

 Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto)

hoặc cách giống như SwiftUI bằng cách sử dụng @resultBuilder

 Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) }


Cả hai cách đều như nhau, tuy nhiên, tôi thích cách thứ nhất hơn vì tính năng tự động hoàn thành ngay sau khi tôi nhập . 😀

Tất cả các phương thức CSS được mô tả trên MDN đều khả dụng.

Hơn thế nữa, nó tự động xử lý các tiền tố của trình duyệt!

Tuy nhiên, bạn có thể đặt thuộc tính tùy chỉnh theo cách này trong một số trường hợp cụ thể

 Rule(...selector...) .custom("customKey", "customValue")

Bộ chọn

Để thiết lập những yếu tố Quy tắc sẽ ảnh hưởng, chúng ta phải thiết lập một bộ chọn. Tôi thấy bộ chọn là truy vấn trong cơ sở dữ liệu, nhưng các phần của truy vấn bộ chọn đó tôi gọi là con trỏ.


Cách dễ nhất để xây dựng một con trỏ là khởi tạo nó bằng chuỗi thô

 Pointer("a")


Nhưng cách nhanh chóng đúng đắn là xây dựng nó bằng cách gọi .pointer tại thẻ HTML cần thiết như thế này

 H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId

Đó là về các con trỏ cơ bản, nhưng chúng cũng có các công cụ sửa đổi như :hover :first :first-child , v.v.

 H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover

Bạn có thể khai báo bất kỳ công cụ sửa đổi hiện có nào, tất cả chúng đều có sẵn.

Nếu thiếu một cái gì đó, đừng ngần ngại tạo một tiện ích mở rộng để thêm nó!

Và đừng quên gửi pull request trên github để add cho mọi người nhé.

Bạn cũng có thể nối các con trỏ

 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


Cách sử dụng bộ chọn trong Quy tắc

 Rule(Pointer("a")) // or Rule(A.pointer)

Cách sử dụng nhiều bộ chọn trong Quy tắc

 Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer))

Nó tạo ra mã CSS sau

 a, h1#myId, div > p { }

khả năng phản ứng

Hãy khai báo các kiểu tối và sáng cho ứng dụng của chúng ta và sau này, chúng ta sẽ có thể dễ dàng chuyển đổi giữa chúng.

 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 }) } }


LightStyleDarkStyle có thể được khai báo trong các tệp riêng biệt hoặc ví dụ: trong 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) } }


Và sau đó ở đâu đó trong giao diện người dùng của một số trang, chỉ cần gọi

 App.current.theme = .light // to switch to light theme // or App.current.theme = .dark // to switch to dark theme

Và nó sẽ kích hoạt hoặc hủy kích hoạt các bảng định kiểu liên quan! Điều đó không tuyệt sao? 😎


Nhưng bạn có thể nói rằng mô tả phong cách trong Swift thay vì CSS khó hơn, vậy vấn đề là gì?


Điểm chính là phản ứng! Chúng ta có thể sử dụng @State với các thuộc tính CSS và thay đổi giá trị một cách nhanh chóng!


Chỉ cần xem qua, chúng ta có thể tạo một lớp với một số thuộc tính phản ứng và thay đổi nó bất cứ lúc nào trong thời gian chạy, do đó, bất kỳ phần tử nào trên màn hình sử dụng lớp đó sẽ được cập nhật! Nó hiệu quả hơn nhiều so với việc chuyển lớp cho nhiều phần tử!


 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) } }


Sau này từ bất kỳ nơi nào trong mã chỉ cần gọi

 App.current.reactiveColor = .yellow // or any color you want

và nó sẽ cập nhật màu trong Biểu định kiểu và trong tất cả các phần tử sử dụng nó 😜


Ngoài ra, có thể thêm CSS thô vào Biểu định kiểu

 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; } """ } }

bạn có thể trộn các chuỗi CSS thô nhiều lần nếu cần

trang

Bộ định tuyến đang hiển thị các trang trên mỗi tuyến. Trang là bất kỳ lớp nào được kế thừa từ PageController .


PageController có các phương thức vòng đời như willLoad didLoad willUnload didUnload , các phương thức giao diện người dùng buildUIbody và biến bao bọc thuộc tính cho các phần tử HTML.

Về mặt kỹ thuật, PageController chỉ là một Div và bạn có thể đặt bất kỳ thuộc tính nào của nó trong phương thức 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") } } }


Nếu trang của bạn rất nhỏ, bạn có thể khai báo nó theo cách ngắn gọn này

 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ó không đẹp và ngắn gọn sao? 🥲


Phương pháp tiện lợi tiền thưởng

alert(message: String) - phương thức alert JS trực tiếp

changePath(to: String) - chuyển đổi đường dẫn URL

Phần tử HTML

Cuối cùng, tôi sẽ cho bạn biết cách (!) để xây dựng và sử dụng các phần tử HTML!


Tất cả các phần tử HTML với các thuộc tính của chúng đều có sẵn trong Swift, danh sách đầy đủ ví dụ như trên MDN .


Chỉ là một ví dụ về danh sách ngắn các phần tử HTML:

mã swifWeb

Mã HTML

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”>


Như bạn có thể thấy, rất dễ dàng truy cập vào bất kỳ thẻ HTML nào trong Swift vì tất cả chúng đều được thể hiện dưới cùng một tên, ngoại trừ các đầu vào. Điều này là do các loại đầu vào khác nhau có các phương pháp khác nhau và tôi không muốn trộn lẫn chúng.


Div đơn giản

 Div()

chúng ta có thể truy cập tất cả các thuộc tính và thuộc tính kiểu của nó như thế này

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

phân lớp

Phân lớp phần tử HTML để xác định trước kiểu dáng cho nó hoặc để tạo một phần tử tổng hợp có nhiều phần tử con được xác định trước và một số phương thức thuận tiện có sẵn bên ngoài hoặc để đạt được các sự kiện vòng đời như didAddToDOMdidRemoveFromDOM .

Hãy tạo một phần tử Divider chỉ là một Div nhưng với lớp .divider được xác định trước

 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) } }

Điều rất quan trọng là gọi các siêu phương thức khi phân lớp.

Nếu không có nó, bạn có thể gặp hành vi không mong muốn.

Đang thêm vào DOM

Phần tử có thể được thêm vào DOM của PageController hoặc phần tử HTML ngay hoặc sau này.


Ngay lập tức

 Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } }


Hoặc sau này sử dụng lazy var

 lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 }

Vì vậy, bạn có thể khai báo trước một phần tử HTML và thêm nó vào DOM bất kỳ lúc nào sau đó!

Xóa khỏi DOM

 lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove()

Truy cập phần tử gốc

Bất kỳ phần tử HTML nào cũng có thuộc tính giám sát tùy chọn cấp quyền truy cập cho cha mẹ của nó nếu nó được thêm vào DOM

 Div().superview?.backgroundColor(.red)

điều kiện nếu/khác

Chúng ta thường chỉ cần hiển thị các phần tử trong một số điều kiện nhất định, vì vậy hãy sử dụng if/else cho điều đó

 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") } }

Nhưng nó không phản ứng. Nếu bạn cố đặt showDiv2 thành false thì không có gì xảy ra.


ví dụ phản ứng

 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 }


Tại sao chúng ta nên sử dụng $showDiv2.map {…} ?

Sắp xếp: bởi vì nó không phải là SwiftUI. Ở tất cả.


Đọc thêm về @State bên dưới.


HTML thô

Bạn cũng có thể cần thêm HTML thô vào trang hoặc phần tử HTML và có thể dễ dàng

 Div { """ <a href="https://google.com">Go to Google</a> """ }


Cho mỗi

ví dụ tĩnh

 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) } }

ví dụ động

 @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

Tương tự như ví dụ trên, nhưng cũng có 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 } }


Bạn có thể sử dụng BuilderFunction trong vòng lặp ForEach để tính toán một số giá trị chỉ một lần như giá trị delay trong ví dụ sau

 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) } }


Nó cũng có thể lấy chức năng như một đối số

 BuilderFunction(calculate) { calculatedValue in // CSS rule or DOM element } func calculate() -> Int { return 1 + 1 }

BuilderFunction cũng có sẵn cho các phần tử HTML :)

Phản ứng với @State

@State là điều mong muốn nhất hiện nay đối với lập trình khai báo.


Như tôi đã nói với bạn ở trên: nó không phải là SwiftUI, vì vậy không có máy trạng thái toàn cầu nào theo dõi và vẽ lại mọi thứ. Và các phần tử HTML không phải là các cấu trúc tạm thời mà là các lớp, vì vậy chúng là các đối tượng thực và bạn có quyền truy cập trực tiếp vào chúng. Nó tốt hơn và linh hoạt hơn nhiều, bạn có toàn quyền kiểm soát.

Nó là gì dưới mui xe?

Nó là một trình bao bọc thuộc tính thông báo cho tất cả những người đăng ký về những thay đổi của nó.

Làm thế nào để đăng ký thay đổi?

 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)") }

Các phần tử HTML có thể phản ứng với những thay đổi như thế nào?

Ví dụ văn bản đơn giản

 @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

Ví dụ số đơn giản

 @State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div

Ví dụ boolean đơn giản

 @State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div

ví dụ ánh xạ

 @State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" })

Ánh xạ hai trạng thái

 @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 })

Ánh xạ nhiều hơn hai trạng thái

 @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ất cả các thuộc tính HTML và CSS có thể xử lý các giá trị @State

Tiện ích mở rộng

Mở rộng các phần tử HTML

Bạn có thể thêm một số phương thức tiện lợi vào các phần tử cụ thể như Div

 extension Div { func makeItBeautiful() {} }

Hoặc các nhóm phần tử nếu bạn biết class cha của chúng.


Có ít lớp cha.

BaseActiveStringElement - dành cho các phần tử có thể được khởi tạo bằng chuỗi, như a , h1 , v.v.

BaseContentElement - dành cho tất cả các phần tử có thể chứa nội dung bên trong nó, như div , ul , v.v.

BaseElement - dành cho tất cả các phần tử


Vì vậy, phần mở rộng cho tất cả các yếu tố có thể được viết theo cách này

 extension BaseElement { func doSomething() {} }

Khai báo màu sắc

Lớp màu chịu trách nhiệm về màu sắc. Nó có màu HTML được xác định trước, nhưng bạn có thể có màu của riêng mình

 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) }

Sau đó, sử dụng nó như H1(“Text“).color(.myColor1)

khai báo các lớp

 extension Class { var my: Class { "my" } }

Sau đó, sử dụng nó như Div().class(.my)

khai báo id

 extension Id { var myId: Id { "my" } }

Sau đó, sử dụng nó như Div().id(.my)

API web

Cửa sổ

đối tượng window được bao bọc hoàn toàn và có thể truy cập thông qua biến App.current.window .

Tham khảo đầy đủ có sẵn trên MDN .


Hãy làm tổng quan ngắn dưới đây

Cờ tiền cảnh

Bạn có thể nghe nó trong Lifecycle trong App.swift hoặc trực tiếp theo cách này

 App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed }

hoặc chỉ cần đọc nó mọi lúc mọi nơi

 if App.current.window.isInForeground { // do somethign }

hoặc phản ứng với nó bằng phần tử HTML

 Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white })

Cờ hoạt động

Nó giống như cờ Foreground, nhưng có thể truy cập qua App.current.window.isActive

Nó phát hiện nếu người dùng vẫn đang tương tác bên trong cửa sổ.

Trạng thái trực tuyến

Tương tự như cờ Foreground, nhưng có thể truy cập qua App.current.window.isOnline

Nó phát hiện nếu người dùng vẫn có quyền truy cập vào internet.

trạng thái chế độ tối

Tương tự như cờ Foreground, nhưng có thể truy cập qua App.current.window.isDark

Nó phát hiện xem trình duyệt hoặc hệ điều hành của người dùng có ở chế độ tối hay không.

Kích thước bên trong

Kích thước của khu vực nội dung của cửa sổ (khung nhìn) bao gồm thanh cuộn

App.current.window.innerSize là đối tượng Kích thước trong các giá trị widthheight bên trong.

Cũng có sẵn dưới dạng biến @State .

Kích thước bên ngoài

Kích thước của cửa sổ trình duyệt, bao gồm thanh công cụ/thanh cuộn.

App.current.window.outerSize là đối tượng Kích thước trong các giá trị widthheight bên trong.

Cũng có sẵn dưới dạng biến @State .

Màn hình

Đối tượng đặc biệt để kiểm tra các thuộc tính của màn hình mà cửa sổ hiện tại đang được hiển thị. Có sẵn qua App.current.window.screen .

Thuộc tính thú vị nhất thường là pixelRatio .

Lịch sử

Chứa các URL được người dùng truy cập (trong cửa sổ trình duyệt).

Có sẵn qua App.current.window.history hoặc chỉ History.shared .

Nó có thể truy cập dưới dạng biến @State , vì vậy bạn có thể lắng nghe những thay đổi của nó nếu cần.

 App.current.window.$history.listen { history in // read history properties }

Nó cũng có thể truy cập dưới dạng biến đơn giản

 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

Thông tin chi tiết có sẵn trên MDN .

Vị trí

Chứa thông tin về URL hiện tại.

Có sẵn qua App.current.window.location hoặc chỉ Location.shared .

Nó có thể truy cập dưới dạng biến @State , vì vậy bạn có thể lắng nghe những thay đổi của nó nếu cần.

Đây là cách bộ định tuyến hoạt động chẳng hạn.

 App.current.window.$location.listen { location in // read location properties }


Nó cũng có thể truy cập như một biến đơn giản

 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

Thông tin chi tiết có sẵn trên MDN .

Hoa tiêu

Chứa thông tin về trình duyệt.

Có sẵn qua App.current.window.navigator hoặc chỉ Navigator.shared

Các thuộc tính thú vị nhất thường là cookieEnabled language platform userAgent .

Lưu trữ cục bộ

Cho phép lưu các cặp khóa/giá trị trong trình duyệt web. Lưu trữ dữ liệu không có ngày hết hạn.

Có sẵn dưới dạng App.current.window.localStorage hoặc chỉ 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()

Theo dõi thay đổi

 LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") }

Theo dõi tất cả các mục loại bỏ

 LocalStorage.onClear { print("LocalStorage: all items has been removed") }

PhiênStorage

Cho phép lưu các cặp khóa/giá trị trong trình duyệt web. Lưu trữ dữ liệu chỉ trong một phiên.

Có sẵn dưới dạng App.current.window.sessionStorage hoặc chỉ SessionStorage.shared .

API hoàn toàn giống như trong LocalStorage được mô tả ở trên.

Tài liệu

Đại diện cho bất kỳ trang web nào được tải trong trình duyệt và phục vụ như một điểm vào nội dung của trang web.

Có sẵn qua 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]

bản địa hóa

nội địa hóa tĩnh

Bản địa hóa cổ điển là tự động và phụ thuộc vào ngôn ngữ hệ thống của người dùng

Cách sử dụng

 H1(String( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))

Bản địa hóa động

Nếu bạn muốn thay đổi các chuỗi được bản địa hóa trên màn hình khi đang di chuyển (không cần tải lại trang)

Bạn có thể thay đổi ngôn ngữ hiện tại bằng cách gọi

 Localization.current = .es

Nếu bạn đã lưu ngôn ngữ của người dùng ở đâu đó trong cookie hoặc bộ lưu trữ cục bộ thì bạn phải đặt ngôn ngữ đó khi khởi chạy ứng dụng

 Lifecycle.didFinishLaunching { Localization.current = .es }

Cách sử dụng

 H1(LString( .en("Hello"), .fr("Bonjour"), .ru("Привет"), .es("Hola"), .zh_Hans("你好"), .ja("こんにちは")))

Ví dụ nâng cao

 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 }

Tìm về

 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()

ổ cắm web

 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")) }

Bảng điều khiển

print(“Hello world“) tương đương với console.log('Hello world') trong JavaScript


Các phương pháp điều khiển cũng được bao bọc bằng tình yêu ❤️

 Console.dir(...) Console.error(...) Console.warning(...) Console.clear()

Xem trước trực tiếp

Để thực hiện công việc xem trước trực tiếp, hãy khai báo lớp WebPreview trong mỗi tệp bạn muốn.

 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

Vui lòng đọc hướng dẫn trên trang kho lưu trữ . Đó là giải pháp phức tạp nhưng hoàn toàn hiệu quả 😎


VSCode

Chuyển đến Tiện ích mở rộng bên trong VSCode và tìm kiếm Webber .

Khi nó được cài đặt, nhấn Cmd+Shift+P (hoặc Ctrl+Shift+P trên Linux/Windows)

Tìm và khởi chạy Webber Live Preview .

Ở bên phải, bạn sẽ thấy cửa sổ xem trước trực tiếp và cửa sổ này sẽ làm mới bất cứ khi nào bạn lưu tệp chứa lớp WebPreview .

Truy cập vào JavaScript

Nó có sẵn thông qua JavaScriptKit , nền tảng của SwifWeb .

Read how to you nằm trong kho lưu trữ chính thức .

Tài nguyên

Bạn có thể thêm css , js , png , jpg và bất kỳ tài nguyên tĩnh nào khác bên trong dự án.

Nhưng để chúng có sẵn trong quá trình gỡ lỗi hoặc trong các tệp phát hành cuối cùng, bạn phải khai báo tất cả chúng trong Package.swift như thế này

 .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") ]),

Sau này bạn sẽ có thể truy cập chúng, ví dụ như thế này Img().src(“/images/logo.png“)

gỡ lỗi

Khởi chạy Webber theo cách sau

webber serve chỉ để nhanh chóng khởi chạy nó

webber serve -t pwa -s Service để khởi chạy nó ở chế độ PWA

thông số bổ sung

-v hoặc --verbose để hiển thị thêm thông tin trong bảng điều khiển cho mục đích gỡ lỗi

-p 443 hoặc --port 443 để khởi động máy chủ webber trên cổng 443 thay vì 8888 mặc định

--browser chrome/safari để tự động mở trình duyệt mong muốn, theo mặc định, nó không mở bất kỳ trình duyệt nào

--browser-self-signed cần thiết để gỡ lỗi nhân viên dịch vụ cục bộ, nếu không thì chúng không hoạt động

--browser-incognito để mở phiên bản bổ sung của trình duyệt ở chế độ ẩn danh, chỉ hoạt động với chrome


Vì vậy, để xây dựng ứng dụng của bạn ở chế độ gỡ lỗi, hãy tự động mở ứng dụng đó trong Chrome và tự động làm mới trình duyệt bất cứ khi nào bạn thay đổi bất kỳ tệp nào, hãy khởi chạy ứng dụng đó theo cách này

cho SPA

webber serve --browser chrome

để thử nghiệm PWA thực

webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito


Tải ứng dụng ban đầu

Bạn có thể muốn cải thiện quá trình tải ban đầu.

Đối với điều đó, chỉ cần mở thư mục .webber/entrypoint/dev bên trong dự án và chỉnh sửa tệp index.html .

Nó chứa mã HTML ban đầu với các trình lắng nghe rất hữu ích: WASMLoadingStarted WASMLoadingStartedWithoutProgress WASMLoadingProgress WASMLoadingError .

Bạn có thể tự do chỉnh sửa mã đó thành bất kỳ thứ gì bạn muốn để triển khai phong cách tùy chỉnh của mình 🔥

Khi bạn hoàn thành việc triển khai mới, đừng quên lưu vào thư mục .webber/entrypoint/release

phát hành tòa nhà

Chỉ cần thực hiện webber release hoặc webber release -t pwa -s Service cho PWA.

Sau đó lấy các tệp đã biên dịch từ thư mục .webber/release và tải chúng lên máy chủ của bạn.

cách triển khai

Bạn có thể tải tệp của mình lên bất kỳ dịch vụ lưu trữ tĩnh nào.

Dịch vụ lưu trữ phải cung cấp Loại nội dung chính xác cho các tệp wasm !

Có, điều rất quan trọng là phải có đúng tiêu đề Content-Type: application/wasm cho các tệp wasm , nếu không, rất tiếc, trình duyệt sẽ không thể tải ứng dụng WebAssugging của bạn.

Ví dụ: GithubPages không cung cấp Loại nội dung chính xác cho các tệp wasm nên rất tiếc là không thể lưu trữ các trang WebAssembly trên đó.

Nginx

Nếu bạn sử dụng máy chủ của riêng mình với nginx, hãy mở /etc/nginx/mime.types và kiểm tra xem nó có chứa application/wasm wasm; ghi. Nếu có thì bạn tốt để đi!

Phần kết luận

Tôi hy vọng hôm nay tôi đã làm bạn ngạc nhiên và ít nhất bạn sẽ dùng thử SwifWeb và tối đa sẽ bắt đầu sử dụng nó cho dự án web lớn tiếp theo của bạn!


Vui lòng đóng góp cho bất kỳ thư viện SwifWeb nào và đồng thời gắn dấu sao ⭐️tất cả chúng!


Tôi có một cộng đồng SwiftStream tuyệt vời trong Discord, nơi bạn có thể tìm thấy sự hỗ trợ to lớn, đọc các hướng dẫn nhỏ và được thông báo trước về mọi bản cập nhật sắp tới! Sẽ rất tuyệt khi gặp bạn với chúng tôi!


Đây mới chỉ là bước khởi đầu, vì vậy hãy theo dõi các bài viết khác về SwifWeb!


Hãy nói với bạn bè của bạn!