📦vue组件发布npm最佳实践
前言
我们经常使用组件,二次封装或开发新组件,在团队内部使用; 可当我们想通过npm分享组件时,却没了之前的得心应手,本文旨在帮助大家在可以更轻松地发布组件
首先,把vue组件发布到npm这件事可以拆分成两个部分:
- 在npm上发布一个包
- 将vue组件打包
npm发包
有人说,发包不是一行命令就搞定了么
npm publish
是的,可是还忽略了以下几点:
- 首先你要在npmjs上注册一个账号
- 查看你的
.npmrc
设置,确保你的registry是https://www.npmjs.com/, 而不是淘宝源 - 在终端
npm login
,登录你的账号
做好以上三点,才可以通过npm publish
简单地发布一个包。若要遵循最佳实践,还有一些准备工作要做好,下面将为你讲述
完善基本信息
package.json的以下字段需要注意
name
version
description
keywords
author
license
repository
main
unpkg
module
scripts
engines
name
就是发布到npm上的包名,也即别人安装时输入的名字yarn add ${name}
, 包名应该是kebab-case
, 即英文单词全小写,中划线分割(lower case and dash-separated)
version
是语义化的,major.minor.patch
. 如果是major
变动,通常意味着不兼容的修改; 如果是minor
,意味着添加向后兼容的新功能,如果是patch
, 意味着bug的修复。详情见semver.org
description
是对包的描述,在npmjs.com上搜索时会显示,有助于用户在搜索时进行筛选
keywords
同样也是帮助用户查找到你的包
author
的格式一般是${your name} ${email}
, 当然也可以是一个github地址
license
可能很多人会忽略,最好也写上去。至于用哪个,vue的官方项目全是MIT,因此我也是MIT,不纠结
repository
的格式参考如下:
"repository": {
"type": "git",
"url": "https://github.com/FEMessage/el-data-table.git"
}
这样在npm包页面就有会个github的入口
main
定义了包的入口文件,在NodeJs环境,语句import pkg from 'package-name'
时,其实导入的就是main
定义的文件,它可以是CommonJs格式的, 也可以是umd格式
需要注意的是,当你把一个包发布到npm上时,它同时也可以在unpkg上获取到。也就是说,你的代码既可能在NodeJs环境也可能浏览器环境执行。为此你需要用umd格式打包,并且在package.json定义unpkg
字段,一般而言此时它的命名为name.min.js
最后,别忘了定义module
或jsnext:main
字段,它表示用ES6模块格式打包,给Webpack 2+或Rollup等先进的构建工具来处理。
我们来看一下三个字段的示例:
"main": "dist/el-data-table.js",
"unpkg": "dist/el-data-table.min.js",
"module": "dist/el-data-table.esm.js"
scripts
为了防止出现发包前忘记构建的乌龙事件,定义一下发布前的脚本, 这样每次执行npm publish
前都会先执行npm run build
"prepublishOnly": "npm run build"
engines
可以告诉用户运行你的包对NodeJs版本的要求,这是非常重要的,不然你使用了NodeJs新版本特性,却没有定义该字段,导致低版本NodeJs用户运行报错,让人摸不着头脑。
定义依赖
当你开发一个项目时,比如一个静态网站或一个单面应用,dependencies和devDependencies并没有太多区别,因为你npm install
或 yarn
时,这些依赖都会下载下来,因为你是在开发。
但对于发布到npm的包则不同:
dependencies 是运行你的包必须安装的依赖,即当用户yarn add my-awesome-package
时,这些依赖也会下载。
devDependencies 是开发你的包时需要安装的依赖,比如eslint
, jest
等开发工具,当用户yarn add my-awesome-package
时,这些依赖并不会下载!
peerDependencies 一般用于开发插件的场景,它要求用户必须预先安装了某些依赖。比如开发webpack
的插件,如果你把对webpack
的依赖定义成dependencies, 如果用户安装的webpack
跟dependencies里的minor
版本不一致, 则用户yarn add my-webpack-plugin
时会把dependencies定义的webpack
也下载下来,也即用户会安装两个major
版本相同的webpack
, 这就不合适了。
所以说,定义好你的包的依赖,可以让用户安装使用你的包时少点困惑,多些愉悦。
忽略文件
如果有 .gitignore
文件,则发布时会忽略 .gitignore
中定义的文件; 也即这些文件不需要在.npmignore
重新定义。如果用.gitignore
忽略了dist
目录,但发包时又需要发布dist
目录,此时可以在package.json定义files
字段,这是一个白名单,里面的文件都会被发布出去
"files": [
"dist"
]
需要注意的是,子文件夹.gitignore
或.npmignore
同样有效,而它们会覆盖files
字段
另外,有些文件无论如何设置,都不会发布出去:
node_modules
.git
(包括.gitignore
)
README.md
别忘了这个文件,写下与包相关的更具体的信息,告诉用户这个包有哪些功能,如何使用。这很重要,用户不会使用一个没有文档说明的包!
发布
一个版本只能发布一次,为了避免每次手动修改package.json
, 可以使用npm version [major | minor | patch]
命令来更新package.json
里的版本
打标签
假设你的包最新版本是1.0.0
, 当用户yarn add my-awesome-package
或yarn add my-awesome-package@1.0.0
时,其实是相当于yarn add my-awesome-package@latest
, 即不指定标签安装时,默认安装latest
版本。
假设你正在开发2.0.0
版本,它还不稳定,你想发布它让用户测试一波,此时又不能让它变成latest
版本,不然用户yarn add my-awesome-package
时就安装了2.0.0
了,那将让用户崩溃。这时该怎么办呢?标签就用上场了。
可以这样发布
npm publish --tag beta
则用户yarn add my-awesome-package
安装的是1.0.0
版本, yarn add my-awesome-package@beta
时,安装的是2.0.0
版本,不影响老用户,棒!🎉
记住,你只能对一个版本打一个标签,使用npm dist-tag ls
可以查看npm包一共打了几个标签
打包Vue
脚手架
经过一番折腾,在Vue Conf上找到一个vue组件的打包脚手架(vue官方文档也有说明),进行“本土化”修改完善后,已在github开源:https://github.com/FEMessage/vue-sfc-cli
说明
我们以开源组件el-data-table为例,解释目录结构及文件
├── README.md
├── build
│ └── rollup.config.js
├── dist
│ ├── el-data-table.esm.js
│ ├── el-data-table.min.js
│ └── el-data-table.umd.js
├── docs
│ ├── build
│ └── index.html
├── package.json
├── src
│ ├── el-data-table.vue
│ └── index.js
├── styleguide.config.js
├── test
│ └── index.test.js
└── yarn.lock
先来看三个文件:
README.md
package.json
yarn.lock
README.md
与package.json
大家都懂,有yarn.lock
因为是我们鼓励大家使用yarn, 它比npm
更快。虽然npm
6.0号称提速17倍(可以想象6之前是得有多慢😂),但经测试,还是不如yarn
接下来看build
, dist
, src
目录
├── build
│ └── rollup.config.js
├── dist
│ ├── el-data-table.esm.js
│ ├── el-data-table.min.js
│ └── el-data-table.umd.js
├── src
│ ├── el-data-table.vue
│ └── index.js
build
目录下放编译时的配置文件,这个跟vue-cli 2.x
生成的模板build目录作用是一样的,只不过这里放置的是rollup.config.js
。至于为什么用Rollup, 一是因为配置更简单,二是因为它更适合打包类库,当源文件中有import lib from 'awesome-lib'
类似的代码时,Rollup并不会把awesome-lib
捆绑输出,这正是开发类库或组件需要的特性
dist
是输出目录,也有叫lib
的,我也纠结了好久。看了一些优秀的开源项目,发现叫dist
的比较多,而webpack4
默认的输出目录也是dist
, 因此决定用dist
。至于dist
目录下会有三个文件,前文已说过原因。而命名为何不是camelcase
, 而是kebab-case
, 后面风格指南会说到
src
是输入目录。把index.js
放在src
目录,也是经过一番考虑。也想把index.js
跟package.json
同级,最终参考了webpack4
, 它默认输入是src/index.js
, 那就跟主流保持一致。该文件主要工作是把src
目录下的vue文件设置成vue的插件。同样,vue文件的命名后面风格指南会说到
├── test
│ └── index.test.js
test
目录下是基于jest
及vue/test-utils
的单元测试文件,具体教程可参考官方文档
├── docs
│ ├── build
│ └── index.html
├── styleguide.config.js
docs
存放的是组件的api文档,包含props
, slot
, event
等内容的说明,使用的是vue-styleguidist作为vue组件文档生成工具。为啥叫 docs
呢,因为Github Pages
支持从master
分支的docs
目录读取文件,在仓库Settings
里选择Github Pages
的Source
即可, 具体看官方文档
风格指南
vue组件把template/script/style都放在一个vue文件里,这个称之为单文件组件,Single File Component
,缩写为SFC, 这就是vue-sfc-cli中sfc的寓意
通读vue官方风格指南, 由于我们是kebab-case
的重度用户,因此我们更看重的是在多个项目中保持相同的大小写规则,以下是摘取的适用于我们团队协作习惯的指南:
这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的
我们选择使用kebab-case
好例子:
components/
|- my-component.vue
好例子:
<!-- template -->
<my-component></my-component>
- [JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中只使用Vue.component 进行全局组件注册时,可以使用 kebab-case 字符串](JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中只使用Vue.component 进行全局组件注册时,可以使用 kebab-case 字符串)
这里我们选择使用PascalCase
, 因为在编程语言里,kebab-case
并不是最佳实践。 也即,在非编程语言的范围,我们能用kebab-case
就用
好例子:
Vue.component('MyComponent', {
// ...
})
import MyComponent from './my-component.vue'
export default {
name: 'MyComponent',
// ...
}
综上所述,就可以明白前文中el-data-table的文件命名风格为kebab-case
的原因了
参考
- How to publish your package on npm
- module-best-practices
- npm-style-guide
- semver.org
- unpkg
- umd
- npm scripts
- .npmignore
- 2018 Vue Conf
- Vue Cookbook
- Vue Style Guide
- npm-vs-yarn
- Github Pages
注
本文最早发布在掘金,现迁移至github。