
npm 是 node 捆绑的依赖管理器,常用程度可想而知。那么你每天都在 npm/yarn run 的命令到底是如何运行项目的呢?
前端项目中运行 npm run xxx 的时候发生了什么?
大家都知道目前的 node 是捆绑 npm 的。npm 是 node 的依赖管理器,虽然它不是唯一的选择,我们还有 pnpm/yarn/cnpm/ni 。
但是,的依赖管理器都是在解决 npm 的某个痛点。对于 npm 依赖声明文件
package.json
本身是基本没有变化的。
例如我们可以使用
npm run serve
运行某个命令, 也可以使用
yarn serve
运行某个命令。
可以看到在这个地方 yarn 可以省略 run 这个参数。
但是,他们都只是对
package.json
进行解析而已,例如下面的文件,当运行
npm run serve
时,其实就是运行该 json 文件中的
scripts
下的
serve
键对应的命令。
{
"name": "h5",
"version": "1.0.7",
"private": true,
"scripts": {
"serve": "vue-cli-service serve"
},
"dependencies": {
"axios": "^0.19.2",
"vuex": "^3.4.0"
},
"devDependencies": {
"node-sass": "^4.12.0"
}
}
上面说是 命令
只是用于方便理解,例如:
npm run server # 类似于在命令行运行以下命令 vue-cli-service serve
还是用上面的配置来描述:
{
"scripts": {
"serve": "vue-cli-service serve"
}
}
npm 在运行
vue-cli-service serve
这条命令的时候,会先在当前
node_modules/.bin
下面看有没有同名的可执行文件,如果有,则使用其运行。
这里我们可以打开这个目录看看:

如果直接在命令行中运行
vue-cli-service serve
这条命令,是不会从 node_modules 中查找可执行程序的。
那么什么叫可执行文件呢?上面的图中有很多个同名的 vue-cli-service ,到底是运行哪个?
我们先来分析这几个文件怎么来的?
例如
@vue/cli-service
有以下
package.json
文件,注意 bin 字段,当我们运行
npm i @vue/cli-service
这条命令时,npm 就会在
node_modules/.bin/
目录中创建好以
vue-cli-service
为名的几个可执行文件了。
{
"name": "@vue/cli-service",
"version": "4.4.6",
"description": "local service for vue-cli projects",
"main": "lib/Service.js",
"typings": "types/index.d.ts",
"bin": {
"vue-cli-service": "bin/vue-cli-service.js"
}
}
对于可执行这个定义,每个系统不一样。在 windows 系统上,可执行文件是通过组策略和环境变量决定的。
使用
set pathext
可以查看
pathext
这个环境变量,他定义了可以作为可执行文件的后缀。
# 查看可执行文件后缀 set pathext

由上面的配置可以发现,我们常见的 exe 也在其中,这个可执行文件在 windows 上,在命令行中输入文件名或双击时即可以运行。
在 unix 系统上面,是通过设置文件的属性为可执行,再在文件中的第一行声明解释器来运行的。

如果我们在 cmd 里运行的时候,windows 一般是调用了
vue-cli-service.cmd
这个文件,这是 windows 下的批处理脚本:
@ECHO off SETLOCAL CALL :find_dp0 SET _maybeQuote=" IF EXIST "%dp0%\node.exe" ( SET "_prog=%dp0%\node.exe" ) ELSE ( SET _maybeQuote= SET "_prog=node" SET PATHEXT=%PATHEXT:;.JS;=;% ) %_maybeQuote%%_prog%%_maybeQuote% "%dp0%\..\_@vue_cli-service@4.4.6@@vue\cli-service\bin\vue-cli-service.js" %* ENDLOCAL EXIT /b %errorlevel% :find_dp0 SET dp0=%~dp0 EXIT /b
所以当我们运行
vue-cli-service serve
这条命令的时候,就相当于运行
node_modules/.bin/vue-cli-service.cmd serve
然后这个脚本会使用 node 去运行
vue-cli-service.js
这个 js 文件,由于 node 中可以使用一系列系统相关的 api ,所以在这个 js 中可以做很多事情,例如读取并分析运行这条命令的目录下的文件,根据模板生成文件等。
# unix 系默认的可执行文件,必须输入完整文件名 vue-cli-service # windows cmd 中默认的可执行文件,当我们不添加后缀名时,自动根据 pathext 查找文件 vue-cli-service.cmd # Windows PowerShell 中可执行文件,可以跨平台 vue-cli-service.ps1
这里多提了下,在 windows 中 cmd 脚本使用得比较多,兼容性也较好。 powerShell 虽然比较强大,但他运行命令的方式由于和 cmd 命令有较大不同,这会导致你常常搞不清什么命令应该在什么解释器里运行。
示例:运行命令的方式不兼容

示例:windows 很多系统会默认禁止此脚本运行,导致 npm 命令运行错误

所以如果遇到 powerShell 相关错误时建议用 cmd 试试。
这一节我们通过调试 npm 的源码来进行说明。
首先我们在 mockm 这个前端接口联调工具的源码中先来个 debugger, 注意有从 process.env 中获取 NPM_CONFIG_REGISTRY 这个环境变量,这是 npm 安装时可配置的镜像地址。

然后我们再看一下这个环境变量,在当前系统中是没有定义的。

让我们开始调试 mockm package.json 中的 scripts
npm run s2
{
"scripts": {
"s2": "node run.js remote --config=D:/git2/mockm/server/example/full.mm.config.js",
}
}

为了节省篇幅,这里直接断点在关键地点:

这是 npm@v6.x 的源码,可以发现 npm 使用了 npm-lifecycle 这个依赖来运行的子进程调用我们的
run.js
文件。
在通过 spawn 运行 run.js 的时候,同时设置了进程相关的一些信息,这是由 node 原生支持的。

例如刚刚说到的 NPM_CONFIG_REGISTRY 环境变量。
下面把继续进入下一个断点, run.js 文件:

可以发现子进程成功获取了父进程给予的信息。
运行 npm run xxx
的时候,npm 会先在当前目录的 node_modules/.bin 查找要执行的程序,如果找到则运行;
没有找到则从全局的 node_modules/.bin 中查找,npm i -g xxx
就是安装到到全局目录;
如果全局目录还是没找到,那么就从 path 环境变量中查找有没有其他同名的可执行程序。
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象