前端自动化 - 项目启动时自动安装依赖

1,824 阅读4分钟

不知道你是否有过这种经历(不能是我一个人这么菜吧😂):
早上来到公司, git三板斧+npm run dev一套行云流水准备开始一天的工作。
报错了。
不能啊, 没动什么呀, 看了一通提交记录感觉也没啥问题, 可报错还是存在。
好吧, 开始一点点追踪错误, 弄了好久最后发现是依赖的问题, 代码里写的调用某个API, 依赖库里实际上没有。
愣了一下, 再去翻提交记录...原来时依赖更新了我本地没有npm install...被自己蠢哭了🙄

这个事吧其实也还好, 一次两次之后就有经验了, 反正pull完就顺带看下package.json/package-lock.json是否有更新, 但心里总想着自动化这个步骤, 写程序的不就是要能自动化的都自动化么。
正好也在学Rust, Rust的包管理器是Cargo, 每次cargo run的时候都会去检查项目的依赖是否有变动, 然后自动执行安装, 这个功能就是我想要的, 那么能不能在npm这边实现这样的逻辑呢?

package.json/package-lock.json

npm在管理依赖时有package.json/package-lock.json两个文件, 要实现依赖更新自动安装其实就是监视这两个文件的变化, 那么是两个文件都需要检测, 还是只需关注其中一个? 如果只需要监测一个的话又应该是哪一个呢?

这要大概了解一下这两个文件的目的是什么:

  • package.json 这个文件是在npm init时产生的, 记录了项目的相关信息, 其中dependenciesdevDependencies两个节点记录了项目的依赖信息
  • package-lock.json 这个文件是npm5后引入的, 会为任意的, npm修改modules树或package.json的操作自动产生(package-lock.json | npm Docs (npmjs.com)), 记录了项目依赖的关系和精确版本信息

需要注意的是, package.json文件内记录的依赖也有版本信息, 但这里的版本信息是语义化版本配合版本范围, 虽然也能指定一个精确的版本号, 但一般实践中不会这么做。

简单了解之后, 显然package-lock.json更适合作为是否需要重新安装依赖的标识。

监测依赖变动

本来是打算自己动手做的, 但是github上搜了一下发现有类似的库, 比如这个ninesalt/install-changed: Tiny npm package that only runs npm install if package.json has been modified. (github.com)
基本原理就是:

  • 读取package-lock.json, 用MD5算出一个哈希值(信息摘要), 保存在项目根目录的一个文件里:
//找到 package-lock.json
let pkgLockPath = '';
let current = process.cwd();
let last = current;
do {
    const search = path.join(current, "package-lock.json");
    if (fs.existsSync(search)) {
      packagePath = search;
      break;
    }
    last = current;
    current = path.dirname(current);
} while (current !== last);

// 读取并计算 package-lock.json 的哈希值
const hashSum = crypto.createHash("md5");
const contents = fs.readFileSync('file', "utf-8");
hashSum.update(Buffer.from(contents));
// 记录当前 package-lock.json 哈希值
fs.writeFileSync(path.join(path.dirname(packagePath), pkg_hash.txt), hashSum.digest("hex"));
  • 每次运行时先找到当前项目根目录是否存在pkg_hash.txt, 若存在则取出其中的md5和当前的对比, 不一致则运行npm install / npm ci
// 类似上面的代码, 计算当前 package-lock.json 的MD5哈希值
// const recentDigest = hashFile(pkgLockPath);
const pkgHashPath = path.join(path.dirname(pkgLockPath), pkg_hash.txt);

// 第一次运行或两次hash值不一致, 自动运行 `npm install`
if (!fs.existsSync(pkgHashPath) || fs.readFileSync(pkgHashPath, "utf-8") !== recentDigest) {
    require("child_process").execSync('npm install', { stdio: "inherit", })
}

// 记录本次新的hash值
fs.writeFileSync(packageHashPath, recentDigest);

不过这个库可能已经没有维护了, 当前npm上不是最新版本, 且代码有些问题, repo内有几个PR也没有处理。我fork了一份做了简单的修改, 重新发布了一个npm模块: install-pkg-lock - npm (npmjs.com)

这类库啊, 还有相关的问题啊都比较少, 难道真的是很少有人像我这样菜么🤦‍♂️

并入前端流程

因为我想要模仿cargo run自动安装依赖嘛, 实际使用的时候我就把这个命令放在了原有的npm run dev前面:

scripts: {
    "pre-run": "install-changed",
    "dev": "npm run pre-run && vite",
}

实际上也可以用过Git Hook来实现git pull之后就执行这个命令(使用post-merge钩子):

"husky": {
    "hooks": {
      "post-merge": "install-changed",
    }
},

此外, 为了防止手动执行npm install后没有更新pkg_hash.txt, 我们还需要使用npm提供的postinstall钩子来在依赖安装完成后更新一下pkg_hash:

scripts: {
    "postinstall": "install-changed --hash-only",
}

这里传入了一个 hash-only 来跳过执行npm install, 只更新hash值。

最后还需要把pkg_hash.txt加入gitignore, 这个不用多说, 加到版本控制的话就玩不转咯。