Serve main bundle resources from root directory (#176)
This is required to make the `Image` view work as described in https://github.com/TokamakUI/Tokamak/pull/155#issuecomment-723677472. * Demangle and print Firefox stacktraces in terminal * Update the entrypoints archive URL, rename file * Add missing newline to `ProcessRunner.swift` * Remove redundant `console.log` call from `dev.js` * Detect destination env from `User-Agent` header * Silence linter in tests where it can't be avoided * Add support for basic testing in browsers * Add a browser message on finish, shut down server * Serve main bundle resources from root directory * Fix archive hashes, bump JSKit in TestApp to 0.9.0 * Add comments to clarify behavior * Fix typo in doc comment in `Package.swift`
This commit is contained in:
parent
ef402e71e8
commit
09a4e67e9e
|
@ -66,6 +66,11 @@ struct ProductType: Codable {
|
|||
public struct Product: Codable {
|
||||
let name: String
|
||||
let type: ProductType
|
||||
|
||||
/** List of names of targets that this product is composed of. Can be used to infer actual
|
||||
target descriptions used in this product.
|
||||
*/
|
||||
public let targets: [String]
|
||||
}
|
||||
|
||||
public enum TargetType: String, Codable {
|
||||
|
|
|
@ -114,26 +114,25 @@ public final class Toolchain {
|
|||
return AbsolutePath(binPath)
|
||||
}
|
||||
|
||||
private func inferDevProduct(hint: String?) throws -> String? {
|
||||
private func inferDevProduct(hint: String?) throws -> Product? {
|
||||
let package = try self.package.get()
|
||||
|
||||
var candidateProducts = package.products
|
||||
.filter { $0.type.library == nil }
|
||||
.map(\.name)
|
||||
|
||||
if let product = hint {
|
||||
candidateProducts = candidateProducts.filter { $0 == product }
|
||||
if let productName = hint {
|
||||
candidateProducts = candidateProducts.filter { $0.name == productName }
|
||||
|
||||
guard candidateProducts.count == 1 else {
|
||||
terminal.write("""
|
||||
Failed to disambiguate the executable product, \
|
||||
make sure `\(product)` product is present in Package.swift
|
||||
make sure `\(productName)` product is present in Package.swift
|
||||
""", inColor: .red)
|
||||
return nil
|
||||
}
|
||||
|
||||
terminal.logLookup("- development product: ", product)
|
||||
return product
|
||||
terminal.logLookup("- development product: ", productName)
|
||||
return candidateProducts[0]
|
||||
} else if candidateProducts.count == 1 {
|
||||
return candidateProducts[0]
|
||||
} else {
|
||||
|
@ -194,7 +193,7 @@ public final class Toolchain {
|
|||
public func buildCurrentProject(
|
||||
product: String?,
|
||||
isRelease: Bool
|
||||
) throws -> (builderArguments: [String], mainWasmPath: AbsolutePath) {
|
||||
) throws -> (builderArguments: [String], mainWasmPath: AbsolutePath, Product) {
|
||||
guard let product = try inferDevProduct(hint: product)
|
||||
else { throw ToolchainError.noExecutableProduct }
|
||||
|
||||
|
@ -217,14 +216,14 @@ public final class Toolchain {
|
|||
}
|
||||
|
||||
let binPath = try inferBinPath(isRelease: isRelease)
|
||||
let mainWasmPath = binPath.appending(component: "\(product).wasm")
|
||||
let mainWasmPath = binPath.appending(component: "\(product.name).wasm")
|
||||
terminal.logLookup("- development binary to serve: ", mainWasmPath.pathString)
|
||||
|
||||
terminal.write("\nBuilding the project before spinning up a server...\n", inColor: .yellow)
|
||||
|
||||
let builderArguments = [
|
||||
swiftPath.pathString, "build", "-c", isRelease ? "release" : "debug", "--product", product,
|
||||
"--enable-test-discovery", "--triple", "wasm32-unknown-wasi",
|
||||
swiftPath.pathString, "build", "-c", isRelease ? "release" : "debug",
|
||||
"--product", product.name, "--enable-test-discovery", "--triple", "wasm32-unknown-wasi",
|
||||
]
|
||||
|
||||
try Builder(arguments: builderArguments, mainWasmPath: mainWasmPath, fileSystem, terminal)
|
||||
|
@ -235,10 +234,10 @@ public final class Toolchain {
|
|||
"Failed to build the main executable binary, fix the build errors and restart\n",
|
||||
inColor: .red
|
||||
)
|
||||
throw ToolchainError.failedToBuild(product: product)
|
||||
throw ToolchainError.failedToBuild(product: product.name)
|
||||
}
|
||||
|
||||
return (builderArguments, mainWasmPath)
|
||||
return (builderArguments, mainWasmPath, product)
|
||||
}
|
||||
|
||||
/// Returns an absolute path to the resulting test bundle
|
||||
|
|
|
@ -48,7 +48,7 @@ struct Bundle: ParsableCommand {
|
|||
|
||||
let toolchain = try Toolchain(localFileSystem, terminal)
|
||||
|
||||
let (_, mainWasmPath) = try toolchain.buildCurrentProject(
|
||||
let (_, mainWasmPath, inferredProduct) = try toolchain.buildCurrentProject(
|
||||
product: product,
|
||||
isRelease: !debug
|
||||
)
|
||||
|
@ -85,7 +85,8 @@ struct Bundle: ParsableCommand {
|
|||
optimizedPath: optimizedPath,
|
||||
buildDirectory: mainWasmPath.parentDirectory,
|
||||
bundleDirectory: bundleDirectory,
|
||||
toolchain: toolchain
|
||||
toolchain: toolchain,
|
||||
product: inferredProduct
|
||||
)
|
||||
|
||||
terminal.write("Bundle generation finished successfully\n", inColor: .green, bold: true)
|
||||
|
@ -102,7 +103,8 @@ struct Bundle: ParsableCommand {
|
|||
optimizedPath: AbsolutePath,
|
||||
buildDirectory: AbsolutePath,
|
||||
bundleDirectory: AbsolutePath,
|
||||
toolchain: Toolchain
|
||||
toolchain: Toolchain,
|
||||
product: Product
|
||||
) throws {
|
||||
// Rename the final binary to use a part of its hash to bust browsers and CDN caches.
|
||||
let optimizedHash = try localFileSystem.readFileContents(optimizedPath).hexSHA256.prefix(16)
|
||||
|
@ -144,5 +146,29 @@ struct Bundle: ParsableCommand {
|
|||
terminal.logLookup("Copying resources to ", targetDirectory)
|
||||
try localFileSystem.copy(from: resourcesPath, to: targetDirectory)
|
||||
}
|
||||
|
||||
/* While a product may be composed of multiple targets, not sure this is widely used in
|
||||
practice. Just assuming here that the first target of this product is an executable target,
|
||||
at least until SwiftPM allows specifying executable targets explicitly, as proposed in
|
||||
swiftlint:disable:next line_length
|
||||
https://forums.swift.org/t/pitch-ability-to-declare-executable-targets-in-swiftpm-manifests-to-support-main/41968
|
||||
*/
|
||||
let inferredMainTarget = package.targets.first {
|
||||
product.targets.contains($0.name)
|
||||
}
|
||||
|
||||
guard let mainTarget = inferredMainTarget else { return }
|
||||
|
||||
let targetPath = package.resourcesPath(for: mainTarget)
|
||||
let resourcesPath = buildDirectory.appending(component: targetPath)
|
||||
for file in try localFileSystem.traverseRecursively(resourcesPath) {
|
||||
let targetPath = bundleDirectory.appending(component: file.basename)
|
||||
|
||||
guard localFileSystem.exists(resourcesPath) && !localFileSystem.exists(targetPath)
|
||||
else { continue }
|
||||
|
||||
terminal.logLookup("Copying this resource to the root bundle directory ", file)
|
||||
try localFileSystem.copy(from: file, to: targetPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ struct Dev: ParsableCommand {
|
|||
)
|
||||
}
|
||||
|
||||
let (arguments, mainWasmPath) = try toolchain.buildCurrentProject(
|
||||
let (arguments, mainWasmPath, inferredProduct) = try toolchain.buildCurrentProject(
|
||||
product: product,
|
||||
isRelease: release
|
||||
)
|
||||
|
@ -110,6 +110,7 @@ struct Dev: ParsableCommand {
|
|||
customIndexContent: HTML.readCustomIndexPage(at: customIndexPage, on: localFileSystem),
|
||||
// swiftlint:disable:next force_try
|
||||
package: try! toolchain.package.get(),
|
||||
product: inferredProduct,
|
||||
entrypoint: Self.entrypoint
|
||||
),
|
||||
terminal
|
||||
|
|
|
@ -50,12 +50,7 @@ struct Test: ParsableCommand {
|
|||
@Argument(help: "The list of test cases to run in the test suite.")
|
||||
var testCases = [String]()
|
||||
|
||||
@Option(
|
||||
help: """
|
||||
Environment used to run the tests, either a browser, or command-line Wasm host.
|
||||
Possible values: `defaultBrowser` or `wasmer`.
|
||||
"""
|
||||
)
|
||||
@Option(help: "Environment used to run the tests, either a browser, or command-line Wasm host.")
|
||||
private var environment = Environment.wasmer
|
||||
|
||||
@Option(
|
||||
|
@ -94,6 +89,7 @@ struct Test: ParsableCommand {
|
|||
customIndexContent: nil,
|
||||
// swiftlint:disable:next force_try
|
||||
package: try! toolchain.package.get(),
|
||||
product: nil,
|
||||
entrypoint: Self.entrypoint
|
||||
),
|
||||
terminal
|
||||
|
|
|
@ -23,6 +23,7 @@ extension Application {
|
|||
let mainWasmPath: AbsolutePath
|
||||
let customIndexContent: String?
|
||||
let package: SwiftToolchain.Package
|
||||
let product: Product?
|
||||
let entrypoint: Entrypoint
|
||||
let onWebSocketOpen: (WebSocket, DestinationEnvironment) -> ()
|
||||
let onWebSocketClose: (WebSocket) -> ()
|
||||
|
@ -71,5 +72,19 @@ extension Application {
|
|||
).pathString))
|
||||
}
|
||||
}
|
||||
|
||||
let inferredMainTarget = configuration.package.targets.first {
|
||||
configuration.product?.targets.contains($0.name) == true
|
||||
}
|
||||
|
||||
guard let mainTarget = inferredMainTarget else { return }
|
||||
|
||||
let resourcesPath = configuration.package.resourcesPath(for: mainTarget)
|
||||
get("**") {
|
||||
$0.eventLoop.makeSucceededFuture($0.fileio.streamFile(at: AbsolutePath(
|
||||
buildDirectory.appending(component: resourcesPath),
|
||||
$0.parameters.getCatchall().joined(separator: "/")
|
||||
).pathString))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ final class Server {
|
|||
let port: Int
|
||||
let customIndexContent: String?
|
||||
let package: SwiftToolchain.Package
|
||||
let product: Product?
|
||||
let entrypoint: Entrypoint
|
||||
}
|
||||
|
||||
|
@ -111,6 +112,7 @@ final class Server {
|
|||
mainWasmPath: configuration.mainWasmPath,
|
||||
customIndexContent: configuration.customIndexContent,
|
||||
package: configuration.package,
|
||||
product: configuration.product,
|
||||
entrypoint: configuration.entrypoint,
|
||||
onWebSocketOpen: { [weak self] ws, environment in
|
||||
ws.onText { _, text in
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
"repositoryURL": "https://github.com/swiftwasm/JavaScriptKit",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "8ba4135d5fd6a734c3771ef3fac66896bbcb0214",
|
||||
"version": "0.8.0"
|
||||
"revision": "b7a02434c6e973c08c3fd5069105ef44dd82b891",
|
||||
"version": "0.9.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ let package = Package(
|
|||
.executable(name: "TestApp", targets: ["TestApp"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.8.0"),
|
||||
.package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.9.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test
|
||||
|
|
|
@ -45,6 +45,9 @@ buttonNode.onclick = .function(handler)
|
|||
var div = document.createElement("div")
|
||||
div.innerHTML = .string(#"""
|
||||
<a href=\#(Bundle.module.path(forResource: "data", ofType: "json")!)>Link to a static resource</a>
|
||||
<br/>
|
||||
<a href=\#(Bundle.main
|
||||
.path(forResource: "data", ofType: "json")!)>Link to a <code>Bundle.main</code> resource</a>
|
||||
"""#)
|
||||
_ = document.body.appendChild(div)
|
||||
|
||||
|
|
Loading…
Reference in New Issue