日记

日记

  • Diary
  • GitHub

›All Blog Posts

All Blog Posts

  • 再看高程3
  • 面试
  • 个人简历
  • react的高性能
  • webpack源码学习
  • 树莓派
  • 布局
  • 模块化
  • 攻击与安全
  • 异步
  • TODO
  • React状态管理比较
  • 性能优化
  • HTTP缓存
  • (译)JS中数据的改变与发现
  • CSS杂记
  • XML与SVG
  • 个人简历
  • Workspaces是什么
  • NPM包测试之低高级策略
  • 谈谈version
  • 减肥之道
  • 问题汇总
  • 一个nginx配置
  • 好用的工具
  • 好用的npm包
  • 流行框架学习对比

TODO

June 25, 2018

Shiyong Yin

  • 原型
  • 异步
  1. flex
  2. throttle
  3. bind
  4. arguments
  5. []==false
  6. 动态规划
  7. 异步
  8. let =>

回答的不好的

  • https
  • csrf
  • vue react diff
  • amd cmd

React状态管理比较

June 24, 2018

hd

性能优化

June 23, 2018

Shiyong Yin

优化方法

HTTP缓存

June 23, 2018

Shiyong Yin

HTTP caching

缓存的种类有很多,大致归为两类:私有与共享缓存。共享缓存能够被多个用户使用。本文主要介绍浏览器与代理缓存,除此之外还有网关 缓存,CDN,反向代理缓存,负载均衡器等部署在服务器上,为站点和web应用提供更好的稳定性、性能和扩展性。

缓存控制

cache-control

HTTP/1.1定义的Cache-Control投用来区分对缓存机制的支持情况,请求头和响应头都支持这个属性。 通过它来定义缓存策略。

  • 禁止进行缓存

    Cache-Control: no-store
    
  • 强制确认缓存

    Cache-Control: no-cache
    
  • 私有和公共缓存

    Cache-Control: private, public
    
  • 缓存过期机制

    max-age=<seconds>相比Expires而言,max-age是距离请求发起的时间的秒数。针对应用中那些不会改变的文件 通常可以手动设置一定的时长以保证缓存有效,如图片、css、js等静态资源

    Cache-Control: max-age=387488400
    
  • 缓存验证确认

    这就意味着在考虑使用一个陈旧的资源时,必须先验证她的状态,已过期的缓存将不被使用。

    Cache-Control: must-revalidate
    

    Pragma头

    Pragma是HTTP/1.0标准中定义的一个header属性,只能用在头中。作用和cache-control:no-cache相同

新鲜度

缓存驱逐

理论上来讲,当一个资源被缓存存储后,改资源可以被永久存储在缓存中。由于缓存只有有限空间,所以将定期删除 以下副本,这个过程叫做缓存驱逐。

由于HTTP是C/S模式的协议,服务器更新一个资源时,不可能直接通知客户端及其缓存,所以双方必须为该资源约定 一个过期时间,在此之前是,该资源是新鲜的,在此之后就是陈旧的。

一个陈旧的资源是不会被直接清楚或忽略的

更新旧资源的过程如下

  • 客户端发起请求,缓存检测到一个对应的旧资源
  • 缓存将此请求附加一个If-None-Match头,发送给服务器
    • 服务器返回304(该响应头不会带有实体信息),表示此资源副本是新鲜的
    • 服务器判断已过期,则返回带有实体内容

client

对于含有特定头部信息的请求,会去计算缓存寿命。通常情况下

  1. 看max-age
  2. 对于不含max-age这个属性的请求则会去查看是否包含Expires属性,通过比较Expires的值和头里面Date属性的值来判断是否缓存还有效
  3. 如果Expires也没有,找着头里的Last-Modifyed信息。如果有,缓存的寿命就等于头里面的Date的值减去Last-Modified的值除以10

加速资源

不频繁跟新的文件会使用特停的命名方式:在URL后面(通常是文件名后面)会加上版本号。加上版本号后的资源被视作一个完全新的 独立的资源,同时拥有一年伸着更长的缓存过期时长。同时更新的时候不会法伤部分缓存先更新而引起新旧文件内容不一致的问题。

缓存验证

用户点击刷新按钮时开始缓存验证。

ETags

作为缓存的一种强校验器,ETags响应头是一个队用户代理不透明的值。如果资源请求的响应头里含有ETag,客户端可以在后续的 请求的头中带上If-None-match头来验证缓存。

Last-Modified响应头可以作为一种弱校验器。因为只能精确到一秒。如果响应头里有这个信息,客户端可以在后续的请求的头部带上 If-Modified-Since来做校验

带Vary头的响应

Vary: User-Agent

当缓存服务器收到一个请求,只有当前的请求和原始的请求头跟缓存的响应头里的Vary都匹配才能使用缓存。

使用Vary头有利于内容服务的动态多样性。如果需要区分移动端和桌面端的展示内容,就可以使用Vary: User-Agent,避免在不同终端展示 错误的布局。

Expires

GMT

响应头包含日期/时间,即在此时候之后,响应过期。

无效的日期,比如0,代表过去的日期,即该资源已过期。

如果在Cache-Control中设置了max-age或者s-max-age指令,Expires会被忽略。

Date

GMT

Date是一个通用首部,其中包含了消息生成的日期和时间。

Last-Modified

GMT

Last-Modified是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。由于精度比ETag要低, 所以这是一个备用机制。包含有If-Modified-Since或If-Unmodifed-Since首部的条件求情会使用这个字段。

ETag

ETag 响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省贷款,因为如果内容没有变,web服务器 不需要发送完整的响应。如果变了,Etag能有防止资源的同时更新相互覆盖("空中碰撞")。

避免"空中碰撞"

ETag与If-Match配合来检测到空中碰撞的编辑冲突

编辑内容是wiki被散列,放入Etag:

Etag: jdoiaj8383838392983ijaojdosdjiojsdij

将更改保存到wiki页面时,POST请求中包含有Etag值得If-Match头来检查是否为最新版本

缓存为更改的资源

原理上面已经讲到

应用缓存appcache

专门为离线应用而设计的缓存

appcache是从浏览器缓存中分出来的一块缓存,要想使用该缓存,使用manifest文件

一个applicationCache对象API applicationCache属性

  • status applicationCache方法
  • checking
  • error
  • noupdate
  • downloading
  • process
  • updateready
  • cached

离线缓存service worker

  • weather pwa

HTTP2

相对于http1.1有以下不同

  • 采用二进制格式
  • 完全多路复用
  • 报头压缩
  • 服务器推送

HTTPS

客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。

  (1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。

  (2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。

  (3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。

  (4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给服务器。

  (5)Web服务器利用自己的私钥解密出会话密钥。

  (6)Web服务器利用会话密钥加密与客户端之间的通信。

web storage

Web Storage API 提供机制, 使浏览器能以一种比使用Cookie更直观的方式存储键/值对。

indexedDB

虽然 Web Storage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案。

IndexedDB 是一种在用户浏览器中持久存储数据的方法。它允许您不考虑网络可用性,创建具有丰富查询能力的可离线 Web 应用程序。

(译)JS中数据的改变与发现

June 23, 2018

Shiyon Yin

原文传送门

比较Angular,Ember,React,Backbone的角度有很多,但是也许比较他们是如何管理状态的角度是最有趣的。

投影数据

我们可以理解为将我们的数据投影到屏幕上的。比如JS中的对象,数组,字符串是源头,HTML中的forms,links, buttons,images等是显示屏幕上的内容。

我们称之为渲染过程。我们可以认为是数据到可视化界面的投影。当我们根据数据渲染模板的时候,我们得到了代表了我们 数据的DOM(HTML)。

onchange_base

平常来说这是没有什么问题的。

假如数据随着时间的改变而改变的话,这就比较有挑战性了。比如用户的操作导致了数的变化,或者什么发生了什么改变了数据。 UI需要体现出这些变化。更重要的是,重新构建DOM是花费昂贵的。我们希望最小化的更新节点

onchange_change

这可比只渲染一次UI困难多了,因为牵涉到了状态的变化。我们就从这里探讨上面框架的解决方案与不同之处。

服务端渲染:重置全局

没有改变,全局不可变

在大JS的时代之前,每一个点击,没一个表单提交,都会将页面unload掉,从后端请求整个渲染的页面,回来之后,再次渲染。 这就是所谓的服务端渲染。

onchange_reload

这种方法前端是不管理任何state的,都由后端处理,前端只是提供下html,css,或许有点点JS。

显而易见这种方法速度很慢

  1. UI需要全部渲染
  2. 需要走后端请求,一去一回要不少时间

第一代JS:手动重新渲染

我不知道哪些应该重新渲染,你来搞明白

总的来说,数据改变,发出事件,重新渲染UI你来决定。

第一代框架如backbone, ext, dojo第一次在浏览器中引入了data model,同时也是第一次 我们需要改变state在浏览器端。data model的内容改变之后需要你来获取改变然后改变UI. 数据改变的时候回触发一些事件,但是重新渲染UI是你的责任。

onchange_manual

到底是渲染一大部分还是渲染一小部分就由你来决定了。灵活性很大,但同时不要忘了性能。

手动重新渲染解决方案

Ember.js:数据绑定

我知道什么变化了,也知道哪里需要重新渲染,因为我控制了model和view

总的来说,通过我设计的API来控制Model

和backbone一样,Ember也会在数据改变的时候发出事件,不同的是,我们把UI绑定到data model上, 也就是说,有一个数据变化的监听器,在监听器里可以和UI做绑定。这个监听器知道在接收到数据变化的时候 如何更新UI。(我们可以通过watch来获得变化,并进行UI的绑定)

onchange_kvo

最大的不好的地方是,Ember必须永远知道数据的变化,这就要求我们使用Ember设计的一套API。

AngularJS:脏检查

我不知道什么发生了改变,所以我就检查下所有地方好了

虽然AngularJS也在着手解决手动重新渲染的问题,但是它是从另一个角度解决问题的。

当我们通过angular模板渲染{{foo.x}}的时候,angular不仅渲染数据,还为这个特殊的值创建了的一个watcher, 从此以后,只要有变化,它就检查watcher中的值是否变了,如果变了就在UI中重新渲染这个值。整个过程就是脏检查。

onchange_watch

不好的地方就是,当改变发生的时候,angular并没有深入的探测到具体是哪个数据发生了改变。所以,只要一有情况发生, 所有的watcher都要跑一遍。

听起来这是个性能的噩梦,但实际上还是挺快的,因为仅仅是纯JS的逻辑执行,没有牵涉到DOM的更新。但是当UI十分庞大的时候, 或者需要频繁渲染的时候,额外的优化技巧就是必须的了。

另外提一下,ES7中的Object.observe对Ember和Angular会十分有帮助,因为这给出了原生的watching在属性的改变上面。

React:VirtualDOM

我不知道发生了哪些改变,所以我就重新渲染下,看看与之前有什么不同

React和Angular类似,不需要data modal API的支持。那么React是如何根据数据的变化来解决UI的更新的呢?

React好像将我们带到了服务端渲染的方式。React的做法是从头到尾的渲染了整个UI。

这听起来是低效的,如果这就是故事的结尾,那确实是。然而,React 用了特殊的方法进行重新渲染。

当React UI进行渲染的时候,它首先渲染到Virtual DOM里面,并不是真正的DOM,而是一个轻量级的,纯粹的对象和数组的 JavaScript数据结构,这个数据结构代表了真实的DOM对象图。然后一个单独的进程采用该DOM结构在屏幕上渲染出真实的DOM结构。

onchange_vdom_initial

当数据改变的时候,一个新的Virtual DOM被从头的创建。新的Virtual DOM中包含了变化的值。React从这两个Virtual DOM执行 diff算法。来获取变化的地方。而且只有那些被改变的地方才会被真实DOM重新渲染。

onchange_vdom_change

Immutable-js

尽管React的Virtual DOM已经很快了,但是在UI很大或者需要频繁渲染的时候还是会出现瓶颈。

问题是真的没有方法能够渲染整个DOM,(UI太大渲染不过来,渲染太快,还没渲染完就需要渲染洗一次了)。除非想Ember一想引入 一套data model API。

一个有效的解决的方法就是使用immutable,这和React的Virtual DOM很般配。

immutable是这么一个原理。正如他的名字一样,你永远不能直接改变一个对象,当时我们可以基于这个对象产生一个新的版本。

使用immutable的意义就是,我们可以重复使用上次Virtual DOM的那些没有改变的Virtual DOM部分。

onchange_immutable

像Ember一样,我们不能使用原生JS对象,必须使用额外的API。但是不同点在于,这次并不是框架的需要,我们使用它因为这是一种 更好的管理state的方法。这不仅提高新性能,而且是一种文化的象征。

总结

对改变的发现是UI渲染的核心问题,JS库通过各种各样的途径来解决这个问题。

EmberJS能够检测到变化,当他们发生的时候,因为EmberJS通过API控制了Model和View,当你调用他们的时候你可以触发事件。

AngularJS在变化之后去检测变化,通过re-running在UI中所有的绑定来看看值是否发生了改变。

纯React通过re-render whole UI到一个Virtual DOM然后和就得版本比较来获得改变。不管发生了什么,都补丁到真实DOM上。

有immutable的React加强了纯React,通过让component快速标记那些没有改变来提升速度,这是性能的选择,同时也是文化的选择。

CSS杂记

June 22, 2018

Shiyong Yin

box-sizing

2个取值(inherit不计算在内)

  • content-box 简单来说,整个元素宽度=width+border+margin+padding,高度类似
  • border-box 简单来说,整个元素宽度=width,width=内容宽度+border+margin+padding

建议将所有元素的border-boxing都设置为border-box

示例

box-sizing: content-box; width: 100%; padding: 0; border-width: 0;
box-sizing: content-box; width: 100%; padding: 0; border-width: 0;
box-sizing: border-box; width: 100%; padding: 0; border-width: 0;

flex布局

.a {
  flex: 1 1 100px;
}

这表示什么意思?

上面的样式等价于

.a {
  flex-grow: 1; //扩展比率
  flex-shrink: 1; //收缩比率
  flex-basic: 100px; //伸缩基准值
}

flex-shrink示例

wrapper的总长度是500,根据基准值算出的总长度是200+200+200=600,显然超出了,所以根据收缩比率进行压缩 (600-500) / (1+2+3) = 16.666,那么green块收缩16.666x1,black块收缩16.666x2,blue块收缩16.666x3 所以最后的宽度green = 200 - 16.666x1, 同理,black,blue类似

<style>
.ysy-f-wrapper {
  display: flex;
  width: 500px;
  height: 50px;
}
.ysy-f-green{
  flex: 1 1 200px;
  background: green;
  height: 50px;
}
.ysy-f-black{
  flex: 2 2 200px;
  background: black;
  height: 50px;
}
.ysy-f-blue{
  flex: 3 3 200px;
  height: 50px;
  background: blue;
}
</style>
<div class="ysy-f-wrapper">
  <div class="ysy-f-green"></div>
  <div class="ysy-f-black"></div>
  <div class="ysy-f-blue"></div>
</div>

flex-grow示例

这个省略了,比flex-shrink好理解

作业

要求如下

  1. 内容居中,左右边距10px
  2. A元素垂直居中
  3. A元的内容垂直居中
A

XML与SVG

June 20, 2018

Shiyong Yin

XML简介

XML被设计用来传输和存储数据

HTML被设计用来显示数据

XMLHttpRequest是XML的衍生体,它不仅能从服务器取回数据,也能读取本地的xml文件

SVG简单示例

SVG的元素和属性必须按标准格式书写,因为XML是区分大小写的(这一点和html不同)

SVG里的属性值必须用引号引起来,就算是数值也必须这样做。

<?xml version="1.0" standalone="no"?>
<svg
  version="1.1"
  baseProfile="full"
  width="300" height="200"
  xmlns="http://www.w3.org/2000/svg">
  <rect width="100%" height="100%" fill="red" />
  <circle cx="150" cy="100" r="80" fill="green" />
  <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>
SVG

HTML中如何引入SVG

  • 如果HTML是XHTML并且声明类型为application/xhtml+xml,可以直接把SVG嵌入到XML源码中
  • 如果HTML是HTML5并且浏览器支持HTML5,同样可以直接嵌入SVG。然而为了符合HTML5标准,可能需要做一些语法调整。
  • 可以通过 object 元素引用SVG文件
  • 类似的也可以使用 iframe 元素引用SVG文件:
  • 理论上同样可以使用 img 元素,但是在低于4.0版本的Firefox 中不起作用。
  • 最后SVG可以通过JavaScript动态创建并注入到HTML DOM中。 这样具有一个优点,可以对浏览器使用替代技术,在不能解析SVG的情况下,可以替换创建的内容。

服务器返回头部信息

  • Content-Type: image/svg+xml

什么是像素

基本上,在 SVG 文档中的1个像素对应输出设备(比如显示屏)上的1个像素。但是这种情况是可以改变的,否则 SVG 的名字里也不至于会有“Scalable”(可缩放)这个∫词

基本图形

<?xml version="1.0" standalone="no"?>
<svg width="200" height="250" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>
  <rect x="60" y="10" rx="10" ry="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>
  <circle cx="25" cy="75" r="20" stroke="red" fill="transparent" stroke-width="5"/>
  <ellipse cx="75" cy="75" rx="20" ry="5" stroke="red" fill="transparent" stroke-width="5"/>
  <line x1="10" x2="50" y1="110" y2="150" stroke="orange" fill="transparent" stroke-width="5"/>
  <polyline points="60 110 65 120 70 115 75 130 80 125 85 140 90 135 95 150 100 145"
      stroke="orange" fill="transparent" stroke-width="5"/>
  <polygon points="50 160 55 180 70 180 60 190 65 205 50 195 35 205 40 190 30 180 45 180"
      stroke="green" fill="transparent" stroke-width="5"/>
  <path d="M20,230 Q40,205 50,230 T90,230" fill="none" stroke="blue" stroke-width="5"/>
</svg>

矩形 rect

  • x
    • 起点x坐标
  • y
    • 起点y坐标
  • rx
    • 圆角x方位的半径
  • ry
    • 圆角方位的半径
  • width
    • x 方向宽度
  • height
    • y 方向高度

圆形 circle

  • cx
    • 圆心x的位置
  • cy
    • 圆心y的位置

椭圆 ellipse

  • rx
    • 椭圆x的半径
  • ry
    • 椭圆y的半径
  • cx
    • 椭圆心x的位置
  • cy
    • 椭圆心y的位置

折现 polyline

  • points
    • 点集数列。每个数字用空白、逗号、终止命令符或者换行符分隔开。每个点必须包含2个数字,一个是x坐标,一个是y坐标。所以点列表 (0,0), (1,1) 和(2,2)可以写成这样:“0 0, 1 1, 2 2”。

多边形 polygon

  • points
    • 点集数列。每个数字用空白符、逗号、终止命令或者换行符分隔开。每个点必须包含2个数字,一个是x坐标,一个是y坐标。所以点列表 (0,0), (1,1) 和(2,2)可以写成这样:“0 0, 1 1, 2 2”。路径绘制完后闭合图形,所以最终的直线将从位置(2,2)连接到位置(0,0)。

路径 path

  • d
    • 属性d的值是一个“命令+参数”的序列

直线路径

直线命令

  • M: Move to 因为M命令仅仅是移动画笔,但不画线。所以M命令经常出现在路径的开始处,用来指明从何处开始画。
  • 能够真正画出线的命令有三个
    • L L需要两个参数,分别是一个点的x轴和y轴坐标
    • H 水平线
    • V 垂直线
  • Z Z命令会从当前点画一条直线到路径的起点
<?xml version="1.0" standalone="no"?>
<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 10 H 90 V 90 H 10 z" stroke-width="5" fill="transparent" stroke="blue"></path>
  <circle cx="10" cy="10" r="2" fill="red"/>
  <circle cx="90" cy="90" r="2" fill="red"/>
  <circle cx="90" cy="10" r="2" fill="red"/>
  <circle cx="10" cy="90" r="2" fill="red"/>
</svg>

曲线路径

绘制平滑曲线的命令有三个,其中两个用来绘制贝塞尔曲线,另外一个用来绘制弧形或者说是圆的一部分

贝塞尔曲线的类型有很多,但是在path元素里,只存在两种贝塞尔曲线:三次贝塞尔曲线C,和二次贝塞尔曲线Q。

贝塞尔曲线 CSQT

4个命令

  • C C(第一个控制点,第二个控制点,终点)
    • 三次贝塞尔曲线需要定义一个点和两个控制点,所以用C命令创建三次贝塞尔曲线,需要设置三组坐标参数
  • S S(第二个控制点,终点)
    • S命令可以用来创建与之前那些曲线一样的贝塞尔曲线
      • 如果S命令跟在一个C命令或者另一个S命令的后面,它的第一个控制点,就会被假设成前一个控制点的对称点
      • 如果S命令单独使用,前面没有C命令或者另一个S命令,那么它的两个控制点就会被假设为同一个点
  • Q Q(控制点,终点)
    • 它比三次贝塞尔曲线简单,只需要一个控制点,用来确定起点和终点的曲线斜率。因此它需要两组参数,控制点和终点坐标。
  • T T(终点)
    • 就像三次贝塞尔曲线有一个S命令,二次贝塞尔曲线有一个差不多的T命令,可以通过更简短的参数,延长二次贝塞尔曲线
C x1 y1, x2 y2, x y (or c dx1 dy1, dx2 dy2, dx dy)

这里的最后一个坐标(x,y)表示的是曲线的终点,另外两个坐标是控制点,(x1,y1)是起点的控制点,(x2,y2)是终点的控制点

C示例

<?xml version="1.0" standalone="no"?>
<svg width="190px" height="160px" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 10 C 20 20, 40 20, 50 10" stroke="black" fill="transparent"/>
  <path d="M70 10 C 70 20, 120 20, 120 10" stroke="black" fill="transparent"/>
  <path d="M130 10 C 120 20, 180 20, 170 10" stroke="black" fill="transparent"/>
  <path d="M10 60 C 20 80, 40 80, 50 60" stroke="black" fill="transparent"/>
  <path d="M70 60 C 70 80, 110 80, 110 60" stroke="black" fill="transparent"/>
  <path d="M130 60 C 120 80, 180 80, 170 60" stroke="black" fill="transparent"/>
  <path d="M10 110 C 20 140, 40 140, 50 110" stroke="black" fill="transparent"/>
  <path d="M70 110 C 70 140, 110 140, 110 110" stroke="black" fill="transparent"/>
  <path d="M130 110 C 120 140, 180 140, 170 110" stroke="black" fill="transparent"/>
</svg>

S示例

<?xml version="1.0" standalone="no"?>
<svg width="190px" height="160px" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80" stroke="black" fill="transparent"/>
  <circle cx="10" cy="80" r="2" fill="red"/>
  <circle cx="40" cy="10" r="2" fill="red"/>
  <path d="M 10 80 L 40 10" stroke="red"/>
  <circle cx="65" cy="10" r="2" fill="red"/>
  <circle cx="95" cy="80" r="2" fill="red"/>
  <path d="M 65 10 L 95 80" stroke="red"/>
  <circle cx="150" cy="150" r="2" fill="red"/>
  <circle cx="125" cy="150" r="2" fill="red"/>
  <path d="M 95 80 L 125 150" stroke="blue"/>
  <path d="M 150 150 L 180 80" stroke="blue"/>
</svg>
<?xml version="1.0" standalone="no"?>
<svg width="190px" height="160px" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 80 S 40 10, 95 80" stroke="black" fill="transparent"/>
  <circle cx="10" cy="80" r="2" fill="red"/>
  <circle cx="40" cy="10" r="2" fill="red"/>
  <circle cx="95" cy="80" r="2" fill="red"/>
  <path d="M10 80 L 40 10" stroke="red"/>
  <path d="M95 80 L 40 10" stroke="red"/>
</svg>

相同的起始点和终点,相同的控制点,在三次和二次赛贝尔曲线中勾画出来的图形不一样

Q示例

<?xml version="1.0" standalone="no"?>
<svg width="190px" height="160px" version="1.1" xmlns="http://www.w3.org/2000/svg"">
  <path d="M10 80 Q 95 10 180 80" stroke="black" fill="transparent"/>
</svg>

T示例

<?xml version="1.0" standalone="no"?>
<svg width="190px" height="160px" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 80 Q 52.5 10, 95 80 T 180 80" stroke="black" fill="transparent"/>
</svg>

弧形 A

已知椭圆形的长轴半径和短轴半径,并且已知两个点(在椭圆上),根据半径和两点,可以画出两个椭圆,在每个椭圆上根据两点都可以画出两种弧形。所以,仅仅根据半径和两点,可以画出四种弧形。为了保证创建的弧形唯一,A命令需要用到比较多的参数

A rx ry x-axis-rotation large-arc-flag sweep-flag x y
a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy

参数解释

  • rx x半径
  • ry y半径
  • x-axis-rotation 弧形旋转情况(如下图第一个弧形的椭圆旋转0,第二个旋转-45)
  • large-arc-flag 角度大小
  • sweep-falg 弧线方向
  • x 弧形起点
  • y 弧形终点
<?xml version="1.0" standalone="no"?>
<svg width="320px" height="320px" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 315
           L 110 215
           A 30 50 0 0 1 162.55 162.45
           L 172.55 152.45
           A 30 50 -45 0 1 215.1 109.9
           L 315 10" stroke="black" fill="green" stroke-width="2" fill-opacity="0.5"/>
</svg>
<?xml version="1.0" standalone="no"?>
<svg width="325px" height="325px" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <path d="M80 80
           A 45 45, 0, 0, 0, 125 125
           L 125 80 Z" fill="green"/>
  <path d="M230 80
           A 45 45, 0, 1, 0, 275 125
           L 275 80 Z" fill="red"/>
  <path d="M80 230
           A 45 45, 0, 0, 1, 125 275
           L 125 230 Z" fill="purple"/>
  <path d="M230 230
           A 45 45, 0, 1, 1, 275 275
           L 275 230 Z" fill="blue"/>
</svg>

Fill和Stroke属性

fill属性设置对象内部的颜色

stroke属性设置绘制对象的线条的颜色

上色 fill stroke

<svg>
<rect x="10" y="10" width="100" height="100" stroke="blue" fill="purple"
       fill-opacity="0.5" stroke-opacity="0.8"/>
</svg>

描边 stroke-line(cap|join|dasharray)

除了颜色属性,还有其他一些属性用来控制绘制描边的方式

  • stroke-linecap 描边是以路径为中心线绘制的
    • butt 用直边结束线段
    • square 稍微超出实际路径的范围,超出的大小由stroke-width控制
    • round 示边框的终点是圆角,圆角的半径也是由stroke-width控制的
  • stroke-linejoin 每条折线都是由两个线段连接起来的,连接处的样式由stroke-linejoin属性控制
    • miter 尖角
    • round 圆角
    • bevel 平滑
  • stroke-linedasharray 将虚线类型应用在描边上
    • stroke-dasharray属性的参数,是一组用逗号分割的数字组成的数列
    • 注意,和path不一样,这里的数字必须用逗号分割(空格会被忽略)
    • 每一组数字,第一个用来表示填色区域的长度,第二个用来表示非填色区域的长度。

stroke-linecap示例

<?xml version="1.0" standalone="no"?>
<svg width="160" height="140" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <line x1="40" x2="120" y1="20" y2="20" stroke="black" stroke-width="20" stroke-linecap="butt"/>
  <line x1="40" x2="120" y1="60" y2="60" stroke="black" stroke-width="20" stroke-linecap="square"/>
  <line x1="40" x2="120" y1="100" y2="100" stroke="black" stroke-width="20" stroke-linecap="round"/>
</svg>

stroke-linejoin示例

<?xml version="1.0" standalone="no"?>
<svg width="160" height="280" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <polyline points="40 60 80 20 120 60" stroke="black" stroke-width="20"
      stroke-linecap="butt" fill="none" stroke-linejoin="miter"/>
  <polyline points="40 140 80 100 120 140" stroke="black" stroke-width="20"
      stroke-linecap="round" fill="none" stroke-linejoin="round"/>
  <polyline points="40 220 80 180 120 220" stroke="black" stroke-width="20"
      stroke-linecap="square" fill="none" stroke-linejoin="bevel"/>
</svg>

stroke-linedasharray示例

<?xml version="1.0" standalone="no"?>
<svg width="200" height="120" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <path d="M 10 75 Q 50 10 100 75 T 190 75" stroke="black"
    stroke-linecap="round" stroke-dasharray="5,15,5" fill="none"/>
  <path d="M 10 75 L 190 75" stroke="red"
    stroke-linecap="round" stroke-width="1" stroke-dasharray="1,2,3,4,5" fill="none"/>
</svg>

解释说明

  • 第一个例子,该路径首先渲染5个填色单位,10个空白单位,5个填色单位,然后回头以这3个数字做一次循环,但是这次是创建5个空白单位,10个填色单位,5个空白单位。通过这两次循环得到偶数模式,并将这个偶数模式不断重复。
  • 第二个例子,先做5个像素单位的填色,紧接着是5个空白单位,然后又是5个单位的填色。如果你想要更复杂的虚线模式,你可以定义更多的数字

使用CSS

除了定义对象的属性外,你也可以通过CSS来样式化填充和描边。语法和在html里使用CSS一样,只不过你要把background-color、border改成fill和stroke。

注意,不是所有的属性都能用CSS来设置。上色和填充的部分一般是可以用CSS来设置的,比如fill,stroke,stroke-dasharray等,但是不包括下面会提到的渐变和图案等功能。另外,width、height,以及路径的命令等等,都不能用css设置。判断它们能不能用CSS设置还是比较容易的。

SVG规范将属性区分成properties和其他attributes,前者是可以用CSS设置的,后者不能。

3种CSS用法

  1. CSS可以利用style属性插入到元素的行间

    <svg>
    <rect x="10" height="180" y="10" width="180" style="stroke: black; fill: red;"/>
    </svg>
    

  2. 利用<style>设置一段样式段落。就像在<html>里这样的<style>一般放在<head>里,在svg里<style>则放在<defs>标签里。<defs>表示定义,这里面可以定义一些不会在SVG图形中出现、但是可以被其他元素使用的元素。同样可以使用hover

    <?xml version="1.0" standalone="no"?>
    <svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
      <defs>
        <style type="text/css"><![CDATA[
          #MyRect {
            stroke: black;
            fill: red;
          }
        ]]></style>
      </defs>
      <rect x="10" height="180" y="10" width="180" id="MyRect"/>
    </svg>
    

  3. 你也可以定义一个外部的样式表

    <?xml version="1.0" standalone="no"?>
    <?xml-stylesheet type="text/css" href="style.css"?>
    
    <svg width="200" height="50" xmlns="http://www.w3.org/2000/svg" version="1.1">
      <rect height="10" width="10" id="MyRect2"/>
    </svg>
    

渐变

并非只能简单填充颜色和描边,更令人兴奋的是,你还可以创建和并在填充和描边上应用渐变色。

有两种类型的渐变:线性渐变和径向渐变。你必须给渐变内容指定一个id属性,否则文档内的其他元素就不能引用它。为了让渐变能被重复使用,渐变内容需要定义在<defs>标签内部,而不是定义在形状上面

线性渐变

线性渐变沿着直线改变颜色,要插入一个线性渐变,你需要在SVG文件的<defs>元素内部,创建一个<linearGradient> 节点。

<?xml version="1.0" standalone="no"?>
<svg width="120" height="240" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <defs>
      <linearGradient id="Gradient1">
        <stop class="stop1" offset="0%"/>
        <stop class="stop2" offset="50%"/>
        <stop class="stop3" offset="100%"/>
      </linearGradient>
      <linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="1">
        <stop offset="0%" stop-color="red"/>
        <stop offset="50%" stop-color="black" stop-opacity="0"/>
        <stop offset="100%" stop-color="blue"/>
      </linearGradient>
      <style type="text/css"><![CDATA[
        #rect1 { fill: url(#Gradient1); }
        .stop1 { stop-color: red; }
        .stop2 { stop-color: black; stop-opacity: 0; }
        .stop3 { stop-color: blue; }
      ]]></style>
  </defs>
  <rect id="rect1" x="10" y="10" rx="15" ry="15" width="100" height="100"/>
  <rect x="10" y="120" rx="15" ry="15" width="100" height="100" fill="url(#Gradient2)"/>
</svg>

以上是一个应用了线性渐变的<rect>元素的示例。线性渐变内部有几个<stop> 结点,这些结点通过指定位置的offset(偏移)属性和stop-color(颜色中值)属性来说明在渐变的特定位置上应该是什么颜色;可以直接指定这两个属性值,也可以通过CSS来指定他们的值,该例子中混合使用了这两种方法.

使用渐变时,我们需要在一个对象的属性fill或属性stroke中引用它,这跟你在CSS中使用url引用元素的方法一样。在本例中,url只是一个渐变的引用,我们已经给这个渐变一个ID——“Gradient”。要想附加它,将属性fill设置为url(#Gradient)即可。现在对象就变成多色的了,也可以用同样的方式处理stroke。

<linearGradient>元素还需要一些其他的属性值,它们指定了渐变的大小和出现范围。渐变的方向可以通过两个点来控制,它们分别是属性x1、x2、y1和y2,这些属性定义了渐变路线走向。渐变色默认是水平方向的,但是通过修改这些属性,就可以旋转该方向。下例中的Gradient2创建了一个垂直渐变。

注意: 你也可以在渐变上使用xlink:href属性。如果使用了该属性时,一个渐变的属性和颜色中值(stop)可以被另一个渐变包含引用。在下例中,你就不需要再Grandient2中重新创建全部的颜色中值(stop)。

<linearGradient id="Gradient1">
   <stop id="stop1" offset="0%"/>
   <stop id="stop2" offset="50%"/>
   <stop id="stop3" offset="100%"/>
 </linearGradient>
 <linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="1"
    xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#Gradient1"/>

径向渐变

径向渐变与线性渐变相似,只是它是从一个点开始发散绘制渐变。创建径向渐变需要在文档的defs中添加一个<radialGradient>元素.

stops的使用方法与之前一致,但是现在这个对象的颜色是中间是红色的,且向着边缘的方向渐渐的变成蓝色。跟线性渐变一样,<radialGradient> 节点可以有多个属性来描述其位置和方向,但是它更加复杂。

cx、cy、fx、fy和r的取值范围0-1,也就是宽度和高度的比率,参考gradientUnits

cx,cy表示渐变的中心点,r表示渐变的半径,这三者构成了渐变区域。fx,fy表示渐变区域的焦点。

  • 若无fx,fy属性,则fx,fy和cx,cy重叠
  • 若有fx,fy属性,fx,fy的取值点不能超出渐变区域,否则无法渐变。

示例

<?xml version="1.0" standalone="no"?>
<svg width="120" height="240" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <defs>
      <radialGradient id="RadialGradient1">
        <stop offset="0%" stop-color="red"/>
        <stop offset="100%" stop-color="blue"/>
      </radialGradient>
      <radialGradient id="RadialGradient2" cx="0.25" cy="0.25" r="0.25">
        <stop offset="0%" stop-color="red"/>
        <stop offset="100%" stop-color="blue"/>
      </radialGradient>
  </defs>
  <rect x="10" y="10" rx="15" ry="15" width="100" height="100" fill="url(#RadialGradient1)"/> 
  <rect x="10" y="120" rx="15" ry="15" width="100" height="100" fill="url(#RadialGradient2)"/> 
</svg>

中心和焦点

<?xml version="1.0" standalone="no"?>
<svg width="120" height="120" version="1.1"
  xmlns="http://www.w3.org/2000/svg">
  <defs>
      <radialGradient id="Gradient"
            cx="0.5" cy="0.5" r="0.5" fx="0.25" fy="0.25">
        <stop offset="0%" stop-color="red"/>
        <stop offset="100%" stop-color="blue"/>
      </radialGradient>
  </defs>
  <rect x="10" y="10" rx="15" ry="15" width="100" height="100"
        fill="url(#Gradient)" stroke="black" stroke-width="2"/>
  <circle cx="60" cy="60" r="50" fill="transparent" stroke="white" stroke-width="2"/>
  <circle cx="35" cy="35" r="2" fill="white" stroke="white"/>
  <circle cx="60" cy="60" r="2" fill="white" stroke="white"/>
  <text x="38" y="40" fill="white" font-family="sans-serif" font-size="10pt">(fx,fy)</text>
  <text x="63" y="63" fill="white" font-family="sans-serif" font-size="10pt">(cx,cy)</text>
</svg>
(fx,fy) (cx,cy)

spreadMethod

线性渐变和径向渐变都需要一些额外的属性用于描述渐变过程,这里我希望额外提及一个spreadMethod属性,该属性控制了当渐变到达终点的行为,但是此时该对象尚未被填充颜色.

  • pad 就是我们之前看到的,即当渐变到达终点时,最终的偏移颜色被用于填充对象剩下的空间
  • reflect reflect会让渐变一直持续下去,不过它的效果是与渐变本身是相反的,以100%偏移位置的颜色开始,逐渐偏移到0%位置的颜色,然后再回到100%偏移位置的颜色
  • repeat repeat也会让渐变继续,但是它不会像reflect那样反向渐变,而是跳回到最初的颜色然后继续渐变。
<?xml version="1.0" standalone="no"?>
<svg width="220" height="220" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <defs>
      <radialGradient id="GradientPad"
            cx="0.5" cy="0.5" r="0.4" fx="0.75" fy="0.75"
            spreadMethod="pad">
        <stop offset="0%" stop-color="red"/>
        <stop offset="100%" stop-color="blue"/>
      </radialGradient>
      <radialGradient id="GradientReflect"
            cx="0.5" cy="0.5" r="0.4" fx="0.75" fy="0.75"
            spreadMethod="reflect">
        <stop offset="0%" stop-color="red"/>
        <stop offset="100%" stop-color="blue"/>
      </radialGradient>
      <radialGradient id="GradientRepeat"
            cx="0.5" cy="0.5" r="0.4" fx="0.75" fy="0.75"
            spreadMethod="repeat">
        <stop offset="0%" stop-color="red"/>
        <stop offset="100%" stop-color="blue"/>
      </radialGradient>
      <radialGradient id="GradientReflect"
            cx="0.5" cy="0.5" r="0.4" fx="0.75" fy="0.75"
            spreadMethod="reflect">
        <stop offset="0%" stop-color="red"/>
        <stop offset="100%" stop-color="blue"/>
      </radialGradient>
  </defs>
  <rect x="10" y="10" rx="15" ry="15" width="100" height="100" fill="url(#GradientPad)"/>
  <rect x="10" y="120" rx="15" ry="15" width="100" height="100" fill="url(#GradientRepeat)"/>
  <rect x="120" y="120" rx="15" ry="15" width="100" height="100" fill="url(#GradientReflect)"/>
  <text x="15" y="30" fill="white" font-family="sans-serif" font-size="12pt">Pad</text>
  <text x="15" y="140" fill="white" font-family="sans-serif" font-size="12pt">Repeat</text>
  <text x="125" y="140" fill="white" font-family="sans-serif" font-size="12pt">Reflect</text>
</svg>

Pad Reflect Repeat

gradientUnits

该属性有两个值,默认值为objectBoundingBox

  • objectBoundingBox 我们目前看到的效果都是在这种系统下的,它大体上定义了对象的渐变大小范围,所以你只要指定从0到1的坐标值,渐变就会自动的缩放到对象相同大小
  • userSpaceOnUse 使用绝对单元,所以你必须知道对象的位置,并将渐变放在同样地位置上。上例中的radialGradient需要被重写成

图案

在我看来patterns(图案)是SVG中用到的最让人混淆的填充类型之一。它的功能非常强大,所以我认为他们值得讨论一下并且我们应至少对他们有最基本的了解。跟渐变一样,<pattern>需要放在SVG文档的<defs>内部。

<?xml version="1.0" standalone="no"?>
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <linearGradient id="Gradient1">
      <stop offset="5%" stop-color="white"/>
      <stop offset="95%" stop-color="blue"/>
    </linearGradient>
    <linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="1">
      <stop offset="5%" stop-color="red"/>
      <stop offset="95%" stop-color="orange"/>
    </linearGradient>
    <pattern id="Pattern" x="0" y="0" width=".25" height=".25">
      <rect x="0" y="0" width="50" height="50" fill="skyblue"/>
      <rect x="0" y="0" width="25" height="25" fill="url(#Gradient2)"/>
      <circle cx="25" cy="25" r="20" fill="url(#Gradient1)" fill-opacity="0.5"/>
    </pattern>
  </defs>
  <rect fill="url(#Pattern)" stroke="black" x="0" y="0" width="200" height="200"/>
</svg>

<?xml version="1.0" standalone="no"?>
<svg width="300" height="300" xmlns="http://www.w5.org/2000/svg/" version="1.1">
  <defs>
    <linearGradient id="Gradient111">
      <stop offset="5%" stop-color="white"/>
      <stop offset="95%" stop-color="blue"/>
    </linearGradient>
    <linearGradient id="Gradient222" x1="0" x2="0" y1="0" y2="1">
      <stop offset="5%" stop-color="red"/>
      <stop offset="95%" stop-color="orange"/>
    </linearGradient>
    <pattern id="Pattern12" x="0" y="0" width=".25" height=".25">
      <rect x="0" y="0" width="50" height="50" fill="skyblue"/>
      <rect x="0" y="0" width="25" height="25" fill="url(#Gradient222)"/>
      <circle cx="25" cy="25" r="20" fill="url(#Gradient111)" fill-opacity="0.5"/>
    </pattern>
  </defs>
  <rect fill="url(#Pattern12)" stroke="black" x="0" y="0" width="300" height="300"/>
</svg>

改变对象大小,并不能自适应

patternUnits

  • objectBoundingBox 默认
  • userSpaceOnUse

patternContentUnits

  • userSpaceOnUse 默认
  • objectBoundingBox

这意味着除非你至少指定其中一个属性值(patternContentUnits或patternUnits),否则在pattern中绘制的形状将与pattern元素使用的坐标系不同.

如果对象改变了大小,pattern会自适应其大小,但是对象里面的内容不会自适应。如上图对象rect的大小增大了,内容并没有增大

<?xml version="1.0" standalone="no"?>
<svg width="300" height="300" xmlns="http://www.w6.org/2000/svg/" version="1.1">
  <defs>
    <linearGradient id="Gradient11">
      <stop offset="5%" stop-color="white"/>
      <stop offset="95%" stop-color="blue"/>
    </linearGradient>
    <linearGradient id="Gradient22" x1="0" x2="0" y1="0" y2="1">
      <stop offset="5%" stop-color="red"/>
      <stop offset="95%" stop-color="orange"/>
    </linearGradient>
    <pattern id="Pattern123" width=".25" height=".25" patternContentUnits="objectBoundingBox">
      <rect x="0" y="0" width=".25" height=".25" fill="skyblue"/>
      <rect x="0" y="0" width=".125" height=".125" fill="url(#Gradient22)"/>
      <circle cx=".125" cy=".125" r=".1" fill="url(#Gradient11)" fill-opacity="0.5"/>
    </pattern>
  </defs>
  <rect fill="url(#Pattern123)" stroke="black" x="0" y="0" width="300" height="300"/>
</svg>

设置了patternContentUnits="objectBoundingBox",并改变了取值单位后,就可以自适应了

文本

在一个SVG文档中,元素内部可以放任何的文字。

<?xml version="1.0" standalone="no"?>
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <text x="10" y="10">Hello World!</text>
  <text>
  <tspan font-weight="bold" fill="red">This is bold and red</tspan>
</text>
</svg>

Hello World! This is bold and red

个人简历

June 16, 2018

尹士勇

尹士勇

求职意向:前端工程师

电话:(0)188-1096-1061
性别:男
邮箱:braveyin168@outlook.com
地址:成都市天府新区

教育背景

2010.09~2014.07
西南科技大学
计算机科学与技术/本科

工作经历

2014.07~2018.07
去哪儿·大住宿
前端工程师
北京

去哪儿是中国领先的旅游搜索引擎,去哪儿是目前全球最大的中文在线旅行网站

业务上

  • 参与5个web网站、1个公众号、1个H5和2个RN项目的开发,目前为前端负责人
  • 酒店业务华为对接,协调团结10多个小组近30人,推动项目的发展,按时交付

技术上

  • 设计开发任务模板系统,提高运营效率100%,省50%成本,获年度优秀项目奖(10,000元)
  • 设计开发npm包q-antd,提高项目开发效率30%,提高项目易维护性30%
  • 优化去哪儿前端构建工具packing,新建页面错误率降为0,新建页面节省50%时间
  • 设计开发grunt-i19n插件,准确率提高到100%,节省50%工作时间
2018.07~2018.11
货车帮·大客户
前端工程师
成都

去哪儿是中国领先的旅游搜索引擎,去哪儿是目前全球最大的中文在线旅行网站 业务上

  • 参与5个web网站、1个公众号、1个H5和2个RN项目的开发

  • 酒店业务华为对接,协调团结10多个小组近30人,推动项目的发展,按时交付

技术上

  • 设计开发任务模板系统,提高运营效率100%,省50%成本,获年度优秀项目奖(10,000元)

  • 设计开发npm包q-antd,提高项目开发效率30%,提高项目易维护性30%

  • 优化去哪儿前端构建工具packing,新建页面错误率降为0,新建页面节省50%时间

  • 设计开发grunt-i19n插件,准确率提高到100%,节省50%工作时间

爱折腾,知识面广,吸收了travis和jest等使得开发效率、正确率和规范化大大提高

工作技能

  • 掌握JavaScript、Vue、React、React Native
  • 熟悉github、travis、jest、enzyme、eslint、prettier
  • 熟悉node、socket.io、packing、gulp、grunt、webpack、bash、python、django
  • 熟悉mobx、rxjs、redux
  • 熟悉iview、antd
  • 熟悉ajax、CORS、http、websocket
  • 熟悉jquery、html、pug、css

自我评价

喜欢逛github,研究优秀的项目,从中学习新知识和牛逼的地方

Workspaces是什么

June 14, 2018

Shiyong Yin

感觉vue-cli写得相当不错,想瞅瞅源码,于是就进入package.json中找找main字段吧,因为main 字段就是工程的入口文件嘛。

上下看了三遍,卧槽,没有!

好吧,main字段没有,bin总该有吧。

上下又看了三遍,卧槽,还是没有,唯一可疑的发现了个workspaces字段。

寻找

你牛B,那就去npm官网瞅瞅workspaces字段是什么意思吧。

卧槽,无此字段的介绍。瞬间一脸的懵逼。

后来各种搜索why package.json no main keyword,未找到答案

结果

最后尝试了下package.json workspaces关键词的搜索,找到了yarn的一篇文章(原文传送门),柳暗花明又一村了。

大概意思是,随着时间的推移我们的项目会变得越来越大,模块越来越多。这时候有两种解决方案。

  1. 将子模块放到其他repo中来进行管理,但是你会发现负担很重,需要多个地方写代码,多个地方处理issue等
  2. 将所有模块进行拆分都放到同一个repo钟来管理

Monorepos

放在同一个repo中来管理的时候通常使用Monorepos的方式来管理此repo,使用这种方式的有很多我们经常使用 的js包,比如Babel, React, Vue, Angular, Jest等

然而,仅仅进行拆分并使用Monorepos管理远远是不够的,比如测试,管理依赖,发布多个包等变得复杂起来。 这是一些工具比如Lerna就派上了用场

Lerna

Lerna是一个对多个子模块工程管理进行优化的工具。简而言之,Lerna会安装在工程中的每个package,并在 相互依赖的package之间创建软链接。

然而,作为一个包管理的装饰器,Lerna不能有效的控制node_module中的内容。 比如多个子模块都依赖同一个包。Lerna会安装多次而不是一次。

Workspaces

Workspaces能够让用户根据根package.json中的workspace字段从多个子文件中的package.json中安装以依赖。

例如Jest的例子

工程目录结构如下

| jest/
| ---- package.json //根package.json
| ---- packages/
| -------- jest-matcher-utils/
| ------------ package.json //子package.json
| -------- jest-diff/
| ------------ package.json
...

根package.json内容如下

{
  "private": true,
  "name": "jest",
  "devDependencies": {
    "chalk": "^2.0.1"
  },
  "workspaces": [
    "packages/*"
  ]
}

两个字模块中的package.json如下

  1. jest-matcher-utils
    {
      "name": "jest-matcher-utils",
      "description": "...",
      "version": "20.0.3",
      "license": "...",
      "main": "...",
      "browser": "...",
      "dependencies": {
        "chalk": "^1.1.3",
        "pretty-format": "^20.0.3"
      }
    }
    
  2. jest-diff (依赖了jest-matcher-utils)
    {
      "name": "jest-diff",
      "version": "20.0.3",
      "license": "...",
      "main": "...",
      "browser": "...",
      "dependencies": {
        "chalk": "^1.1.3",
        "diff": "^3.2.0",
        "jest-matcher-utils": "^20.0.3",
        "pretty-format": "^20.0.3"
      }
    }
    

使用Lerno进行安装结果

| jest/
| ---- node_modules/
| -------- chalk/
| ---- package.json
| ---- packages/
| -------- jest-matcher-utils/
| ------------ node_modules/
| ---------------- chalk/
| ---------------- pretty-format/
| ------------ package.json
| -------- jest-diff/
| ------------ node_modules/
| ---------------- chalk/
| ---------------- diff/
| ---------------- jest-matcher-utils/  (symlink) -> ../jest-matcher-utils
| ---------------- pretty-format/
| ------------ package.json
...

我们可以看到jest-matcher-utils的冗余

使用workpaces进行安装

| jest/
| ---- node_modules/
| -------- chalk/
| -------- diff/
| -------- pretty-format/
| -------- jest-matcher-utils/  (symlink) -> ../packages/jest-matcher-utils
| ---- package.json
| ---- packages/
| -------- jest-matcher-utils/
| ------------ node_modules/
| ---------------- chalk/
| ------------ package.json
| -------- jest-diff/
| ------------ node_modules/
| ---------------- chalk/
| ------------ package.json
...

我们看到只有一个jest-matcher-utils

管理workspaces中的依赖

仅仅使用yarn add等命令在workspaces文件家中进行管理即可。

$ cd packages/jest-matcher-utils/
$ yarn add left-pad
✨ Done in 1.77s.
$ git status
modified: package.json
modified: ../../yarn.lock

但是注意,workspaces中无yarn.lock,只在root中含有一个yarn.lock

整合workspaces和Lerna

workspaces并没有使Lerna过时

Lerna有他的长处和优点,可以和workspaces共存

Jest使用yarn来启动工程,使用Lerna来发布。

NPM包测试之低高级策略

June 12, 2018

Shiyong Yin

以下操作是基于yarn的,npm同样有效

2018.06.17更

原文问题

在之前介绍的三个方法中,虽然第三个方法看上去很完美。但是依然有一个问题。会出现multi package的问题,这是一个什么问题呢?比如我们的实际工程merchant_fe中引入了react,我们引入的q-antd(q-antd是被测试的包)中也引入了react。那么这时就需要在merchant_fe和q-antd都引入react,他们并没有引用同一个react包。

高级策略——jest

那么有没更好的方法来解决测试的问题?我们是不是可以换一种思路来解决问题,之前都是通过装包的方式来进行测试。 我们可否不装包就行测试,而是直接对包进行测试呢?

左思右想了下,之前了解过jest,不就是可以用在这种情况下。

于是,引入jest进行尝试。果不其然,完全可以。这才是真正的正解吧,只要我的包通过了各种姿势的测试,就不怕安装的包有问题。

每次发包真的烦

开发一个npm包,偶尔或许经常需要优化下,每次都发包,然后安包,最后再测试,肯定太繁琐了。还有可能遇到愚蠢的错误,比如变量 未定义变量等。

所以,在发包前进行简单或者完整的测试是有必要的,这样也不至于导致我们的包的版本非常多。

方法一、使用路径引用包

  • 在修改测试包之后是否能够更新的到:能

比如要测试q-antd

import { Form } from 'absoulte_path|relative_path/to/q-antd'

这个方法最简单,适用于有个专门的测试工程来做测试这件事情.在实际工程中我们往往是这样引用的

import { Form } from 'q-antd'

每次测试完还需要改回来,太愚蠢了。

所以,不建议此方法。

方法二、使用pack

  • 在修改测试包之后是否能够更新的到:不能,需要再次pack

具体步骤如下

#!/usr/local/env bash
cd path/to/q-antd
yarn pack
cd path/to/测试工程
yarn add absolute_path|realtive_path/to/q-antd/q-antd-version.tgz

yarn pack会将q-antd包生成一个tgz压缩包,然后就可以通过yarn来安装了

此方法其实和发布npm类似,没死修改之后都需要执行上面的至少两个步骤。而且每次pack的版本不能相同,否则,新增的内容不会 被引用到

总之,此方法也不行,太麻烦

方法三、完美大招——使用link

  • 在修改测试包之后是否能够更新的到:能

具体步骤如下

#!/usr/local/env bash
cd path/to/q-antd
yarn link
cd path/to/测试工程
yarn link q-antd

yarn link会将q-antd放到node的全局变量里面,所以在目标工程里直接yarn link q-antd就可以了。

link一次,使用永久

此乃最佳方法

← PrevNext →
Copyright © 2018-2019 Shiyong Yin