Skip to content

Commit d459e7c

Browse files
Rework resource guide (#3485)
Co-authored-by: FabianLars <[email protected]> Co-authored-by: FabianLars <[email protected]>
1 parent 6fda8d1 commit d459e7c

File tree

2 files changed

+275
-26
lines changed

2 files changed

+275
-26
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
"prettier.documentSelectors": ["**/*.astro"],
44
"[astro]": {
55
"editor.defaultFormatter": "astro-build.astro-vscode"
6+
},
7+
"[mdx]": {
8+
"editor.defaultFormatter": "esbenp.prettier-vscode"
69
}
710
}

src/content/docs/develop/resources.mdx

Lines changed: 272 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,130 @@ title: Embedding Additional Files
33
i18nReady: true
44
---
55

6+
import { Tabs, TabItem } from '@astrojs/starlight/components';
7+
68
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`.
79

8-
To bundle the files of your choice, you can add the `resources` property to the `bundle` object in your `tauri.conf.json` file.
10+
## Configuration
911

10-
See more about `tauri.conf.json` configuration [here][tauri.bundle].
12+
To bundle the files of your choice, add the `resources` property to the `bundle` object in your `tauri.conf.json` file.
1113

12-
`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.
14+
To include a list of files:
1315

14-
Here is a sample to illustrate the configuration. This is not a complete `tauri.conf.json` file:
16+
<Tabs syncKey="explanation">
17+
<TabItem label="Syntax">
1518

1619
```json title=tauri.conf.json
1720
{
1821
"bundle": {
1922
"resources": [
23+
"./path/to/some-file.txt",
24+
"/absolute/path/to/textfile.txt",
25+
"../relative/path/to/jsonfile.json",
26+
"some-folder/",
27+
"resources/**/*.md"
28+
]
29+
}
30+
}
31+
```
32+
33+
</TabItem>
34+
<TabItem label="Explanation">
35+
36+
```json title=tauri.conf.json5
37+
{
38+
"bundle": {
39+
"resources": [
40+
// Will be placed to `$RESOURCE/path/to/some-file.txt`
41+
"./path/to/some-file.txt",
42+
43+
// The root in an abosolute path will be replaced by `_root_`,
44+
// so `textfile.txt` will be placed to `$RESOURCE/_root_/absolute/path/to/textfile.txt`
2045
"/absolute/path/to/textfile.txt",
21-
"relative/path/to/jsonfile.json",
22-
"resources/**/*"
46+
47+
// `..` in a relative path will be replaced by `_up_`,
48+
// so `jsonfile.json` will be placed to `$RESOURCE/_up_/relative/path/to/textfile.txt`,
49+
"../relative/path/to/jsonfile.json",
50+
51+
// If the path is a directory, the entire directory will be copied to the `$RESOURCE` directory,
52+
// preserving the original structures, for example:
53+
// - `some-folder/file.txt` -> `$RESOURCE/some-folder/file.txt`
54+
// - `some-folder/another-folder/config.json` -> `$RESOURCE/some-folder/another-folder/config.json`
55+
// This is the same as `some-folder/**/*`
56+
"some-folder/",
57+
58+
// You can also include multiple files at once through glob patterns.
59+
// All the `.md` files inside `resources` will be placed to `$RESOURCE/resources/`,
60+
// preserving their original directory structures, for example:
61+
// - `resources/index.md` -> `$RESOURCE/resources/index.md`
62+
// - `resources/docs/setup.md` -> `$RESOURCE/resources/docs/setup.md`
63+
"resources/**/*.md"
2364
]
2465
}
2566
}
2667
```
2768

28-
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:
69+
</TabItem>
70+
</Tabs>
71+
72+
The bundled files will be in `$RESOURCES/` with the original directory structure preserved,
73+
for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`
74+
75+
To fine control where the files will get copied to, use a map instead:
76+
77+
<Tabs syncKey="explanation">
78+
<TabItem label="Syntax">
2979

3080
```json title=tauri.conf.json
3181
{
3282
"bundle": {
3383
"resources": {
3484
"/absolute/path/to/textfile.txt": "resources/textfile.txt",
3585
"relative/path/to/jsonfile.json": "resources/jsonfile.json",
36-
"resources/**/*": "resources/"
86+
"resources/": "",
87+
"docs/**/*md": "website-docs/"
3788
}
3889
}
3990
}
4091
```
4192

42-
:::note
93+
</TabItem>
94+
<TabItem label="Explanation">
95+
96+
```json title=tauri.conf.json5
97+
{
98+
"bundle": {
99+
"resources": {
100+
// `textfile.txt` will be placed to `$RESOURCE/resources/textfile.txt`
101+
"/absolute/path/to/textfile.txt": "resources/textfile.txt",
43102

44-
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"`.
103+
// `jsonfile.json` will be placed to `$RESOURCE/resources/jsonfile.json`
104+
"relative/path/to/jsonfile.json": "resources/jsonfile.json",
45105

46-
:::
106+
// Copy the entire directory to `$RESOURCE`, preserving the original structures,
107+
// the target is "" which means it will be placed directly in the resource directory `$RESOURCE`, for example:
108+
// - `resources/file.txt` -> `$RESOURCE/file.txt`
109+
// - `resources/some-folder/config.json` -> `$RESOURCE/some-folder/config.json`
110+
"resources/": "",
111+
112+
// When using glob patterns, the behavior is different from the list one,
113+
// all the matching files will be placed to the target directory without preserving the original file structures
114+
// for example:
115+
// - `docs/index.md` -> `$RESOURCE/website-docs/index.md`
116+
// - `docs/plugins/setup.md` -> `$RESOURCE/website-docs/setup.md`
117+
"docs/**/*md": "website-docs/"
118+
}
119+
}
120+
}
121+
```
47122

48-
## Source path syntax
123+
</TabItem>
124+
</Tabs>
125+
126+
To learn about where `$RESOURCE` resolves to on each platforms, see the documentation of [`resource_dir`]
127+
128+
<details>
129+
<summary>Source path syntax</summary>
49130

50131
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.
51132

@@ -56,19 +137,111 @@ In the following explanations "target resource directory" is either the value af
56137
- `"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.
57138
- `"dir/**/**`: throws an error because `**` only matches directories and therefore no files can be found.
58139

59-
## Accessing files in Rust
140+
</details>
141+
142+
## Resolve resource file paths
143+
144+
To resolve the path for a resource file, instead of manually calculating the path, use the following APIs
60145

61-
In this example we want to bundle additional i18n json files that look like this:
146+
<Tabs syncKey="lang">
147+
<TabItem label="Rust">
62148

63-
```json title=de.json
149+
On the Rust side, you need an instance of the [`PathResolver`] which you can get from [`App`] and [`AppHandle`],
150+
then call [`PathResolver::resolve`]:
151+
152+
```rust
153+
tauri::Builder::default()
154+
.setup(|app| {
155+
let resource_path = app.path().resolve("lang/de.json", BaseDirectory::Resource)?;
156+
Ok(())
157+
})
158+
```
159+
160+
To use it in a command:
161+
162+
```rust
163+
#[tauri::command]
164+
fn hello(handle: tauri::AppHandle) {
165+
let resource_path = handle.path().resolve("lang/de.json", BaseDirectory::Resource)?;
166+
}
167+
```
168+
169+
</TabItem>
170+
<TabItem label="JavaScript">
171+
172+
To resolve the path in JavaScript, use [`resolveResource`]:
173+
174+
```javascript
175+
import { resolveResource } from '@tauri-apps/api/path';
176+
const resourcePath = await resolveResource('lang/de.json');
177+
```
178+
179+
</TabItem>
180+
</Tabs>
181+
182+
### Path syntax
183+
184+
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`,
185+
or a paths like `../relative/folder/toml_file.toml` that resolves to `$RESOURCE/_up_/relative/folder/toml_file.toml`,
186+
these APIs use the same rules as you write `tauri.conf.json > bundle > resources`, for example:
187+
188+
```json title=tauri.conf.json
189+
{
190+
"bundle": {
191+
"resources": ["folder/json_file.json", "../relative/folder/toml_file.toml"]
192+
}
193+
}
194+
```
195+
196+
```rust
197+
let json_path = app.path().resolve("folder/json_file.json", BaseDirectory::Resource)?;
198+
let toml_path = app.path().resolve("../relative/folder/toml_file.toml", BaseDirectory::Resource)?;
199+
```
200+
201+
### Android
202+
203+
Currently the resources are stored in the APK as assets so the return value of those APIs are not normal file system paths,
204+
we use a special URI prefix `asset://localhost/` here that can be used with the [`fs` plugin],
205+
with that, you can read the files through [`FsExt::fs`] like this:
206+
207+
```rust
208+
let resource_path = app.path().resolve("lang/de.json", BaseDirectory::Resource).unwrap();
209+
let json = app.fs().read_to_string(&resource_path);
210+
```
211+
212+
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]
213+
214+
## Reading resource files
215+
216+
In this example we want to bundle additional i18n json files like this:
217+
218+
```
219+
.
220+
├── src-tauri/
221+
│ ├── tauri.conf.json
222+
│ ├── lang/
223+
│ │ ├── de.json
224+
│ │ └── en.json
225+
│ └── ...
226+
└── ...
227+
```
228+
229+
```json title=tauri.conf.json
230+
{
231+
"bundle": {
232+
"resources": ["lang/*"]
233+
}
234+
}
235+
```
236+
237+
```json title=lang/de.json
64238
{
65239
"hello": "Guten Tag!",
66240
"bye": "Auf Wiedersehen!"
67241
}
68242
```
69243

70-
In this case, we store these files in a `lang` directory next to the `tauri.conf.json`.
71-
For this we add `"lang/*"` to `resources` as shown above.
244+
### Rust
72245

73246
On the Rust side, you need an instance of the [`PathResolver`] which you can get from [`App`] and [`AppHandle`]:
74247

@@ -79,8 +252,11 @@ tauri::Builder::default()
79252
// `tauri.conf.json > bundle > resources`
80253
let resource_path = app.path().resolve("lang/de.json", BaseDirectory::Resource)?;
81254

82-
let file = std::fs::File::open(&resource_path).unwrap();
83-
let lang_de: serde_json::Value = serde_json::from_reader(file).unwrap();
255+
let json = std::fs::read_to_string(&resource_path).unwrap();
256+
// Or when dealing with Android, use the file system plugin instead
257+
// let json = app.fs().read_to_string(&resource_path);
258+
259+
let lang_de: serde_json::Value = serde_json::from_str(json).unwrap();
84260

85261
// This will print 'Guten Tag!' to the terminal
86262
println!("{}", lang_de.get("hello").unwrap());
@@ -94,18 +270,21 @@ tauri::Builder::default()
94270
fn hello(handle: tauri::AppHandle) -> String {
95271
let resource_path = handle.path().resolve("lang/de.json", BaseDirectory::Resource)?;
96272

97-
let file = std::fs::File::open(&resource_path).unwrap();
98-
let lang_de: serde_json::Value = serde_json::from_reader(file).unwrap();
273+
let json = std::fs::read_to_string(&resource_path).unwrap();
274+
// Or when dealing with Android, use the file system plugin instead
275+
// let json = handle.fs().read_to_string(&resource_path);
276+
277+
let lang_de: serde_json::Value = serde_json::from_str(json).unwrap();
99278

100279
lang_de.get("hello").unwrap()
101280
}
102281
```
103282

104-
## Accessing files in JavaScript
283+
### JavaScript
105284

106-
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`]
285+
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].
107286

108-
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:
287+
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:
109288

110289
```json title=src-tauri/capabilities/default.json ins={8-9}
111290
{
@@ -135,11 +314,78 @@ const langDe = JSON.parse(await readTextFile(resourcePath));
135314
console.log(langDe.hello); // This will print 'Guten Tag!' to the devtools console
136315
```
137316

138-
[tauri.bundle]: /reference/config/#bundleconfig
317+
## Permissions
318+
319+
Since we replace `../` to `_up_` in relative paths and the root to `_root_` in abosolute paths when using a list,
320+
those files will be in sub folders inside the resource directory,
321+
to allow those paths in Tauri's [permission system](/security/capabilities/),
322+
use `$RESOURCE/**/*` to allow recursive access to those files
323+
324+
### Examples
325+
326+
With a file bundled like this:
327+
328+
```json title=tauri.conf.json
329+
{
330+
"bundle": {
331+
"resources": ["../relative/path/to/jsonfile.json"]
332+
}
333+
}
334+
```
335+
336+
To use it with the [`fs` plugin]:
337+
338+
```json title=src-tauri/capabilities/default.json ins={8-15}
339+
{
340+
"$schema": "../gen/schemas/desktop-schema.json",
341+
"identifier": "main-capability",
342+
"description": "Capability for the main window",
343+
"windows": ["main"],
344+
"permissions": [
345+
"core:default",
346+
"fs:allow-stat",
347+
"fs:allow-read-text-file",
348+
"fs:allow-resource-read-recursive",
349+
{
350+
"identifier": "fs:scope",
351+
"allow": ["$RESOURCE/**/*"],
352+
"deny": ["$RESOURCE/secret.txt"]
353+
}
354+
]
355+
}
356+
```
357+
358+
To use it with the [`opener` plugin]:
359+
360+
```json title=src-tauri/capabilities/default.json ins={8-15}
361+
{
362+
"$schema": "../gen/schemas/desktop-schema.json",
363+
"identifier": "main-capability",
364+
"description": "Capability for the main window",
365+
"windows": ["main"],
366+
"permissions": [
367+
"core:default",
368+
{
369+
"identifier": "opener:allow-open-path",
370+
"allow": [
371+
{
372+
"path": "$RESOURCE/**/*"
373+
}
374+
]
375+
}
376+
]
377+
}
378+
```
379+
380+
[`resource_dir`]: https://docs.rs/tauri/latest/tauri/path/struct.PathResolver.html#method.resource_dir
139381
[`pathresolver`]: https://docs.rs/tauri/latest/tauri/path/struct.PathResolver.html
382+
[`PathResolver::resolve`]: https://docs.rs/tauri/latest/tauri/path/struct.PathResolver.html#method.resolve
383+
[`resolveResource`]: https://tauri.app/reference/javascript/api/namespacepath/#resolveresource
140384
[`app`]: https://docs.rs/tauri/latest/tauri/struct.App.html
141385
[`apphandle`]: https://docs.rs/tauri/latest/tauri/struct.AppHandle.html
142-
[`plugin-fs`]: /plugin/file-system/
386+
[`fs` plugin]: /plugin/file-system/
387+
[`FsExt::fs`]: https://docs.rs/tauri-plugin-fs/latest/tauri_plugin_fs/trait.FsExt.html#tymethod.fs
143388
[basic setup]: /plugin/file-system/#setup
144389
[Scope Permissions]: /plugin/file-system/#scopes
145390
[scopes]: /plugin/file-system/#scopes
391+
[`opener` plugin]: /plugin/opener/

0 commit comments

Comments
 (0)