Crates.io | rn |
lib.rs | rn |
version | 0.2.2 |
source | src |
created_at | 2017-07-22 21:46:13.364111 |
updated_at | 2017-08-02 14:52:22.704919 |
description | Command line tool for managing per-session nodejs versions based on package.json files |
homepage | |
repository | https://gitlab.com/bff/rn |
max_upload_size | |
id | 24574 |
size | 2,511,914 |
Nursing your node environment to good health!
rn
is yet another version manager for Node.js, a bit like rvm is a Ruby version manager. It's primary goals are ease of use and fast execution for use in a local development environment on MacOS or Linux.
RN includes a bash script to install itself on MacOS and Linux and perform simple diagnostics on your shell:
curl -sSL https://gitlab.com/bff/rn/raw/master/install.sh | bash
Check that it works (either platform):
rn
curl https://sh.rustup.rs -sSf | sh
git clone git@gitlab.com:bff/rn.git
cd rn
cargo build --release
sudo cp target/release/rn /usr/local/bin/rn
cp bin/rn.sh .config/rn/
echo 'source $HOME/.config/rn/rn.sh' >> .bashrc
{
"engines": {
"node": "0.10.37"
}
}
$ rn --list
0.10.37
4.3.0
6.4.9
RN_DIR
Defaults to $HOME/.rn
; this is where rn
will store node versions, and potentially metadata in the future. Node binaries are stored in $RN_DIR/versions
.
For example, if you had node 5.7.1 locally, and RN_DIR
was unset, you could find the local copy of node 5.7.1 at /home/bryce/.rn/versions/v5.7.1
.
I think the easiest way to understand rn
is to compare it to the other Node.js version managers in the space.
Project | rn | nvm | nodenv1 | n |
---|---|---|---|---|
Design Goal | local dev | local dev | prod & dev | system node |
Feature Set | tiny | large | enormous | small |
Implemented in | Rust | Bash/Zsh | All Shells | Bash |
Full Semver Support | never | yes | yes | yes |
Per-shell Versions | yes | yes | yes | no |
gLobal Version | soon | yes | yes | yes |
Sourced? | partially | yes | partially | no |
Package.json Parsing | yes | no | no | no |
Manages npm |
soon | no | no | no |
Hooks cd |
yes | no | no | no |
The biggest downsides to working with Rust are that it isn't as portable as Go or Bash. However, I think there are enough good technical reasons to justify using Rust inspite of the portability.
My main goal was make life better for developers in their local shell. If the package.json specifies only the major version of Node.js, then it requires an additional network and more parsing to find acceptable verion to download and install.
Additionally, I've worked on teams that found breaking changes happening on minor versions (semver not followed) or serious bugs emerge from changing even the patch version. I also happen to believe that having a high fidelity between one's development environment and production environment is a great way to avoid nasty surprises. By locking in the exact patch version, I hope I'm saving you from some serious issues.
Lastly, if those (hopefully) sound technical reasons don't sway you, I'll admit that I didn't want to go implement a full semver logic and I wanted to execute more quickly on this project.
For all these reasons, I have no plans to implement full semver in the package.json parsing.
It's often handy to run multiple Node.js applications simultaneous (for microservices, etc). By configuring each shell's PATH separately, I can ensure that each terminal session has exactly the version of node it needs. Think of it as a poor man's Docker.
On 2015 MacBook Pro's, I've seen sourcing thousands of lines of Bash add seconds to the init time required to open a new shell. Imagine if every time you opened a new tab in Firefox, you had wait 2+ seconds for the location bar to become responsive. You'd switch to Chrome in a heartbeat.
Sourcing large Bash files is a great way to avoid re-loading them every time you want to use their functionality. However, by leveraging Rust (or any compiled language for that matter), it's very cheap to load machine code into memory for each execution. This also allows me to skip the expensive upfront time spent sourcing a large Bash file.
cd
?Because rvm does it?! One problem with rvm's implementation is that it has a large feature set mostly built in Bash that's easy to screw up. By wrapping cd
in a small, fast, and narrowed scoped Bash function, I can easily have the native Rust code do all the logic and heavy lifting of downloading node versions and updating the PATH with less opprotunity for things to go badly. However, to udpate your current shell and not have my changes to the shell disappear in a subshell, Bash functions are an obvious choice. Honestly, I can't think of another way to do it that's less terrible. If you know of one, open an issue and tell me!
rn.sh
replaces the builtin cd
utility with a Bash function. This function checks for the existence of a package.json file. If a package.json file is found, rn
is invoked with the full path to that file.
Internally, rn
finds the previously downloaded versions of node (if any), and determines whether or not to download a tarball of the desired version of Node.js from nodejs.org. rn
streams the response data through a decompression algorithm in memory and unpacks the decompressed archive into RN_DIR.
Finally, whether or not a download occurred, rn
will attempt to strip out any previously set node versions from the path and add the new node version to the front of the path. It prints out the final desired PATH over STDOUT and exits 0.
rn.sh
receives STDOUT and replaces PATH in the current shell. You're ready to run your project!
If anything goes wrong, rn
will print out an error to STDERR and exit 1; the PATH will not be updated. Helpful error messages have already been implemented for invalid package.json files and unusable node versions, among other scenarios. Additionally contextual error messages will be implemented as time permits.
I haven't use nodenv personally, so don't 100% trust my description of nodenv, gleaned from the documentation. ↩