In a Dart pub workspace, what do the root pubspec's `workspace` list and each member's `resolution: workspace` do together?
answer
- root lists, members opt in
- SDK constraint of at least ^3.6.0
- one pubspec.lock beside the root
- stray lockfiles get deleted
- packages/* globs since 3.11
basics
~20 sThe root pubspec.yaml's workspace list names the member directories, and each member's resolution: workspace opts it in. dart pub get run anywhere then resolves every member together into one pubspec.lock and one .dart_tool/package_config.json at the root.
solid answer
~40 sPub workspaces (Dart 3.6+) replace a monorepo's separate resolutions with one. The root `pubspec.yaml` - often `name: _` with `publish_to: none` - lists member paths under `workspace`; since Dart 3.11 a glob such as `packages/*` picks up every subdirectory that has a `pubspec.yaml`. Each member adds `resolution: workspace`, and every member needs an SDK constraint of at least `^3.6.0`. Running `dart pub get` anywhere in the repository writes a single `pubspec.lock` and a single `.dart_tool/package_config.json` next to the root pubspec, covering all members' `dependencies` and `dev_dependencies`, and deletes the stray per-package lockfiles and package configs. A non-member `pubspec.yaml` sitting between the root and a member makes `pub get` fail. `dart pub workspace list` (Dart 3.13) prints each member with its path.
code
yaml · 10 lines# packages/core/pubspec.yaml - one of the four local packages
name: core
version: 2.1.0
resolution: workspace
environment:
sdk: ^3.13.0
dependencies:
meta: ^1.15.0go deeper
Recall the two keys - workspace in the root pubspec, resolution: workspace in each member - and that one pubspec.lock sits at the root.
Explain what dart pub get writes and deletes, the ^3.6.0 SDK requirement, globs from 3.11, and why a stray non-member pubspec.yaml breaks resolution.
Plan a migration: add the keys, clear stray lockfiles and package configs, check SDK constraints, and use dart pub workspace list in scripts to enumerate members.
Decide which packages belong in the shared resolution at all, since joining a workspace ties their dependency versions together for good.
## The problem a workspace removes A **monorepo** keeps several Dart packages in one repository - for example a Flutter app in `apps/field_app` and four local packages, `core`, `api_client`, `ui_kit` and `storage`, under `packages/`. Without a workspace, each of those five directories is resolved on its own. dart.dev lists the downsides: - you run `dart pub get` once per package; - each package gets its own `pubspec.lock`, so third-party versions can drift between them; - opening the repository root in an IDE makes the Dart analyzer create a separate analysis context per package, which costs memory. A **pub workspace**, introduced in Dart 3.6, puts all of them into **one shared resolution**. ## Declaring the workspace Two keys do the work, one on each side: 1. **The root `pubspec.yaml`** gets a `workspace` entry listing the member directories. The root is itself a package; dart.dev's example names it `_` and marks it `publish_to: none`, since it is never published. 2. **Each member's `pubspec.yaml`** adds `resolution: workspace`, which says "resolve me as part of the enclosing workspace, not on my own". 3. **Every workspace package** needs an SDK constraint of at least `^3.6.0`. The constraint applies to the workspace's own packages, not to their third-party dependencies. ```yaml # pubspec.yaml at the repository root name: _ publish_to: none environment: sdk: ^3.13.0 workspace: - apps/field_app - packages/* ``` ## What `dart pub get` produces You can run `dart pub get` in **any** directory of the repository. It then: - writes **one `pubspec.lock`** next to the root `pubspec.yaml`, containing the resolution of all `dependencies` and `dev_dependencies` of all workspace packages; - writes **one `.dart_tool/package_config.json`** at the root, mapping every package name to a location on disk; - **deletes** any older `pubspec.lock` and `.dart_tool/package_config.json` files next to member packages. ## Globs and nested workspaces - Since **Dart 3.11**, entries in `workspace` may be globs: `packages/*` includes every subdirectory of `packages/` that contains a `pubspec.yaml`, so a new package joins by being created. The package that declares the glob needs an SDK constraint of 3.11 or higher; on older constraints, list explicit paths. - Workspaces can be **nested**: a member may declare its own `workspace` list, such as `server` listing `auth` and `api`. The root lists only `packages/server`, and pub discovers the rest; everything still joins the single shared resolution. ## Stray files When an existing monorepo is migrated, old per-package files would shadow the new shared ones, so pub is strict about them: | What pub finds between the root and a member | What happens | |---|---| | a `pubspec.lock` | deleted by `dart pub get` | | a `.dart_tool/package_config.json` | deleted by `dart pub get` | | a `pubspec.yaml` that is **not** a workspace member | `dart pub get` reports an error and fails to resolve | ## Listing and targeting members - **`dart pub workspace list`**, added in Dart 3.13, prints every workspace package with its directory (and `--json` for scripts). - Commands that act on a "current" package, such as `dart pub add` and `dart pub publish`, apply to the package in the current directory; `dart pub -C packages/core <command>` points pub at another one. - `dart pub upgrade` upgrades the shared resolution for all members at once. ## Common migration mistakes - **Opting in on one side only.** Every directory the root lists needs `resolution: workspace` in its own pubspec, and every member listed needs to be reachable from the root; the two keys only work as a pair. - **Old SDK constraints.** A member still declaring `sdk: ^3.3.0` cannot join; raise every workspace package to `^3.6.0` or higher (3.11 or higher wherever a glob is used). - **A forgotten intermediate pubspec.** An old `packages/pubspec.yaml` from a previous tool sits between the root and the members; `dart pub get` refuses to resolve until it is removed or made a member. - **Committing the old lockfiles.** After migration, per-member `pubspec.lock` files are deleted by `dart pub get`; make sure version control does not keep re-adding them, and commit the single root lockfile if the repository ships an app. - **Expecting per-member upgrades.** `dart pub upgrade` now moves the shared resolution for everyone, so a dependency upgrade becomes a repository-wide change. ## Summary - Root: `workspace:` list (or globs, 3.11+). Members: `resolution: workspace`. All: SDK `^3.6.0` or higher. - One `pubspec.lock` and one `package_config.json` at the root; stray per-package copies are deleted. - `dart pub workspace list` shows what is in the workspace.
- Why does a workspace lower the Dart analyzer's memory use in a large repository?Without one, opening the repository root makes the analyzer create a separate analysis context for each package, each with its own package configuration. With a single shared resolution and one `.dart_tool/package_config.json`, the packages share that analysis, so large repositories need less memory and analyse faster.
- Can a member package itself contain a workspace?Yes. A member can declare its own `workspace` list - say `server` listing `auth` and `api` - while still using `resolution: workspace`. The root lists only `packages/server`; pub discovers the nested packages through it and puts them all in the same single resolution.
saying these in an interview costs you the question
- Each workspace member keeps its own pubspec.lock beside its pubspec.yaml
- You must run dart pub get separately inside every member package
- Listing a package in the root workspace list is enough; members need no opt-in
- Any Dart 3 SDK constraint on the members is enough for workspaces
- Glob entries such as packages/* work on every SDK that supports workspaces