npm run xxx 到底发生了啥?

352 阅读5分钟

大家在每天开始一天新的工作的时候,第一件事就是打开vs code,然后就在命令行输入npm run start,emmm...之后再等待个一小会,项目就跑起来了,就开始愉快的一天了。

那问题来了,你想过npm run xxx之后发生了什么吗?

package.json文件

package.json 用来描述项目及项目所依赖的模块信息,安装包依赖关系都由package.json来管理,开发者几乎不必考虑它们,但是有必要了解package.json中各个字段的含义和使用方法。

例如如下package.json:

{
  "name": "doraemon-pocket-task",
  "version": "0.0.0",
  "scripts": {   //重点。。。
    "start": "vite",
    "build": "tsc && cross-env NODE_OPTIONS=--max_old_space_size=10240 vite build",
    "serve": "vite preview",
    "test": "jest",
    "test:c": "jest --coverage",
    "stylelint": " stylelint \"./src\"",
    "cspell": "cspell \"src/**/{*.ts,*.tsx,*.js,*.jsx}\"",
    "eslint": "eslint -c ./.eslintrc.json \"src/**/{*.ts,*.tsx,*.js,*.jsx}\"",
    "checker": "npm-run-all -p eslint cspell stylelint ts-check",
    "ts-check": "tsc --noEmit",
    "moveAntdcss": "node ./scripts/moveAntdcss.js"
  },
  "dependencies": {
     xxxx
  },
  "devDependencies": {
     //省略很多xxxx
    "vite": "^2.8.4"
  }
}
  • name:项目/模块名称,长度必须小于等于214个字符,不能以"."(点)或者" "(下划线)开头,不能包含大写字母。
  • version:项目版本
  • author:项目开发者,它的值是你在npmjs.org网站的有效账户名,遵循“账户名<邮件>”的规则,例如:zhangsan zhangsan@163.com
  • description:项目描述,是一个字符串。它可以帮助人们在使用npm search时找到这个包。
  • keywords:项目关键字,是一个字符串数组。它可以帮助人们在使用npm search时找到这个包。
  • private:是否私有,设置为 true 时,npm 拒绝发布。
  • license:软件授权条款,让用户知道他们的使用权利和限制。
  • bugs:bug 提交地址。
  • contributors:项目贡献者 。
  • repository:项目仓库地址。
  • homepage:项目包的官网 URL。
  • dependencies:生产环境下,项目运行所需依赖。
  • devDependencies:开发环境下,项目所需依赖。
  • scripts:执行 npm 脚本命令简写,比如 “start”: “react-scripts start”, 执行 npm start 就是运行 “react-scripts start”。
  • bin:内部命令对应的可执行文件的路径。而.bin里面的文件可以找到相应的包,就是这里配置的
  • main:项目默认执行文件,比如 require(‘webpack’);就会默认加载 lib 目录下的 webpack.js 文件,如果没有设置,则默认加载项目跟目录下的 index.js 文件。
  • module:是以 ES Module(也就是 ES6)模块化方式进行加载,因为早期没有 ES6 模块化方案时,都是遵循 CommonJS 规范,而 CommonJS 规范的包是以 main 的方式表示入口文件的,为了区分就新增了 module 方式,但是 ES6 模块化方案效率更高,所以会优先查看是否有 module 字段,没有才使用 main 字段。
  • eslintConfig:EsLint 检查文件配置,自动读取验证。
  • engines:项目运行的平台/环境。
  • browserslist:供浏览器使用的版本列表。
  • style:供浏览器使用时,样式文件所在的位置;样式文件打包工具parcelify,通过它知道样式文件的打包位置。
  • files:打包是被项目包含的文件名数组;

再package.json中,我们可以看到一个script的属性,当我们执行了npm run start,npm就会在这里寻找到start命令对应的脚本,也就是"vite",换句话说,执行了npm run start就相当于执行了vite,那为什么需要npm脚本属性呢?难道是为了简化我们的命令输入吗?但是我测试发现,当我们直接在终端执行vite时行不通 的,出现了如下的报错

window下: image.png mac下:

image.png

无论是 windows还是mac下都是类似原因导致的无法直接执行,那为什么npm run start,之后却可以运行呢?这主要的原因就在node_modules里面了。

node_modules

node_modules中,大家通常比较关注modules中的包,但是node_modules中第一个文件夹是.bin,而这个文件夹主要存放命令对应的可执行文件的软链接文件,指向对应第三方依赖的bin文件。

npm 在执行install的时候,会根据第三方依赖中的package.json里面的bin配置,在node_modules下面的.bin目录生成一个可执行文件。生成的可执行文件其实是一个替身文件(软链接文件),这个替身文件真正指向的就是第三方依赖package.json里面的bin配置指向的那个文件。

npm运行script脚本时会自动给脚本路径都加上了node_moudles/.bin前缀,这意味着:你在试图运行script中某个脚本时,实际上是运行node_moudles/.bin下的对应软链接文件。

如果这里找不到相应的软连接,则会尝试去全局的node_modules下面找相应的软连接

image.png

友情提示:.vite文件夹存放的是vite的依赖预构建

vite对于在脚本文件中通过import导入

// 裸导入
import * as qs from 'qs';
// 经过 Vite 转换后 import __vite__cjsImport0_qs from "/node_modules/.vite/qs.js?v=f72810ea"; const qs = __vite__cjsImport0_qs;

// 相对路径导入, Vite 暂时没有转换
import { add } from './utils.js';

对于裸导入,Vite 会尝试从node_modules文件夹查询依赖,并重写导入路径。可以看到导入路径被转换为/node_modules/.vite/qs.js?v=f72810ea 。之所以不是/node_modules/qs/xxx,Vite 对node_modules中的依赖进行了预处理,将结果放在了.vite文件夹下。

官网关于依赖预构建的文档

.bin文件夹 我们展开.bin文件夹中,可以看到 mac下,

image.png

文件中start方法指向了一个../dist/node/cli的文件,(下面会看看这个文件)

我们文件夹中发现了vite的符号连接,而这个链接指向是node_modules文件夹下面vite的包

在windows中,是这样的

image.png

从图中可以看到,

  1. 看到文件顶部写着 #!/bin/sh ,表示这是一个脚本。
  2. 文中../vite/bin/vite.js就只真正执行服务的文件
  3. 该文件vite和下面的vite.cmd的含义
  • unix 系默认的可执行文件,必须输入完整文件名 vite

  • windows cmd 中默认的可执行文件,当我们不添加后缀名时,自动根据 pathext 查找文件 vite.cmd

vite文件夹

首先看一下文件的bin文件夹,值得关注的是,在mac中。.bin文件夹中的比特

image.png

再看一下目录

image.png

大家从图中功能就可以看出,vite主要用了rollupesbuild,都支持了原生的esm模块,就想他在官网写的 image.png

image.png

image.png

rollup

image.png