我写了一个根据项目自动切换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...

相关推荐
福兮说5 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理5 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6666 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)6 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan7 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_7 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒8 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技8 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu8 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
niyongsheng9 小时前
后端零改动,给若依换一套现代化前端
vue.js·开源·node.js