Running Node.js natively on Apple Silicon
- Published
- 2021-01-14
- Tags
- PerformanceAppleDevOps
I just got the one of the newest M1-based MacBooks and I wanted to get it up and running as fast as possible so I went with the Migration Assistant and migrated everything from my old MacBook Pro 2017 running macOS 10. To my surprise it worked without any issues at all (I would expect at least it requires same OS versions).
Although it already felt much faster than my previous machine even running most of the software through Rosetta 2 emulation mode, I was still curious: how much faster it actually is when running things natively.
Since the existing Homebrew installation wouldn't allow my updating or installing packages anymore because of more restrictive OS permissions, I was faced with "update the x86 homebrew" or “use an experimental cutting-edge ARM build”. I went with the latter and this post summarizes my experience.
Step 0. Remove x86 zsh binary
One of the biggest confusion for me was the fact my shell would not run in arm64 mode. This was caused by the fact I already have been using zsh shell before and it was migrated over. The new macOS Big Sur comes with zsh as a default shell so you don’t need to install it separately anymore.
To solve this issue I had to remove x86 compiled zsh that I installed via Homebrew. This cannot be done with brew remove
since Homebrew won’t have enough permissions but you can remove zsh manually:
// 1. verify you're not running native zsh
which zsh
// 2. if it is different from /bin/zsh, run
rm "$(which zsh)"
Verify by opening a new terminal session and running arch
.
➜ arch
arm64
Step 1. Install Apple Silicon version of the Homebrew
To install the ARM version of Homebrew on the Apple Silicon Mac, I used the manual "untar anywhere" method: https://docs.brew.sh/Installation#untar-anywhere
This method allows installing Homebrew alongside with the x86 version which is probably a good idea for a time being since not all packages are yet pre-built for ARM. I had a few issues while building from sources, too.
After installing Homebrew into separate directory, add the following to your .zshrc
file
# Support for two Homebrew installations
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
alias ibrew='arch -x86_64 /usr/local/bin/brew'
After that you can use both arm64 and x86 installations of Homebrew side-by-side.
For more information on Homebrew compatibility on Apple Silicon check out https://github.com/mikelxc/Workarounds-for-ARM-mac and https://soffes.blog/homebrew-on-apple-silicon
Step 2. Install node and yarn
After you have installed Homebrew, install node (which includes npm) normally running brew install node
Install Yarn using brew install yarn
.
To verify that you're running the Node in ARM architecture, enable the row "Architecture" in Activity Monitor,
Step 3. Install packages to run Gatsby
After running Node natively I had issues running Gatsby project. While doing npm install
in the Gatsby project, it will fail because of the binding for the Sharp — a native C library Gatsby using — needs to be compiled under the ARM architecture. It didn't work out of the box for me since some native packages were missing. I was able to resolve it following this GitHub issue https://github.com/lovell/sharp/issues/2460 and doing
cd ~/gatsby-project
brew install vips
rm -rf node_modules
npm install
After that the installation went well and I could run the project. There were no issues whatsoever running other JavaScript projects (using TypeScript or Bable).
So, how much faster is ARM Node compared to x86
I ran a few tests on the same machine and also compared build speeds with my previous MacBook Pro 2017 that has 2,9 GHz i7 Quad Core CPU and 16 GB of RAM.
The results were... astonishing!
Running x86 vs arm64 node resulted in a ~40% improvement. Building a Gatsby site with a few dozens of pages went from 55 seconds on MacBook Pro to 15 seconds on MacBook Air!
Let's not forget, MacBook Air is a fan-less machine that you can put on your lap without a risk of getting a skin burn. And my old computer was making noises similar to an airplane taking off 🤦♂️.
Here is the related Twitter thread: https://twitter.com/okonetchnikov/status/1346162117506985985
Next steps
I still didn't finish this, but I'd like to try cleaning up previous installation of Homebrew completely and move everything to /opt/homebrew
and also try installing only native packages. It didn't work for me for fnm that I'm using to manage node versions meaning if I install a different node version over it it will use x86 build again.
Automating installation
In order to save time in the future, I have a collection of scripts that automate the process of setting up a new computer. You can find them onGitHub https://github.com/okonet/dotfiles. I've updated it accordingly so it should work on M1 Macs. Use it your own risk and please report issues and send PRs if you decide to use those scripts.