Symlinks and Npm

Laurie - Dec 14 '20 - - Dev Community

Symlink is a phrase you'll hear a lot in relation to local development. But it applies in a number of situations and it's a helpful JavaScript concept to understand.

What is a symlink

A symlink is a reference to a file or folder. Think of it like an alias. If I typically want to access ../../../dir/laurie.js, I can use a symlink so that I only have to reference laurie.

In most cases, a symlink is operating as a shortcut. But technically the name could be longer than the path, so alias is more accurate. Then again, if you're making the path longer I'm not entirely sure why!

If that explanation doesn't work for you, @chrisbiscardi explains them as "symlinks make things look like they're in one place when they're actually in another", which is a good one.

It works on my machine

You can create a symlink in your local file system. So that you don't have to remember the file path to that random executable.

You can do that by running the ln command in your terminal.

ln -s /path/to/target.js linked-target.js
Enter fullscreen mode Exit fullscreen mode

You can do the same thing with directories.

ln -s /this/is/a/nested/proj/ /proj/
Enter fullscreen mode Exit fullscreen mode

Once you set a symbolic link, that information is available at that path in your filesystem.

Note that the link can be name, name.js, whatever you want. However, in most cases you'll see links set with the extension intact.

Packages

Symlinks are incredibly powerful ways to provide shortcuts in your filesystem. So the question becomes, can you use them to alter other people's filesystem? The answer is, kinda!

Npm

So how is this enabled? There are a few different ways. A postinstall script, or the bin field in your package.json file.

post-install

If you've read my package.json post you know that there are some reserved script keys. postinstall is one of those keys.

It's a lifecycle script and it runs after the package has been installed. Therefore, you could use it to define something like this:

{
  "scripts": {
    "postinstall": "ln -s ../ ./node_modules/my-thing"
  }
}
Enter fullscreen mode Exit fullscreen mode

With this symlink, a user can require(my-thing) and gain access to the root of the project.

bin

Going back to that package.json post again there is a section on bin. I talk about how it facilitates commands users can run directly with npx. And that's true! But it's all because of symlinks, which is the true power of the bin field.

The commands defined in bin are symlinks to executables. If a user runs the command using npx, the executable runs without having to install the package. But what if the user does install the package?

Local install

If a user installs your package in a project, the package gets downloaded into their node_modules. From then on, the symlink is pointing to a relative path inside node_modules. That means that a user can run the executable it points to, or reference it as an import, as long as they're in the original directory.

If they aren't, it won't work. Because ./node_modules will no longer resolve.

Global install

Conversely, some packages are meant to be installed globally in your operating system, using a -g flag. When that happens, the symlinks are available globally, as they were in the ln local machine example.

This means that you can use the symlink to run that executable no matter where you are in the filesystem. It's globally available rather than being relative.

And that's it

Symlinks are a powerful tool and learning about them helps us understand a lot about the command line tools and import paths we use.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .