1. package.json 是什么?里面的字段可以分成哪几类?
核心思路(一句话)
package.json 本质上就是 npm 包的配置文件,负责描述包的信息、入口、依赖以及相关工程配置。
可以从 4 个维度理解:
text
package.json
│
├── ① 包的基本信息
│ ├── name
│ ├── version
│ ├── description
│ ├── keywords
│ ├── author
│ └── license
│
├── ② 包如何被消费
│ ├── main
│ ├── module
│ ├── browser
│ ├── types / typings
│ ├── exports
│ └── files
│
├── ③ 工程依赖与脚本
│ ├── dependencies
│ ├── devDependencies
│ ├── peerDependencies
│ ├── optionalDependencies
│ └── scripts
│
└── ④ 工具/生态扩展配置
├── sideEffects
├── browserslist
├── eslintConfig
├── prettier
├── rollup
└── 其他工具自定义字段
主要矛盾
不要死记"哪些字段是标准、哪些字段是非标准"。
真正重要的是:谁读取这个字段,以及它会影响什么行为。
例如:
text
package.json
│
├── npm → 发布、安装、依赖
│
├── Node.js → 模块解析
│
├── TypeScript → 类型声明解析
│
├── Webpack / Rollup / Vite → 模块入口、构建
│
└── 其他工具 → 读取自己的扩展配置
2. name、version、description 是干什么的?
核心思路(一句话)
这三个字段描述一个 npm 包"是谁、什么版本、做什么"。
json
{
"name": "vue",
"version": "3.5.0",
"description": "The progressive JavaScript framework"
}
name
包名。
bash
npm install vue
这里的:
text
vue
就是 name。
version
包版本,通常遵循语义化版本规范:
text
MAJOR.MINOR.PATCH
主版本.次版本.修订版本
例如:
text
3.5.0
description
包的描述信息,主要用于 npm 生态展示和搜索等场景。
3. main 是什么?它和 module 有什么区别?
这是最值得重点掌握的地方之一。
核心思路(一句话)
main 是传统 CommonJS 入口声明;module 是生态约定的 ES Module 入口声明,module 并不是 Node.js package.json 的标准字段。
例如:
json
{
"main": "dist/index.cjs",
"module": "dist/index.js"
}
可以简单理解:
text
消费者
│
└── import / require
│
├── require(...)
│
│ └── 通常优先考虑 main
│
└── import ...
│
└── 构建工具可能优先使用 module
但这里一定要注意:
不能简单理解成"import 一定读 module,require 一定读 main"。
现代 Node.js 的模块解析主要由:
text
exports
imports
type
main
等字段共同决定。而:
text
module
更多是打包工具生态形成的约定。
4. module 为什么存在?
核心思路(一句话)
因为很多构建工具希望拿到 ES Module 版本,从而进行静态分析和 Tree Shaking。
假设:
json
{
"main": "dist/index.cjs",
"module": "dist/index.js"
}
其中:
text
dist/index.cjs
↓
CommonJS
dist/index.js
↓
ES Module
构建工具如果能够使用:
text
dist/index.js
就可以更容易分析:
js
export function foo() {}
export function bar() {}
然后:
text
应用只使用 foo
↓
构建工具静态分析
↓
发现 bar 没被使用
↓
Tree Shaking
↓
删除 bar
所以:
module 的核心价值不是"让 import 能运行",而是给构建工具提供 ES Module 入口。
5. exports 是什么?为什么现代项目越来越重要?
这是一个高级面试重点。
核心思路(一句话)
exports 是用来规定这个包对外暴露哪些入口,以及不同调用方式或运行环境下该加载哪个文件,它还定义了 package 的公开 API 边界。
例如:
json
{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./utils": {
"import": "./dist/utils.js",
"require": "./dist/utils.cjs"
}
}
}
于是:
js
import xxx from "my-package";
可以映射到:
text
./dist/index.js
而:
js
const xxx = require("my-package");
可以映射到:
text
./dist/index.cjs
还可以:
js
import { xxx } from "my-package/utils";
映射:
text
./dist/utils.js
更重要的一点
exports 还可以限制:
text
用户只能访问:
my-package
my-package/utils
不能直接访问:
my-package/dist/internal.js
所以高级面试可以直接说:
exports不只是入口映射,它还定义了 package 的公开 API 边界。
6. types / typings 是什么?
核心思路(一句话)
它告诉 TypeScript:这个 npm 包的类型声明文件在哪里。
例如:
json
{
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
代码:
text
业务代码
│
└── import xxx from "my-package"
│
├── 运行时
│ ↓
│ dist/index.js
│
└── 类型检查
↓
dist/index.d.ts
这里要注意:
text
types
typings
是 TypeScript 生态使用的字段。它不是"Node.js 运行时执行时去加载类型"。
类型声明主要服务于 TypeScript 编译器、编辑器等工具。
7. files 是什么?
核心思路(一句话)
files 控制 npm 发布包时,哪些文件会被打进最终的 npm package。
例如:
json
{
"files": [
"dist",
"index.d.ts"
]
}
执行:
bash
npm publish
最终发布到 npm 的内容不会简单等于整个项目目录。可以理解成:
text
项目源码
│
├── src
├── dist
├── test
├── docs
├── node_modules
├── .git
└── package.json
│
↓
npm publish
│
↓
根据发布规则筛选
│
↓
npm package
所以:
text
files
解决的是:
"这个包发布出去以后,消费者能拿到哪些文件?"
8. files 和 .npmignore 有什么区别?
核心思路(一句话)
files 是"明确允许发布什么",.npmignore 是"明确排除什么",两者共同影响 npm 包最终内容。
例如:
json
{
"files": [
"dist"
]
}
表示主要发布:
text
dist/
这也是为什么你:
bash
npm install vue
之后看到的包内容,和 Vue GitHub 源码仓库里的完整内容完全不同。
面试重点
不要说:
files决定 npm 上传哪些文件。
更准确地说:
files参与决定npm package中最终包含哪些文件。
因为 npm 还有默认包含/排除规则以及 .npmignore 等因素。
9. sideEffects 是什么?为什么会影响 Tree Shaking?
核心思路(一句话)
sideEffects 是给打包工具提供"模块是否存在顶层副作用"的信息,从而帮助它安全地删除未使用模块。
例如:
json
{
"sideEffects": false
}
表示:
这个包中的模块可以被认为没有需要保留的顶层副作用。
例如:
js
// utils.js
export function add(a, b) {
return a + b;
}
如果:
js
import { add } from "./utils.js";
构建工具发现其他东西没用到,就可以删除无关代码。
但 sideEffects: false 不能乱写
例如:
js
// polyfill.js
window.foo = 'bar';
虽然没有导出:
js
export ...
但是:
js
import "./polyfill.js";
依然有意义。这就是:
text
副作用
如果错误配置:
json
{
"sideEffects": false
}
构建工具可能认为这个模块可以删除,导致程序行为发生变化。所以高级回答应该强调:
sideEffects的核心是帮助构建工具判断"未被直接引用的模块能不能删",配置错误可能导致代码被错误 Tree Shaking。
10. repository 是什么?
核心思路(一句话)
repository 描述这个 npm 包对应的源码仓库地址。
例如:
json
{
"repository": {
"type": "git",
"url": "https://github.com/example/project.git"
}
}
主要用于:
text
npm
GitHub
开发者工具
生态平台
定位源码仓库。
11. keywords 是什么?
核心思路(一句话)
keywords 是 npm 包的搜索关键词,帮助用户发现这个包。
例如:
json
{
"keywords": [
"vue",
"reactivity",
"javascript",
"frontend"
]
}
12. author、license、bugs、homepage 是什么?
这些属于包的元数据。
| 字段 | 作用 |
|---|---|
author |
作者信息 |
license |
开源许可证 |
bugs |
Bug 反馈地址 |
homepage |
项目主页 |
例如:
json
{
"author": "xxx",
"license": "MIT",
"bugs": {
"url": "https://github.com/example/project/issues"
},
"homepage": "https://example.com"
}
面试不需要展开太多。
13. dependencies 是什么?
核心思路(一句话)
dependencies 是运行这个包所需要的依赖。
例如:
json
{
"dependencies": {
"axios": "^1.0.0"
}
}
意思是:
text
我的项目运行
↓
需要 axios
↓
所以 axios 属于运行时依赖
14. devDependencies 是什么?
核心思路(一句话)
devDependencies 是开发、测试、构建过程中需要,但通常不是这个包运行时直接需要的依赖。
例如:
json
{
"devDependencies": {
"webpack": "^5.0.0",
"typescript": "^5.0.0",
"eslint": "^9.0.0"
}
}
典型:
text
Webpack
TypeScript
ESLint
Jest
测试工具
构建工具
15. dependencies、devDependencies、peerDependencies 怎么区分?
这是非常高频的面试题。
核心思路(一句话)
dependencies 是"我运行需要它",devDependencies 是"我开发需要它",peerDependencies 是"我要求使用我的人提供兼容版本"。
例如开发一个 React 组件库:
json
{
"peerDependencies": {
"react": ">=18"
},
"devDependencies": {
"react": "^19.0.0",
"typescript": "^5.0.0"
}
}
为什么 React 放 peerDependencies?因为组件库通常不应该自己再带一份 React。
否则可能出现:
text
业务项目
│
├── React A
│
└── 组件库
│
└── React B
最终可能出现:
text
两个 React 实例
↓
运行时问题
所以:
组件库需要 React,但 React 应该由宿主项目提供。
这就是 peerDependencies 的典型场景。
16. browser 是什么?
核心思路(一句话)
browser 是一个生态约定字段,用于告诉某些构建工具:当这个包运行在浏览器环境时,应该使用哪个入口或替换哪些 Node.js 模块。
例如:
json
{
"main": "./dist/index.js",
"browser": "./dist/index.browser.js"
}
或者:
json
{
"browser": {
"fs": false
}
}
意思可以理解为:
text
Node 环境
↓
使用 Node 版本
浏览器环境
↓
使用 browser 对应版本
17. type 是什么?为什么它很重要?
核心思路(一句话)
type 决定 .js 文件在 Node.js 中默认按照 CJS 还是 ESM 解释。
例如:
json
{
"type": "module"
}
那么:
js
// index.js
import fs from "node:fs";
会按照 ECMAScript Module 解释。如果:
json
{
"type": "commonjs"
}
则 .js 默认按照 CommonJS 解释。还可以通过:
text
.mjs
.cjs
明确指定模块类型。
注意
type主要影响 Node.js 对.js文件模块类型的解释;它不是简单地决定所有工具的模块解析策略。
18. module、types、exports 的定位不能混为一谈
可以直接用这张图记:
text
package.json
│
┌──────────────────┼──────────────────┐
↓ ↓ ↓
Node.js 构建工具 TypeScript
│ │ │
│ │ │
exports/main module/browser types
│ │ │
↓ ↓ ↓
运行时模块入口 构建时模块入口 类型声明
现代包还可以进一步:
text
exports
│
├── import
├── require
├── browser
├── node
├── development
└── production
因此不能简单说:
text
import → module
require → main
高级回答一定要把:
text
Node.js 原生解析
构建工具解析
TypeScript 类型解析
分开。
19. package.json 字段到底应该怎么记?
不要按照顺序死记。建议按照"谁读取、解决什么问题"记。
text
package.json
│
├── 包是谁
│ ├── name
│ ├── version
│ └── description
│
├── 包怎么被使用
│ ├── exports ← 现代核心入口/导出边界
│ ├── main ← 传统入口
│ ├── module ← 构建工具生态约定
│ ├── browser ← 浏览器环境约定
│ ├── types ← TypeScript 类型入口
│ └── type ← Node.js 模块类型
│
├── 发布什么
│ └── files
│
├── 如何优化构建
│ └── sideEffects
│
├── 依赖什么
│ ├── dependencies
│ ├── devDependencies
│ ├── peerDependencies
│ └── optionalDependencies
│
└── 项目元数据
├── keywords
├── author
├── license
├── repository
├── bugs
└── homepage
20. 面试满分答案
面试官:你对
package.json了解多少?
package.json 可以理解成一个 npm 包的"说明书和入口配置",它不仅描述包的基本信息,还会影响包怎么被发布、怎么被 Node.js 和构建工具解析,以及它有哪些依赖。
我一般把它分成四类来看。
第一类是包的基本信息 ,比如 name、version、description、keywords、author、license、repository 等。
第二类是模块入口和导出规则 ,这是前端工程里比较重要的一部分。传统上有 main 指定 CommonJS 等场景下的入口,module 是构建工具生态中约定的 ES Module 入口,types 或 typings 指定 TypeScript 类型声明。现代 npm 包更重要的是 exports,它不仅可以根据 import、require、浏览器、Node.js 等条件选择不同入口,还可以限制包对外暴露的模块边界。
第三类是发布和构建相关配置 。files 控制 npm 包发布时哪些文件进入最终 package;sideEffects 则给 Webpack、Rollup 等构建工具提供副作用信息,帮助 Tree Shaking 判断哪些模块可以安全删除。
第四类是依赖管理 。dependencies 是运行时依赖,devDependencies 是开发、测试和构建依赖,peerDependencies 则表示这个包要求宿主项目提供兼容版本,组件库依赖 React、Vue 这类宿主框架时非常常见。
所以我认为理解 package.json 最重要的不是背字段,而是知道每个字段是谁读取、解决什么问题,以及它最终影响的是运行时、构建时、类型检查还是 npm 发布过程。