Switch const serialize to use CBOR (#4932)

* implement const cbor encoding + decoding for basic types

* implement map encoding and decoding

* fix map test

* all tests passing

* dynamically sized arrays

* smaller str encoding

* swap array and list and add more documentation

* move serialization and deserialization for each struct together

* support both legacy and new assets

* bump const serialize version

* fix asset conversion code

* pull out manganis symbol struct from tuble

* test reading old asset versions

* more list/array cleanup and bump rust version

* fix clippy

* switch cli opt port

* install playwright deps

* split cli-optimization package to avoid two dioxus' in the same crate

* fix clippy

* force the toast.png asset to be included in the linker stage #[used] has platform depedant behavior

* bundle both formats in the link section and only use the asset the cli changed

* remove sudo which doesn't exist on windows

* add binstall to the playwright workflow

* separate bin target for the old cli

* lockfile

* cargo update to get versions shaken out

* notes on binstalls, add back `.read()` for compat

* use 0.8.0-alpha.0 for const-serialize instead of 0.8

this ensures we can still publish breaking changes on 0.8 without taking over the full version

* fix clippy in example

* small debug nits

* update rust-overlay in nix

* fix flake?

* migrate to new structure

* just comment out binstalls

* just remove old test harness

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2025-12-28 21:49:34 -05:00
1 parent c68a47b2d3
commit c977cc125e
48 files changed
+3386 -2007

No files matched your search

@@ -1,59 +1,66 @@
// @ts-check
const { test, expect } = require("@playwright/test");
test("optimized scripts run", async ({ page }) => {
await page.goto("http://localhost:8989");
const test_variants = [
{ port: 8989, name: "current version" },
];
// // Expect the page to load the script after optimizations have been applied. The script
// // should add an editor to the page that shows a main function
// const main = page.locator("#main");
// await expect(main).toContainText("hi");
for (let { port, name } of test_variants) {
test(`optimized scripts run in ${name}`, async ({ page }) => {
await page.goto(`http://localhost:${port}`);
// Expect the page to contain an image with the id "some_image"
const image = page.locator("#some_image");
await expect(image).toBeVisible();
// Get the image src
const src = await image.getAttribute("src");
// // Expect the page to load the script after optimizations have been applied. The script
// // should add an editor to the page that shows a main function
// const main = page.locator("#main");
// await expect(main).toContainText("hi");
// Expect the page to contain an image with the id "some_image_with_the_same_url"
const image2 = page.locator("#some_image_with_the_same_url");
await expect(image2).toBeVisible();
// Get the image src
const src2 = await image2.getAttribute("src");
// Expect the page to contain an image with the id "some_image"
const image = page.locator("#some_image");
await expect(image).toBeVisible();
// Get the image src
const src = await image.getAttribute("src");
// Expect the urls to be different
expect(src).not.toEqual(src2);
// Expect the page to contain an image with the id "some_image_with_the_same_url"
const image2 = page.locator("#some_image_with_the_same_url");
await expect(image2).toBeVisible();
// Get the image src
const src2 = await image2.getAttribute("src");
// Expect the page to contain an image with the id "some_image_without_hash"
const image3 = page.locator("#some_image_without_hash");
await expect(image3).toBeVisible();
// Get the image src
const src3 = await image3.getAttribute("src");
// Expect the src to be without a hash
expect(src3).toEqual("/assets/toasts.avif");
});
// Expect the urls to be different
expect(src).not.toEqual(src2);
test("unused external assets are bundled", async ({ page }) => {
await page.goto("http://localhost:8989");
// Expect the page to contain an image with the id "some_image_without_hash"
const image3 = page.locator("#some_image_without_hash");
await expect(image3).toBeVisible();
// Get the image src
const src3 = await image3.getAttribute("src");
// Expect the src to be without a hash
expect(src3).toEqual("/assets/toasts.avif");
});
// Assert http://localhost:8989/assets/toasts.png is found even though it is not used in the page
const response = await page.request.get(
"http://localhost:8989/assets/toasts.png"
);
// Expect the response to be ok
expect(response.status()).toBe(200);
// make sure the response is an image
expect(response.headers()["content-type"]).toBe("image/png");
});
test(`unused external assets are bundled in ${name}`, async ({ page }) => {
await page.goto(`http://localhost:${port}`);
test("assets are resolved", async ({ page }) => {
await page.goto("http://localhost:8989");
// Assert http://localhost:{port}/assets/toasts.png is found even though it is not used in the page
const response = await page.request.get(
`http://localhost:${port}/assets/toasts.png`
);
// Expect the response to be ok
expect(response.status()).toBe(200);
// make sure the response is an image
expect(response.headers()["content-type"]).toBe("image/png");
});
// Expect the page to contain an element with the id "resolved-data"
const resolvedData = page.locator("#resolved-data");
await expect(resolvedData).toBeVisible();
// Expect the element to contain the text "List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
await expect(resolvedData).toContainText(
"List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
);
});
test(`assets are resolved in ${name}`, async ({ page }) => {
await page.goto(`http://localhost:${port}`);
// Expect the page to contain an element with the id "resolved-data"
const resolvedData = page.locator("#resolved-data");
await expect(resolvedData).toBeVisible();
// Expect the element to contain the text "List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
await expect(resolvedData).toContainText(
"List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
);
});
}
@@ -1,4 +1,5 @@
dist
target
monaco-editor
partial-monaco-editor
partial-monaco-editor
dx-07
@@ -15,3 +15,8 @@ serde_json.workspace = true
# reqwest = { workspace = true, features = ["blocking"] }
# flate2 = "1.1.2"
# tar = "0.4.44"
# We need a separate bin for the old CLI build to avoid conflicting caches
[[bin]]
name = "cli-optimization-old-cli"
path = "src/old_cli.rs"
@@ -0,0 +1,93 @@
// This test checks the CLI optimizes assets correctly without breaking them
use dioxus::prelude::*;
const SOME_IMAGE: Asset = asset!("/images/toasts.png", AssetOptions::image().with_avif());
const SOME_IMAGE_WITH_THE_SAME_URL: Asset =
asset!("/images/toasts.png", AssetOptions::image().with_jpg());
#[used]
static SOME_IMAGE_WITHOUT_HASH: Asset = asset!(
"/images/toasts.png",
AssetOptions::image().with_avif().with_hash_suffix(false)
);
// This asset is unused, but it should still be bundled because it is an external asset
#[used]
static _ASSET: Asset = asset!(
"/images/toasts.png",
AssetOptions::builder().with_hash_suffix(false)
);
pub fn main() {
dioxus::launch(App);
}
#[component]
fn App() -> Element {
// todo: test monaco more....
// const MONACO_FOLDER: Asset = asset!("/monaco-editor/package/min/vs");
// let script = format!("(() => {{
// require.config({{ paths: {{ vs: '{MONACO_FOLDER}' }} }});
// require(['vs/editor/editor.main'], () => {{
// var model = monaco.editor.createModel('fn main() {{\\n\\tprintln!(\\\"hi\\\")\\n}}', 'rust');
// var editor = monaco.editor.create(document.getElementById('editor'));
// editor.setModel(model);
// }})
// }})()");
rsx! {
div {
id: "editor",
width: "100vw",
height: "100vw",
}
// // Monaco script
// script {
// src: "{MONACO_FOLDER}/loader.js",
// "onload": script
// }
img {
id: "some_image",
src: "{SOME_IMAGE}"
}
img {
id: "some_image_with_the_same_url",
src: "{SOME_IMAGE_WITH_THE_SAME_URL}"
}
img {
id: "some_image_without_hash",
src: "{SOME_IMAGE_WITHOUT_HASH}"
}
LoadsAsset {}
}
}
const JSON: Asset = asset!("/assets/data.json");
#[derive(Debug, Clone, serde::Deserialize)]
struct Data {
list: Vec<i32>,
}
#[component]
fn LoadsAsset() -> Element {
let data = use_resource(|| async {
let bytes = dioxus::asset_resolver::read_asset_bytes(&JSON)
.await
.unwrap();
serde_json::from_slice::<Data>(&bytes).unwrap()
});
match data() {
Some(data) => rsx! {
div {
id: "resolved-data",
"List: {data.list:?}"
}
},
None => rsx! {
div {
"Loading..."
}
},
}
}
@@ -1,93 +1,3 @@
// This test checks the CLI optimizes assets correctly without breaking them
use dioxus::prelude::*;
const SOME_IMAGE: Asset = asset!("/images/toasts.png", AssetOptions::image().with_avif());
const SOME_IMAGE_WITH_THE_SAME_URL: Asset =
asset!("/images/toasts.png", AssetOptions::image().with_jpg());
#[used]
static SOME_IMAGE_WITHOUT_HASH: Asset = asset!(
"/images/toasts.png",
AssetOptions::image().with_avif().with_hash_suffix(false)
);
// This asset is unused, but it should still be bundled because it is an external asset
#[used]
static _ASSET: Asset = asset!(
"/images/toasts.png",
AssetOptions::builder().with_hash_suffix(false)
);
fn main() {
dioxus::launch(App);
}
#[component]
fn App() -> Element {
// todo: test monaco more....
// const MONACO_FOLDER: Asset = asset!("/monaco-editor/package/min/vs");
// let script = format!("(() => {{
// require.config({{ paths: {{ vs: '{MONACO_FOLDER}' }} }});
// require(['vs/editor/editor.main'], () => {{
// var model = monaco.editor.createModel('fn main() {{\\n\\tprintln!(\\\"hi\\\")\\n}}', 'rust');
// var editor = monaco.editor.create(document.getElementById('editor'));
// editor.setModel(model);
// }})
// }})()");
rsx! {
div {
id: "editor",
width: "100vw",
height: "100vw",
}
// // Monaco script
// script {
// src: "{MONACO_FOLDER}/loader.js",
// "onload": script
// }
img {
id: "some_image",
src: "{SOME_IMAGE}"
}
img {
id: "some_image_with_the_same_url",
src: "{SOME_IMAGE_WITH_THE_SAME_URL}"
}
img {
id: "some_image_without_hash",
src: "{SOME_IMAGE_WITHOUT_HASH}"
}
LoadsAsset {}
}
}
const JSON: Asset = asset!("/assets/data.json");
#[derive(Debug, Clone, serde::Deserialize)]
struct Data {
list: Vec<i32>,
}
#[component]
fn LoadsAsset() -> Element {
let data = use_resource(|| async {
let bytes = dioxus::asset_resolver::read_asset_bytes(&JSON)
.await
.unwrap();
serde_json::from_slice::<Data>(&bytes).unwrap()
});
match data() {
Some(data) => rsx! {
div {
id: "resolved-data",
"List: {data.list:?}"
}
},
None => rsx! {
div {
"Loading..."
}
},
}
dioxus_cli_optimization_test::main()
}
@@ -0,0 +1,3 @@
fn main() {
dioxus_cli_optimization_test::main()
}
+12 -12
View File
@@ -9,17 +9,17 @@
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.53.1"
"@playwright/test": "^1.56.1"
}
},
"node_modules/@playwright/test": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz",
"integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.53.1"
"playwright": "1.56.1"
},
"bin": {
"playwright": "cli.js"
@@ -44,13 +44,13 @@
}
},
"node_modules/playwright": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz",
"integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.53.1"
"playwright-core": "1.56.1"
},
"bin": {
"playwright": "cli.js"
@@ -63,9 +63,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz",
"integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==",
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
+1 -1
View File
@@ -12,6 +12,6 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.53.1"
"@playwright/test": "^1.56.1"
}
}
@@ -144,7 +144,6 @@ if (process.platform === "win32") {
},
{
cwd: path.join(process.cwd(), "cli-optimization"),
// Remove the cache folder for the cli-optimization build to force a full cache reset
command:
'cargo run --package dioxus-cli --release -- run --addr "127.0.0.1" --port 8989',
port: 8989,