从 1 到完美,写一个 js 库、node 库、前端组件库
之前讲了很多关于项目工程化、前端架构、前端构建等方面的技术,这次说说怎么写一个完美的第三方库。
1. 选择合适的规范来写代码
js
模块化的发展大致有这样一个过程 iife => commonjs/amd => es6
,而在这几个规范中:
-
iife
:js
原生支持,但一般不会直接使用这种规范写代码 -
amd
:requirejs
定义的加载规范,但随着构建工具的出现,便一般不会用这种规范写代码 -
commonjs
:node
的模块加载规范,一般会用这种规范写node
程序 -
es6
:ECMAScript2015
定义的模块加载规范,但到目前为止,几乎所有的js
运行环境都不支持,包括浏览器、node
(包括electron
、nw.js
)、React Native
等
针对原生不支持任何规范的运行环境程序(如浏览器、React Native
),建议使用 es6
规范来写代码,然后由工具转换成原生 js
能够运行的。
而针对 node
程序,可以直接用 commonjs
规范来写,也可由 es6
规范来写,然后用工具转化成 commonjs
规范。
所以,总的来说,都可以使用 es6
规范来写代码,然后用工具转换成其他规范,而且 es6
的代码可以使用 tree-shaking
功能。
参考:
- IIFE(Immediately-invoked function expression)
- requirejs、electron、nw.js、react-native
- Tree shaking
2. 选择合适的构建工具
对于前端项目来说,因为有静态资源(如图片、字体等)加载与按需加载的需求,所以使用 webpack
是不二选择,但对于第三方库来说,其实还有更好的选择:rollup
。
可以查看 webpack 之外的另一种选择:rollup 了解 webpack
与 rollup
之间各自的差异与优势。
webpack
在打包成第三方库的时候只能导出 amd/commonjs/umd
,而 rollup
能够导出 amd/commonjs/umd/es6
。使用 rollup
导出 es6
模块,就可以在使用这个库的项目中构建时使用 tree-shaking
功能。
对于有样式文件(css
、less
、scss
)、静态资源文件(图片、字体)的前端组件来说,可以使用 rollup-plugin-postcss 插件配合 rollup
处理样式文件与静态资源文件。
参考:
- webpack、rollup、rollup-plugin-postcss
- webpack 之外的另一种选择:rollup
- UMD (Universal Module Definition)
- tree-shaking
- webpack 如何优雅的使用tree-shaking(摇树优化)
3. 定好目录结构
一般库项目的目录:
|-- / # 项目根目录
|-- src/ # 源代码目录
|-- lib/(dist/) # 发布文件目录
|-- test/ # 测试文件目录
|-- ... # 更多其他目录
如果是多包项目(一个项目里有多个 npm packages,比如 babel):
|-- / # 项目根目录
|-- packages/ # packages 目录
|-- pkg1/ # package1 目录
|-- src/ # 源代码目录
|-- lib/(dist/) # 发布文件目录
|-- pkg2/ # package2 目录
|-- src/ # 源代码目录
|-- lib/(dist/) # 发布文件目录
|-- ...
后面会详细讲解多包项目。
4. 搭建一个好的脚手架
不管是应用项目还是第三方库项目,都需要搭建一个好的脚手架,来帮助我们更好的编写代码、构建项目等。
可以查看 搭建自己的前端脚手架 了解一些基本的脚手架文件与工具。
比如:
-
.editorconfig
: 用这个文件来统一不同编辑器的一些配置,比如tab
转 2 个空格、自动插入空尾行、去掉行尾的空格等,http://editorconfig.org - eslint、stylelint、prettier: 规范化代码风格、优化代码格式等
- husky、lint-staged: 在
git
提交之前对代码进行审查,否则不予提交 -
.travis.yml
: 一个很棒的持续集成服务,https://www.travis-ci.org/
详细的文件、工具与配置,参考 搭建自己的前端脚手架。
另外,针对开源的第三方库,还可以有:
-
LICENSE
: 协议文件 -
CONTRIBUTING.md
: 项目代码参与者 -
codecov.yml
: 测试覆盖率配置文件 -
.github
:github
上的一些自定义配置,比如issue
模板、pr
模板等 -
/docs
: 文档目录 -
/examples
: 使用示例目录 -
/scripts
: 脚本目录
加上 rollup
的配置文件 rollup.config.js
:
rollup.config.js
如果是 node
程序,把 es6
规范转化成 commonjs
规范:
export default {
input: 'src/index.js',
output: {
file: 'lib/index.js',
format: 'cjs',
},
};
如果是前端库,还需要转 es6+
到 es5
、导出不同规范的文件(es6/commonjs/amd/umd
):
import babel from 'rollup-plugin-babel';
import postcss from 'rollup-plugin-postcss';
export default [
{
file: 'lib/cjs.js',
format: 'cjs',
},
{
file: 'lib/m.js',
format: 'esm',
},
{
file: 'lib/umd.js',
format: 'umd',
name: 'Name',
},
{
file: 'lib/amd.js',
format: 'amd',
},
].map(output => ({
input: 'src/index.js',
output,
plugins: [
babel({
presets: ['@babel/preset-env'],
}),
postcss({ extract: !0 }), // 构建样式文件时需要这个插件
],
}));
.gitignore
一般来说,我们并不希望把发布文件放到 git
的版本控制之中,而只是发布到仓库而已,所以:
# .gitignore
.DS_Store
node_modules
bower_components
/coverage
*.log
.idea
.vscode
.eslintcache
package-lock.json
/lib # 把 lib 排除在外
/packages/*/lib # 多包项目
package.json
{
...
# node 项目
"main": "lib/index.js",
# 前端项目
"main": "lib/cjs.js", # commonjs 规范文件
"module": "lib/m.js", # es6 规范文件
"umd:main": "lib/umd.js", # umd 规范文件
"amd:main": "lib/amd.js", # amd 规范文件
"files": [ # 发布时只发布 lib 目录下文件
"lib"
],
"scripts": {
...
"build": "rollup -c", # 构建发布文件
"prepublishOnly": "npm run build", # npm publish 之前先 npm run build
"pretest": "npm run build", # npm run test 之前先 npm run build
},
...
}
在实际项目中,构建工具(如 webpack
)会首先找这个包中的 module
字段对应的 es6
规范文件,并使用 tree-shaking
;如果不存在,然后找 main
字段对应的文件。
有些构建工具可能也会用 amd
规范文件与 umd
规范文件。
参考:
- 搭建自己的前端脚手架
5. 构建多包项目
如果一个项目很大,需要分割成多个 npm
包进行管理,但这些包仍然在一个项目里,并且这些包可能有相互依赖关系,这个时候就比较难以管理和开发了。
为了方便的管理多包项目,lerna 便应运而生,babel、create-react-app、jest、lila 等都是用 lerna
来管理多个包的。
英文不好的童鞋,可以参考 使用lerna管理大型前端项目,了解 lerna
的一些基本用法。
lerna 一般目录文件结构
my-lerna-repo/
package.json
packages/
package-1/
package.json
package-2/
package.json
安装 lerna,初始化项目
# 安装
npm i -g lerna
# 初始化
git init lerna-repo && cd lerna-repo
lerna init
# 初始化后的目录及文件
lerna-repo/
packages/
package.json
lerna.json
配置文件 lerna.json
{
"version": "0.5.2", # 当前版本号
"packages": [
"packages/*"
],
"command": {
"publish": { # 发布配置
"ignoreChanges": [ # 哪些文件变动不会引发发布新版本
"*.md",
"*.json",
"*.txt",
"test/**",
"example/**",
"package.json"
]
},
"bootstrap": {
"npmClient": "cnpm" # lerna bootstrap 时使用哪个 npm 客户端
}
},
"npmClientArgs": [ # npm 客户端 运行时的参数
"--no-package-lock"
]
}
常用命令
lerna publish
: 发布所有有更新的包
在默认的固定模式(Fixed mode)下,这个命令会检查 packages
目录下哪些包的文件有更新(lerna.json
中 command.publish.ignoreChanges
除外),然后把 lerna.json
中的 version
与有更新的包中 package.json
的 version
字段更新到一个新的版本号上,最后把这些有更新的包都发布到远程仓库上。
lerna bootstrap
: 启动建立包相互之间的 node_modules
链接
这个命令会根据各个包下 package.json
里面的 dependencies
和 devDependencies
配置,使用 symlink
在各个包的 node_modules
下面建立引用关系。这样就解决了相互之间有依赖而无法联调的问题。
lerna changed
: 查看哪些包有更新,可以发布一个新的版本
lerna diff [package?]
: 查看包都更新了些什么
lerna run [script]
: 使用 npm
运行每个包下面的 [script]
参考:
- lerna
- 使用lerna管理大型前端项目
6. 示例
单个包的 node
项目可以参考我的项目:sclean
单个包的前端项目可以参考我的项目:see-fetch
多个包的项目可以参考我的项目:lila
后续
更多博客,查看 https://github.com/senntyou/blogs
作者:深予之 (@senntyou)
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)