您需要安装 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
用于渐进式 Web 应用程序
-t spa
用于单个 Web 应用程序
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 service worker
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 } } }
路径中下一个有趣的事情是查询,它也非常易于使用。例如,让我们考虑/search路由,它期望具有搜索text
和age
查询参数。
//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
它将系统激活或禁用相关联的版式表!这不再是很酷吗? 😎
但你或许能说用 Swift 说明图样用无限流量卡比用 CSS 更难,那有怎样的的意义呢?
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编号 | 公司网页源代码 |
---|---|
| |
| |
| |
| |
| |
| |
简单的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
。
让我们创建一个Divider
元素,它只是一个Div
但具有预定义的.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或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
值
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() {} }
颜色类负责颜色。它有预定义的 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 })
它与前景标志相同,但可通过App.current.window.isActive
访问
与前景标志相同,但可通过App.current.window.isOnline
访问
与前景标志相同,但可通过App.current.window.isDark
访问
App.current.window.innerSize
是Size对象里面的width
height
值。
也可用作@State
变量。
App.current.window.outerSize
是Size对象里面的width
和height
值。
也可用作@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 或 localstorage 中的某处,那麼您须得在应用软件程序代码开机时去软件设置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中的扩展并搜索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
以 PWA 模式启动它
-v
或--verbose
在控制台中显示更多信息以进行调试
-p 443
或--port 443
在 443 端口而不是默认的 8888 端口上启动 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;
记录。如果是,那么您就可以开始了!
请随时为任何做出贡献,并为 ⭐️ 全部加注星标!