eamonnzhang / blog Goto Github PK
View Code? Open in Web Editor NEW:book: Some Notes
:book: Some Notes
本文旨在给大家提供一种构建一个完整UI库脚手架的思路:包括如何快速并优雅地构建UI库的主页、如何托管主页、如何编写脚本提升自己的开发效率、如何生成CHANGELOG等等,这里提供了一个Demo可供大家参考:
主流的开源UI库代码结构主要分为三大部分:
我们先用vue-cli3初始化一个模板,然后在根目录下新增三个文件夹,一个用来存放 组件 的代码(packages
),一个用来存放 预览示例的网站 代码(examples
)(这里直接把初始化模板的src
目录更改为examples
即可),一个用来存放 脚本 代码(build
)
文件的名字可以根据自己喜好去命名。
具体的代码组织方式可以查看Github上的源码,这里需要注意,我们尽量以组件名来命名文件夹名,然后在文件夹内新建index.vue
组件。这么做是为了方便后面我们用代码直接生成index.js
入口文件的内容。
为了更好的处理样式,我们把所有的样式文件单独处理(代码地址),这里主要用Gulp来处理任务。
当我们完成了一个组件,就可以打包了,打包很简单,这得益于vue-cli3的build
命令引入了构建目标参数,只需执行
vue-cli-service build --target lib --name vue-cards --dest lib packages/index.js
就可以把packages
下所有的代码以库
模式打包出去。
在库模式中,Vue 是外置的。这意味着包中不会有 Vue,即便你在代码中导入了 Vue。如果这个库会通过一个打包器使用,它将尝试通过打包器以依赖的方式加载 Vue;否则就会回退到一个全局的 Vue 变量。
发布之前,我们需要新增一个.npmignore
文件,把一些不需要发布到npm
包的文件或者文件夹都写在里面,写法和.gitignore
一致。
具体怎么发布一个npm
包这里就不在赘述了。
可能大多数人觉得写一个开源UI库最头疼的事情就是写文档,如何快速又优雅的构建出像ElementUI官网这类网站呢?
目前最流行的写文档的方式就是通过markdown
编写,那我们要解决的就是如何将Markdown文档HTML化,直接展示在页面中。
这里我们可以用vue-markdown-loader插件,该插件的具体配置可以查看项目的vue.config.js文件。
具体到本项目的效果如图所示:
代码示例部分都是用markdown
编写的,其他部分则是普通的vue
组件。
我们可以利用GithubPages功能来发布我们的文档网站,具体的使用方法网上有很多教程,比如GitHub Pages 使用入门。
其实还有一种很简单的方式,可以用gh-pages
这个工具,一键发布到对应仓库的gh-pages
分支。具体配置和使用可以参考本项目的build/publish-docs.js
文件。
在本项目的build
文件夹下,有很多能够大大提升我们开发效率的文件,下面来一一讲解一下它们的用途。
get-components.js
文件主要为了获取packages
目录下所有的组件目录,为我们构建packages
下的入口文件做准备。
build-entry.js
文件主要是根据get-components.js
的结果,然后将代码写入/packages/index.js
,生成入口文件。
build-lib.js
文件主要是做一些发布npm包前的构建准备,包括构建入口文件、构建库、构建样式文件等。
publish-docs.js
文件的作用是可以一键发布文档到本仓库的gh-pages
分支。
release.sh
脚本文件主要是把一些发布npm包的命令集合在了一起,包括很多git和npm操作。
CHANGELOG我们可以自己手写MD文档,当然这并不明智。我们更希望的是用代码去自动化生成,原理无非就是提取你的git提交信息并写入文件,这里我们可以用conventional-changelog来生成CHANGELOG。为了生成更精细和正确的CHANGELOG文档,我们需要严格规范自己的提交记录,我们可以用conventional-changelog
的标准提交记录:cz-conventional-changelog,具体的用法可以参考cz-cli。本项目的package.json的init
脚本其实也封装了相关的脚本命令,可以参考。
Babel的前身的叫6to5,顾名思义它最初的作用就是把ES6转化为ES5,但是Babel的创始人一直觉得这个名字并没有传达出团队的真正目标,他们想做的不仅仅是把ES6转化为ES5这么单一的事情,况且现在ES6又被叫作ES2015,所以这个名字总归会引起很多歧义,于是团队决定把名字重新命名为 Babel。
Babel 一词来源于道格拉斯·亚当斯(Douglas Adams)所著的《银河系漫游指南》里的巴别鱼(BabelFish),它是一个能帮助人类理解任何语言的虚构物种,同时也恰好向巴别塔(Babel Tower)的故事致敬,《创世纪》中正是巴别塔的坠落造就了各种各样的语言。
截止到目前(2018/11/08),Babel已经发布到了第七个版本,7和6的包除了命名方式上有变化(7全部以@babel/*
而非babel-*
)之外,构成方式的变化并不算太大(之后会更新Babel6和7的异同)。值得注意的是,babel从5升级到6对包的构成进行了一个重大的改版,很多包被分离了出来,更注重了模块化和插件化,而不再是像5一样的“一锅大杂烩”。
从Github可以看到babel5.x版本的packages构成,只有几个包,但是切换到babel6.x以上的版本可以看到多了很多包,不要被这么多包吓到,我们来仔细梳理一下,根据前缀大致把包可以分为以下四大类:
babel采用monorepo来管理自己module
接下来将以Babel7.x为基础讲解。
第一类的包大致可以分为两种:核心包和外围包。
核心包(Core Packages):
@babel/core
babel.transform
方法,用于转换源代码。babel编译器又可以分解下面的三个部分。@babel/parser
@babel/traverse
@babel/generator
其实从这个四个核心包就能基本看出Babel的工作流程和原理大致就是:
输入字符串 ->
@babel/parser
编译 -> AST -> transformer[s] 使用@babel/traverse
去遍历上一步AST并进行一些操作 -> AST ->@babel/generator
把最终的AST再次生成代码 -> 输出字符串
外围包(Other Packages):
@babel/cli
@babel/core
的命令行工具,同时也包含了@babel/node
运行环境。@babel/types
@babel/polyfill
core-js
和regenerator-runtime
包在一起的包装器。@babel/runtime
@babel/plugin-transform-runtime
一起使用(后面会讲为什么)。@babel/register
@babel/template
@babel/types
构建AST节点的繁琐。@babel/helpers
@babel/template
功能。@babel/code-frame
ok,Babel一些很重要的包都介绍完了,那是不是装了@babel/core
之后,然后调用transform
方法就能直接编译出我们想要的代码了呢?比如:
const babel = require('@babel/core')
let transformCode = babel.transform('(x)=>x')
console.log(transformCode.code)
发现并没有任何作用,所以如果想让编译有效果,还需要下面两类包的配置,其实这也是Babel升级为6版本之后插件化的精髓所在。
安装Babel不就是为了使用es的所有新特性么,为什么还要弄个插件配置来说明我要用哪些新特性? - 尤雨溪的回答 - 知乎
https://www.zhihu.com/question/61124018/answer/184718397
插件包是Babel的核心,也是让Babel能够产出我们想要的代码的一类包。
插件包大致又可分为三类:
由此可见Babel将插件拆分的特别细,但是一个前端项目往往是庞大的,使用的各种语法也很繁杂,并且运行浏览器环境是不确定的,比如有的项目需要兼容IE9,有的只在现代浏览器里运行,这都会对插件配置的有影响,如果一个一个插件配置显然是很繁琐的,因此Babel又帮我们预置了一些插件集合,就是下面一类包。
预置包其实就是插件的一个集合,这里“隆重”推荐@babel/preset-env
。
env代表环境的意思,也就是说这个包可以根据当前你想要配置的执行的环境的版本来引入不同的插件,Babel会自动计算不同环境版本之间对不同新特性之间支持的差异,从而做到最优。
这类包主要用于各种其他插件的内部使用。
众所周知,JavaScript在计算某些浮点数的运算时会出现精度的丢失,比如你在控制台输入0.1+0.2
,得到的结果是0.30000000000000004
而不是0.3
,原因是什么?
我们知道,计算机里所有的数据最终都是以二进制保存的,当然数字也一样。所以当计算机计算0.1+0.2
的时候,实际上计算的是这两个数字在计算机里所存储的二进制,那么0.1
在JavaScript里存储的二进制到底是多少?
我们先根据十进制转二进制的方法,把0.1
转化为二进制是:0.0001100110011001100...
(1100循环),然后把0.2
转化为二进制是:0.00110011001100...
(1100循环)。
我们发现,它们都是无限循环的二进制。显然,计算机当然不会用自己“无限的空间”去存储这些无限循环的二进制数字。那对于这类数据该怎么办?
不同的语言可能会有不同的存储标准,JavaScript中所用的数字包括整数和小数,都只有一种类型就是Number
,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数(相关的还有float 32位单精度),具体的双精度浮点数的存储方式这里不再赘述(可以看后面章节的详细描述),我们只需要知道,在二进制科学表示法中,双精度浮点的小数部分最多只能保留52位(比如1.xxx...*2^n
,这里x
最多保留52位)加上前面的1,其实就是保留53位有效数字,剩余的舍去,遵从“0舍1入”,那么0.1
的二进制舍去之后就是:
0.00011001100110011001100110011001100110011001100110011010
同理我们得到0.2
的舍去之后的二进制表示为:
0.0011001100110011001100110011001100110011001100110011010
二者相加得到:
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111
可以看到结果正好为:0.30000000000000004
。
注:大多数语言中的小数默认都是遵循 IEEE 754 的 float 浮点数,包括 Java、Ruby、Python,本文中的浮点数问题同样存在。
双精度浮点数一共占据64位:
这里的符号位、指数位、小数位是和科学记数法联系在一起的。
我们以78.735
为例
最后的1.001110011*2^6
就是科学记数法,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这就叫浮点数。
那我们不妨根据这个规定,对号入座,把78.735
转化为双精度的表示法,符号位和小数位很明显能看出来,只需要把指数部分6
转化为二进制是110
就可以了,最终为:
0(sign) 00000000110(exponent) 00111001 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
(这个结果其实错误的,具体为什么错,继续看下文)
我们再根据双精度规范,来看看上文提到的0.1
到底是如何存储的,我们已知它的二进制是:
0.00011001100110011001100110011001100110011001100110011001 10011...
转化为科学表示法就是:
1.1001100110011001100110011001100110011001100110011001*2^-2
也就是说0.1
的:
0
1001100110011001100110011001100110011001100110011001
-2
到这里我就懵逼了,-2
怎么转为二进制呢,虽然双精度浮点规范规定了一个符号位,但是这个符号位表示的是整个数据的正负,而非指数的正负,难道还要保留一位专门存储指数的正负吗?答案是否定的。
为了减少不必要的麻烦,IEEE规定了一个偏移量,这个偏移量是干嘛用的呢,就是对于指数部分,每次都加这个偏移量进行保存,这样即使指数是负数,那么加上这个偏移量也变为正数啦。为了使所有的负指数加上这个偏移量都能够变为正数,这个偏移量的设置也是有规律的。
以double双精度为例,我们知道它的指数部分是二进制的11位,那么能够表示的数据范围就是0~2047
,IEEE规定1023
为双精度的偏移量。
e-Bias
。 此时e最小值是1,则1-1023= -1022
,e最大值是2046
,则2046-1023=1023
,可以看到,这种情况下取值范围是-1022~1013
。1-Bias
,即1-1023= -1022
。NaN(not a number)
。 具体的,小数位不为0的时候表示NaN;小数位为0时,当符号位s=0时表示正无穷,s=1时候表示负无穷。这个时候我们再看78.735
的到底该如何转化,指数部分存储的时候需要加上偏移量6+1023
就是1029
,转化为二进制就是:
10000000101
,所以78.735
正确存储方式为:
0 10000000101 00111001 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
同理,你是否也知道0.1
的双精度的浮点存储形式了呢?
如果你认真读到了这里,想必你应该能推算出JavaScript的所能表示的数值范围了吧。
e的最大值是1023。
1.111..(52位)..11*2^1023
转为普通二进制就是:
1 111..(52位)..11 000..(971位)..00
把二进制转为十进制就是:
我们会发现这个值和Number.MAX_VALUE
的值一致,都是1.7976931348623157e+308
。
但实际上这个值还不算最大,比如我们在此数值基础上继续加一些数,发现并没有返回Infinity
。
所以Number.MAX_VALUE
和Infinity
之间还存在很多数,根据IEEE规范我们可以得知,正无穷当且仅当是指数部分全为1(指数部分的最大值Math.pow(2,11)-1-1023 == 1024
),小数部分为0的时候,就是:
1.000...*2^1024
所以Math.pow(2,1024)
就是正无穷,那么其实JavaScript所能存储的最大数字是Math.pow(2,1024)-1
。
但是Number.MAX_VALUE
和Math.pow(2,1024)
之间的数据我们无法正常表示出来,精度会丢失。
同理也可推算最小数。
所谓安全范围,就是我们在这个范围内计算不会出现精度的丢失。
根据双精度的定义,可以得知,最大的安全整数:
1.11..(52位)*2^52
转为十进制就是Math.pow(2,53)-1
,即9007199254740991
。
在JavaScript中,有
Number.MAX_SAFE_INTEGER
来表示最大安全整数
我们发现和我们自己推算出来的值是一样的。
这里推荐Number-Precision库,不到1K的体积。
参考文章:
讲如何配置Babel之前,我们先来看看怎么使用Babel。
从JS运行环境上分类,可分为两种:
浏览器环境下如何使用Babel取决于你如何处理和编写JS代码。
<div id="output"></div>
<!-- 加载 Babel -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<!-- 你的脚本代码 -->
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById('output').innerHTML = getMessage();
</script>
如代码所示,只需在头部引入babel-standalone@6/babel.min.js"
,然后把ES6代码写在<script>
标签中,注意标签的type="text/babel"
。
这种使用Babel的方式非常不推荐,首先是因为引入了babel-standalone@6/babel.min.js"
,这个模块里包含了所有Babel的插件和功能,全特性支持,但是体积接近1MB,造成了多余的请求;其次是因为实时的转码也会浪费时间和资源,性能也不高。所以这种方式只推荐在开发和学习中使用,生产环境万万不可。
针对Webpack的打包,只需引入babel-loader
即可转译JS代码:
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}
其他的打包工具也是根据工具配置文档来配置,方式大同小异,不再赘述。
Gulp为例,只需引入gulp-babel
:
var gulp = require("gulp");
var babel = require("gulp-babel");
gulp.task("default", function () {
return gulp.src("src/app.js")
.pipe(babel())
.pipe(gulp.dest("dist"));
});
其他构建工具使用方法也类似,不再赘述。
Babel提供了babel-register
模块,只需:
require("babel-register");
require("other ES6&7&8 Module");
这个包重写了require
命令,加上了一个钩子。此后通过 node 引入的带 .es6
, .es
, .jsx
和 .js
后缀的所有后续文件都将会被 Babel 转译。
这种方式也是实时转码,所以也只适合在开发环境中使用。
友情提示,Node发展到今日(2018/11/10)已经基本上支持了所有(99%~100%)的包括ES2015、ES2016、ES2017、ES2018的特性,详见Node.js ES Support,所以要想使用新的ES特性,请尽快升级你的Node版本吧!
Babel有两种并行的文件配置,可以一起用,也可以单独使用,都是放在项目或者模块的根目录下:
babel.config.js
,写法如下:
module.exports = function () {
const presets = [ ... ];
const plugins = [ ... ];
return {
presets,
plugins
};
}
.babelrc
或者package.json
的babel
字段,写法如下:
.babelrc
:
{
"presets": [...],
"plugins": [...]
}
package.json
:
{
"name": "my-package",
"version": "1.0.0",
"babel": {
"presets": [ ... ],
"plugins": [ ... ],
}
}
那么读到这里你应该奇怪,为什么要用两种文件配置,直接用一种不好吗?下面我来粗浅的讲一下,二者的异同。
这两种配置方式差异主要体现在Babel向上级目录查找的方式。
.babelrc
的查找机制是这样的:
Babel从被编译的文件开始向上查找该文件,合并以及重写配置,一旦找到包含package.json的目录,搜索将停止,因此.babelrc
只应用于单个模块中。
这会带来一个问题就是,比如我们用了第三方的node_modules
或者其他模块,我们想在自己的项目里重写它们的Babel配置变的十分困难,直接去修改该模块的.babelrc
显然是不友好的。
所以Babel7.x引入了babel.config.js
,和.babelrc
查找机制不同的地方在于,Babel对于这个文件会一直向上查找,查到一个根目录(后面对这个目录会做讲解),然后读取该目录下的babel.config.js
,最后合并以及重写配置。这个根目录,可以理解为是当前工作的目录,但是如果babel.config.js
被放在了如下目录,将会被直接忽略:
babel.config.js
packages/
mod1/
package.json
src/index.js
mod2/
package.json
src/index.js
必须放在packages目录下才有效。当然也可以通过设置"configFile"
来指定配置文件的位置,最终也可以被Babel读取到。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.