skip to content

How do Dart conditional imports such as if (dart.library.io) let one package ship native and web implementations, and what are the pitfalls?

level: seniorimportance: should knowfreq 35%

answer

  1. default URI plus if-clauses
  2. first true condition wins
  3. dart.library.name keys only
  4. available, not imported
  5. every file must expose the same API

basics

~20 s

A 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
dart
// 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

for a junior

Recall the syntax: a default URI followed by if (dart.library.x) alternatives.

for a middle

Explain first-true resolution, what available means for the keys, and which keys are true on native, JS and Wasm targets.

for a senior

Design the stub, native and web files around one API and add per-platform CI builds so a drifting alternative is caught.

for a principal

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