Swift를 설치해야 합니다. 가장 쉬운 방법은 다음과 같습니다.
brew install swifweb/tap/webber
나중에 최신 버전으로 업데이트하려면 다음을 실행하세요. 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
나중에 마지막 버전으로 업데이트하려면 실행하세요. cd /opt/webber sudo git pull sudo swift build -c release
메인 브랜치에는 항상 안정적인 코드가 포함되어 있으므로 자유롭게 업데이트를 가져오세요.
webber new
대화형 메뉴에서 pwa
또는 spa
선택하고 프로젝트 이름을 입력합니다.
새로 생성된 프로젝트로 디렉터리를 변경하고 webber serve
실행합니다.
이 명령은 프로젝트를 WebAssembly로 컴파일하고, 필요한 모든 파일을 특수 .webber
폴더에 패키지하고, 기본적으로 포트 8888
사용하여 모든 인터페이스에서 프로젝트 제공을 시작합니다.
webber serve
에 대한 추가 인수
프로그레시브 웹 앱의 경우 -t pwa
단일 웹 앱용 -t spa
서비스 작업자 대상의 이름(일반적으로 PWA 프로젝트에서는 Service
라고 함)
-s Service
앱 대상 이름(기본적으로 App
)
-a App
-v
Webber 서버용 포트(기본값은 8888
)
-p 8080
실제 SSL처럼 테스트하려면 -p 443
사용하세요(자체 서명된 SSL 설정이 허용됨).
--browser safari
또는 --browser chrome
--browser-self-signed
--browser-incognito
앱은 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() } }
iOS와 유사한 방식으로 작동합니다.
didFinishLaunching
앱이 막 시작되었을 때
willTerminate
앱이 종료될 때
창이 비활성화될 때 willResignActive
창이 활성화되었을 때 didBecomeActive
창이 배경으로 들어갈 때 didEnterBackground
willEnterForeground
창이 전경으로 들어갈 때
여기서 가장 유용한 방법은 didFinishLaunching
입니다. 왜냐하면 앱을 구성하기에 좋은 장소이기 때문입니다. 정말 iOS 앱과 같은 느낌이군요! 😀
여기 app
에는 유용한 편의 방법이 포함되어 있습니다.
PWA 서비스 워커를 등록하기 위한 registerServiceWorker(“serviceName“)
호출
상대 또는 외부 스크립트를 추가하기 위한 addScript(“path/to/script.js“)
호출
상대 또는 외부 스타일을 추가하기 위한 addStylesheet(“path/to/style.css“)
호출
addFont(“path/to/font.woff”, type:)
상대 또는 외부 글꼴을 추가하기 위한 호출, 선택적으로 유형 설정
addIcon(“path/to/icon“, type:color:)
아이콘을 추가하기 위한 호출, 선택적으로 유형 및 색상 설정
//website.com/hello/world - 여기서는 /hello/world 가 경로 입니다.
Page("/") { IndexPage() } Page("") { IndexPage() } Page { IndexPage() }
마지막이 제일 예쁜 것 같아요 🙂
Page("login") { LoginPage() } Page("registration") { RegistrationPage() }
매개변수 관련 경로
Page("article/:id") { ArticlePage() }
위 예의 :id는 경로의 동적 부분입니다. ArticlePage 클래스에서 이 식별자를 검색하여 이와 관련된 기사를 표시할 수 있습니다.
class ArticlePage: PageController { override func didLoad(with req: PageRequest) { if let articleId = req.parameters.get("id") { // Retrieve article here } } }
경로에서 다음으로 흥미로운 점은 사용하기 매우 쉬운 쿼리 입니다. 예를 들어, 검색 text
와 age
쿼리 매개변수가 있을 것으로 예상되는 /search 경로를 고려해 보겠습니다.
//website.com/search**?text=Alex&age=19** - 마지막 부분은 쿼리 입니다.
Page("search") { SearchPage() }
그리고 다음과 같이 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)") } } }
*
사용하여 다음과 같이 특정 경로 부분의 모든 항목을 허용하는 경로를 선언할 수도 있습니다.
Page("foo", "*", "bar") { SearchPage() }
위의 경로는 foo와 bar 사이의 모든 항목을 허용합니다(예: /foo/aaa/bar, /foo/bbb/bar 등). **
기호를 사용하면 특정 경로의 다른 경로와 일치하지 않는 모든 항목을 처리하는 특수 포괄 경로를 설정할 수 있습니다.
Page("**") { NotFoundPage() }
또는 특정 경로(예: 사용자를 찾을 수 없는 경우) Page("user", "**") { UserNotFoundPage() }
/user/1 - /user/:id에 대한 경로가 있으면 UserPage 를 반환합니다. 그렇지 않으면…
UserNotFound페이지
/user/1/hello - /user/:id/hello에 대한 경로가 있으면 UserNotFoundPage 에 속하게 됩니다.
/something - /something에 대한 경로가 없으면 NotFoundPage 에 속하게 됩니다.
다음 경로를 위해 페이지의 전체 콘텐츠를 교체하는 것이 아니라 특정 블록만 교체하고 싶을 수도 있습니다. FragmentRouter 가 유용한 곳입니다!
/user 페이지에 탭이 있다고 가정해 보겠습니다. 각 탭은 하위 경로이며 FragmentRouter를 사용하여 하위 경로의 변경 사항에 반응하려고 합니다.
App 클래스에서 최상위 경로를 선언합니다.
Page("user") { UserPage() }
UserPage 클래스에서 FragmentRouter를 선언합니다.
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() } } } }
위의 예에서 FragmentRouter는 /user/profile 및 /user/friends 하위 경로를 처리하고 Navbar 아래에 렌더링하므로 페이지는 전체 콘텐츠를 다시 로드하지 않고 특정 조각만 다시 로드합니다.
Btw FragmentRouter 는 Div 이며 호출하여 구성할 수 있습니다.
FragmentRouter(self) .configure { div in // do anything you want with the div }
Swift를 사용하여 CSS 규칙을 선언하기 위해 Rule 객체가 있습니다.
Rule(...selector...) .alignContent(.baseline) .color(.red) // or rgba/hex color .margin(v: 0, h: .auto)
또는 @resultBuilder를 사용하는 SwiftUI와 유사한 방식 Rule(...selector...) { AlignContent(.baseline) Color(.red) Margin(v: 0, h: .auto) }
두 가지 방법 모두 동일하지만 을 입력한 직후의 자동 완성 때문에 첫 번째 방법을 선호합니다 .
😀
Rule(...selector...) .custom("customKey", "customValue")
Pointer("a")
하지만 올바른 빠른 방법은 다음과 같이 필요한 HTML 태그에서 .pointer
호출하여 빌드하는 것입니다.
H1.pointer // h1 A.pointer // a Pointer.any // * Class("myClass").pointer // .myClass Id("myId").pointer // #myId
기본 포인터에 관한 것이지만 :hover
:first
:first-child
등과 같은 수정자도 있습니다.
H1.pointer.first // h1:first H1.pointer.firstChild // h1:first-child H1.pointer.hover // h1:hover
기존 수정자를 선언할 수 있으며 모두 사용할 수 있습니다. 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
규칙 에서 선택기를 사용하는 방법
Rule(Pointer("a")) // or Rule(A.pointer)
규칙 에서 둘 이상의 선택기를 사용하는 방법
Rule(A.pointer, H1.id(.myId), Div.pointer.parent(P.pointer))
다음 CSS 코드를 생성합니다. 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 과 DarkStyle은 별도의 파일이나 예를 들어 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
그리고 관련 스타일시트를 활성화하거나 비활성화합니다! 멋지지 않나요? 😍
하지만 CSS 대신 Swift로 스타일을 설명하는 것이 더 어렵다고 말할 수도 있습니다. 그렇다면 요점은 무엇입니까?
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
그러면 스타일시트와 이를 사용하는 모든 요소의 색상이 업데이트됩니다 😜
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; } """ } }
필요한 만큼 원시 CSS 문자열을 혼합할 수 있습니다.라우터는 각 경로에서 페이지를 렌더링하고 있습니다. Page는 PageController 에서 상속된 클래스입니다.
PageController 에는 willLoad
didLoad
willUnload
didUnload
, UI 메서드 buildUI
및 body
, HTML 요소에 대한 속성 래퍼 변수와 같은 수명 주기 메서드가 있습니다.
기술적으로 PageController 는 단지 Div이며 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 }
아름답고 간결하지 않나요? 🥲
보너스 편의 방법
alert(message: String)
- 직접 JS alert
방법
changePath(to: String)
- URL 경로 전환
SwifWeb 코드 | HTML 코드 |
---|---|
| |
| |
| |
| |
| |
| |
간단한 Div
Div()
다음과 같이 모든 속성과 스타일 속성에 액세스할 수 있습니다. 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;">
HTML 요소를 하위 클래스화하여 스타일을 미리 정의하거나, 미리 정의된 많은 하위 요소와 외부에서 사용할 수 있는 몇 가지 편리한 메서드를 사용하여 복합 요소를 만들거나, didAddToDOM
및 didRemoveFromDOM
과 같은 수명 주기 이벤트를 달성합니다.
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) } }
요소는 즉시 또는 나중에 PageController 의 DOM 또는 HTML 요소 에 추가될 수 있습니다.
Div { H1("Title") P("Subtitle") Div { Ul { Li("One") Li("Two") } } }
또는 나중에 lazy var
사용하여
lazy var myDiv1 = Div() lazy var myDiv2 = Div() Div { myDiv1 myDiv2 }
따라서 HTML 요소를 미리 선언하고 나중에 언제든지 DOM에 추가할 수 있습니다!
lazy var myDiv = Div() Div { myDiv } // somewhere later myDiv.remove()
Div().superview?.backgroundColor(.red)
특정 조건에서만 요소를 표시해야 하는 경우가 많으므로 이를 위해 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") } }
하지만 반응성이 없습니다. showDiv2
false
로 설정하려고 하면 아무 일도 일어나지 않습니다.
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 }
왜 $showDiv2.map {…}
사용해야 합니까 ?
아래에서 @State
에 대해 자세히 알아보세요 .
Div { """ <a href="//google.com">Go to Google</a> """ }
let names = ["Bob", "John", "Annie"] Div { ForEach(names) { name in Div(name) } // or ForEach(names) { index, name in Div("\(index). \(name)") } // or with range ForEach(1...20) { index in Div() } // and even like this 20.times { Div().class(.shootingStar) } }
@State var names = ["Bob", "John", "Annie"] Div { ForEach($names) { name in Div(name) } // or with index ForEach($names) { index, name in Div("\(index). \(name)") } } Button("Change 1").onClick { // this will append new Div with name automatically self.names.append("George") } Button("Change 2").onClick { // this will replace and update Divs with names automatically self.names = ["Bob", "Peppa", "George"] }
위의 예와 동일하지만 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 } }
다음 예제의 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) } }
BuilderFunction(calculate) { calculatedValue in // CSS rule or DOM element } func calculate() -> Int { return 1 + 1 }
BuilderFunction 은 HTML 요소에도 사용할 수 있습니다 :)
@State
는 오늘날 선언적 프로그래밍 에 가장 바람직한 것입니다.
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
간단한 숫자 예 @State var height = 20.px Div().height($height) // whenever height var changes it updates height of the Div
간단한 부울 예제 @State var hidden = false Div().hidden($hidden) // whenever hidden changes it updates visibility of the Div
매핑 예 @State var isItCold = true H1($isItCold.map { $0 ? "It is cold 🥶" : "It is not cold 😌" })
두 상태 매핑 @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 })
두 개 이상의 상태 매핑 @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 })
모든 HTML 및 CSS 속성은 @State
값을 처리할 수 있습니다.
extension Div { func makeItBeautiful() {} }
또는 상위 class
알고 있는 경우 요소 그룹입니다.
부모 클래스가 거의 없습니다.
BaseActiveStringElement
- a
, h1
등과 같이 문자열로 초기화할 수 있는 요소용입니다.
BaseContentElement
- div
, ul
등과 같이 내부에 콘텐츠를 포함할 수 있는 모든 요소에 사용됩니다.
BaseElement
- 모든 요소에 사용됩니다.
extension BaseElement { func doSomething() {} }
Color 클래스는 색상을 담당합니다. HTML 색상이 미리 정의되어 있지만 자신만의 색상을 가질 수 있습니다.
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) }
그런 다음 H1(“Text“).color(.myColor1)
처럼 사용하십시오.
extension Class { var my: Class { "my" } }
그런 다음 Div().class(.my)
처럼 사용하십시오.
extension Id { var myId: Id { "my" } }
그런 다음 Div().id(.my)
처럼 사용하십시오.
window
객체는 App.current.window
변수를 통해 완전히 래핑되고 액세스 가능합니다.
App.swift
의 Lifecycle
에서 또는 이 방법으로 직접 들을 수 있습니다.
App.current.window.$isInForeground.listen { isInForeground in // foreground flag changed }
아니면 언제 어디서나 읽으세요. if App.current.window.isInForeground { // do somethign }
또는 HTML 요소로 반응 Div().backgroundColor(App.current.window.$isInForeground.map { $0 ? .grey : .white })
Foreground 플래그와 동일하지만 App.current.window.isActive
통해 액세스할 수 있습니다.
Foreground 플래그와 동일하지만 App.current.window.isOnline
통해 액세스할 수 있습니다.
Foreground 플래그와 동일하지만 App.current.window.isDark
통해 액세스할 수 있습니다.
App.current.window.innerSize
는 내부의 width
와 height
값 내의 Size 개체입니다.
@State
변수로도 사용 가능합니다.
App.current.window.outerSize
는 내부의 width
및 height
값 내의 Size 개체입니다.
@State
변수로도 사용 가능합니다.
현재 창이 렌더링되는 화면의 속성을 검사하기 위한 특수 개체입니다. App.current.window.screen
통해 사용 가능합니다.
가장 흥미로운 속성은 일반적으로 pixelRatio
입니다.
App.current.window.history
또는 History.shared
통해 사용할 수 있습니다.
@State
변수로 액세스할 수 있으므로 필요한 경우 변경 사항을 수신할 수 있습니다.
App.current.window.$history.listen { history in // read history properties }
단순 변수로도 접근 가능 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
자세한 내용은 에서 확인할 수 있습니다. App.current.window.location
또는 Location.shared
통해 사용할 수 있습니다.
@State
변수로 액세스할 수 있으므로 필요한 경우 변경 사항을 수신할 수 있습니다.
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
자세한 내용은 에서 확인할 수 있습니다. App.current.window.navigator
또는 Navigator.shared
통해 사용 가능
가장 흥미로운 속성은 일반적으로 userAgent
platform
language
cookieEnabled
.
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()
변경 사항 추적 LocalStorage.onChange { key, oldValue, newValue in print("LocalStorage: key \(key) has been updated") }
모든 항목 제거 추적 LocalStorage.onClear { print("LocalStorage: all items has been removed") }
App.current.window.sessionStorage
또는 SessionStorage.shared
로 사용할 수 있습니다.
API는 위에서 설명한 LocalStorage 와 완전히 동일합니다.
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
사용자의 언어를 쿠키나 로컬 저장소에 저장한 경우 앱 실행 시 설정해야 합니다. 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")) }
간단한 print(“Hello world“)
JavaScript의 console.log('Hello world')
와 동일합니다.
콘솔 메소드도 사랑으로 포장됩니다 ❤️
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() } }
VSCode 내의 Extensions 로 이동하여 Webber를 검색하세요.
설치가 완료되면 Cmd+Shift+P
(또는 Linux/Windows에서는 Ctrl+Shift+P
)를 누르세요.
Webber Live Preview
찾아 실행합니다.
오른쪽에는 실시간 미리보기 창이 표시되며 WebPreview 클래스가 포함된 파일을 저장할 때마다 새로 고쳐집니다.
SwifWeb 의 기초인 JavaScriptKit을 통해 사용할 수 있습니다.
에서 방법을 읽어보세요. 프로젝트 내부에 css
, js
, png
, jpg
및 기타 정적 리소스를 추가할 수 있습니다.
하지만 디버그 도중 이나 최종 릴리스 파일에서 사용할 수 있게 하려면 Package.swift 에서 다음과 같이 모두 선언해야 합니다.
.executableTarget(name: "App", dependencies: [ .product(name: "Web", package: "web") ], resources: [ .copy("css/*.css"), .copy("css"), .copy("images/*.jpg"), .copy("images/*.png"), .copy("images/*.svg"), .copy("images"), .copy("fonts/*.woff2"), .copy("fonts") ]),
나중에 Img().src(“/images/logo.png“)
와 같이 액세스할 수 있습니다.
다음과 같은 방법으로 Webber를 시작하십시오.
webber serve
빠르게 실행하기 위한 것입니다.
webber serve -t pwa -s Service
-v
또는 --verbose
디버깅 목적으로 콘솔에 추가 정보 표시
-p 443
또는 --port 443
기본 8888 대신 443 포트에서 Webber 서버를 시작합니다.
--browser chrome/safari
원하는 브라우저를 자동으로 엽니다. 기본적으로 어떤 브라우저도 열리지 않습니다.
--browser-self-signed
서비스 워커를 로컬에서 디버그하는 데 필요합니다. 그렇지 않으면 작동하지 않습니다.
--browser-incognito
시크릿 모드에서 브라우저의 추가 인스턴스를 열려면 Chrome에서만 작동합니다.
webber serve --browser chrome
webber serve -t pwa -s Service -p 443 --browser chrome --browser-self-signed --browser-incognito
이를 위해서는 프로젝트 내부의 .webber/entrypoint/dev
폴더를 열고 index.html
파일을 편집하세요.
여기에는 매우 유용한 리스너가 포함된 초기 HTML 코드( WASMLoadingStarted
WASMLoadingStartedWithoutProgress
WASMLoadingProgress
WASMLoadingError
)가 포함되어 있습니다.
새로운 구현을 마치면 동일한 내용을 .webber/entrypoint/release
폴더에 저장하는 것을 잊지 마세요.
webber release
또는 webber release -t pwa -s Service
for PWA를 실행하기만 하면 됩니다.
그런 다음 .webber/release
폴더에서 컴파일된 파일을 가져와 서버에 업로드하세요.
호스팅은 wasm 파일에 대한 올바른 콘텐츠 유형을 제공해야 합니다!
예, wasm 파일에 대한 올바른 헤더 Content-Type: application/wasm
갖는 것이 매우 중요합니다. 그렇지 않으면 불행하게도 브라우저가 WebAssembly 애플리케이션을 로드할 수 없습니다.
예를 들어 GithubPages는 wasm 파일에 대한 올바른 Content-Type을 제공하지 않으므로 불행히도 WebAssembly 사이트를 호스팅하는 것은 불가능합니다.
nginx와 함께 자체 서버를 사용하는 경우 /etc/nginx/mime.types
열고 application/wasm wasm;
기록. 그렇다면 가셔도 좋습니다!
에 자유롭게 기여하고 ⭐️모두 별표 표시해 주세요!