In Flutter, what do asset transformers declared in pubspec.yaml do, and how would you add one to shrink bundled JSON question packs?
answer
- path: plus transformers: in pubspec
- dart run <package> --input --output
- chained in declared order
- non-zero exit fails the build
- FLUTTER_BUILD_MODE in the environment
basics
~20 sAn asset transformer is a Dart command-line package the flutter tool runs on an asset while bundling it, writing a transformed file under the same key. Declare it per asset with path: and transformers: in pubspec.yaml, and add the package as a dev dependency.
solid answer
~40 sSince Flutter 3.22 an entry under `flutter: assets:` can be a map: `path:` plus `transformers:`, each item naming a `package:` and optional `args:`. During the build the tool runs `dart run <package> --input=<source> --output=<target>` plus the args, with `FLUTTER_BUILD_MODE` set to the build mode, and bundles the output under the original key. Transformers chain in declared order, and a non-zero exit or a missing output file fails the build with the transformer's output. The package must be resolvable by `dart run`, so it goes in `dev_dependencies`. The standard example is `vector_graphics_compiler`, which precompiles SVGs; for question packs I would write a small local package that minifies JSON and apply it to `path: assets/questions/`.
code
dart · 22 linesimport 'dart:convert';
import 'dart:io';
void main(List<String> args) {
String? valueOf(String name) {
final prefix = '--$name=';
for (final arg in args) {
if (arg.startsWith(prefix)) return arg.substring(prefix.length);
}
return null;
}
final input = valueOf('input');
final output = valueOf('output');
if (input == null || output == null) {
stderr.writeln('json_minifier: expected --input=<file> and --output=<file>');
exit(64);
}
final decoded = jsonDecode(File(input).readAsStringSync());
File(output).writeAsStringSync(jsonEncode(decoded));
}go deeper
Know that pubspec.yaml can attach a transformer package to an asset path and that the app still loads the asset by its original key.
Explain the invocation, dart run with --input and --output, chaining order, and why the package belongs in dev_dependencies.
Build a deterministic transformer, use FLUTTER_BUILD_MODE to keep debug builds fast, and make failures clear on CI.
Decide which content processing belongs in the build, which in a separate content pipeline, and how to keep bundled output traceable to its source.
## What an asset transformer is An **asset transformer** is a Dart **command-line program**, published as a package, that the `flutter` tool runs on an asset **at build time**, before the asset is written into the bundle. The app still asks for the original key at runtime; it simply receives the transformed bytes. Transformers run on the developer's or CI machine, never on the device. Typical uses: - **`vector_graphics_compiler`** precompiles an SVG into a compact binary that the `vector_graphics` package draws without parsing XML at runtime. - Minifying or stripping JSON, such as removing editor notes from trivia question packs. - Optimising images or converting formats as part of the build. ## Declaring a transformer Instead of a plain string, the asset entry becomes a map with a `path` and a `transformers` list: ```yaml dev_dependencies: json_minifier: path: tool/json_minifier flutter: assets: - path: assets/questions/ transformers: - package: json_minifier - path: assets/logo.svg transformers: - package: vector_graphics_compiler args: ['--tessellate'] ``` - `path` can be a single file or a directory ending in `/`; for a directory the transformers apply to each file in it. - `package` names a package that `dart run` can execute, which means it must be a dependency of the app; `dev_dependencies` is the usual home, since nothing ships in the app's code. - `args` is an optional list of extra command-line arguments. - Several transformers **chain in the order declared**: each receives the previous one's output. The same map form also accepts `platforms:` (Flutter 3.41+, to bundle an asset only for some platforms) and `flavors:` (to tie assets to build flavors). ## How the flutter tool runs it For each asset and each transformer, the tool runs: ```bash dart run json_minifier --input=/path/to/source.json --output=/path/to/transformed.json ``` 1. Any `args` are appended after `--input` and `--output`. 2. The environment variable **`FLUTTER_BUILD_MODE`** is set to the build mode's command-line name (`debug`, `profile` or `release`), so a transformer can, for example, skip expensive work in debug builds. 3. A **non-zero exit code** fails the build, and the error message includes the command and the transformer's stdout and stderr. 4. If the transformer exits cleanly but writes **no output file**, the build fails as well. ## Writing a transformer package A transformer is an ordinary Dart package with an executable in `bin/` named after the package, so that `dart run <package>` finds it. Its `main` reads the `--input` and `--output` arguments, processes the file and writes the result. The code example is a minimal JSON minifier: it re-encodes the parsed JSON without whitespace. Keep transformers deterministic, since the same input should always produce the same bundled bytes, and print a clear message to `stderr` before exiting non-zero. ## The SVG case end to end The documented example pairs a transformer with a runtime package: 1. `pubspec.yaml` declares `- path: assets/logo.svg` with `transformers: - package: vector_graphics_compiler`. 2. At build time the compiler turns the SVG into a binary vector format and the tool bundles that output under the key `assets/logo.svg`. 3. At runtime the app draws it with the `vector_graphics` package, `VectorGraphic(loader: AssetBytesLoader('assets/logo.svg'))`, which reads the precompiled bytes instead of parsing XML. Choosing between runtime SVG parsing and precompiled vectors is a separate design question; the transformer is only the build-time half. ## Debugging a transformer - Run the same command by hand, `dart run <package> --input=... --output=...`, from the app's directory to see its output directly. - Compare the bundled file with the source when behaviour differs between the repository copy and the app. ## Consequences to keep in mind | Aspect | Effect | |---|---| | Runtime key | unchanged; code keeps loading `assets/questions/history.json` | | Build time | grows with the number of assets and the transformer's cost | | Debugging | the bundled file differs from the one in the repository | | CI | the transformer's dependencies must resolve on the build machine | ## When not to use one - Work that depends on the device, such as screen density, belongs at runtime or in resolution variants. - Content that changes after release cannot be transformed by the build; it needs a download pipeline. - A one-off conversion is simpler to run once and commit than to repeat on every build.
- What does the app's code change after adding the minifying transformer to assets/questions/?Nothing. The transformer's output is bundled under the original key, so `rootBundle.loadString('assets/questions/history.json')` keeps working and simply receives the smaller file. Only the build changes: it now runs the transformer for every file in that directory.
- How can a transformer do less work in debug builds?Read the `FLUTTER_BUILD_MODE` environment variable, which the flutter tool sets to `debug`, `profile` or `release` when it runs the transformer. A transformer can copy the input unchanged in debug mode for fast iteration and do the expensive optimisation only for `release`.
saying these in an interview costs you the question
- Asset transformers run on the device when the asset is first loaded.
- After transformation the app must load the asset under a new key.
- Transformers listed for one asset run in parallel on the original file.
- A failing transformer is skipped and the original file is bundled instead.
- The transformer package must be a regular dependency shipped in the app.