开发一个 npm 库应该做哪些工程配置?
网页版带有主题和代码高亮,阅读体验更佳~
刚学习 Vue、React 的时候,还只会使用 cli 创建项目。Vue 内置的 ESlint 工具最令人头疼,常常报错导致项目跑不起来,最后只能删除项目重新创建。上一家公司也是,都是水平很差的前端,项目根本不敢开 ESlint,没有人能解决 ESlint 的报错问题,导致项目代码里充斥着各种奇珍异兽。这些东西如果不熟悉,不理解,遇到问题很难去解决,而此类工程化相关的东西,我觉得是 3 到 5 年前端能和别人最快拉开差距的知识点。抛开 JS、框架,我们每天打交道最多的就是这些东西,一个优秀的前端项目工程,代码提交检查、代码格式规范是基本配置,在此基础上才能去谈 TS、CI CD、单元测试等内容。
本篇文章就围绕这些工程化工具为大家做一次入门引领,从 0 到 1 搭建一个开源项目,从整体的视角来看待这些工具,从而更好地掌握工具,而不是排斥工具、害怕工具。
阅读本篇文章,你将学到以下工具的使用:
| 工具 | 描述 |
|---|---|
ESlint | 代码格式校验 |
husky | 执行 git 钩子函数,一般用来检查 commit 信息 |
lint-staged | 与 husky 配套使用,只检查暂存区的代码 |
conventional-changelog-cli | 生成 CHANGELOG.md 文件,记录库的版本日志 |
LICENSE | 如何选择开源协议及如何快速生成开源协议 |
.editorconfig | 编辑器配置 |
bumpp | 自动升级版本而不用每次都手动改 |
package.json | 依赖配置文件,一些你在平时项目不会用也不会关注的字段 |
vite | 如何构建一个 npm 包 |
从开源项目中学习
随便打开一个开源项目的 github 仓库,就会发现除了核心代码之外,还有很多工程化相关的配置文件:
.husky.eslintrc.eslintignore.editorconfig.npmrc.stylelint.js.commitlint.config.jsREADME.mdLICENSECHANGELOG.md
除此之外,还有一个最关键的文件:package.json,这是最关键的一员,我们会在具体的知识点中来讲解这个文件。
ant-design 的部分工程配置文件

基于 vite 搭建项目
出于方便高效等各种原因选择 Vite,但是这不是最关键的内容,你也可以使用 Webpack。为了保持学习的一致性,建议你和我保持一样的操作。
使用 Vite,对 Node 版本有一定要求,你可以选择和我保持同样的版本:

Vite需要 Node.js 版本 14.18+,16+。然而,有些模板需要依赖更高的Node版本才能正常运行,当你的包管理器发出警告时,请注意升级你的Node版本。
包管理工具我选择 yarn,你也可以选择 pnpm。
在终端执行命令创建项目:yarn create vite,我们这里并不依赖任何框架,上下箭头移动选择 others,然后回车:

它又会问我们是不是 Electron 项目,当然也不是,选择第一个回车:

接下来它会询问我们选择一个预设,这里我们选择库模式:

它还会问我们是否需要 Typescript,我们这里需要:

Vite 为我们生成的项目模板:

可以看到它自带了一个 lib 文件,这其实就是我们最终生成的库的源码包,但是现在还只是个初级版,还不符合我们的需求。接下来我们需要做一些额外的配置。
编写 vite.config.js
首先明确我们需要支持的模块化,如果需要同时支持 esm 和 cjs,那么我们可以生成两个最终产物,分别定义两种模块化的入口,如果你只支持 esm 或者 cjs 那么只生成一个最终产物就可以了,当然你还可以使用 UMD,一个最终产物同时支持以上两种模块化。这里我们以同时生成 esm 和 cjs 两种包为例编写配置文件。
import { defineConfig } from 'vite';
export default defineConfig({
build: {
target: 'modules', // 详情参考官方文档:https://cn.vitejs.dev/config/build-options.html#build-target
minify: true, // 是否开启代码压缩
rollupOptions: {
input: ['src/index.ts'], // 打包的入口文件 https://cn.rollupjs.org/configuration-options/#input
output: [ // 产物输出配置
{
format: 'es', // 指定模块化类型 https://cn.rollupjs.org/configuration-options/#output-format
entryFileNames: '[name].js', // 入口文件名,默认 https://cn.rollupjs.org/configuration-options/#output-entryfilenames
preserveModules: true, // 该选项将使用原始模块名作为文件名
dir: 'es', // 输出的目录
preserveModulesRoot: 'src' // 确保输出的目录和输入时的一致
},
{
format: 'cjs', // 指定模块化类型
entryFileNames: '[name].js',
preserveModules: true,
dir: 'lib',
preserveModulesRoot: 'src'
}
]
},
lib: { // https://cn.vitejs.dev/config/build-options.html#build-lib
entry: './index.ts', // 定义作为库的入口是哪个
}
},
});
我们执行下 yarn build 来看看当前配置会为我们生成什么样的产物:
来看个小插曲,因为我们 Vite 生成的模板 src 下的入口默认是 main.ts,但是我们在配置文件写的是 index.ts,它找不到 index.ts 文件就报错了:

首先需要将 main.ts 改名为 index.ts,然后需要改写里面的代码,我们随意写个变量并导出,因为我们是库,如果不导出模块是会报错的。

来看看最终打包的产物:一个 es 一个 lib,这里有了这两个文件后,我们接下来可以做的事就多了。

针对 es 和 lib 做处理
第一件事,这两个包我不希望他们被提交到仓库里,我需要在 .gitignore 文件里忽略它们:
...
es
lib
...
第二件事,这两个包必须得在 package.json 里定义它的入口文件,否则发布成 npm 库后,别人下载下来找不到入口文件,也就没法引用代码了。
{
"name": "vite-project",
"private": false,
"version": "0.0.0",
"type": "module",
"main": "lib/index.js",
"module": "es/index.js",
"types": "./index.d.ts",
"scripts": {
"dev": "vite",
"build": "tsc && vite build"
},
"files": [
"es",
"lib",
"index.d.ts"
],
"devDependencies": {
"typescript": "^4.9.4",
"vite": "^4.0.4"
}
}
main 字段我们定义 cjs 模块的入口,module 我们定义 es 模块的入口。另外,private 设置为 false,files 是指我们最终发布 npm 时要上传的文件,现在我们需要将 es 与 lib 两个文件加进来,这样我们发布代码时才能将代码发布上去,不然别人下载你的包会连代码都没有,这里一定小心,非常重要!
配置 ESlint
第一步,在终端执行命令:npx eslint --init,它会让我们选择模式:
- 仅仅检查语法
- 检查语法,发现问题
- 检查语法,发现问题,强制代码风格
这里我们选择第二个。

接下来它会询问我们使用的模块化,我们选择 esm:

它会问我们的项目是否基于某一个框架,由于我们这里没有,就不选,你可以根据你自己的情况选择:

接下来的就不是很重要了,我的选择给大家一个参考:

最终生成一个 .eslintrc.cjs 的规则配置文件,但是它会报红。因为我们是使用的 esm,我们把后缀 cjs 换成 js 就行了。不过这样其实也不太好,因为模块化可能会有各种潜在问题,所以我们直接将文件格式改成 json,也是一样的。这里顺嘴提一句,当项目存在多个 .eslintrc 配置文件且后缀不同时,优先级是 js > json > yaml,没有后缀的话默认是 json 格式。
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"overrides": [
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"semi": 2
}
}
我们可以在 rules 字段下新增一个规则 "semi": 2,检查下 index.ts 中有没有报红,报红就说明配置生效了。其中,0 是关闭,1 是警告,2 是报错,还有其它的一些规则值,大家感兴趣可以自行研究,我们这里使用 012 已经够用了。

接下来简单讲讲配置中各个主配置字段的含义:
eslintrc 配置字段含义解读
| 字段 | 描述 |
|---|---|
env | 在配置文件中使用 env 键指定环境,并通过将每个环境设置为 true 来启用想要的环境 |
globals | 要在配置文件中配置全局变量,请将 globals 配置属性设置成对象,其中包含为你要使用的每个全局变量命名的键。对于每个全局变量的键,将相应的值设置为 writable 以允许变量被覆盖,或者 readonly 以禁止覆盖。比如你可以把 window 禁用了,设置为只读 |
extends | eslint 检查用哪些规范,包括内置的和第三方的 |
overrides | 覆盖配置中基于文件 glob 模式的设置 (你只要知道基本用不到就对了) |
parser | 配置解析器 |
parserOptions | ESLint 允许你指定你想要支持的 JavaScript 语言选项。默认情况下,ESLint 希望使用 ECMAScript 5 语法。你可以通过使用解析器选项来覆盖这一设置,以实现对其他 ECMAScript 版本以及 JSX 的支持。 |
plugins | 对于特殊语法需要使用插件进行识别,例如对 TS、React 语法的检查都依赖插件 |
rules | 具体的校验规则,比如有分号没分号,允不允许 console.log 等 |
到这里我们的 eslint 基本就配置完了,其实很简单对不对?但是我们看到 .eslintrc 的缩进是 4 个,我个人是不喜欢 4 缩进的,这里就引出了另一个配置文件:.editorconfig。
不过在讲 .editorconfig 之前,还有一个文件和 ESlint 有关,那就是 .eslinignore。并不是所有文件都需要代码检查,ESlint 一般也只辅助我们检查 js、ts 代码,所以有些文件我们得忽略掉。在根目录下创建文件:.eslintignore:

一些构建后的产物没有检查的意义,所以有必要忽略掉。目前我们需要忽略的文件就是 node_modules、test、dist、build、es、lib。你若有其它的可忽略文件加进来即可。
.editorconfig
这个文件有什么意义?
.editorconfig有助于为跨不同编辑器和ide从事同一项目的多个开发人员维护一致的编码风格。.editorconfig项目由用于定义编码样式的文件格式和一组文本编辑器插件组成,这些插件使编辑器能够读取文件格式并遵循已定义的样式。.editorconfig文件很容易阅读,并且可以很好地与版本控制系统配合使用。
假如你用 macOS 开发,我用 windows 开发,那么两个系统使用的编码格式不同,就会出现乱码,你一定遇到过下载的代码在换行时有一个 DR 符号的情况,这就是系统差异导致的。还有就是,有的人喜欢 4 个缩进,有的喜欢 2 个,缩进不同就会导致代码差异比较大,有些人还比较喜欢使用代码格式化工具,一保存就会自动格式化,导致代码一堆冲突,这时候解决起来就麻烦了。
.editorconfig 长什么样呢?一般是在项目的根目录下新建一个 .editorconfig,不带任何后缀即可,它会被编辑器自动解析。
贴一下我的配置:
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line-length = off
trim_trailing_whitespace = false
我这里将代码缩进设置为 2 个,刚才的 .eslintrc 立马有了反应,缩进线变了,可以进行 2 个空格的缩进了:

这就是 .editorconfig 的能力,可以控制编辑器的缩进、utf 编码、去除空白符等等。这对团队协作非常重要,有利于大家的项目保持统一的风格,避免一些非代码问题导致的问题降低开发效率。
husky & lint-staged 代码检查并修复
团队协作最重要的就是标准和规范,没有标准规范就会有各种各样的问题,例如代码格式混乱、commit 信息千奇百怪。标准规范制定虽然容易,执行落地却很困难,所以必须借助于工具。ESlint 虽然已经帮助我们避免大量的代码格式问题了,但是人总是会出错的,为了错误代码不被提交,特别是有些人使用未定义的变量,这样一定会导致代码报错,从而导致生产故障,这时就需要其它工具来进行辅助检查。
husky 加上 lint-staged 就是这种帮助代码检查的工具,配合 ESlint 一起使用。
暂存区代码 commit 前进行修复
第一步,执行 yarn add husky lint-staged -D,先下载依赖。
第二步,执行 npx husky-init && husky install。该命令会在 package.json 中新增一个 script:"prepare": "husky install"。
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"prepare": "husky install"
},
紧接着会下载 .husky 文件,也就是 git hooks,这些钩子会在一定的时机执行。husky 的 hooks 有 pre-commit、commit-msg、pre-push 等。同时,它会生成一个 pre-commit 的 shell 脚本文件,负责执行 pre-commit 钩子。这个脚本文件就是在里面输入 shell 命令,husky 会在一定的 git 执行时机执行这个命令。你可以理解为类似于在终端执行的命令,只不过写在了这个文件里而已。

pre-commit 钩子是在 git commit 之前执行,我们现在要做的就是利用这个钩子在代码 commit 前修复一些错误。
第三步,package.json 新增 lint-staged 配置,这里的意思是在每次 commit 执行之前也就是 pre-commit 的时候执行 lint-staged 命令。它会匹配以 .js、.ts 结尾的文件,并修复这些文件里的代码格式错误,并继续将修改后的文件存入暂存区,pre-commit 之后才会执行我们提交的 commit 命令。
"lint-staged": {
"src/**/*.{js,ts}": [
"eslint --fix",
"git add"
]
},
第四步,我们需要将 pre-commit 文件里的 npm test 替换为 npx lint-staged,这样它就会在 pre-commit 钩子执行时调用第三步里的方法,对暂存区的代码进行扫描和修复。

我们来测试一下,src/index.ts 里我们之前留了几个分号的报错,现在我们将它进行 commit:

可以看到分号的错误已经被修复。
对 commit 信息进行格式检查
如果你接触过一些正式的 commit 规范,会知道我们的 commit 信息一般是这样:feat: 搭建项目骨架。首先是此次变更的关键词,常见的有 feat、style、docs、chore 等。具体看下表:
| 关键字 | 具体含义 |
|---|---|
feat | 新增特性 (feature) |
fix | 修复 bug(bug fix) |
docs | 修改文档 (document) |
style | 代码格式修改 |
refactor | 代码重构 (refactor) |
perf | 改善性能 (performance) |
test | 测试 |
build | 变更项目构建或外部依赖 |
chore | 杂项 |
revert | 代码回退 |
这样的信息我们也有工具进行规范。我相信有人看到这里会觉得这种东西似乎没有什么作用,有点多此一举了。仁者见仁智者见智吧,我觉得专业的前端团队这种东西应该都会做,毕竟工程化的东西,其它的都能做,这件小事也就是顺手的事情,有利无弊的事,做了总比没做好。
第一步,下载两个依赖:yarn add @commitlint/cli @commitlint/config-conventional -D。
第二步,终端执行命令:npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1",它会在 .husky 下生成一个 commit-msg 的 shell 脚本文件,并且会在我们 commit 提交 message 时执行 npx --no-install commitlint --edit 命令,对我们的信息进行校验。

但是怎么个校验法它并不知道,所以需要我们在根目录下编写一个配置文件 commitlint.config.js:
const types = ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'release', 'chore', 'revert'];
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-empty': [2, 'never'],
'type-enum': [2, 'always', types],
'scope-case': [0, 'always'],
'subject-empty': [2, 'never'],
'subject-case': [0, 'never'],
'header-max-length': [2, 'always', 88],
},
};
以上配置仅供参考。来测试下:

一个小插曲,报错的信息说不支持 esm,需要导出 cjs 模块。那么我们需要将 commitlint.config.js 替换为 commitlint.config.cjs,并将其进行 module.exports。
继续测试,可以看到配置已经生效了。

不过有时候不想这个配置生效,觉得这个校验很麻烦怎么办?就比如前同事留下的垃圾代码,每次 commit 触发校验一堆报错,老代码又不想动,新代码又必须提交,怎么办?其实加个参数就可以了:--no-verify。例如:git commit -m 'feat: 完成项目骨架' --no-verify,它就会跳过 commit 校验,包括代码格式校验及 commit message 校验。
生成 CHANGELOG.md 更新日志
打开一些开源项目的 github 仓库,发现人家对每次版本更新都有很详细的记录,有时候觉得人家真细致,不愧是开源作者,但其实人家也是借助的工具。
第一步,执行命令:yarn add conventional-changelog-cli -D。
第二步,在 package.json 中配置脚本:"changelog": "conventional-changelog -p -i CHANGELOG.md -s"。
我们来测试下:yarn changelog。

就这样两步就能生成 CHANGELOG.md。
自动升级版本号
每次发布新的版本都要手动更改 package.json 的 version 实在是麻烦,而且最麻烦的是会忘记更改。可以下载 yarn add bumpp -D,每次 release 之前都执行下 bumpp,它会提示我们对版本进行升级,还有各种合适的版本号推荐,例如 beta 版、major 版等。
在 package.json 中新增一个 script:"version": "bumpp"。终端执行 yarn version 看看效果:

它会继续问我们是否需要更改到其它版本,我们这里不需要,直接选择 as-is 0.0.2:

最后它会问我们是否需要继续,我们选择 N:

主要的功能是防止忘记更改版本,可以将其放到每次发布之前执行。
快速生产开源协议
开源项目离不开开源协议,但是不可能手写开源协议吧?其实这个也不用我们自己操作,vscode 有插件帮助我们快捷生成。

快捷键 shift command (ctrl) p 可进行选择:


开源协议有很多,这里就不展开了,一般选择 MIT 就可以。下面列个常见开源协议的资料,有兴趣的话可以细看。
| 协议名称 | 协议描述 |
|---|---|
BSD (Berkeley Software Distribution license) | BSD 允许使用者修改和重新发布代码,也允许基于 BSD 代码上开发商业软件的发布和销售。遵循 BSD 协议的代码完全可控,必要的时候可以修改或者二次开发 |
MIT(Massachusetts Institute of Technology) | MIT 是宽松的许可协议,作者只想保留版权,而无任何其它限制。只需在发布的源代码、二进制可执行文件相关文档中包含 MIT 许可协议声明,便可自由的使用、修改源代码、作为商业软件再发布、甚至使用开源机构名字做产品的市场推广 |
Apache Licence 2.0 | 详见:开源协议详解 |
GPL(General Public License) | 代码的开源免费使用和引用,修改及衍生代码的开源免费使用,但其不允许修改后和衍生的代码做为闭源的商业软件发布和销售(只要使用 GPL 协议的相关类库与代码,则该软件亦必须采用GPL 协议,必须开源与免费) |
LGPL(Lesser General Public License) | LGPL 允许商业软件通过类库引用方式使用 LGPL 类库而不需要开源商业软件的代码,如果修改了 LGPL 协议的代码或衍生,则所有修改的代码和衍生的代码都必须采用 LGPL 协议 |
Mozilla(Mozilla Public License) | 在自己已有的源代码库上加一个接口,除了对接 Mozilla Public License 开源库的接口程序源代码以MPL许可的形式对外许可外,源代码中的其他源码可以不用 MPL 许可证的方式强制对外许可 |
完善 package.json 配置
配置完开源协议文件后,还需要在 package.json 里新增字段:"license": "MIT"。
除此之外,还可以设置关键词,简要标记下库的功能和技术点:
"keywords": [
"vite",
"commitlint",
"husky",
"CHANGELOG",
"LICENSE",
"ESlint"
],
files 字段再把 README 和 LICENSE 带上,因为使用了 TS,index.d.ts 也是必须上传的。另外,上传哪个类型声明文件,是因为我们配置了字段:"types": "./index.d.ts",。这里配置的是谁,你在 files 字段里上传的类型声明文件就是谁。
"files": [
"es",
"lib",
"README",
"LICENSE",
"index.d.ts"
],
还有 repository 和 publishConfig,分别记录库的代码仓库及发布时 npm 的镜像地址。如果是开源的话就是 https://registry.npmjs.org/,不是开源的话可以设置成自己或者公司的私有镜像地址。
"repository": {
"type": "git",
"url": "https://github.com"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
}
贴一下完整的 package.json:
{
"name": "open-source-config-template",
"private": false,
"version": "0.0.1",
"type": "module",
"main": "lib/index.js",
"module": "es/index.js",
"types": "./index.d.ts",
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext '.js,.ts' --fix",
"prepare": "husky install",
"commit": "git-cz",
"version": "bumpp",
"changelog": "conventional-changelog -p -i CHANGELOG.md -s",
"before-release": "yarn changelog && yarn build && bumpp"
},
"lint-staged": {
"src/**/*.{js,ts}": [
"eslint --fix",
"git add"
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"files": [
"es",
"lib",
"README",
"LICENSE",
"index.d.ts"
],
"keywords": [
"vite",
"commitlint",
"husky",
"CHANGELOG",
"LICENSE",
"ESlint"
],
"devDependencies": {
"@commitlint/cli": "^17.6.5",
"@commitlint/config-conventional": "^17.6.5",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"bumpp": "^9.1.0",
"commitizen": "^4.3.0",
"conventional-changelog-cli": "^2.2.2",
"eslint": "^8.42.0",
"husky": "^8.0.0",
"lint-staged": "^13.2.2",
"typescript": "^4.9.4",
"vite": "^4.0.4"
},
"repository": {
"type": "git",
"url": "https://github.com"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
}
}
写在最后
做到现在,我们来看下整个项目的文件目录:

基本上已经与一个开源项目的配置差不太多了,如果需要 stylelint 等其它配置,继续往里面添加即可。不过不是所有项目都会写 CSS,更细化的配置就是根据项目需要再添加了。
如果本篇文章对你有帮助,请点赞收藏关注三连一波,可以通过我的 vscode 截图看到我的 vscode 背景从亮变黑,从早写到晚写了整整一天,感谢 ~。
下面是本期文章配对的代码仓库:open-source-config-template
往期推荐
分享我在前端学习与开发中用到的神仙网站和工具 40+ 👍🏻 110+ ❤
uniapp 踩坑记录(二) 130+ 👍🏻 150+ ❤
闲来无事,摸鱼时让 chatgpt 帮忙,写了一个 console 样式增强库并发布 npm 100+ 👍🏻 110+ ❤
uniapp 初体验踩坑记录 30+ 👍🏻 60+ ❤
两小时学会 JS 正则表达式,终身不忘 50+ 👍🏻
【一年前端必知必会】如何写出简洁清晰的代码 50+ 👍🏻
【一年前端必知必会】了解 Blob,ArrayBuffer,Base64 40+ 👍🏻 90+ ❤
转载自:https://juejin.cn/post/7240461753934889021