使用 Generator 和 requestIdleCallback 实现简单的时间分片

知识准备

  1. 如果不熟悉 generator 先去看下文档
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*

  2. requestIdleCallback
    https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
    了解过 react16 任务调度的同学对这个 api 应该不会陌生

开搞

如果没空看文档,这里就简单说一下前提:

  • 在 generator 函数里面,你可以使用 yield 进行“暂停”
  • requestIdleCallback 提供了在一帧的空闲时间内执行所给的回调的能力,也就是执行回调的控制权交回给浏览器结合以上两点,我们理论上可以对现有的长耗时任务进行时间分片,使得任务执行时不“阻塞”主进程。

一个简单的时间分片函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 接受一个 generator 函数
function timeSlicing(gen) {
// 生成 generator
gen = gen();
// deadline 为 传进 requestIdleCallback 的回调自带的参数
return function next(idleDeadline) {
// 剩余的 generator 可迭代部分
let res = null;
// generator 的未迭代完,并且这一帧的空闲时间大于 0
// 也就是说单帧的空闲时间为可以执行多个任务
do {
// 迭代一次
// 可以理解为 执行一个任务
res = gen.next();
} while(
!res.done &&
idleDeadline.timeRemaining() > 0
);

// generator 已经迭代完了,所有分割的任务都完成了
// 退出
if (res.done) {
return;
}

// 将剩余的任务放在下一次 idle 执行
requestIdleCallback(next);
}
}

具体代码可以查看 github

一个简单的例子

现在网页上有个 button

1
2
3
4
5
6
7
8
9
10
11
12
const button = document.querySelector('#button');

document.addEventListener('mousemove', ({ pageX, pageY }) => {
button.style.top = `${pageY}px`;
button.style.left = `${pageX}px`;
});

setTimeout(() => {
for(let i = 0; i < 1000000; i++) {
button.innerHTML = i;
}
}, 1000);

这个的执行结果应该大家都会知道,就是一秒后,同步代码会阻塞主进程,同步代码执行完后,innerHTML 才会改变,并且 button 才会跟随鼠标移动,情况如下:
2020-01-25-13-47-28.gif

但是如果我们使用 时间分片来做这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const button = document.querySelector('#button');

document.addEventListener('mousemove', ({ pageX, pageY }) => {
button.style.top = `${pageY}px`;
button.style.left = `${pageX}px`;
})

window.requestIdleCallback(
timeSlicing(
// 这里相当于把这个 for 循环分割成了 1000000 个任务
function*() {
for(let i = 0; i < 1000000; i++) {
yield;
button.innerHTML = i;
}
}
)
);

for 循环的过程中掺杂着 渲染 以及 事件监听响应,情况如下
2020-01-25-13-47-49.gif

有兴趣的同学可以使用 浏览器 的 performance 查看一下每一帧的执行情况,相信会更有收获。

需要注意的点

  1. 时间分片不是“银弹“,只是提供一个在长耗时任务执行过程中响应用户交互的可能性
  2. 任务切分的颗粒度非常重要,会影响到任务执行的总时长(不过最终的结果都是会导致任务执行的总时长会更长,颗粒度可以优化)
  3. 如果用户的交互持续且频繁,任务执行的总时长会变得更加长,有点得不偿失(虽然可以通过给 requestIdleCallback 的第二个参数里面添加 timeout 来解决,但是仍然会有性能问题)
  4. 这跟 react16 的时间分片有比较大的联系吗?几乎没有。有兴趣的小伙伴可以看这篇文章 https://zhuanlan.zhihu.com/p/60307571 了解一下 react 的调度以及时间分片

最后

祝大家 2020 新年快乐

记一次搭建一个 autodraw 后台的经历

Autodraw

前言

不知道小伙伴们玩过 Google 的 Autodraw 没,这是一款非常有趣的产品,只需要在画板上小小画几笔,它就会识别出你想画的是什么,然后就会根据你想画的提供对应预置好的涂鸦集,让你可以快速创作。最近试做一个 demo 的时候发现 google 的接口太慢了,没梯子有时候还访问不了,这怎么可以呢!于是乎做一个类似于 autodraw 的接口这个想法就出来了。

前期调研

首先先分析这个接口的输入输出分别是什么。
autodraw 接口请求体
先不管请求体是什么,先看看返回体。
autodraw 接口返回体
可以看到,接口这边不是直接返回图片链接,而是先识别涂鸦是什么,然后返回相应的关键词数组。因为之前有了解过 Quickdraw,它是一个快速识别涂鸦的神经网络。Google 向全球收集涂鸦数据,做成数据集然后进行训练,然后赋予了 Quickdraw 项目这一神奇的能力。果不其然,仔细查看 Quickdraw 项目可以发现它使用的是与 Autodraw 相同的接口。所以可以断定,Autodraw 只是利用 Quickdraw 的能力做了小小拓展。

搜刮资料

了解到构建一个比较完整的神经网络需要比较多机器学习的知识,但是自己对这一块并不熟悉(或者说从零开始学习训练一个模型需要比较长的时间,即使 Google 有最大的开源涂鸦数据集),就把目标定为找一个训练好的开源模型上,即把输入交给模型,模型输出结果,这边根据结果做相应处理。
然后了解到了 Kaggle 这一个平台。Kaggle 是一个数据建模和数据分析竞赛平台。企业和研究者可在其上发布数据,统计学者和数据挖掘专家可在其上进行竞赛以产生最好的模型(现在已经被 Google 收购)。
在上面搜索 Quickdraw,嘿嘿嘿。然后发现其实 kaggle 里面有举行过这样一个 challenge。
Quickdraw recognition challenge in Kaggle
然后找到竞赛第一名的答案。接下来的时间就是读懂它里面的代码了。作者在 Notebook 里面附上了训练以及测试用的代码。我们需要的是测试用的代码。

开始编码

挖坑势力登场
挖坑(因为这部分的心路历程比较复杂,是一段解读 python 代码、学习 panda 库里面的 DataFrame 数据结构以及学习用 flask 起一个 TensorFlow 模型服务器的过程)。

总结

最后的成品如下,只是一个简单的 demo。
Demo Screenshot
虽然是站在巨人的肩膀上做出来的一个东西,其中的 python 以及 tensorflow 也是没接触过的东西,东捣鼓西捣鼓也算是做出来了,也算是有一点点成就感,所以写这篇博客记录下。最后非常感谢模型的作者!

遇到问题时的参考链接

提高 Debug 效率的小功能

2019-07-29-16-06-16.jpg

前言

打代码的时候肯定也会遇到很多需要 debug 的时候,或定位 bug,或查看代码运行状况。正所谓工欲善其事,必先利其器,懂得如何更好地去 debug 肯定可以为我们节省不少时间。接下来介绍几种在开发中常会用到的 debug 方法,希望可以为大家提高效率。

条件断点

条件断点,顾名思义,就是符合条件才会断住的断点。只需要在代码行数号码上面按下鼠标右键,就可以看到 Add conditional breakpoint... 的选项
2019-07-29-17-28-02.png
该选项用于创建一个条件断点。如下图是一个条件断点,
2019-07-29-17-25-42.png
它的作用是在 sum === 10 的时候会阻塞代码的执行。
条件断点常用在某个循环里面需要根据条件来阻塞代码。

Tips:

  1. 也可以在配置条件断点的表达式里面输入 console.log 语句,代码执行到这里的时候就会执行打印语句。这就跟 logpoint 的作用非常类似了。
  2. 有时候可能不知道代码应该在那里断,这时候可以借助 DOM 断点来帮助你定位。
    2019-07-30-09-58-44.png

console

  1. console.log
    这个相信大家都用过,但是如果有时候打印的值太多,会容易让人忘记这个值代表的是哪个变量,这里可使用 ES6 的解构赋值语法,就会自动带上变量名。
    如果我们要打印变量 a,b:
    2019-07-30-09-37-32.png

  2. console.assert ,自带的断言工具。
    2019-07-29-18-26-02.png

  3. console.table
    这个方法可以很规范“美观”地输出我们想要的信息,既可以用于对象也可以用于数组,更支持对详细属性的提取,更详细可以看 MDN
    这里举一个例子,对于一个 BlockSvg[]BlockSvg 里面有个属性 type,我想要拿到积木区所有积木的名字并规范输出。
    2019-07-30-09-46-22.png

“观察者”

点击“眼睛”图标可以添加一条表达式,
2019-07-30-10-00-16.png
这条表达式可以是变量也可以是判断条件,添加成功后会常驻在 console 上方并实时显示表达式的值,如上文的 sum:
2019-07-30-10-03-43.png

请求重发不需要刷新页面

对于 XHR 请求,有时候我们需要检测请求的可用性或者联调,这时候并不需要刷新页面,可以使用 Replay XHR 这个选项,就可以重新发一次这个请求了。
2019-07-30-09-50-53.png

override 的妙用

Source 的左侧边栏会有一个 override 的选项,
2019-07-30-10-06-08.png
这真是个非常好用的功能,它的强大之处在于可以用于 debug 线上代码,其实就是将本地修改存到一个文件夹,然后重新请求线上网站的时候会先从本地的资源取,所以线上的代码其实是你保存到本地的代码,从而达到 debug 的效果。但是要注意调试完需要删除掉本地 override 的代码。

不要忽略右键菜单

开发者工具的右键菜单会提供很多有用的选项,但是很多时候我们并没有注意到。

  1. 有时候我们需要找一个层级比较深的元素,需要在 Elements 页那里对元素进行不断的展开,我们可以使用
    2019-07-30-09-54-06.png
    顾名思义就是递归展开,这样可以节省很多时间。

  2. 有一些伪类的样式不好调试,可以使用
    2019-07-30-09-55-10.png
    如选择 :hover ,该元素就会维持在 hover 状态,此时测试 hover 的样式就不用移动鼠标到元素上方了。

这里说的都是我常在开发 debug 上会用到的一些小功能,这些都是从工具的方面提高 debug 的效率,大家对代码的熟悉掌握程度才是关键。🌝

浅谈 SVG Filter

<filter> 是 SVG 一个非常有用的标签,利用内置的 SVG 自带的滤镜可以实现很多效果,这里浅谈一下滤镜的组合使用,对于自带的滤镜就不多讲了,可以详细看 MDN 文档。

用 filter 实现描边

先来熟悉一下 filter 的使用:

1
2
3
4
5
6
7
8
9
<filter id="strokeFilter" height="160%" width="180%" y="-30%" x="-40%">
<feGaussianBlur in="SourceGraphic" stdDeviation="1.5"></feGaussianBlur>
<feComponentTransfer result="outBlur">
<feFuncA type="table" tableValues="0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1"></feFuncA>
</feComponentTransfer>
<feFlood flood-color="#fc3" flood-opacity="1" result="outColor"></feFlood>
<feComposite in="outColor" in2="outBlur" operator="in" result="outGlow"></feComposite>
<feComposite in="SourceGraphic" in2="outGlow" operator="over"></feComposite>
</filter>

修改 stdDeviation 以及 flood-color 即可绘制出描边。

组合 filter 的使用

  1. <feMerge>
    1
    2
    3
    4
    5
    6
    <feMerge>
    <feMergeNode in="filter1" />
    <feMergeNode in="filter2" />
    <feMergeNode in="filter3" />
    <feMergeNode in="SourceGraphic" />
    </feMerge>

按照顺序先后层叠,如下图

svg-filter-merge-2.png

  1. <feComposite>

主要用到的属性是 in , in2 , operator

1
<feComposite in="A" in2="B" operator="over" />

over 为覆盖,也就是 A 层叠 B 的上方,正如 <feMergeNode>

1
<feComposite in="A" in2="B" operator="in" />

in 类似于蒙版效果,A 的一部分重叠在 B 的不透明区域,仅仅基于 Balpha 通道。

1
<feComposite in="A" in2="B" operator="out" />

A 的一部分位于在 B 的不透明区域外部。

1
<feComposite in="A" in2="B" operator="atop" />

A 的一部分位于 B 里面,B 的一部分在 A 的外面。如 “报纸 atop 桌面,结果包含桌面上的报纸部分以及桌面。

1
<feComposite in="A" in2="B" operator="xor" />

包含位于 B 的外面的 A 的部分以及位于 A 的外面的 B 的部分。

1
<feComposite in="A" in2="B" operator="arithmetic" ... />

提供 4 个系数:k1, k2, k3, k4 ,每个像素的每个通道的结果按照如下方式计算:k1 A B + k2 A + k3 B + k4。AB 分别来自于输入图形的颜色分量。

可以在这个例子里面尝试变换 operator 来查看 filter 的作用范围以及得到的结果。

动态 filter

大多数 filter 都支持 <animate> 元素,下面是一个为元素添加呼吸描边的例子:

在 Typescript 项目中定义使用第三方库类型的全局变量

前言

如果想要在 typescript 项目中定义一个全局变量,通常的做法都是在项目根目录新建一个 types 文件夹,里面写一个 index.d.ts 文件,内容如下:

types/index.d.ts
1
2
// 如定义一个名为 global_variable 类型为 any 的全局变量
declare const global_variable:any;

然后在 tsconfig.json 增加 "types" 字段,这样在其他ts文件里面就可以直接使用 global_variable 变量,而不需要 import 。

问题

但是最近碰到个需求需要定义一个使用第三方库类型的全局变量,刚开始我是这样写的:

types/index.d.ts
1
2
3
4
import { Interface } from 'third-party-lib';

declare const variable:Interface;
declare const global_variable:any;

然鹅,这样不仅不能用,还导致了 Cannot find name "global_variable" 的错误,也就是在使用 global_variable 的地方,我还需要 import 它。

原因

找了许久才找到原因,因为 import 语句使得这个 types/index.d.ts 从全局模块变成了一个普通模块,需要 import 才可以使用。所以我们在定义全局模块的时候需要避免 import ... from '...' 这种写法,不然你就会被突如其来的一大堆报错而吓到。
那这样我们可不可以定义一个使用 import 进来的类型的全局变量呢?答案是肯定的。
typescript 2.9 为我们提供了 import() 语法,这样我们就可以在类型文件中愉快地定义带有复杂类型的全局变量了。

types/index.d.ts
1
2
declare const variable:import('third-party-lib').Interface;
declare const global_variable:any;

import 的可以是自己项目中写的类型,也可以是第三方库的类型定义。

参考链接

利用 Jest 来测试你的 Javascript 代码吧

最近在给 Blink 项目写测试用例,可谓是写到天昏地暗。当时选用 Jest 纯粹是看上了它的文档写的很好,容易上手。但是这两三天下来也就用了几个 api ,新手村都还没出,其实应该是没有那么相对复杂的应用环境,所以没有使用到 Jest 的一些高级功能,所以这里另写一篇文章,深入了解一下。

配置

话不多说,立马实践一下,因为技术栈一直是 Typescript ,所以这里使用了 ts-jest

1
2
# npm安装所有依赖
yarn add jest @types/jest ts-jest typescript -D

运行完后就安装好了我们所需要的所有依赖包,然后在项目目录下加一个配置文件 jest.config.js,添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
'transform': {
'^.+\\.tsx?$': 'ts-jest'
},
'testRegex': '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
'moduleFileExtensions': [
'ts',
'tsx',
'js',
'jsx',
'json',
'node'
],
globals: {
'ts-jest': {
'diagnostics': false,
}
}
}

这里是按 ts-jest 推荐配置所配置的,如果需要更详细的配置项可以自行 google。这里需要说明一点,这个 testRegex 配置项需要我们把测试用例文件写在项目根目录的 __test__ 文件夹中,并且文件命名以 .test.ts 或者 .spec.ts 结尾。
配置文件加完后,还需要把 package.jsonscripts 添加:

1
"test": "jest --verbose"

–verbose 配置项可以使我们看到更详细的测试结果。

第一个测试用例

新建一个 __test__ 文件夹,里面新建一个 lebron.test.ts 文件,写入下面的内容:

__test__/lebron.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Position = 'PG' | 'SG' | 'PF' | 'SF' | 'C';

export class Lebron {
// Unit: cm
public height:number = 203;

// Unit: pound
public weight:number = 250;
public team:string = 'Lakers';
public position:Position;

constructor(position:Position) {
this.position = position;
}
}

describe('Jest test for Lebron James', () => {
const lebron = new Lebron('SF');

it('Lebron is a SF', () => {
expect(lebron.position).toBe('SF');
})
});

然后运行 yarn test,即可看到如下结果。
结果

在上面那个测试用例,期望结果是:Lebron 是一个小前锋,describe 用于定义一个测试集,在测试集里面你可以放很多个测试用例,同时也说明他们是同一类测试,上面的例子则是定义了一个 Lebron 的 Jest 测试集。
对于测试用例,可以使用 it 或者 test 去定义,基本上没啥区别,然后就是断言了,基本上 toBe() 以及 not.toBe() 以及可以满足大部分需求了,然而 Jest 还是提供了很多浅显易懂好用的 API,我挑几个展示一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 判断数字是否大于
expect(2).toBeGreaterThan(1); // true

// true (非 false, 0, '', null, undefined, NaN)
expect('foo').toBeTruthy();
// false (false, 0, '', null, undefined, NaN)
expect('').toBeFalsy();

// 判断字符串:传入字符串或者正则
expect('felixzzz.cn').toMatch('felix'); // true

// 是否 defined
expect(undefined).toBeUndefined(); // true
expect(undefined).toBeDefined(); // false

// 对象
const obj1 = {
a: 1,
b: 2,
};
const obj2 = {
b: 2,
}
// 判断是否有对象子集
expect(obj1).toMatchObject(obj2); // true
// 判断是否有目标属性
expect(obj1).toHaveProperty('b'); // true

// ...

这样的 API 你可以在 Jest 官网上查看,这里就不多说了。
挖坑不填系列 = =,大家可以看一下这篇文章

以后婚礼的歌单

Jay 婚礼赛高!
记录一下以后婚礼会放的歌单,不定时更新。

  1. 难得一遇
  2. 遇到了
  3. 前所未见
  4. 情非得已
  5. 暗号
  6. 默契
  7. 约定
  8. 爱情小品
  9. Love Story
  10. 你的名字我的姓氏
  11. 霸气情歌
  12. 小传奇
  13. 每天爱你多一些
  14. 唯独你是不可取替
  15. Thinking Out Loud
  16. 我与你
  17. 简单爱

使用 Gulp 优化 Hexo 站点静态资源

2018年12月更:本教程仅适用于 gulp v3.x,升级 gulp4 的小伙伴可能要根据新的 api 修改下。
今天打开 Hexo 自动生成的 public 文件夹,惊奇地发现里面的 js , css 以及 html 文件竟然没有压缩,这就忍不了了。虽然文件数量不多,但是自动化的理念还是要灌输下去的。首先理一下需求:

  1. 这些文件没有统一一个入口文件,或者说实现起来麻烦,所以 webpack 以及 parcel 被pass掉。
  2. 需要有处理相应类型文件的模块。
  3. 需要有自动化流程。
  4. 得简单好用。

经过 237234871981 纳秒的思考以及 google 后,我决定使用 gulp 这个自动化构建工具。话不多说,开始我们的资源优化(压缩)之旅。

安装 gulp

gulp 可以通过 npm 安装,非常方便。

1
2
3
4
5
6
7
8
9
# 全局安装 gulp-cli
npm install -g gulp-cli

# 进入 hexo 项目,安装 gulp
npm install -D gulp

# 像 webpack 有一个配置文件一样
# gulp 的配置文件是 gulpfile.js
touch gulpfile.js

安装所需要的模块

gulp 提供了许多有用的模块,在压缩 css 和 js 这里我使用到 gulp-minify-css 以及 gulp-uglify 两个 npm 包。

使用模块

gulp 主要通过 gulp.task() 来构建任务,在处理文件方面一般会以 gulp.src() 指定文件的入口,以 gulp.dest() 指定输出文件的目录,然后通过 gulp.pipe() 来使用模块,使用过 koa 的同学会觉得这跟中间件的作用比较类似,用起来也会比较舒服。

gulpfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 引入模块
const gulp = require('gulp');
const minifycss = require('gulp-minify-css');
const uglify = require('gulp-uglify');

// 需要压缩的文件入口以及 output
// 这里以 hexo 生成的 public 文件夹为例子
const paths = {
styles: {
src: 'public/css/*.css',
dest: 'public/css/',
},
scripts: {
src: 'public/js/*.js',
dest: 'public/js/',
},
}

// 构建压缩 css 任务
gulp.task('minify-css', () => {
return gulp.src(paths.styles.src)
.pipe(minifycss())
.pipe(gulp.dest(paths.styles.dest));
});

// 构建压缩 js 任务
gulp.task('minify-js', () => {
return gulp.src(paths.scripts.src)
.pipe(uglify())
.pipe(gulp.dest(paths.scripts.dest));
});

以上就构建了两个任务,一个是压缩 css 并输出到源文件夹,另一个是压缩 js 并输出到源文件夹。

构建 default 任务

以上任务构建完了,但你运行 gulp 命令时并不会成功,因为需要添加默认任务。

gulpfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
// 构建任务队列
const minify_tasks = [
'minify-css',
'minify-js',
];

// 任务运行函数
const minify = () => {
gulp.run(minify_tasks);
};

// 构建默认任务
gulp.task('default', minify)

这样就构建好了一个默认任务,它是执行 gulp 命令时立即执行的任务,以上代码构建的默认任务开始时会运行所有的 minify 任务,压缩资源文件。现在就可以执行 gulp 压缩资源文件啦~

压缩 html

为什么要把压缩 html 文件分出来单独讲呢,因为我突然想起我的服务器开启了 gzip 压缩,再压缩 html 文件并没有多大提升。但是这里可以给一下教程给需要的同学。
gulp 提供了 gulp-htmlmin 模块用于压缩 html 文件。大家可以到 html-minifier 上查看更详细的配置项。

gulpfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');

// html 文件目录
const paths = {
html: {
src: 'public/**/*.html',
dest: 'public/'
}
}

// 构建压缩 html 任务
gulp.task('minify-html', () => {

// html 压缩配置项
const htmlmin_options = {
removeComments: true, // 清除HTML注释
collapseWhitespace: true, // 压缩HTML
collapseBooleanAttributes: true, // 省略布尔属性的值 <input checked="true"/> ==> <input />
removeEmptyAttributes: true, // 删除所有空格作属性值 <input id="" /> ==> <input />
removeScriptTypeAttributes: true, // 删除<script>的type="text/javascript"
removeStyleLinkTypeAttributes: true, // 删除<style>和<link>的type="text/css"
minifyJS: true, // 压缩页面JS
minifyCSS: true // 压缩页面CSS
};

return gulp.src(paths.html.src)
.pipe(htmlmin(htmlmin_options))
});

然后问题就出现了,html 文件在多个不同层级的文件夹内,怎么把 html 文件压缩好后放回源文件夹呢?
经过 google 了解到 gulp-rename 这个 npm 包可以实现这样的效果,额外的使用方法可以到 这里 查看。以上需求的实现如下:

gulpfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 构建压缩 html 任务
const rename = require('gulp-rename');

// html 文件目录
// ...

gulp.task('minify-html', () => {

// html 压缩配置项
//...

return gulp.src(paths.html.src)
.pipe(htmlmin(htmlmin_options))
.pipe(rename({}))
.pipe(gulp.dest(paths.html.dest));
});

然后添加到 minify 任务列表即可:

gulpfile.js
1
2
3
4
5
6
// 构建任务队列
const minify_tasks = [
'minify-css',
'minify-js',
'minify-html',
];

这样运行 gulp 就会帮你处理需要处理的资源文件了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 开始 gulp
gulp

# log
[19:45:59] Using gulpfile your-project-path/gulpfile.js
[19:45:59] Starting 'default'...
gulp.run() has been deprecated. Use task dependencies orgulp.watch task triggering instead.
[19:45:59] Starting 'minify-css'...
[19:45:59] Starting 'minify-js'...
[19:45:59] Starting 'minify-html'...
[19:45:59] Finished 'default' after 13 ms
[19:46:02] Finished 'minify-css' after 2.5 s
[19:46:02] Finished 'minify-js' after 2.54 s
[19:46:02] Finished 'minify-html' after 2.63 s

到此我们的资源压缩之旅也结束啦~

Cent OS 笔记

搬的砖多了,便成了建筑师。

比格伯德瞎说的

前言

现在买的服务器一般都是用 Cent OS 作为系统的,这篇不定时更新的文章是用于记录一些比较基本的操作记录以及所踩过的坑。

“Oh My ZSH!”

“Your terminal never felt this good before”,对的,不装 zsh 的 Terminal 不是一个优秀的终端。

首先安装oh-my-zsh

1
sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"

安装插件

我装的插件就一个 —— zsh-autosuggestions

  1. git clone 把仓库下载到本地。

    1
    git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
  2. 添加在以下代码 ~/.zshrc 文件里面。

    1
    source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh
  3. source ~/.zshrc 使其生效。

Node.js

作为一名前端开发工程狮,Node 环境是不可或缺的。我尝试过以下两种方式安装 nodejs。

通过setup程序安装

  1. distributions 提供了各个版本的 nodejs 安装程序。如果需要安装8.x版本的nodejs,只需要以下代码:

    1
    curl --silent --location https://rpm.nodesource.com/setup_8.x | bash -
  2. 执行 sudo yum install -y nodejs 安装

  3. 查看node版本

    1
    2
    3
    4
    5
    # 查看node版本
    node -v

    # 查看npm版本
    npm -v

通过源码安装

  1. 官网 找到所需要的 nodejs 版本的下载链接,通过 wget 下载到本地。

    1
    2
    3
    4
    5
    # 解压
    tar zxvf node-v8.12.0.tar.gz

    # 进入目录
    cd node-v8.12.0
  2. 解压后进入目录。

    1
    2
    # 如 v8.12.0 版本
    wget https://nodejs.org/dist/v8.12.0/node-v8.12.0.tar.gz
  3. 配置以及安装

    1
    2
    3
    4
    5
    6
    7
    8
    # 配置
    ./configure

    # 编译
    make

    # 安装
    make install
  4. 查看node版本

    1
    2
    3
    4
    5
    # 查看node版本
    node -v

    # 查看npm版本
    npm -v

Git

接下来就装一下最好的分散式版本控制软件 – git。

卸载原有的 git

如果以及安装 git 但是版本较旧的话,可以先使用 yum remove git 来卸载

找到需要的版本

可以到 这里 找到需要的 git 版本下载链接。

下载并安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 下载,如下载 2.19.1 版本
wget https://github.com/git/git/archive/v2.19.1.tar.gz

# 解压
tar -zxvf git-2.19.1.tar.gz

# 进入解压后目录
cd git-2.19.1

# 安装依赖
sudo yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel

# 编译并安装
make prefix=/usr/local/git all
sudo make prefix=/usr/local/git install

# 配置环境变量
echo "export PATH=/usr/local/git/bin:$PATH" >> /etc/profile
source /etc/profile

# 查看 git 版本
git --version