新MacBook Air到手那天,我想着装个Node、装个Git、装个VS Code,半小时搞定前端环境,结果从下午四点折腾到晚上九点半。中间踩了Command Line Tools安装卡死、Homebrew下载超时、nvm装好后node -v还是旧版本、pnpm装上了但pnpm -v提示command not found这一堆坑。后来换了台预装MacOS 13.7.8的新机器,我把所有步骤整理成了一套一键搭建脚本,重装系统后只要跑一条命令,几分钟就能恢复到能写代码的状态。
这篇文章就是那套方案的完整复盘:在MacOS 13.7.8上做前端环境一键搭建,覆盖组件选型、脚本设计、执行验证和避坑记录。适合刚入手Mac的前端新人,也适合需要批量给团队新同事配环境的人参考。文中所有命令和脚本片段都是我在Apple Silicon和Intel两台机器上实际跑过的,不是从文档里抄出来的理想化步骤。
1. 为什么我要把前端环境搭建写成脚本
1.1 手动搭建的真实成本
先说个实际数字:手动配置一次前端环境,顺利的话90分钟,不顺利的话三小时起步,而且大概率会在某个环节卡住。
我把第一次手动搭建的过程大致拆过账,时间主要花在等待和排查上。Command Line Tools下载安装本身要15到30分钟,Homebrew安装要看GitHub连接状况,有时候光是Updating阶段就卡得让人怀疑网络是不是坏了。nvm和Node装完以为结束了,结果打开新的终端窗口,node -v又变回系统自带的版本,还要回头查PATH配置。等所有工具装齐,Oh My Zsh的主题和插件又花掉半小时。
这些时间加起来,比实际动手敲命令的时间多得多,而且每台机器遇到的报错还不一样,网上一搜答案五花八门,越搜越乱。
1.2 脚本化解决的三个核心问题
一键搭建听起来就是把命令按顺序塞进一个.sh文件,实际设计的时候远没那么简单。我梳理下来,脚本化真正要解决的是三个问题。
第一是依赖顺序。前端环境不是一堆独立工具的集合,很多组件之间有严格的先后关系。比如必须先有Command Line Tools,Homebrew才能编译依赖;必须先有nvm,才能用Node;必须先把Node装好,pnpm才能全局安装。想清楚这个依赖链,脚本才不会在某个步骤上报脆弱的错。
第二是幂等性。所谓幂等,就是同一个脚本跑两遍、三遍,结果是一样的,不会因为某项工具已存在就重复安装导致冲突。我见过很多"一键配置"脚本,第一次跑成功,第二次跑直接报错,原因就是没有判断"是否已安装"这个前置条件。好的脚本里,每个模块都应该检查状态,已安装就跳过。
第三是环境可复现。入职新公司要配新电脑,重装系统后要恢复开发环境,这些场景下,有一份可执行的脚本和没有是两种完全不同的体验。脚本不只是节省时间,它把"我当时到底装了哪些东西"这个记忆负担变成了版本化、可审计的代码。
2. 动手之前,先把系统状态摸清楚
2.1 确认芯片架构和安装路径
MacOS 13.7.8既支持Apple Silicon(M1/M2/M3系列),也支持Intel芯片的老机器,这两类机器在前端环境搭建上有一个关键区别:Homebrew的安装前缀完全不同。
Apple Silicon的Homebrew默认装在/opt/homebrew,Intel的装在/usr/local。这个区别直接影响后续所有环境变量的配置。如果在Apple Silicon机器上继续沿用Intel时代的/usr/local/bin路径,运行brew会提示找不到命令。
判断架构只需要一条命令:
uname -m输出arm64就是Apple Silicon,输出x86_64就是Intel。确认之后,脚本里所有涉及路径的地方都要围绕这个结果来设置:
if [[ "$(uname -m)" == "arm64" ]]; then HOMEBREW_PREFIX="/opt/homebrew" else HOMEBREW_PREFIX="/usr/local" fi很多一键脚本在这个细节上翻车,直接把路径写死,导致部分机器执行到一半报错。凡是标榜"一键"的脚本,架构兼容性是第一道关。
2.2 系统版本对搭建的影响
MacOS 13.7.8属于Ventura的长期维护版本,从环境搭建的角度看,它和14.x、15.x在前端工具链上没有本质差异。系统自带Python3、Ruby、Git、zsh,这些都是前端环境的基础依赖,Ventura之后依然沿用同样的结构。
真正需要注意的是系统和工具链的默认策略。MacOS 13开始,默认shell已经是zsh而不是bash,这意味着所有环境变量的持久化配置要写入~/.zshrc,而不是~/.bash_profile或~/.bashrc。很多教程还停留在改bash配置文件的阶段,跟着做会发现新开的终端窗口根本不生效。
另外一个容易忽视的地方是系统自带的Git版本。Ventura自带的Git可能不是最新版,但对前端开发来说已经够用,不需要额外从源码编译。我的建议是:直接用系统Git,只做用户级配置,不要去动/usr/bin/git,否则容易引入一堆编译依赖问题。
2.3 Command Line Tools:第一块拼图
我见过不少新手跳过了这一步,直接去装Homebrew,结果报错信息指向clang: command not found才意识到少了基础组件。Command Line Tools(CLT)包含了Git、C/C++编译器、make等前端工具链的底层依赖,没有它,Homebrew会失败,node-gyp编译原生模块也会失败。
安装命令很简单:
xcode-select --install系统会弹出图形化安装窗口,等待下载完成即可。比较坑的是下载速度和安装进度不直观,看起来像卡住了,实际上后台还在跑。可以用下面这条命令确认CLT是否已安装:
xcode-select -p如果输出/Library/Developer/CommandLineTools,说明已经就绪。如果提示error: unable to locate,说明还需要安装。
有的机器会遇到弹窗提示"网络连接失败"或者安装失败。这种情况不要反复重试,可以先执行sudo rm -rf /Library/Developer/CommandLineTools把残留清掉,再重新执行xcode-select --install。如果还是不行,去苹果开发者官网下载对应版本的Command Line Tools安装包手动安装,这个笨办法反而最稳。
2.4 网络源与镜像选择
前端环境涉及的下载源非常多:Homebrew的安装脚本在国内连接GitHub经常不稳定,npm官方源的下载速度让人着急,Node安装包也未必能稳定从nodejs.org拉下来。这不是什么玄学问题,在国内网络环境下属于开发者的日常。
我的处理方式很朴素:不折腾特殊工具,用国内镜像源解决。
Homebrew可以配置清华、中科大或阿里的镜像;npm registry用npmmirror(原淘宝镜像);Node的二进制文件从nodejs.cn或npmmirror的二进制镜像下载。这些都是在正式动手搭建之前就应该想清楚的事,否则脚本写到一半被网络问题打断,体验会非常糟糕。
3. 前端环境要装什么:我的选型清单与理由
环境搭建的第一步是明确"要装什么",这比"怎么装"更考验经验。不同技术栈的人需要的环境不完全一样,下面这张表是我针对前端开发整理的刚需清单,每一项后面都会解释为什么这么选。
| 组件 | 选型 | 理由 |
|---|---|---|
| 系统包管理器 | Homebrew | macOS生态最成熟的包管理器,图形应用和命令行工具都能管理 |
| Node版本管理 | nvm | 安装在用户目录,无需sudo权限,可随时切换Node版本 |
| JS包管理器 | pnpm | 安装快、节省磁盘,npm保留作为兜底备选 |
| 版本控制 | 系统Git | 无需额外安装,只需做全局配置和SSH密钥 |
| 终端环境 | zsh + Oh My Zsh | 系统默认shell,插件生态丰富,提升日常操作效率 |
| 编辑器 | VS Code | 前端工具链支持最完善,插件覆盖全面 |
| 容器环境 | Docker(可选) | 需要模拟后端服务时才有必要,资源占用大,所以默认跳过 |
3.1 Node版本管理:选择nvm而不是直接brew install node
很多人图省事,直接用brew install node一把梭。这个方案初期看不出问题,等你在两个项目之间切换Node版本时就痛苦了:A项目要用Node 18,B项目要用Node 22,系统全局只有一个版本,要么升级降级反复折腾,要么就得去查各种骚操作。
nvm(Node Version Manager)把每个版本的Node独立安装在用户目录~/.nvm/versions/node/下,通过修改PATH环境变量快速切换版本,整个过程不需要sudo,不会污染系统目录,逻辑非常干净。
安装nvm的官方命令:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash如果这个地址拉取很慢,可以在安装前把环境变量NVM_SOURCE指向镜像,或者直接下载脚本内容审核后本地执行。装完后编辑~/.zshrc,确认下面这段加载逻辑存在:
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"然后安装最新LTS版本,并设置为默认版本:
nvm install --lts nvm alias default 'lts/*'设置默认版本这个动作很容易漏,漏掉之后每次新开终端还得手动nvm use,体验大打折扣。
3.2 包管理器:pnpm为主,npm兜底
Node装完之后,npm会跟着一并安装。npm在项目中做依赖管理完全够用,但遇到Monorepo或者大依赖项目时,安装速度和磁盘占用会比较吃亏。pnpm的核心优势是"内容寻址存储":所有依赖包都存放在一个全局仓库里,项目通过硬链接引用,多个项目共享同一份依赖,磁盘占用大幅下降,安装速度也明显更快。
全局安装pnpm:
npm install -g pnpm不过npm官方源在国内安装速度不稳定,建议先切换镜像源:
npm config set registry https://registry.npmmirror.com切换之后,安装速度通常会从几十KB/s提升到几MB/s。pnpm本身也会读取npm的registry配置,所以这一条命令同时解决了pnpm的下载速度问题。
对个人的建议是:不要安装yarn。Yarn过去在依赖锁定和安装机制上有优势,但pnpm和npm都吸收了这些优点,新项目直接pnpm,老项目用npm也够用,没必要给环境再增加一个包管理器,少一个工具就少一类版本冲突问题。
3.3 Git配置与SSH密钥
系统自带Git,跳过安装,但有两件事必须做:全局用户信息和SSH Key。没有用户信息,commit时会报错或者写入错误的作者;没有SSH Key,走HTTPS每次push都要输密码,效率极低。
全局配置:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"SSH Key用ed25519算法生成,现代GitHub和GitLab都支持:
ssh-keygen -t ed25519 -C "你的邮箱"生成过程中可以直接按三次回车跳过设置密码。然后把~/.ssh/id_ed25519.pub的内容添加到GitHub或GitLab的SSH Keys里。验证是否成功:
ssh -T git@github.com如果看到Hi username! You've successfully authenticated,说明SSH链路已经通了。这里有个小细节:检查~/.ssh目录权限,如果权限过于开放,SSH会拒绝使用密钥文件,报错通常是Permissions 0664 for 'id_ed25519' are too open,需要执行chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_ed25519。
3.4 终端体验:zsh + Oh My Zsh + 高频插件
MacOS 13.x默认shell就是zsh,所以不用装shell本身,重点在On My Zsh和插件。
Oh My Zsh提供了主题和插件管理,让终端从呆板的黑白变成了有git信息、有命令提示的工作台。安装命令:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"我用的主题是robbyrussell(Oh My Zsh默认主题),没有额外装powerlevel10k,原因是后者需要配置Nerd Font,对前端开发本身没有帮助,属于锦上添花里的过度工程。如果你喜欢折腾,可以自己选;如果你追求实用,默认主题足够。
两个插件建议必装。一个是zsh-autosuggestions,会根据历史命令给出灰色提示,按右方向键直接补全;另一个是zsh-syntax-highlighting,让合法命令显示为绿色、非法命令显示为红色,能直观发现敲错字。
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting然后编辑~/.zshrc里的plugins=(git)这一行,改成:
plugins=(git zsh-autosuggestions zsh-syntax-highlighting)保存后执行source ~/.zshrc生效。注意:如果先执行了source再改plugins,顺序反了,插件不会加载,必须是先改配置文件再source。
3.5 VS Code与code命令行
VS Code是目前前端开发的主流编辑器,核心优势在于插件生态:ESLint、Prettier、Volar(Vue)、TS Language Server这些前端工具链都有完善的支持。
安装方式推荐Homebrew:
brew install --cask visual-studio-code装完之后有个很容易忽略的步骤:在VS Code里按Cmd+Shift+P,输入Shell Command: Install 'code' command in PATH,让终端可以执行code命令。这个能力意味着你可以直接在项目目录输入code .打开整个项目,不用每次用鼠标点图标再找目录,效率差距明显。
3.6 Docker:按需安装,不进默认脚本
很多前端项目需要本地启动Mock服务、Redis或MySQL,这些场景依赖Docker。但Docker Desktop在MacOS上的资源占用和启动速度一直不太理想,如果不确定项目用得到,我不建议放在默认安装列表里。
更合理的做法是给脚本留一个开关参数,需要的时候才安装。后面讲脚本设计时,我会专门说这个开关怎么实现。
4. 一键脚本的完整设计与核心代码
4.1 脚本的整体结构
我设计的脚本分成六个区域:环境检测、Homebrew、nvm与Node、Git配置、应用安装、验证输出。每个区域是一个函数,函数内部先检查当前系统状态,再决定执行还是跳过。
这样设计的好处是易读、易维护、易排错。某个模块出问题时,日志里能看到是哪个函数执行失败,直接定位,不用从头看到尾。
4.2 脚本骨架:颜色日志与环境检测
#!/usr/bin/env bash set -euo pipefail GREEN="\033[32m" RED="\033[31m" YELLOW="\033[33m" RESET="\033[0m" info() { echo -e "${GREEN}[INFO]${RESET} $1"; } warn() { echo -e "${YELLOW}[WARN]${RESET} $1"; } error() { echo -e "${RED}[ERROR]${RESET} $1"; } check_arch() { local arch arch="$(uname -m)" if [[ "$arch" == "arm64" ]]; then HOMEBREW_PREFIX="/opt/homebrew" else HOMEBREW_PREFIX="/usr/local" fi info "检测到芯片架构: $arch" info "Homebrew安装前缀: $HOMEBREW_PREFIX" } install_command_line_tools() { if xcode-select -p &>/dev/null; then info "Command Line Tools 已安装,跳过" else info "正在安装 Command Line Tools..." xcode-select --install warn "请在弹出的窗口中点击安装,并等待完成" until xcode-select -p &>/dev/null; do sleep 5 done info "Command Line Tools 安装完成" fi }set -euo pipefail是bash脚本的安全模式:-e表示任何命令返回非零状态就退出,-u表示使用未定义变量时报错,-o pipefail让管道命令中的任何一个环节失败都能被感知到。不过在用这个模式时要注意,某些安装脚本本身会返回非零状态但其实成功了,所以有些函数内部要加入容错逻辑,不能全交给-e处理。
4.3 Homebrew安装与镜像配置
install_homebrew() { if command -v brew &>/dev/null; then info "Homebrew 已安装,跳过" brew --version | head -n 1 return fi info "开始安装 Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # 将 Homebrew 的 bin 目录加入 PATH if [[ -d "$HOMEBREW_PREFIX/bin" ]]; then export PATH="$HOMEBREW_PREFIX/bin:$PATH" if ! grep -q "HOMEBREW_PREFIX/bin" ~/.zshrc 2>/dev/null; then echo "export PATH=\"$HOMEBREW_PREFIX/bin:\$PATH\"" >> ~/.zshrc fi fi info "Homebrew 安装完成" }这里有一个实操细节:很多人在脚本里导出PATH后发现当前终端能用了,关掉终端再开又不行了。因为export只对当前shell会话生效,要让配置永久生效,必须写入~/.zshrc。上面的代码同时做了这两件事:当前会话和持久化配置。
关于镜像配置,我把它单独放到了脚本后半部分,因为要等Homebrew本身可用之后才能执行brew命令。配置方式如下:
configure_brew_mirror() { export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git" export HOMEBREW_API_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api" export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles" export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git" }这些环境变量不只在安装时需要,日常执行brew install时也需要。所以脚本里我把它们同时追加到了~/.zshrc里,不然下次终端配置一刷新,下载又回到慢速状态。
4.4 nvm、Node与pnpm安装
install_nvm_node() { if [ -s "$HOME/.nvm/nvm.sh" ]; then info "nvm 已安装,跳过" else info "安装 nvm..." curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash fi export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" if ! command -v node &>/dev/null; then info "安装 Node.js LTS..." nvm install --lts fi nvm alias default 'lts/*' info "默认 Node 版本: $(node -v)" } install_pnpm() { if command -v pnpm &>/dev/null; then info "pnpm 已安装: $(pnpm -v)" else info "安装 pnpm..." npm install -g pnpm fi }一个很重要的细节是:脚本执行时,nvm是个shell函数而不是可执行文件,所以在非交互式shell里直接调用nvm命令可能会报command not found。解决方式就是先sourcenvm脚本,这一步不能省,而且要在调用nvm之前完成。
4.5 Git、VS Code与可选Docker
configure_git() { git config --global user.name "${GIT_USERNAME:-你的名字}" git config --global user.email "${GIT_EMAIL:-you@example.com}" if [ ! -f "$HOME/.ssh/id_ed25519" ]; then info "生成 SSH Key..." ssh-keygen -t ed25519 -C "${GIT_EMAIL:-you@example.com}" -N "" -f "$HOME/.ssh/id_ed25519" info "请将以下公钥添加到 GitHub/GitLab:" cat "$HOME/.ssh/id_ed25519.pub" else info "SSH Key 已存在,跳过" fi } install_vscode() { if [ -d "/Applications/Visual Studio Code.app" ]; then info "VS Code 已安装,跳过" else info "安装 VS Code..." brew install --cask visual-studio-code fi } install_docker() { if [ -d "/Applications/Docker.app" ]; then info "Docker 已安装,跳过" else info "安装 Docker Desktop..." brew install --cask docker fi }Git的用户名和邮箱不应该硬编码在脚本里,更合理的做法是通过环境变量传入,脚本里给一个默认值兜底。SSH Key在脚本生成时会直接输出公钥内容,这一步之后需要你自己去GitHub添加。
4.6 幂等性、可选参数与执行入口
脚本做到这个程度,已经具备基本的幂等性:每个安装函数都先检查目标是否已存在,存在就跳过。重复执行不会导致重复安装或冲突。
为了让Docker这类可选组件能自由控制,我加了环境变量开关:
OPTIONAL=${OPTIONAL:-"none"} run() { check_arch install_command_line_tools install_homebrew configure_brew_mirror install_nvm_node install_pnpm configure_git install_vscode if echo "$OPTIONAL" | grep -q docker; then install_docker fi print_versions } run执行方式:
bash install.sh # 需要Docker时 OPTIONAL=docker bash install.shOPTIONAL支持逗号分隔,方便以后扩展更多可选组件。
4.7 验证输出函数
最后一个函数把关键版本统一打印出来,方便确认环境状态。这一步看似简单,实际上价值很大,它能让你在30秒内判断全部组件是否正常。
print_versions() { echo "" info "========== 环境版本验证 ==========" echo "Arch : $(uname -m)" echo "System : $(sw_vers -productName) $(sw_vers -productVersion)" echo "Brew : $(brew --version | head -n 1)" echo "Node : $(node -v)" echo "npm : $(npm -v)" echo "pnpm : $(pnpm -v 2>/dev/null || echo '未安装')" echo "Git : $(git --version)" echo "VS Code : $(code --version 2>/dev/null | head -n 1 || echo 'code命令未配置')" }5. 执行脚本后必须做的验证:别急着进入写代码状态
脚本跑完,终端会打出一堆版本号,但输出的版本号只能说明"命令能找到",不能完全说明环境真的可用。我会额外走一遍验证流程。
5.1 版本体检清单
第一步,新开一个终端窗口,逐项执行下面这张表的命令。这里强调"新开窗口",是因为脚本里的export只在当前shell生效,新窗口加载~/.zshrc后才是真正的日常状态。
| 命令 | 预期输出 | 不正常的信号 |
|---|---|---|
node -v | v22.x.x(LTS版本) | command not found,或显示系统旧版本 |
npm -v | 10.x.x | command not found |
pnpm -v | 9.x.x | command not found,说明pnpm全局bin目录不在PATH |
git --version | git version 2.x.x | command not found |
code --version | 1.x.x | command not found,说明还没安装code命令 |
brew --version | Homebrew 4.x.x | command not found,说明PATH配置有问题 |
ssh -T git@github.com | Hi username! | Permission denied,SSH密钥未生效 |
如果node -v显示的不是nvm管理的版本,最常见的原因是系统自带Node位于/usr/local/bin(Intel机器)且PATH顺序里它排在nvm前面。可以执行which -a node看看所有路径,然后检查~/.zshrc中nvm的加载语句是否真的生效。正常的which node应该指向~/.nvm/versions/node/...。
5.2 用真实项目跑通依赖安装链路
版本命令全部正常,建议再克隆一个真实的前端项目,把依赖安装、本地启动跑一遍,这是最接近日常开发的环境验证。
git clone git@github.com:vuejs/create-vue.git cd create-vue pnpm install pnpm devpnpm install阶段如果出现node-gyp相关的编译错误,通常意味着系统缺少Python或C++编译环境。虽然Command Line Tools自带clang,但部分原生模块仍需要Python3。MacOS 13自带Python3,一般不用额外处理;如果报错提示找不到python,执行brew install python3即可。
还有一个高频问题是pnpm在安装过程中提示Cannot read properties of null或Failed to resolve之类,这往往不是pnpm本身的问题,而是registry配置指向了一个失效的源。检查一下npm config get registry,确保输出是https://registry.npmmirror.com或官方源。
5.3 验证变量持久化
环境变量持久化是很多人忽略的验证项。前面的脚本已经把需要的变量写入了~/.zshrc,但最好确认一下:
grep -n "NVM_DIR\|HOMEBREW_PREFIX\|pnpm" ~/.zshrc如果你看到类似下面的输出,说明持久化配置正常:
export NVM_DIR="$HOME/.nvm" export PATH="/opt/homebrew/bin:$PATH" export PATH="$HOME/Library/pnpm:$PATH"特别注意pnpm的全局bin目录。pnpm通常把全局可执行文件放在~/Library/pnpm(Apple Silicon)或者~/.local/share/pnpm,这个目录如果不在PATH里,通过pnpm i -g安装的任何命令行工具都无法直接执行。
6. 我在这套脚本上踩过的坑:从日志里翻出来的问题
这一部分是我最想写的。很多环境搭建教程只给标准路径,但真实世界里的报错五花八门。下面几个坑是我在MacOS 13.7.8上反复踩过的,几乎每条都和文档里的"理想流程"对不上。
6.1 Homebrew安装卡在Updating阶段
现象:执行Homebrew安装脚本后,终端长时间停留在==> Tapping homebrew/core或Updating Homebrew...,进度条不动,看着像死机。
原因:Homebrew默认从GitHub拉取仓库,国内网络环境下git fetch经常超时。
解决:安装前先设置镜像变量。我在脚本里把HOMEBREW_BREW_GIT_REMOTE和HOMEBREW_CORE_GIT_REMOTE指到清华镜像,安装过程从20分钟缩短到3分钟。还有一个思路:如果你的网络环境实在差到连curl都拉不动安装脚本,可以去第三方平台找用户备份好的安装脚本,或者从镜像站下载Homebrew的tar包手动解压。手动解压方式有点麻烦,但也是可行方案。
6.2 nvm装好了,node -v却显示系统旧版本
现象:脚本执行完,nvm list能看到nvm管理的新Node版本,但新开终端后node -v显示的是v18.x甚至更老的系统自带版本。
原因:Mac系统自带Node(一般在/usr/local/bin,Intel机器)或者之前手动装过brew install node,这两个路径在PATH中的优先级高于~/.nvm/versions/node。
解决:先执行which -a node查看所有Node路径,然后检查~/.zshrc中nvm的加载语句是不是在PATH设置之前。我的经验是:把nvm的加载语句尽量放在~/.zshrc靠前的位置,并且在追加Homebrew PATH时不要用覆盖语法,要用切片语法保留已有PATH:
export PATH="$HOMEBREW_PREFIX/bin:$PATH"这里顺便说一个新手容易搞错的点:export PATH="/opt/homebrew/bin:$PATH"和export PATH="$PATH:/opt/homebrew/bin"的优先级是相反的。前者让Homebrew的路径排在最前面,正确;后者会让系统路径优先,如果系统路径里有同名的旧命令,就会出现"明明装了新版却执行旧版"的问题。
6.3 pnpm全局安装的命令找不到
现象:pnpm i -g typescript执行成功,但输入tsc -v提示command not found。
原因:pnpm的全局bin目录没有加入PATH。pnpm的全局bin目录在~/Library/pnpm(Apple Silicon),它不在系统默认PATH范围内。
解决:执行pnpm config get global-bin-dir查看当前全局bin目录,然后把它加入~/.zshrc:
export PATH="$HOME/Library/pnpm:$PATH"这里还有个隐藏问题:如果你把pnpm本身也放在这个目录里,删除重装pnpm时会遇到Windows风格的"文件占用"问题。实际表现是npm uninstall -g pnpm后,pnpm命令还在,因为指向同一个目录的硬链接没有清理干净。解决方法是先删掉~/Library/pnpm下的相关文件,再重新安装。
6.4 oh-my-zsh的插件不生效
现象:~/.zshrc里明明有plugins=(git zsh-autosuggestions zsh-syntax-highlighting),但终端没有自动建议、命令也没有颜色高亮。
原因:插件目录没有正确clone到~/.oh-my-zsh/custom/plugins/。尤其是网络不稳定的情况,git clone在后台静默失败,但脚本没有检测git clone的返回值,于是你以为装上了。
解决:安装完插件后,先检查目录存在再继续:
ls ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/如果目录为空或不存在,重新clone。然后在~/.zshrc里确认插件名和目录名完全一致,包括大小写和连字符。zsh-autosuggestions这个目录名就和它的插件名一致;如果把插件名写成了autosuggestions,虽然看起来像,但Oh My Zsh找不到对应目录就不会加载。
6.5 Command Line Tools出现license弹窗
现象:第一次运行git或clang时,终端会弹出一个让人误以为是系统卡死的授权协议界面,有"Agree"和"Disagree"两个选项,但键盘怎么按都没反应。
原因:CLT安装后第一次使用时需要接受Xcode license,但终端窗口里的license提示并不支持键盘选择,需要用鼠标点击或者通过命令方式接受。
解决:直接sudo xcodebuild -license accept提前接受协议,就不会有弹窗打断了。我通常把它放在脚本里CLT安装完成之后自动执行。
6.6 Docker Desktop安装后启动异常
现象:通过brew install --cask docker装好了Docker Desktop,但双击启动后一直转圈或提示Docker Desktop requires a newer macOS version。
原因:Docker Desktop对macOS版本有最低要求,有些版本要求13或更高,13.7.8本身满足条件,但部分机器的内存或虚拟化特性不满足Docker Desktop的要求。
解决:这类问题通常出现在老旧Intel Mac上,处理方式是检查sysctl kern.hv_support,如果返回0说明当前机器不支持Hypervisor.framework,Docker Desktop基本没法用,只能考虑改用云环境或轻量替代方案。所以我才把Docker设计成脚本里的可选项,能不用就不折腾。
最后再分享一点经验
这套脚本前前后后跑了半年,在Apple Silicon和Intel两台机器上各执行过好几次。第一次线上跑通大概花了七八分钟,后续再跑时因为大部分组件已经存在,30秒内就能完成全部验证输出。日常维护也很简单,哪个工具出了新版本,直接在函数里把版本号或者安装命令更新一下就好。
有一点必须提醒:脚本是我自己写的,我清楚每一步在做什么,所以敢直接执行。如果你从网上下载了别人的一键配置脚本,或者从本文复制代码,强烈建议先通读一遍再跑,尤其是涉及curl ... | bash这种直接执行的写法。不是说不信任别人,而是你至少要知道这个脚本会在你的机器上创建哪些目录、修改哪些配置、执行哪些命令。这个习惯比任何脚本都值钱。
另外,这个脚本只是一个起点。前端生态变化很快,也许过两年nvm被更快的运行时管理器取代、pnpm成为官方内置工具,到时候改造思路依然是一致的:环境检测控制依赖顺序、幂等逻辑控制重复安装、版本验证保证结果可见。把这三件事想明白,不管环境怎么变,一套脚本就能长期用下去。