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

相关推荐
学不会•13 分钟前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜3 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点3 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow3 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o3 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic4 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā4 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年5 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder5 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727575 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架