You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Update 2026-05-12 We have an experiment in Flutter (flutter/flutter#146264), but not yet in Dart standalone.
Problems:
Flutter has an assets system, Dart does not. This is problematic when wanting to access bytes at runtime in a platform independent way.
Various types of assets could benefit from minifying and/or tree-shaking. For example a translation package might only want include translations in a translations.json asset with keys that are actually reachable from Dart code after Dart AOT optimizations.
If we introduce something to support a featuire like AssetBundle, we should consider doing something fairly simple and low-level, that a more complicated class like AssetBundle can be designed on top of.
We used to have Resource, which was removed because it didn't work with AoT compilation. It was a way to access files through package: URIs, which only makes sense when running in the same environment as compilation.
We'll need some abstraction between (location of) the raw bytes and the runtime system, which allows for including the resources in the deployment unit, and which can be tree-shaken to avoid including unnecessary resources.
If it's something that independent packages can use to get their own resources included in programs that use the package, then there also needs to be some kind of name-spacing — which is where using package: URIs worked so well, it clearly assigns a unique prefix to any resource introduced by a package.
@lrhn : Yes, the idea is to introduce an interface which other frameworks can implement themselves, such as the already existing implementation from Flutter. The implementation to allow dart build to ship assets can be (hopefully) fairly simple.
Regarding the package: URIs, this is also how Flutter does it I believe, at least for images.
For example when I compile my project using dart compile exe project.dart and add files that are used in the project these files are not added to the compiled file, it is a problem if I want my executable to work as a service on any computer
/// A resource contanining a sequence of bytes.abstractfinalclassByteResourceimplementsResource<Uint8List> {
externalconstfactoryBytesResource(String url);
/// The length in bytes of this resource.Future<int> get length;
/// An unmodifiable view of the bytes of the resource.Future<Uint8List> load();
/// An unmodifiable buffer of the bytes of the resource. /// /// Can then be viewed as any type of typed data, like /// ```dart /// var doubles = (await res.loadBuffer()).asFloat64List(); /// ```Future<ByteBuffer> loadBuffer();
/// Read the resource into an existing byte list.Future<Uint8List> loadInto(Uint8List target, [int offset =0]);
/// Read a range of the resource into an existing byte list.Future<Uint8List> loadRangeInto(int start, int end, Uint8List target, [int offset =0]);
}
/// A resource containing a Dart [String].abstractfinalclassStringResourceimplementsResource<String> {
/// A string resource, loaded from [url] and decoded using [encoding]. /// /// Encoding *must* be one of [utf8], [latin1] or [ascii].// (TODO: Add `utf16` decoder, then also allow `utf16`, `utf16.littleEndian` and `utf16.bigEndian`.)externalconstfactoryStringResource(String url, [Encoding encoding = utf8]);
/// Length of the string, in UTF-16 code units.Future<int> get length;
/// Load the content of the string.Future<String> load();
}
/// A resource containing JSON data.abstractfinalclassJsonResourceimplementsResource<Object?> {
/// The [url] must refer to a file contining JSON source, which must be UTF-8 encoded.externalconstfactoryJsonResource(String url);
/// Read the JSON file into an unmodifiable Dart JSON value. /// /// A JSON value is either a `List` of JSON values, a `Map` from strings to JSON values, /// or a simple [String], [num], [bool] or `null` value.Future<Object?> load();
}
Then you specify a resource by declaring a constant:
It'll be a compile-time error to use the constructor in a non-const way.
The constants can be tree-shaken as any other constant.
Whichever constanta are still left after compilation, the files their urls point to are included in the distributable, in a way such that myStringFromFile.load can load it. The compiler and runtime gets to decide how and where.
Data can be put into the .data segment of the executable, as unmodifiable, if that helps.
It's up to the runtime to decide whether to cache the file contents on first load or not, or for how long, which format the content is stored in.
For example, it can be compressed. JSON can be massively compressed if it has the same structure many times, and since we control the format, we can parse the file as JSON at compile time, store it in a specialized binary format, and read it back from there (possibly even providing a cheap view on top of the compressed structure, instead of building a a structure using normal Dart objects.)
The one thing I'd consider is whether to support synchronous access.
I'd probably have separate resource classes like StringSyncResource for that, with a synchronous load method. Then the compiler/linker can decide how that's best implemented.
For the web, async resources can be lazy-loaded, while sync resources must be part of the initial deployment.
/// A resource contanining a sequence of bytes.abstractfinalclassByteResourceimplementsResource<Uint8List> {
externalconstfactoryBytesResource(String url);
/// The length in bytes of this resource.Future<int> get length;
/// An unmodifiable view of the bytes of the resource.Future<Uint8List> load();
/// An unmodifiable buffer of the bytes of the resource. /// /// Can then be viewed as any type of typed data, like /// ```dart /// var doubles = (await res.loadBuffer()).asFloat64List(); /// ```Future<ByteBuffer> loadBuffer();
/// Read the resource into an existing byte list.Future<Uint8List> loadInto(Uint8List target, [int offset =0]);
/// Read a range of the resource into an existing byte list.Future<Uint8List> loadRangeInto(int start, int end, Uint8List target, [int offset =0]);
}
/// A resource containing a Dart [String].abstractfinalclassStringResourceimplementsResource<String> {
/// A string resource, loaded from [url] and decoded using [encoding]. /// /// Encoding *must* be one of [utf8], [latin1] or [ascii].// (TODO: Add `utf16` decoder, then also allow `utf16`, `utf16.littleEndian` and `utf16.bigEndian`.)externalconstfactoryStringResource(String url, [Encoding encoding = utf8]);
/// Length of the string, in UTF-16 code units.Future<int> get length;
/// Load the content of the string.Future<String> load();
}
/// A resource containing JSON data.abstractfinalclassJsonResourceimplementsResource<Object?> {
/// The [url] must refer to a file contining JSON source, which must be UTF-8 encoded.externalconstfactoryJsonResource(String url);
/// Read the JSON file into an unmodifiable Dart JSON value. /// /// A JSON value is either a `List` of JSON values, a `Map` from strings to JSON values, /// or a simple [String], [num], [bool] or `null` value.Future<Object?> load();
}
Then you specify a resource by declaring a constant:
It'll be a compile-time error to use the constructor in a non-const way. The constants can be tree-shaken as any other constant.
Whichever constanta are still left after compilation, the files their urls point to are included in the distributable, in a way such that myStringFromFile.load can load it. The compiler and runtime gets to decide how and where. Data can be put into the .data segment of the executable, as unmodifiable, if that helps.
It's up to the runtime to decide whether to cache the file contents on first load or not, or for how long, which format the content is stored in. For example, it can be compressed. JSON can be massively compressed if it has the same structure many times, and since we control the format, we can parse the file as JSON at compile time, store it in a specialized binary format, and read it back from there (possibly even providing a cheap view on top of the compressed structure, instead of building a a structure using normal Dart objects.)
The one thing I'd consider is whether to support synchronous access. I'd probably have separate resource classes like StringSyncResource for that, with a synchronous load method. Then the compiler/linker can decide how that's best implemented. For the web, async resources can be lazy-loaded, while sync resources must be part of the initial deployment.
@mosuem I believe we should not only have Uint8List as a type, but also (Pointer<Void>, int lengthInBytes). (And Future<Uint8List> and Future<(Pointer<Void>, int)>.
Reasoning: One might want to pass a data asset to native code. And if the Dart type is Uint8List, we can't see that it's an external typed data (and we must assume that it could be in the Dart heap and be moved by GC).
Data asset as File ?
Some C APIs actually want a file path instead of the buffer of bytes. I'm not entirely sure if we are able to support this. We could give a file path if, and only if the embedder actually has the file on disk. If the asset is embedded in something else (a zip file, a data section in assembly, ...) there wont be a file path. So the only thing someone could possibly do is write the file manually to disk with File.writeFromBytes. Also, if we ever get data assets on the web backends, then there is no File type at all.
Of course, having to manually write the file to disk if the embedder already has the file on disk is maybe also undesirable. So maybe we should consider allowing File and Future<File>. And the embedder API should then have DataAsset_fopen and char* DataAsset_file_path. With the latter one returning the symlink resolved absolute path if it exists and otherwise nullptr.
(And then for the web backends we might be interested in somehow exposing a file to C code compiled to WASM via the WASI interface.)
API options
Just to pen down the thoughts about the API @mosuem and I have discussed the last couple of days. We have two options:
classAssetBundle {
externalUint8ListloadBytesSync(assetId);
externalFuture<Uint8List> loadBytes(assetId);
external (Pointer<Void>, int) loadBytesAsPointerSync(assetId);
externalFuture<(Pointer<Void>, int)> loadBytesAsPointer(assetId);
externalboolavailableAsFile(assetId);
// No need for a sync and async variant.// Non-null if `availableAsFile` returns true.externalFile?asFile(assetId);
}
pro: discoverability (auto complete on methods)
pro: enables dynamic assetIds (the link hook writer has to make sure that these assets are bundled)
con: not aligned with @Native external functions/symbols
con: File in the API makes it incompatible with non-VM
@Data(assetId)
externalUInt8ListloadMyAsset();
@Data(assetId)
externalFuture<UInt8List> loadMyAsset();
@Data(assetId)
external (Pointer<Void>, int) loadMyAsset();
@Data(assetId)
externalFuture<(Pointer<Void>, int)> loadMyAsset();
@Data(assetId)
externalFile?get myAsset;
// On the web backends ?@Data(assetId)
externalBlob?get myAsset;
pro: aligned with @Native external functions/symbols
pro: does not conflict with data assets on the web.
con: does not enable dynamic asset Ids
con: discoverability
We can have option 3, which patches up requirement for having dynamic assets by making assetId optional in the @Data annotation, and allowing an argument on the definition:
con: File in the API makes it incompatible with non-VM
That could be helped by having different AssetBundles with different APIs
classAssetBundle {
externalUint8ListloadBytesSync(assetId);
externalFuture<Uint8List> loadBytes(assetId);
}
classPointerAssetBundle {
external (Pointer<Void>, int) loadBytesAsPointerSync(assetId);
externalFuture<(Pointer<Void>, int)> loadBytesAsPointer(assetId);
}
classFileAssetBundle {
externalboolavailableAsFile(assetId);
// No need for a sync and async variant.// Non-null if `availableAsFile` returns true.externalFile?asFile(assetId);
}
I guess these bundles should then live in different places, and the ones that do not use File due to being available on the web, cannot refer in doc comments to the one with File.
Con: bad discoverability due to possible ways to interact being spread out.
Side question for the AssetBundles, should all methods be static? Otherwise we can have dynamic invocations on an object. With static method we know if there are non-const invocations, with instance methods we have to always assume there are dynamic invocations. (Hence why half of the FFI is static methods and the other half is extension methods which are also static.)
Then you need to invoke the Asset constructor to have an asset, and const invocations are easily found.
But that's not much different from just a top-level external ByteBuffer loadAssetBytes(String key, {bool readOnly = true});
The loadBytes -> ByteBuffer is the only operation we need, assuming all assets are byte sequences.
However, consider if assets could be typed:
a "byte asset" is a (well-aligned) chunk of memory, but
a "string asset" is something the system can load as a string, without you needing to know how it's stored, that's between the linker and the runtime system doing what's most efficient. (Fx, store as UTF-16, load by creating external string backed by loaded bytes.)
Then we will need a load-function per asset kind.
I'm a little worried that the keys are just strings, but I guess the linker will complain if two assets have the same name, and any statically detectable asset access can be checked against available assets at compile-time.
I'm a little worried that the keys are just strings, but I guess the linker will complain if two assets have the same name, and any statically detectable asset access can be checked against available assets at compile-time.
I would also like typed keys, something like an enum, but this would require codegen when adding an asset to be able to use it in the application. Having typed keys with a const constructor would help in finding usages of asset loading for tracking in resources.json... :)
Still means that someone can access an asset of another package if they know the name.
That is a feature for native code assets. We want to avoid bundling two identical dynamic libraries. Also for native code, if static linking is used, all code lives in the same namespace. So trying to create encapsulation would start to create weird behavior when a different linking mode is used.
I am not entirely sure if we should have the same goal with data assets or not.
Maybe we should consider making an asset with id package:my_package/foo.txt accessible from all code, but an asset with id package:my_package:_foo.txt only accessible from Dart code in package:my_package.
Is that optional, or does the framework providing the assets enforce that the string has that form?
Declaring assets in build and link hooks does enforce this. The usage from Dart does not (yet).
only accessible from Dart code in package:my_package
That would require a loadBytes(String key) to check where it's called from. Let's just not do that.
(I'd argue that it's not enough to check the top of the stack, you'd have to check the entire stack to see if the call was initiated by code from that package, otherwise you cant use helper libraries. And that's assuming you can even say which package code comes from after compiling.)
Giving a warning for a constant key string is fine. Trying to enforce runtime security is not worth it.
Activity
lrhn commentedon Nov 13, 2023
If we introduce something to support a featuire like
AssetBundle, we should consider doing something fairly simple and low-level, that a more complicated class likeAssetBundlecan be designed on top of.We used to have
Resource, which was removed because it didn't work with AoT compilation. It was a way to access files throughpackage:URIs, which only makes sense when running in the same environment as compilation.We'll need some abstraction between (location of) the raw bytes and the runtime system, which allows for including the resources in the deployment unit, and which can be tree-shaken to avoid including unnecessary resources.
If it's something that independent packages can use to get their own resources included in programs that use the package, then there also needs to be some kind of name-spacing — which is where using
package:URIs worked so well, it clearly assigns a unique prefix to any resource introduced by a package.mosuem commentedon Nov 13, 2023
@lrhn : Yes, the idea is to introduce an interface which other frameworks can implement themselves, such as the already existing implementation from Flutter. The implementation to allow
dart buildto ship assets can be (hopefully) fairly simple.Regarding the
package:URIs, this is also how Flutter does it I believe, at least for images.xvrh commentedon Nov 13, 2023
That would be SO cool if we could tree-shake assets in flutter (flutter/flutter#64106)
It probably requires to use a generated ID in the code instead of a dynamic string to reference the asset (this is also a win btw).
SantiiRepair commentedon Dec 5, 2023
So what could be a solution?
For example when I compile my project using
dart compile exe project.dartand add files that are used in the project these files are not added to the compiled file, it is a problem if I want my executable to work as a service on any computerlrhn commentedon Dec 5, 2023
My, very simplistic, suggestion would be:
Introduce a
Resourcesuper-class:Introduce a set of specialized resource kinds:
Then you specify a resource by declaring a constant:
It'll be a compile-time error to use the constructor in a non-
constway.The constants can be tree-shaken as any other constant.
Whichever constanta are still left after compilation, the files their
urls point to are included in the distributable, in a way such thatmyStringFromFile.loadcan load it. The compiler and runtime gets to decide how and where.Data can be put into the
.datasegment of the executable, as unmodifiable, if that helps.It's up to the runtime to decide whether to cache the file contents on first load or not, or for how long, which format the content is stored in.
For example, it can be compressed. JSON can be massively compressed if it has the same structure many times, and since we control the format, we can parse the file as JSON at compile time, store it in a specialized binary format, and read it back from there (possibly even providing a cheap view on top of the compressed structure, instead of building a a structure using normal Dart objects.)
The one thing I'd consider is whether to support synchronous access.
I'd probably have separate resource classes like
StringSyncResourcefor that, with a synchronousloadmethod. Then the compiler/linker can decide how that's best implemented.For the web, async resources can be lazy-loaded, while sync resources must be part of the initial deployment.
SantiiRepair commentedon Dec 5, 2023
I think it is the same as
Isolatedcharkes commentedon May 24, 2024
Data asset as
(Pointer<Void>, int lengthInBytes)@mosuem I believe we should not only have
Uint8Listas a type, but also(Pointer<Void>, int lengthInBytes). (AndFuture<Uint8List>andFuture<(Pointer<Void>, int)>.Reasoning: One might want to pass a data asset to native code. And if the Dart type is
Uint8List, we can't see that it's an external typed data (and we must assume that it could be in the Dart heap and be moved by GC).Data asset as
File?Some C APIs actually want a file path instead of the buffer of bytes. I'm not entirely sure if we are able to support this. We could give a file path if, and only if the embedder actually has the file on disk. If the asset is embedded in something else (a zip file, a data section in assembly, ...) there wont be a file path. So the only thing someone could possibly do is write the file manually to disk with
File.writeFromBytes. Also, if we ever get data assets on the web backends, then there is noFiletype at all.Of course, having to manually write the file to disk if the embedder already has the file on disk is maybe also undesirable. So maybe we should consider allowing
FileandFuture<File>. And the embedder API should then haveDataAsset_fopenandchar* DataAsset_file_path. With the latter one returning the symlink resolved absolute path if it exists and otherwisenullptr.(And then for the web backends we might be interested in somehow exposing a file to C code compiled to WASM via the WASI interface.)
API options
Just to pen down the thoughts about the API @mosuem and I have discussed the last couple of days. We have two options:
@Native externalfunctions/symbolsFilein the API makes it incompatible with non-VM@Native externalfunctions/symbolsWe can have option 3, which patches up requirement for having dynamic assets by making
assetIdoptional in the@Dataannotation, and allowing an argument on the definition:@Native externalfunctions/symbolsmosuem commentedon May 24, 2024
That could be helped by having different
AssetBundles with different APIsdcharkes commentedon May 24, 2024
I guess these bundles should then live in different places, and the ones that do not use
Filedue to being available on the web, cannot refer in doc comments to the one withFile.Side question for the
AssetBundles, should all methods bestatic? Otherwise we can have dynamic invocations on an object. With static method we know if there are non-const invocations, with instance methods we have to always assume there are dynamic invocations. (Hence why half of the FFI is static methods and the other half is extension methods which are also static.)lrhn commentedon May 29, 2024
Another possible API is:
Then you need to invoke the
Assetconstructor to have an asset, andconstinvocations are easily found.But that's not much different from just a top-level
external ByteBuffer loadAssetBytes(String key, {bool readOnly = true});The
loadBytes->ByteBufferis the only operation we need, assuming all assets are byte sequences.However, consider if assets could be typed:
Then we will need a load-function per asset kind.
I'm a little worried that the keys are just strings, but I guess the linker will complain if two assets have the same name, and any statically detectable asset access can be checked against available assets at compile-time.
mosuem commentedon May 29, 2024
I would also like typed keys, something like an enum, but this would require codegen when adding an asset to be able to use it in the application. Having typed keys with a const constructor would help in finding usages of asset loading for tracking in
resources.json... :)I added the suggestion to go/dart-assets.
dcharkes commentedon May 30, 2024
The are namespaces per package
package:my_package/foo.txt.That is a good idea, we should do that once we have the
resources.jsonintegration.lrhn commentedon May 31, 2024
Is that optional, or does the framework providing the assets enforce that the string has that form?
If the latter, can you compile a file with assets, if that file doesn't have a package:URI, or is not in a package directory?
(Still means that someone can access an asset of another package if they know the name. Probably no way to avoid that without some trickery.)
dcharkes commentedon May 31, 2024
That is a feature for native code assets. We want to avoid bundling two identical dynamic libraries. Also for native code, if static linking is used, all code lives in the same namespace. So trying to create encapsulation would start to create weird behavior when a different linking mode is used.
I am not entirely sure if we should have the same goal with data assets or not.
Maybe we should consider making an asset with id
package:my_package/foo.txtaccessible from all code, but an asset with idpackage:my_package:_foo.txtonly accessible from Dart code inpackage:my_package.Declaring assets in build and link hooks does enforce this. The usage from Dart does not (yet).
lrhn commentedon May 31, 2024
That would require a
loadBytes(String key)to check where it's called from. Let's just not do that.(I'd argue that it's not enough to check the top of the stack, you'd have to check the entire stack to see if the call was initiated by code from that package, otherwise you cant use helper libraries. And that's assuming you can even say which package code comes from after compiling.)
Giving a warning for a constant key string is fine. Trying to enforce runtime security is not worth it.
YousefAK009 commentedon Mar 23, 2025
What is the current status of this issue?
mosuem commentedon Mar 24, 2025
Work is in progress - @mkustermann is prototyping an implementation. The API will probably be something like https://dart-review.googlesource.com/c/sdk/+/412920.