npm init @vitejs/app的背后,仅是npm CLI的冰山一角

1,420 阅读9分钟

结尾的话说在前面。

我有时候会得出这样的结论:原来那些我不常用的命令或工具,都是为了解决大佬们遇到的问题而存在的!

我们每天都和npm打交道,但是不少人对npm的掌握程度还停留在一个比较浅的层面(当然这也包括我)。就比如说一个用 vite 创建 app 的命令npm init @vitejs/app,很多人就懵了,“npm init不是用来创建package.json文件的吗?”

同样还有npx create-react-app my-app这样的命令,懵吗?

的确,这些命令背后还有一些我们很少关注的逻辑,虽然不难,但是我们却没有系统去了解过。

考虑到这些,最近我有系统地去学习npm,主要的学习方式是利用一些空余时间,结合我之前的npm使用经验,从npm官方文档入手去排查一些知识盲点和疑惑。顺着官方文档一块块看下来,同时对不清楚的知识点进行资料查阅和验证之后,虽然还有那么一小部分知识点我几乎没用过,但是我的确对npm有了更多的认识。最后我也是以思维导图的形式,把自己的一些学习所得简单记录下来。

思维导图

经过这几天的学习,我发现我学习npm的两个大方向是npm CLIpackage.json

今天我想先聊聊npm CLICLI就是Command Line Interface,也就是我们说的命令行接口。npm提供了非常多的CLI,具体可以参考npm CLI commands

Usage: npm <command>

where <command> is one of:
    access, adduser, audit, bin, bugs, c, cache, ci, cit,
    clean-install, clean-install-test, completion, config,
    create, ddp, dedupe, deprecate, dist-tag, docs, doctor,
    edit, explore, fund, get, help, help-search, hook, i, init,
    install, install-ci-test, install-test, it, link, list, ln,
    login, logout, ls, org, outdated, owner, pack, ping, prefix,
    profile, prune, publish, rb, rebuild, repo, restart, root,
    run, run-script, s, se, search, set, shrinkwrap, star,
    stars, start, stop, t, team, test, token, tst, un,
    uninstall, unpublish, unstar, up, update, v, version, view,
    whoami

npm <command> -h  quick help on <command>
npm -l            display full usage info
npm help <term>   search for help on <term>
npm help npm      involved overview

Specify configs in the ini-formatted file:
    C:\Users\Tusi\.npmrc
or on the command line via: npm <command> --key value
Config info can be viewed via: npm help config

命令太多,就不全部解释一遍了。我筛选出了一些基础的,同时也是我见得比较多的一些命令来简单介绍下。

npm CLI常见命令

npm help

不懂就问,npm help是个好命令。就像我用git --help一样,对于有些比较模糊的命令,我都会用help来查一下。

npm init

在我们初始化一个 npm 包,或者说创建 package.json 文件,就需要用到npm init

npm init xxx

虽然之前在创建vue或者react应用时,我都用到了npm init xxx,但我都没怎么关注npm init xxx背后发生了什么。

比如npm init @vitejs/app,只知道官网说它是用来创建应用的,但很少会去想到其背后是调用了npx @vitejs/create-app,其实就是在执行一个create-app脚本。

这也就是说,如果你想让别人通过npm init xxx命令调用你的包,就必须提供一个create-xxx脚本。

// 对于 yarn 来说
yarn create electron-app my-new-app
// 等价于
npx create-electron-app my-new-app
// 等价于
npm init electron-app my-new-app

npx

npx 用来运行本地或远程npm包的一个命令。比如前面提到的npx @vitejs/create-app

如果 npx 请求的包(比如@vitejs/create-app)没有出现在本地项目的依赖中,npm 就会把@vitejs/create-app安装到全局的 npm cache 目录下。

接着会执行create-app脚本,而这个脚本需要定义在package.jsonbin配置项下。

npm init xxxnpx create-xxx也是一般CLI工具的常用套路。

npm config

npm config是用来管理配置文件的,我们平时用的最多的是设置npm的源。

npm config set registry https://registry.npm.taobao.org 

利用npm config list,我们可以列出所有的配置项;利用get, set, delete可以执行查询,设置,删除配置项的操作。

npm install / uninstall

npm install 不指定包时,会将 package.json 列出的依赖安装到 node_modules 中,如果指定包名,则安装指定的包。主要注意:

  • -g是全局安装;

  • 如果指定了 --production ,或者 NODE_ENV 是 production,就不会安装 devDependencies 中的依赖。

  • --save 等价于 -S,安装的依赖包信息保存到 package.json中的 dependencies,这些依赖(比如vue, react)如果有进入 bundler (比如 webpack )的 Dependency Graph(依赖关系图),会被打包到项目的构建结果中;npm install vue会默认执行-S的行为,但是建议显示给出-S,给人的感觉会比较清晰。

  • --save-dev 等价于 -D,安装的依赖包信息保存到 devDependencies 中,这些依赖一般是开发环境的工具,比如 eslint, webpack, babel之类的,这些依赖一般不会被打包工具处理到构建结果中。

但是,如果你使用npm install -D vue安装了vue,并且在项目中引用了vue依赖,那么 webpack 的 Dependency Graph 中也会有vue,最终vue也会体现到构建结果中;

看到这里,是不是又懵逼了?不管是npm install -S vue还是npm install -D vue,如果项目中引用了vue,都会把vue打包进构建结果,那么-S-D有什么区别?

注意了,webpack 不关心一个依赖是dependencies还是devDependencies,只要进入 webpack 的 Dependency Graph,就会打包到结果中。

所以我们不要被构建工具迷了眼,-S-D影响的是npm install,而且影响的也是有限的场景。

如果别人 install 你的包package-a,他会顺便安装package-a中的dependencies,而不会去安装package-a中的devDependencies

分两方面来看

  • 第一种情况:生产依赖误入开发依赖

假设你的包package-a通过package.jsonmodule字段提供了一个ESM入口。

"module": "module-entry.js"

module-entry.js里面又依赖了一个包,假设是lodash-es吧。

// module-entry.js
import { cloneDeep } from "lodash-es"

但是,你没注意你是通过npm install -D lodash-es安装的,你在本地调试package-a时,没有任何问题。于是,你发布了这个package-a,同事小王安装了package-a却发现使用时报错了(因为他不会自动安装package-adevDependencies)。

  • 第二种情况:开发依赖误入生产依赖

开发环境的依赖进入了生产环境,会导致构建时多了无意义的开发依赖,打包结果变大,这常发生于开发库或组件时。

import VueAwesomeProgress from "./index.vue";

// 开发组件时,不必要的vue引入;
// 导致最终build的文件变大。
import Vue from "vue"
console.log(Vue)

VueAwesomeProgress.install = function(Vue) {
    Vue.component(VueAwesomeProgress.name, VueAwesomeProgress);
};

if (typeof window !== 'undefined' && window.Vue) {
    window.Vue.use(VueAwesomeProgress)
}

export default VueAwesomeProgress

实质上,我们在开发一个Vue组件时,仅仅需要把vue作为devDependencies即可。

npm start

npm start是一个语义化的命令。通常我们会在 scripts 中自定义 start 脚本,比如:

"start": "npm run dev"

如果没有指定自定义的 start 脚本,npm start默认会执行:

node server.js

npm run

npm run用来运行我们定义的scripts,命令后直接跟脚本名称就行。在npm run时,我们可以调用一些特殊路径下的可执行文件或脚本,这些路径包括环境变量PATH定义的路径,也包括当前项目node_modules中的./bin

In addition to the shell's pre-existing PATH, npm run adds node_modules/.bin to the PATH provided to scripts.

npm version

这个命令也是值得掌握的,从语义上看,npm version会修改package.json中的version字段,用来管理包的版本号。

你可以试着运行:

npm version major/minor/patch -m "reason for upgrade"

major/minor/patch三选一,分别代表主版本/次版本/补丁版本。当然也可以传其他的版本参数,具体参考npm-version

通常,我们还会定义一个自定义的 version 脚本,配合conventional-changelog用来自动生成CHANGELOG.md

{
  "scripts": {
    "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md"
  }
}

发包相关

npm login/ npm adduser

发布一个 npm 包的流程并不复杂。首先你必须通过命令行登录 npm,这用到了npm adduser,别名是npm login

确保你的代理正确

有时候,考虑到国内环境,我们安装依赖时,会设置 npm 的源为淘宝镜像。但是在发布 npm 包之前,必须把源切回到 npm。

npm config set registry http://registry.npmjs.org/

npm publish

发布一个npm包,发布的界限是以 version 判断的,不能发布相同的 version。即便你只是改了一个README,也必须修改 version 才能重新 npm publish

npm unpublish

与发包对应的就是移除已发布的包。你可以选择移除整个已发布的包,也可以针对性地下架某个版本。

npm pack

将package打包成 tgz 格式。举个例子,在vue-awesome-progress目录下,运行 npm pack 将得到一个 vue-awesome-progress-1.9.1.tgz,其中 1.9.1 是取自 version 字段。

然后通过 npm install vue-awesome-progress-1.9.1.tgz 会在当前目录的 node_moudles 目录下安装 vue-awesome-progress包及其相关依赖。

npm link

npm link用于创建一个符号链接,类似于 Linux 软链接(ln -s)的效果。

首先需要在待创建 link 的包目录(比如vue-awesome-progress)下运行 npm link,这会在 npm 全局文件夹下创建一个 symlink。

npm prefix -g指向 npm 全局文件夹,我这里的值是:

PS C:\Users\Tusi> npm prefix -g
C:\Users\Tusi\AppData\Roaming\npm

npm link后,C:\Users\Tusi\AppData\Roaming\npm\node_modules\中就有一个vue-awesome-progress的目录了,其实是一个快捷方式。

同时,如果 vue-awesome-progress 中有配置 bin 文件,也会被 link 到全局。

要用到 vue-awesome-progress 的地方可以通过npm link vue-awesome-progress安装它,也会安装到 node_modules 下,不过是一个全量的 vue-awesome-progress,而非npm publish后的 vue-awesome-progress。

个人感觉,npm link 适合在本地对两个及以上的包做调试用,这样就不用每次调试问题时,还要重新 npm run build, npm publish,省去了很多事。

写在结尾

当我们习惯了一个工具的常用功能时,很少会去想它背后发生了什么,甚至更少会去思考它还有没有其他能力。但是,当你有一定使用经验后,再去深入了解它,你会感叹:“卧槽!原来这个命令是用来解决这个问题的,大佬们果然还是考虑得全面!”

就像我开头说的那样,一个人如果想在技术领域进阶,一定要给自己提出足够多的问题,带着问题去深入。至于如何带着问题深入,我觉得最好是做一个自己的产品,可以是项目,也可以是组件,或者是 library,甚至是 framework。遇到困惑时,如果你发现大佬们给了解决方案,你会惊喜;如果没有,你来成为大佬!

如果您觉得这篇文章还不错,欢迎点个赞,加个关注(前端司南),真诚感谢您的支持。也欢迎和我直接交流,我是Tusi,期待与您共同进步!