How do Dart conditional imports such as if (dart.library.io) let one package ship native and web implementations, and what are the pitfalls?
answer
- default URI plus if-clauses
- first true condition wins
- dart.library.name keys only
- available, not imported
- every file must expose the same API
basics
~20 sA conditional import names a default file and alternatives guarded by dart.library.* conditions; the compiler picks the first alternative whose library is available on the target, otherwise the default. All alternatives must expose the same API.
solid answer
~50 s`export 'src/impl_stub.dart' if (dart.library.io) 'src/impl_io.dart' if (dart.library.js_interop) 'src/impl_web.dart';` lets callers import one library while the compiler selects an implementation per target. Conditions are checked in order and the first true one wins; if none is true, the default URI is used. The only keys provided are of the form `dart.library.<name>`, true when `dart:<name>` is **available** on the platform - not whether you import it. `dart.library.io` is true on the VM and AOT targets and false for the web compilers; `dart.library.js_interop` is true for both JavaScript and Wasm web builds, while `dart.library.html` and `dart.library.js` are false under Wasm. Pitfalls: the files must implement the **same API**, but a mismatch in the file not chosen for the current build can surface only when compiling for the other platform; the stub should throw `UnsupportedError` rather than silently no-op; and each platform needs its own compile or test run to catch breakage.
code
dart · 16 lines// lib/storage.dart
export 'src/storage_stub.dart'
if (dart.library.io) 'src/storage_io.dart'
if (dart.library.js_interop) 'src/storage_web.dart';
// lib/src/storage_stub.dart
Future<String?> loadConfig() =>
throw UnsupportedError('No storage on this platform');
// lib/src/storage_io.dart
import 'dart:io';
Future<String?> loadConfig() async {
final file = File('.my_tool.json');
return await file.exists() ? file.readAsString() : null;
}go deeper
Recall the syntax: a default URI followed by if (dart.library.x) alternatives.
Explain first-true resolution, what available means for the keys, and which keys are true on native, JS and Wasm targets.
Design the stub, native and web files around one API and add per-platform CI builds so a drifting alternative is caught.
Decide where the platform seam sits in a multi-target codebase so platform code stays small and testable.
## The problem conditional imports solve A Dart package often has logic that must differ by platform: reading a file with `dart:io` on native targets, calling browser APIs through `package:web` on the web. Importing `dart:io` in code that is compiled for the web is a problem, and importing `package:web` in a native build is too. **Conditional imports and exports** let one import site resolve to a different file per compilation target. ## Syntax and resolution ```dart export 'src/storage_stub.dart' if (dart.library.io) 'src/storage_io.dart' if (dart.library.js_interop) 'src/storage_web.dart'; ``` The rules: 1. The first URI is the **default**. 2. Each `if (condition) 'uri'` clause is checked **in order**, and the **first true** condition selects its URI. 3. If no condition is true, the default is used. 4. The same form works with `import`. The only condition keys the tools provide are **`dart.library.<name>`**. A key is `"true"` when `dart:<name>` is **available for use** on the current platform - it says nothing about whether any code actually imports that library. | Key | Native (VM, AOT) | Web (JS) | Web (Wasm) | |---|---|---|---| | `dart.library.io` | true | false | false | | `dart.library.js_interop` | false | true | true | | `dart.library.html` | false | true | false | | `dart.library.js` | false | true | false | That table explains the current guidance: detect the web with **`dart.library.js_interop`**, which covers both JavaScript and Wasm builds; conditions on `dart.library.html` or `dart.library.js` silently pick the wrong file in a Wasm build. ## Designing the three files The dart.dev package guide states that all conditionally exported libraries **must implement the same API**. A robust layout: - **`storage_stub.dart`** - the default, with the same functions or classes whose bodies throw `UnsupportedError`, so a platform nobody planned for fails loudly. - **`storage_io.dart`** - imports `dart:io`, implements the API with files. - **`storage_web.dart`** - imports `package:web`, implements it with browser storage. - **`storage.dart`** - the public library containing the conditional export, which is all callers import. Defining the API once, for example as an abstract class that each file implements with a top-level factory, makes a mismatch a compile error in that file rather than a surprise at a call site. ## Pitfalls - **Only one resolution is checked per build.** A native build never compiles `storage_web.dart`, so a missing function there surfaces only when someone builds for the web. CI should compile or test for each target. - **Leaking platform libraries.** If the public library, or a file every platform imports, pulls in `dart:io` directly, the conditional import does not help. - **Ordering.** Because the first true condition wins, a broad condition placed first can shadow a more specific one. - **Custom keys.** Only `dart.library.*` keys exist; you cannot switch implementations on your own `-D` define this way. - **Silent stubs.** A stub that returns empty values hides a missing implementation until production. ## Scenario: a CLI core that also ships to the web A CLI's configuration loader reads a file on native targets and `localStorage` in a browser demo. With the layout above, the same `loadConfig()` call compiles into the native executable with the `dart:io` implementation and into the JavaScript and Wasm builds with the `package:web` one. ## Common mistakes - Using `dart.library.html` for web detection in code that must also compile to Wasm. - Letting the three files' signatures drift. - Assuming conditions check whether a library is imported rather than available.
- What does dart.library.io being true actually mean?That `dart:io` is available for use on the platform being compiled for. It does not mean any code imports `dart:io`; the key reflects the platform's library set, which is why it is true on the VM and AOT targets and false for the web compilers.
- Why should the default file in a conditional export throw UnsupportedError instead of doing nothing?The default is chosen when no condition matches - a platform nobody implemented. Throwing makes that gap visible the first time the code runs, while a silent no-op returns plausible empty results and hides the missing implementation.
- Can you switch a conditional import on your own -D define, such as a flavour?No. The tools only provide keys of the form `dart.library.<name>`. For flavour-style switches, read a compile-time constant with `String.fromEnvironment` or `bool.fromEnvironment` in ordinary code instead.
saying these in an interview costs you the question
- Thinks dart.library.io is true only when dart:io is imported somewhere
- Uses dart.library.html to detect the web in Wasm builds
- Believes all alternatives are type-checked in every build
- Expects conditional imports to accept custom -D keys
- Writes a stub that silently returns empty values