arm64 的 Mac 上面使用 fnm 的问题

注:文中 mac arm64、apple silicon、m1 皆指代采用 Apple 自研芯片的 mac

早前我把自己的 Nodejs 版本管理工具切换到了 fnm,比 nvm 更快更简洁,比 n 更灵活是我选择它的原因。

不久前我的 mac 更新为了 m1 芯片的版本,随之而来的开发环境的问题也很多。

首先就是 Nodejs 的问题。

Nodejs 的 mac arm64 支持现状

截止本文发布时间(2021-1-20),只有 v15.x 版本的 Nodejs 支持了 arm64 版本的 mac 系统,其余版本的 Nodejs 只能通过 Rosetta 运行(不过问题不多)。

在 m1 芯片能够提供极大程度性能提升的情况下,大家当然希望更多版本的 Nodejs 可以原生支持 arm64 的 mac。

不过目前 Nodejs 官方还没有正式宣布支持 apple silicon另一个链接),距离正式支持应该还有一段时间。

由于以上原因,fnm 在 mac arm64 下的使用也产生了一些问题。

在给项目提交了一次更新之后,目前 fnm 已经可以在 mac arm64 下正常编译。

由此,fnm 在 mac arm64 下会去尝试从 Nodejs 官方网站下载 darwin-arm64 版本的预编译二进制包。但是因为官方还没有提供这一版本的包,所以 fnm 无法正常工作。

我的 fnm 临时解决方案

由于这个问题,我只能采用一个临时解决方案(不准备切换回 nvm 或者 n 了)。

方案大致逻辑是:

  1. v15.x 的 Nodejs 我需要使用 mac arm64 的版本(性能提升显著,为什么不呢)
  2. 其余更低版本的 Nodejs 使用 x64 版本通过 Rosetta 运行

确定了基础逻辑之后,我需要先找一个获得 mac arm64 版本的方法,从源码编译是一种选择,这也是 nvm 的方法,不过手动编译效率有些低下。好在 homebrew 已经提供了arm64 的预编译包(homebrew 提供预编译包的方式),从 homebrew bottles 下载是一个不错的选择。

步骤

  1. 首先我们正常初始化 fnm

    我同时使用 fnm 的 mac arm64 和 x64 版本,使用 x64 版本安装 x64 版本的 Nodejs,使用 arm64 版本做其余逻辑。

  2. 安装 arm64 版本的 Nodejs

    大致流程就是从 homebrew bottles 下载 darwin-arm64 版本的 Nodejs 预编译包,按照 fnm 的文件结构把文件放到相应位置,比如 15.6.0 对应 ~/.fnm/node-versions/v15.6.0/installation

这样 fnm 就可以同时管理 x64 和 arm64 版本的 Nodejs 了。

为了方便我获取 arm64 版本的 Nodejs,我写了一个 shell 脚本,从 homebrew bottles 下载最新的 darwin-arm64 Nodejs 包。

#!/bin/bash

# 假设你已经安装了 homebrew, wget 和 fx(https://github.com/antonmedv/fx)

# 所有工作在 /tmp 下完成避免意外情况
mkdir -p /tmp/fnm-node;
pushd /tmp/fnm-node;
rm -fr ./*;
mkdir fnm;
# 保存 homebrew 提供的 node 信息 
NODE_INFO=./tmp_node_info.json;
brew info node --json > ${NODE_INFO};
# 获取最新的 Node 版本
NODE_VERSION=$(fx $NODE_INFO .[0].linked_keg);
echo "nodejs version: ${NODE_VERSION;}";
# 建立 fnm 的相同文件结构
mkdir -p ./fnm/node-versions/v${NODE_VERSION}/installation;
# bottles 上的 Node 包
NODE_TAR=$(fx $NODE_INFO .[0].bottle.stable.files.arm64_big_sur.url)
# sha256
TAR_SHA256=$(fx $NODE_INFO .[0].bottle.stable.files.arm64_big_sur.sha256)
# 本地的包文件
NODE_TAR_FILE="node-${NODE_VERSION}.arm64_big_sur.bottle.tar.gz"
# 下载包
wget ${NODE_TAR};
# 校验 sha256
FILE_SHA=$(shasum -a 256 ${NODE_TAR_FILE} | head -c 64);
if [[ "$FILE_SHA" != "$TAR_SHA256" ]]; then
    echo "sha256 check failed";
    exit 1;
fi
# 解压
tar xzf ${NODE_TAR_FILE};
# 移动文件到 fnm 镜像结构
mv node/${NODE_VERSION}/* ./fnm/node-versions/v${NODE_VERSION}/installation;
# 安装npm和npx
pushd ./fnm/node-versions/v${NODE_VERSION}/installation;
cp -r libexec/lib/node_modules ./lib;
ln -sf ../lib/node_modules/npm/bin/npm-cli.js ./bin/npm;
ln -sf ../lib/node_modules/npm/bin/npx-cli.js ./bin/npx;
rm -fr AUTHORS etc INSTALL_RECEIPT.json libexec share;
popd;
# 创建 fnm 的文件夹结构(可能还没有安装过node)
mkdir -p ~/.fnm/node-versions;
# 移动最终文件到 fnm 的真正文件夹中
mv ./fnm/node-versions/v${NODE_VERSION} ~/.fnm/node-versions;
popd;
echo "all things done";
fnm ls;

执行完之后,fnm 就获得了当前 homebrew 中最新的 arm64 版本的 Nodejs。

根据你的本地配置修改脚本中的相应参数。

GitHub gist: fnm: install arm64 version of node from homebrew bottles

期待 Nodejs 早日正式支持 mac arm64,以及更多 lts 版本的支持。