Pub workspaces (monorepo support)
Learn more about pub workspaces, a way to manage package monorepos.
When working on a project, you might develop multiple Dart packages in the same version control repository (a monorepo).
For example, you might have a directory layout like:
-
/
-
packages/
-
shared/
- pubspec.yaml
- pubspec.lock
-
.dart_tool/
- package_config.json
-
client_package/
- pubspec.yaml
- pubspec.lock
-
.dart_tool/
- package_config.json
-
server_package/
- pubspec.yaml
- pubspec.lock
-
.dart_tool/
- package_config.json
-
-
There are some downsides to this setup:
-
You need to run
dart pub getonce for each package. - You risk ending up with different versions of dependencies for each package, leading to confusion when context switching between the packages.
- If you open the root folder in your IDE, the dart analyzer will create separate analysis contexts for each package, increasing memory usage.
Pub allows you to organize your repository as a workspace using a single shared resolution for all your packages. Using workspaces for large repositories reduces the amount of memory required for analysis, hence improving performance.
To create a workspace:
-
Add a
pubspec.yamlat the repository root directory with aworkspaceentry enumerating the paths to the packages of the repository (the workspace packages):yamlname: _ publish_to: none environment: sdk: ^3.6.0 workspace: - packages/shared - packages/client_package - packages/server_package -
For each of the existing
pubspec.yamlfiles, make sure their SDK constraint is at least^3.6.0and add aresolutionentry:yamlenvironment: sdk: ^3.6.0 resolution: workspace -
Run
dart pub getanywhere in the repository. This will:-
Create a single
pubspec.locknext to the rootpubspec.yamlthat contains the resolution of all thedependenciesanddev_dependenciesof all the workspace packages. -
Create a single shared
.dart_tool/package_config.jsonthat maps package names to file locations. -
Delete any other existing
pubspec.lockand.dart_tool/package_config.jsonfiles next to workspace packages.
-
Create a single
Now the file structure looks like this:
-
/
-
packages/
-
shared/
- pubspec.yaml
-
client_package/
- pubspec.yaml
-
server_package/
- pubspec.yaml
-
- pubspec.yaml
- pubspec.lock
-
.dart_tool/
- package_config.json
-
Glob pattern support
#
The workspace entry supports glob patterns to
automatically include packages:
workspace:
- packages/*
This includes all subdirectories in
packages/ that contain a
pubspec.yaml file, eliminating the need to
manually list each package. This is especially useful for
large or frequently growing monorepos.
Instead of:
workspace:
- packages/shared
- packages/client_package
- packages/server_package
You can use a glob pattern that automatically picks up new packages.
Nested workspaces
#
You can nest workspaces to organize packages hierarchically
in large repositories. A workspace member can declare its
own workspace field, just like the root
pubspec.yaml.
For example, if a server package splits its
implementation into auth and
api sub-packages, list them in the
workspace field of the
server package:
name: server
resolution: workspace
environment:
sdk: ^3.6.0
workspace:
- auth
- api
Then mark each sub-package as using workspace resolution:
name: auth
resolution: workspace
environment:
sdk: ^3.6.0
The root pubspec.yaml only needs to list
packages/server. Pub discovers
auth and api through the
workspace entry of the
server package and includes them in the single,
shared dependency resolution.
Stray files
#
When you migrate an existing monorepo to use Pub workspaces,
there will be existing "stray" pubspec.lock and
.dart_tool/package_config.json files adjacent
to each pubspec. These shadow the
pubspec.lock and
.dart_tool/package_config.json files placed
next to the root.
Therefore, pub get will delete any
pubspec.lock and
.dart_tool/package_config.json located in
directories between the root and (including) any workspace
package.
-
/
-
packages/
-
foo/
- pubspec.yaml # Workspace member
-
pubspec.lock
# Deleted by
pub get -
.dart_tool/package_config.json# Deleted by
pub get
-
pubspec.lock
# Deleted by
pub get -
.dart_tool/package_config.json# Deleted by
pub get
-
- pubspec.yaml # Root
-
If any directory between the workspace root and a workspace
package contains a "stray" pubspec.yaml file
that is not member of the workspace,
pub get will report an error and fail to
resolve. This is because resolving such a
pubspec.yaml would create a
.dart_tool/package_config.json file that
shadows the one at the root.
For example:
-
/
-
packages/
-
foo/
- pubspec.yaml # Workspace member
- pubspec.yaml # Not workspace member => error
-
-
pubspec.yaml
# Root
workspace: ['packages/foo']
-
Interdependencies between workspace packages
#If any of the workspace packages depend on each other, they will automatically resolve to the one in the workspace, regardless of the source.
Eg. packages/client_package/pubspec.yaml might
depend on shared:
dependencies:
shared: ^2.3.0
When resolved inside the workspace, the
local version of shared will be used.
The local version of shared would still have to
match the constraint (^2.3.0) though.
But when the package is consumed as a dependency without
being part of the workspace, the original source (here
implicitly hosted) is used.
So if client_package is published to pub.dev
and someone depends on it, they will get the hosted version
of shared as a transitive dependency.
Dependency overrides in a workspace
#
All dependency_overrides sections in the
workspace packages are respected. You can also place a
pubspec_overrides.yaml file next to any of the
workspace pubspec.yaml files.
You can only override a package once in the workspace. To
keep overrides organized, it's preferable to keep
dependency_overrides in the root
pubspec.yaml.
Running a command in a specific workspace package
#
Some pub commands, such as dart pub add, and
dart pub publish operate on a "current"
package. You can either change the directory, or use
-C to point pub at a directory:
dart pub -C packages/client_package publish
# Same as
cd packages/client_package ; dart pub publish ; cd -
Temporarily resolving a package outside its workspace:
#Sometimes you might want to resolve a workspace package on its own, for example to validate its dependency constraints.
One way to do this is to create a
pubspec_overrides.yaml file that resets the
resolution setting, like so:
# packages/client_package/pubspec_overrides.yaml
resolution:
Now running dart pub get inside
packages/client_package will create an
independent resolution.
Listing all workspace packages
#
You can run dart pub workspace list to list the
packages of a workspace.
dart pub workspace list
Package Path
_ ./
client_package packages/client_package/
server_package packages/server_package/
shared packages/shared/