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
の追加引数
プログレッシブ Web アプリの-t pwa
-t spa
for Single Web App
Service Worker ターゲットの名前 (通常、PWA プロジェクトではService
という名前)
-s Service
アプリ ターゲットの名前 (既定ではApp
)
-a App
-v
Webber サーバーのポート (デフォルトは8888
)
-p 8080
-p 443
を使用して、実際の SSL のようにテストします (自己署名 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
には便利な便利なメソッドが含まれています。
registerServiceWorker(“serviceName“)
を呼び出して、PWA サービス ワーカーを登録します。
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 } } }
パスで次に興味深いのはqueryです。これも非常に使いやすいです。たとえば、検索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が返されます。そうでなければ、それは…に陥ります
UserNotFoundPage
/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の下にレンダリングするため、ページはコンテンツ全体をリロードすることはなく、特定のフラグメントのみをリロードします。
ところで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) } }
Element は、すぐにまたは後でPageControllerまたはHTML 要素の DOM に追加できます。
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 } }
ForEach
ループでBuilderFunction
使用して、次の例のdelay
値のように、値を 1 回だけ計算できます。
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 😌" })
2 つの状態のマッピング@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 })
3 つ上述の州のマッピング@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() {} }
カラークラスは色を担当します。定義済みの 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
ユーザーの言語を Cookie またはローカル ストレージのどこかに存为した場合は、アプリの起動時に設定する有必要があります。 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
Service Worker をローカルでデバッグするために必要です。そうしないと機能しません
--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ファイルに正しい Content-Type を提供する必要があります!
はい、 wasmファイルの正しいヘッダーContent-Type: application/wasm
を持つことが非常に重要です。そうしないと、残念ながらブラウザーは WebAssembly アプリケーションをロードできません。
たとえば、GithubPages はwasmファイルの正しい Content-Type を提供しないため、残念ながら WebAssembly サイトをホストすることはできません。
nginx で独自のサーバーを使用している場合は、 /etc/nginx/mime.types
を開き、 application/wasm wasm;
記録。はいの場合は、準備完了です。
のいずれかに自由に貢献し、それらすべてにスター ⭐️ を付けてください!