背景
事情是这样的,我最近在学习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实现原理
-
n
将所有版本的Node.js都保存在一个目录中(默认是/usr/local/n/versions),每个版本的Node.js被保存在一个以版本号命名的子目录中。 -
切换版本就是把
/usr/local/n/versions/{版本号}/bin/node
二进制文件复制到/usr/local/bin
目录下。
所以n切换版本是全局的,不能按目录设置node版本。
/usr/local/n/versions目录
nvm实现原理
-
安装:当你使用
nvm
安装特定版本的 Node.js 时,它会从 Node.js 的官方网站下载预编译的二进制包,然后将这个包解压到~/.nvm/versions/node
目录下的一个以版本号命名的子目录中。这意味着每个版本的 Node.js 都有各自独立的安装位置。 -
切换版本:当你使用
nvm use <version>
命令切换 Node.js 版本时,nvm
实际上是修改了PATH
环境变量,将选定版本的 Node.js 二进制文件所在路径添加到PATH
最前面。这样,当你在 shell 中输入node
或npm
命令时,就会调用选定版本的 Node.js 或 npm。 -
脚本加载:为了在新的 shell 会话中自动使用
nvm
,需要在用户的 bash 或 zsh 配置文件(如~/.bashrc
或~/.zshrc
)中,添加 sourcingnvm.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...