Rust in Everything
基于 topcoat-asset 0.10.0

no asset bundle at ...:直接 cargo run 起不来

主线位置:发现主线的另一支。asset! 只在二进制里留一段标记,文件由 CLI 的 bundler 扫出来、拷进 assets/、写好 manifest.toml;程序启动时 AssetBundle::load() 去读这份清单(asset! 与 bundler)。

flowchart LR
  build["cargo build"] --> bin["二进制:带 TOPCOAT_ASSET 标记"]
  bin --> bundler["topcoat asset bundle 或 topcoat dev"]
  bundler --> dir["可执行文件旁边的 assets/:文件 + manifest.toml"]
  bin -->|"直接运行"| load["AssetBundle::load()"]
  dir --> load
  load -->|"目录不在"| err["NotFound:no asset bundle at ..."]

现象

依赖:topcoat =0.10.0 的 full、tokio =1.54.1。程序照上游文档的样子加载资产包:

const LOGO: Asset = asset!("./data/logo.svg");

#[tokio::main]
async fn main() {
  let bundle = AssetBundle::load().unwrap();
  topcoat::start(module_router!().discover().assets(bundle).build()).await.unwrap();
}

#[page]
async fn home() -> Result<impl View> {
  Ok(view! { <img src=(LOGO) alt="logo"> })
}

cargo build 之后直接运行:

thread 'main' (2645365) panicked at src/main.rs:12:36:
called `Result::unwrap()` on an `Err` value: Custom { kind: NotFound, error: "no asset bundle at <target>/debug/assets" }

用了 05 章运行时的程序也一样:topcoat::runtime::script() 引用的运行时 JS 本身就是一个 asset!,同样要从资产包里服务。

原因

AssetBundle::load 只在一个地方找:std::env::current_exe() 的父目录下的 assets/manifest.toml。cargo build 不会生成它,生成它的是 CLI 的 bundler:扫描编译好的二进制、解析每条记录的路径、拷贝或下载文件、按内容哈希命名、写清单。

修法

开发时先打包,再运行:

$ topcoat asset bundle --bin myapp
bundled assets into <target>/debug/assets

$ cat <target>/debug/assets/manifest.toml
version = 1

[[assets]]
id = 3361005576191161558
file = "logo-e4dd91a9545a8dc7.svg"
hash = "e4dd91a9545a8dc742502e27ebd5773bb002c3526ab15f2ffa4334c282dd60c4"
content_type = "image/svg+xml"

再运行同一个二进制,页面里的 <img src="/_topcoat/assets/logo-e4dd91a9545a8dc7.svg" alt="logo"> 能取到文件,带 cache-control: public, max-age=31536000, immutable。日常开发用 topcoat dev,它在每次构建后自动打包(tailwind、font、icon 与 topcoat CLI)。

-o 把包写到别处时,程序里要换成 AssetBundle::load_dir(dir),二者要对上。

部署时只拷了二进制

把同一个二进制单独拷到一个新目录运行:

called `Result::unwrap()` on an `Err` value: Custom { kind: NotFound, error: "no asset bundle at <部署目录>/assets" }

部署单元是「二进制 + 同一次构建生成的 assets/」。只拷二进制会得到上面的错误;拷了一个旧的 assets/ 更隐蔽:启动不报错(load 只读清单,不检查二进制里声明了哪些资产),渲染到清单里没有的那个资产时才 panic,那一页变成 500(asset! 与 bundler的「运行时」一节)。

容器镜像里常见的写法是构建阶段跑 topcoat asset bundle --release,再把可执行文件和它旁边的 assets/ 一起 COPY 到运行阶段,保持相对位置不变。

让错误更好读

unwrap() 只给出一行 NotFound。在启动处把它换成一句说明怎么修的话:

let bundle = AssetBundle::load()
  .unwrap_or_else(|error| panic!("{error}; run `topcoat asset bundle` (or `topcoat dev`) first"));

不用 asset! 也不用运行时脚本的程序,不调 .assets(...) 就不需要资产包。

小结

  • AssetBundle::load() 只找可执行文件旁边的 assets/manifest.toml,cargo build 不生成它,直接 cargo run 就是 NotFound。
  • 先 topcoat asset bundle(或用 topcoat dev)再运行;-o 换了位置就用 load_dir。
  • 部署单元是二进制加同一次构建的 assets/;旧清单不会在启动时报错,到渲染缺失资产时才 500。

💬 关联讨论

发起讨论
加载中...