大家好,我是Cn。今天早上很有幸我读到了一篇文章,然后根据作者的建议,我就写了一个脚本进行自动化处理。可以进行通用化处理的一个方式,可以让掘金的小伙伴们应用到各自的项目中就可以了。
背景
来自 water大佬写的一篇掘金文章# 你需要知道的 Node 版本管理工具 fnm------一次彻底的前端工程环境升级,我觉得对于团队项目使用的pnpm和node版本管理进行统一化管理是挺有效的。为此,我想了一下处理方式,就感觉能把整个判断流程串起来,实现自动化"傻瓜式"处理,我感觉还是挺好用的。为此,我研究了一下,写了一个"类似"自动化的脚本。
一、脚本实现步骤
js
/**
脚本的执行顺序是:
检查并更新 package.json 中的 packageManager
检查并创建 .nvmrc 文件
检查并安装 fnm(如果未安装)
检查并更新 .zshrc 中的 fnm 配置
检查并安装 Node.js 18.17.0(如果未安装)
检查并启用 corepack
检查并更新 .gitignore 文件
*/
// 执行所有检查
function main() {
console.log("🚀 Running post-install checks...");
try {
checkAndUpdatePackageManager();
checkAndCreateNvmrc();
checkAndInstallFnm();
checkAndUpdateZshrc();
checkAndInstallNodeVersion();
checkAndEnableCorepack();
checkAndUpdateGitignore();
console.log("✨ All checks completed successfully!");
} catch (error) {
console.error("❌ Error during post-install checks:", error);
process.exit(1);
}
}
下面我就按照顺序的处理给大家一一解答每一步的处理方式。
前提 - 项目需要require相关模块进行处理
ini
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const os = require("os");
第一步:检查并更新 package.json 中的 packageManager
因为在Node 16.10+内置了corepack,可以直接用其对pnpm进行管理和激活。他会根据package.json
中packageManager
字段,对此进行管理和激活,为此第一步,我们就要在package.json
中配置该字段
js
// 检查并更新 package.json 中的 packageManager
function checkAndUpdatePackageManager() {
const packageJsonPath = path.join(process.cwd(), "package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const { pnpm } = getVersionConfig();
if (!packageJson.packageManager) {
packageJson.packageManager = `pnpm@${pnpm}`;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
console.log(`✅ Added packageManager to package.json with pnpm@${pnpm}`);
} else {
console.log("✅ packageManager already exists in package.json");
}
}
通过获取目录下的package.json
文件,读取其内容并且解析,判断有没有已经写入了packageManager
字段,如果没写入,我们就帮其填入项目想要设置和维护的pnpm
版本,那这里就会通过getVersionConfig
进行获取。
getVersionConfig - 获取用户设定的pnpm和node版本
js
// 默认版本配置
const DEFAULT_VERSIONS = {
pnpm: "8.6.12",
node: "18.17.0",
};
// 版本配置文件路径
const VERSION_CONFIG_PATH = path.join(process.cwd(), "RN_CONFIG_VERSION.json");
// 读取或创建版本配置
function getVersionConfig() {
try {
let config = {};
// 如果配置文件存在,读取它
if (fs.existsSync(VERSION_CONFIG_PATH)) {
config = JSON.parse(fs.readFileSync(VERSION_CONFIG_PATH, "utf8"));
}
// 检查并填充缺失的版本配置
let hasChanges = false;
if (!config.pnpm) {
config.pnpm = DEFAULT_VERSIONS.pnpm;
hasChanges = true;
}
if (!config.node) {
config.node = DEFAULT_VERSIONS.node;
hasChanges = true;
}
// 如果有更改,写入配置文件
if (hasChanges) {
fs.writeFileSync(VERSION_CONFIG_PATH, JSON.stringify(config, null, 2));
console.log("✅ Updated version configuration file");
}
return config;
} catch (error) {
console.error("❌ Error handling version configuration:", error.message);
return DEFAULT_VERSIONS;
}
}
这里,我们是利用JSON配置化的形式进行处理,我们是会去读取目录中是否RN_CONFIG_VERSION.json
这个文件,如果没有该文件,我们会帮其创建这个文件出来;如果有,就读取该文件,观察其有没有配置pnpm
和node
字段,如果没有,我们就会自动赋予其默认的pnpm和node版本,由于小弟的项目中用的是[email protected]
和[email protected]
,所以就默认用这两个版本,更新后最终写入到RN_CONFIG_VERSION.json
文件中。所以如果用户自定义自己的版本,只需要创建该文件,然后配置项目自定义的脚本,再执行脚本,即可实现。
第二步:检查并创建 .nvmrc 文件
js
function checkAndCreateNvmrc() {
const nvmrcPath = path.join(process.cwd(), ".nvmrc");
const { node } = getVersionConfig();
if (!fs.existsSync(nvmrcPath)) {
fs.writeFileSync(nvmrcPath, node);
console.log(`✅ Created .nvmrc file with Node version ${node}`);
} else {
console.log("✅ .nvmrc file already exists");
}
}
这里就是利用fnm的特性,通过定义.nvmrc
文件,指定对应的node版本,进行维护,这个函数的实现也比较简单,就是判断一下项目目录有没有.nvmrc
文件,如果有,就不需要处理;如果没有,就创建该文件,并且写入对应的node版本
第三步:检查并安装fnm
js
// 检查命令是否已安装
function isCommandInstalled(command) {
try {
execSync(`which ${command}`, { stdio: "ignore" });
return true;
} catch (error) {
return false;
}
}
// 检查并安装 fnm
function checkAndInstallFnm() {
try {
if (!isCommandInstalled("fnm")) {
console.log("📦 Installing fnm...");
// 检测系统架构
const arch = process.arch;
if (arch === 'arm64') {
// ARM 架构 (M1/M2/M3) 使用 curl 安装
execSync('curl -fsSL https://fnm.vercel.app/install | bash', { stdio: 'inherit' });
} else {
// x86 架构使用 brew 安装
execSync("brew install fnm", { stdio: "inherit" });
}
console.log("✅ Fnm installed successfully");
} else {
console.log("✅ Fnm is already installed");
}
} catch (error) {
console.error("❌ Error with fnm:", error.message);
console.log(
"⚠️ Skipping fnm setup. You may need to manually install fnm."
);
console.log(" For ARM Mac (M1/M2/M3), run:");
console.log(" curl -fsSL https://fnm.vercel.app/install | bash");
console.log(" For Intel Mac, run:");
console.log(" brew install fnm");
}
}
这里的操作,主要是检查当前电脑中是否安装fnm
,如果没有安装,我们通过Homebrew
进行安装,因为原本大佬提供的安装方式是curl -fsSL https://fnm.vercel.app/install | bash
进行安装,但这个需要🪜方式进行处理才能安装成功,所以我就退而求其次选择了Homebrew
。
第四步:检查并更新~/.zshrc中关于fnm的配置
js
// 检查并更新 .zshrc 中的 fnm 配置
function checkAndUpdateZshrc() {
try {
const zshrcPath = path.join(os.homedir(), ".zshrc");
const fnmConfig = 'eval "$(fnm env --use-on-cd)"';
// 检查文件是否存在,如果不存在则创建
if (!fs.existsSync(zshrcPath)) {
fs.writeFileSync(zshrcPath, fnmConfig + "\n");
console.log("✅ Created .zshrc and added fnm configuration");
return;
}
// 读取文件内容
const content = fs.readFileSync(zshrcPath, "utf8");
// 检查是否已包含 fnm 配置
if (content.includes(fnmConfig)) {
console.log("✅ Fnm configuration already exists in .zshrc");
} else {
// 在文件末尾添加配置
fs.appendFileSync(zshrcPath, "\n" + fnmConfig + "\n");
console.log("✅ Added fnm configuration to .zshrc");
}
} catch (error) {
console.error("❌ Error updating .zshrc:", error.message);
console.log(
"⚠️ Skipping .zshrc update. You may need to manually add fnm configuration."
);
console.log(" Add this line to your ~/.zshrc:");
console.log(' eval "$(fnm env --use-on-cd)"');
}
}
这里主要操作是配置环境变量
,首先,我们会对.zshrc
文件进行判断是否存在,不存在的话,我们帮其创建该文件并且写入fnm的环境变量配置;如果存在的话,我们判断内容中是否已经配置了fnm的环境变量,如果配置了就不需要处理,如果没有,我们就会在文件末尾追加该配置。
第五步:利用fnm检查并安装 Node.js 版本
js
// 检查并安装 Node.js 版本
function checkAndInstallNodeVersion() {
try {
const { node } = getVersionConfig();
// 获取已安装的 Node.js 版本列表
const installedVersions = execSync("fnm list", { encoding: "utf8" });
// 检查是否已安装指定版本
if (!installedVersions.includes(node)) {
console.log(`📦 Installing Node.js ${node}...`);
execSync(`fnm install ${node}`, { stdio: "inherit" });
console.log(`✅ Node.js ${node} installed successfully`);
} else {
console.log(`✅ Node.js ${node} is already installed`);
}
} catch (error) {
console.error("❌ Error installing Node.js version:", error.message);
console.log(
"⚠️ Skipping Node.js installation. You may need to manually install it."
);
console.log(" You can do this by running:");
console.log(" fnm install <version>");
}
}
这里首先会检查用户自定义的node版本是否用fnm已经安装了,如果没有,就会通过fnm进行install;如果安装了,就不需要处理
第六步:检查并启用corepack
js
// 检查并启用 corepack
function checkAndEnableCorepack() {
try {
// 检查 corepack 是否已安装
if (!isCommandInstalled("corepack")) {
console.log("📦 Installing corepack...");
execSync("npm install -g corepack", { stdio: "inherit" });
} else {
console.log("✅ Corepack is already installed");
}
// 尝试启用 corepack
console.log("🔄 Enabling corepack...");
execSync("corepack enable", { stdio: "inherit" });
console.log("✅ Corepack enabled successfully");
} catch (error) {
console.error("❌ Error with corepack:", error.message);
console.log(
"⚠️ Skipping corepack setup. You may need to manually install and enable corepack."
);
console.log(" You can do this by running:");
console.log(" npm install -g corepack");
console.log(" corepack enable");
}
}
同样,首先我们检查一下当前corepack是否已经安装了,一般node版本是16.10+以上是内置了。这里同样判断当前使用已经安装了corepack,如果没有,我们会进行全局安装下,安装完毕后,我们就会执行corepack enable
进行激活管理。
第七步:检查并更新 .gitignore 文件【这一步看项目需要,如果觉得不想提交上去维护,就ignore就好】
js
// 检查并更新 .gitignore 文件
function checkAndUpdateGitignore() {
try {
const gitignorePath = path.join(process.cwd(), ".gitignore");
const nvmrcEntry = ".nvmrc";
// 检查文件是否存在,如果不存在则创建
if (!fs.existsSync(gitignorePath)) {
fs.writeFileSync(gitignorePath, nvmrcEntry + "\n");
console.log("✅ Created .gitignore and added .nvmrc");
return;
}
// 读取文件内容
const content = fs.readFileSync(gitignorePath, "utf8");
// 检查是否已包含 .nvmrc
if (content.includes(nvmrcEntry)) {
console.log("✅ .nvmrc already exists in .gitignore");
} else {
// 在文件末尾添加 .nvmrc
fs.appendFileSync(gitignorePath, "\n" + nvmrcEntry + "\n");
console.log("✅ Added .nvmrc to .gitignore");
}
} catch (error) {
console.error("❌ Error updating .gitignore:", error.message);
console.log(
"⚠️ Skipping .gitignore update. You may need to manually add .nvmrc to .gitignore."
);
console.log(" Add this line to your .gitignore:");
console.log(" .nvmrc");
}
}
这一步其实很简单理解啦,就是看是否维护.nvmrc到项目中,如果觉得没必要维护,就是ignore掉不提交上去。
以上就是整个脚本的实现原理和解析啦,大家可以根据我的脚本,可以给点建议和意见,帮我进一步迭代优化,也是没问题的,因为我是花了一个多小时秒出来的,所以可能很多东西不够严谨,还需多加维护吧哈哈,希望大家都多提一下宝贵意见啦~
二、应用脚本的步骤
这里我主要是考虑的是用户可以git clone拉到项目之后,他不需要去关心,直接执行完pnpm install
之后,就会执行我的脚本进行处理。所以我把脚本放到了postinstall执行。如果觉得不好的话,大家可以根据自己的实际情况,进行脚本执行处理即可。
- 首先,在你的项目之中,创建一个 scripts 文件夹,然后把这个脚本放入到文件夹中
- 在 package.json 文件中,添加如下脚本
json
"postinstall": "node ./scripts/postinstall.js && rm -rf v8-compile-cache-0",
- 如果你想指定对应 pnpm 和 node 版本,可以在项目根目录中创建 RN_CONFIG_VERSION.json 文件,然后写入该内容
json
{
"pnpm": "8.6.12",
"node": "18.17.0"
}
这样其实就完成,当你执行 pnpm install 的时候,就会自动执行脚本
总结
我觉得现在在业务实践和前端工程化中,大家可以多点思考。让代码也更加智能化和自动化啦。比如实现这个脚本,我觉得对于项目维护有两个优点:
- 可以统一团队大家使用的pnpm和node版本。
- 并且便于后续的升级版本的管理。
这样也可以大大增强工程化维护的健壮性和可持续性啦~ 感谢你们能够读完这篇文章啦,希望他能够对大家有益啦~我是CnLiang
,有建议的同学或小伙伴,可以给我发邮件[email protected]
哦,我会及时进行关注和回复,也希望大家能多多支持关注,感谢🙏