06 - 开发环境实战

工具链落位是 WSL 实战的第一课:Node、Python、Go、Rust、Java、C++ 各有各的安装哲学;数据库放 Linux 侧才有性能;代码必须放 ~/ 而不是 /mnt/c——这一条铁律背后是 9p 协议与 inotify 的双重陷阱。本章逐个给出可复制的安装命令,并串起 Windows Terminal 集成与前后端混编联调的完整场景。


6.1 语言工具链逐个落位

Node.js:nvm + corepack

不要用 apt install nodejs,版本太旧且升级痛苦。nvm 是社区事实标准:

# 安装 nvm(以官方脚本为准,版本号可能更新)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
 
# 重载 shell 后
source ~/.bashrc
nvm install --lts      # 装 LTS 版本并设为默认
nvm install 20         # 多版本并存
nvm use 20             # 切换
 
node -v && npm -v

包管理器用 corepack 启用 pnpm(Node 16.13+ 内置):

corepack enable
corepack prepare pnpm@latest --activate
pnpm -v

项目里若有 "packageManager": "pnpm@9.x.x" 字段,corepack 会自动锁定版本,团队协作不再吵架。

Python:pyenv + venv

pyenv 从源码编译 Python,先补齐编译依赖:

sudo apt update
sudo apt install -y build-essential libssl-dev zlib1g-dev \
  libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
  libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \
  libffi-dev liblzma-dev git
 
curl https://pyenv.run | bash

按脚本提示把以下内容追加到 ~/.bashrc

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

日常流:

pyenv install 3.12.5
pyenv global 3.12.5          # 全局默认
pyenv local 3.11.9           # 项目目录内固定版本(写入 .python-version)
 
python -m venv .venv          # 项目虚拟环境
source .venv/bin/activate
pip install requests
deactivate                    # 退出虚拟环境

数据科学向可以选 miniconda 替代 pyenv+venv:

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh -b -p ~/miniconda3
~/miniconda3/bin/conda init bash
conda create -n ds python=3.12 numpy pandas matplotlib jupyterlab
conda activate ds

Go:官网 tar 包直装

apt 的 golang 版本通常落后一到两个大版本,直接从官网装:

wget https://go.dev/dl/go1.23.0.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.23.0.linux-amd64.tar.gz
rm go1.23.0.linux-amd64.tar.gz

PATH 追加到 ~/.bashrc

export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin

这里正是 PATH 机制 的日常应用:/usr/local/go/bin 提供 go 命令本体,~/go/bin 收纳 go install 装出的二进制工具。

验证与初始化项目:

go version
mkdir ~/hello && cd ~/hello
go mod init example.com/hello
go run .

Rust:rustup 标准路径

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
rustc --version && cargo --version

rustup 默认把工具链放在 ~/.rustup,可执行文件放 ~/.cargo/bin 并自动注入 PATH。升级一条命令:

rustup update

再次呼应 PATH 机制~/.cargo/env 做的事情就是向 PATH 追加一个目录,理解了 PATH 就能看懂几乎所有工具链安装脚本。

Java:SDKMAN 多版本管理

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
 
sdk list java                 # 浏览可用发行版与版本
sdk install java 21.0.4-tem   # Eclipse Temurin JDK 21
sdk install java 17.0.12-tem  # 再装一个 LTS 并存
sdk use java 17.0.12-tem      # 当前 shell 切换
sdk default java 21.0.4-tem   # 设为默认
java -version

Spring Boot 项目跑通要点:

# 安装 Maven 或 Gradle 也交给 sdkman
sdk install maven
 
git clone https://github.com/spring-guides/gs-rest-service.git
cd gs-rest-service/complete
./mvnw spring-boot:run
# Windows 浏览器打开 http://localhost:8080/greeting —— localhost 直通生效

要点清单:JDK 用 sdkman 管理保证版本可控;./mvnw wrapper 锁定构建工具版本;默认端口 8080 可从 Windows 直接访问。更系统的 Java 内容见 Java 教程

C/C++:build-essential 全家桶

sudo apt install -y build-essential gdb cmake clang-format
gcc --version && g++ --version && cmake --version

VS Code 侧配合调试(扩展必须装在 WSL 侧,见 05 章):

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "g++ build & debug",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/build/app",
      "cwd": "${workspaceFolder}",
      "MIMode": "gdb",
      "preLaunchTask": "build"
    }
  ]
}

配合 CMake Tools 扩展则更省心:选择 kit → Configure → F5 一路点下去,断点落在 Linux 的 gdb 里。


6.2 数据库开发环境

MySQL / PostgreSQL 装在 WSL 内

# PostgreSQL
sudo apt install -y postgresql postgresql-contrib
sudo service postgresql start        # 未启用 systemd 时
sudo -u postgres psql                # 进入交互
# CREATE DATABASE devdb;
# CREATE USER dev WITH PASSWORD 'dev123';
# GRANT ALL PRIVILEGES ON DATABASE devdb TO dev;
 
# MySQL
sudo apt install -y mysql-server
sudo service mysql start
sudo mysql_secure_installation

数据文件必须留在 Linux 侧。apt 安装的默认 datadir 在 /var/lib/postgresql/var/lib/mysql,都在 ext4 上,无需动它。若手动改过配置指向 /mnt/c,数据库会慢到怀疑人生且可能锁损坏。

/mnt/wsl/ 共享挂载点说明

WSL2 提供了一个发行版间的共享挂载树:

mount | grep /mnt/wsl
ls /mnt/wsl/

多个发行版通过它共享某些内核级挂载(如 GPU 驱动设备)。它是 WSL 内部机制,不是给用户当”跨发行版 U 盘”用的,日常开发不必依赖;跨发行版共享文件走 ~/ 加 tar 打包或 git 更可靠。

DBeaver 从 Windows 连接

两条路:

  1. localhost 直通(推荐,WSL2 默认支持):DBeaver 新建连接,Host 填 localhost,端口填 5432/3306 即可。localhost 直通让 Windows 到 WSL 的 TCP 无需任何配置。
  2. portproxy:仅当直通失效(老版本 Windows 或 mirrored 网络特殊配置)时启用:
netsh interface portproxy add v4tov4 listenport=5432 listenaddress=127.0.0.1 `
  connectport=5432 connectaddress=(wsl hostname -I).Trim()

注意 PostgreSQL 还要允许监听非本地连接时改 listen_addresses = '*' 并配 pg_hba.conf;只用 localhost 直通时保持默认最安全。

Redis 一条龙

sudo apt install -y redis-server
sudo service redis-server start
 
redis-cli ping                       # PONG
redis-cli set hello wsl
redis-cli get hello
 
# 持久化按需开启(AOF)
# sudo sed -i 's/^appendonly no/appendonly yes/' /etc/redis/redis.conf

Redis 单线程内存库,即使放默认位置也几乎无 IO 瓶颈,但持久化文件(dump.rdb)同样应留在 ext4。


6.3 项目位置铁律:代码放 ~/ 不放 /mnt/c

为什么

WSL 访问 /mnt/c 走 9p 文件协议,每次系统调用都要跨越 Linux 内核到 Windows NTFS 的边界。后果有两个:

  1. IO 慢 5-10 倍npm installgit status、编译全量读写都显著变慢
  2. inotify 失效:9p 不支持完整的文件变更通知,vite/webpack 的 hot reload 监听不到文件变化,页面不自动刷新

实测参考对比(同机器、同一 vite 项目冷启动 + 全量构建):

操作~/project (ext4)/mnt/c/project (9p)差距
npm install18 s96 s约 5 倍
git status0.15 s1.8 s约 12 倍
vite 冷启动1.2 s无法热更新功能性故障
tsc 全量编译22 s130 s约 6 倍

数字随硬件浮动,量级结论稳定:代码在 Linux 侧,Windows 只当显示器和键盘

正确姿势

mkdir -p ~/work && cd ~/work
git clone https://github.com/your/repo.git
code .

需要用 Windows 图形工具(如 Photoshop 改设计稿)处理这些文件时,走 \\wsl$\Ubuntu\home\you\work\ 网络路径访问,方向反过来了:Linux 进程读 Linux 文件快,Windows 进程经 9p 反向访问也可接受(低频操作无妨)。

反向同理:Windows 侧的 IDE 项目(如 Visual Studio 大型工程)就让它待在 NTFS,别搬进 WSL。


6.4 浏览器访问 WSL dev server

localhost 直通演示:

cd ~/work && pnpm create vite demo --template react
cd demo && pnpm install && pnpm dev
# VITE ready on http://localhost:5173/

此刻在 Windows 浏览器地址栏输入 http://localhost:5173 直接打开。流量链路:

flowchart LR
    B["Windows 浏览器"] -->|"localhost:5173"| H["Windows localhost 直通层"]
    H -->|"自动转发"| V["WSL 内 vite :5173"]
    V -->|"热更新 WebSocket"| B

Node/Spring Boot/Flask/Django 同理,只要服务绑定在 0.0.0.0 或 localhost 上均可。唯一例外是绑定到 WSL eth0 具体 IP 的服务,此时用该 IP 访问而非 localhost。

防火墙拦不住 localhost 直通;但局域网其他设备想访问你的 dev server,仍需 portproxy + 防火墙规则,见 05 章


6.5 API 密钥管理

.env 文件实践

密钥绝不进 git。项目根建 .env,同时提供 .env.example(不含真值)进仓库:

# .env(已在 .gitignore 中)
DATABASE_URL=postgres://dev:dev123@localhost:5432/devdb
OPENAI_API_KEY=sk-xxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxx

Node 侧 dotenv,Python 侧 python-dotenv 自动加载。.env 实践 的核心原则:环境变量是进程级配置,文件只是它的载体,载体的权限和作用域要收窄:

chmod 600 .env      # 仅属主可读写

direnv:目录级环境自动加载

比 .env 更进一步,进入目录自动 source、离开自动卸载:

sudo apt install -y direnv
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
exec $SHELL
 
cd ~/work/demo
echo 'export OPENAI_API_KEY=sk-xxx' > .envrc
direnv allow
# 之后 cd 进来自动带上变量,cd 离开自动清除

好处:变量不落到任何全局 shell 配置里,不同项目互不污染,密钥泄漏面最小化。.envrc 同样要进 .gitignore


6.6 Windows Terminal 集成

Profile 自动生成

安装新版 WSL 发行版后,Windows Terminal 会自动为其生成 profile(名字如 Ubuntu-22.04),下拉菜单直接可选。若没有,设置 → 添加新配置文件 → 新空配置文件 → 命令行填 wsl.exe -d Ubuntu-22.04

Nerd Font 装在 Windows 侧

终端渲染发生在 Windows,字体也必须装在 Windows:下载 Nerd Font(如 JetBrainsMono Nerd Font),右键 ttf → 为所有用户安装,然后在 Terminal profile → 外观 → 字体选择它。starship、powerlevel10k 等 prompt 的图标才能正常显示。WSL 内不需要装字体。

默认启动目录设为 \wsl$

profile → 常规 → 启动目录填:

\\wsl$\Ubuntu-22.04\home\yourname\work

或更简单地在 profile 命令行后加参数:

{
  "name": "Ubuntu Dev",
  "commandline": "wsl.exe -d Ubuntu-22.04 --cd ~",
  "font": { "face": "JetBrainsMono Nerd Font" },
  "startingDirectory": "//wsl$/Ubuntu-22.04/home/yourname/work"
}

打开即落在 Linux 家目录,省掉每次 cd。


6.7 全栈混编联调场景

典型组合:前端 vite 跑在 WSL(享受 ext4 性能与热更新),后端 Spring Boot 跑在 Windows IDEA(公司统一 IDE、连 Windows 上的内网认证)。

flowchart LR
    subgraph WSL["WSL2"]
        FE["vite dev server :5173"]
    end
    subgraph WIN["Windows"]
        BE["Spring Boot :8080<br/>IDEA 运行"]
        BR["浏览器"]
    end
    BR -->|"http://localhost:5173"| FE
    FE -->|"代理 /api → localhost:8080"| BE
    BE -->|"返回 JSON"| FE

互通方案要点:

  1. 端口双向可达:localhost 直通对 Windows→WSL 生效;WSL→Windows 方向,WSL2 中 Windows 主机网关 IP 可由 /etc/resolv.conf 的 nameserver 获得,或直接用 host.docker.internal 风格的名称(mirrored 模式下直接 localhost 双通)
# WSL 内拿到 Windows 主机 IP(NAT 模式)
WIN_IP=$(grep nameserver /etc/resolv.conf | awk '{print $2}')
curl http://$WIN_IP:8080/api/health
  1. vite 代理配置
// vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      "/api": {
        target: `http://${process.env.WIN_IP ?? "localhost"}:8080`,
        changeOrigin: true,
      },
    },
  },
});
  1. CORS 免烦恼:走 vite 代理后浏览器看到的是同源请求,后端无需单独开 CORS。
  2. 数据库归属:PostgreSQL 建议放 WSL,Windows 后端用 localhost:5432 直连即可,方向仍是直通支持的。

Java 生态的系统学习见 Java 教程


6.8 性能预期管理

WSL2 不是裸机,但差距远小于直觉。社区大量基准测试的共识区间:

维度WSL2 相对裸机说明
CPU 计算/编译慢 5-15%Hyper-V 虚拟化开销,正常范围
磁盘 IO(ext4 内)慢 5-10%virtio 接近原生
网络视模式而定NAT 有轻微开销,mirrored 接近原生
内存有静态底噪VM 常驻,可用 .wslconfig 控制

编译速度慢 5-15% 属于预期之内,不值得为此焦虑;真正的性能悬崖只出现在一种情况——代码放错位置(/mnt/c),那是以倍数计的损失,回到 6.3 节的铁律即可解决。


6.9 小结

  • 语言工具链各归其位:nvm 管 Node、pyenv/miniconda 管 Python、官网 tar 管 Go、rustup 管 Rust、sdkman 管 Java、build-essential 管 C++
  • 数据库装 WSL 内、数据留 ext4;DBeaver 用 localhost 直通零配置连接
  • 铁律:代码必须在 ~/,9p 协议让 /mnt/c 上的项目 IO 慢 5-10 倍且热更新失灵
  • localhost 直通使浏览器无缝访问 dev server;混编开发靠 vite 代理打通前后端
  • 密钥用 .env + chmod 600 或 direnv 目录级管理;字体装 Windows 侧、启动目录设 \wsl$
  • 编译比裸机慢 5-15% 是虚拟化的正常代价,别把放错位置的锅甩给 WSL 本身

下一篇 07-WSL与虚拟机协作 讨论 WSL2、VirtualBox、VMware 与双系统的共存与抉择。