Skip to content

Commands isolate

albertoodev edited this page Aug 19, 2026 · 7 revisions

isolate: targeted profiling

isolate extracts rebuild scopes such as a Flutter State class or a BlocBuilder callback into standalone Dart files. The smaller files make it possible to profile one UI segment without running the rest of the application.

Usage

spm isolate --output-dir ./isolated_widgets /path/to/project
spm isolate -o ./isolation-output -j map.jsonl /path/to/project

Options and flags

Flag / Option Short Description
--output-dir <path> -o Required. Directory where the isolated files are saved.
--jsonl <path> -j Output JSONL mapping original source paths → isolated file paths.
--verbose -v Enable verbose logging of the isolation process.
<dir> [dir…] One or more directories to scan for rebuild scopes.

How it works

Isolation combines static analysis with source transformation.

1. Discovery

An AST visitor scans the target directories for rebuild boundaries:

  • Classes that extend State, ConsumerWidget, or HookConsumerWidget.
  • Anonymous builder functions passed to BlocBuilder, BlocSelector, BlocConsumer, Consumer, Selector, Obx, GetX, GetBuilder, or Observer.

2. Normalization and transplantation

The TransplantExtractor converts a discovered scope into a new, self-contained StatefulWidget (GeneratedWidget).

  • For a State class, SPM also finds its companion StatefulWidget to extract fields and constructors.
  • For a builder function, SPM extracts parameters such as state or model and converts them into fields on the new state.
  • context is never lifted, since State already supplies one. A source class that declares its own context field is skipped for the same reason: copying it across shadows State.context.

3. Transitive dependency resolution

To keep the generated file compilable, SPM recursively crawls the code for every referenced symbol:

  • Methods, fields, and getters used within the same class or file move into the generated file.
  • A custom widget, enum, or painter from another file is extracted along with its own dependencies.
  • Business logic, models, and services are left out; the generated target may need mocks or values supplied by the benchmark harness.
  • Required package:flutter and dart: imports are collected automatically.

4. Captured bindings

A scope also reads names it does not declare, and those do not travel with the transplanted source. Two kinds are lifted onto the generated State:

  • Locals and parameters of the enclosing method. A BlocBuilder callback sitting inside a method can read that method's bool onlyNKN; the declaration stays behind when the callback moves.
  • Members inherited from a package supertype. controller on GetView from package:get is the recurring case. The transplant never emits such a class, so the member becomes a field.

Names declared inside the scope, members of the enclosing class, and top-level declarations are left alone: the first travel with the source and the other two are already handled by steps 2 and 3.

Lifting a parameter to a field costs it type promotion, which Dart applies to locals but not to fields. Uses whose promoted type was narrower than the declared one are rewritten with an explicit cast, so state.wallets inside an if (state is WalletLoaded) becomes (state as WalletLoaded).wallets.

5. Fixture seeding

Lifted fields have no values, because the isolated scope is no longer called with arguments. SPM generates an initState that assigns each one from a conventionally named symbol you define alongside the file:

Lifted binding Assignment generated
field wallets wallets = fixtureWallets;
field _controller _controller = fixtureController;
cross-file project global application application = applicationValue;

Globals are assigned first, since a field initialiser may read one. A scope whose transplanted class already brought its own initState keeps it: overwriting a real one would discard setup the scope depends on, so its lifted fields must be seeded by hand.

6. Visual transformation (skeletonization)

Isolated widgets often lack the original project's assets. The Skeletonizer:

  • Identifies widgets like Image.network, AssetImage, or SvgPicture.
  • Replaces them with lightweight placeholders.
  • Keeps missing image files from preventing layout and rendering.

7. Formatting

After the files are written, SPM runs dart format over the output directory. The transplant concatenates fragments that keep their original indentation, so unformatted output differs between runs in layout as well as in code. Formatting is best-effort: a scope that produced unparseable Dart is still written out for inspection.

Output structure

output/
├── State/
│   ├── login_form_state.dart
│   └── product_list_state.dart
├── BlocBuilder/
│   └── user_profile_builder.dart
└── mapping.jsonl

Mapping file (mapping.jsonl)

Links each isolated file back to its original source. One JSON object per line:

{
  "originalPath": "/abs/path/to/project/lib/ui/login_form.dart",
  "nodeType": "State",
  "isolatedPath": "/abs/path/to/output/State/login_form_state.dart",
  "name": "LoginFormState"
}
Field Description
originalPath Absolute path of the source file the scope was extracted from.
nodeType Scope kind, which is also the output subdirectory name (State, BlocBuilder, Consumer, …).
isolatedPath Absolute path of the generated standalone file.
name Name of the extracted class or builder-owning widget.

When isolation fits

Use an isolated scope when a whole application adds unrelated work to frame timings or when a benchmark needs direct control of the values passed into one builder. The generated GeneratedWidget can be placed in a benchmark loop and supplied with controlled inputs.

Limitations

  • Because SPM skips non-UI dependencies, code involving complex models or services may need manual adjustment or mocking in the generated file. The fixture… and …Value symbols named by the generated initState are yours to define; until they exist, the isolated file does not compile.
  • The Skeletonizer handles images, but custom fonts or localized strings may still need manual setup if critical to the widget.

Clone this wiki locally