- Start Date: 2017-05-04
- RFC PR:
- Yarn Issue: yarnpkg/yarn#3294
Add workspaces keyword to package.json to allow multi project dependency installation and management. When running install command yarn will aggregate all the dependencies in package.json files listed in workspaces field, generate a single yarn.lock and install them in a single node_modules.
This RFC is based on a need to manage multiple projects in one repository issue #3294 aggregates all related discussions.
[Image: https://fb.quip.com/-/blob/CTYAAAX6p4Y/3y0pmJnH2NKkg4ae06hVyw] We are taking an iterative approach to implement the whole end to end experience of workspaces, there will be RFC and PRs for:
- Installing dependencies of all workspaces in the root node_modules (this RFC)
- Commands to manage (add/remove/upgrade) dependencies in workspaces from the parent directory
- Ability for workspaces to refer each other, e.g. jest-diff → jest-matcher-utils
- package-hoister for workspaces, e.g. when dependencies conflict and can't be installed on root level
- Command to publish all workspaces in one go
For a while workspaces will be considered an experimental feature and we will expect breaking changes until it becomes stable.
I'll use Jest for the example implementation.
Workspaces can be enabled by a flag in .yarnrc:
yarn-offline-mirror "path"
disable-self-update-check true
workspaces-experimental true
The structure of the source code is following
| jest/
| ---- package.json
| ---- packages/
| -------- babel-jest/
| ------------ package.json
| -------- babel-preset-jest/
| ------------ package.json
...
Top level package.json is like
{
"private": true,
"name": "jest",
"devDependencies": {
"ansi-regex": "^2.0.0",
"babel-core": "^6.23.1,
},
"workspaces": [
"packages/*"
]
}
babel-jest
{
"name": "babel-jest",
"description": "Jest plugin to use babel for transformation.",
"version": "19.0.0",
"repository": {
"type": "git",
"url": "https://github.com/facebook/jest.git"
},
"license": "BSD-3-Clause",
"main": "build/index.js",
"dependencies": {
"babel-core": "^6.0.0",
"babel-plugin-istanbul": "^4.0.0",
"babel-preset-jest": "^19.0.0"
}
}
babel-preset-jest
{
"name": "babel-preset-jest",
"version": "19.0.0",
"repository": {
"type": "git",
"url": "https://github.com/facebook/jest.git"
},
"license": "BSD-3-Clause",
"main": "index.js",
"dependencies": {
"babel-plugin-jest-hoist": "^19.0.0"
}
}
If workspaces is enabled and yarn install is run at the root level of jest Yarn would install dependencies as if the package.json contained all the dependencies of all the package.json files combined, i.e.
{
"devDependencies": {
"ansi-regex": "^2.0.0",
"babel-core": "^6.23.1,
},
"dependencies": {
"babel-core": "^6.0.0",
"babel-plugin-istanbul": "^4.0.0",
"babel-preset-jest": "^19.0.0"
"babel-plugin-jest-hoist": "^19.0.0"
}
}
The algorithm is the same as in Yarn's hoisting algorithm during linking phase.
In the example above babel-core is used in both top level package.json and one of the workspaces. Yarn will resolve the highest possible common version and install it. If versions are conflicting Yarn will install the most common used one at the root level and install the other versions in each of the workspaces folder.
This should be enough for Node.js to resolve required dependencies when running in each of the workspaces.
Note: In the first implementation workspaces level hoisting won't be implemented and Yarn would throw an error in case of dependency conflicts between packages.
Note: linking, i.e. workspaces referring each other is not covered in this RFC, it will come in a next phase
After running yarn install at the top level Yarn will generate a yarn.lock for all the used dependencies at the root level in workspaces and save it only at the root level. Yarn won't save yarn.lock files at workspaces' folders.
Yarn will automatically run all commands as if running in the root folder, i.e. install won't install node_modules in workspaces' folders individually.
Changes will be needed to check command and integrity-checker considering the new patterns added during resolve phase.
Not sure how exactly cross-platform symlinks are today. However it looks like Microsoft might be catching up on this issue.
Lerna as used in jest now. Having multi-project dependency management natively in Yarn gives us a more cohesive user experience and because Yarn has access to dependency resolution graph the whole solution should provide more features than a wrapper like Lerna.
-
Running lifecycle scripts may cause unexpected results if they require a specific folder structure in node_modules.
-
How do we prevent people from publishing package and forgetting to setup correct dependencies for every workspace? E.g.
left-pad
may be absent from a workspace package.json and be present in the workspace root package.json. Testing the workspace code with node_modules installed in the root won't reveal this issue.