故事要从改造公司项目脚手架说起,去年(2022年)我在部门做了vite技术分享,会后与前端基建同学聊了一下,打算将公司脚手架的构建工具由webpack升级成vite,提升开发体验,生产环境依然采用webpack。迎来的第一个问题就是如何解决node版本隔离问题?公司目前大多数项目node版本都是8.9.4,而vite要求node版本最小是12.0.0,当然开发同学可以本地自行安装高版本,但是存在以下弊端:
- 不够优雅,开发需要手动安装
- 污染全局,可能导致其他项目无法运行
- 手动降级,需要换回webpack打包时,不能无缝切换
- 心智负担,频繁切换node版本
其实这个问题已经有解决方案了,并且公司一直在使用这个方案,就是使用volta(点我了解一下)来做到项目级别的node版本控制。但是目前的问题是:volta已经用于生产环境打包,没法在一个项目里边设置多个node版本(当然手动切换版本也可以,但比较繁琐)。如果我们直接修改volta的配置,虽然可以满足运行vite版本要求 但也导致生产环境node版本被改了,结果具有不可预测性,显然不是一个好的做法。
那我们能否从volta、nvm、n等node版本管理解决方案上得到一些启发呢?比如在运行npm scripts时候指定node的版本,如果不存在就从网络下载zip包,解压得到可执行文件。运行指定路径的node可执行文件。实际以上这些工具就是这么干的,实现大体思路大同小异。
在经过一番调研后,发现其实社区已经有解决方案了,这里列举2个npm包, 这两个包都可以做到项目级别的node版本控制,使用方式和工作原理也很类似,但是他们有一个很大的区别:
nodeinstall 同时安装node与npm,可指定node下载镜像与执行路径
node 只安装node,不提供参数配置
在经过一番demo测试后,发现确实可以做到项目隔离,不会影响全局node。 看文档得知以下2点重要结论:
- 下载npm包(包含了node二进制可执行文件)到本地
- npm scripts会优先去本地node_modules中的.bin目录寻找环境变量
首先验证一下结论1,查看一下node_moduls/.bin文件夹里都有啥,发现眼熟的node其实是一个软连接!其真实路径为node_modules/bin/node
细心的同学可能发现了,.bin目录下的文件全是软连接
接着验证结论2,分别在全局执行node -v和使用npm scripts执行node -v,结果如下:
可以看到,两者的node版本不一样!
经过一番验证,确实如文档描述一样。那么有意思的事情就来了,这里要提出几个问题:
- 为什么终端可以运行js脚本
- node_modules下的.bin文件夹是干什么用的
- 为什么运行npm scripts会优先使用本地的node
带着疑问,我们一步步揭开npm的神秘面纱。
当我们输入npm run xxx,敲下回车的时候,都发生了什么?
首先我们知道一个命令想要执行,至少先要找到命令的位置,其次还得是可执行文件/脚本,那我们就顺藤摸瓜,看看npm命令的执行流程就能找到最终答案。
找到npm本尊以后,这里我通过vscode断点调试,整理其核心实现如下:
将项目node_modules/.bin 与npm命令包下的node-gyp-bin添加到PATH环境变量最前端(注意追加顺序,node-gyp-bin在node_modules/.bin之前),为什么要追加到最前面?这里就要涉及到一点操作系统的知识了。
简单来说,PATH最重要的功能就是为系统查找命令/变量提供一个规则。当我们在终端输入一个指令的时候,系统就会去PATH中去查找,而查找的优先级就是从左到右。
设置好环境变量以后,就开始运行npm script了,核心实现可以看到是利用node子进程去执行命令
通过关键源码分析得出结论:
- npm会在运行之前为追加PATH路径
- 然后使用node子进程执行命令
再来验证一下,是否符合预期,查看系统PATH环境变量
运行npm scripts时候,输出系统PATH环境变量
结果不言而喻,都是符合我们的预期,到这一步已经可以解释为什么我们运行node命令的时候会优先从项目目录node_modules/.bin中去查找,以及.bin目录是干什么用的
那再来说说,为什么终端能运行js代码?这里以vite为例,可以看到vite的真实路径是 node_modules/vite/bin/vite.js
打开这个文件,可以看到引入眼帘的第一句就是一行类似‘注释’的代码,不要小看这一句’注释‘,他包含了两个部分:
#!
它叫shebang,起到标识的作用,说明这个文件可以当做脚本来运行- /usr/bin/env就是告诉系统可以在PATH目录中查找
所以配置#!/usr/bin/env node, 就是解决了不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件,这也解释了为什么我们在写cli的时候需要在第一行加入这段`注释`
最后顺便谈谈 npm hooks,写了如下demo
npm hooks处理核心代码,最关键的一句。如果当前scripts不是以pre或者post开头的,则在当前命令前后追加pre和post命令,然后顺序执行
以上疑问都已解决,看到这里相信你对npm的执行流程应该有一个清晰的认识了。
那最终我是如何实现node版本隔离的呢?由于`node`这个npm包无法指定node安装路径,这会导致运行所有npm scripts都会默认去.bin目录寻找node,在部署生产环境的时候不希望使用本地node。
前面说过nodeinstall可以指定execPath参数,利用这个就可以指定一个node安装路径,这样就不会影响到项目其他npm脚本原有的执行流程。有了node执行文件,接下来就是如何解决在运行npm scripts的时候指定我们安装的本地node,
这里使用node子进程的fork方法就可以实现,其可以指定execPath(既node可执行文件位置),这样就达到了最终效果。
标签:npm,node,深入,版本,scripts,bin,vite From: https://www.cnblogs.com/zt123123/p/17115099.html