From d459e7ca1c3dd6607ffe634120f7a4fbd1228768 Mon Sep 17 00:00:00 2001 From: Tony <68118705+Legend-Master@users.noreply.github.com> Date: Wed, 3 Sep 2025 01:29:58 +0800 Subject: [PATCH] Rework resource guide (#3485) Co-authored-by: FabianLars Co-authored-by: FabianLars --- .vscode/settings.json | 3 + src/content/docs/develop/resources.mdx | 298 ++++++++++++++++++++++--- 2 files changed, 275 insertions(+), 26 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e66e92071..be713d381 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,8 @@ "prettier.documentSelectors": ["**/*.astro"], "[astro]": { "editor.defaultFormatter": "astro-build.astro-vscode" + }, + "[mdx]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" } } diff --git a/src/content/docs/develop/resources.mdx b/src/content/docs/develop/resources.mdx index 202ee0b4c..794ca030d 100644 --- a/src/content/docs/develop/resources.mdx +++ b/src/content/docs/develop/resources.mdx @@ -3,29 +3,79 @@ title: Embedding Additional Files i18nReady: true --- +import { Tabs, TabItem } from '@astrojs/starlight/components'; + You may need to include additional files in your application bundle that aren't part of your frontend (your `frontendDist`) directly or which are too big to be inlined into the binary. We call these files `resources`. -To bundle the files of your choice, you can add the `resources` property to the `bundle` object in your `tauri.conf.json` file. +## Configuration -See more about `tauri.conf.json` configuration [here][tauri.bundle]. +To bundle the files of your choice, add the `resources` property to the `bundle` object in your `tauri.conf.json` file. -`resources` expects a list of strings targeting files or directories either with absolute or relative paths. It supports glob patterns in case you need to include multiple files from a directory. +To include a list of files: -Here is a sample to illustrate the configuration. This is not a complete `tauri.conf.json` file: + + ```json title=tauri.conf.json { "bundle": { "resources": [ + "./path/to/some-file.txt", "/absolute/path/to/textfile.txt", - "relative/path/to/jsonfile.json", - "resources/**/*" + "../relative/path/to/jsonfile.json", + "some-folder/", + "resources/**/*.md" ] } } ``` -Alternatively the `resources` config also accepts a map object if you want to change where the files will be copied to. Here is a sample that shows how to include files from different sources into the same `resources` folder: + + + +```json title=tauri.conf.json5 +{ + "bundle": { + "resources": [ + // Will be placed to `$RESOURCE/path/to/some-file.txt` + "./path/to/some-file.txt", + + // The root in an abosolute path will be replaced by `_root_`, + // so `textfile.txt` will be placed to `$RESOURCE/_root_/absolute/path/to/textfile.txt` + "/absolute/path/to/textfile.txt", + + // `..` in a relative path will be replaced by `_up_`, + // so `jsonfile.json` will be placed to `$RESOURCE/_up_/relative/path/to/textfile.txt`, + "../relative/path/to/jsonfile.json", + + // If the path is a directory, the entire directory will be copied to the `$RESOURCE` directory, + // preserving the original structures, for example: + // - `some-folder/file.txt` -> `$RESOURCE/some-folder/file.txt` + // - `some-folder/another-folder/config.json` -> `$RESOURCE/some-folder/another-folder/config.json` + // This is the same as `some-folder/**/*` + "some-folder/", + + // You can also include multiple files at once through glob patterns. + // All the `.md` files inside `resources` will be placed to `$RESOURCE/resources/`, + // preserving their original directory structures, for example: + // - `resources/index.md` -> `$RESOURCE/resources/index.md` + // - `resources/docs/setup.md` -> `$RESOURCE/resources/docs/setup.md` + "resources/**/*.md" + ] + } +} +``` + + + + +The bundled files will be in `$RESOURCES/` with the original directory structure preserved, +for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt` + +To fine control where the files will get copied to, use a map instead: + + + ```json title=tauri.conf.json { @@ -33,19 +83,50 @@ Alternatively the `resources` config also accepts a map object if you want to ch "resources": { "/absolute/path/to/textfile.txt": "resources/textfile.txt", "relative/path/to/jsonfile.json": "resources/jsonfile.json", - "resources/**/*": "resources/" + "resources/": "", + "docs/**/*md": "website-docs/" } } } ``` -:::note + + -In Tauri's [permission system](/reference/acl/capability/), absolute paths and paths containing parent components (`../`) can only be allowed via `"$RESOURCE/**"`. Relative paths like `"path/to/file.txt"` can be allowed explicitly via `"$RESOURCE/path/to/file.txt"`. +```json title=tauri.conf.json5 +{ + "bundle": { + "resources": { + // `textfile.txt` will be placed to `$RESOURCE/resources/textfile.txt` + "/absolute/path/to/textfile.txt": "resources/textfile.txt", -::: + // `jsonfile.json` will be placed to `$RESOURCE/resources/jsonfile.json` + "relative/path/to/jsonfile.json": "resources/jsonfile.json", -## Source path syntax + // Copy the entire directory to `$RESOURCE`, preserving the original structures, + // the target is "" which means it will be placed directly in the resource directory `$RESOURCE`, for example: + // - `resources/file.txt` -> `$RESOURCE/file.txt` + // - `resources/some-folder/config.json` -> `$RESOURCE/some-folder/config.json` + "resources/": "", + + // When using glob patterns, the behavior is different from the list one, + // all the matching files will be placed to the target directory without preserving the original file structures + // for example: + // - `docs/index.md` -> `$RESOURCE/website-docs/index.md` + // - `docs/plugins/setup.md` -> `$RESOURCE/website-docs/setup.md` + "docs/**/*md": "website-docs/" + } + } +} +``` + + + + +To learn about where `$RESOURCE` resolves to on each platforms, see the documentation of [`resource_dir`] + +
+Source path syntax In the following explanations "target resource directory" is either the value after the colon in the object notation, or a reconstruction of the original file paths in the array notation. @@ -56,19 +137,111 @@ In the following explanations "target resource directory" is either the value af - `"dir/**/*"`: copies all files in the `dir` directory _recursively_ (all files in `dir/` and all files in all sub-directories) into the target resource directory. - `"dir/**/**`: throws an error because `**` only matches directories and therefore no files can be found. -## Accessing files in Rust +
-In this example we want to bundle additional i18n json files that look like this: +## Resolve resource file paths -```json title=de.json +To resolve the path for a resource file, instead of manually calculating the path, use the following APIs + + + + +On the Rust side, you need an instance of the [`PathResolver`] which you can get from [`App`] and [`AppHandle`], +then call [`PathResolver::resolve`]: + +```rust +tauri::Builder::default() + .setup(|app| { + let resource_path = app.path().resolve("lang/de.json", BaseDirectory::Resource)?; + Ok(()) + }) +``` + +To use it in a command: + +```rust +#[tauri::command] +fn hello(handle: tauri::AppHandle) { + let resource_path = handle.path().resolve("lang/de.json", BaseDirectory::Resource)?; +} +``` + + + + +To resolve the path in JavaScript, use [`resolveResource`]: + +```javascript +import { resolveResource } from '@tauri-apps/api/path'; +const resourcePath = await resolveResource('lang/de.json'); +``` + + + + +### Path syntax + +The path in the API calls can be either a normal relative path like `folder/json_file.json` that resolves to `$RESOURCE/folder/json_file.json`, +or a paths like `../relative/folder/toml_file.toml` that resolves to `$RESOURCE/_up_/relative/folder/toml_file.toml`, +these APIs use the same rules as you write `tauri.conf.json > bundle > resources`, for example: + +```json title=tauri.conf.json +{ + "bundle": { + "resources": ["folder/json_file.json", "../relative/folder/toml_file.toml"] + } +} +``` + +```rust +let json_path = app.path().resolve("folder/json_file.json", BaseDirectory::Resource)?; +let toml_path = app.path().resolve("../relative/folder/toml_file.toml", BaseDirectory::Resource)?; +``` + +### Android + +Currently the resources are stored in the APK as assets so the return value of those APIs are not normal file system paths, +we use a special URI prefix `asset://localhost/` here that can be used with the [`fs` plugin], +with that, you can read the files through [`FsExt::fs`] like this: + +```rust +let resource_path = app.path().resolve("lang/de.json", BaseDirectory::Resource).unwrap(); +let json = app.fs().read_to_string(&resource_path); +``` + +If you want or must have the resource files to be on a real file system, copy the contents out manually through the [`fs` plugin] + +## Reading resource files + +In this example we want to bundle additional i18n json files like this: + +``` +. +├── src-tauri/ +│ ├── tauri.conf.json +│ ├── lang/ +│ │ ├── de.json +│ │ └── en.json +│ └── ... +└── ... +``` + +```json title=tauri.conf.json +{ + "bundle": { + "resources": ["lang/*"] + } +} +``` + +```json title=lang/de.json { "hello": "Guten Tag!", "bye": "Auf Wiedersehen!" } ``` -In this case, we store these files in a `lang` directory next to the `tauri.conf.json`. -For this we add `"lang/*"` to `resources` as shown above. +### Rust On the Rust side, you need an instance of the [`PathResolver`] which you can get from [`App`] and [`AppHandle`]: @@ -79,8 +252,11 @@ tauri::Builder::default() // `tauri.conf.json > bundle > resources` let resource_path = app.path().resolve("lang/de.json", BaseDirectory::Resource)?; - let file = std::fs::File::open(&resource_path).unwrap(); - let lang_de: serde_json::Value = serde_json::from_reader(file).unwrap(); + let json = std::fs::read_to_string(&resource_path).unwrap(); + // Or when dealing with Android, use the file system plugin instead + // let json = app.fs().read_to_string(&resource_path); + + let lang_de: serde_json::Value = serde_json::from_str(json).unwrap(); // This will print 'Guten Tag!' to the terminal println!("{}", lang_de.get("hello").unwrap()); @@ -94,18 +270,21 @@ tauri::Builder::default() fn hello(handle: tauri::AppHandle) -> String { let resource_path = handle.path().resolve("lang/de.json", BaseDirectory::Resource)?; - let file = std::fs::File::open(&resource_path).unwrap(); - let lang_de: serde_json::Value = serde_json::from_reader(file).unwrap(); + let json = std::fs::read_to_string(&resource_path).unwrap(); + // Or when dealing with Android, use the file system plugin instead + // let json = handle.fs().read_to_string(&resource_path); + + let lang_de: serde_json::Value = serde_json::from_str(json).unwrap(); lang_de.get("hello").unwrap() } ``` -## Accessing files in JavaScript +### JavaScript -For the JavaScript side, you can either use a command like the one above and call it through `await invoke('hello')` or access the files using the [`plugin-fs`] +For the JavaScript side, you can either use a command like the one above and call it through `await invoke('hello')` or access the files using the [`fs` plugin]. -When using the [`plugin-fs`], addition from the [basic setup], you'll also need to configure the access control list to enable any [`plugin-fs`] APIs you will need as well as permissions to access the `$RESOURCE` folder: +When using the [`fs` plugin], in addition to the [basic setup], you'll also need to configure the access control list to enable any plugin APIs you need as well as the permissions to access the `$RESOURCE` folder: ```json title=src-tauri/capabilities/default.json ins={8-9} { @@ -135,11 +314,78 @@ const langDe = JSON.parse(await readTextFile(resourcePath)); console.log(langDe.hello); // This will print 'Guten Tag!' to the devtools console ``` -[tauri.bundle]: /reference/config/#bundleconfig +## Permissions + +Since we replace `../` to `_up_` in relative paths and the root to `_root_` in abosolute paths when using a list, +those files will be in sub folders inside the resource directory, +to allow those paths in Tauri's [permission system](/security/capabilities/), +use `$RESOURCE/**/*` to allow recursive access to those files + +### Examples + +With a file bundled like this: + +```json title=tauri.conf.json +{ + "bundle": { + "resources": ["../relative/path/to/jsonfile.json"] + } +} +``` + +To use it with the [`fs` plugin]: + +```json title=src-tauri/capabilities/default.json ins={8-15} +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "main-capability", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "fs:allow-stat", + "fs:allow-read-text-file", + "fs:allow-resource-read-recursive", + { + "identifier": "fs:scope", + "allow": ["$RESOURCE/**/*"], + "deny": ["$RESOURCE/secret.txt"] + } + ] +} +``` + +To use it with the [`opener` plugin]: + +```json title=src-tauri/capabilities/default.json ins={8-15} +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "main-capability", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + { + "identifier": "opener:allow-open-path", + "allow": [ + { + "path": "$RESOURCE/**/*" + } + ] + } + ] +} +``` + +[`resource_dir`]: https://docs.rs/tauri/latest/tauri/path/struct.PathResolver.html#method.resource_dir [`pathresolver`]: https://docs.rs/tauri/latest/tauri/path/struct.PathResolver.html +[`PathResolver::resolve`]: https://docs.rs/tauri/latest/tauri/path/struct.PathResolver.html#method.resolve +[`resolveResource`]: https://tauri.app/reference/javascript/api/namespacepath/#resolveresource [`app`]: https://docs.rs/tauri/latest/tauri/struct.App.html [`apphandle`]: https://docs.rs/tauri/latest/tauri/struct.AppHandle.html -[`plugin-fs`]: /plugin/file-system/ +[`fs` plugin]: /plugin/file-system/ +[`FsExt::fs`]: https://docs.rs/tauri-plugin-fs/latest/tauri_plugin_fs/trait.FsExt.html#tymethod.fs [basic setup]: /plugin/file-system/#setup [Scope Permissions]: /plugin/file-system/#scopes [scopes]: /plugin/file-system/#scopes +[`opener` plugin]: /plugin/opener/