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。