After sitting through a 15 minute yarn install
I said “enough is enough” and I decided to migrate to the recently announced Yarn 3 with PnP support to get rid of our install process. Yes, get rid of yarn install
via Zero Install.
This post is verbose on purpose. I’ve searched for the errors Yarn was outputting multiple times and didn’t find much help online, so I am adding all the errors I had here so that I can save you some time 1. Also, I may refer to Yarn as Yarn Berry or Yarn 3, they all mean the same thing, as Berry is the name of the repository post-v1.
We’ve been using lerna for our JavaScript monorepo project 2. I would glady stay with lerna
, but after some tries I couldn’t get lerna to work with yarn 3
, and it seems it won’t be supporting the new yarn versions anytime soon. Well…
The migration guide from the Yarn team is great, I’ll take the liberty of copying it here for ease of access:
<yarn-migration>
npm install -g yarn
to update the global yarn version to latest v1yarn set version berry
to enable v2 (cf Install for more details).npmrc
or .yarnrc
, you’ll need to turn them into the new format (see also 1, 2)nodeLinker: node-modules
in your .yarnrc.yml
file (we will try to enable this later)yarn-X.Y.Z.js
, .yarnrc.yml
, …)yarn install
to migrate the lockfile</yarn-migration>
On step 8 they recommend modifying your .gitignore
file. Here I recommend the gitignore plugin from oh-my-zsh
, which allows me to just run gi yarn >> .gitignore
and forget about it. Also, if you plan on using Zero Install, go check that section for more on gitignores
Also go ahead and delete all yarn.lock
files that aren’t in the root of your project. This took me some time to figure out, but Yarn looks for the yarn.lock
s outside the root folder to mark that dir as a possible node_modules
-compatible sub-package.
.npmrc
and .yarnrc
to .yarnrc.yml
Yarn 3 completely ignores the .npmrc
and .yarnrc
files on your project. I was actually using those to connect with my private npm repository.
My current .npmrc
contains:
which becomes a .yarnrc.yml
(keep reading and you’ll know what issue does this excerpt hold here):
The .yarnrc
contained a pointer to .yarn/releases/yarn-1.22.11.cjs
, which I won’t be needing anymore. So I deleted both the .yarnrc
and this file.
We used the nohoist
option in our package.json
, which has a slightly different behavior in Yarn 3. Previously, you’d list in the nohoist
option which packages should not be hoisted to the top-level node_modules
folder, and all sub-packages would follow that. Now, you’d go to the specific package.json
and set the installConfig option to avoid hoisting that whole workspace. This is a great change if you ask me, specially if you are using Create React App.
Since I only had "nohoist": []
there, I just deleted the property, but your case may be different.
This was hard to figure out.
The first thing you need to do after setting up your .yarnrc.yml
is run yarn npm login
. After you have this login setup, you need to open your ~/.yarnrc.yml
(on your Home, not your project) and add npmAlwaysAuth: true
under the login settings for the registry.
I tried to follow the documentation anyway I could but I kept getting either this error:
or this “Invalid authentication (as an anonymous user)” error:
I simply could not find anything online that would point me in the right direction here, it appeared as if Yarn simply gave up on trying to authenticate me with my registry. After trying for long I started inserting debugger
statements inside the Yarn compiled source code, gave up on that, bit the bullet and downloaded the berry codebase to debug yarn from inside.
It turns out Yarn can’t merge your local registry config and your global config. At least I learned how to debug yarn?
If you ever need to debug Yarn itself, this is how you do it:
yarn install
on the resulting dir;realpath scripts/run-yarn.js | pbcopy
to copy this path to your clipboardyarnPath
entry in .yarnrc.yml
to /path/to/your/clone/berry/scripts/run-yarn.js
(or just paste it if you used the command above)Now whenever you run yarn
inside this project folder you will use the development version. Props to the yarn team for using node-ts
to load the typescript files on the go, that helped me a lot.
lerna
to yarn
In order to migrate from lerna we need to add a new plugin to our environment:
While it is installing the plugin, let’s go ahead and delete our lerna.json
file, remove all scripts that run lerna
, and remove the dependency from our packages. If you only had your subpackages listed in lerna.json
, it is time to migrate it to Yarn’s ``package.json`:
workspace:
protocolYarn berry now supports a few things that lerna didn’t, one of those things is explicitly setting the package imports to always match the ones on your monorepo. This means that it doesn’t matter when or where this package is run, it will only work when running inside the monorepo. If your CI/CD are ready to deal with monorepos, this can be a great thing, and you can go ahead and change the version of your packages to workspace:*
(this will match the folder you’re in). Unfortunately, that is not my case and I’ll have to go without this.
yarn start
commandIn many React projects we have the start
script ready to get things going. When using lerna
we used to have the following command to use it in our monorepo:
This would simply execute all scripts named start
in our subpackages. While okay, it did waste resources by running start
on unnecessary packages.
Now, with Yarn berry we can run, directly in the package we need:
This command come from the workspace-tools
plugin I’ve mentioned. It is using these options:
v
: verbose, prefix the output with the package that printed that;p
: run in parallel;i
print all outputs in realtime;R
: recursive.
dependencies
/devDependencies
and only run start
on the packages that actually need to be started!Here’s an example. A workspace have these packages, all with a start
script in their package.json
file:
@my/a
, importing:
@my/b
@my/c
@my/d
, importing:
@my/b
@my/d
@my/e
.With lerna, all of these packages’ start
script would run, no matter if I am working only on @my/a
right now. With the recursive
option, if I run it inside @my/a
, it will only run start
on @my/a
, @my/b
, and @my/c
, leaving @my/d
and @my/e
alone.
It turns out that when running the start command mentioned above, one can get the following error:
As mentioned in the beginning, you need to delete the yarn.lock files inside all subpackges on yarn 3, something that yarn 1 created.
If you are following along with your repo, I recommend you to commit your changes now, as this has been some work already!
You may not need PnP, you may need to evaluate if the gains from enabling PnP are really worth it for you, there are a lot stuff that breaks when migrating… But I am here for the Zero Install! Well, once again, the migration tutorial covers a lot of the use cases, lets copy it again:
<yarn-migration>
.yarnrc.yml
file for the nodeLinker
settingpnp
, then it’s all good: you’re already using Plug’n’Play!yarn install
We have a dedicated documentation, but if you’re using VSCode (or some other IDE with Intellisense-like feature) the gist is:
typescript
, eslint
, prettier
, … all dependencies typically used by your IDE extensions are listed at the top level of the project (rather than in a random workspace)yarn dlx @yarnpkg/sdks vscode
</yarn-migration>
I didn’t know which deps should be on the root or not when I ran the step #2 for the editor support. So I did like any dev would and looked at the code to see which tools are supported by it. In my case I only needed to move typescript
, eslint
and prettier
(my CSSs are written by another team).
Another caveat I bumped into was that after doing all that, my VSCode’s TypeScript still didn’t find my files. Turns out that if you have a .code-workspace
file, VSCode will load the settings from there! So I added the settings that Yarn generated in that file and voila!
If you have a build
script in all your packages, you can run
and it will show you if your build is running or not. Turns out that mine wasn’t well.
One of the packages I depend on didn’t declare their dependencies correctly. In order to fix that I was tempted to add their dependency in my root’s package.json
, well, this broke other stuff in my project because the mere installation of that particular package interfered with the generation of the html on my compilation (yeah).
The correct approach here is to use Yarn’s package extension feature to correctly patch their package.json
and load the package they are missing.
My next headache was with eslint
. I first went with the dumb route and started adding my configurations in the root folder, but it wasn’t cutting it. In my setup I have a package called @mycompany/eslint-config-react
for my React apps, and this package is meant to be the only eslint config place for the project. To make eslint
work you need to add @rushstack/eslint-patch
to your configuration project, and on your .eslintrc.js
(or index.js
) you have to add:
In my project this highlighted a bunch of plugins I was using but didn’t declare on my package.json.
After adding all those plugins, I have the following error:
Running eslint directly yielded a better error log:
Fixing this was simple: upgrade this rowdy package (really they’ve fixed it yesterday 😂).
On to the eslint
/jest
error:
To fix this, set the jest
version:
eslint-plugin-import
has some issues with PnP.
eslint-plugin-import
needs to know how to resolve the packages, it should do that internally, but it tries to load the resolver from the project that the linted files are in. This makes things weird here on PnP because we do not have all files dumped on the same dir. Usually the resolution is to add eslint-import-resolver-node as a direct dev dependency. Since we are using workspaces it must be added on the root package.json
.
Before enabling zero installs to speed up your dev lifecycle, clone your project anew and make a clean install to see if you are on the green.
You can follow Yarn’s guide here to un-ignore the cache files. Adding to your .gitignore
the following:
Have you noticed there are some packages that compile some stuff after you install them? Think fsevents
. Well, after they compile, Yarn dumps their content unto .yarn/unplugged
.
You have two ways to deal with this:
enableScripts: false
to your .yarnrc.yml
file so that no package can ever add something there.!.yarn/unplugged
to your .gitignore
folder, and then git add -f .yarn/unplugged
.In my case, I went with the ignore/deletion route, but you should test your project to see what to do.
After doing all that work (seriously it took me days). We need to check if it works! Clone your project and try to build
/start
it! If it doesn’t work, well, you’ll have to dig deeper.
Was it worth my investment? In my context, the time saved by having Zero Install is surely worth it, people can focus more on their code and less on their tooling.
Seriously, let me know if you have problems (post on StackOverflow and send me a link), or if I’ve helped you at all.
Things I didn’t cover, but you might need: