我写了一个根据项目自动切换node版本的插件,再也不用手动使用命令切换了。

背景

事情是这样的,我最近在学习Next.js,启动Next.js项目需要node版本不低于v18.17.0,然而我们公司里的老项目要求node版本不能高于v16.20.0版本。于是我上班的时候使用的版本是v16.20.0,下班学习Next使用的版本是v20.0.0。

最开始切换node版本工具,我使用的是n,但是每次上班或下班启动项目时,总是会忘记切换版本,导致启动报错,这让我觉得很麻烦,于是我打算写一个能根据项目自动切换node版本的插件。

n和nvm实现原理

n实现原理

  1. n将所有版本的Node.js都保存在一个目录中(默认是/usr/local/n/versions),每个版本的Node.js被保存在一个以版本号命名的子目录中。

  2. 切换版本就是把/usr/local/n/versions/{版本号}/bin/node二进制文件复制到/usr/local/bin目录下。

所以n切换版本是全局的,不能按目录设置node版本。

/usr/local/n/versions目录

nvm实现原理

  1. 安装:当你使用 nvm 安装特定版本的 Node.js 时,它会从 Node.js 的官方网站下载预编译的二进制包,然后将这个包解压到 ~/.nvm/versions/node 目录下的一个以版本号命名的子目录中。这意味着每个版本的 Node.js 都有各自独立的安装位置。

  2. 切换版本:当你使用 nvm use <version> 命令切换 Node.js 版本时,nvm 实际上是修改了 PATH 环境变量,将选定版本的 Node.js 二进制文件所在路径添加到 PATH 最前面。这样,当你在 shell 中输入 node 或 npm 命令时,就会调用选定版本的 Node.js 或 npm。

  3. 脚本加载:为了在新的 shell 会话中自动使用 nvm ,需要在用户的 bash 或 zsh 配置文件(如 ~/.bashrc 或 ~/.zshrc)中,添加 sourcing nvm.sh 的代码行。这样可以在启动新 shell 时加载 nvm 环境。

通过这种方式,nvm 能够在不同的 shell 会话中使用各自的 Node.js 和 npm 版本,且版本间相互独立。

~/.nvm/versions/node目录

node脚本

实现思路

写一个node脚本,从项目根目录读取package.json里的nodeVersion属性,如果存在,使用n管理工具切换版本,然后在启动命令前执行这个脚本。

具体实现

因为这个方案有问题,所以只实现了个demo版本。

js 复制代码
const { spawn } = require('child_process');
const { readFileSync } = require('fs');
const path = require('path');
const { cwd } = require('process');

// 从项目根目录读取 package.json里的 nodeVersion,如果存在,使用n管理工具切换版本
let packageJson = readFileSync(path.join(cwd(), '/package.json')).toString();

if (packageJson) {
  packageJson = JSON.parse(packageJson);
}

if (packageJson.nodeVersion) {
  // 执行n命令
  spawn('n', [packageJson.nodeVersion]);
}

在项目根目录下package.json文件中定义node版本

修改启动项目命令

小结

这种方式需要给每个命令前加上切换版本的命令,需要也可以,但是太不优雅了,改的东西也比较多。如果能在所有npm run之前执行切换版本脚本就好了,找了很多方法,并且也看了npm run-script源码,没有找到办法,所以放弃了这条路。

vscode插件

实现思路

上面方案行不通,我就想了另外一个方案,一般我们启动项目都是在vscode中,所以如果能在用户打开终端时,获取当前项目里定义的node版本,自动把当前node版本切换成对应版本就行了。切换版本方案最开始想到的是用node执行命令切换,但是这样有一个问题,只能全局切换,因为使用nvm use临时切换不行,然而全局切换会影响其他项目,这个方案也不行。

查了一些资料,vscode插件开发中,可以给打开的终端发送想要执行的命令,然后终端默认执行。因为在当前终端中执行的切换命令,所以不会影响全局。

具体实现

创建vscode插件项目

sh 复制代码
npm install yo -g
yo code

代码实现

js 复制代码
// extension.js

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const { existsSync, readFileSync } = require('fs');
const { join } = require('path');
const vscode = require('vscode');
const { exec } = require('child_process');

// 判断某个命令是否存在shell中
function commandExistsInShell(shell, command, callback) {
    let execCommand = `type ${command}`;

    if (shell.includes('zsh')) {
      execCommand = `source ~/.zshrc &&  type ${command}`;
    }

    exec(execCommand, { shell }, (error, stdout, stderr) => {
       if (error) {
         console.warn(`Error: ${error.message}`);
         return callback(false);
       }
       if (stderr) {
          console.warn(`Stderr: ${stderr}`);
          return callback(false);
       }
       callback(true);
    });
  }

function activate() {
  // 监听打开的terminal
  vscode.window.onDidOpenTerminal((terminal) => {
    // 获取工作区目录
    if (!vscode.workspace.workspaceFolders) {
      return;
    }

    // 获取工作区目录路径
    const path = vscode.workspace.workspaceFolders[0].uri.fsPath;

    // 获取package.json
    const packageJsonFilePath = join(path, '/package.json');

    // 检查打开的项目中是否有package.json
    if (!existsSync(packageJsonFilePath)) {
      return;
    }

    // 读取package.json
    let packageJsonString = readFileSync(
      packageJsonFilePath,
      'utf8'
    ).toString();

    if (!packageJsonString) {
      return;
    }

    try {
      // 解析package.json
      const packageJson = JSON.parse(packageJsonString);

      // 检查是否有nodeVersion
      if (!packageJson.nodeVersion) return;

      const { nodeVersion } = packageJson;

      // 检查当前打开的shell中nvm是否存在
      // @ts-ignore
      const { shellPath } = terminal.creationOptions;

      commandExistsInShell(shellPath, 'nvm', (exists) => {
        if (exists) {
          // 检查版本是否存在,兼容zsh和fish终端
          let command = `source ~/.nvm/nvm.sh && nvm ls ${nodeVersion}`;

          if (shellPath.includes('fish')) {
            command = `nvm ls ${nodeVersion}`;
          }

          exec(command, { shell: shellPath }, (error, stdout, stderr) => {
            // 如果当前版本不存在,安装版本
            if (error) {
              vscode.window
                .showErrorMessage(
                  `${nodeVersion}版本不存在,是否需要安装?`,
                  '是',
                  '否'
                )
                .then((value) => {
                  if (value === '是') {
                    // 安装版本,并切换
                    terminal.sendText(`nvm install ${nodeVersion}`, true);
                  }
                });
              return;
            }
            if (stderr) {
              vscode.window
                .showErrorMessage(
                  `${nodeVersion}版本不存在,是否需要安装?`,
                  '是',
                  '否'
                )
                .then((value) => {
                  if (value === '是') {
                    // 安装版本,并切换
                    terminal.sendText(`nvm install ${nodeVersion}`, true);
                  }
                });
              return;
            }

            if (stdout) {
              // 切换版本
              terminal.sendText('nvm use ' + packageJson.nodeVersion, true);
            }
          });
        } else {
          vscode.window
            .showErrorMessage(
              'nvm命令不存在,请先安装。安装教程:',
              '查看安装教程',
              '否'
            )
            .then((value) => {
              if (value === '查看安装教程') {
                vscode.env.openExternal(
                  vscode.Uri.parse('https://github.com/nvm-sh/nvm')
                );
              }
            });
        }
      });
    } catch (error) { }
  });
}

// this method is called when your extension is deactivated
function deactivate() { }

module.exports = {
  activate,
  deactivate,
};

效果展示

最后

插件已经上传到vscode插件市场了,需要的可以前往marketplace.visualstudio.com/items?itemN...这个地址下载使用,或者到插件市场搜索auto-switch-node-version安装。

使用插件前需要先安装nvm,nvm安装教程可以到这里查看。

代码仓库地址:github.com/dbfu/auto-s...

相关推荐
deli00739 分钟前
手机拍灯为什么有一条条黑纹?PWM 240Hz 实测
前端
wangruofeng5 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng6 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈9 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技9 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569159 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马11 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng12 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding13 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js
鶴哥只手遮天13 小时前
《Electron 45.2.3 内核解剖》第 4 章 补丁体系:380 个补丁改了什么
前端·electron