主题
NPM与发布 npm 包(Day8 2/4)
本页对应 Day08_Node,共 6 段视频,约 44 分钟。
学习目标
- 能够使用
npm install 包名@版本号安装指定版本的包,并通过package.json与运行结果验证版本生效。 - 能够使用
npm remove 包名删除本地包、使用npm remove -g 包名删除全局包,并验证删除后依赖与命令均不可用。 - 能够使用
npm outdated检查可更新包,使用npm update 包名更新包,并说明版本范围与锁版本对更新结果的影响。 - 能够按 npm 与 Git 协作流程完成项目初始化、忽略
node_modules、克隆项目后执行npm install,并在拉取更新后重新安装依赖以恢复项目运行。 - 能够在
package.json的scripts中配置命令别名,使用npm run 别名执行脚本,并说明start别名可简写为npm start。 - 能够安装并使用
cnpm通过淘宝镜像初始化项目、安装依赖,并验证其与 npm 命令的基本对应关系。
知识点
本节回顾清单
js
// 1. CommonJS
// 1.1 模块中暴露数据
module.exports
exports
// 1.2 如何导入模块(引用模块)
require() // 返回值就是模块暴露的数据
// 1.3 模块文件的扩展名
// .js .json .node 其他 目录
// 模块路径省略扩展名
// 2. ESM
// 2.1 暴露数据
// ① 暴露单个数据
export default 数据;
// ② 暴露多个数据
export 变量声明语句
export 变量声明语句
export 变量声明语句
// 或者
export { 变量名, 变量名, 变量名, 变量名 }
// 2.2 导入模块
// ① 如果模块暴露单个数据
import 变量名 from '模块名'
// ② 如果模块暴露多个数据
import { 变量名, 变量名 } from '模块名'
import * as 变量名 from '模块名'
// 2.3 开启 ESM 模块规则
// 方式一:脚本扩展改为 .mjs
// 方式二:package.json 配置 {"type":"module"}
// 3. 模块的路径 文件的路径
// 文件的路径,使用相对路径 参照命令行所在的目录
// 模块的路径,使用相对路径 参照所在文件的目录,跟命令行所在目录无关!CommonJS 的暴露与导入
为什么需要: Node 早期没有浏览器那套模块语法,代码要拆分、复用,就必须有“导出”和“导入”的约定。CommonJS 就是这套约定。
怎么想: 一个文件就是一个模块。模块内部把要给别人用的数据挂到 module.exports 上;另一个文件用 require() 拿到这个返回值。
怎么做:
js
// a.js
const name = '高小乐'
module.exports = { name }
// b.js
const data = require('./a')
console.log(data.name)exports 也可以挂属性,例如 exports.name = name。但注意:exports 只是 module.exports 的引用,不能直接写 exports = { name } 来替换整个导出对象。
易错点: require() 的返回值就是对方暴露的数据,不是“模块对象”本身;module.exports 和 exports 混用时,以最终 module.exports 指向为准。
CommonJS 的扩展名与目录
为什么需要: 写 require('./a') 时,我们不想每次都写 .js,Node 需要有一套查找规则。
怎么想: 模块路径可以省略扩展名,Node 会按 .js、.json、.node 等规则尝试;如果给的是目录,还会找目录下的入口文件。
怎么做:
js
const a = require('./a') // 可能找到 a.js
const data = require('./data') // 可能找到 data.json易错点: 省略扩展名不是“随便什么文件都能省”,要符合 Node 的解析规则;.json 被 require 时返回的是解析后的对象。
ESM 的暴露
为什么需要: ESM 是 ES6 标准模块语法,浏览器和现代 Node 都支持。它让导出更静态、更清晰。
怎么想: 只给一个主数据,用 export default;要给多个具名数据,用 export 声明或统一 export { ... }。
怎么做:
js
// 单个默认导出
export default function () {}
// 多个具名导出
export const a = 1
export const b = 2
// 或
const c = 3
const d = 4
export { c, d }易错点: 一个模块只能有一个 export default;export { 变量名 } 不是对象字面量,不能写成 export { a: 1 }。
ESM 的导入与开启规则
为什么需要: 导出后要能按默认导出、具名导出分别导入;同时 Node 默认按 CommonJS 解析 .js,必须显式开启 ESM。
怎么想: 默认导出用任意变量名接;具名导出必须用花括号且名字对应;全部导入用 import * as。开启 ESM 有两条路:.mjs 或 package.json 的 "type":"module"。
怎么做:
js
import fn from './a.js'
import { a, b } from './a.js'
import * as mod from './a.js'json
{ "type": "module" }转写稿里有一个调试过程:安装最新 chalk 后写 const chalk = require('chalk'),运行报错,提示不支持 require ES Module。推导结论是:新版 chalk 只支持 ESM,而当前 index.js 是 CommonJS,所以不能直接用 require 导入。解决方向不是硬改 require,而是切换模块规则(.mjs 或 "type":"module"),或者使用仍支持 CommonJS 的旧版本。
易错点: 只改文件扩展名或只加 "type":"module" 后,文件里的 import/export 才会按 ESM 解析;CommonJS 的 require 不能直接导入 ESM 模块。
模块路径与文件路径
为什么需要: 相对路径到底相对谁,是初学者最容易混淆的点。模块路径和文件路径的参照物不同。
怎么想: require('./x') 是模块路径,相对“当前文件所在目录”;fs.readFile('./x') 这类文件路径,相对“命令行所在目录”,也就是进程工作目录。
怎么做:
js
// 模块路径:相对当前文件
const x = require('./utils/x')
// 文件路径:相对命令行目录
fs.readFile('./data.txt', ...)易错点: 在项目根目录运行和在子目录运行,文件相对路径结果可能不同;模块相对路径不受命令行目录影响。文件路径建议拼成明确的绝对路径,避免“换个目录运行就找不到文件”。
先弄清:为什么要指定版本
为什么需要:npm install 包名 默认装最新版,但最新版不一定兼容你当前项目。转写稿里的例子很典型:安装最新版 chalk 后,用 require('chalk') 运行就报错,因为新版只支持 ES6 模块,而当前项目还在用 CommonJS。
怎么想:先判断这个包是“命令行工具”还是“项目代码依赖”。chalk 是给代码引入的,所以装到本项目,不是全局。若最新版不兼容,就查它的历史版本,找仍支持 CommonJS 的版本。
怎么做:先安装并复现报错:
bash
npm install chalk代码中这样引入会报错:
js
const chalk = require('chalk')删除后改装指定版本:
bash
npm remove chalk
npm install [email protected]安装后 package.json 的依赖里会记录版本,例如 "chalk": "^4.1.0"。再运行:
bash
node index.js易错点:@ 后写版本号;删除包用 npm remove 包名,全局包要加 -g;^4.1.0 表示允许更新到 4.x,不会随便升到 5.x。
删除包:本地和全局要分清
为什么需要:装错包、包不再使用、或为了换版本,都要删除。删除后应同时从 node_modules 和 package.json 依赖中消失。
怎么想:先看它是项目依赖还是全局命令。项目依赖直接删;全局工具必须加 -g。
怎么做:
bash
npm remove 包名
npm remove 包名 -g转写稿中还删除了全局安装的 nodemon:
bash
npm remove nodemon -g易错点:全局包不加 -g,只会去本项目里找,删不掉全局命令;删除后命令报“找不到”是正常现象。
更新包:先看 outdated,再理解锁版本
为什么需要:包会修复 bug、增加功能,需要知道哪些能更新。但不是所有包都能直接升大版本。
怎么想:先用 npm outdated 查看哪些包可更新,再决定是否 npm update。如果 package.json 写的是 ^4.1.0,更新通常只在 4.x 内进行,不会升到 5.x。
怎么做:
bash
npm outdated
npm update 包名
npm update 包名 -g转写稿中执行更新后,chalk 仍在 4.x,因为 ^4.1.0 起了锁主版本作用。更新后再运行 node index.js 验证项目是否正常。
易错点:把 npm update 当成“必然升到最新大版本”是错的;大版本升级可能破坏兼容性,要结合项目模块规范判断。
npm 与 Git 协作:不要提交 node_modules
为什么需要:node_modules 文件多、体积大,提交到 Git 会浪费仓库和网络。协作时只同步源码和 package.json。
怎么想:别人克隆项目后没有 node_modules,直接运行会报“找不到模块”。这时应根据 package.json 重新安装依赖。
怎么做:先初始化并写忽略文件:
bash
git init.gitignore 中写:
text
node_modules/提交、推送后,别人克隆下来先执行:
bash
npm install也可以简写:
bash
npm i转写稿还演示了:删掉 node_modules 后运行报错,执行 npm install 后依赖恢复;每天拉取新代码后,如果对方新增了包,也要再执行一次 npm install。
易错点:克隆后直接 node index.js,往往因缺依赖失败;忽略目录末尾斜杠表示忽略整个目录。
配置命令别名:把长命令放进 scripts
为什么需要:启动命令可能很长,参数很多,每次手写容易错。package.json 的 scripts 可以配置别名。
怎么想:把常用命令写进 scripts,用 npm run 别名 执行。start 是特殊别名,可以省略 run。
怎么做:
json
{
"scripts": {
"start": "node index.js",
"build": "node index.js"
}
}运行:
bash
npm run start
npm start
npm run build易错点:只有 start 通常可直接写 npm start;其他别名一般要写 npm run 别名。拿到别人项目时,先看 scripts 能推断启动方式。
cnpm 淘宝镜像:源不稳定时的替代方案
为什么需要:npm 官方源服务器在国外,网络不稳定时安装可能卡住。国内镜像会同步 npm 包,提高下载稳定性。
怎么想:推荐方式一是额外安装一个 cnpm 命令,不改变原来的 npm;方式三是直接改 npm 的 registry 配置。
怎么做:全局安装 cnpm:
bash
npm install -g cnpm之后用 cnpm 安装:
bash
cnpm install
cnpm install lodash查看当前源:
bash
npm config get registry易错点:cnpm 是全局命令,别人同步你的项目不会自动拥有它,需要自己安装;cnpm 与 npm 用法基本一致,但不要混淆“多一个命令”和“改掉 npm 源”这两种方式。
本节围绕 npm 包的版本管理、删除更新、与 Git 协作、命令别名和国内镜像展开。核心不是“会敲几个命令”,而是理解:package.json 记录依赖,node_modules 只是安装结果;版本号决定代码能不能被当前项目正确加载。
为什么安装包要指定版本:从 chalk 报错说起
为什么需要:npm 默认安装最新版,但最新版不一定兼容你的代码。转写中安装 chalk 后,用 CommonJS 的 require 引入却报错,原因是新版 chalk 只支持 ES6 模块,而当前项目仍用 CommonJS。
怎么想:遇到“新版本不兼容”,有两条路:改项目模块规范,或退回支持 CommonJS 的旧版本。课堂选择后者,因为改动小、能快速验证。
怎么做:先安装最新版观察问题,再卸载并安装指定版本:
bash
npm install chalk
npm remove chalk
npm install [email protected]代码中仍可这样使用:
js
const chalk = require('chalk')
console.log(chalk.red('高小乐'))
console.log(chalk.bgRed('爱吃鱼'))运行 node index.js,能看到红字和红底输出。
易错点:@ 后写版本号;卸载后 node_modules 和 package.json 依赖会同步变化;^4.1.0 不是永远锁死 4.1.0,而是允许 4.x 范围内更新,所以更新后仍可能停留在 4.x。全局安装也可指定版本,加 -g 即可。
删除包与更新包:别把本地和全局混了
为什么需要:装错的包、试验用的包要清理;依赖升级也要可控。
怎么想:先判断包装在哪里。项目依赖用本地删除,命令行工具如 nodemon 往往是全局安装,必须全局删。
怎么做:
bash
npm remove 包名
npm remove nodemon -g更新前先看哪些包可更新:
bash
npm outdated
npm update chalk易错点:全局包不加 -g,只会去本项目里找,删不到;npm update 受 package.json 版本范围限制,不一定跨大版本升级。删完全局 nodemon 后命令找不到,是正常现象。
npm 与 Git 协作:node_modules 不入库
为什么需要:node_modules 文件多、体积大,提交到 Git 会浪费仓库和流量。真正需要同步的是源码和 package.json。
怎么想:依赖清单在 package.json,别人拿到项目后按清单安装即可。
怎么做:在 .gitignore 写:
text
node_modules/克隆项目后先执行:
bash
npm install它会把 package.json 里的依赖全部装上。若队友更新代码并新增了包,你拉取后也要再 npm install。
易错点:克隆后直接 node index.js 常会报“找不到模块”,因为还没有 node_modules;不要手写提交 node_modules;npm install 可简写 npm i。
配置命令别名:把长命令收进 scripts
为什么需要:启动命令可能很长,尤其后面接一堆参数,每次手敲容易错。
怎么想:package.json 的 scripts 就是命令别名表。
怎么做:
json
{
"scripts": {
"serve": "node index.js",
"start": "node index.js"
}
}运行:
bash
npm run serve
npm startstart 是特殊别名,可省略 run;其他名字一般要 npm run 名字。
易错点:改了别名后旧名字立即失效;看别人项目时,先看 scripts 能推断启动方式;非 start 不要想当然写成 npm serve。
使用 cnpm 淘宝镜像:解决下载慢和卡住
为什么需要:npm 官方源服务器在国外,网络不稳时 npm install 可能很慢甚至卡住。国内镜像会同步包,提升稳定性。
怎么想:方式一安装全局 cnpm,保留原来的 npm 命令,二者并存,最稳妥。
怎么做:按镜像站方式一全局安装 cnpm 后,用 cnpm 执行同类命令:
bash
cnpm init
cnpm install lodash
cnpm install uniq易错点:cnpm 是全局命令行工具,队友不会因为你提交项目就自动拥有,需要自己安装;镜像同步有短暂延迟;若直接改 npm registry,会影响原 npm 命令,方式一更不容易混淆。
课堂画面






代码示例
示例一:安装指定版本 + CommonJS 引入 chalk(4.x)
bash
# ① 直接装最新版:装到 5.x,只支持 ESM,CommonJS 里 require 会报错
npm install chalk
# ② 卸掉
npm remove chalk
# ③ 改装指定版本:4.x 仍支持 CommonJS
npm i [email protected]
# ④ 全局安装同样可以指定版本
npm i -g [email protected]js
// index.js —— 项目未配置 {"type":"module"},默认走 CommonJS
const chalk = require('chalk');
console.log(chalk.red('高小乐')); // 红字
console.log(chalk.bgRed('爱吃鱼')); // 红底bash
node index.jsjson
// 安装后 package.json 自动记录
{
"dependencies": {
"chalk": "^4.1.0"
}
}要点解释
包名@版本号就是安装指定版本,npm i是npm install的简写;全局安装再加-g。- 转写稿里的报错原因:新版 chalk 只提供 ES Module,
require()导入 ESM 会直接抛错;要么改.mjs/ 配"type":"module",要么退到支持 CommonJS 的 4.x。 - 易错点:
npm remove会同时清掉node_modules里的目录和package.json里的依赖记录,所以删完要重新npm i [email protected]。 ^4.1.0的^是“锁大版本”,后续npm update也只会升到 4.x,不会跳到 5.x——这正是它还能继续跑 CommonJS 的原因。
示例二:package.json 配置命令别名(scripts)
json
{
"name": "product01",
"version": "1.0.0",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "node index.js",
"build": "node index.js",
"start": "node index.js"
},
"dependencies": {
"chalk": "^4.1.0"
}
}bash
npm run serve # 执行 node index.js
npm run build # 同上,只是别名不同
npm start # start 是 npm 特批的,可以省掉 run
npm run start # 写法等价
npm run test # 执行默认那条 echo要点解释
scripts里键名可以随便取(serve、build都行),值是真正要执行的命令;npm run 别名就会执行对应命令。- 唯一例外是
start:npm start可以省略run,其他别名(如build)写成npm build是无效的,必须npm run build。 - 为什么要别名:转写稿里说得很清楚,以后接 webpack 之类,命令后面参数一长串,手敲不现实;另外别人拿到你的开源项目,打开
package.json看scripts就知道项目怎么启动。 - 易错点:别名里的命令是在项目根目录执行的,
node index.js的路径要相对package.json所在目录写。
示例三:git + npm 协作流程(忽略 node_modules → install → 更新)
bash
# ① 项目初始化并建忽略文件
git initgitignore
# .gitignore —— 结尾斜杠表示忽略整个目录及其下所有内容
node_modules/bash
# ② 提交与推送
git add -A
git commit -m "项目初始化"
git remote add origin <远程仓库地址>
git push
# ③ 别人克隆下来后:一定没有 node_modules,第一步必须装依赖
git clone <远程仓库地址>
cd product01
npm install # 简写 npm i,按 package.json 把依赖全部装回来
node index.js
# ④ 日常:看哪些包能更新、更新、再跑
npm outdated # 列出可更新的包(全局的加 -g)
npm update chalk # 更新本项目里的包(受 ^ 约束,不会跨大版本)
npm update -g nodemon # 全局包更新/删除都要带 -g
npm remove -g nodemon # 全局包删除,不带 -g 只会在本项目里找
# ⑤ 国内镜像(方式一):全局装一个 cnpm 命令,npm 本身不受影响
npm install -g cnpm --registry=https://registry.npmmirror.com
cnpm -v
cnpm install lodash
cnpm install uniq要点解释
node_modules绝不进 git(体积大、浪费流量),所以必须有.gitignore;同步出去的只有源码和package.json。- 克隆后不装依赖直接
node index.js必然报“找不到模块”,因为node_modules压根没同步过来;npm install就是按package.json的dependencies把包装回来。 - 队友新增了包并 push 之后,你
git pull只会拿到新代码和新package.json,所以每次拉完代码都要再npm install一次,否则新代码里require的包本地没有,照样报错。 - 易错点一:
npm remove nodemon(不加-g)删的是本项目的包,全局包纹丝不动,命令还能用;必须npm remove -g nodemon。 - 易错点二:
npm outdated看到有新版本不代表npm update会升上去,^只允许小版本升级,想跨大版本得重新npm i 包名@版本号。 - 易错点三:cnpm 是“多出来的一个命令行工具”,只装在自己机器上,不会写进项目;用方式三(直接改 registry)则会改变
npm本身的下载源。
常见错误
现象:
npm i chalk装完最新版,代码里require('chalk')直接抛ERR_REQUIRE_ESM,整个脚本跑不起来。 原因:chalk 5.x 起只发 ESM,CommonJS 的require无法加载;而项目本身还是 CJS(没.mjs、package.json 也没"type":"module")。 修法:先npm rm chalk,再npm i [email protected]这类仍兼容 CJS 的旧主版本;或者反过来,把项目整体切到 ESM 再装新版。装之前先去包主页看它的版本线和模块格式,不要默认"最新版最好"。现象:
npm rm nodemon执行成功,但终端里nodemon命令照样能用;或者相反,想删本地依赖却把全局工具删了。 原因:本地依赖和全局包是两套目录,-g决定删哪一套,很多人凭手感敲。 修法:删本地就npm rm 包名,删全局工具就npm rm -g 包名;不确定装在哪,用npm ls 包名和npm ls -g 包名各查一次再动手。现象:
npm update chalk跑完了,npm outdated再看,当前版本还是 4.1.x,没升到 5.x。 原因:package.json 里记的是^4.1.0,update只在^允许的范围内升,不会跨主版本。 修法:要跨主版本必须显式指定npm i [email protected]。反过来说,^正是防止依赖被意外大版本升级搞崩的保护,别为了"升级"去手改它。现象:从远程仓库
git clone下来,目录里没有node_modules,直接node index.js报Cannot find module。 原因:.gitignore忽略了node_modules,仓库里只同步源码和 package.json。 修法:进项目第一件事npm install(简写npm i),让它按 package.json 把依赖补齐,然后再运行。这不是异常,是所有 Node 项目的标准开场。现象:昨天还能跑的项目,今天
git pull完再跑就报缺包,报的还是自己没写过的包名。 原因:同事新装了依赖并提交了 package.json,但node_modules不在版本控制里,你拉下来的只是代码。 修法:养成"拉代码后立刻npm i"的肌肉记忆,每次同步后都跑一遍,别等报错才想起来。现象:scripts 里配了
"build": "node index.js",终端敲npm build提示找不到命令。 原因:只有start和test是 npm 内置可省略run的,其他别名必须带run。 修法:npm run build;想让入口命令短一点,就把启动脚本命名为start,之后npm start即可。
练习
你的项目是 CommonJS(index.js 用
require),现在要引入 chalk 做彩色输出。写出完整的"装错→发现→回退→验证"四步操作,并说明每一步你在看什么信息来判断下一步。描述一个场景:什么时候你会选择
cnpm而不是npm?如果你用了方式三(直接改 registry)之后想切回官方源,命令是什么?为什么讲师更推荐方式一?你接手一个陌生的开源项目,只有仓库地址。写出从零到"本地跑起来"的完整命令序列,并说明你是从哪里得知"该怎么启动这个项目"的。
判断对错并说明理由: ①
npm update可以把任意包升到最新版; ② 全局安装的包也会写进 package.json 的 dependencies; ③npm install在node_modules已存在时执行是浪费时间的多余操作。团队里有人把
node_modules提交进了 Git,仓库体积暴涨。写出修复步骤(含.gitignore内容),并说明新同事克隆后要做什么才能跑起来。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | npm-安装指定版本的包 | 07:54 |
| 2 | npm-删除包 | 01:14 |
| 3 | npm-更新包 | 02:24 |
| 4 | npm&git开发流程 | 21:06 |
| 5 | 配置命令别名 | 04:54 |
| 6 | cnpm淘宝镜像 | 07:23 |
小结
- "最新版"不是默认正确选项——版本号是指定行为的手段,遇到模块格式不兼容时,降版本比改整个项目的模块体系成本低得多。
- 安装、删除、更新三个动作都要先问一句"作用域是本地还是全局",
-g加错比命令敲错更难排查。 ^前缀是保护而不是障碍:它让npm update只能在小版本内漂移,跨主版本必须由人显式决定。- package.json 是项目的依赖契约,node_modules 是可再生的产物——所以前者进仓库,后者永远不进。
- "克隆后先
npm i、拉代码后先npm i"应当成为条件反射,它解决的是"代码对了但环境没跟上"这一类问题。 - 拿到陌生项目时,读 package.json 的 scripts 比读源码更快知道怎么启动;命令别名就是给长参数起的快捷键。
- 镜像源解决的是网络稳定性问题,不改变 npm 的语义,可以在 npm / cnpm 之间无缝切换,不必有心理负担。
- 判断一个包装对了没有,不看安装时的输出,而看三处:node_modules 里有没有、package.json 里记没记、代码跑起来报不报错。