当前位置: 首页 > 后端技术 > Node.js

游雨溪推荐神器ni,能不能替代npm-yarn-pnpm?简单易用!源代码公开!

时间:2023-04-03 15:58:51 Node.js

1。前言大家好,我是若川。最近,我们组织了一次源码分享活动。有兴趣的可以加我微信ruochuan12参与。已经持续两个多月了。大家可以一起交流学习,共同进步。如果想学习源码,强烈推荐我之前写的《学习源码整体架构系列》,包括jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4、koa-compose、vue-next-release、vue-this、create-vue等十余篇源码文章。本文仓库ni-解析,求一个star^_^最近组织了一个源码阅读活动,大家一起学习源码。于是我们搜索了各种值得学习的源代码,而且代码行数不多。之前写过两篇Vue3相关的文章。Vue3源码中初学者都能看懂的实用基础工具功能Vue3.2已经发布了,那么游鱼溪是怎么发布Vue.js的呢?文章是用yarn写的。参与源码阅读的小伙伴跟着我的文章,但是拉取了最新的仓库代码,发现yarninstall无法安装依赖,报错给我。于是去github仓库,发现尤雨熙把vue3仓库从yarn改成了pnpm。贡献文档中有一句话。我们还建议安装ni以帮助在使用不同包管理器的repos之间切换。ni还提供了方便的nr命令,可以更轻松地运行npm脚本。ni还提供了方便的nr命令,使运行npm脚本更加容易。虽然这个ni项目的源码是ts的,但是没用过ts的朋友也很容易理解,而且主文件其实不到100行,非常适合我们学习。阅读本文后,您将学习到:1.学习如何使用和理解ni的原理2.学习调试和学习源码3.日常工作中也可以使用ni4.你应该)在它运行之前,它会检测你的yarn.lock/pnpm-lock.yaml/package-lock.json以了解当前的包管理器,并运行适当的命令。单从这句话可能很难理解,或者我还不知道是什么。让我解释。在你的项目中使用`ni`安装依赖时:假设你的项目中有一个锁文件`yarn.lock`,它最终会执行`yarninstall`命令。假设你的项目中有一个锁文件`pnpm-lock.yaml`,它最终会执行`pnpmi`命令。假设你的项目中有一个锁文件`package-lock.json`,它最终会执行`npmi`命令。使用`ni-gvue-cli`安装全局依赖时,默认使用`npmi-gvue-cli`,当然不仅仅是`ni`安装依赖。还有`nr`-run`nx`-execute`nu`-upgrade`nci`-cleaninstall`nrm`-remove我看了源码发现:可以加\?在与ni相关的命令的末尾,表示只打印,并没有真正强制执行。所以在全局安装ni之后,可以随便测试,比如ni\?,nrdev--port=3000\?,因为是打印出来的,可以在各个目录下执行,有助于理解ni的源代码。我测试如下图所示:假设项目目录下没有lock文件,默认会允许用户选择npm、yarn、pnpm,然后执行相应的命令。但是,如果在~/.nirc文件中设置了全局默认配置,则相应的命令将使用默认配置执行。配置;~/.nirc;未找到锁时回退defaultAgent=npm#默认“提示”;forglobalinstallsglobalAgent=npm因此,我们可以知道这个工具必须做三件事:1.根据锁定文件npm/yarn/pnpm猜测使用哪个包管理2.抹平不同包管理器的命令差异3.最后运行相应的脚本然后继续阅读README中其他命令的使用,就会很容易理解。3.使用方法见nigithub文档。再次在yarn项目中使用npmi?F**k!ni-使用正确的包管理器进行全局安装。npmi-g@antfu/ni如果全局安装遇到冲突,我们可以加上--force参数强制安装。举几个常见的例子。3.1ni-installni#npminstall#yarninstall#pnpminstallniaxios#npmiaxios#yarnaddaxios#pnpmiaxios3.2nr-runnrdev--port=3000#npmrundev----port=3000#yarnrundev--port=3000#pnpmrundev----port=3000nr#交互选择命令执行#交互选择脚本运行#支持https://www.npmjs.com/package/npm-scripts-infoconventionnr-#Rerunthelastexecutedcommand#rerunthelastcommand3.3nx-executenxjest#npxjest#yarndlxjest#pnpmdlxjest4.阅读源码前的准备4.1Clone#推荐克隆我的仓库(我的保证对应文章版本)gitclonehttps://github.com/lxchuan12/ni-analysis.gitcdni-analysis/ni#npmi-gpnpm#安装依赖pnpmi#当然也可以直接使用ni#或者clone官方仓库gitclonehttps://github.com/antfu/ni.gitcdni#npmi-gpnpm#安装依赖pnpmi#当然你也可以直接使用ni。众所周知,要看一个开源项目,首先要从package.json文件升起。4.2package.json文件{"name":"@antfu/ni","version":"0.10.0","description":"使用正确的包管理器",//公开六个命令"bin":{"ni":"bin/ni.js","nci":"bin/nci.js","nr":"bin/nr.js","nu":"bin/nu.js","nx":"bin/nx.js","nrm":"bin/nrm.js"},"scripts":{//省略其他命令,用esno执行ts文件//可以加?为了便于调试,你也可以不添加//或终端npmrundev\?“dev”:“esnosrc/ni.ts?”},}根据dev命令,我们找到主入口文件src/ni.ts。4.3从主源码入口开始调试//ni/src/ni.tsimport{parseNi}from'./commands'import{runCli}from'./runner'//我们可以在断点找到ni/runCli(parseNi)这里是package.json的脚本,将鼠标移到dev命令,会出现运行脚本和调试脚本的命令。选择调试脚本,如下图所示。5.主进程runner——runCli函数该函数是对终端传入的命令行参数进行解析。最后执行run函数。对进程不了解的读者可以看一下阮一峰老师写的进程对象//ni/src/runner.tsexportasyncfunctionrunCli(fn:Runner,options:DetectOptions={}){//进程.argv:返回一个数组,其成员都是当前进程的命令行参数。//其中process.argv的第一个和第二个元素是Node可执行文件和正在执行的JavaScript文件的完全限定文件系统路径,无论您是否输入它们。constargs=process.argv.slice(2).filter(Boolean)try{awaitrun(fn,args,options)}catch(error){//process.exit方法用于退出当前进程。它可以接受一个数字参数。如果参数大于0,则表示执行失败;如果等于0,则表示执行成功。process.exit(1)}}接下来我们看run函数。6.Mainprocessrunner——runmainfunction这个函数主要做了三件事:1.根据lock文件猜测使用哪个包管理器npm/yarn/pnpm——detect函数2.抹平不同包管理器的命令差异——parseNifunction3.最后运行对应的脚本-execatool//ni/src/runner.ts//源码已删除importexecafrom'execa'constDEBUG_SIGN='?'exportasyncfunctionrun(fn:Runner,args:string[],options:DetectOptions={}){//命令参数包含问号?是调试模式,不执行脚本constdebug=args.includes(DEBUG_SIGN)if(debug)//在调试模式下,删除问号remove(args,DEBUG_SIGN)//cwd方法返回当前进程目录(绝对路径)letcwd=process.cwd()letcommand//支持指定文件目录//ni-Cpackages/foovite//nr-Cplaygrounddevif(args[0]==='-C'){cwd=resolve(cwd,args[1])//删除这两个参数-Cpackages/fooargs.splice(0,2)}//如果是全局安装,使用全局包管理器constisGlobal=args.includes('-g')if(isGlobal){command=awaitfn(getGlobalAgent(),args)}else{letagent=awaitdetect({...options,cwd})||getDefaultAgent()//猜测使用哪个包管理器,如果没有找到锁文件,则返回null,然后调用getDefaultAgent函数,默认返回是让用户选择提示if(agent==='prompt'){agent=(awaitprompts({name:'agent',type:'select',message:'Choosetheagent',choices:agents.map(value=>({title:value,value})),})).agentif(!agent)return}//这里的fn是解析代码中传入的函数command=awaitfn(agentasAgent,args,{hasLock:Boolean(agent),cwd,})}//如果没有command,直接return,最后一个runCli函数报错,退出进程if(debug){//eslint-disable-next-lineno-consoleconsole.log(command)return}//最后用execa执行命令,比如npmi//https://github.com/sindresorhus/execa//简介:进程执行为人类awaitexeca.command(command,{stdio:'inherit',encoding:'utf-8',cwd})}学习完主进程,我们来看两个重要的函数:检测函数,parseNi函数。根据入口我们可以知道。runCli(parseNi)run(fn)其中fn是parseNi6.1根据lock文件猜测使用哪个包管理器(npm/yarn/pnpm)——detect函数的代码比较少,所以我就全部释放.主要做了三件事1.找到项目根路径下的锁文件。返回相应的包管理器`npm/yarn/pnpm`。2.如果没有找到,返回`null`。3、如果找到了,但是用户的电脑没有这个命令,询问用户是否自动安装。//ni/src/agents.tsexportconstLOCKS:Record={'pnpm-lock.yaml':'pnpm','yarn.lock':'yarn','package-lock.json':'npm',}//ni/src/detect.tsexportasyncfunctiondetect({autoInstall,cwd}:DetectOptions){constresult=awaitfindUp(Object.keys(LOCKS),{cwd})constagent=(result?LOCKS[path.basename(result)]:null)if(agent&&!cmdExists(agent)){if(!auto??Install){console.warn(`检测到${agent}但似乎没有安装。\n`)if(process.env.CI)process.exit(1)constlink=terminalLink(agent,INSTALL_PAGE[agent])const{tryInstall}=awaitprompts({name:'tryInstall',type:'confirm',message:`Wouldyouliketogloballyinstall${link}?`,})if(!tryInstall)process.exit(1)}awaitexeca.command(`npmi-g${agent}`,{stdio:'inherit',cwd})}returnagent}接着我们来看parseNi函数。6.2消除不同包管理器之间的命令差异-parseNifunction//ni/src/commands.tsexportconstparseNi=((agent,args,ctx)=>{//ni-v输出版本号if(args.length===1&&args[0]==='-v'){//eslint-disable-next-lineno-consoleconsole.log(`@antfu/niv${version}`)process.exit(0)}if(args.length===0)returngetCommand(agent,'install')//部分代码省略})通过getCommand获取命令。//ni/src/agents.ts//有删减//一个配置,把命令写在这三个包管理器里。exportconstAGENTS={npm:{'install':'npmi'},yarn:{'install':'yarninstall'},pnpm:{'install':'pnpmi'},}//ni/src/commands.tsexportfunctiongetCommand(agent:Agent,command:Command,args:string[]=[],){//包管理器不在AGENTS中会报错//比如npm不在if(!(agentinAGENTS))thrownewError(`Unsupportedagent"${agent}"`)//获取要安装的命令,对应npminstallconstc=AGENTS[agent][command]//如果是是函数,执行函数。if(typeofc==='function')returnc(args)//如果没有找到command,会报错if(!c)thrownewError(`Command"${command}"isnotsupportbyagent"${agent}"`)//最后拼接成命令字符串returnc.replace('{0}',args.join('')).trim()}6.3最后运行对应的脚本得到相应的命令,比如npmi,最后使用execa这个工具来执行最终对应的脚本。awaitexeca.command(command,{stdio:'inherit',encoding:'utf-8',cwd})7.总结看了源码我们可以知道这个神器ni主要做了三件事:1.根据锁定文件猜猜使用哪个包管理器npm/yarn/pnpm-检测功能2.抹平不同包管理器的命令差异-parseNi功能3.最后运行相应的脚本-execa工具在我们日常开发中,可能很容易对于npm、yarn、pnpm混合。有了ni,就可以进行日常的开发使用了。Vue的核心成员AnthonyFu发现了这个问题,并最终开发了一个工具ni来解决这个问题。而这种发现问题和解决问题的能力,正是我们前端开发工程师所需要的。另外,我发现很多Vue生态已经基本转用pnpm了。由于文章篇幅不宜过长,并未完整描述源码中的所有细节。强烈推荐读者朋友按照文章中的方法使用VSCode调试ni源码。学会调试源码后,源码并没有想象中那么难。最后,大家可以继续关注我@沉川。欢迎加我微信ruochuan12交流,参与源码阅读活动。大家可以一起学习源码,共同进步。