Skip to main content

npm

Package

why

在进行项目开发时,往往需要用到别人现成的代码,但是这里就会涉及到一个问题:

  • 如果采用传统的方式,那么每次在引入一个包的时候,需要从官网下载代码,解压,然后放入到需要的项目中,这样的做法太过原始,非常繁琐。
  • 在现代开发中,引用的包往往存在复杂的依赖关系,例如模块A依赖于模块B,模块B又依赖于模块C,如果让开发者来管理这种依赖,非常容易出错也很麻烦。

因此包管理器诞生了,包管理器就是专门用于管理软件包、库以及相互之间的依赖关系的一种工具。

一般来讲,一门成熟的语言,都会有配套的包管理器:

  • Node.js: npm (Node Package Manager)。
  • Python: pip (Pip Installs Packages)。
  • Ruby: rubygems (Ruby Gems)。
  • Java: Maven (Maven Repository)。
  • PHP: Composer (Dependency Manager for PHP)。
  • Rust: Cargo (Rust's Package Manager)。
  • Go: Go mod (Go's Package Manager)。

npm

npm(Node Package Manager),是 Node 对应的包管理器。

npm 大致由3个部分组成:

  • 网站:npm 官网,注册账号、搜索某一个包查看这个包(某一个插件没有官网那种)的说明。
  • CLI(Command Line Interface):所谓命令行接口,就是在控制台输入命令来进行交互。
  • registry:npm 对应的大型仓库,上传的包都会存储到这个仓库里面。

什么是包

从软件工程的角度来讲,包是一种组织代码结构的方式。一般来讲,一个包提供了一个功能来解决某一个问题,一般一个包会将相关的所有目录和文件放到一个独立的文件夹中。

在 Node 中,通过一个特殊的文件 package.json 来描述这个包,如果要向 npm 发布包,npm 要求包必须要有 package.json 这个文件。

模块

module 即模块。一般来讲,一个单独的 JS 文件称之为一个模块,这个模块通常会包含一个或多个变量、函数、类、对象的导出。模块是一个独立的单元,可以被其他模块导入并使用。

my-package/
|-- lib/
| |-- string-utils.js
|-- package.json
|-- README.md

my-package 是一个包,而 string-utils.js 是一个模块。

(非)作用域包

scoped package(作用域包):必须以 @ 符号开头,后面跟上作用域名称,接下来一个斜杠,最后是包名:@scope-name/package-name

  1. 作用域包在安装的时候,需要将作用域名写全:
npm i @vue/cli
  1. 在被引入的时候,也需要将作用域名写全:
const mypackage = require("@myorg/mypackage");
  1. 作用域名可以充当一个命名空间,减少包名重名的情况。
  2. 通过作用域名往往也能表达某一系列包是属于某一个组织。

unscoped package(非作用域包):非作用域包由于没有特定的作用域,因此在发布的时候一定要保证包名是全局唯一的。

npm i axios

公(私)有包

Node中,包还可以分为 publicprivate,即公有包和私有包。

  • 公有包: 公共包是在 npm 注册表中公开发布的包,任何人都可以搜索、查看和安装这些包。公共包在发布时默认为开源许可证(如 MIT、BSD、Apache 等),这意味着其他人可以自由地查看源代码、修改代码并在自己的项目中使用。当你希望与广泛的开发者社区共享你的代码并允许他们参与到项目中时,可以选择发布为公共包。

  • 私有包: 私有包是在 npm 注册表中非公开发布的包,它们只能被特定的用户或团队成员搜索、查看和安装。私有包通常用于存储企业内部的代码和资源,或者在开发过程中尚未准备好向公众发布的项目。要发布和使用私有包,你需要拥有一个 npm 付费账户 并将包的 private 属性设置为 true。私有包通常都是作用域包。

npm

npm 提供了大量的 Commands 来管理包,以下介绍一些常用的命令。

基本指令

  • npm init --y:初始化项目,生成 package.json 文件。
  • npm install <package>:安装指定依赖或安装 package.json 中的依赖。
  • npm uninstall <package>:移除指定依赖。
  • npm version:查看当前 npm cli 的详细信息,相比 npm -v 显示的信息要更丰富一些。
  • npm root:查找本地或者全局安装的包的根目录。查看全局的包目录的话,需要添加 -g
  • npm info <package>:查看某一个包的详细信息,包括包的版本、依赖项、作者、描述等信息,便于开发者选择合适的包。
  • npm search <package>:对包进行搜索,提供一个关键字,会搜索出所有和关键字相关的包。
  • npm outdated:该命令可以用于检查当前项目中的依赖包是否过时,以及当前可用的最新版本。
  • npm ls:可以罗列出当前项目安装的依赖包以及依赖包下层的依赖。通过 --depth 0/1/2 来进行层级的调整,例如 npm ls --depth 1罗列出当前依赖以及当前依赖下一层所需的依赖,npm ls -g罗列出全局安装的包。
  • npm help:帮助指令,可以查看 npm 中提供的所有指令。

配置指令

  • npm config get:获取配置。
npm config get registry
  • npm config set:设置配置。
npm config set name=tom
  • npm config list: 罗列出用户配置。
; "user" config from /Users/jason/.npmrc

//registry.npmjs.org/:_authToken = (protected)

; node bin location = /Users/jason/.nvm/versions/node/v22.1.0/bin/node
; node version = v22.1.0
; npm local prefix = /Users/jason/code/tech-archive
; npm version = 10.7.0
; cwd = /Users/jason/code/tech-archive
; HOME = /Users/jason
; Run `npm config ls -l` to show all defaults.
  • npm config edit:进入到编辑模式。

链接指令

  • npm link:该命令可对一个包创建一个快捷方式。假设其他项目要用到这个包,因为有快捷方式,其他项目通过快捷方式可以快速的链接到这个包,不需要每次这个包重新发布,其他项目重新安装,常用于本地开发测试。例如,现在正在开发一个名为 A 的包,要对其进行测试,运行 npm link 后,会在全局 node_modules 目录下创建一个 A 包的软链接,当 B 包使用 A 包进行功能测试时,可通过 npm link A 进行链接。
  • npm unlink:当开发完成后,可通过 npm unlink A 断开链接。
  • npm unlink -g:当 A 包已经没有被任何项目所链接,那么可通过 npm unlink -g A 从全局 node_modules 里面删除。

缓存指令

当安装、更新或者卸载包的时候,npm 会将这些包的 tarball 文件缓存到本地磁盘上,有助于加速将来的安装过程。之后再次安装的时候,可以直接从缓存文件中去获取,而无需再次从远程仓库下载。

note

tarball 文件是一种压缩文件格式,通常用于在 Unix 和 Linux 系统中打包和分发源代码、二进制文件或其他文件。tarball 文件的扩展名通常为 .tar.gz.tgz,它们是通过将多个文件打包成一个 .tar 文件,然后将该文件进行 gzip 压缩而创建的。

在 npm 中,tarball 文件通常用于将包的所有文件(源代码、二进制文件、文档等)打包成一个单独的文件,以便在安装或更新包时从 npm 仓库下载。当运行 npm install <package> 时,npm 会从远程仓库下载包的 tarball 文件,然后在本地解压缩和安装该包。

  • npm cache clean:清理缓存。在较新的版本中,目前已经不推荐直接清理缓存,而是推荐 npm cache verify 去验证缓存。
  • npm cache verify:验证缓存。验证缓存的完整性,检查缓存是否已经过期、无效、损坏,也就是说,验证缓存是否有用,如果没用再进行删除。
  • npm cache add <package>:添加缓存。一般不需要手动添加缓存,因为在安装包的时候就会自动添加缓存。
  • npm cache ls:查看缓存。查看npm缓存的所有的包。
  • npm config get cache:查看缓存目录。

更新指令

  • npm update:更新当前项目中的依赖包。npm 会检查是否有新的版本,如果有就会进行更新,在更新的时候会去满足 package.json 里面的版本范围规定。
  • npm update <package>:更新指定的包。
  • npm audit:用于检查当前项目中哪些依赖有漏洞。
  • npm dedupe:优化项目中的依赖树的结构。npm dedupe 无法将所有重复的包进行消除,因为很多时候不同的依赖项就是需要不同版本的相同依赖,但 npm dedupe会尽量去消除重复的包。
  • npm prune:删除没有在 package.json 文件中列出的依赖包,该命令可以帮助清理 node_modules,删除不再需要的依赖。

发布指令

  • npm login:登陆 npm 账号,用于发布包。
  • npm profile:查看账号信息。
  • npm publish: 发布包,将包推送到 npm 仓库。
  • npm whoami:查看当前登录用户。

package.json

package.json 是一个包的必要文件,以下介绍一些常用的配置。

说明信息

  • name:包的名称,必须是唯一的。
  • version:包的版本号,一般由三个数字部分组成,格式为 x.y.z
    • x 代表主版本号(Major Version),当软件包发生了重大变化或者不兼容的升级,那么需要增加主版本号。
    • y 代表次版本号(Minor Version),当软件包增加了新的功能或者新的特性,需要增加次版本号。
    • z 代表修订号(Patch Version),当软件包进行问题修复、性能优化等较小的改动,需要增加修订号。
  • description:包的描述信息。
  • keyword:包的关键词,用于搜索和分类。
  • author:作者信息。
  • contributors:包的贡献者名单。
  • license:包的许可证信息,指定包的开源类型。
  • repository:包的源代码仓库信息,可提供一个 Git 地址。
  • engines:指定项目需要的 Node 版本以及 npm 版本,减少用户在使用包时出现一些因为版本不支持而产生的问题。
{
"name": "demo",
"version": "1.1.1",
"description": "A JavaScript library for building UI",
"keyword": ["library", "JavaScript", "UI"],
"author": {
"name": "Jason Wang",
"email": "jasonwang@example.com",
"url": "https://example.com/johndoe"
},
"contributors": ["Tom", "Jack"],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/username/example.git"
},
"engines": {
"node": ">=12.0.0",
"npm": ">=6.0.0"
}
}

执行配置

  • main:包的入口文件。
  • browser:如果是在浏览器环境下,可以替换一些特定模块或者文件。
    Details
    • 指定浏览器的入口文件。
    {
    "main": "index.js",
    "browser": "browser.js"
    }

    在 Node 环境下,index.js 作为入口文件,如果是浏览器环境,使用 browser.js 作为入口文件。

    • 替换特定的模块。
    {
    "browser": {
    "./node-version.js": "./browser-version.js"
    }
    }
    • 排除某些模块。
    {
    "browser": {
    "fs": false
    }
    }
  • scripts:配置可执行命令。
    Details
    {
    "scripts": {
    "start": "node index.js",
    "test": "jest",
    "build": "webpack",
    "lint": "eslint src",
    "format": "prettier --write src"
    }
    }

    脚本是可以配置生命周期钩子方法,pre 代表在执行某个脚本之前,post 代表在执行某个脚本之后。

    {
    "scripts": {
    "prestart": "npm run build",
    "start": "node index.js",
    "test": "mocha",
    "build": "webpack",
    "lint": "eslint src",
    "format": "prettier --write src",
    "posttest": "npm run lint && npm run format"
    }
    }

    prestart 代表在执行 start 之前先执行,posttest 代表在执行 test 之后再执行。

依赖配置

  • dependencies:包的依赖列表,最终打包的时候,是会将这一部分依赖打包进去。

  • devDependencies:包的开发依赖,开发的时候会用到,但是最终打包的时候不需要打包进去。

  • peerDependencies:该配置项通常用于项目时,表示需要与项目一起使用的依赖,确保这些依赖有一个合适的版本。
    Details

    假设现在在开发一个 React 插件,在开发 React 的时候肯定会涉及到使用 React 的环境,如果此时将 React 记入到 dependencies,那么则意味着使用者在使用此插件的时候,也会去下载 React。 这里存在两个问题:

    • 使用者这既然使用此插件,那么使用者也是在做 React 的开发,使用者的项目肯定已经安装了 React。
    • 如果不记入到 dependencies 里面,那么又会存在因为版本不一致可能出现的兼容问题。
    peerDependencies 就是来解决此问题:
    {
    "name": "my-react-plugin",
    "version": "1.0.0",
    "peerDependencies": {
    "react": "^17.0.0",
    "react-dom": "^17.0.0"
    }
    }

    使用者在使用此插件的时候,必须确保安装符合版本要求的依赖,否则 npm 是会给出警告。
    在开发插件的时候,如果直接将所有用到的依赖全部声明为 peerDependencies,则意味着用户在使用这个插件的时候,需要手动的去安装众多的依赖,这显然也是不合适的。因此 peerDependencies 一般只记录一个插件的主要依赖。

版本范围

  • ^:表示允许更新到相同主版本号的最新版本,也就是说次版本可以变,补丁版本可以变,但是主版本不能变。例如:^1.2.3 更新的时候,允许的范围就是 >= 1.2.3< 2.0.0
  • :表示主版本号和次版本号都必须相同,也就是说能够更新的只有补丁号。例如:~1.2.3 更新的时候,允许的范围为 >= 1.2.3< 1.3.0
  • 版本范围详细规则

发布配置

  • files:发布包时,只有 files 字段里面出现的文件或目录才会被上传。
  • type:设置模块类型,commonjsmodule
    Details

    type 不是 npm 所提供的配置选项,而是 Node 提供的配置选项。

    • commonjs
      type 的值设置为 commonjs 时,node.js 将默认使用 CommonJS 模块系统,这是 Node.js 中最常见的模块系统。在这种情况下,可以直接使用 require 函数来导入模块。如果想使用 ECMAScript 模块,则需要将文件扩展名设置为 .mjs
    • module
      type 的值设置为 module 时,Node.js 将默认使用 ECMAScript 模块系统。在这种情况下,可以直接使用 importexport 语法来导入和导出模块。如果你想使用 CommonJS 模块,则需要将文件扩展名设置为 .cjs
  • exports:定义一个模块的导出映射。通过这个配置项,可以对模块的导入环境以及条件做一个更精细的控制,指定不同的模块的入口文件。
    Details
    {
    "exports": {
    "import": "./dist/index.esm.js",
    "require": "./dist/index.cjs"
    }
    }
    • 使用的 ESM,那么在导入模块的时候,Node 会去加载 index.esm.js
    • 使用的是 CommonJS,那么在导入模块的时候,Node 会去加载 index.cjs

Verdaccio

在企业开发中,很多时候要发布的包是私有的,npm 上面虽然支持发布私有包,但是需要付费账号,因此更好的选择就是搭建私有服务器。

  • 能够保证代码的私密性。
  • 处于局域网内部,下载速度更快。
  • 可以将发布的包做一些权限上的设置,利于维护。

Verdaccio 是企业开发中非常流行的用来搭建 npm 私有仓库的一个工具,通过该工具可以快速的搭建一个 npm 私服。

主要特点

  • 轻量级:Verdaccio 采用 Node.js 编写,安装和运行非常快速。它不依赖于任何外部数据库,而是将数据存储在本地文件系统中。
  • 简单的配置:Verdaccio 的配置非常简单,只需一个 .yaml 文件即可。可以轻松地指定用户权限、上游代理、缓存设置等。
  • 缓存和代理:Verdaccio 可以作为上游 npm 注册表的代理,从而帮助减轻网络延迟和提高包的安装速度。同时,它还会缓存已经下载的包,以便在没有互联网连接的情况下也能正常工作。
  • 访问控制:Verdaccio 支持基于用户和包的访问控制,可以轻松地管理谁可以访问、发布和安装私有 npm 包。
  • 插件支持:Verdaccio 支持插件,可以扩展其功能,如添加身份验证提供程序、审计日志等。

安装与启动

// 安装
npm i -g verdaccio
// 查看 verdaccio 基本信息
verdaccio -h
// 启动
verdaccio

配置

Verdaccio 基本上做到了开箱即用,但是很多时候需要根据项目的需求做一些配置,以下是一些常用配置。

  • storage:存储包的路径。
  • web:网站相关的配置。
  • uplinks:上游代理,通过私服下载某些包的时候,私服如果没有,此时会从上游代理中去下载这些包,然后缓存到私服。
  • packages:权限控制。
  • auth:设置用户身份的验证方法,默认采用的是 htpasswd 的方式。
storage: /Users/username/.local/share/verdaccio/storage
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'@your-scope/*':
access: $authenticated
publish: $authenticated
proxy: npmjs
'**':
access: $all
publish: $authenticated
proxy: npmjs

@your-scope/ 只允许认证过的用户访问和发布,对于其他的包,所有用户都能够访问,但是只有认证过的用户才能发布,从而能够对权限做一个很好的控制。

nrm

nrm 是一个专门用于管理 npm 镜像的工具,英语全称就是 npm registry manager。

npm i -g nrm

nrm 常见的指令:

  • nrm ls:列出所有可用镜像。
  • nrm use <registry>:切换镜像。
  • nrm add <registry> <url>:添加镜像。
  • nrm del <registry>:删除镜像。