欢迎光临
我们一直在努力

第11期 | npm入门:包管理的世界

第11期 | npm入门:包管理的世界

🎯 学习目标

  • 理解npm的本质:它不只是一个安装命令,而是一个完整的包管理生态系统
  • 掌握npm install/uninstall的全套用法:本地安装、全局安装、开发依赖、生产依赖
  • 理解node_modules的目录结构和查找机制
  • 掌握npx的原理和使用场景
  • 理解package-lock.json的作用和团队协作中的重要性

📖 核心知识

一、npm是什么

1.1 从"造轮子"说起

假设你要在项目里格式化一个日期。如果没有包管理器,你的选择是:

// 方案1:自己写一个(费时费力,容易出bug)
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}${month}${day}`;
}

// 方案2:去网上搜一段代码,复制粘贴(不知道代码质量,可能有坑)
// 方案3:找到一个别人写好的库,下载源码,放到项目里(手动管理更新和依赖)

有了npm之后:

# 一行命令搞定
npm install dayjs

const dayjs = require('dayjs');
console.log(dayjs().format('YYYY-MM-DD')); // 2026-07-10

这就是包管理器的价值:让你站在巨人的肩膀上,复用经过社区验证的代码。

1.2 npm的三重身份

npm不只是一个工具,它同时是三样东西:

身份说明你接触的形式
CLI工具 命令行包管理器 npm install、npm run
包仓库 全球最大的开源软件仓库 npmjs.com 网站
Registry 包的存储和分发服务 registry.npmjs.org

截至2026年,npm Registry上有超过300万个包,每周下载量超过2000亿次。它是JavaScript生态的基石。

1.3 npm vs yarn vs pnpm

三大包管理器对比:

特性npmyarnpnpm
默认安装 Node.js自带 需单独安装 需单独安装
安装速度 中等 最快
磁盘占用 最小(硬链接)
Monorepo支持 workspaces workspaces workspace(最强)
幽灵依赖 无(严格隔离)
学习成本

新手建议:先用npm,它是最通用的。等你遇到monorepo或磁盘空间问题再换pnpm。本课程全程使用npm。


二、npm install全解

2.1 基本用法

# 安装一个包
npm install express

# install可以简写为i
npm i express

# 安装指定版本
npm i express@4.18.0

# 安装最新版本
npm i express@latest

# 安装指定大版本的最新
npm i express@4

安装后,包会被放到node_modules/目录,同时写入package.json的dependencies字段:

{
"dependencies": {
"express": "^4.18.0"
}
}

2.2 依赖类型

npm有四种依赖类型,理解它们的区别至关重要:

# 1. 生产依赖(运行时需要)
npm i express
# 等同于
npm i express –save

# 2. 开发依赖(只在开发时需要,不打包到生产)
npm i jest –save-dev
# 简写
npm i jest -D

# 3. 全局安装(命令行工具)
npm i -g nodemon

# 4. 可选依赖(安装失败不报错)
npm i fsevents –save-optional
# 简写
npm i fsevents -O

在package.json中的体现:

{
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"jest": "^29.0.0"
},
"optionalDependencies": {
"fsevents": "^2.3.0"
}
}

关键区别:

# 安装所有依赖(包括devDependencies)
npm install

# 只安装生产依赖(不装devDependencies)
npm install –production
# 或设置环境变量
NODE_ENV=production npm install

在Docker部署时,这个区别很重要:

# 构建阶段:安装所有依赖
RUN npm install

# 生产阶段:只装生产依赖
RUN npm install –production

2.3 安装行为详解

# 普通安装:从registry下载,放入node_modules,更新package-lock.json
npm i axios

# 如果package.json中有该包但node_modules里没有(比如git clone后)
npm i # 会根据package-lock.json安装所有依赖

# 强制重新安装(删除后重装)
npm i axios –force

# 清除缓存后重装(解决奇怪的缓存问题)
npm cache clean –force
npm i

一个常见的坑:修改了package.json手动加了一个依赖,但忘了在node_modules里安装:

# 只更新package.json,不实际安装
npm i axios –package-lock-only

# 正确做法:正常安装
npm i axios


三、node_modules的查找机制

3.1 目录结构

假设你安装了express,express本身又依赖body-parser等包,node_modules的结构是这样的:

node_modules/
├── express/
│ ├── node_modules/ # express自己的依赖(嵌套)
│ │ ├── body-parser/
│ │ └── …
│ └── …
├── axios/
│ └── …
└── lodash/
└── …

但实际你会看到很多包都被"提升"(hoist)到了顶层:

node_modules/
├── express/
├── axios/
├── lodash/
├── body-parser/ # 被提升到顶层
├── debug/ # 被提升到顶层(多个包的公共依赖)
└── …

这是npm的依赖提升机制,目的是减少重复安装。

3.2 模块查找算法

当你在代码里require('axios')时,Node.js的查找顺序是:

require('axios');

查找过程:

1. 当前目录的node_modules/axios
2. 上级目录的node_modules/axios
3. 再上一级…
4. 直到系统根目录
5. 最后找全局node_modules

用代码验证:

// 查看模块的完整查找路径
console.log(require.resolve.paths('axios'));
// [
// '/project/node_modules',
// '/node_modules',
// '/node_modules'
// ]

实际意义:

// 项目结构
project/
├── node_modules/
│ └── axios/ # 在这里找到
├── src/
│ └── utils/
│ └── helper.js // 在这里 require('axios')

即使helper.js在src/utils/目录下,也能找到project/node_modules/axios,因为Node.js会逐级向上查找。

3.3 幽灵依赖问题

// package.json 只声明了 express
{
"dependencies": {
"express": "^4.18.0"
}
}

// 但你能直接 require express的子依赖(因为它被提升到了顶层)
const bodyParser = require('body-parser'); // 能找到!但它不在你的package.json里

这就是幽灵依赖:你用了没在package.json中声明的包。风险是:

# 某天express升级,不再依赖body-parser了
npm i express@5.0.0

# 你的代码报错了:Cannot find module 'body-parser'

pnpm通过严格的依赖隔离解决了这个问题,但用npm的话,你需要自己注意不要直接require未声明的包。


四、npm uninstall与更新

4.1 卸载

# 卸载一个包
npm uninstall express
# 简写
npm un express

# 卸载开发依赖
npm un jest

# 卸载全局包
npm un -g nodemon

卸载会同时从node_modules和package.json中删除。

4.2 检查更新

# 检查哪些包有新版本
npm outdated

# 输出示例
# Package Current Wanted Latest Location
# express 4.17.0 4.18.2 5.1.0 my-app
# jest 29.0.0 29.7.0 30.0.0 my-app

四个版本号的含义:

  • Current:当前安装的版本
  • Wanted:根据semver规则允许升级到的版本
  • Latest:registry上的最新版本(可能是大版本升级)

# 更新到Wanted版本(安全更新)
npm update

# 更新指定包
npm update express

# 更新到Latest版本(可能是大版本,需要手动指定)
npm i express@latest

4.3 npm-check-updates

如果想要批量检查大版本更新,使用npm-check-updates:

# 全局安装
npm i -g npm-check-updates

# 检查所有可更新的包
ncu

# 输出
# express ^4.17.0 → ^5.1.0
# jest ^29.0.0 → ^30.0.0

# 一次性更新package.json中的所有版本号
ncu -u

# 然后安装
npm i

⚠️ 注意:大版本升级通常有Breaking Changes,一定要看changelog和测试。


五、npx详解

5.1 npx是什么

npx是npm 5.2+自带的工具,用于执行包的命令,而不需要全局安装。

# 不用npx:全局安装再使用
npm i -g create-react-app
create-react-app my-app

# 用npx:直接执行,不需要全局安装
npx create-react-app my-app

npx的工作原理:

  • 检查本地node_modules/.bin/是否有该命令
  • 检查全局是否有该命令
  • 如果都没有,临时下载到缓存目录并执行
  • 执行完成后不会全局安装
  • 5.2 实用场景

    # 1. 执行脚手架工具(只执行一次,不需要全局安装)
    npx create-react-app my-app
    npx create-next-app my-app
    npx degit user/repo my-project

    # 2. 执行本地安装的CLI工具
    npm i eslint # 本地安装
    npx eslint . # 执行(不需要全局安装)

    # 3. 执行指定版本的包
    npx cowsay@1.5.0 "Hello"

    # 4. 执行GitHub上的脚本
    npx github:piuccio/cowsay "Hello from GitHub"

    # 5. 一次性执行命令,不污染全局
    npx serve # 在当前目录启动一个静态服务器
    npx http-server

    5.3 –yes 标志

    # 第一次执行未安装的包,npx会问你是否安装
    npx cowsay "Hello"
    # Need to install the following packages:
    # cowsay@1.5.0
    # Ok to proceed? (y)

    # 跳过确认
    npx –yes cowsay "Hello"
    # 简写
    npx -y cowsay "Hello"

    5.4 npm exec

    npm 7+引入了npm exec,功能类似npx但有细微差别:

    # npx
    npx prettier –write .

    # npm exec
    npm exec — prettier –write .

    在npm scripts中,推荐使用npm exec,因为它更明确。


    六、package-lock.json

    6.1 它是什么

    package-lock.json记录了每个依赖的确切版本和完整的依赖树。

    {
    "name": "my-app",
    "version": "1.0.0",
    "lockfileVersion": 3,
    "requires": true,
    "packages": {
    "": {
    "dependencies": {
    "express": "^4.18.0"
    }
    },
    "node_modules/express": {
    "version": "4.18.2",
    "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
    "integrity": "sha512-…"
    },
    "node_modules/body-parser": {
    "version": "1.20.1",
    "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
    "integrity": "sha512-…"
    }
    }
    }

    6.2 为什么需要它

    场景:你开发了项目,package.json里写的是"express": "^4.18.0"。

    # 你本地安装时,express最新是4.18.2
    npm install
    # 安装了 express@4.18.2

    # 半年后,同事clone项目后安装
    npm install
    # express ^4.18.0 允许 4.19.0,安装了 express@4.19.0

    # 你和同事的express版本不同!可能出bug

    有了package-lock.json:

    # 同事clone项目后
    npm install
    # 读取lock文件,安装 express@4.18.2(和你一样)
    # 每个人安装的版本完全一致

    6.3 团队协作规则

    # 规则1:package-lock.json 必须提交到Git
    git add package-lock.json

    # 规则2:不要手动编辑lock文件

    # 规则3:不要用 npm install 安装新依赖时改lock(用 npm i xxx)

    # 规则4:CI/CD中使用 npm ci 而不是 npm install

    6.4 npm ci vs npm install

    # npm install:可能修改lock文件
    npm install

    # npm ci:严格按lock文件安装,不修改
    npm ci

    npm ci的特点:

    • 删除整个node_modules后重新安装
    • 严格按package-lock.json安装,不修改版本号
    • 如果package.json和package-lock.json不一致,直接报错
    • 速度比npm install快(不需要计算依赖树)

    # GitHub Actions 中使用
    name: Install dependencies
    run: npm ci # CI环境永远用ci


    七、npm配置

    7.1 .npmrc文件

    npm的配置文件,可以放在三个位置:

    # 1. 项目级:项目根目录/.npmrc
    registry=https://registry.npmmirror.com/
    @myorg:registry=https://npm.pkg.github.com

    # 2. 用户级:~/.npmrc
    registry=https://registry.npmjs.org/
    init-author-name=Your Name
    init-license=MIT

    # 3. 全局级:npm配置目录
    npm config get globalconfig # 查看路径

    优先级:项目级 > 用户级 > 全局级。

    7.2 常用配置

    # 设置国内镜像(加速下载)
    npm config set registry https://registry.npmmirror.com/

    # 查看当前配置
    npm config list

    # 查看单个配置
    npm config get registry

    # 删除配置
    npm config delete registry

    # 使用nrm管理多个registry
    npm i -g nrm
    nrm ls # 列出所有镜像
    nrm use taobao # 切换到淘宝镜像
    nrm test # 测试各镜像速度

    7.3 私有registry

    # 企业内部registry
    npm config set @mycompany:registry https://npm.company.com/

    # 带认证的registry
    npm config set //npm.company.com/:_authToken "your-token"

    # 发布到私有registry
    npm publish –registry https://npm.company.com/


    🛠️ 实战练习

    练习1:基础题——从零初始化项目

    # 1. 创建项目目录
    mkdir my-first-project && cd my-first-project

    # 2. 初始化package.json
    npm init -y

    # 3. 安装生产依赖
    npm i express

    # 4. 安装开发依赖
    npm i -D nodemon

    # 5. 查看 node_modules 目录结构
    ls node_modules/

    # 6. 检查是否有更新
    npm outdated

    # 7. 查看express的版本
    npm ls express

    练习2:进阶题——理解依赖树

    # 创建项目
    mkdir dep-tree-demo && cd dep-tree-demo
    npm init -y

    # 安装express
    npm i express

    # 查看完整依赖树
    npm ls
    # 输出类似:
    # dep-tree-demo@1.0.0
    # └── express@4.18.2
    # ├── accepts@1.3.8
    # │ ├── mime-types@2.1.35
    # │ │ └── mime-db@1.52.0
    # │ └── negotiator@0.6.3
    # ├── body-parser@1.20.1
    # …

    # 只看顶层依赖
    npm ls –depth=0

    # 查看某个包的所有依赖
    npm ls –all express

    # 解释为什么body-parser在顶层(依赖提升)

    写一段代码验证幽灵依赖:

    // app.js
    // express依赖了body-parser,虽然你没直接安装
    const bodyParser = require('body-parser');
    console.log('bodyParser version:', require('body-parser/package.json').version);
    // 能运行!但这是不安全的——它依赖express的内部依赖

    // 检查你的package.json,没有body-parser

    练习3:挑战题——搭建CI友好的项目

    mkdir ci-friendly && cd ci-friendly
    npm init -y

    # 1. 安装依赖
    npm i express cors
    npm i -D jest eslint prettier

    # 2. 生成lock文件
    npm install

    # 3. 模拟CI环境安装
    rm -rf node_modules
    npm ci # 应该和之前安装完全一致

    # 4. 添加脚本

    修改package.json:

    {
    "name": "ci-friendly",
    "version": "1.0.0",
    "scripts": {
    "start": "node index.js",
    "test": "jest",
    "lint": "eslint .",
    "ci": "npm ci && npm run lint && npm test"
    },
    "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2"
    },
    "devDependencies": {
    "eslint": "^8.0.0",
    "jest": "^29.0.0",
    "prettier": "^3.0.0"
    }
    }

    // index.js
    const express = require('express');
    const cors = require('cors');

    const app = express();
    app.use(cors());
    app.get('/', (req, res) => res.json({ status: 'ok' }));

    if (require.main === module) {
    app.listen(3000, () => console.log('Server on http://localhost:3000'));
    }

    module.exports = app;

    // __tests__/index.test.js
    const request = require('supertest');
    const app = require('../index');

    test('GET / returns ok', async () => {
    const res = await request(app).get('/');
    expect(res.body.status).toBe('ok');
    });

    # 运行CI流程
    npm run ci


    💡 面试考点

    Q1: npm install 和 npm ci 的区别?

    A: npm install会解析package.json,可能更新package-lock.json,适合开发环境。npm ci严格按package-lock.json安装,删除并重建node_modules,不会修改lock文件,适合CI/CD环境,速度更快且确保一致性。

    Q2: 什么是幽灵依赖?怎么解决?

    A: 幽灵依赖是指项目使用了未在package.json中声明的包,但因为依赖提升(hoisting)能在node_modules中找到。风险是当被依赖的包升级或移除该子依赖时,代码会报错。解决方案是使用pnpm(严格的依赖隔离),或者自己确保只require在package.json中声明的包。

    Q3: npx的作用是什么?和npm install -g有什么区别?

    A: npx用于执行包的命令而不需要全局安装。npm install -g会永久全局安装,占用磁盘空间。npx临时下载执行后不保留全局安装。适合执行脚手架工具(如create-react-app)或一次性命令。

    Q4: package-lock.json需要提交到Git吗?

    A: 必须提交。它锁定了所有依赖的确切版本,确保团队成员和CI环境安装的依赖完全一致。没有它,^和~语义版本范围会导致不同时间安装的版本不同。

    Q5: dependencies和devDependencies的区别?

    A: dependencies是运行时需要的包(如express),devDependencies是开发时才需要的包(如jest、eslint)。npm install –production或NODE_ENV=production时只安装dependencies。在Docker多阶段构建中,生产镜像只装dependencies以减小体积。


    📌 本期要点

  • npm是三合一:CLI工具 + 包仓库 + Registry服务,是JS生态的基石
  • 四种依赖类型:dependencies(生产)、devDependencies(开发)、optionalDependencies(可选)、peerDependencies(对等)
  • node_modules使用依赖提升机制,Node.js逐级向上查找模块
  • 幽灵依赖是npm的固有问题,pnpm通过严格隔离解决
  • npx用于执行而不安装,适合脚手架和一次性命令
  • package-lock.json锁定确切版本,必须提交Git,CI环境用npm ci
  • .npmrc管理npm配置,项目级 > 用户级 > 全局级
  • 国内开发建议配置npmmirror镜像加速下载

  • 🔗 下期预告

    第12期 | package.json深度解析:项目配置大全——我们将深入这个项目"身份证"的每一个字段,从name、version到scripts、engines,让你完全掌握项目配置的方方面面。

    iPA上传工具 – IPA解析与AppStore提交

    赞(0)
    未经允许不得转载:171主机测评 » 第11期 | npm入门:包管理的世界
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址