我写了一个根据项目自动切换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 中输入 nodenpm 命令时,就会调用选定版本的 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...

相关推荐
茶卡盐佑星_几秒前
说说你对es6中promise的理解?
前端·ecmascript·es6
Манго нектар28 分钟前
JavaScript for循环语句
开发语言·前端·javascript
蒲公英100136 分钟前
vue3学习:axios输入城市名称查询该城市天气
前端·vue.js·学习
天涯学馆1 小时前
Deno与Secure TypeScript:安全的后端开发
前端·typescript·deno
以对_1 小时前
uview表单校验不生效问题
前端·uni-app
程序猿小D2 小时前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
奔跑吧邓邓子2 小时前
npm包管理深度探索:从基础到进阶全面教程!
前端·npm·node.js
前端李易安3 小时前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp
汪子熙3 小时前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ3 小时前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞