Knitting a Yarn Plugin

·

2 min read

Hi! It's me again👋

I'm writing about some things I've learned while working on my Outreachy internship project with Debian. The aim of my project is to:

customize Yarn to allow an apt-installed package satisfy a Node.js project's dependencies.

How is this project helpful?

As an example use case of the project, let's say you wanted to add enhanced-resolve as a dependency in a Node.js project using Yarn. Normally, Yarn would download enhanced-resolve (and its dependencies) from the npm registry, then add it to the Node.js project. However, what if you already had local copies of enhanced-resolve (and its dependencies) installed through apt. Why couldn't you just tell Yarn to use those instead?

Yarn's Architecture

Thanks to it's design, customizing Yarn is straightforward. Yarn's design consists of a core API, @yarnpkg/core, with a plugin interface. With plugins, we can add configurations, commands, resolvers, fetchers, linkers and life-cycle methods to Yarn. It is interesting to know that most Yarn features are just built-in plugins which make use of the core API.

When adding a dependency to a Node.js project, Yarn goes through the resolving stage, fetching stage and linking stage, in that order. As you may have guessed, the resolving stage is handled by resolvers, the fetching stage is handled by fetchers and the linking stage is handled by linkers.

I will be giving a high-level overview of what resolvers, fetchers and linkers do since they are most concerned with my internship project.

Let's say you wanted to add enhanced-resolve to your Node.js project with Yarn. You would run yarn add enhanced-resolve and Yarn would go through the resolving, fetching and linking stages to add enhanced-resolve to your Node.js project.

The resolver is responsible for finding which package actually matches your command argument, enhanced-resolve. The details of the package it finds will include the package name, the package version and the list of its dependencies. It would look similar to this:

{ "name": "enhanced-resolve", "version": "2.0.0", "dependencies": { ... } }

From the package details, the fetcher is responsible for getting the actual package contents from wherever (e.g. npm registry) and placing it on your disk.

Now, there are many ways you could place your package contents on your disk. So, the job of the linker is to make your environment, Node.js, understand the placement of the package contents on your disk. This is to ensure require and import statements work like they should.

This is really high-level but I hope you get the gist. I'm off to continue my work. Wish me luck😉.

Â