[npm] NPM入门教程

1 NPM

1.1 简介

  • NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:
  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的命令行程序上传到NPM服务器供别人使用。
  • npm 的工作原理

npm的工作原理是基于一个庞大的公共仓库

  • 每个包都包含一个package.json文件,其中包含有关包的元数据,如名称、版本、作者和依赖项等。
  • npm会自动从公共仓库下载这些包,并将其安装到本地项目的node_modules目录中。

npm的依赖项解析算法是基于一个叫做"模块系统"的概念。

  • 当npm需要安装一个新包时,它会检查该包所依赖的其他包,并递归地安装这些包及其依赖项。
  • 这种递归安装的过程保证了项目中所有的包和依赖项都能够被正确地安装和管理。
  • nodejs 与 npm

由于新版的nodejs已经集成了npm,所以之前npm也一并安装好了。同样可以通过输入 "npm -v" 来测试是否成功安装。命令如下,出现版本提示表示安装成功:
(因此,npm 可随 nodejs 一同安装。具体可参见:NodeJs入门教程-2 Node.js 安装配置 - 博客园/千千寰宇 )

$ npm -v
2.3.0
  • 如果你安装的是旧版本的 npm,可以很容易得通过 npm 命令来升级,命令如下:
$ sudo npm install npm -g
/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.js
npm@2.14.2 /usr/local/lib/node_modules/npm

如果是 Window 系统使用以下命令即可:

npm install npm -g

使用淘宝镜像的命令:

npm install -g cnpm --registry=https://registry.npmmirror.com

1.2 使用 npm 命令安装模块

  • npm 安装 Node.js 模块语法格式:
$ npm install <Module Name>

详情参见:https://docs.npmjs.com/cli/v7/commands/npm-install/

以下实例,我们使用 npm 命令安装常用的 Node.js web框架模块 : express:

$ npm install express

安装好之后,express 包就放在了工程目录下的 node_modules 目录中,因此在代码中只需要通过 require('express') 的方式就好,无需指定第三方包路径。

var express = require('express');
  • 安装指定的包或依赖项,并将其添加到package.json文件中的dependencies对象中
npm install --save <package-name>
  • 安装指定的包或依赖项,并将其添加到package.json文件中的devDependencies对象中
npm install --save-dev <package-name>

1.3 全局安装与本地安装

  • npm包安装分为本地安装(local)、全局安装(global)两种,从敲的命令行来看,差别只是有没有-g而已,比如
npm install express          # 本地安装
npm install express -g   # 全局安装

如果出现以下错误:

npm err! Error: connect ECONNREFUSED 127.0.0.1:8087 

解决办法为:

$ npm config set proxy null

本地安装

  1. 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。
  2. 可以通过 require() 来引入本地安装的包。

全局安装

  1. 将安装包放在 /usr/local 下或者你 node 的安装目录。
  2. 可以直接在命令行里使用。

如果你希望具备两者功能,则需要在两个地方安装它或使用 npm link

接下来我们使用全局方式安装 express

$ npm install express -g

安装过程输出如下内容,第一行输出了模块的版本号及安装位置。

express@4.13.3 node_modules/express
├── escape-html@1.0.2
├── range-parser@1.0.2
├── merge-descriptors@1.0.0
├── array-flatten@1.1.1
├── cookie@0.1.3
├── utils-merge@1.0.0
├── parseurl@1.3.0
├── cookie-signature@1.0.6
├── methods@1.1.1
├── fresh@0.3.0
├── vary@1.0.1
├── path-to-regexp@0.1.7
├── content-type@1.0.1
├── etag@1.7.0
├── serve-static@1.10.0
├── content-disposition@0.5.0
├── depd@1.0.1
├── qs@4.0.0
├── finalhandler@0.4.0 (unpipe@1.0.0)
├── on-finished@2.3.0 (ee-first@1.1.1)
├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1)
├── debug@2.2.0 (ms@0.7.1)
├── type-is@1.6.8 (media-typer@0.3.0, mime-types@2.1.6)
├── accepts@1.2.12 (negotiator@0.5.3, mime-types@2.1.6)
└── send@0.13.0 (destroy@1.0.3, statuses@1.2.1, ms@0.7.1, mime@1.3.4, http-errors@1.3.1)

1.4 查看安装信息

你可以使用以下命令来查看所有全局安装的模块:

$ npm list -g

├─┬ cnpm@4.3.2
│ ├── auto-correct@1.0.0
│ ├── bagpipe@0.3.5
│ ├── colors@1.1.2
│ ├─┬ commander@2.9.0
│ │ └── graceful-readlink@1.0.1
│ ├─┬ cross-spawn@0.2.9
│ │ └── lru-cache@2.7.3
……

如果要查看某个模块的版本号,可以使用命令如下:

$ npm list grunt

projectName@projectVersion /path/to/project/folder
└── grunt@0.4.1
  • 获取包信息
npm info vue

1.5 使用 package.json

package.json

package.json 位于模块的目录下,用于定义包的属性。接下来让我们来看下 express 包的 package.json 文件,位于 node_modules/express/package.json 内容:

{
  "name": "express",
  "description": "Fast, unopinionated, minimalist web framework",
  "version": "4.13.3",
  "author": {
    "name": "TJ Holowaychuk",
    "email": "tj@vision-media.ca"
  },
  "contributors": [
    {
      "name": "Aaron Heckmann",
      "email": "aaron.heckmann+github@gmail.com"
    },
    {
      "name": "Ciaran Jessup",
      "email": "ciaranj@gmail.com"
    },
    {
      "name": "Douglas Christopher Wilson",
      "email": "doug@somethingdoug.com"
    },
    {
      "name": "Guillermo Rauch",
      "email": "rauchg@gmail.com"
    },
    {
      "name": "Jonathan Ong",
      "email": "me@jongleberry.com"
    },
    {
      "name": "Roman Shtylman",
      "email": "shtylman+expressjs@gmail.com"
    },
    {
      "name": "Young Jae Sim",
      "email": "hanul@hanul.me"
    }
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/strongloop/express.git"
  },
  "homepage": "http://expressjs.com/",
  "keywords": [
    "express",
    "framework",
    "sinatra",
    "web",
    "rest",
    "restful",
    "router",
    "app",
    "api"
  ],
  "dependencies": {
    "accepts": "~1.2.12",
    "array-flatten": "1.1.1",
    "content-disposition": "0.5.0",
    "content-type": "~1.0.1",
    "cookie": "0.1.3",
    "cookie-signature": "1.0.6",
    "debug": "~2.2.0",
    "depd": "~1.0.1",
    "escape-html": "1.0.2",
    "etag": "~1.7.0",
    "finalhandler": "0.4.0",
    "fresh": "0.3.0",
    "merge-descriptors": "1.0.0",
    "methods": "~1.1.1",
    "on-finished": "~2.3.0",
    "parseurl": "~1.3.0",
    "path-to-regexp": "0.1.7",
    "proxy-addr": "~1.0.8",
    "qs": "4.0.0",
    "range-parser": "~1.0.2",
    "send": "0.13.0",
    "serve-static": "~1.10.0",
    "type-is": "~1.6.6",
    "utils-merge": "1.0.0",
    "vary": "~1.0.1"
  },
  "devDependencies": {
    "after": "0.8.1",
    "ejs": "2.3.3",
    "istanbul": "0.3.17",
    "marked": "0.3.5",
    "mocha": "2.2.5",
    "should": "7.0.2",
    "supertest": "1.0.1",
    "body-parser": "~1.13.3",
    "connect-redis": "~2.4.1",
    "cookie-parser": "~1.3.5",
    "cookie-session": "~1.2.0",
    "express-session": "~1.11.3",
    "jade": "~1.11.0",
    "method-override": "~2.3.5",
    "morgan": "~1.6.1",
    "multiparty": "~4.1.2",
    "vhost": "~3.0.1"
  },
  "engines": {
    "node": ">= 0.10.0"
  },
  "files": [
    "LICENSE",
    "History.md",
    "Readme.md",
    "index.js",
    "lib/"
  ],
  "scripts": {
    "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
    "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",
    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
    "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"
  },
  "gitHead": "ef7ad681b245fba023843ce94f6bcb8e275bbb8e",
  "bugs": {
    "url": "https://github.com/strongloop/express/issues"
  },
  "_id": "express@4.13.3",
  "_shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",
  "_from": "express@*",
  "_npmVersion": "1.4.28",
  "_npmUser": {
    "name": "dougwilson",
    "email": "doug@somethingdoug.com"
  },
  "maintainers": [
    {
      "name": "tjholowaychuk",
      "email": "tj@vision-media.ca"
    },
    {
      "name": "jongleberry",
      "email": "jonathanrichardong@gmail.com"
    },
    {
      "name": "dougwilson",
      "email": "doug@somethingdoug.com"
    },
    {
      "name": "rfeng",
      "email": "enjoyjava@gmail.com"
    },
    {
      "name": "aredridel",
      "email": "aredridel@dinhe.net"
    },
    {
      "name": "strongloop",
      "email": "callback@strongloop.com"
    },
    {
      "name": "defunctzombie",
      "email": "shtylman@gmail.com"
    }
  ],
  "dist": {
    "shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",
    "tarball": "http://registry.npmjs.org/express/-/express-4.13.3.tgz"
  },
  "directories": {},
  "_resolved": "https://registry.npmjs.org/express/-/express-4.13.3.tgz",
  "readme": "ERROR: No README data found!"
}

package.json 属性说明

  • name - 包名。
  • version - 包的版本号。
  • description - 包的描述。
  • homepage - 包的官网 url 。
  • author - 包的作者姓名。
  • contributors - 包的其他贡献者姓名。
  • dependencies - (生产环境下的)依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。

安装项目运行时所依赖的模块。比如jQuery库,等项目上线以后依然是要继续使用的,我们就要安装在生产环境中,如果没有把需要的依赖安装到生产环境中,项目上线运行时就有可能会报错。
使用 --save 命令安装到 dependencies 下,命令语法:

npm install --save packageName

# 简写
npm i -S packageName
  • devDependencies - (开发环境下的)依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。

只会在开发环境下依赖的模块,生产环境不会被打入包内
即:安装项目开发时所依赖的模块。比如像webpack/sass等工具,只是用来构建项目和打包,这些都是在开发阶段才使用的,等项目上线后就用不到webpack工具了,那么我们就可以把webpack安装到开发环境中,使用 --save-dev命令安装到devdependencies下,命令语法:

npm install --save-dev packageName

# 简写
npm i -D packageName
  • peerDependencies - 主要作用是用来做版本检查的

例如 react-router 的 peerDependencies 是 react>=16.8.0,那么就会出现 3 种情况:

  • 项目下压根没有安装 react
  • 项目下的 react 符合要求,确实 >=16.8.0,比如 17.0.2
  • 项目下的 react 不符合版本要求,比如 16.0.0
  • repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上。
  • main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。
  • keywords - 关键字

devDependencies

npm的包查找规则

  • dependenciesdevDependenciespeerDependenciesNPM 包的查找规则密切相关。

这是区分三者的基础知识,如果不搞懂 NPM 包的查找规则,在排查问题的时候也是会一头雾水。

{
  "dependencies": {
    "@remix-run/router": "1.6.2"
  },
  "devDependencies": {
    "react": "^18.2.0"
  },
  "peerDependencies": {
    "react": ">=16.8"
  },
}

我们从上图可以看到它三个类型都有,有 1 个 dependencies、1 个 devDependencies 和 1 个 peerDependencies
@remix-run/router 自己是 0 依赖,react 则有依赖了 loose-envify,而 loose-envify 依赖 js-tokens,也就是 react -> loose-envify -> js-tokens

npm 9.5.1
npm 包管理器项目下有 5 个 npm 包,对应我们上面的分析。
把所有的依赖都打平到 node_modules 下面
安装了 peerDependencies

  • 按照 官方文档 描述:

如果传递给 require() 的模块标识符不是 core 模块,并且不是以 '/'、'../' 或 './' 开头,则 Node.js当前模块的目录开始,并添加 /node_modules,并尝试从中加载模块。
如果在那里找不到它,则它移动到父目录,依此类推,直到到达文件系统的根目录

  • 也就是说: Node.js 首先判断它是不是 Node.js 自带的 core 包,如果不是,则继续判断是否为相对路径或者绝对路径,如果都不是那就只能是第三方的 NPM 包,则:它就会按照如下规则查找:
  • 先去项目目录下的 node_modules 找,如果能找到就返回
  • 找不到的话,向上级目录找,直到找到 /node_modules

例如:我在 /home/johnny/demo/index.js 里面有下面一段代码:

import { useHistory } from 'react-router-dom'

它的查找规则就是:

  • /home/johnny/demo/node_modules/react-router-dom : 先看看项目下的 node_modules 是否存在
  • /home/johnny/node_modules/react-router-dom :再看父级目录的 node_modules 是否存在
  • /home/node_modules/react-router-dom :同上
  • /node_modules/react-router-dom :同上
  • 你可以使用 require.resolve.paths API 查看解析的过程。

例如: console.log(require.resolve.paths('react-router-dom'))

创建 package.json

  • 搭建一个前端项目之前,通常会在项目的根目录下生成一个名为package.json的文件作为NPM包的描述文件,使用该文件来定义项目信息、配置包依赖关系。
  • package.json文件可以自己手动创建,也可以使用命令来创建:
npm init

文件中包含了NPM包的基本信息(项目名称、版本号、项目描述、作者)和依赖管理等。

{
    "name": "demo",
    "version": "1.0.0",
    "devDependencies": {
        "webpack": "^4.29.6"
    }
}

npm run <package.json-script>

  • 运行package.json文件中定义的脚本
npm run <package.json-script>

换言之:npm run命令需和项目根目录下的package.json文件配合使用

npm run执行package.json文件的“scripts”属性中定义的命令,如下例:

{
  "name": "my-project",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "author": "zhouguoqing <zhouguoqing@clinbrain.com>",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "unit": "jest --config test/unit/jest.conf.js --coverage",
    "e2e": "node test/e2e/runner.js",
    "test": "npm run unit && npm run e2e",
    "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
    "build": "node build/build.js"
  }
}

在项目根目录下打开终端(cmdpower shell),执行npm run build,将执行node命令:node build/build.js

执行npm run start,将执行npm run dev

  • ruoyi-ui前端框架为例:
  • node : v20
  • package.json
{
  ...
  "scripts": {
    "dev": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
    "build:prod": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
    "build:stage": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build --mode staging",
    "preview": "node build/index.js --preview",
    "lint": "eslint --ext .js,.vue src"
  }
  ...
}
  • npm run dev

  • npm run build:prod



dist即构建成功后的输出目录

1.6 卸载模块

  • 我们可以使用以下命令来卸载 Node.js 模块。
$ npm uninstall express
  • 卸载后,你可以到 /node_modules/ 目录下查看包是否还存在,或者使用以下命令查看:
$ npm ls

1.7 更新模块

我们可以使用以下命令更新模块:

$ npm update express

1.8 搜索模块

使用以下来搜索模块:

$ npm search express

1.9 创建模块

创建模块,package.json 文件是必不可少的。我们可以使用 NPM 生成 package.json 文件,生成的文件包含了基本的结果。

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (node_modules) runoob                   # 模块名
version: (1.0.0) 
description: Node.js 测试模块(www.runoob.com)  # 描述
entry point: (index.js) 
test command: make test
git repository: https://github.com/runoob/runoob.git  # Github 地址
keywords: 
author: 
license: (ISC) 
About to write to ……/node_modules/package.json:      # 生成地址

{
  "name": "runoob",
  "version": "1.0.0",
  "description": "Node.js 测试模块(www.runoob.com)",
  ……
}


Is this ok? (yes) yes

以上的信息,你需要根据你自己的情况输入。在最后输入 "yes" 后会生成 package.json 文件。

  • 接下来我们可以使用以下命令在 【npm 资源库】中注册用户(使用邮箱注册):
$ npm adduser
Username: mcmohd
Password:
Email: (this IS public) mcmohd@gmail.com
  • 接下来我们就用以下命令来发布模块:
$ npm publish

如果你以上的步骤都操作正确,你就可以跟其他模块一样使用 npm 来安装。

1.10 版本号

  • 使用 NPM 下载和发布代码时都会接触到版本号。NPM 使用语义版本号来管理代码,这里简单介绍一下。

  • 语义版本号分为X.Y.Z三位,分别代表主版本号次版本号补丁版本号。当代码变更时,版本号按以下原则更新。

  • 如果只是修复bug,需要更新Z位。

  • 如果是新增了功能,但是向下兼容,需要更新Y位。
  • 如果有大变动,向下不兼容,需要更新X位。
  • 版本号有了这个保证后,在申明第三方包依赖时,除了可依赖于一个固定版本号外,还可依赖于某个范围的版本号。例如"argv": "0.0.x"表示依赖于0.0.x系列的最新版argv。

NPM支持的所有版本号范围指定方式可以查看官方文档

1.11 npm config

  • 查看npm全局安装保存路径
# npm config get prefix
C:\Useres\new\AppData\Roaming\npm
  • 查看npm安装缓存cache路径
# npm config get cache
C:\Useres\new\AppData\Local\npm-cache
  • 查看目前的镜像设置
npm config get registry
  • 更换镜像(淘宝镜像)
# 临时指定淘宝镜像源:
npm --registry https://registry.npm.taobao.org install express

# 永久指定淘宝镜像源:
npm config set registry https://registry.npm.taobao.org

# 安装 cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org

备注:以上淘宝镜像过期,可以尝试安装淘宝新的镜像,如下:
npm config set registry https://registry.npmmirror.com

  • 检查镜像是否安装成功
npm config get registry

1.12 npm的顺序钩子(前置钩子/后置钩子)

  • npm 脚本有prepost两个钩子。

举例来说,build脚本命令的钩子就是prebuild和postbuild:

{
    "scripts": {
        "prebuild": "echo I run before the build script",
        "build": "webpack",
        "postbuild": "echo I run after the build script"
    }
}
  • 用户执行npm run build的时候,会自动按照下面的顺序执行。

npm run prebuild && npm run build && npm run postbuild&&代表继发执行,执行完前面再执行后面;&代表并行执行,同时执行)
因此,可以在这两个钩子里面,完成一些准备工作和清理工作。例如:

{
    "scripts": {
        "clean": "rm -rf ./dist && mkdir dist",
        "prebuild": "npm run clean",
        "build": "webpack"
    }
}
  • 执行npm run build时,就会执行:npm run prebuild && npm run build

  • npm 默认提供下面这些钩子:

prepublish, publish, postpublish :发布模块

preinstall, install, postinstall :安装模块

preuninstall, uninstall, postuninstall :卸载模块

preversion, version, postversion :在使用 npm version 修改版本号的时候执行

pretest, test, posttest :执行 npm test 的时候

prestop, stop, poststop :执行 npm stop 的时候

prestart, start, poststart :执行 npm start 的时候

prerestart, restart, postrestart :执行 npm restart 的时候

preshrinkwrap, shrinkwrap, postshrinkwrap :执行 npm shrinkwrap 的时候

1.Y NPM 常用命令

除了本章介绍的部分外,NPM还提供了很多功能,package.json里也有很多其它有用的字段。

除了可以在 npmjs.org/doc/ 查看官方文档外,这里再介绍一些NPM常用命令。

  • NPM提供了很多命令,例如install和publish,使用npm help可查看所有命令。

  • 查看某条命令的详细帮助

命令格式: npm help <command>
样例命令:npm help install

  • package.json所在目录下使用npm install . -g可先在本地安装【当前命令行程序】,可用于发布前的本地测试。

  • 把当前目录下node_modules子目录里边的对应模块更新至最新版本

npm update <package>
  • 把【全局安装的对应命令行程序】更新至最新版
npm update <package> -g
  • 清空NPM本地缓存,用于对付使用相同版本号发布新版本代码的人
npm cache clear
  • 撤销发布自己发布过的某个版本代码
npm unpublish <package>@<version>

1.X npm 使用最佳实践

  • 始终在项目根目录下创建一个package.json文件。这个文件包含了项目的元数据和依赖项信息。
  • 使用npm install命令来安装依赖项,而不是手动下载和安装。
  • 使用--save--save-dev参数来将依赖项添加到package.json文件中的dependenciesdevDependencies对象中。
  • 避免使用全局安装npm包,因为这会污染全局环境,也会使项目难以维护。
  • 使用.npmignore文件来忽略不必要的文件或目录,以减小发布的包的大小。
  • 使用remove-node-modules移除npm项目下的node_modules模块
  • step1 安装 : npm install -g remove-node-modules
  • step2 使用 : 当前项目根目录下输入 remove-node-modules ,即可快速删除 node_modules 文件夹

2 NPM的FAQ

Q1 : npm run build时报错:Error: ENOTEMPTY: directory not empty, rmdir ...

  • 问题描述

例如:Error: ENOTEMPTY: directory not empty, rmdir 'D:/project/vite-react/node_modules/.vite/deps'

  • 解决方法:
  • windows 系统
{
  "scripts": {
    "predev": "rd /s /q node_modules\\.vite", // rd 
    "dev": "vite --host"
  }
}

  • macos / linux 系统
{
  "scripts": {
    "predev": "rm -rf ./node_modules/.vite",
    "dev": "vite --host"
  }
}
  • 参考文献

X 参考文献

  • npmjs 官网
  • 其他
posted @ 2024-03-17 20:36  千千寰宇  阅读(33)  评论(0编辑  收藏  举报