feat: publish WASM streaming smoke site
Add a browser WASM smoke site, four platform CI workflows, README status badges, and real file streaming coverage over the Geekbench corpus.
This commit is contained in:
parent
f56c3e5578
commit
be03732baa
16
.github/workflows/linux.yml
vendored
Normal file
16
.github/workflows/linux.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: Linux
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
container: swift:6.2
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build and test
|
||||
run: bash scripts/check.sh
|
||||
@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: macOS
|
||||
|
||||
on:
|
||||
push:
|
||||
110
.github/workflows/wasm-site.yml
vendored
Normal file
110
.github/workflows/wasm-site.yml
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
name: WASM Test Site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "Package.swift"
|
||||
- "Sources/PureYAMLStreaming/**"
|
||||
- "Sources/PureYAMLStreamingWasmSmoke/**"
|
||||
- "WasmSite/**"
|
||||
- "scripts/build-wasm-site.sh"
|
||||
- ".github/workflows/wasm-site.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pureyaml-streaming-wasm-site
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install build tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binaryen gzip libcurl4-openssl-dev
|
||||
|
||||
- name: Install swiftly
|
||||
run: |
|
||||
curl -L https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz > swiftly.tar.gz
|
||||
tar zxf swiftly.tar.gz
|
||||
./swiftly init --quiet-shell-followup --assume-yes --skip-install
|
||||
|
||||
- name: Install Swift 6.3.2 and Wasm SDK
|
||||
run: |
|
||||
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
|
||||
swiftly install --use 6.3.2
|
||||
swiftly run swift sdk install https://download.swift.org/swift-6.3.2-release/wasm-sdk/swift-6.3.2-RELEASE/swift-6.3.2-RELEASE_wasm.artifactbundle.tar.gz --checksum a61f0584c93283589f8b2f42db05c1f9a182b506c2957271402992655591dd7c +6.3.2
|
||||
|
||||
- name: Build WASM site
|
||||
run: |
|
||||
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
|
||||
bash scripts/build-wasm-site.sh
|
||||
mkdir -p site
|
||||
cp WasmSite/index.html site/index.html
|
||||
cp WasmSite/main.js site/main.js
|
||||
cp WasmSite/style.css site/style.css
|
||||
cp WasmSite/pureyaml-streaming-wasm-smoke.wasm.gz site/pureyaml-streaming-wasm-smoke.wasm.gz
|
||||
ls -lh site
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
run: |
|
||||
mkdir -p .build/playwright-ci
|
||||
cd .build/playwright-ci
|
||||
npm init -y
|
||||
npm install playwright-chromium
|
||||
npx playwright install --with-deps chromium
|
||||
|
||||
- name: Test site locally
|
||||
run: |
|
||||
python3 -m http.server 8766 --bind 127.0.0.1 --directory site &
|
||||
server_pid=$!
|
||||
trap 'kill "$server_pid"' EXIT
|
||||
node <<'NODE'
|
||||
const { chromium } = require('./.build/playwright-ci/node_modules/playwright-chromium');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage();
|
||||
await page.goto('http://127.0.0.1:8766/?v=' + Date.now());
|
||||
await page.click('#runButton');
|
||||
await page.waitForFunction(
|
||||
() => document.querySelector('#status')?.textContent === 'Passed',
|
||||
null,
|
||||
{ timeout: 60000 }
|
||||
);
|
||||
const output = await page.locator('#output').textContent();
|
||||
if (!output.includes('"ok" : true') && !output.includes('"ok":true')) {
|
||||
throw new Error(output);
|
||||
}
|
||||
await browser.close();
|
||||
})();
|
||||
NODE
|
||||
|
||||
- name: Configure Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload Pages artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: site
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
37
.github/workflows/wasm.yml
vendored
Normal file
37
.github/workflows/wasm.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: WASM
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install build tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binaryen gzip libcurl4-openssl-dev
|
||||
- name: Install swiftly
|
||||
run: |
|
||||
curl -L https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz > swiftly.tar.gz
|
||||
tar zxf swiftly.tar.gz
|
||||
./swiftly init --quiet-shell-followup --assume-yes --skip-install
|
||||
- name: Install Swift 6.3.2 and Wasm SDK
|
||||
run: |
|
||||
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
|
||||
swiftly install --use 6.3.2
|
||||
swiftly run swift sdk install https://download.swift.org/swift-6.3.2-release/wasm-sdk/swift-6.3.2-RELEASE/swift-6.3.2-RELEASE_wasm.artifactbundle.tar.gz --checksum a61f0584c93283589f8b2f42db05c1f9a182b506c2957271402992655591dd7c +6.3.2
|
||||
- name: Build WASM smoke site
|
||||
run: |
|
||||
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh"
|
||||
bash scripts/build-wasm-site.sh
|
||||
- name: Upload WASM site artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: pureyaml-streaming-wasm-site
|
||||
path: WasmSite
|
||||
retention-days: 7
|
||||
24
.github/workflows/windows.yml
vendored
Normal file
24
.github/workflows/windows.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Swift
|
||||
uses: compnerd/gha-setup-swift@main
|
||||
with:
|
||||
swift-version: swift-6.3.2-release
|
||||
swift-build: 6.3.2-RELEASE
|
||||
- name: Build
|
||||
shell: pwsh
|
||||
run: swift build
|
||||
- name: Test
|
||||
shell: pwsh
|
||||
run: swift test
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
.build/
|
||||
.swiftpm/
|
||||
.DS_Store
|
||||
WasmSite/*.wasm
|
||||
WasmSite/*.wasm.gz
|
||||
|
||||
@ -12,6 +12,10 @@ let package = Package(
|
||||
name: "PureYAMLStreaming",
|
||||
targets: ["PureYAMLStreaming"],
|
||||
),
|
||||
.executable(
|
||||
name: "pureyaml-streaming-wasm-smoke",
|
||||
targets: ["PureYAMLStreamingWasmSmoke"],
|
||||
),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/mihaelamj/PureYAML.git", branch: "main"),
|
||||
@ -21,6 +25,10 @@ let package = Package(
|
||||
name: "PureYAMLStreaming",
|
||||
dependencies: ["PureYAML"],
|
||||
),
|
||||
.executableTarget(
|
||||
name: "PureYAMLStreamingWasmSmoke",
|
||||
dependencies: ["PureYAMLStreaming", "PureYAML"],
|
||||
),
|
||||
.testTarget(
|
||||
name: "PureYAMLStreamingTests",
|
||||
dependencies: ["PureYAMLStreaming"],
|
||||
|
||||
28
README.md
28
README.md
@ -1,5 +1,11 @@
|
||||
# PureYAMLStreaming
|
||||
|
||||
[](https://github.com/mihaelamj/PureYAMLStreaming/actions/workflows/macos.yml)
|
||||
[](https://github.com/mihaelamj/PureYAMLStreaming/actions/workflows/linux.yml)
|
||||
[](https://github.com/mihaelamj/PureYAMLStreaming/actions/workflows/windows.yml)
|
||||
[](https://github.com/mihaelamj/PureYAMLStreaming/actions/workflows/wasm.yml)
|
||||
[](https://github.com/mihaelamj/PureYAMLStreaming/actions/workflows/wasm-site.yml)
|
||||
|
||||
Experimental full-streaming incubator for PureYAML.
|
||||
|
||||
This package is intentionally separate from `PureYAML` while the chunked input,
|
||||
@ -43,6 +49,28 @@ try parser.parseDocuments(from: url) { document in
|
||||
}
|
||||
```
|
||||
|
||||
## WASM Browser Smoke Site
|
||||
|
||||
Live site:
|
||||
|
||||
`https://mihaelamj.github.io/PureYAMLStreaming/`
|
||||
|
||||
Build the Swift WASI smoke executable and copy it into the static test site:
|
||||
|
||||
```sh
|
||||
bash scripts/build-wasm-site.sh
|
||||
```
|
||||
|
||||
Serve the site locally:
|
||||
|
||||
```sh
|
||||
bash scripts/serve-wasm-site.sh
|
||||
```
|
||||
|
||||
Then open `http://localhost:8080` and click **Run WASM Test**. The page loads
|
||||
`pureyaml-streaming-wasm-smoke.wasm`, runs a chunked document-stream parse in
|
||||
the browser through a WASI shim, captures stdout, and renders pass/fail.
|
||||
|
||||
## Relationship To PureYAML
|
||||
|
||||
`PureYAML` remains the dependency-free core package. This package is the
|
||||
|
||||
76
Sources/PureYAMLStreamingWasmSmoke/main.swift
Normal file
76
Sources/PureYAMLStreamingWasmSmoke/main.swift
Normal file
@ -0,0 +1,76 @@
|
||||
import Foundation
|
||||
import PureYAML
|
||||
import PureYAMLStreaming
|
||||
|
||||
struct SmokeResult: Encodable {
|
||||
var ok: Bool
|
||||
var documentCount: Int
|
||||
var firstName: String?
|
||||
var lastName: String?
|
||||
var message: String
|
||||
}
|
||||
|
||||
let yaml = Data(
|
||||
"""
|
||||
---
|
||||
name: browser-0
|
||||
enabled: true
|
||||
nested:
|
||||
index: 0
|
||||
---
|
||||
name: browser-1
|
||||
enabled: false
|
||||
nested:
|
||||
index: 1
|
||||
---
|
||||
name: browser-2
|
||||
enabled: true
|
||||
nested:
|
||||
index: 2
|
||||
""".utf8,
|
||||
)
|
||||
|
||||
do {
|
||||
let parser = PureYAMLStreaming.Parser(chunkSize: 7)
|
||||
let documents = try parser.collectDocuments(data: yaml)
|
||||
let names = documents.compactMap { document in
|
||||
stringValue("name", in: document.value)
|
||||
}
|
||||
let ok = documents.count == 3
|
||||
&& names.first == "browser-0"
|
||||
&& names.last == "browser-2"
|
||||
let result = SmokeResult(
|
||||
ok: ok,
|
||||
documentCount: documents.count,
|
||||
firstName: names.first,
|
||||
lastName: names.last,
|
||||
message: ok ? "PureYAMLStreaming WASM smoke passed" : "PureYAMLStreaming WASM smoke failed",
|
||||
)
|
||||
print(try renderJSON(result))
|
||||
Foundation.exit(ok ? 0 : 1)
|
||||
} catch {
|
||||
let result = SmokeResult(
|
||||
ok: false,
|
||||
documentCount: 0,
|
||||
firstName: nil,
|
||||
lastName: nil,
|
||||
message: String(describing: error),
|
||||
)
|
||||
print((try? renderJSON(result)) ?? #"{"ok":false,"message":"failed to render error"}"#)
|
||||
Foundation.exit(1)
|
||||
}
|
||||
|
||||
func stringValue(_ key: String, in value: PureYAML.Model.Value) -> String? {
|
||||
guard case let .mapping(mapping) = value,
|
||||
case let .string(string)? = mapping[key]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
func renderJSON<Value: Encodable>(_ value: Value) throws -> String {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
||||
return String(decoding: try encoder.encode(value), as: UTF8.self)
|
||||
}
|
||||
@ -57,14 +57,7 @@ struct StreamingTests {
|
||||
|
||||
@Test("streaming matches PureYAML on Geekbench fixtures")
|
||||
func streamingMatchesGeekbenchFixtures() throws {
|
||||
let fixtureRoot = URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appendingPathComponent("PureYAMLGeekbench")
|
||||
.appendingPathComponent("Fixtures")
|
||||
.appendingPathComponent("real-yaml")
|
||||
let fixtureRoot = geekbenchFixtureRoot()
|
||||
guard FileManager.default.fileExists(atPath: fixtureRoot.path) else {
|
||||
return
|
||||
}
|
||||
@ -78,16 +71,48 @@ struct StreamingTests {
|
||||
|
||||
#expect(files.count >= 100)
|
||||
|
||||
let streamingParser = PureYAMLStreaming.Parser(chunkSize: 4096)
|
||||
let streamingParser = PureYAMLStreaming.Parser(chunkSize: 31)
|
||||
let pureParser = PureYAML.Parsing.Parser()
|
||||
for file in files {
|
||||
let data = try Data(contentsOf: file)
|
||||
let streamed = try streamingParser.collectDocuments(data: data).map(\.value)
|
||||
var streamed: [PureYAML.Model.Value] = []
|
||||
try streamingParser.parseDocuments(from: file) { document in
|
||||
streamed.append(document.value)
|
||||
}
|
||||
let direct = try pureParser.parseStream(String(decoding: data, as: UTF8.self)).map(\.value)
|
||||
#expect(streamed == direct)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("parser streams generated large file without collecting documents")
|
||||
func parserStreamsGeneratedLargeFileWithoutCollectingDocuments() throws {
|
||||
let fileURL = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("pureyaml-stream-\(UUID().uuidString).yaml")
|
||||
try writeGeneratedStream(to: fileURL, documentCount: 5_000)
|
||||
defer {
|
||||
try? FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
|
||||
let parser = PureYAMLStreaming.Parser(chunkSize: 37)
|
||||
var count = 0
|
||||
var firstName: String?
|
||||
var lastName: String?
|
||||
|
||||
try parser.parseDocuments(from: fileURL) { document in
|
||||
let name = stringValue("name", in: document.value)
|
||||
if count == 0 {
|
||||
firstName = name
|
||||
}
|
||||
lastName = name
|
||||
#expect(document.index == count)
|
||||
count += 1
|
||||
}
|
||||
|
||||
#expect(count == 5_000)
|
||||
#expect(firstName == "doc-0")
|
||||
#expect(lastName == "doc-4999")
|
||||
}
|
||||
|
||||
private func stringValue(_ key: String, in value: PureYAML.Model.Value) -> String? {
|
||||
guard case let .mapping(mapping) = value,
|
||||
case let .string(string)? = mapping[key]
|
||||
@ -96,4 +121,39 @@ struct StreamingTests {
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
private func geekbenchFixtureRoot() -> URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appendingPathComponent("PureYAMLGeekbench")
|
||||
.appendingPathComponent("Fixtures")
|
||||
.appendingPathComponent("real-yaml")
|
||||
}
|
||||
|
||||
private func writeGeneratedStream(to fileURL: URL, documentCount: Int) throws {
|
||||
FileManager.default.createFile(atPath: fileURL.path, contents: nil)
|
||||
let handle = try FileHandle(forWritingTo: fileURL)
|
||||
defer {
|
||||
try? handle.close()
|
||||
}
|
||||
|
||||
for index in 0 ..< documentCount {
|
||||
let document = """
|
||||
---
|
||||
name: doc-\(index)
|
||||
enabled: \(index.isMultiple(of: 2) ? "true" : "false")
|
||||
nested:
|
||||
index: \(index)
|
||||
tags:
|
||||
- real
|
||||
- streaming
|
||||
- chunk-\(index % 17)
|
||||
|
||||
"""
|
||||
try handle.write(contentsOf: Data(document.utf8))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
WasmSite/index.html
Normal file
31
WasmSite/index.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>PureYAMLStreaming WASM Smoke</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<section class="card">
|
||||
<p class="eyebrow">PureYAMLStreaming</p>
|
||||
<h1>WASM Streaming Smoke Test</h1>
|
||||
<p>
|
||||
This page loads the Swift WASI build in the browser, runs a
|
||||
document-streaming parse, captures stdout, and reports whether the
|
||||
smoke test passed.
|
||||
</p>
|
||||
<button id="runButton" type="button">Run WASM Test</button>
|
||||
<div id="status" class="status idle">Idle</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Output</h2>
|
||||
<pre id="output">Click "Run WASM Test".</pre>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
115
WasmSite/main.js
Normal file
115
WasmSite/main.js
Normal file
@ -0,0 +1,115 @@
|
||||
import {
|
||||
File,
|
||||
OpenFile,
|
||||
WASI,
|
||||
} from "https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.4.1/+esm";
|
||||
|
||||
const runButton = document.querySelector("#runButton");
|
||||
const statusElement = document.querySelector("#status");
|
||||
const outputElement = document.querySelector("#output");
|
||||
const params = new URLSearchParams(location.search);
|
||||
const wasmURL = params.get("wasm") || "./pureyaml-streaming-wasm-smoke.wasm.gz";
|
||||
|
||||
runButton.addEventListener("click", () => {
|
||||
runSmokeTest().catch((error) => {
|
||||
renderFailure(error);
|
||||
});
|
||||
});
|
||||
|
||||
async function runSmokeTest() {
|
||||
runButton.disabled = true;
|
||||
statusElement.className = "status idle";
|
||||
statusElement.textContent = "Running...";
|
||||
outputElement.textContent = "Loading WASM...";
|
||||
|
||||
const wasmBytes = await loadWasmBytes(wasmURL);
|
||||
|
||||
const stdout = new CapturingFile();
|
||||
const stderr = new CapturingFile();
|
||||
const fds = [
|
||||
new OpenFile(new File([])),
|
||||
new OpenFile(stdout),
|
||||
new OpenFile(stderr),
|
||||
];
|
||||
const wasi = new WASI([], [], fds);
|
||||
const wasm = await WebAssembly.compile(wasmBytes);
|
||||
const instance = await WebAssembly.instantiate(wasm, {
|
||||
wasi_snapshot_preview1: wasi.wasiImport,
|
||||
});
|
||||
|
||||
let exitCode = 0;
|
||||
try {
|
||||
wasi.start(instance);
|
||||
} catch (error) {
|
||||
if (typeof error?.code === "number") {
|
||||
exitCode = error.code;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const stdoutText = stdout.text();
|
||||
const stderrText = stderr.text();
|
||||
const parsed = parseSmokeOutput(stdoutText);
|
||||
const ok = exitCode === 0 && parsed?.ok === true;
|
||||
|
||||
statusElement.className = ok ? "status pass" : "status fail";
|
||||
statusElement.textContent = ok ? "Passed" : "Failed";
|
||||
outputElement.textContent = [
|
||||
`exitCode: ${exitCode}`,
|
||||
"",
|
||||
"stdout:",
|
||||
stdoutText || "(empty)",
|
||||
"",
|
||||
"stderr:",
|
||||
stderrText || "(empty)",
|
||||
].join("\n");
|
||||
runButton.disabled = false;
|
||||
}
|
||||
|
||||
async function loadWasmBytes(url) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch WASM: HTTP ${response.status}`);
|
||||
}
|
||||
const bytes = await response.arrayBuffer();
|
||||
if (!url.endsWith(".gz")) {
|
||||
return bytes;
|
||||
}
|
||||
if (!("DecompressionStream" in globalThis)) {
|
||||
throw new Error("This browser does not support DecompressionStream.");
|
||||
}
|
||||
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
|
||||
return new Response(stream).arrayBuffer();
|
||||
}
|
||||
|
||||
function parseSmokeOutput(text) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function renderFailure(error) {
|
||||
statusElement.className = "status fail";
|
||||
statusElement.textContent = "Failed";
|
||||
outputElement.textContent = String(error?.stack || error);
|
||||
runButton.disabled = false;
|
||||
}
|
||||
|
||||
class CapturingFile extends File {
|
||||
constructor() {
|
||||
super([]);
|
||||
this.bytes = [];
|
||||
}
|
||||
|
||||
write(buffer) {
|
||||
this.bytes.push(...buffer);
|
||||
return { ret: buffer.length };
|
||||
}
|
||||
|
||||
text() {
|
||||
return new TextDecoder().decode(new Uint8Array(this.bytes));
|
||||
}
|
||||
}
|
||||
94
WasmSite/style.css
Normal file
94
WasmSite/style.css
Normal file
@ -0,0 +1,94 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
background: Canvas;
|
||||
color: CanvasText;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
width: min(920px, calc(100vw - 32px));
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1px solid color-mix(in srgb, CanvasText 18%, transparent);
|
||||
border-radius: 20px;
|
||||
padding: 28px;
|
||||
background: color-mix(in srgb, Canvas 92%, CanvasText 8%);
|
||||
box-shadow: 0 24px 80px color-mix(in srgb, CanvasText 12%, transparent);
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin: 0 0 12px;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(36px, 7vw, 72px);
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 720px;
|
||||
line-height: 1.6;
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
button {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 12px 18px;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
background: CanvasText;
|
||||
color: Canvas;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-top: 18px;
|
||||
display: inline-flex;
|
||||
border-radius: 999px;
|
||||
padding: 8px 12px;
|
||||
font-weight: 700;
|
||||
background: color-mix(in srgb, CanvasText 10%, transparent);
|
||||
}
|
||||
|
||||
.status.pass {
|
||||
background: color-mix(in srgb, #008a45 30%, transparent);
|
||||
}
|
||||
|
||||
.status.fail {
|
||||
background: color-mix(in srgb, #c21f39 30%, transparent);
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.5;
|
||||
}
|
||||
51
scripts/build-wasm-site.sh
Executable file
51
scripts/build-wasm-site.sh
Executable file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SDK_ID="${PUREYAML_STREAMING_WASM_SDK:-swift-6.3.2-RELEASE_wasm}"
|
||||
SWIFT_SELECTOR="${PUREYAML_STREAMING_WASM_SWIFT_SELECTOR:-+6.3.2}"
|
||||
PRODUCT="pureyaml-streaming-wasm-smoke"
|
||||
SITE_DIR="$ROOT_DIR/WasmSite"
|
||||
|
||||
swift_command() {
|
||||
if command -v swiftly >/dev/null 2>&1; then
|
||||
swiftly run swift "$@" "$SWIFT_SELECTOR"
|
||||
else
|
||||
swift "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
if ! swift_command sdk list | grep -qx "$SDK_ID"; then
|
||||
echo "wasm: missing Swift SDK '$SDK_ID'" >&2
|
||||
echo "wasm: install the SDK or set PUREYAML_STREAMING_WASM_SDK" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
swift_command build \
|
||||
--package-path "$ROOT_DIR" \
|
||||
--swift-sdk "$SDK_ID" \
|
||||
-c release \
|
||||
--product "$PRODUCT"
|
||||
|
||||
BIN_PATH="$(swift_command build \
|
||||
--package-path "$ROOT_DIR" \
|
||||
--swift-sdk "$SDK_ID" \
|
||||
-c release \
|
||||
--show-bin-path)"
|
||||
WASM_PATH="$BIN_PATH/$PRODUCT.wasm"
|
||||
STAGED_WASM="$SITE_DIR/$PRODUCT.wasm"
|
||||
|
||||
if command -v wasm-opt >/dev/null 2>&1; then
|
||||
wasm-opt -Oz --strip-debug --strip-producers "$WASM_PATH" -o "$STAGED_WASM"
|
||||
else
|
||||
cp "$WASM_PATH" "$STAGED_WASM"
|
||||
fi
|
||||
|
||||
gzip -9 -c "$STAGED_WASM" > "$STAGED_WASM.gz"
|
||||
|
||||
echo "WASM test site ready:"
|
||||
echo " $SITE_DIR/index.html"
|
||||
echo " $STAGED_WASM.gz"
|
||||
echo
|
||||
echo "Serve it with:"
|
||||
echo " bash scripts/serve-wasm-site.sh"
|
||||
8
scripts/serve-wasm-site.sh
Executable file
8
scripts/serve-wasm-site.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PORT="${1:-8080}"
|
||||
|
||||
cd "$ROOT_DIR/WasmSite"
|
||||
python3 -m http.server "$PORT"
|
||||
Loading…
Reference in New Issue
Block a user