Mac OS命令行下使用VS Code打开文本文件

了解Mac环境变量设置

环境变量的位置

位置 级别 读写权限 摘要
/etc/paths 1(系统级) root 文件,每一行代表一个环境变量,不建议修改
/etc/paths.d/xxx 2(系统级) root 文件夹, 可以在里面建任意不带后缀名的文件, 在文件中写入环境变量, 系统级环境变量建议放在该文件夹中
~/.bash_profile 3(用户级) 用户 文件,~ 代表当前用户目录,该文件可能不存在,不存在创建一个即可
临时变量 4(用户级) 用户 临时变量,仅在当前终端有效,关闭终端后自动失效

说明:

  • 1.级别数字代表读取顺序,数值越的优先读取。环境变量文件读取顺序: paths -> paths.d -> .bash_profile
  • 2.级别数字代表执行顺序,数值高的优先执行。若条命令在 用户级 和 系统级 里面均有,优先执行用户级命令。

安装相应编辑器

可以直接下载相应dmg文件进行安装,我已经安装好VS Code

修改配置文件

打开用户配置文件:

1
vim ~/.bash_profile

添加别名,路径位置视实际情况而定:

1
2
3
4
5
6
7
#配置Visual Studio Code的命令行方式
alias vsc="'/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code'"

#如果不添加别名,也可以将路径添加到环境变量下
VSC_BIN=/Applications/Visual Studio Code.app/Contents/Resources/app/bin
PATH=$VSC_BIN:$PATH
export PATH

保存后回到命令行终端执行命令使其生效:

1
source ~/.bash_profile

使用VSCode打开文本文件

1
vsc ~/.bash_profile

参考资料:

Mac 可设置环境变量的位置、查看和添加PATH环境变量

Mac环境变量设置

“error while loading shared libraries: xxx.so.x” 错误的原因和解决办法

OpenSSL资源下载

HTTPS, SPDY和 HTTP/2性能的简单对比

HTTPS、SPDY和HTTP/2的性能比较

腾讯地图api使用指南

腾讯地图产品

产品 产品描述 使用限制
地图组件(H5) 手机浏览器专用,完整应用功能无需开发,极简接入,包括: H5定位组件(大幅提升手机浏览器定位效果)、地点标注、路线规划等功能
查看文档>>
无限制
地图JavascriptAPI 用于浏览器端地图显示与应用,手机 与 PC浏览器全兼容
查看文档>>
无限制
定位SDK 用于手机端APP定位终端位置:
Android定位SDK iOS定位SDK
无限制
地图SDK 用于手机端APP嵌入地图显示与应用:
Android地图SDK iOS地图SDK
无限制
WebServiceAPI http数据接口,适用于服务端或任意支持http协议的环境, 提供 地址解析、地点搜索、行政区划、路线规划、距离计算等功能, 服务通过输入参数,计算并返回json结构的结果,如需在地图中展现,需要结合SDK或JsAPI一起使用
查看文档>>
日限制:1万次/key/接口
并发限制:5次/秒/key/接口
如需更大配额可进行申请:
点击查看详情

关于 JavaScript API

JavaScript API V2可用于在网站中加入交互性强的街景、地图,能很好地支持PC及手机设备,身材小巧,动画效果顺滑流畅,动感十足,提供地图操作、标注、地点搜索、出行规划、地址解析、街景等接口,功能丰富,并免费开放各种附加工具库。JavaScript API V2是免费服务,任何提供免费访问的网站都可以调用,请参见使用条款。

JavaScript API上手还是比较简单的,资料这块比较齐全:

主要包含大类:

地图
控件
覆盖物
服务
地图类型
街景
事件
基础类
MVC
Geometry Library
Place Library
Drawing Library
Convertor Library

使用指南:
开发指南: http://lbs.qq.com/javascript_v2/index.html
参考手册: http://lbs.qq.com/javascript_v2/doc/index.html
示例demo: http://lbs.qq.com/javascript_v2/demo.html
H5定位组件: http://lbs.qq.com/tool/component-marker.html

说说几个常用的例子:
1.禁止所有的默认控件

1
2
3
4
5
6
7
var map = new qq.maps.Map(document.getElementById("map-canvas"), {
disableDefaultUI: true //禁止所有控件
//或
panControl: true, //平移控件的初始启用/停用状态
zoomControl: false, //缩放控件的初始启用/停用状态
scaleControl: true //滚动标尺控件的初始启用/停用状态
});

2.拖动地图显示地图中心坐标信息

1
2
3
4
5
6
7
8
9
10
11
12
var map = new qq.maps.Map(document.querySelector(".homev2_map_img"), {
// 地图的中心地理坐标。
center: new qq.maps.LatLng(lat, lng),
zoom: 10, //取值1~15
disableDefaultUI: true,
mapTypeId: qq.maps.MapTypeId.ROADMAP
});

//地图移动获取中心点坐标
qq.maps.event.addListener(map, 'center_changed', function() {
console.log("导出中心点", map.getCenter())
})

3.添加Marker覆盖物标签对齐居中显示

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//创建Marker覆盖物
createMarker (map, lat, lng, callback, txt = "塞纳春天大家好") {
//http://lbs.qq.com/javascript_v2/doc/markeroptions.html 定义覆盖物图片
//http://lbs.qq.com/javascript_v2/doc/markerimage.html
let center = new qq.maps.LatLng(lat, lng)
let anchor = new qq.maps.Point(0, 58/4),
size = new qq.maps.Size(52, 58),
origin = new qq.maps.Point(0, 0),
scaleSize = new qq.maps.Size(52/2, 58/2)
let markerIcon = new qq.maps.MarkerImage(
"//wq.360buyimg.com/fd/h5/wx/home_v2/images/homev2_map_icon_b00aa33e.png",
size,
origin,
anchor,
scaleSize
)

let marker = new qq.maps.Marker({
position: center,
//animation:qq.maps.MarkerAnimation.DROP, 动画类型,需设置阴影,如果没则出现关闭叉
map: map
})

marker.setIcon(markerIcon)
if(callback){
//绑定事件
qq.maps.event.addListener(marker, 'click', function() {
callback && callback()
})
}


this.createMarkerLable(map, lat, lng, txt, function(){
callback && callback()
})
}

//创建标签,通过样式控制居中
createMarkerLable (map, lat, lng, txt, callback) {
//http://lbs.qq.com/javascript_v2/doc/label.html
//http://lbs.qq.com/javascript_v2/doc/labeloptions.html文字设置
let center = new qq.maps.LatLng(lat, lng)

let markerLabel = new qq.maps.Label({
position: center,
map: map,
content: txt,
offset: new qq.maps.Size(24/2, -34),
zIndex: 10,
style: {
color: "#fff",
backgroundColor: "rgba(165,103,65,.85)",
// left: "50%",
webkitTransform: "translateX(-50%)",
height: "15px",
textAlign: "center",
fontSize: "10px",
lineHeight: "15px",
padding: "0 4px",
border: "0",
webkitBorderRadius: "2px"
}
})
//绑定事件
qq.maps.event.addListener(markerLabel, 'click', function() {
callback && callback()
})
}

ps: pc端没有定位传感器,无法做到准确定位,定位实现通常是通过ip分析转换,腾讯地图默认没有传感器不会做处理。

参考资料:

开发指南
参考手册
示例demo
H5定位组件
为何在PC浏览器地图上,不制作定位当前位置的功能?
网页版百度地图是如何定位的?

history对象--转载

一、概述

浏览器窗口有一个 history 对象,用来保存浏览历史。

如果当前窗口先后访问了三个网址,那么 history 对象就包括三项,history.length 属性等于3。

1
history.length // 3

history 对象提供了一系列方法,允许在浏览历史之间移动。

back():移动到上一个访问页面,等同于浏览器的后退键。
forward():移动到下一个访问页面,等同于浏览器的前进键。
go():接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back()。

1
2
3
history.back();
history.forward();
history.go(-2);

如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是默默的失败。
history.go(0)相当于刷新当前页面。

1
history.go(0);

常见的“返回上一页”链接,代码如下。

1
2
3
document.getElementById('backLink').onclick = function () {
window.history.back();
}

注意,返回上一页时,页面通常是从浏览器缓存之中加载,而不是重新要求服务器发送新的网页。

二、history.pushState()

HTML5为 history 对象添加了两个新方法,history.pushState()history.replaceState(),用来在浏览历史中添加和修改记录。

1
2
3
4
5
if (!!(window.history && history.pushState)){
// 支持History API
} else {
// 不支持
}

上面代码可以用来检查,当前浏览器是否支持History API。如果不支持的话,可以考虑使用Polyfill库 History.js

history.pushState 方法接受三个参数,依次为:

state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。
title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。

假定当前网址是 example.com/1.html,我们使用 pushState 方法在浏览记录(history对象)中添加一个新记录。

1
2
var stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', '2.html');

添加上面这个新记录后,浏览器地址栏立刻显示 example.com/2.html,但并不会跳转到 2.html,甚至也不会检查 2.html 是否存在,它只是成为浏览历史中的最新记录。假定这时你访问了 google.com,然后点击了倒退按钮,页面的url将显示 2.html,但是内容还是原来的 1.html。你再点击一次倒退按钮,url将显示 1.html,内容不变。

总之,pushState 方法不会触发页面刷新,只是导致 history 对象发生变化,地址栏会有反应。

如果 pushState 的url参数,设置了一个新的锚点值(即hash),并不会触发 hashchange 事件。如果设置了一个跨域网址,则会报错。

1
history.pushState(null, null, 'https://twitter.com/hello');

上面代码中,pushState 想要插入一个跨域的网址,导致报错。这样设计的目的是,防止恶意代码让用户以为他们是在另一个网站上。

三、history.replaceState()

history.replaceState 方法的参数与 pushState 方法一模一样,区别是它修改浏览历史中当前纪录。

假定当前网页是 example.com/example.html

1
2
3
4
5
6
7
8
9
10
11
12
history.pushState({page: 1}, 'title 1', '?page=1');
history.pushState({page: 2}, 'title 2', '?page=2');
history.replaceState({page: 3}, 'title 3', '?page=3');

history.back()
// url显示为//example.com/example.html?page=1

history.back()
// url显示为//example.com/example.html

history.go(2)
// url显示为//example.com/example.html?page=3

四、history.state属性

history.state 属性返回当前页面的 state 对象。

1
2
3
4
history.pushState({page: 1}, 'title 1', '?page=1');

history.state
// { page: 1 }

五、popstate事件

每当同一个文档的浏览历史(即 history 对象)出现变化时,就会触发 popstate 事件。

需要注意的是,仅仅调用 pushState 方法或 replaceState 方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用JavaScript调用 backforwardgo方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。

使用的时候,可以为 popstate事件指定回调函数。这个回调函数的参数是一个 event 事件对象,它的 state 属性指向 pushStatereplaceState 方法为当前URL所提供的状态对象(即这两个方法的第一个参数)。

1
2
3
4
5
6
7
8
9
10
11
window.onpopstate = function (event) {
console.log('location: ' + document.location);
console.log('state: ' + JSON.stringify(event.state));
};

// 或者

window.addEventListener('popstate', function(event) {
console.log('location: ' + document.location);
console.log('state: ' + JSON.stringify(event.state));
});

上面代码中的 event.state,就是通过 pushStatereplaceState 方法,为当前URL绑定的 state 对象。

这个 state 对象也可以直接通过 history 对象读取。

1
var currentState = history.state;

注意,页面第一次加载的时候,在 load 事件发生后,Chrome和Safari浏览器(Webkit核心)会触发 popstate 事件,而Firefox和IE浏览器不会。

六、URLSearchParams API

URLSearchParams API用于处理URL之中的查询字符串,即问号之后的部分。没有部署这个API的浏览器,可以用 url-search-params 这个垫片库。

1
2
var paramsString = 'q=URLUtils.searchParams&topic=api';
var searchParams = new URLSearchParams(paramsString);

URLSearchParams有以下方法,用来操作某个参数。

has():返回一个布尔值,表示是否具有某个参数
get():返回指定参数的第一个值
getAll():返回一个数组,成员是指定参数的所有值
set():设置指定参数
delete():删除指定参数
append():在查询字符串之中,追加一个键值对
toString():返回整个查询字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var paramsString = 'q=URLUtils.searchParams&topic=api';
var searchParams = new URLSearchParams(paramsString);

searchParams.has('topic') // true
searchParams.get('topic') // "api"
searchParams.getAll('topic') // ["api"]

searchParams.get('foo') // null,注意Firefox返回空字符串
searchParams.set('foo', 2);
searchParams.get('foo') // 2

searchParams.append('topic', 'webdev');
searchParams.toString() // "q=URLUtils.searchParams&topic=api&foo=2&topic=webdev"

searchParams.append('foo', 3);
searchParams.getAll('foo') // [2, 3]

searchParams.delete('topic');
searchParams.toString() // "q=URLUtils.searchParams&foo=2&foo=3"

URLSearchParams还有三个方法,用来遍历所有参数。

keys():遍历所有参数名
values():遍历所有参数值
entries():遍历所有参数的键值对

上面三个方法返回的都是 Iterator 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var searchParams = new URLSearchParams('key1=value1&key2=value2');

for (var key of searchParams.keys()) {
console.log(key);
}
// key1
// key2

for (var value of searchParams.values()) {
console.log(value);
}
// value1
// value2

for (var pair of searchParams.entries()) {
console.log(pair[0]+ ', '+ pair[1]);
}
// key1, value1
// key2, value2

在Chrome浏览器之中,URLSearchParams 实例本身就是 Iterator 对象,与 entries 方法返回值相同。所以,可以写成下面的样子。

1
2
3
for (var p of searchParams) {
console.log(p);
}

下面是一个替换当前URL的例子。

1
2
3
4
5
6
// URL: https://example.com?version=1.0
var params = new URLSearchParams(location.search.slice(1));
params.set('version', 2.0);

window.history.replaceState({}, '', `${location.pathname}?${params}`);
// URL: https://example.com?version=2.0

URLSearchParams实例可以当作POST数据发送,所有数据都会URL编码。

1
2
3
4
5
6
7
let params = new URLSearchParams();
params.append('api_key', '1234567890');

fetch('https://example.com/api', {
method: 'POST',
body: params
}).then(...)

DOM的 a 元素节点的 searchParams 属性,就是一个 URLSearchParams 实例。

1
2
3
var a = document.createElement('a');
a.href = 'https://example.com?filter=api';
a.searchParams.get('filter') // "api"

URLSearchParams 还可以与 URL 接口结合使用。

1
2
var url = new URL(location);
var foo = url.searchParams.get('foo') || 'somedefault';

参考资料:

history对象
操纵浏览器的历史记录
hashchange和popstate的用法区别
历史记录API中hashchange与popstate的比较

模块化工具webpack基础篇

前言

webpack是目前前端开发必不可少的一款模块加载器兼构建工具,它能极其方便的处理各种资源的打包和使用, 让前端开发获得与后端开发几乎一致的体验。

Webpack是什么

CommonJS 和 AMD 是用于 JavaScript 模块管理的两大规范,前者定义的是模块的同步加载,主要用于NodeJS;而后者则是异步加载,通过 requirejs 等工具适用于前端。随着 npm 成为主流的 JavaScript 组件发布平台,越来越多的前端项目也依赖于 npm 上的项目,或者自身就会发布到 npm 平台。因此,让前端项目更方便的使用 npm 上的资源成为一大需求。

web开发中常用到的静态资源主要有JavaScript、CSS、图片、Jade等文件,webpack中将静态资源文件称之为模块。webpack是一个module bundler(模块打包工具),其可以兼容多种js书写规范,且可以处理模块间的依赖关系,具有更强大的js模块化的功能。Webpack对它们进行统一的管理以及打包发布,其官方主页用下面这张图来说明Webpack的作用:

webpack

Webpack的核心原理

Webpack的两个最核心的原理分别是:

1.一切皆模块
正如js文件可以是一个“模块(module)”一样,其他的(如css、image或html)文件也可视作模 块。因此,你可以require(‘myJSfile.js’)亦可以require(‘myCSSfile.css’)。这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重复利用等的目的。

2.按需加载
传统的模块打包工具(module bundlers)最终将所有的模块编译生成一个庞大的bundle.js文件。但是在真实的app里边,“bundle.js”文件可能有10M到15M之大可能会导致应用一直处于加载中状态。因此Webpack使用许多特性来分割代码然后生成多个“bundle”文件,而且异步加载部分代码以实现按需加载。

Webpack特点

  1. 对 CommonJS 、 AMD 、ES6的语法做了兼容
  2. 对js、css、图片等资源文件都支持打包
  3. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持
  4. 有独立的配置文件webpack.config.js
  5. 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间
  6. 支持 SourceUrls 和 SourceMaps,易于调试
  7. 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活
  8. webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快

Webpack安装和配置

安装

webpack 可以作为全局的 npm 模块安装,也可以在当前项目中安装。

1
2
npm install -g webpack  //全局安装
npm install --save-dev webpack //局部安装

对于全局安装的webpack,直接执行此命令会默认使用当前目录的webpack.config.js作为配置文件。如果要指定另外的配置文件,可以执行:

1
webpack —config webpack.custom.config.js

配置

每个项目下都必须配置有一个 webpack.config.js ,它的作用如同常规的 gulpfile.js/Gruntfile.js ,就是一个配置项,告诉 webpack 它需要做什么。

webpack.config.js文件通常放在项目的根目录中,它本身也是一个标准的Commonjs规范的模块。在导出的配置对象中有几个关键的参数:

entry

entry 参数定义了打包后的入口文件,可以是个字符串或数组或者是对象;如果是数组,数组中的所有文件会打包生成一个filename文件;如果是对象,可以将不同的文件构建成不同的文件:

1
2
3
4
5
6
7
8
9
10
11
12
{
entry: {
index: "./page1/index.js",
//支持数组形式,将加载数组中的所有模块,但以最后一个模块作为输出
login: ["./entry1/login.js", "./entry2/register.js"]
},
output: {
path: "dist/js/page",
publicPath: "/output/",
filename: "[chunk].[name].bundle.js"
}
}

ps: chunk:被entry所依赖的额外的代码块,同样可以包含一个或者多个文件

该段代码最终会生成一个 page1.bundle.js 和 page2.bundle.js,并存放到 ./dist/js/page 文件夹下

output

output参数是个对象,定义了输出文件的位置及名字:

1
2
3
4
5
6
7
8
9
10
11
12
13
output: {
path: path.resolve(__dirname, "build"), //打包输出路径,建议使用绝对路径,使用webpack-dev-server, 可将该目录设置为为虚拟web服务器的根目录

// 配置文件在html中引用的根路径,改变静态资源引入的相对路径
publicPath: "/assets/",
//publicPath: "http://cdn.com/assets/",//可加上完整的url,效果与上面一致

// filename 指输出的每个js模块名称及存放路径的定义,会影响在html中模块的引用路径
filename: "js/bundle.js", // 单页应用只有一个入口文件时使用,在html页面上通过<script src="/js/bundle.js"></script>引用入口模块
filename: "js/[name].js", // 传统多页应用有多个入口文件时使用,[name] 代入entry配置中的任意一项模块的名称,如:index, 在不同的页面上引用不同的入口,例如在index.html页面上通过<script src="/js/index.js"></script>引用入口模块
filename: "js/[hash]/[chunkhash].js", // 为生产环境实现前端静态资源增量更新时使用,[hash]是根据每次编译后项目总体文件的hash值, [chunkhash]是根据每个模块内容计算出的hash值

}

path: 打包文件存放的绝对路径
publicPath: 网站运行时的访问路径
filename:打包后的文件名
当我们在entry中定义构建多个文件时,filename可以对应的更改为[name].js用于定义不同文件构建后的名字。

module

在webpack中JavaScript,CSS,LESS,TypeScript,JSX,CoffeeScript,图片等静态文件都是模块,不同模块的加载是通过模块加载器(webpack-loader)来统一管理的。loaders之间是可以串联的,一个加载器的输出可以作为下一个加载器的输入,最终返回到JavaScript上:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
//query和options功能相同,推荐使用option
options: {
loaders: {
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
}
},
//把要处理的目录包括进来,多个用[]数组,单个直接写入
include: [
path.resolve(__dirname, "app")
],
//排除不处理的目录
exclude: /node_modules/
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/, //排除node_modules文件夹
},
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'file-loader',
query: {
limit: 8192,
name: './images/[name].[ext]?[hash]'
},

},
// 使用多个插件,使用use,sass文件使用style-loader, css-loader, less-loader来处理
{
test: /\.(sass|scss)$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
extractTextPlugin.extract(['style','css', 'less'])
],
}
]
}

注意
webpack 2.x 之后 module.loaders改成了module.rules
webpack1.x中loaders可以链式调用,在2.x中使用rule.use配置项替换
取消在模块中自动添加-loader后缀

resolve

webpack在构建包的时候会按目录的进行文件的查找,resolve属性中的extensions数组中用于配置程序可以自行补全哪些文件后缀:

1
2
3
4
5
6
7
8
9
10
11
12
resolve: {
//查找module的话从这里开始查找
root: '/pomy/github/flux-example/src', //绝对路径
//自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
extensions: ['', '.js', '.json', '.scss'],
//模块别名定义,方便后续直接引用别名,无须多写长长的地址
alias: {
AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可
ActionType : 'js/actions/ActionType.js',
AppAction : 'js/actions/AppAction.js'
}
}

然后我们想要加载一个js文件时,只要 require(‘common’)就可以加载common.js文件了。

注意一下, extensions 第一个是空字符串 ! 对应不需要后缀的情况.

plugin

webpack提供了[丰富的组件]用来满足不同的需求,当然我们也可以自行实现一个组件来满足自己的需求:

1
2
3
plugins: [
//your plugins list
]

在webpack中编写js文件时,可以通过require的方式引入其他的静态资源,可通过loader对文件自动解析并打包文件。通常会将js文件打包合并,css文件会在页面的header中嵌入style的方式载入页面。但开发过程中我们并不想将样式打在脚本中,最好可以独立生成css文件,以外链的形式加载。这时extract-text-webpack-plugin插件可以帮我们达到想要的效果。需要使用npm的方式加载插件,然后参见下面的配置,就可以将js中的css文件提取,并以指定的文件名来进行加载。

1
npm install extract-text-webpack-plugin –save-dev
1
2
3
plugins: [
new ExtractTextPlugin('styles.css')
]

externals

防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。

例如,从 CDN 引入 jQuery,而不是把它打包:

1
2
3
<script src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous"></script>
1
2
3
externals: {
"jquery": "jQuery"
}

这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:

1
2
3
import $ from 'jquery';

$('.my-element').animate(...);

关于 webpack.config.js 更详尽的配置可以参考 这里

Webpack常用命令

webpack的使用通常有三种方式:

1、命令行使用:webpack 其中entry.js是入口文件,result.js是打包后的输出文件
2、node.js API使用:

1
2
3
4
var webpack = require('webpack');
webpack({
//configuration
}, function(err, stats){});

3、默认使用当前目录的 webpack.config.js 作为配置文件。如果要指定另外的配置文件,可以执行:webpack –config webpack.custom.config.js

webpack 的执行也很简单,直接执行

1
webpack --display-error-details

常用命令

webpack的使用和browserify有些类似,下面列举几个常用命令:

webpack 最基本的启动webpack命令
webpack -w 提供watch方法,实时进行打包更新
webpack -p 对打包后的文件进行压缩
webpack -d 提供SourceMaps,方便调试
webpack –colors 输出结果带彩色,比如:会用红色显示耗时较长的步骤
webpack –profile 输出性能数据,可以看到每一步的耗时
webpack –display-modules 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块

参考资料:

webpack
webpack中文文档
【翻译向】webpack2 指南(上)
【翻译向】webpack2 指南(中)
【翻译向】webpack2 指南(下)
今天,你升级Webpack2了吗?
webpack使用小记
Webpack 2 有哪些新东西
webpack2 终极优化
基于webpack搭建前端工程解决方案探索
webpack入门
webpack使用优化(基本篇
webPack 参考
webpack打包bundle.js体积大小优化
开发工具心得:如何 10 倍提高你的 Webpack 构建效率
详解前端模块化工具-Webpack
webpack 多页面构建
使用Webpack打包时的“多页”实践
Webpack2.x踩坑与总结
Webpack——令人困惑的地方

ES6的模块化理解

前沿

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

现在ES6自带了模块化,可以直接作用import和export在浏览器中导入和导出各个模块。每一个ES6模块都是一个包含JS代码的文件,模块本质上就是一段脚本,而不是用 module 关键字定义一个模块,但是模块与脚本还是有两点区别:

  • 在ES6模块中,无论你是否加入“use strict;”语句,默认情况下模块都是在严格模式下运行。
  • 在模块中你可以使用import和export关键字。

现代浏览器对模块(module)支持程度不同, 目前都是使用babelJS, 或者Traceur把ES6代码转化为兼容ES5版本的js代码.

ES6的模块化特点

1.每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;

2.每一个模块内声明的变量都是局部变量, 不会污染全局作用域;

3.模块内部的变量或者函数可以通过export导出;

4.一个模块可以导入别的模块

export 命令

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

1.export命令输出变量

1
2
3
4
5
6
7
8
9
10
11
//导出变量
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

或者

var firstName = 'Michael'
var lastName = 'Jackson'

export {firstName, lastName}

2.export命令输出函数或类(class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//导出函数
export function multiply(x, y) {
return x * y;
};

//导出类
export class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '('+this.x+', '+this.y+')';
}
}

通常情况下,export 输出的变量就是本来的名字,但是可以使用as关键字重命名。

1
2
3
4
5
6
7
8
function v1() { ... }
function v2() { ... }

export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};

上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。

注意: 需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

1
2
3
4
5
6
// 报错
export 1;

// 报错
var m = 1;
export m;

上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出1,第二种写法通过变量m,还是直接输出1。1只是一个值,不是接口。正确的写法是下面这样。

1
2
3
4
5
6
7
8
9
10
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

上面三种写法都是正确的,规定了对外的接口m。其他脚本可以通过这个接口,取到值1。它们的实质是,在接口名与模块内部变量之间,建立了一一对应的关系。

同样的,functionclass的输出,也必须遵守这样的写法。

1
2
3
4
5
6
7
8
9
10
// 报错
function f() {}
export f;

// 正确
export function f() {};

// 正确
function f() {}
export {f};

另外,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

1
2
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

上面代码输出变量foo,值为bar,500毫秒之后变成baz

最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了ES6模块的设计初衷。

1
2
3
4
function foo() {
export default 'bar' // SyntaxError
}
foo()

上面代码中,export语句放在函数之中,结果报错。

import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

1
2
3
4
5
6
// main.js
import {firstName, lastName, year} from './profile';

function setName(element) {
element.textContent = firstName + ' ' + lastName;
}

上面代码的import命令,用于加载profile.js文件,并从中输入变量。import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

1
import { lastName as surname } from './profile';

import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js路径可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

1
import {myMethod} from 'util';

上面代码中,util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

1
2
3
foo();

import { foo } from 'my_module';

上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

由于import是静态执行,所以不能使用表达式和变量。

export default命令

从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

为了给用户提供方便,就要用到export default命令,为模块指定默认输出。

其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字。

1
2
3
4
// export-default.js
export default function () {
console.log('foo');
}

上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。

export default命令用在非匿名函数前,也是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
// export-default.js
export default function foo() {
console.log('foo');
}

// 或者写成

function foo() {
console.log('foo');
}

export default foo;

参考资料:

“error while loading shared libraries: xxx.so.x” 错误的原因和解决办法

AMD 和 CMD 的区别有哪些

SeaJS和RequireJS的异同

深入浅出ES6(十六):模块Modules - InfoQ

Module 的语法–阮一峰

移动端图片上传旋转问题

前言

最近在处理移动端选择图片实时预览并上传时遇到一个问题:上传前图片使用img标签预览正常,但通过canvas对图片进行裁切处理,有时会出现图片翻转的问题,一般是翻转 90 度。后经一翻测试研究,发现横向拍照,正常,竖直拍照图片出现旋转90度,后来发现图片拍照会有 Exif 信息存在方向问题。

PS:其实,大部分的图片查看客户端早已支持自动旋转,所以一般情况下数码设备拍的照片用电脑看,方向都是正确的。许多缩略图生成程序,也是可以指定缩放前自动旋转的(例如 ImageMagick 的 -auto-orient 参数)。

EXIF

EXIF 全称 (Exchangeable Image File Format) 照相机拍摄图像一般都由两大部分组成,一部分是数据本身,它记录了每个像素的颜色值,另外一部分是文件头,这里面记录着形如图像的宽度,高度等信息。所讨论的方向信息便是被存储于文件头中。更为具体一些:百度百科中对其 EXIF 的解释为:

可交换图像文件格式常被简称为Exif(Exchangeable image file format),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据… Exif可以附加于JPEG、TIFF、RIFF等文件之中。

注意:PNG格式的图像中不包含。

Exif 信息

当然,Exif 中的 Orientation 属性,取决于拍照的设备是否拥有方向传感器。不过根据我的了解,目前大部分数码拍照设备都支持记录方向。1 是默认值,2、4、5、7 表示照片进行了翻转。一般情况下,取值应该是 1、3、6、8 中的一种。下面有张更形象的图描述了具体的旋转策略:

Exif 信息

获取EXIF旋转图片

如何从图片中获取 Exif 信息,各个语言都有封装好的代码可以直接使用。推荐一个比较好用的库,api也比较齐全 exif-js,它做法分为两步,首先判断 img.src,如果是 Data URI(即base64)则通过 atob 函数转解码base-64编码字符串换为二进制,如果是 Object URL 通过 readAsArrayBuffer 函数读取二进制,反之则通过 Ajax 获取原始二进制;第二步是从原始数据不同位置匹配获取相关信息,基本是体力活了。

从图片 Exif 信息中取到 Orientation 后,就可以根据它来自动旋转图片了,示例代码:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
input.addEventListener("change", function(){
var file = this.files[0];

// 接受 jpeg, jpg, png 类型的图片
// if (!/\/(?:jpeg|jpg|png)/i.test(file.type)) return;

var reader = new FileReader();
var compressCanvas = document.querySelector(".babyhappy_compressimg_con");
compressCtx = compressCanvas.getContext("2d");

reader.onload = function(event) {
var result = this.result;
var image = new Image();
image.src = reader.result;

image.onload = function(){
EXIF.getData(image, function(){
EXIF.getAllTags(this);
var orientation = EXIF.getTag(this, 'Orientation');
switch(orientation){
case 1:
//0°
compressCanvas.height = image.height;
compressCanvas.width = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.drawImage(image, 0, 0, image.width, image.height);
break;
case 6:
//顺时针90°
compressCanvas.width = image.height;
compressCanvas.height = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.translate(0, 0);
compressCtx.rotate(90 * Math.PI / 180);
compressCtx.drawImage(image, 0, -image.height, image.width, image.height);
break;
case 8:
//逆时针90°
compressCanvas.width = image.height;
compressCanvas.height = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.translate(0, 0);
compressCtx.rotate(-90 * Math.PI / 180);
compressCtx.drawImage(image, -image.width, 0, image.width, image.height);
break;
case 3:
//180°
compressCanvas.height = image.height;
compressCanvas.width = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.translate(0, 0);
compressCtx.rotate(Math.PI);
compressCtx.drawImage(image, -image.width, -image.height, image.width, image.height);
break;
default:
compressCanvas.height = image.height;
compressCanvas.width = image.width;
compressCtx.clearRect(0, 0, compressCanvas.width, compressCanvas.height)
compressCtx.drawImage(image, 0, 0, image.width, image.height);
}
var base64 = compressCanvas.toDataURL("image/jpeg", .5);

})

}
reader.readAsDataURL(file);
}, false)

示例演示demo

参考资料:

Exif.js 读取图像的元数据
exif-js github
H5拍照应用开发经历的那些坑儿
图片自动旋转的前端实现方案

git生成ssh key及本地解决多个ssh key的问题

ssh是一种网络协议,用于计算机之间的加密登录。

生成ssh key步骤

这里以配置github的ssh key为例:

1.配置git用户名和邮箱

1
2
git config user.name "用户名"
git config user.email "邮箱"

在config后加上 –global 即可全局设置用户名和邮箱。

2.生成ssh key

1
ssh-keygen -t rsa -C "邮箱"

然后根据提示连续回车即可在~/.ssh目录下得到id_rsa和id_rsa.pub两个文件,id_rsa.pub文件里存放的就是我们要使用的key。

3.上传key到github

1
clip < ~/.ssh/id_rsa.pub
  • 复制key到剪贴板
  • 登录github
  • 点击右上方的Accounting settings图标
  • 选择 SSH key
  • 点击 Add SSH key

4.测试是否配置成功

1
ssh -T git@github.com

如果配置成功,则会显示:
Hi username! You’ve successfully authenticated, but GitHub does not provide shell access.

解决本地多个ssh key问题

有的时候,不仅github使用ssh key,工作项目或者其他云平台可能也需要使用ssh key来认证,如果每次都覆盖了原来的id_rsa文件,那么之前的认证就会失效。这个问题我们可以通过在~/.ssh目录下增加config文件来解决。

下面以配置jdresource平台的ssh key为例。

1.第一步依然是配置git用户名和邮箱

1
2
git config user.name "用户名"
git config user.email "邮箱"

2.生成ssh key时同时指定保存的文件名

1
ssh-keygen -t rsa -f ~/.ssh/id_rsa.jd -C "email"

上面的id_rsa.jd就是我们指定的文件名,这时~/.ssh目录下会多出id_rsa.jd和id_rsa.jd.pub两个文件,id_rsa.jd.pub里保存的就是我们要使用的key。

3.新增并配置config文件

添加config文件

如果config文件不存在,先添加;存在则直接修改

1
touch ~/.ssh/config

在config文件里添加如下内容(User表示你的用户名)

1
2
3
Host *.jd.com
IdentityFile ~/.ssh/id_rsa.jd
User test

4.上传key到云平台后台(省略)

5.测试ssh key是否配置成功

1
ssh -T git@git.source.jd.com

成功的话会显示:

1
Welcome to GitLab, username!

至此,本地便成功配置多个ssh key。日后如需添加,则安装上述配置生成key,并修改config文件即可。

部署 HTTP/2

HTTP/2 是什么?

HTTP/2(超文本传输协议第2版,最初命名为HTTP 2.0 ),是HTTP协议的的第二个主要版本,使用于万维网。HTTP/2是HTTP协议自1999年HTTP 1.1发布后的首个更新,主要基于SPDY协议。它由互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工作小组进行开发。与 HTTP1.1 完全语义兼容,几乎可以无缝升级。目前主流浏览器都已经支持 HTTP/2 了(IE 自 IE 11 开始支持)。

早些时候,Nginx 曾发布过一个 early-alpha patch 来提供对 HTTP/2 的支持,但从最新发布的 Nginx 1.9.5 开始,httpv2module 已经替换了 ngxhttpspdy_module 并正式开始提供全面的 HTTP/2 支持。

下面说下如何升级到HTTP/2,目前我的nginx版本为 1.10.3 :

升级 OpenSSL

Note that accepting HTTP/2 connections over TLS requires the “Application-Layer Protocol Negotiation” (ALPN) TLS extension support, which is available only since OpenSSL version 1.0.2. Using the “Next Protocol Negotiation” (NPN) TLS extension for this purpose (available since OpenSSL version 1.0.1) is not guaranteed.

官方提到,目前升级 HTTP/2 使用 ALPN,OpenSSL需要在 1.0.2 之上。

1.检查OpenSSL版本

1
openssl version

如果版本不够可以,在OpenSSL下载。

2.下载 OpenSSL 的最新版,当前最新版本为1.1.0e

1
2
3
4
5
wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz

tar zxvf openssl-1.1.0e.tar.gz

mv openssl-1.1.0e/ openssl

3.编译安装

1
2
3
./config --prefix=/usr --openssldir=/etc/ssl --libdir=lib shared zlib-dynamic
make depend
make && make install

4.检查版本

1
2
openssl version
OpenSSL 1.1.0e 16 Feb 2017

Nginx启动HTTP/2

开启HTTP/2也十分简单,直接在指定的域名nginx.conf中配置。

1
2
3
4
5
6
7
8
9
10
server {  
listen 443 ssl http2;
server_name luolei.org;

#SSL配置
ssl on;
ssl_certificate /etc/nginx/conf.d/certificate.crt;
ssl_certificate_key /etc/nginx/conf.d/certificate.key;

}

在listen后面增加http2即可。

注意:不能混用SPDY和HTTP/2,如果你两个都同时开启,会报错。

1
nginx: [warn] invalid parameter "spdy": ngx_http_spdy_module was superseded by ngx_http_v2_module in /etc/nginx/conf.d/vhost.conf:12

配置Nginx

Nginx 配置 http_v2_module 和 http_ssl_module 两个模块,以及最新openssl

1
./configure --with-openssl=.././openssl --with-http_v2_module --with-http_ssl_module

重启nginx服务器

1
service nginx restart

重启服务器之后,打开chrome浏览器,进入网络Network,打开Protocol,看到主域的Protocol已经变了成了h2,这就意味着已经成功升级到HTTP/2。
http2

或者使用Chrome的网络工具,在地址栏中输入chrome://net-internals/#http2

http2

参考资料:

HTTP/2 资料汇总

本博客 Nginx 配置之完整篇

“error while loading shared libraries: xxx.so.x” 错误的原因和解决办法

OpenSSL资源下载

HTTPS, SPDY和 HTTP/2性能的简单对比

HTTPS、SPDY和HTTP/2的性能比较

用 Git 钩子进行自动部署

Git 钩子

Git 钩子(hooks)是在 Git 仓库中特定事件(certain points)触发后被调用的脚本。通过钩子可以自定义 Git 内部的相关(如 git push)行为,在开发周期中的关键点触发自定义的行为。Git 含有两种类型的钩子:客户端的和服务器端的。客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。

Webhook1

Git 钩子最常见的使用场景包括根据仓库状态改变项目环境、接入持续集成工作流等。由于脚本是可以完全定制,所以你可以用 Git 钩子来自动化或者优化你开发工作流中任意部分。

在这篇文章中,我们会先简要介绍 Git 钩子相关要素,然后实例使用 Git 钩子进行博客自动部署。

Git 钩子安装

Git 钩子存在于每个 Git 仓库的 .git/hooks 目录中。 当你用 git init 初始化一个新版本库时,Git 默认会在这个目录中放置一些示例脚本。所有的示例都是 shell 脚本,其中一些还混杂了 Perl 代码,不过,任何正确命名的可执行脚本都可以正常使用 —— 你可以用 Ruby 或 Python,或其它语言编写它们。

1
2
3
4
➜  hooks git:(master) ls
applypatch-msg.sample pre-applypatch.sample pre-rebase.sample
commit-msg.sample pre-commit.sample prepare-commit-msg.sample
post-update.sample pre-push.sample update.sample

.sample拓展名是为了防止它们默认被执行,安装一个钩子只需要去掉.sample拓展名即可。

Git 钩子的作用域

Git 钩子是对本地仓库相关操作影响,对于任何 Git 仓库来说钩子都是本地的,初始的钩子都是从 Git 默认模板目录中自动安装。

在开发团队中为了保持团队所使用钩子一致,维护起来算是比较复杂的,因为 .git/hooks 目录不随你的项目一起拷贝,也不受版本控制影响。

Webhook1

简单的解决办法是把钩子文件存放在项目的实际目录中(在.git 外),这样就可以像其他文件一样进行版本控制,然后在.git/hooks中创建一个链接,或者简单地在更新后把它们复制到.git/hooks目录下。

当我们了解了以上 Git 钩子基础知识后,下面我们来实例操作 Git 钩子进行博客自动部署。

Git 钩子进行自动部署

如何实现 Git 钩子进行自动部署,其实原理很简单,我们只需要监听每次本地 git push到远程服务器,然后远程服务器同步拉取最新文件,重启服务器即可(pm2 reload xx)。

Webhook1

1.在服务器初始化一个远程 Git 裸仓库 (git init –bare)

裸仓库与 git init 初使化的仓库不太一样,裸仓库其实相当于通过克隆来的仓库里的.git文件夹,整个裸仓库中只有git索引(index),不包含工作目录。要实现Push to Deploy,首先我们需要一个裸仓库。

1
2
3
4
5
6
7
git init --bare xxx-bare.git



mkdir xxx-bare.git
cd xxx-bare.git
git init --bare

post-update.sample去掉sample, 在里面增加执行脚本:

1
2
3
4
5
6
7
8
#!/bin/sh
#
DIR_ONE=/home/user/deploy-directory
#
cd $DIR_ONE
git clean -fd
#
git checkout --force

2.在服务器初始化一个本地 Git 仓库

deploy-directory目录 git init 初始化本地仓库,它的作用是拉去远程仓库最新的源码,然后在这个仓库里进行编译,把代码编译到 www 目录(网站的根目录)。

post-update.sample去掉sample, 在里面增加执行脚本

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
unset GIT_DIR
DeployPath=/home/user/Deploy
WwwPath=/home/wwwroot/Deploy
cd $DeployPath
git clean -f
git pull origin master

ath build WwwPath
#exec git-update-server-info

注意: 一定要unset GIT_DIR清除变量, 不然会引起remote: fatal: Not a git repository: '.'错误。

post-update添加执行权限:

1
chmod +x post-receive

3.本地仓库添加 remote 源

1
2
3
4
git init
git remote add origin user@1.2.3.4:/home/git/xxx-bare.git
//例如git remote add origin ssh://root@41.72.11.11:26244/home/www/BRIDGE_REPO.git
git push origin master

参考资料:

自定义 Git - Git 钩子
Git钩子:自定义你的工作流
github-webhook-handler
如何通过Git钩子自动部署(Push to Deploy)
用 Git Hooks 进行自动部署
“fatal: not a git repository: ‘.’”

Webhook 进行网站自动化部署

Webhook 是什么?

Webhook,也就是人们常说的钩子,是一个很有用的工具。Webhook 允许第三方应用监听 Github.com 上的特定事件,在这些事件发生时通过 HTTP POST 方式通知( 超时5秒) 到第三方应用指定的 Web URL。 例如项目有新的内容 Push,或是 Merge Request 有更新等。 WebHook 可方便用户实现自动部署,自动测试,自动打包,监控项目变化等。

如此一来,可以通过这种方式去自动完成一些重复性工作;比如,用 Webhook 来自动触发一些持续集成(CI)工具的运作,比如 Travis CI;又或者是通过 Webhook 去部署你的线上服务器。

Webhook使用场景

实际工作中,经常有这样的场景,本地提交代码到git仓库以后需要网站远程自动同步代码, 大概流程如下图:

Webhook

Webhook自动化部署

1.为了响应 Webhook 发出的请求,从而做一些我们想做的事情,我们得先实现一个响应服务器。本文采用 Node 来实现一个原型,你当然也可以用 PHP,python 等, 这里使用了github-webhook-handler插件:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const http = require('http')
const createHandler = require('github-webhook-handler')
const handler = createHandler({ path: '/webhook', secret: 'myhashsecret' })
const exec = require('child_process').exec

var commands = [
'cd .././blog',
'git clean -df',
'git pull'
].join(' && ')

http.createServer(function (req, res) {
console.log(req.url)
handler(req, res, function (err) {
res.statusCode = 404
res.end('no such location')
})
}).listen(7777)

handler.on('error', function (err) {
console.error('Error:', err.message)
})

handler.on('push', function (event) {
console.log('Received a push event for %s to %s',
event.payload.repository.name,
event.payload.ref)

//执行通关exec做操作,也可以尝试用sh脚本
exec(commands, function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error code: ' + error.code);
}
console.log('博客更新 deploy');
});

})

handler.on('issues', function (event) {
console.log('Received an issue event for %s action=%s: #%d %s',
event.payload.repository.name,
event.payload.action,
event.payload.issue.number,
event.payload.issue.title)
})

例如 deploy.sh

1
2
3
4
5
6
7
8
//执行通关exec做操作,也可以尝试用sh脚本
exec('sh deploy.sh', function (error, stdout, stderr) {
if (error) {
console.log(error.stack);
console.log('Error code: ' + error.code);
}
console.log('博客更新 deploy');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

WEB_PATH='/www/blog/'

echo "Start deployment"

cd $WEB_PATH
echo "pulling source code..."
git reset --hard origin/master
git clean -f
git pull
git checkout master
echo "Finished."

配置 Webhook

Webhook 的配置是十分简单的,首先进入你的 repo 主页,通过点击页面上的按钮 [settings] -> [Webhooks & service] 进入 Webhooks 配置主页面。也可以通过下面这个链接直接进入配置页面:

Webhook

配置好 Webhook 后,Github 会发送一个 ping 来测试这个地址。如果成功了,那么这个 Webhook 前就会加上一个绿色的勾;如果你得到的是一个红色的叉,那就好好检查一下哪儿出问题了吧!

Webhook

参考资料:

Webhook

coding Webhook

github-webhook-handler