skip to content

Shared-Lockfile Workspaces

A pub workspace lists member packages in the root pubspec, each member opts in with resolution: workspace, and one lockfile resolves them all. Interviewers ask how monorepos stay consistent.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In a Dart pub workspace, what do the root pubspec's `workspace` list and each member's `resolution: workspace` do together?

level: juniorimportance: should knowfreq 22%

answer

  1. root lists, members opt in
  2. SDK constraint of at least ^3.6.0
  3. one pubspec.lock beside the root
  4. stray lockfiles get deleted
  5. packages/* globs since 3.11

basics

~20 s

The 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 s

Pub 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
yaml
# 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.0

go deeper

for a junior

Recall the two keys - workspace in the root pubspec, resolution: workspace in each member - and that one pubspec.lock sits at the root.

for a middle

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.

for a senior

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.

for a principal

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
open as a page

In a Dart pub workspace, when `api_client` declares `core: ^2.0.0`, which copy of `core` does it use inside the workspace and after publishing?

level: middleimportance: should knowfreq 28%

basics

~20 s

Inside the workspace api_client resolves to the local packages/core, whatever source the dependency names, as long as that copy's version satisfies ^2.0.0. Once api_client is published, its users get core from pub.dev as an ordinary hosted dependency.

open as a page

After moving an app and four packages into one Dart pub workspace, `dart pub get` fails on incompatible `http` majors that built fine separately; why, and what are your options?

level: seniorimportance: should knowfreq 25%

basics

~20 s

A workspace resolves every member's dependencies and dev_dependencies together, and a resolution holds one version per package, so ^0.13.0 and ^1.2.0 no longer have a solution. Migrate the lagging member rather than overriding, or keep it outside the workspace.

open as a page

In a Dart pub workspace, what can a member's `pubspec_overrides.yaml` change, and how do you use it to resolve that member on its own?

level: seniorimportance: nice to knowfreq 12%

basics

~20 s

pubspec_overrides.yaml sits next to a pubspec.yaml and overrides only its dependency_overrides, workspace and resolution keys, usually without being committed. An empty resolution: in it detaches the member, so dart pub get there creates an independent resolution.

open as a page