之前讲了很多关于项目工程化、前端架构、前端构建等方面的技术,这次说说怎么写一个完美的第三方库。

1. 选择合适的规范来写代码

js 模块化的发展大致有这样一个过程 iife => commonjs/amd => es6,而在这几个规范中:

  • iifejs 原生支持,但一般不会直接使用这种规范写代码
  • amdrequirejs 定义的加载规范,但随着构建工具的出现,便一般不会用这种规范写代码
  • commonjsnode 的模块加载规范,一般会用这种规范写 node 程序
  • es6ECMAScript2015 定义的模块加载规范,但到目前为止,几乎所有的 js 运行环境都不支持,包括浏览器、node(包括 electronnw.js)、React Native 等

针对原生不支持任何规范的运行环境程序(如浏览器、React Native),建议使用 es6 规范来写代码,然后由工具转换成原生 js 能够运行的。

而针对 node 程序,可以直接用 commonjs 规范来写,也可由 es6 规范来写,然后用工具转化成 commonjs 规范。

所以,总的来说,都可以使用 es6 规范来写代码,然后用工具转换成其他规范,而且 es6 的代码可以使用 tree-shaking 功能。

参考:

2. 选择合适的构建工具

对于前端项目来说,因为有静态资源(如图片、字体等)加载与按需加载的需求,所以使用 webpack 是不二选择,但对于第三方库来说,其实还有更好的选择:rollup

可以查看 webpack 之外的另一种选择:rollup 了解 webpack 与 rollup 之间各自的差异与优势。

webpack 在打包成第三方库的时候只能导出 amd/commonjs/umd,而 rollup 能够导出 amd/commonjs/umd/es6。使用 rollup 导出 es6 模块,就可以在使用这个库的项目中构建时使用 tree-shaking 功能。

对于有样式文件(csslessscss)、静态资源文件(图片、字体)的前端组件来说,可以使用 rollup-plugin-postcss 插件配合 rollup处理样式文件与静态资源文件。

参考:

3. 定好目录结构

一般库项目的目录:

|-- /                   # 项目根目录
|-- src/ # 源代码目录
|-- lib/(dist/) # 发布文件目录 |-- test/ # 测试文件目录
|-- ... # 更多其他目录

如果是多包项目(一个项目里有多个 npm packages,比如 babel):

|-- /                           # 项目根目录
|-- packages/ # packages 目录
|-- pkg1/ # package1 目录
|-- src/ # 源代码目录
|-- lib/(dist/) # 发布文件目录
|-- pkg2/ # package2 目录
|-- src/ # 源代码目录
|-- lib/(dist/) # 发布文件目录 |-- ...

后面会详细讲解多包项目。

4. 搭建一个好的脚手架

不管是应用项目还是第三方库项目,都需要搭建一个好的脚手架,来帮助我们更好的编写代码、构建项目等。

可以查看 搭建自己的前端脚手架 了解一些基本的脚手架文件与工具。

比如:

详细的文件、工具与配置,参考 搭建自己的前端脚手架

另外,针对开源的第三方库,还可以有:

  • LICENSE: 协议文件
  • CONTRIBUTING.md: 项目代码参与者
  • codecov.yml: 测试覆盖率配置文件
  • .githubgithub 上的一些自定义配置,比如 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 便应运而生,babelcreate-react-appjestlila 等都是用 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]

参考:

6. 示例

单个包的 node 项目可以参考我的项目:sclean

单个包的前端项目可以参考我的项目:see-fetch

多个包的项目可以参考我的项目:lila

后续

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

从 0 到 1 到完美,写一个 js 库、node 库、前端组件库的更多相关文章

  1. Vue折腾记 - (3)写一个不大靠谱的typeahead组件

    Vue折腾记 - (3)写一个不大靠谱的typeahead组件 2017年07月20日 15:17:05 阅读数:691 前言 typeahead在网站中的应用很多..今天跟着我来写一个不大靠谱的ty ...

  2. 让我们纯手写一个js继承吧

    继承在前端逻辑操作中是比较常见的,今天我们就从零开始写一个js的继承方式 在es5中继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上Parent.call(this),在es6中则 ...

  3. 基于vue2.0前端组件库element中 el-form表单 自定义验证填坑

    eleme写的基于vue2.0的前端组件库: http://element.eleme.io 我在平时使用过程中,遇到的问题. 自定义表单验证出坑: 1: validate/resetFields 未 ...

  4. 从零开始写一个npm包,一键生成react组件(偷懒==提高效率)

    前言 最近写项目开发新模块的时候,每次写新模块的时候需要创建一个组件的时候(包含组件css,index.js,组件js),就只能会拷贝其他组件修改名称 ,但是写了1-2个后发现效率太低了,而且极容易出 ...

  5. 前端与编译原理——用JS写一个JS解释器

    说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...

  6. 如何手写一个js工具库?同时发布到npm上

    自从工作以来,写项目的时候经常需要手写一些方法和引入一些js库 JS基础又十分重要,于是就萌生出自己创建一个JS工具库并发布到npm上的想法 于是就创建了一个名为learnjts的项目,在空余时间也写 ...

  7. 【转载】写一个js库需要怎样的知识储备和技术程度?

    作者:小爝链接:https://www.zhihu.com/question/30274750/answer/118846177来源:知乎著作权归作者所有,转载请联系作者获得授权. 1,如何编写健壮的 ...

  8. 如何写一个Js上传图片插件。

    项目里面需要一个上传图片的插件,找了半天没有找到满意的,算了 不找了,自己写一个吧,顺便复习一下js方面的知识.完成之后效果还不错,当然还要继续优化,源码在最后. 介绍一种常见的js插件的写法 ; ( ...

  9. 【Part1】用JS写一个Blog(node + vue + mongoDB)

    学习JS也有一段时间了,准备试着写一个博客项目,前后端分离开发,后端用node只提供数据接口,前端用vue-cli脚手架搭建,路由也由前端控制,数据异步交互用vue的一个插件vue-resourse来 ...

随机推荐

  1. 第二阶段scrum-2

    1.整个团队的任务量: 2.任务看板: 会议照片: 产品状态: 正在连接配置数据库部分

  2. hive的join优化

    “国际大学生节”又称“世界大学生节”.“世界学生日”.“国际学生日”.1946年,世界各国学生代表于布拉格召开全世界学生大会,宣布把每年的11月17日定为“世界大学生节”,以加强全世界大学生的团结和友 ...

  3. 字符串专题之KMP算法

    写点自己对KMP的理解,我们有两个字符串A和B,求A中B出现了多少次. 这种问题就可以用KMP来求解. 朴素的匹配最坏情况是O(n^2)的.KMP是个高效的算法,效率是O(n)的. KMP算法的思想是 ...

  4. Nginx系列p4:进程结构

    Nginx 有两种进程结构:单进程结构,多进程结构.本篇文章我们主要说多进程结构. 问:那为什么 Nginx 采用多进程结构,而不是多线程结构呢? 答:这是因为 Nginx 最核心的目的就是要保证高可 ...

  5. BUUCTF-[HCTF 2018]WarmUp

    php中可以使用strpos函数与mb_strpos函数获取指定的字符串在别一个字符串中首次出现的位置,也可以使用它们判断一串字符串中是否包含别一个字符串. PHP strpos() 函数 查找 &q ...

  6. SHIDOU

    1. arp    i  指定网卡     a 查看arp表,显示主机名称和ip      n 查看arp表,并且用ip显示而不是主机名称   2. 119 ~/M2/image-installer- ...

  7. Python Scrapy的QQ音乐爬虫 音乐下载、爬取歌曲信息、歌词、精彩评论

    QQ音乐爬虫(with scrapy)/QQ Music Spider UPDATE 2019.12.23 已实现对QQ音乐文件的下载,出于版权考虑,不对此部分代码进行公开.此项目仅作为学习交流使用, ...

  8. Android如何用一个TextView显示不同颜色得字符

    最近做一个项目,需要一个字符串显示不同的颜色.当时直接想到的就是用多个TextView来拼接,但是如果字符数量多的话,这样写是非常麻烦得.而且还要增加很多控件. 后来发现一个非常方便得方法.直接看代码 ...

  9. 干货 | 用Serverless快速在APP中构建调研问卷

    Serverless 计算将会成为云时代默认的计算范式,并取代 Serverful (传统云)计算模式,因此也就意味着服务器 -- 客户端模式的终结. ------<简化云端编程:伯克利视角下的 ...

  10. Access修改窗体的名称,用于VBA代码的调用