通过webpack对css压缩后所引起的重名问题

起因

我们维护了两个组件库,一套用于PC,另一套用于Mobile。有一个使用方同时引入了这两个库,这时候突然发现了一个诡异的现象:

一个tag组件凭空旋转起来,事实上没有给这个tag添加任何动画效果

发现

直接从页面表现来看,很难发现问题所在。我们进行了以下步骤进行排查

  1. 通过chrome的animate面板,看执行的是什么动画。发现其动画名为a
  2. 通过vue控制台/源码,发现其使用了vuetransition组件,并且其transition使用了PC组件库提供的fade动画。

分析

为什么fade动画会变成了旋转?我们在编译的css文件中寻找tanslate@keyframes关键词,发现

@keyframes a {
    translate:rotate(1turn)
}
@keyframes a {
    0% {
        opacity:0;
    }
    100% {
        opacity:1;
    }
}

有2个名为a的动画,那么问题就清楚了,是两个动画冲突了。但是怎么会都叫a的呢。我们不太可能这么随意的起名。源码中也没有找到名为a的动画。那么有一种可能,就是在打包的时候被改名了。

找到Vue本身提供的webpack.prod.conf.js,发现里面是这样的:

new OptimizeCSSPlugin({
  cssProcessorOptions: config.build.productionSourceMap
    ? { safe: true, map: { inline: false } }
    : { safe: true }
})

而我们用来发布包的webpack.pack.conf.js里面是这样的:

new OptimizeCSSPlugin()

说明pack脚本是按照最大压缩方式进行压缩的,会对css进行改名,如果两个库都采用同样的方式进行压缩,自然会产生命名冲突。vue官方的配置很明显考虑到了这一点,所以直接采用了避免冲突的「安全」模式。

解决

css压缩模式换为safe并重新打包,问题解决。

NPM 撤回已发布的版本

npm unpublish $PackageName@$version

背景

之前写了一个UI组件库ui-nuclear-mobile,其v1.0.8版本上发现了一个css错误。此错误修复后,更新版本至v1.0.9。本以为到此万事大吉,结果发现在更新v1.0.9的过程中,引入了其他的bug。发现有一个使用方已经更新到了v1.0.9,只好让其回退到v1.0.8

决策

为了防止线上有问题的v1.0.9不会被更多的人使用,有两个办法:

  1. 快速修复好bug,发布新的版本
  2. 撤回v1.0.9,修改好之后重新发布

如果选择1,定位bug和修复bug都需要时间。在这段时间内v1.0.9极有可能被使用方更新,之后再修复存在不确定性。整体时间周期和2方案没有本质性的差别。最终选择了方案2

npm unpublish ui-nuclear-mobile@1.0.9

反思

后续引入的bug,应该在被Merge进开发分支时被发现。说明测试和代码review还存在着很多不足。后续的事实证明,该bug是可以被测试出来的。

流程控制依然是安全生产中一个比较重要的环节,该事件引以为戒

昆虫有趋光性吗

概念解释

1. 向光性

Phototropism向性的一种,特指「植物」。有正向负向。比如说「茎」是正向光性,而「根」是负向光性

2. 趋光性

Phototaxis是一种生物对光靠近或远离的趋性,主要指「植物」和「自养生物」。

飞蛾的趋光行为的实际解释

飞蛾之类具有复眼的夜间飞行的昆虫,常被误认为具有趋光性,这是一个认知偏误。虽然飞蛾在实际行为上表现为趋向光源飞行,事实上这是因为人造光源(如:火把,电灯)的光线与自然光线(阳光,月光,星光)不同,前者呈放射状,后者接近平行光。自然状态下通过保持平行光线的夹角修正自己的飞行路线,保持直线飞行。而人造光源呈现放射状,干扰了飞蛾的判断,与光源保持锐角飞行的飞蛾就表现成螺旋状飞向光源。

推荐阅读: 昆虫为什么不会因趋光性齐刷刷地奔向太阳?

白天只有太阳作为光源的情况

太阳光源

夜晚人造光源的情况

人造光源

紫外线灭蚊灯有用吗?

没有什么卵用

推荐阅读: 灭蚊灯放家里使用有效吗

总结

有些你认为理所应当的事情,未必是事实

关于grid布局的一些思考

0. 基础概念

0.1. 什么场景

当一个设计者在进行排版初期时,想要对布局进行整体划分

0.2. 什么问题

只能局部设计,无法从宏观角度设计并逐步深入细节。

0.3. 解决方案

通过对布局进行简单的「数据结构」描述,比如「一棵树」描述,生成可视化的布局结果

0.4. 概念阐述

在容器上进行布局轨道的划分,当内容填充进去时会按照轨道排列

具体解释: Basic concepts of grid layout

1. 数据结构

从具象的角度来出发,一般我们的划分一个区域的过程是这样的:

  1. 先画一个「矩形」
  2. 将这个「矩形」进行分割,从而形成新的矩形区域

做一次最简单的划分:

  1. 先画一个「矩形」1
  2. 将这个「矩形」分为2个,形成了1.11.2
  3. 再将1.2分为2个,形成了1.2.11.2.2

那么可以表达为以下结构

1
|- 1.1
|- 1.2
   |- 1.2.1
   |- 1.2.2

使用对象和数组进行表达

{
  name: '1',
  children: [
    {
      name: '1.1'
    },
    {
      name: '1.2',
      children: [
        {
          name: '1.2.1'
        },
        {
          name: '1.2.2'
        }
      ]
    }
  ]
}

2. 嵌套关系

采用类似html的方式来进行表达

<area name="1">
  <area name="1.1"></area>
  <area name="1.2">
    <area name="1.2.1"></area>
    <area name="1.2.2"></area>
  </area>
</area>

3. 结构演化

仅凭最基础的数据结构,是无法进行视觉表达的,因为不知道这块区域有多大,也不知道它在哪儿。在视觉上,我们认为的「区域」是有「面积」概念。在这里我们通过「宽度」和「高度」对「区域」进行描述

假设我们做这样一个布局

area

3.1 宽高结构

{
  name: '1',
  width: 1024,
  height: 1024,
  children: [
    {
      name: '1.1',
      width: 512,
      height: 1024
    },
    {
      name: '1.2',
      width: 512,
      height: 1024,
      children: [
        {
          name: '1.2.1',
          width: 512,
          height: 512
        },
        {
          name: '1.2.2',
          width: 512,
          height: 512
        }
      ]
    }
  ]
}

按照以上结构,虽然对「面积」有了描述,但是对于「位置」并没有阐释。那我们再引入一个包含xy两个维度的坐标系。

我们用的「坐标」来描述一个「矩形」,我们需要四个

前提是矩形的相邻边都是互相垂直的

在一个平面直角坐标系中,一个的描述为

P= (x, y)

用4个点来表达一个矩形

P_1(0,0)

P_2(1024,0)

P_3(0,-1024)

P_4(1024,-1024)

我们可以认为矩形是4条线段组成的图形

  • 第一条线段:

P_1P_2

  • 第二条线段:

P_2P_4

  • 第三条线段:

P_4P_3

  • 第四条线段:

P_3P_1

3.2 坐标结构

{
  name: '1',
  coords: [
    {
      x: 0,
      y: 0
    },
    {
      x: 1024,
      y: 0
    },
    {
      x: 0,
      y: -1024
    },
    {
      x: 1024,
      y: -1024
    }
  ],
  children: [
    {
      name: '1.1',
      coords: [
        {
          x: 0,
          y: 0
        },
        {
          x: 512,
          y: 0
        },
        {
          x: 0,
          y: -1024
        },
        {
          x: 512,
          y: -1024
        }
      ]
    },
    {
      name: '1.2',
      coords: [
        {
          x: 512,
          y: 0
        },
        {
          x: 1024,
          y: 0
        },
        {
          x: 512,
          y: -1024
        },
        {
          x: 1024,
          y: -1024
        }
      ]
      children: [
        {
          name: '1.2.1',
          coords: [
            {
              x: 512,
              y: 0
            },
            {
              x: 1024,
              y: 0
            },
            {
              x: 512,
              y: -512
            },
            {
              x: 1024,
              y: -512
            }
          ]
        },
        {
          name: '1.2.2',
          coords: [
            {
              x: 512,
              y: -512
            },
            {
              x: 1024,
              y: -512
            },
            {
              x: 512,
              y: -1024
            },
            {
              x: 1024,
              y: -1024
            }
          ]
        }
      ]
    }
  ]
}

全部用点来表达显得非常复杂,考虑在web前端的场景中,我们来试图简化一下

既然是矩形,其表达可以如下

P_1(x_1,y_1)

P_2(x_2,y_1)

P_3(x_1,y_2)

P_4(x_2,y_2)

在浏览器的区域里,我们可以认定原点是在左上角。

  • 原点右侧x+
  • 原点左侧x-
  • 原点上方y-
  • 原点下方y+

原点坐标是(0,0),并且位于左上方

那么我们可以只通过4个参数值来表达一个区域:

  • x 左上原点x坐标

x_1

  • y 左上原点y坐标

y_1

  • width 矩形宽度,实际是右下点的x坐标

x_2

  • height 矩形高度,实际是右下点的y坐标

y_2

3.3 宽高和坐标

{
  name: '1',
  x: 0,
  y: 0,
  width: 1024,
  height: 1024
  children: [
    {
      name: '1.1',
      x: 0,
      y: 0,
      width: 512,
      height: 1024
    },
    {
      name: '1.2',
      x: 512,
      y: 0,
      width: 512,
      height: 1024
      children: [
        {
          name: '1.2.1',
          x: 512,
          y: 0,
          width: 512,
          height: 512
        },
        {
          name: '1.2.2',
          x: 512,
          y: 512,
          width: 512,
          height: 512
        }
      ]
    }
  ]
}

到这一步,我们发现这其实就是「绝对定位」,那么其缺点也显而易见了。就是每一块区域都是独立的,相互之间的关系需要你严格设定。

大多数的设计师在布局的时候都是先画一个区域,然后再画一个区域,逐个进行调整。然后带来的问题就是,设计师最讨厌用设计工具做「表格」类的东西。

为什么会这样?

因为设计工具提供的布局方式就是「绝对定位」

根据以上所示,绝对定位里的概念总结起来就只有一个,叫做「坐标」

3.4 方向、继承以及分配

从上面的结构,我们可以发现几个事情

  • 1的原点和1.1的原点是同一个
  • 1.2的宽度等于1的宽度减去1.1的宽度
  • 1.2.11.2.2的宽度等于1.2的宽度

我们要完成的是「布局」而非「绘图」,所以我们可以让这件事情变得更加简单一些。我们分步来说明

3.4.1 划定基础区域

我们首先来制作一块区域,它的名字叫1,并且宽度是1024、高度也是1024,原点位置为(0,0)(0,0)我们把它设为缺省值,所以不表达。

{
  name: 1,
  width: 1024,
  height: 1024
}

3.4.2 引入分割方向概念

我们现在对其分割,那么这个时候就产生了一个问题,我们是按「水平」方向分割,还是按「垂直」方向分割?因此,我们引入方向(维度)概念。我们简单的定义一下

  • column ,即在「水平」方向上进行分割
  • row , 即在「垂直」方向上进行分割

那么我们对1进行水平方向上的分割

{
  name: '1',
  width: 1024,
  height: 1024,
  split: 'column'
}

3.4.3 引入份数概念

那么分割为几份呢?这时候我们又要引入一个份数的概念,那么我们现在就分成2份。

{
  name: '1',
  width: 1024,
  height: 1024,
  split: 'column',
  part: 2
}

3.4.4 在另一方向上的继承

这个时候我们意识到,因为是按「水平」方向分割,那么每一块的高度应该是和原来的区域是一样的,相当于每一块都继承了原有区域的高度,但是宽度因为被分割了,所以没法继承。

我们得考虑宽度的分配,现在我们将宽度进行了分割,那么每一块占原来宽度的多少呢?我们假设是均分,每一块就是50%,那么用一个值去表达

{
  name: '1',
  width: 1024,
  height: 1024,
  split: 'column',
  part: 2,
  distribute: 0.5
}

上面的结构可以表述为

有一块1024 * 1024 的区域,拆分成2列,每一列的宽度是1024 * 0.5

2column

同理如果是按「垂直」方向分割的话,则高度需要被分割,而宽度可以继承

3.4.5 引入等份单位

那如果不等分怎么办?我们的一个distribute不足矣表达,那么如果distribute是一个数组的话,数组的length其实就代表了part的数量,而且可以直接用对象的key来标识方向,所以可以形式上简化一下

{
  name: '1',
  width: 1024,
  height: 1024,
  columns: [0.4, 0.6]
}

但是以上结构会遇到所有的distribute之和不是1,既可能出现大于1,也可能出现小于1的情况。这时候我们引入一个「等份」的概念,叫做fr1fr代表空间的1等份

比如说,我们把空间分成3块,第一块占3fr,第二块占2fr

那么这两块区域的实际宽度是

  • 第一区域宽度:

\frac{3}{3+2}\times1024 = 614.4

  • 第二区域宽度:

\frac{2}{3+2}\times1024=409.6

这下原来的数据结构可以表达为

{
  name: '1',
  width: 1024,
  height: 1024,
  columns: [1fr, 1fr]
}

3.4.6 绝对数值

并不是所有的情况都是按等份来分配的。比如某块区域,我们想让它占据一个绝对数值的宽度。

{
  name: '1',
  width: 1024,
  height: 1024,
  columns: [200, 1fr, 2fr]
}

这个时候的计算其实是,把200宽度的区域从原来的里面扣除,在进行等份的计算

两块区域的实际宽度是

  • 第一区域宽度:

200

  • 第二区域宽度:

\frac{1}{1+2}\times(1024-200)\approx274.666667

  • 第三区域宽度:

\frac{2}{1+2}\times(1024-200)\approx549.333333

那如果全都是绝对数值会怎样

比如有一个区域宽是800,我们分3列,每一列宽度是200

{
  name: '1',
  width: 800,
  height: 800,
  columns: [200, 200, 200],
}

这个时候其实就是分开处理了,1本身是一个「区域」,3列构成的是另一个「区域」

如果分割的区域之和超出了被分割的区域又该怎么样呢?

其实和上面是一样的,实际上原有的「区域」和实际的「被分割区域」是分开处理的

总结一下就是

当「区域分割规则」都是「绝对数值」的时候,那就直接「划定区域」,而不去做「分割」

前端小伙伴这个时候都笑了,写了这么废话不就是CSS的grid么

我们把最初的数据结构来一次梳理

{
  name: '1',
  width: 1024,
  height: 1024,
  columns: [1fr, 1fr],
  children: [
    {
      name: '1.1'
    },
    {
      name: '1.2',
      rows: [1fr, 1fr]
      children: [
        {
          name: '1.2.1'
        },
        {
          name: '1.2.2'
        }
      ]
    }
  ]
}

是不是很简单,但是上面其他的所有部分是不是很复杂?

这就是为什么我们平时用别人做好的东西很简单,但是如果自己实现却无从下手的原因。

3.4.7 回归到坐标系

以上的结构我们再还原到坐标系画线的模式中,看看是怎样的

先画一个最大的「区域」1

lineTop: (0,0)(width,0) //(0,0)(1024,0)
lineRight: (width,0)(width,height) //(1024,0)(1024,1024)
lineBottom: (width,height)(0,height) //(1024,1024)(0,1024)
lineLeft: (0,height)(0,0) //(0,1024)(0,0)

1.1

lineTop: (0,0)(width*1/(1+1),0) //(0,0)(512,0)
lineRight: (width*1/(1+1),0)(width*1/(1+1),height) //(512,0)(512,1024)
lineBottom: (width*1/(1+1),height)(0,height) //(512,1024)(0,1024)
lineLeft: (0,height)(0,0) //(0,1024)(0,0)

1.2

lineTop: (width*1/(1+1),0)(width,0) //(512,0)(1024,0)
lineRight: (width,0)(width,height) //(1024,0)(1024,1024)
lineBottom: (width,height)(width*1/(1+1),height) //(1024,1024)(512,1024)
lineLeft: (width*1/(1+1),height)(width*1/(1+1),0) //(512,1024)(512,0)

1.2.1

lineTop: (width*1/(1+1),0)(width,0) //(512,0)(1024,0)
lineRight: (width,0)(width,height*1/(1+1)) //(1024,0)(1024,512)
lineBottom: (width,height*1/(1+1))(width*1/(1+1),height*1/(1+1)) //(1024,512)(512,512)
lineLeft: (width*1/(1+1),height*1/(1+1))(width*1/(1+1),0) //(512,512)(512,0)

1.2.2

lineTop: (width*1/(1+1),height*1/(1+1))(width,height*1/(1+1)) //(512,512)(1024,512)
lineRight: (width,height*1/(1+1))(width,height) //(1024,512)(1024,1024)
lineBottom: (width,height)(width*1/(1+1),height) //(1024,1024)(512,1024)
lineLeft: (width*1/(1+1),height)(width*1/(1+1),0) //(512,1024)(512,0)

4. 程序实现

我们发现「布局」的根本方式是「绝对坐标」,在之上抽象出了「栅格(grid)」。非常幸运的是,CSS已经提供了这层抽象。

如果做成vue组件的话,形式化方面不想采用近似iview<row> </col>嵌套结构,而是只有一个area组件,通过一个数据结构来完成area的递归

4.1 组件属性

基于以上的观察,我们可以构建一个组件叫做area,那么它应该包含以下属性

  • width 区域的宽度可选
  • height 区域的高度 可选
  • columns 区域按列切分
  • rows 区域按行切分
  • name 区域的名称

但是我们之前说过,在另一个方向上存在继承关系,那么应该避免同时设置columnsrows,另外考虑到宽度和高度可以从上级继承,我们做一次改进

  • width 区域的宽度可选
  • height 区域的高度 可选
  • grid: 可选columns rows
  • distribute: 分配规则
  • name 区域的名称

4.2 递归

按grid进行递归,如果发现没有grid,就停止递归

5. 用户体验

5.1 兼容性

不是所有的浏览器都支持grid,保险的做法是使用百分比

但是我们就不选择兼容方案了,激进一点

5.2 显性表达区域

用边框表达,还是用颜色表达

  • 用边框会占用区域
  • 颜色要求相邻的区域颜色不重复

5.3 模板

  • 左右
  • 上下
  • 圣杯