Optimize wasm bindgen asset with manganis (#3531)
This commit is contained in:
1 parent
d8850c7666
commit
13dcd80fbf
29 files changed
+933
-1089
No files matched your search
+12
-12
@@ -48,7 +48,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@1.79.0
|
- uses: dtolnay/rust-toolchain@1.84.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
cache-all-crates: "true"
|
cache-all-crates: "true"
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
swap-storage: false
|
swap-storage: false
|
||||||
- run: sudo apt-get update
|
- run: sudo apt-get update
|
||||||
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
|
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
|
||||||
- uses: dtolnay/rust-toolchain@1.79.0
|
- uses: dtolnay/rust-toolchain@1.84.0
|
||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
swap-storage: false
|
swap-storage: false
|
||||||
- run: sudo apt-get update
|
- run: sudo apt-get update
|
||||||
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
|
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
|
||||||
- uses: dtolnay/rust-toolchain@1.79.0
|
- uses: dtolnay/rust-toolchain@1.84.0
|
||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@1.79.0
|
- uses: dtolnay/rust-toolchain@1.84.0
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: sudo apt-get update
|
- run: sudo apt-get update
|
||||||
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
|
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
|
||||||
- uses: dtolnay/rust-toolchain@1.79.0
|
- uses: dtolnay/rust-toolchain@1.84.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
cache-all-crates: "true"
|
cache-all-crates: "true"
|
||||||
@@ -170,7 +170,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: sudo apt-get update
|
- run: sudo apt-get update
|
||||||
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
|
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
|
||||||
- uses: dtolnay/rust-toolchain@1.79.0
|
- uses: dtolnay/rust-toolchain@1.84.0
|
||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
@@ -247,7 +247,7 @@ jobs:
|
|||||||
- {
|
- {
|
||||||
target: aarch64-apple-darwin,
|
target: aarch64-apple-darwin,
|
||||||
os: macos-latest,
|
os: macos-latest,
|
||||||
toolchain: "1.79.0",
|
toolchain: "1.84.0",
|
||||||
cross: false,
|
cross: false,
|
||||||
command: "test",
|
command: "test",
|
||||||
args: "--all --tests",
|
args: "--all --tests",
|
||||||
@@ -255,7 +255,7 @@ jobs:
|
|||||||
- {
|
- {
|
||||||
target: x86_64-apple-darwin,
|
target: x86_64-apple-darwin,
|
||||||
os: macos-13,
|
os: macos-13,
|
||||||
toolchain: "1.79.0",
|
toolchain: "1.84.0",
|
||||||
cross: false,
|
cross: false,
|
||||||
command: "test",
|
command: "test",
|
||||||
args: "--all --tests",
|
args: "--all --tests",
|
||||||
@@ -263,7 +263,7 @@ jobs:
|
|||||||
- {
|
- {
|
||||||
target: aarch64-apple-ios,
|
target: aarch64-apple-ios,
|
||||||
os: macos-latest,
|
os: macos-latest,
|
||||||
toolchain: "1.79.0",
|
toolchain: "1.84.0",
|
||||||
cross: false,
|
cross: false,
|
||||||
command: "build",
|
command: "build",
|
||||||
args: "--package dioxus-mobile",
|
args: "--package dioxus-mobile",
|
||||||
@@ -271,7 +271,7 @@ jobs:
|
|||||||
- {
|
- {
|
||||||
target: aarch64-linux-android,
|
target: aarch64-linux-android,
|
||||||
os: ubuntu-latest,
|
os: ubuntu-latest,
|
||||||
toolchain: "1.79.0",
|
toolchain: "1.84.0",
|
||||||
cross: true,
|
cross: true,
|
||||||
command: "build",
|
command: "build",
|
||||||
args: "--package dioxus-mobile",
|
args: "--package dioxus-mobile",
|
||||||
@@ -327,7 +327,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@1.79.0
|
- uses: dtolnay/rust-toolchain@1.84.0
|
||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
@@ -360,7 +360,7 @@ jobs:
|
|||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - uses: actions/checkout@v4
|
# - uses: actions/checkout@v4
|
||||||
# - uses: dtolnay/rust-toolchain@1.79.0
|
# - uses: dtolnay/rust-toolchain@1.84.0
|
||||||
# - uses: Swatinem/rust-cache@v2
|
# - uses: Swatinem/rust-cache@v2
|
||||||
# with:
|
# with:
|
||||||
# cache-all-crates: "true"
|
# cache-all-crates: "true"
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
- name: Install stable
|
- name: Install stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: "1.79.0"
|
toolchain: "1.84.0"
|
||||||
targets: ${{ matrix.platform.target }}
|
targets: ${{ matrix.platform.target }}
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|||||||
Generated
+177
-806
File diff suppressed because it is too large.
Load diff
@@ -100,6 +100,7 @@ members = [
|
|||||||
"packages/playwright-tests/fullstack-mounted",
|
"packages/playwright-tests/fullstack-mounted",
|
||||||
"packages/playwright-tests/suspense-carousel",
|
"packages/playwright-tests/suspense-carousel",
|
||||||
"packages/playwright-tests/nested-suspense",
|
"packages/playwright-tests/nested-suspense",
|
||||||
|
"packages/playwright-tests/cli-optimization",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|||||||
Generated
+16
-16
@@ -5,11 +5,11 @@
|
|||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1722555600,
|
"lastModified": 1736143030,
|
||||||
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
|
"narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
|
"rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724395761,
|
"lastModified": 1736241350,
|
||||||
"narHash": "sha256-zRkDV/nbrnp3Y8oCADf5ETl1sDrdmAW6/bBVJ8EbIdQ=",
|
"narHash": "sha256-CHd7yhaDigUuJyDeX0SADbTM9FXfiWaeNyY34FL1wQU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ae815cee91b417be55d43781eb4b73ae1ecc396c",
|
"rev": "8c9fd3e564728e90829ee7dbac6edc972971cd0f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -36,23 +36,23 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1722555339,
|
"lastModified": 1735774519,
|
||||||
"narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=",
|
"narHash": "sha256-CewEm1o2eVAnoqb6Ml+Qi9Gg/EfNAxbRx1lANGVyoLI=",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
|
"url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
|
"url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1718428119,
|
"lastModified": 1728538411,
|
||||||
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -75,11 +75,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724638882,
|
"lastModified": 1736438724,
|
||||||
"narHash": "sha256-ap2jIQi/FuUHR6HCht6ASWhoz8EiB99XmI8Esot38VE=",
|
"narHash": "sha256-m0CFbWVKtXsAr5OBgWNwR8Uam/jkPIjWgJkdH9DY46M=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "19b70f147b9c67a759e35824b241f1ed92e46694",
|
"rev": "15897cf5835bb95aa002b42c22dc61079c28f7c8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
+27
-54
@@ -39,57 +39,30 @@ grass = "0.13.4"
|
|||||||
codemap = "0.1.3"
|
codemap = "0.1.3"
|
||||||
|
|
||||||
# Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates
|
# Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates
|
||||||
swc = "=0.285.0"
|
swc_allocator = { version = "=2.0.0", default-features = false }
|
||||||
swc_allocator = { version = "=0.1.8", default-features = false }
|
swc_atoms = { version = "=3.0.2", default-features = false }
|
||||||
swc_atoms = { version = "=0.6.7", default-features = false }
|
swc_bundler = { version = "=7.0.0", default-features = false }
|
||||||
swc_cached = { version = "=0.3.20", default-features = false }
|
swc_cached = { version = "=1.0.0", default-features = false }
|
||||||
swc_common = { version = "=0.37.5", default-features = false }
|
swc_common = { version = "=5.0.0", features = ["tty-emitter"], default-features = false }
|
||||||
swc_compiler_base = { version = "=0.19.0", default-features = false }
|
swc_config = { version = "=1.0.0", default-features = false }
|
||||||
swc_config = { version = "=0.1.15", default-features = false }
|
swc_config_macro = { version = "=1.0.0", default-features = false }
|
||||||
swc_config_macro = { version = "=0.1.4", default-features = false }
|
swc_ecma_ast = { version = "=5.0.1", default-features = false }
|
||||||
swc_ecma_ast = { version = "=0.118.2", default-features = false }
|
swc_ecma_codegen = { version = "=5.0.1", default-features = false }
|
||||||
swc_ecma_codegen = { version = "=0.155.1", default-features = false }
|
swc_ecma_codegen_macros = { version = "=1.0.0", default-features = false }
|
||||||
swc_ecma_codegen_macros = { version = "=0.7.7", default-features = false }
|
swc_ecma_loader = { version = "=5.0.0", features = ["cache", "node"], default-features = false }
|
||||||
swc_ecma_compat_bugfixes = { version = "=0.12.0", default-features = false }
|
swc_ecma_minifier = { version = "=7.0.1", default-features = false }
|
||||||
swc_ecma_compat_common = { version = "=0.11.0", default-features = false }
|
swc_ecma_parser = { version = "=6.0.2", default-features = false }
|
||||||
swc_ecma_compat_es2015 = { version = "=0.12.0", default-features = false }
|
swc_ecma_transforms_base = { version = "=7.0.0", default-features = false }
|
||||||
swc_ecma_compat_es2016 = { version = "=0.12.0", default-features = false }
|
swc_ecma_transforms_macros = { version = "=1.0.0", default-features = false }
|
||||||
swc_ecma_compat_es2017 = { version = "=0.12.0", default-features = false }
|
swc_ecma_transforms_optimization = { version = "=7.0.1", default-features = false }
|
||||||
swc_ecma_compat_es2018 = { version = "=0.12.0", default-features = false }
|
swc_ecma_usage_analyzer = { version = "=7.0.0", default-features = false }
|
||||||
swc_ecma_compat_es2019 = { version = "=0.12.0", default-features = false }
|
swc_ecma_utils = { version = "=7.0.0", default-features = false }
|
||||||
swc_ecma_compat_es2020 = { version = "=0.12.0", default-features = false }
|
swc_ecma_visit = { version = "=5.0.0", default-features = false }
|
||||||
swc_ecma_compat_es2021 = { version = "=0.12.0", default-features = false }
|
swc_eq_ignore_macros = { version = "=1.0.0", default-features = false }
|
||||||
swc_ecma_compat_es2022 = { version = "=0.12.0", default-features = false }
|
swc_fast_graph = { version = "=6.0.0", default-features = false }
|
||||||
swc_ecma_compat_es3 = { version = "=0.12.0", default-features = false }
|
swc_graph_analyzer = { version = "=5.0.0", default-features = false }
|
||||||
swc_ecma_ext_transforms = { version = "=0.120.0", default-features = false }
|
swc_macros_common = { version = "=1.0.0", default-features = false }
|
||||||
swc_ecma_lints = { version = "=0.100.0", default-features = false }
|
swc_parallel = { version = "=1.0.1", default-features = false }
|
||||||
swc_ecma_loader = { version = "=0.49.1", default-features = false }
|
swc_timer = { version = "=1.0.0", default-features = false }
|
||||||
swc_ecma_minifier = { version = "=0.204.0", default-features = false }
|
swc_visit = { version = "=2.0.0", default-features = false }
|
||||||
swc_ecma_parser = { version = "=0.149.1", default-features = false }
|
browserslist-rs = { version = "=0.16.0" }
|
||||||
swc_ecma_preset_env = { version = "=0.217.0", default-features = false, features = [
|
|
||||||
"serde",
|
|
||||||
] }
|
|
||||||
swc_ecma_transforms = { version = "=0.239.0", default-features = false }
|
|
||||||
swc_ecma_transforms_base = { version = "=0.145.0", default-features = false }
|
|
||||||
swc_ecma_transforms_classes = { version = "=0.134.0", default-features = false }
|
|
||||||
swc_ecma_transforms_compat = { version = "=0.171.0", default-features = false }
|
|
||||||
swc_ecma_transforms_macros = { version = "=0.5.5", default-features = false }
|
|
||||||
swc_ecma_transforms_module = { version = "=0.190.0", default-features = false }
|
|
||||||
swc_ecma_transforms_optimization = { version = "=0.208.0", default-features = false }
|
|
||||||
swc_ecma_transforms_proposal = { version = "=0.178.0", default-features = false }
|
|
||||||
swc_ecma_transforms_react = { version = "=0.191.0", default-features = false }
|
|
||||||
swc_ecma_transforms_typescript = { version = "=0.198.1", default-features = false }
|
|
||||||
swc_ecma_usage_analyzer = { version = "=0.30.3", default-features = false }
|
|
||||||
swc_ecma_utils = { version = "=0.134.2", default-features = false }
|
|
||||||
swc_ecma_visit = { version = "=0.104.8", default-features = false }
|
|
||||||
swc_eq_ignore_macros = { version = "=0.1.4", default-features = false }
|
|
||||||
swc_error_reporters = { version = "=0.21.0", default-features = false }
|
|
||||||
swc_fast_graph = { version = "=0.25.0", default-features = false }
|
|
||||||
swc_macros_common = { version = "=0.3.13", default-features = false }
|
|
||||||
swc_node_comments = { version = "=0.24.0", default-features = false }
|
|
||||||
swc_timer = { version = "=0.25.0", default-features = false }
|
|
||||||
swc_trace_macro = { version = "=0.1.3", default-features = false }
|
|
||||||
swc_transform_common = { version = "=0.1.1", default-features = false }
|
|
||||||
swc_typescript = { version = "=0.5.0", default-features = false }
|
|
||||||
swc_visit = { version = "=0.6.2", default-features = false }
|
|
||||||
browserslist-rs = { version = "=0.16.0" }
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use manganis_core::{AssetOptions, CssAssetOptions, ImageAssetOptions, JsAssetOptions};
|
use manganis_core::{AssetOptions, CssAssetOptions, ImageAssetOptions, JsAssetOptions};
|
||||||
use std::path::Path;
|
use std::{ffi::OsStr, path::Path};
|
||||||
|
|
||||||
use crate::css::process_scss;
|
use crate::css::process_scss;
|
||||||
|
|
||||||
@@ -14,6 +14,16 @@ pub fn process_file_to(
|
|||||||
options: &AssetOptions,
|
options: &AssetOptions,
|
||||||
source: &Path,
|
source: &Path,
|
||||||
output_path: &Path,
|
output_path: &Path,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
process_file_to_with_options(options, source, output_path, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a specific file asset with additional options
|
||||||
|
pub(crate) fn process_file_to_with_options(
|
||||||
|
options: &AssetOptions,
|
||||||
|
source: &Path,
|
||||||
|
output_path: &Path,
|
||||||
|
in_folder: bool,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// If the file already exists, then we must have a file with the same hash
|
// If the file already exists, then we must have a file with the same hash
|
||||||
// already. The hash has the file contents and options, so if we find a file
|
// already. The hash has the file contents and options, so if we find a file
|
||||||
@@ -26,56 +36,68 @@ pub fn process_file_to(
|
|||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Processing can be slow. Write to a temporary file first and then rename it to the final output path. If everything
|
||||||
|
// goes well. Without this, the user could quit in the middle of processing and the file will look complete to the
|
||||||
|
// caching system even though it is empty.
|
||||||
|
let mut partial_ext = output_path.extension().unwrap_or_default().to_os_string();
|
||||||
|
partial_ext.push(OsStr::new(".partial"));
|
||||||
|
let temp_output_path = output_path.with_extension(&partial_ext);
|
||||||
|
let temp_output_path = &temp_output_path;
|
||||||
|
|
||||||
match options {
|
match options {
|
||||||
AssetOptions::Unknown => match source.extension().map(|e| e.to_string_lossy()).as_deref() {
|
AssetOptions::Unknown => match source.extension().map(|e| e.to_string_lossy()).as_deref() {
|
||||||
Some("css") => {
|
Some("css") => {
|
||||||
process_css(&CssAssetOptions::new(), source, output_path)?;
|
process_css(&CssAssetOptions::new(), source, temp_output_path)?;
|
||||||
}
|
}
|
||||||
Some("scss" | "sass") => {
|
Some("scss" | "sass") => {
|
||||||
process_scss(&CssAssetOptions::new(), source, output_path)?;
|
process_scss(&CssAssetOptions::new(), source, temp_output_path)?;
|
||||||
}
|
}
|
||||||
Some("js") => {
|
Some("js") => {
|
||||||
process_js(&JsAssetOptions::new(), source, output_path)?;
|
process_js(&JsAssetOptions::new(), source, temp_output_path, !in_folder)?;
|
||||||
}
|
}
|
||||||
Some("json") => {
|
Some("json") => {
|
||||||
process_json(source, output_path)?;
|
process_json(source, temp_output_path)?;
|
||||||
}
|
}
|
||||||
Some("jpg" | "jpeg" | "png" | "webp" | "avif") => {
|
Some("jpg" | "jpeg" | "png" | "webp" | "avif") => {
|
||||||
process_image(&ImageAssetOptions::new(), source, output_path)?;
|
process_image(&ImageAssetOptions::new(), source, temp_output_path)?;
|
||||||
}
|
}
|
||||||
Some(_) | None => {
|
Some(_) | None => {
|
||||||
if source.is_dir() {
|
if source.is_dir() {
|
||||||
process_folder(source, output_path)?;
|
process_folder(source, temp_output_path)?;
|
||||||
} else {
|
} else {
|
||||||
let source_file = std::fs::File::open(source)?;
|
let source_file = std::fs::File::open(source)?;
|
||||||
let mut reader = std::io::BufReader::new(source_file);
|
let mut reader = std::io::BufReader::new(source_file);
|
||||||
let output_file = std::fs::File::create(output_path)?;
|
let output_file = std::fs::File::create(temp_output_path)?;
|
||||||
let mut writer = std::io::BufWriter::new(output_file);
|
let mut writer = std::io::BufWriter::new(output_file);
|
||||||
std::io::copy(&mut reader, &mut writer).with_context(|| {
|
std::io::copy(&mut reader, &mut writer).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to write file to output location: {}",
|
"Failed to write file to output location: {}",
|
||||||
output_path.display()
|
temp_output_path.display()
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AssetOptions::Css(options) => {
|
AssetOptions::Css(options) => {
|
||||||
process_css(options, source, output_path)?;
|
process_css(options, source, temp_output_path)?;
|
||||||
}
|
}
|
||||||
AssetOptions::Js(options) => {
|
AssetOptions::Js(options) => {
|
||||||
process_js(options, source, output_path)?;
|
process_js(options, source, temp_output_path, !in_folder)?;
|
||||||
}
|
}
|
||||||
AssetOptions::Image(options) => {
|
AssetOptions::Image(options) => {
|
||||||
process_image(options, source, output_path)?;
|
process_image(options, source, temp_output_path)?;
|
||||||
}
|
}
|
||||||
AssetOptions::Folder(_) => {
|
AssetOptions::Folder(_) => {
|
||||||
process_folder(source, output_path)?;
|
process_folder(source, temp_output_path)?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!("Unknown asset options: {:?}", options);
|
tracing::warn!("Unknown asset options: {:?}", options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If everything was successful, rename the temp file to the final output path
|
||||||
|
std::fs::rename(temp_output_path, output_path)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
|
|
||||||
use super::file::process_file_to;
|
use crate::file::process_file_to_with_options;
|
||||||
|
|
||||||
/// Process a folder, optimizing and copying all assets into the output folder
|
/// Process a folder, optimizing and copying all assets into the output folder
|
||||||
pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> {
|
pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> {
|
||||||
@@ -32,10 +32,11 @@ pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()>
|
|||||||
|
|
||||||
/// Optimize a file without changing any of its contents significantly (e.g. by changing the extension)
|
/// Optimize a file without changing any of its contents significantly (e.g. by changing the extension)
|
||||||
fn process_file_minimal(input_path: &Path, output_path: &Path) -> anyhow::Result<()> {
|
fn process_file_minimal(input_path: &Path, output_path: &Path) -> anyhow::Result<()> {
|
||||||
process_file_to(
|
process_file_to_with_options(
|
||||||
&manganis_core::AssetOptions::Unknown,
|
&manganis_core::AssetOptions::Unknown,
|
||||||
input_path,
|
input_path,
|
||||||
output_path,
|
output_path,
|
||||||
|
true,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
+221
-52
@@ -1,74 +1,243 @@
|
|||||||
use std::io::Read;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use manganis_core::JsAssetOptions;
|
use manganis_core::JsAssetOptions;
|
||||||
use swc::{config::JsMinifyOptions, try_with_handler, BoolOrDataConfig, JsMinifyExtras};
|
use swc_common::errors::Emitter;
|
||||||
use swc_common::{sync::Lrc, FileName};
|
use swc_common::errors::Handler;
|
||||||
use swc_common::{SourceMap, GLOBALS};
|
use swc_common::input::SourceFileInput;
|
||||||
use swc_ecma_minifier::option::MangleOptions;
|
use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions};
|
||||||
|
use swc_ecma_parser::lexer::Lexer;
|
||||||
|
use swc_ecma_parser::Parser;
|
||||||
|
use swc_ecma_transforms_base::fixer::fixer;
|
||||||
|
use swc_ecma_visit::VisitMutWith;
|
||||||
|
|
||||||
pub(crate) fn minify_js(source: &Path) -> anyhow::Result<String> {
|
use std::collections::HashMap;
|
||||||
let mut source_file = std::fs::File::open(source)?;
|
|
||||||
let cm = Arc::new(SourceMap::default());
|
|
||||||
|
|
||||||
let mut js = String::new();
|
use anyhow::Error;
|
||||||
source_file.read_to_string(&mut js)?;
|
use swc_bundler::{Bundler, Config, Load, ModuleData, ModuleRecord};
|
||||||
let c = swc::Compiler::new(cm.clone());
|
use swc_common::{
|
||||||
let output = GLOBALS
|
errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceMap, Span, GLOBALS,
|
||||||
.set(&Default::default(), || {
|
};
|
||||||
try_with_handler(cm.clone(), Default::default(), |handler| {
|
use swc_ecma_ast::*;
|
||||||
let filename = Lrc::new(FileName::Real(source.to_path_buf()));
|
use swc_ecma_codegen::text_writer::JsWriter;
|
||||||
let fm = cm.new_source_file(filename, js.to_string());
|
use swc_ecma_loader::{resolvers::node::NodeModulesResolver, TargetEnv};
|
||||||
|
use swc_ecma_parser::{parse_file_as_module, Syntax};
|
||||||
|
|
||||||
c.minify(
|
struct TracingEmitter;
|
||||||
fm,
|
|
||||||
handler,
|
|
||||||
&JsMinifyOptions {
|
|
||||||
compress: BoolOrDataConfig::from_bool(true),
|
|
||||||
mangle: BoolOrDataConfig::from_obj(MangleOptions {
|
|
||||||
keep_fn_names: true,
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
JsMinifyExtras::default(),
|
|
||||||
)
|
|
||||||
.context("failed to minify javascript")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.map(|output| output.code);
|
|
||||||
|
|
||||||
match output {
|
impl Emitter for TracingEmitter {
|
||||||
Ok(output) => Ok(output),
|
fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) {
|
||||||
Err(err) => {
|
match db.level {
|
||||||
tracing::error!("Failed to minify javascript: {}", err);
|
swc_common::errors::Level::Bug
|
||||||
Ok(js)
|
| swc_common::errors::Level::Fatal
|
||||||
|
| swc_common::errors::Level::PhaseFatal
|
||||||
|
| swc_common::errors::Level::Error => tracing::error!("{}", db.message()),
|
||||||
|
swc_common::errors::Level::Warning
|
||||||
|
| swc_common::errors::Level::FailureNote
|
||||||
|
| swc_common::errors::Level::Cancelled => tracing::warn!("{}", db.message()),
|
||||||
|
swc_common::errors::Level::Note | swc_common::errors::Level::Help => {
|
||||||
|
tracing::trace!("{}", db.message())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bundle_js_to_writer(
|
||||||
|
file: PathBuf,
|
||||||
|
bundle: bool,
|
||||||
|
minify: bool,
|
||||||
|
write_to: &mut impl std::io::Write,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let globals = Globals::new();
|
||||||
|
let handler = Handler::with_emitter_and_flags(Box::new(TracingEmitter), Default::default());
|
||||||
|
GLOBALS.set(&globals, || {
|
||||||
|
HANDLER.set(&handler, || {
|
||||||
|
bundle_js_to_writer_inside_handler(&globals, file, bundle, minify, write_to)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bundle_js_to_writer_inside_handler(
|
||||||
|
globals: &Globals,
|
||||||
|
file: PathBuf,
|
||||||
|
bundle: bool,
|
||||||
|
minify: bool,
|
||||||
|
write_to: &mut impl std::io::Write,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||||
|
let mut module = if bundle {
|
||||||
|
let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true);
|
||||||
|
let mut bundler = Bundler::new(
|
||||||
|
globals,
|
||||||
|
cm.clone(),
|
||||||
|
PathLoader { cm: cm.clone() },
|
||||||
|
node_resolver,
|
||||||
|
Config {
|
||||||
|
require: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Box::new(Hook),
|
||||||
|
);
|
||||||
|
let mut entries = HashMap::default();
|
||||||
|
entries.insert("main".to_string(), FileName::Real(file));
|
||||||
|
|
||||||
|
let mut bundles = bundler
|
||||||
|
.bundle(entries)
|
||||||
|
.context("failed to bundle javascript with swc")?;
|
||||||
|
// Since we only inserted one entry, there should only be one bundle in the output
|
||||||
|
let bundle = bundles
|
||||||
|
.pop()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("swc did not output any bundles"))?;
|
||||||
|
bundle.module
|
||||||
|
} else {
|
||||||
|
let fm = cm.load_file(Path::new(&file)).expect("Failed to load file");
|
||||||
|
|
||||||
|
let lexer = Lexer::new(
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
SourceFileInput::from(&*fm),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let mut parser = Parser::new_from(lexer);
|
||||||
|
|
||||||
|
parser.parse_module().map_err(|err| {
|
||||||
|
HANDLER.with(|handler| {
|
||||||
|
let message = err.into_diagnostic(handler).message();
|
||||||
|
anyhow::anyhow!("{}", message)
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
|
if minify {
|
||||||
|
module = swc_ecma_minifier::optimize(
|
||||||
|
std::mem::take(&mut module).into(),
|
||||||
|
cm.clone(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&MinifyOptions {
|
||||||
|
rename: true,
|
||||||
|
compress: None,
|
||||||
|
mangle: None,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&ExtraOptions {
|
||||||
|
unresolved_mark: Mark::new(),
|
||||||
|
top_level_mark: Mark::new(),
|
||||||
|
mangle_name_cache: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect_module();
|
||||||
|
module.visit_mut_with(&mut fixer(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut emitter = swc_ecma_codegen::Emitter {
|
||||||
|
cfg: swc_ecma_codegen::Config::default().with_minify(minify),
|
||||||
|
cm: cm.clone(),
|
||||||
|
comments: None,
|
||||||
|
wr: Box::new(JsWriter::new(cm, "\n", write_to, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
emitter.emit_module(&module)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PathLoader {
|
||||||
|
cm: Lrc<SourceMap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Load for PathLoader {
|
||||||
|
fn load(&self, file: &FileName) -> anyhow::Result<ModuleData> {
|
||||||
|
let file = match file {
|
||||||
|
FileName::Real(v) => v,
|
||||||
|
_ => anyhow::bail!("Only real files are supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let fm = self.cm.load_file(file)?;
|
||||||
|
|
||||||
|
let module = HANDLER.with(|handler| {
|
||||||
|
parse_file_as_module(
|
||||||
|
&fm,
|
||||||
|
Syntax::Es(Default::default()),
|
||||||
|
Default::default(),
|
||||||
|
None,
|
||||||
|
&mut Vec::new(),
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
|
let message = err.into_diagnostic(handler).message();
|
||||||
|
anyhow::anyhow!("{}", message)
|
||||||
|
})
|
||||||
|
.context("Failed to parse javascript")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(ModuleData {
|
||||||
|
fm,
|
||||||
|
module,
|
||||||
|
helpers: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from https://github.com/swc-project/swc/blob/624680b7896cef9d8e30bd5ff910538298016974/bindings/binding_core_node/src/bundle.rs#L266-L302
|
||||||
|
struct Hook;
|
||||||
|
|
||||||
|
impl swc_bundler::Hook for Hook {
|
||||||
|
fn get_import_meta_props(
|
||||||
|
&self,
|
||||||
|
span: Span,
|
||||||
|
module_record: &ModuleRecord,
|
||||||
|
) -> Result<Vec<KeyValueProp>, Error> {
|
||||||
|
let file_name = module_record.file_name.to_string();
|
||||||
|
|
||||||
|
Ok(vec![
|
||||||
|
KeyValueProp {
|
||||||
|
key: PropName::Ident(IdentName::new("url".into(), span)),
|
||||||
|
value: Box::new(Expr::Lit(Lit::Str(Str {
|
||||||
|
span,
|
||||||
|
raw: None,
|
||||||
|
value: file_name.into(),
|
||||||
|
}))),
|
||||||
|
},
|
||||||
|
KeyValueProp {
|
||||||
|
key: PropName::Ident(IdentName::new("main".into(), span)),
|
||||||
|
value: Box::new(if module_record.is_entry {
|
||||||
|
Expr::Member(MemberExpr {
|
||||||
|
span,
|
||||||
|
obj: Box::new(Expr::MetaProp(MetaPropExpr {
|
||||||
|
span,
|
||||||
|
kind: MetaPropKind::ImportMeta,
|
||||||
|
})),
|
||||||
|
prop: MemberProp::Ident(IdentName::new("main".into(), span)),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Expr::Lit(Lit::Bool(Bool { span, value: false }))
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn process_js(
|
pub(crate) fn process_js(
|
||||||
js_options: &JsAssetOptions,
|
js_options: &JsAssetOptions,
|
||||||
source: &Path,
|
source: &Path,
|
||||||
output_path: &Path,
|
output_path: &Path,
|
||||||
|
bundle: bool,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let js = if js_options.minified() {
|
let mut writer = std::io::BufWriter::new(std::fs::File::create(output_path)?);
|
||||||
minify_js(source)?
|
if js_options.minified() {
|
||||||
|
if let Err(err) = bundle_js_to_writer(source.to_path_buf(), bundle, true, &mut writer) {
|
||||||
|
tracing::error!("Failed to minify js. Falling back to non-minified: {err}");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut source_file = std::fs::File::open(source)?;
|
let mut source_file = std::fs::File::open(source)?;
|
||||||
let mut source = String::new();
|
std::io::copy(&mut source_file, &mut writer).with_context(|| {
|
||||||
source_file.read_to_string(&mut source)?;
|
format!(
|
||||||
source
|
"Failed to write js to output location: {}",
|
||||||
};
|
output_path.display()
|
||||||
|
)
|
||||||
std::fs::write(output_path, js).with_context(|| {
|
})?;
|
||||||
format!(
|
}
|
||||||
"Failed to write js to output location: {}",
|
|
||||||
output_path.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ description = "CLI for building fullstack web, desktop, and mobile apps with a s
|
|||||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["mobile", "gui", "cli", "dioxus", "wasm"]
|
keywords = ["mobile", "gui", "cli", "dioxus", "wasm"]
|
||||||
rust-version = "1.79.0"
|
rust-version = "1.81.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-autofmt = { workspace = true }
|
dioxus-autofmt = { workspace = true }
|
||||||
@@ -106,6 +106,7 @@ log = { version = "0.4", features = ["max_level_off", "release_max_level_off"] }
|
|||||||
|
|
||||||
# link intercept
|
# link intercept
|
||||||
tempfile = "3.3"
|
tempfile = "3.3"
|
||||||
|
manganis = { workspace = true }
|
||||||
manganis-core = { workspace = true }
|
manganis-core = { workspace = true }
|
||||||
|
|
||||||
# Extracting data from an executable
|
# Extracting data from an executable
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::{BuildRequest, Platform};
|
|||||||
use crate::{Result, TraceSrc};
|
use crate::{Result, TraceSrc};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use dioxus_cli_opt::{process_file_to, AssetManifest};
|
use dioxus_cli_opt::{process_file_to, AssetManifest};
|
||||||
use manganis_core::AssetOptions;
|
use manganis::{AssetOptions, JsAssetOptions};
|
||||||
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
@@ -265,7 +265,7 @@ impl AppBundle {
|
|||||||
app: BuildArtifacts,
|
app: BuildArtifacts,
|
||||||
server: Option<BuildArtifacts>,
|
server: Option<BuildArtifacts>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let bundle = Self { app, server, build };
|
let mut bundle = Self { app, server, build };
|
||||||
|
|
||||||
tracing::debug!("Assembling app bundle");
|
tracing::debug!("Assembling app bundle");
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ impl AppBundle {
|
|||||||
/// For wasm, we'll want to run `wasm-bindgen` to make it a wasm binary along with some other optimizations
|
/// For wasm, we'll want to run `wasm-bindgen` to make it a wasm binary along with some other optimizations
|
||||||
/// Other platforms we might do some stripping or other optimizations
|
/// Other platforms we might do some stripping or other optimizations
|
||||||
/// Move the executable to the workdir
|
/// Move the executable to the workdir
|
||||||
async fn write_main_executable(&self) -> Result<()> {
|
async fn write_main_executable(&mut self) -> Result<()> {
|
||||||
match self.build.build.platform() {
|
match self.build.build.platform() {
|
||||||
// Run wasm-bindgen on the wasm binary and set its output to be in the bundle folder
|
// Run wasm-bindgen on the wasm binary and set its output to be in the bundle folder
|
||||||
// Also run wasm-opt on the wasm binary, and sets the index.html since that's also the "executable".
|
// Also run wasm-opt on the wasm binary, and sets the index.html since that's also the "executable".
|
||||||
@@ -328,7 +328,7 @@ impl AppBundle {
|
|||||||
Platform::Web => {
|
Platform::Web => {
|
||||||
// Run wasm-bindgen and drop its output into the assets folder under "dioxus"
|
// Run wasm-bindgen and drop its output into the assets folder under "dioxus"
|
||||||
self.build.status_wasm_bindgen_start();
|
self.build.status_wasm_bindgen_start();
|
||||||
self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"), &self.build.exe_dir())
|
self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Only run wasm-opt if the feature is enabled
|
// Only run wasm-opt if the feature is enabled
|
||||||
@@ -339,7 +339,7 @@ impl AppBundle {
|
|||||||
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
self.build.root_dir().join("index.html"),
|
self.build.root_dir().join("index.html"),
|
||||||
self.build.prepare_html()?,
|
self.prepare_html()?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,29 +383,41 @@ impl AppBundle {
|
|||||||
// First, clear the asset dir of any files that don't exist in the new manifest
|
// First, clear the asset dir of any files that don't exist in the new manifest
|
||||||
_ = tokio::fs::create_dir_all(&asset_dir).await;
|
_ = tokio::fs::create_dir_all(&asset_dir).await;
|
||||||
// Create a set of all the paths that new files will be bundled to
|
// Create a set of all the paths that new files will be bundled to
|
||||||
let bundled_output_paths: HashSet<_> = self
|
let mut keep_bundled_output_paths: HashSet<_> = self
|
||||||
.app
|
.app
|
||||||
.assets
|
.assets
|
||||||
.assets
|
.assets
|
||||||
.values()
|
.values()
|
||||||
.map(|a| asset_dir.join(a.bundled_path()))
|
.map(|a| asset_dir.join(a.bundled_path()))
|
||||||
.collect();
|
.collect();
|
||||||
|
// The CLI creates a .version file in the asset dir to keep track of what version of the optimizer
|
||||||
|
// the asset was processed. If that version doesn't match the CLI version, we need to re-optimize
|
||||||
|
// all assets.
|
||||||
|
let version_file = self.build.asset_optimizer_version_file();
|
||||||
|
let clear_cache = std::fs::read_to_string(&version_file)
|
||||||
|
.ok()
|
||||||
|
.filter(|s| s == crate::VERSION.as_str())
|
||||||
|
.is_none();
|
||||||
|
if clear_cache {
|
||||||
|
keep_bundled_output_paths.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// one possible implementation of walking a directory only visiting files
|
// one possible implementation of walking a directory only visiting files
|
||||||
fn remove_old_assets<'a>(
|
fn remove_old_assets<'a>(
|
||||||
path: &'a Path,
|
path: &'a Path,
|
||||||
bundled_output_paths: &'a HashSet<PathBuf>,
|
keep_bundled_output_paths: &'a HashSet<PathBuf>,
|
||||||
) -> Pin<Box<dyn Future<Output = std::io::Result<()>> + Send + 'a>> {
|
) -> Pin<Box<dyn Future<Output = std::io::Result<()>> + Send + 'a>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// If this asset is in the manifest, we don't need to remove it
|
// If this asset is in the manifest, we don't need to remove it
|
||||||
let canon_path = dunce::canonicalize(path)?;
|
let canon_path = dunce::canonicalize(path)?;
|
||||||
if bundled_output_paths.contains(canon_path.as_path()) {
|
if keep_bundled_output_paths.contains(canon_path.as_path()) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Otherwise, if it is a directory, we need to walk it and remove child files
|
// Otherwise, if it is a directory, we need to walk it and remove child files
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
for entry in std::fs::read_dir(path)?.flatten() {
|
for entry in std::fs::read_dir(path)?.flatten() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
remove_old_assets(&path, bundled_output_paths).await?;
|
remove_old_assets(&path, keep_bundled_output_paths).await?;
|
||||||
}
|
}
|
||||||
if path.read_dir()?.next().is_none() {
|
if path.read_dir()?.next().is_none() {
|
||||||
// If the directory is empty, remove it
|
// If the directory is empty, remove it
|
||||||
@@ -420,7 +432,7 @@ impl AppBundle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("Removing old assets");
|
tracing::debug!("Removing old assets");
|
||||||
remove_old_assets(&asset_dir, &bundled_output_paths).await?;
|
remove_old_assets(&asset_dir, &keep_bundled_output_paths).await?;
|
||||||
|
|
||||||
// todo(jon): we also want to eventually include options for each asset's optimization and compression, which we currently aren't
|
// todo(jon): we also want to eventually include options for each asset's optimization and compression, which we currently aren't
|
||||||
let mut assets_to_transfer = vec![];
|
let mut assets_to_transfer = vec![];
|
||||||
@@ -474,6 +486,15 @@ impl AppBundle {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("A task failed while trying to copy assets: {e}"))??;
|
.map_err(|e| anyhow::anyhow!("A task failed while trying to copy assets: {e}"))??;
|
||||||
|
|
||||||
|
// Remove the wasm bindgen output directory if it exists
|
||||||
|
_ = std::fs::remove_dir_all(self.build.wasm_bindgen_out_dir());
|
||||||
|
|
||||||
|
// Write the version file so we know what version of the optimizer we used
|
||||||
|
std::fs::write(
|
||||||
|
self.build.asset_optimizer_version_file(),
|
||||||
|
crate::VERSION.as_str(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,9 +575,9 @@ impl AppBundle {
|
|||||||
.krate
|
.krate
|
||||||
.should_pre_compress_web_assets(self.build.build.release);
|
.should_pre_compress_web_assets(self.build.build.release);
|
||||||
|
|
||||||
let bindgen_dir = self.build.exe_dir();
|
let asset_dir = self.build.asset_dir();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
crate::fastfs::pre_compress_folder(&bindgen_dir, pre_compress)
|
crate::fastfs::pre_compress_folder(&asset_dir, pre_compress)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()?;
|
.unwrap()?;
|
||||||
@@ -592,15 +613,14 @@ impl AppBundle {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_wasm_bindgen(
|
pub(crate) async fn run_wasm_bindgen(&mut self, input_path: &Path) -> anyhow::Result<()> {
|
||||||
&self,
|
|
||||||
input_path: &Path,
|
|
||||||
bindgen_outdir: &Path,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
tracing::debug!(dx_src = ?TraceSrc::Bundle, "Running wasm-bindgen");
|
tracing::debug!(dx_src = ?TraceSrc::Bundle, "Running wasm-bindgen");
|
||||||
|
|
||||||
let input_path = input_path.to_path_buf();
|
let input_path = input_path.to_path_buf();
|
||||||
let bindgen_outdir = bindgen_outdir.to_path_buf();
|
// Make sure the bindgen output directory exists
|
||||||
|
let bindgen_outdir = self.build.wasm_bindgen_out_dir();
|
||||||
|
std::fs::create_dir_all(&bindgen_outdir)?;
|
||||||
|
|
||||||
let name = self.build.krate.executable_name().to_string();
|
let name = self.build.krate.executable_name().to_string();
|
||||||
let keep_debug =
|
let keep_debug =
|
||||||
// if we're in debug mode, or we're generating debug symbols, keep debug info
|
// if we're in debug mode, or we're generating debug symbols, keep debug info
|
||||||
@@ -630,6 +650,29 @@ impl AppBundle {
|
|||||||
.await
|
.await
|
||||||
.context("Failed to generate wasm-bindgen bindings")?;
|
.context("Failed to generate wasm-bindgen bindings")?;
|
||||||
|
|
||||||
|
// After running wasm-bindgen, add the js and wasm asset to the manifest
|
||||||
|
let js_output_path = self.build.wasm_bindgen_js_output_file();
|
||||||
|
let wasm_output_path = self.build.wasm_bindgen_wasm_output_file();
|
||||||
|
let new_assets = [
|
||||||
|
(
|
||||||
|
js_output_path,
|
||||||
|
AssetOptions::Js(JsAssetOptions::new().with_minify(true).with_preload(true)),
|
||||||
|
),
|
||||||
|
(wasm_output_path, AssetOptions::Unknown),
|
||||||
|
];
|
||||||
|
for (asset_path, options) in new_assets {
|
||||||
|
let hash = manganis_core::hash::AssetHash::hash_file_contents(&asset_path)?;
|
||||||
|
let output_path_str = asset_path.to_str().ok_or(anyhow::anyhow!(
|
||||||
|
"Failed to convert wasm bindgen output path to string"
|
||||||
|
))?;
|
||||||
|
let bundled_asset = manganis::macro_helpers::create_bundled_asset(
|
||||||
|
output_path_str,
|
||||||
|
hash.bytes(),
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
self.app.assets.assets.insert(asset_path, bundled_asset);
|
||||||
|
}
|
||||||
|
|
||||||
tracing::debug!(dx_src = ?TraceSrc::Bundle, "wasm-bindgen complete in {:?}", start.elapsed());
|
tracing::debug!(dx_src = ?TraceSrc::Bundle, "wasm-bindgen complete in {:?}", start.elapsed());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -656,6 +656,25 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the path to the wasm bindgen temporary output folder
|
||||||
|
pub fn wasm_bindgen_out_dir(&self) -> PathBuf {
|
||||||
|
self.root_dir().join("wasm-bindgen")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the path to the wasm bindgen javascript output file
|
||||||
|
pub fn wasm_bindgen_js_output_file(&self) -> PathBuf {
|
||||||
|
self.wasm_bindgen_out_dir()
|
||||||
|
.join(self.krate.executable_name())
|
||||||
|
.with_extension("js")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the path to the wasm bindgen wasm output file
|
||||||
|
pub fn wasm_bindgen_wasm_output_file(&self) -> PathBuf {
|
||||||
|
self.wasm_bindgen_out_dir()
|
||||||
|
.join(format!("{}_bg", self.krate.executable_name()))
|
||||||
|
.with_extension("wasm")
|
||||||
|
}
|
||||||
|
|
||||||
/// returns the path to root build folder. This will be our working directory for the build.
|
/// returns the path to root build folder. This will be our working directory for the build.
|
||||||
///
|
///
|
||||||
/// we only add an extension to the folders where it sorta matters that it's named with the extension.
|
/// we only add an extension to the folders where it sorta matters that it's named with the extension.
|
||||||
@@ -718,6 +737,11 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the path to the asset optimizer version file
|
||||||
|
pub fn asset_optimizer_version_file(&self) -> PathBuf {
|
||||||
|
self.platform_dir().join(".cli-version")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn platform_exe_name(&self) -> String {
|
pub fn platform_exe_name(&self) -> String {
|
||||||
match self.build.platform() {
|
match self.build.platform() {
|
||||||
Platform::MacOS => self.krate.executable_name().to_string(),
|
Platform::MacOS => self.krate.executable_name().to_string(),
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
use dioxus_cli_config::format_base_path_meta_element;
|
use dioxus_cli_config::format_base_path_meta_element;
|
||||||
|
use manganis::AssetOptions;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::BuildRequest;
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use super::AppBundle;
|
||||||
|
|
||||||
const DEFAULT_HTML: &str = include_str!("../../assets/web/index.html");
|
const DEFAULT_HTML: &str = include_str!("../../assets/web/index.html");
|
||||||
const TOAST_HTML: &str = include_str!("../../assets/web/toast.html");
|
const TOAST_HTML: &str = include_str!("../../assets/web/toast.html");
|
||||||
|
|
||||||
impl BuildRequest {
|
impl AppBundle {
|
||||||
pub(crate) fn prepare_html(&self) -> Result<String> {
|
pub(crate) fn prepare_html(&self) -> Result<String> {
|
||||||
let mut html = {
|
let mut html = {
|
||||||
let crate_root: &Path = &self.krate.crate_dir();
|
let crate_root: &Path = &self.build.krate.crate_dir();
|
||||||
let custom_html_file = crate_root.join("index.html");
|
let custom_html_file = crate_root.join("index.html");
|
||||||
std::fs::read_to_string(custom_html_file).unwrap_or_else(|_| String::from(DEFAULT_HTML))
|
std::fs::read_to_string(custom_html_file).unwrap_or_else(|_| String::from(DEFAULT_HTML))
|
||||||
};
|
};
|
||||||
@@ -25,7 +27,7 @@ impl BuildRequest {
|
|||||||
// Replace any special placeholders in the HTML with resolved values
|
// Replace any special placeholders in the HTML with resolved values
|
||||||
self.replace_template_placeholders(&mut html);
|
self.replace_template_placeholders(&mut html);
|
||||||
|
|
||||||
let title = self.krate.config.web.app.title.clone();
|
let title = self.build.krate.config.web.app.title.clone();
|
||||||
|
|
||||||
replace_or_insert_before("{app_title}", "</title", &title, &mut html);
|
replace_or_insert_before("{app_title}", "</title", &title, &mut html);
|
||||||
|
|
||||||
@@ -33,13 +35,13 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_dev_build(&self) -> bool {
|
fn is_dev_build(&self) -> bool {
|
||||||
!self.build.release
|
!self.build.build.release
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject any resources from the config into the html
|
// Inject any resources from the config into the html
|
||||||
fn inject_resources(&self, html: &mut String) -> Result<()> {
|
fn inject_resources(&self, html: &mut String) -> Result<()> {
|
||||||
// Collect all resources into a list of styles and scripts
|
// Collect all resources into a list of styles and scripts
|
||||||
let resources = &self.krate.config.web.resource;
|
let resources = &self.build.krate.config.web.resource;
|
||||||
let mut style_list = resources.style.clone().unwrap_or_default();
|
let mut style_list = resources.style.clone().unwrap_or_default();
|
||||||
let mut script_list = resources.script.clone().unwrap_or_default();
|
let mut script_list = resources.script.clone().unwrap_or_default();
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ impl BuildRequest {
|
|||||||
|
|
||||||
// Add the base path to the head if this is a debug build
|
// Add the base path to the head if this is a debug build
|
||||||
if self.is_dev_build() {
|
if self.is_dev_build() {
|
||||||
if let Some(base_path) = &self.krate.config.web.app.base_path {
|
if let Some(base_path) = &self.build.krate.config.web.app.base_path {
|
||||||
head_resources.push_str(&format_base_path_meta_element(base_path));
|
head_resources.push_str(&format_base_path_meta_element(base_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,9 +85,45 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inject any resources from manganis into the head
|
// Inject any resources from manganis into the head
|
||||||
// if let Some(assets) = assets {
|
for asset in self.app.assets.assets.values() {
|
||||||
// head_resources.push_str(&assets.head());
|
let asset_path = asset.bundled_path();
|
||||||
// }
|
match asset.options() {
|
||||||
|
AssetOptions::Css(css_options) => {
|
||||||
|
if css_options.preloaded() {
|
||||||
|
head_resources.push_str(&format!(
|
||||||
|
"<link rel=\"preload\" as=\"style\" href=\"/{{base_path}}/assets/{asset_path}\" crossorigin>"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssetOptions::Image(image_options) => {
|
||||||
|
if image_options.preloaded() {
|
||||||
|
head_resources.push_str(&format!(
|
||||||
|
"<link rel=\"preload\" as=\"image\" href=\"/{{base_path}}/assets/{asset_path}\" crossorigin>"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssetOptions::Js(js_options) => {
|
||||||
|
if js_options.preloaded() {
|
||||||
|
head_resources.push_str(&format!(
|
||||||
|
"<link rel=\"preload\" as=\"script\" href=\"/{{base_path}}/assets/{asset_path}\" crossorigin>"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Manually inject the wasm file for preloading. WASM currently doesn't support preloading in the manganis asset system
|
||||||
|
let wasm_source_path = self.build.wasm_bindgen_wasm_output_file();
|
||||||
|
let wasm_path = self
|
||||||
|
.app
|
||||||
|
.assets
|
||||||
|
.assets
|
||||||
|
.get(&wasm_source_path)
|
||||||
|
.expect("WASM asset should exist in web bundles")
|
||||||
|
.bundled_path();
|
||||||
|
head_resources.push_str(&format!(
|
||||||
|
"<link rel=\"preload\" as=\"fetch\" type=\"application/wasm\" href=\"/{{base_path}}/assets/{wasm_path}\" crossorigin>"
|
||||||
|
));
|
||||||
|
|
||||||
replace_or_insert_before("{style_include}", "</head", &head_resources, html);
|
replace_or_insert_before("{style_include}", "</head", &head_resources, html);
|
||||||
|
|
||||||
@@ -95,7 +133,7 @@ impl BuildRequest {
|
|||||||
/// Inject loading scripts if they are not already present
|
/// Inject loading scripts if they are not already present
|
||||||
fn inject_loading_scripts(&self, html: &mut String) {
|
fn inject_loading_scripts(&self, html: &mut String) {
|
||||||
// If it looks like we are already loading wasm or the current build opted out of injecting loading scripts, don't inject anything
|
// If it looks like we are already loading wasm or the current build opted out of injecting loading scripts, don't inject anything
|
||||||
if !self.build.inject_loading_scripts || html.contains("__wbindgen_start") {
|
if !self.build.build.inject_loading_scripts || html.contains("__wbindgen_start") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,9 +142,9 @@ impl BuildRequest {
|
|||||||
"</body",
|
"</body",
|
||||||
r#"<script>
|
r#"<script>
|
||||||
// We can't use a module script here because we need to start the script immediately when streaming
|
// We can't use a module script here because we need to start the script immediately when streaming
|
||||||
import("/{base_path}/wasm/{app_name}.js").then(
|
import("/{base_path}/{js_path}").then(
|
||||||
({ default: init }) => {
|
({ default: init }) => {
|
||||||
init("/{base_path}/wasm/{app_name}_bg.wasm").then((wasm) => {
|
init("/{base_path}/{wasm_path}").then((wasm) => {
|
||||||
if (wasm.__wbindgen_start == undefined) {
|
if (wasm.__wbindgen_start == undefined) {
|
||||||
wasm.main();
|
wasm.main();
|
||||||
}
|
}
|
||||||
@@ -123,22 +161,41 @@ impl BuildRequest {
|
|||||||
true => html.replace("{DX_TOAST_UTILITIES}", TOAST_HTML),
|
true => html.replace("{DX_TOAST_UTILITIES}", TOAST_HTML),
|
||||||
false => html.replace("{DX_TOAST_UTILITIES}", ""),
|
false => html.replace("{DX_TOAST_UTILITIES}", ""),
|
||||||
};
|
};
|
||||||
|
|
||||||
// And try to insert preload links for the wasm and js files
|
|
||||||
*html = html.replace(
|
|
||||||
"</head",
|
|
||||||
r#"<link rel="preload" href="/{base_path}/wasm/{app_name}_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
|
||||||
<link rel="preload" href="/{base_path}/wasm/{app_name}.js" as="script">
|
|
||||||
</head"#
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace any special placeholders in the HTML with resolved values
|
/// Replace any special placeholders in the HTML with resolved values
|
||||||
fn replace_template_placeholders(&self, html: &mut String) {
|
fn replace_template_placeholders(&self, html: &mut String) {
|
||||||
let base_path = self.krate.config.web.app.base_path();
|
let base_path = self.build.krate.config.web.app.base_path();
|
||||||
*html = html.replace("{base_path}", base_path);
|
*html = html.replace("{base_path}", base_path);
|
||||||
|
|
||||||
let app_name = &self.krate.executable_name();
|
let app_name = &self.build.krate.executable_name();
|
||||||
|
let wasm_source_path = self.build.wasm_bindgen_wasm_output_file();
|
||||||
|
let wasm_path = self
|
||||||
|
.app
|
||||||
|
.assets
|
||||||
|
.assets
|
||||||
|
.get(&wasm_source_path)
|
||||||
|
.expect("WASM asset should exist in web bundles")
|
||||||
|
.bundled_path();
|
||||||
|
let wasm_path = format!("assets/{wasm_path}");
|
||||||
|
let js_source_path = self.build.wasm_bindgen_js_output_file();
|
||||||
|
let js_path = self
|
||||||
|
.app
|
||||||
|
.assets
|
||||||
|
.assets
|
||||||
|
.get(&js_source_path)
|
||||||
|
.expect("JS asset should exist in web bundles")
|
||||||
|
.bundled_path();
|
||||||
|
let js_path = format!("assets/{js_path}");
|
||||||
|
|
||||||
|
// If the html contains the old `{app_name}` placeholder, replace {app_name}_bg.wasm and {app_name}.js
|
||||||
|
// with the new paths
|
||||||
|
*html = html.replace("wasm/{app_name}_bg.wasm", &wasm_path);
|
||||||
|
*html = html.replace("wasm/{app_name}.js", &js_path);
|
||||||
|
// Otherwise replace the new placeholders
|
||||||
|
*html = html.replace("{wasm_path}", &wasm_path);
|
||||||
|
*html = html.replace("{js_path}", &js_path);
|
||||||
|
// Replace the app_name if we find it anywhere standalone
|
||||||
*html = html.replace("{app_name}", app_name);
|
*html = html.replace("{app_name}", app_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,12 +212,14 @@ impl BuildRequest {
|
|||||||
// The path is actually a web path which is relative to the root of the website
|
// The path is actually a web path which is relative to the root of the website
|
||||||
let path = path.strip_prefix("/").unwrap_or(path);
|
let path = path.strip_prefix("/").unwrap_or(path);
|
||||||
let asset_dir_path = self
|
let asset_dir_path = self
|
||||||
|
.build
|
||||||
.krate
|
.krate
|
||||||
.legacy_asset_dir()
|
.legacy_asset_dir()
|
||||||
.map(|dir| dir.join(path).canonicalize());
|
.map(|dir| dir.join(path).canonicalize());
|
||||||
|
|
||||||
if let Some(Ok(absolute_path)) = asset_dir_path {
|
if let Some(Ok(absolute_path)) = asset_dir_path {
|
||||||
let absolute_crate_root = self.krate.crate_dir().canonicalize().unwrap();
|
let absolute_crate_root =
|
||||||
|
self.build.krate.crate_dir().canonicalize().unwrap();
|
||||||
PathBuf::from("./")
|
PathBuf::from("./")
|
||||||
.join(absolute_path.strip_prefix(absolute_crate_root).unwrap())
|
.join(absolute_path.strip_prefix(absolute_crate_root).unwrap())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ impl Display for Commands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static VERSION: Lazy<String> = Lazy::new(|| {
|
pub(crate) static VERSION: Lazy<String> = Lazy::new(|| {
|
||||||
format!(
|
format!(
|
||||||
"{} ({})",
|
"{} ({})",
|
||||||
crate::dx_build_info::PKG_VERSION,
|
crate::dx_build_info::PKG_VERSION,
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ pub(crate) fn proxy_to(
|
|||||||
if uri.path().starts_with("/assets")
|
if uri.path().starts_with("/assets")
|
||||||
|| uri.path().starts_with("/_dioxus")
|
|| uri.path().starts_with("/_dioxus")
|
||||||
|| uri.path().starts_with("/public")
|
|| uri.path().starts_with("/public")
|
||||||
|| uri.path().starts_with("/wasm")
|
|
||||||
{
|
{
|
||||||
tracing::trace!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
|
tracing::trace!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -598,6 +598,7 @@ fn hide_last_window(window: &Window) {
|
|||||||
// back to the app. `NSApplication::hide:` has the correct behaviour
|
// back to the app. `NSApplication::hide:` has the correct behaviour
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
#[allow(unexpected_cfgs)]
|
||||||
objc::rc::autoreleasepool(|| unsafe {
|
objc::rc::autoreleasepool(|| unsafe {
|
||||||
let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication];
|
let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication];
|
||||||
let nil = std::ptr::null_mut::<Object>();
|
let nil = std::ptr::null_mut::<Object>();
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ impl WebviewInstance {
|
|||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let window: id = window.ns_window() as id;
|
let window: id = window.ns_window() as id;
|
||||||
|
#[allow(unexpected_cfgs)]
|
||||||
let _: () = msg_send![window, setCollectionBehavior: NSWindowCollectionBehavior::NSWindowCollectionBehaviorManaged];
|
let _: () = msg_send![window, setCollectionBehavior: NSWindowCollectionBehavior::NSWindowCollectionBehaviorManaged];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ impl History for LiveviewHistory {
|
|||||||
visited_indices
|
visited_indices
|
||||||
.iter()
|
.iter()
|
||||||
.position(|&rhs| timeline.current_index == rhs)
|
.position(|&rhs| timeline.current_index == rhs)
|
||||||
.map_or(false, |index| {
|
.is_some_and(|index| {
|
||||||
index > 0 && visited_indices[index - 1] == timeline.current_index - 1
|
index > 0 && visited_indices[index - 1] == timeline.current_index - 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@ impl History for LiveviewHistory {
|
|||||||
visited_indices
|
visited_indices
|
||||||
.iter()
|
.iter()
|
||||||
.rposition(|&rhs| timeline.current_index == rhs)
|
.rposition(|&rhs| timeline.current_index == rhs)
|
||||||
.map_or(false, |index| {
|
.is_some_and(|index| {
|
||||||
index < visited_indices.len() - 1
|
index < visited_indices.len() - 1
|
||||||
&& visited_indices[index + 1] == timeline.current_index + 1
|
&& visited_indices[index + 1] == timeline.current_index + 1
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,6 +59,21 @@ impl BundledAsset {
|
|||||||
Self::new(absolute_source_path, bundled_path, options)
|
Self::new(absolute_source_path, bundled_path, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
/// This should only be called from the macro
|
||||||
|
/// Create a new asset from const paths
|
||||||
|
pub const fn new_from_const(
|
||||||
|
absolute_source_path: ConstStr,
|
||||||
|
bundled_path: ConstStr,
|
||||||
|
options: AssetOptions,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
absolute_source_path,
|
||||||
|
bundled_path,
|
||||||
|
options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the bundled name of the asset. This identifier cannot be used to read the asset directly
|
/// Get the bundled name of the asset. This identifier cannot be used to read the asset directly
|
||||||
pub fn bundled_path(&self) -> &str {
|
pub fn bundled_path(&self) -> &str {
|
||||||
self.bundled_path.as_str()
|
self.bundled_path.as_str()
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
//! Utilities for creating hashed paths to assets in Manganis. This module defines [`AssetHash`] which is used to create a hashed path to an asset in both the CLI and the macro.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
io::Read,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An error that can occur when hashing an asset
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum AssetHashError {
|
||||||
|
/// An io error occurred
|
||||||
|
IoError { err: std::io::Error, path: PathBuf },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for AssetHashError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
AssetHashError::IoError { path, err } => {
|
||||||
|
write!(f, "Failed to read file: {}; {}", path.display(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for AssetHashError {}
|
||||||
|
|
||||||
|
/// The opaque hash type manganis uses to identify assets. Each time an asset or asset options change, this hash will
|
||||||
|
/// change. This hash is included in the URL of the bundled asset for cache busting.
|
||||||
|
pub struct AssetHash {
|
||||||
|
/// We use a wrapper type here to hide the exact size of the hash so we can switch to a sha hash in a minor version bump
|
||||||
|
hash: [u8; 8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetHash {
|
||||||
|
/// Create a new asset hash
|
||||||
|
const fn new(hash: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
hash: hash.to_le_bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the hash bytes
|
||||||
|
pub const fn bytes(&self) -> &[u8] {
|
||||||
|
&self.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new asset hash for a file. The input file to this function should be fully resolved
|
||||||
|
pub fn hash_file_contents(file_path: &Path) -> Result<AssetHash, AssetHashError> {
|
||||||
|
// Create a hasher
|
||||||
|
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
|
||||||
|
// If this is a folder, hash the folder contents
|
||||||
|
if file_path.is_dir() {
|
||||||
|
let files = std::fs::read_dir(file_path).map_err(|err| AssetHashError::IoError {
|
||||||
|
err,
|
||||||
|
path: file_path.to_path_buf(),
|
||||||
|
})?;
|
||||||
|
for file in files.flatten() {
|
||||||
|
let path = file.path();
|
||||||
|
Self::hash_file_contents(&path)?.bytes().hash(&mut hash);
|
||||||
|
}
|
||||||
|
let hash = hash.finish();
|
||||||
|
return Ok(AssetHash::new(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, open the file to get its contents
|
||||||
|
let mut file = std::fs::File::open(file_path).map_err(|err| AssetHashError::IoError {
|
||||||
|
err,
|
||||||
|
path: file_path.to_path_buf(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// We add a hash to the end of the file so it is invalidated when the bundled version of the file changes
|
||||||
|
// The hash includes the file contents, the options, and the version of manganis. From the macro, we just
|
||||||
|
// know the file contents, so we only include that hash
|
||||||
|
let mut buffer = [0; 8192];
|
||||||
|
loop {
|
||||||
|
let read = file
|
||||||
|
.read(&mut buffer)
|
||||||
|
.map_err(|err| AssetHashError::IoError {
|
||||||
|
err,
|
||||||
|
path: file_path.to_path_buf(),
|
||||||
|
})?;
|
||||||
|
if read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hash.write(&buffer[..read]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AssetHash::new(hash.finish()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,3 +17,5 @@ mod asset;
|
|||||||
pub use asset::*;
|
pub use asset::*;
|
||||||
|
|
||||||
pub mod linker;
|
pub mod linker;
|
||||||
|
|
||||||
|
pub mod hash;
|
||||||
@@ -1,15 +1,35 @@
|
|||||||
|
use manganis_core::hash::AssetHash;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
use std::{
|
use std::path::PathBuf;
|
||||||
hash::Hasher,
|
|
||||||
io::Read,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
LitStr, Token,
|
LitStr, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum AssetParseError {
|
||||||
|
AssetDoesntExist { path: PathBuf },
|
||||||
|
InvalidPath { path: PathBuf },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for AssetParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
AssetParseError::AssetDoesntExist { path } => {
|
||||||
|
write!(f, "Asset at {} doesn't exist", path.display())
|
||||||
|
}
|
||||||
|
AssetParseError::InvalidPath { path } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Asset path {} is invalid. Make sure the asset exists within this crate.",
|
||||||
|
path.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_path(raw: &str) -> Result<PathBuf, AssetParseError> {
|
fn resolve_path(raw: &str) -> Result<PathBuf, AssetParseError> {
|
||||||
// Get the location of the root of the crate which is where all assets are relative to
|
// Get the location of the root of the crate which is where all assets are relative to
|
||||||
//
|
//
|
||||||
@@ -53,75 +73,6 @@ fn resolve_path(raw: &str) -> Result<PathBuf, AssetParseError> {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_file_contents(file_path: &Path) -> Result<u64, AssetParseError> {
|
|
||||||
// Create a hasher
|
|
||||||
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
|
||||||
|
|
||||||
// If this is a folder, hash the folder contents
|
|
||||||
if file_path.is_dir() {
|
|
||||||
let files = std::fs::read_dir(file_path).map_err(|err| AssetParseError::IoError {
|
|
||||||
err,
|
|
||||||
path: file_path.to_path_buf(),
|
|
||||||
})?;
|
|
||||||
for file in files.flatten() {
|
|
||||||
let path = file.path();
|
|
||||||
hash_file_contents(&path)?;
|
|
||||||
}
|
|
||||||
return Ok(hash.finish());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, open the file to get its contents
|
|
||||||
let mut file = std::fs::File::open(file_path).map_err(|err| AssetParseError::IoError {
|
|
||||||
err,
|
|
||||||
path: file_path.to_path_buf(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// We add a hash to the end of the file so it is invalidated when the bundled version of the file changes
|
|
||||||
// The hash includes the file contents, the options, and the version of manganis. From the macro, we just
|
|
||||||
// know the file contents, so we only include that hash
|
|
||||||
let mut buffer = [0; 8192];
|
|
||||||
loop {
|
|
||||||
let read = file
|
|
||||||
.read(&mut buffer)
|
|
||||||
.map_err(AssetParseError::FailedToReadAsset)?;
|
|
||||||
if read == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
hash.write(&buffer[..read]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(hash.finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) enum AssetParseError {
|
|
||||||
AssetDoesntExist { path: PathBuf },
|
|
||||||
IoError { err: std::io::Error, path: PathBuf },
|
|
||||||
InvalidPath { path: PathBuf },
|
|
||||||
FailedToReadAsset(std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for AssetParseError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
AssetParseError::AssetDoesntExist { path } => {
|
|
||||||
write!(f, "Asset at {} doesn't exist", path.display())
|
|
||||||
}
|
|
||||||
AssetParseError::IoError { path, err } => {
|
|
||||||
write!(f, "Failed to read file: {}; {}", path.display(), err)
|
|
||||||
}
|
|
||||||
AssetParseError::InvalidPath { path } => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Asset path {} is invalid. Make sure the asset exists within this crate.",
|
|
||||||
path.display()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
AssetParseError::FailedToReadAsset(err) => write!(f, "Failed to read asset: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AssetParser {
|
pub struct AssetParser {
|
||||||
/// The span of the source string
|
/// The span of the source string
|
||||||
path_span: proc_macro2::Span,
|
path_span: proc_macro2::Span,
|
||||||
@@ -189,7 +140,7 @@ impl ToTokens for AssetParser {
|
|||||||
let mut asset_str = proc_macro2::Literal::string(&asset_str);
|
let mut asset_str = proc_macro2::Literal::string(&asset_str);
|
||||||
asset_str.set_span(self.path_span);
|
asset_str.set_span(self.path_span);
|
||||||
|
|
||||||
let hash = match hash_file_contents(asset) {
|
let hash = match AssetHash::hash_file_contents(asset) {
|
||||||
Ok(hash) => hash,
|
Ok(hash) => hash,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let err = err.to_string();
|
let err = err.to_string();
|
||||||
@@ -198,15 +149,17 @@ impl ToTokens for AssetParser {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let hash = hash.bytes();
|
||||||
|
|
||||||
// Generate the link section for the asset
|
// Generate the link section for the asset
|
||||||
// The link section includes the source path and the output path of the asset
|
// The link section includes the source path and the output path of the asset
|
||||||
let link_section = crate::generate_link_section(quote!(__ASSET));
|
let link_section = crate::generate_link_section(quote!(__ASSET));
|
||||||
|
|
||||||
// generate the asset::new method to deprecate the `./assets/blah.css` syntax
|
// generate the asset::new method to deprecate the `./assets/blah.css` syntax
|
||||||
let constructor = if asset.is_relative() {
|
let constructor = if asset.is_relative() {
|
||||||
quote::quote! { new_relative }
|
quote::quote! { create_bundled_asset_relative }
|
||||||
} else {
|
} else {
|
||||||
quote::quote! { new }
|
quote::quote! { create_bundled_asset }
|
||||||
};
|
};
|
||||||
|
|
||||||
let options = if self.options.is_empty() {
|
let options = if self.options.is_empty() {
|
||||||
@@ -218,22 +171,16 @@ impl ToTokens for AssetParser {
|
|||||||
tokens.extend(quote! {
|
tokens.extend(quote! {
|
||||||
{
|
{
|
||||||
// We keep a hash of the contents of the asset for cache busting
|
// We keep a hash of the contents of the asset for cache busting
|
||||||
const __ASSET_HASH: u64 = #hash;
|
const __ASSET_HASH: &[u8] = &[#(#hash),*];
|
||||||
// The source is used by the CLI to copy the asset
|
// The source is used by the CLI to copy the asset
|
||||||
const __ASSET_SOURCE_PATH: &'static str = #asset_str;
|
const __ASSET_SOURCE_PATH: &'static str = #asset_str;
|
||||||
// The options give the CLI info about how to process the asset
|
// The options give the CLI info about how to process the asset
|
||||||
// Note: into_asset_options is not a trait, so we cannot accept the options directly
|
// Note: into_asset_options is not a trait, so we cannot accept the options directly
|
||||||
// in the constructor. Stable rust doesn't have support for constant functions in traits
|
// in the constructor. Stable rust doesn't have support for constant functions in traits
|
||||||
const __ASSET_OPTIONS: manganis::AssetOptions = #options.into_asset_options();
|
const __ASSET_OPTIONS: manganis::AssetOptions = #options.into_asset_options();
|
||||||
// We calculate the bundled path from the hash and any transformations done by the options
|
|
||||||
// This is the final path that the asset will be written to
|
|
||||||
const __ASSET_BUNDLED_PATH: manganis::macro_helpers::const_serialize::ConstStr = manganis::macro_helpers::generate_unique_path(__ASSET_SOURCE_PATH, __ASSET_HASH, &__ASSET_OPTIONS);
|
|
||||||
// Get the reference to the string that was generated. We cannot return &'static str from
|
|
||||||
// generate_unique_path because it would return a reference to data generated in the function
|
|
||||||
const __ASSET_BUNDLED_PATH_STR: &'static str = __ASSET_BUNDLED_PATH.as_str();
|
|
||||||
// Create the asset that the crate will use. This is used both in the return value and
|
// Create the asset that the crate will use. This is used both in the return value and
|
||||||
// added to the linker for the bundler to copy later
|
// added to the linker for the bundler to copy later
|
||||||
const __ASSET: manganis::BundledAsset = manganis::BundledAsset::#constructor(__ASSET_SOURCE_PATH, __ASSET_BUNDLED_PATH_STR, __ASSET_OPTIONS);
|
const __ASSET: manganis::BundledAsset = manganis::macro_helpers::#constructor(__ASSET_SOURCE_PATH, __ASSET_HASH, __ASSET_OPTIONS);
|
||||||
|
|
||||||
#link_section
|
#link_section
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,45 @@ use manganis_core::{AssetOptions, BundledAsset};
|
|||||||
|
|
||||||
use crate::hash::ConstHasher;
|
use crate::hash::ConstHasher;
|
||||||
|
|
||||||
|
/// Create a bundled asset from the input path, the content hash, and the asset options
|
||||||
|
pub const fn create_bundled_asset(
|
||||||
|
input_path: &str,
|
||||||
|
content_hash: &[u8],
|
||||||
|
asset_config: AssetOptions,
|
||||||
|
) -> BundledAsset {
|
||||||
|
let hashed_path = generate_unique_path_with_byte_hash(input_path, content_hash, &asset_config);
|
||||||
|
BundledAsset::new_from_const(ConstStr::new(input_path), hashed_path, asset_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a bundled asset from the input path, the content hash, and the asset options with a relative asset deprecation warning
|
||||||
|
///
|
||||||
|
/// This method is deprecated and will be removed in a future release.
|
||||||
|
#[deprecated(
|
||||||
|
note = "Relative asset!() paths are not supported. Use a path like `/assets/myfile.png` instead of `./assets/myfile.png`"
|
||||||
|
)]
|
||||||
|
pub const fn create_bundled_asset_relative(
|
||||||
|
input_path: &str,
|
||||||
|
content_hash: &[u8],
|
||||||
|
asset_config: AssetOptions,
|
||||||
|
) -> BundledAsset {
|
||||||
|
create_bundled_asset(input_path, content_hash, asset_config)
|
||||||
|
}
|
||||||
|
|
||||||
/// Format the input path with a hash to create an unique output path for the macro in the form `{input_path}-{hash}.{extension}`
|
/// Format the input path with a hash to create an unique output path for the macro in the form `{input_path}-{hash}.{extension}`
|
||||||
pub const fn generate_unique_path(
|
pub const fn generate_unique_path(
|
||||||
input_path: &str,
|
input_path: &str,
|
||||||
content_hash: u64,
|
content_hash: u64,
|
||||||
asset_config: &AssetOptions,
|
asset_config: &AssetOptions,
|
||||||
|
) -> ConstStr {
|
||||||
|
let byte_hash = content_hash.to_le_bytes();
|
||||||
|
generate_unique_path_with_byte_hash(input_path, &byte_hash, asset_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format the input path with a hash to create an unique output path for the macro in the form `{input_path}-{hash}.{extension}`
|
||||||
|
const fn generate_unique_path_with_byte_hash(
|
||||||
|
input_path: &str,
|
||||||
|
content_hash: &[u8],
|
||||||
|
asset_config: &AssetOptions,
|
||||||
) -> ConstStr {
|
) -> ConstStr {
|
||||||
// Format the unique path with the format `{input_path}-{hash}.{extension}`
|
// Format the unique path with the format `{input_path}-{hash}.{extension}`
|
||||||
// Start with the input path
|
// Start with the input path
|
||||||
@@ -43,7 +77,7 @@ pub const fn generate_unique_path(
|
|||||||
// Hash the contents along with the asset config to create a unique hash for the asset
|
// Hash the contents along with the asset config to create a unique hash for the asset
|
||||||
// When this hash changes, the client needs to re-fetch the asset
|
// When this hash changes, the client needs to re-fetch the asset
|
||||||
let mut hasher = ConstHasher::new();
|
let mut hasher = ConstHasher::new();
|
||||||
hasher = hasher.write(&content_hash.to_le_bytes());
|
hasher = hasher.write(content_hash);
|
||||||
hasher = hasher.hash_by_bytes(asset_config);
|
hasher = hasher.hash_by_bytes(asset_config);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { test, expect } = require("@playwright/test");
|
||||||
|
|
||||||
|
test("optimized scripts run", async ({ page }) => {
|
||||||
|
await page.goto("http://localhost:8989");
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dist
|
||||||
|
target
|
||||||
|
monaco-editor-0.52.2
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "dioxus-cli-optimization-test"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Playwright test for Dioxus CLI optimization"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dioxus = { workspace = true, features = ["web"]}
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
reqwest = { workspace = true, features = ["blocking"] }
|
||||||
|
flate2 = "1.0.35"
|
||||||
|
tar = "0.4.43"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// If the monaco editor folder doesn't exist, download it
|
||||||
|
let monaco_path = PathBuf::from("monaco-editor-0.52.2");
|
||||||
|
if monaco_path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz";
|
||||||
|
let bytes = reqwest::blocking::get(url).unwrap().bytes().unwrap();
|
||||||
|
let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(bytes.as_ref()));
|
||||||
|
archive.unpack(&monaco_path).unwrap();
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// This test checks the CLI optimizes assets correctly without breaking them
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const MONACO_FOLDER: Asset = asset!("/monaco-editor-0.52.2/package/min/vs");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus::launch(App);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn App() -> Element {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -138,5 +138,15 @@ module.exports = defineConfig({
|
|||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
stdout: "pipe",
|
stdout: "pipe",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
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 -- serve --addr "127.0.0.1" --port 8989',
|
||||||
|
port: 8989,
|
||||||
|
timeout: 50 * 60 * 1000,
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
stdout: "pipe",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user