Docker实践

参考 https://yeasy.gitbooks.io/docker_practice/content/introduction/

Docker介绍

Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目。它基于 Google 公司推出的 Go 语言实现。 项目后来加入了 Linux 基金会,遵从了 Apache 2.0 协议,项目代码在 GitHub上进行维护。

Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。 Docker 的基础是 Linux 容器(LXC)等技术。
在 LXC 的基础上 Docker 进行了进一步的封装,让用户不需要去关心容器的管理,使得操作更为简便。用户操作 Docker 的容器就像操作一个快速轻量级的虚拟机一样简单。
下面的图片比较了 Docker 和传统虚拟化方式的不同之处,可见容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统方式则是在硬件层面实现。
Alt
Alt

Docker的优势

作为一种新兴的虚拟化方式,Docker 跟传统的虚拟化方式相比具有众多的优势。

首先,Docker 容器的启动可以在秒级实现,这相比传统的虚拟机方式要快得多。 其次,Docker 对系统资源的利用率很高,一台主机上可以同时运行数千个 Docker 容器。

容器除了运行其中应用外,基本不消耗额外的系统资源,使得应用的性能很高,同时系统的开销尽量小。传统虚拟机方式运行 10 个不同的应用就要起 10 个虚拟机,而Docker 只需要启动 10 个隔离的应用即可。

我理解的Docker

目前,我在使用docker的过程中基本上是本地开发好项目之后,生成dist文件目录,迁移到docker当中,相当于只在生产环境开发使用docker,原因主要有以下几点:

  1. 在开发环境使用docker感觉还是不是很舒服,每次修改都要重新build,以及run,比较麻烦,本地开发更加舒服;
  2. docker实际上可以看做是一个封闭的盒子,想做一些查看和操作还是有点麻烦的的,交互性不是很好,感觉比较适合生产环境。

Docker hub介绍

目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了超过 15,000 的镜像,大部分需求,都可以通过在 Docker Hub 中直接下载镜像来实现。

登陆

可以通过执行 docker login 命令来输入用户名、密码和邮箱来完成注册和登录。 注册成功后,本地用户目录的 .dockercfg 中将保存用户的认证信息。

1
docker login --username=username --email=email@gmail.com

下载

根据是否是官方提供,可将镜像资源分为两类。 一种是类似 centos 这样的基础镜像,被称为基础或根镜像。这些基础镜像是由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。 还有一种类型,比如 tianon/centos 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。可以通过前缀 user_name/ 来指定使用某个用户提供的镜像,比如 tianon 用户。

1
2
3
docker pull apline
// or
docker pull mhart/alpine-node-auto

提交

修改当前镜像之后需要提交修改后的镜像,修改一次镜像包括提交的过程如下:

先使用下载的镜像启动容器。

1
2
docker run -t -i training/sinatra /bin/bash
root@0b2616b0e5a8:/#

注意:记住容器的 ID,稍后还会用到。
在容器中添加 json package(一个 ruby gem)。

1
root@0b2616b0e5a8:/# gem install json

当结束后,我们使用 exit 来退出,现在我们的容器已经被我们改变了,使用 docker commit 命令来提交更新后的副本。

1
2
docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2
4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c

其中,-m 来指定提交的说明信息,跟我们使用的版本控制工具一样;-a 可以指定更新的用户信息;之后是用来创建镜像的容器的 ID;最后指定目标镜像的仓库名和 tag 信息。创建成功后会返回这个镜像的 ID 信息。

使用 docker images 来查看新创建的镜像。

之后,可以使用新的镜像来启动容器

1
2
$ docker run -t -i ouruser/sinatra:v2 /bin/bash
root@78e82f680994:/#

上传

用户可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享。例如,用户在 Docker Hub 上完成注册后,可以推送自己的镜像到仓库中。

1
2
3
4
$ docker push ouruser/sinatra
The push refers to a repository [ouruser/sinatra] (len: 1)
Sending image list
Pushing repository ouruser/sinatra (3 tags)

修改镜像的标签

用 docker tag 命令来修改镜像的标签。

1
2
3
4
5
6
$ sudo docker tag 5db5f8471261 ouruser/sinatra:devel
$ sudo docker images ouruser/sinatra
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ouruser/sinatra latest 5db5f8471261 11 hours ago 446.7 MB
ouruser/sinatra devel 5db5f8471261 11 hours ago 446.7 MB
ouruser/sinatra v2 5db5f8471261 11 hours ago 446.7 MB

利用 Dockerfile 来创建镜像

使用 docker commit 来扩展一个镜像比较简单,但是不方便在一个团队中分享。我们可以使用 docker build 来创建一个新的镜像。为此,首先需要创建一个 Dockerfile,包含一些如何创建镜像的指令。

Dockerfile 中每一条指令都创建镜像的一层,例如:

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
# Dockerfile.alpine
FROM mhart/alpine-node:latest
MAINTAINER zhanfang "fzhanxd@gmail.com"
# If you have native dependencies, you'll need extra tools
# RUN apk add --no-cache make gcc g++ python
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# If your project depends on many package, you can use cnpm instead of npm
# RUN npm install cnpm -g --registry=https://registry.npm.taobao.org
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install --registry=https://registry.npm.taobao.org
# Bundle app source
COPY . /usr/src/app
# Expose port
EXPOSE 3000
CMD [ "npm", "start" ]

编写完成 Dockerfile 后可以使用 docker build 来生成镜像。

1
docker build -t alpine-koa2-startkit .

其中 -t 标记来添加 tag,指定新的镜像的用户信息。 “.” 是 Dockerfile 所在的路径(当前目录),也可以替换为一个具体的 Dockerfile 的路径。

Dockfile 中的指令被一条一条的执行。每一步都创建了一个新的容器,在容器中执行指令并提交修改(就跟之前介绍过的 docker commit 一样)。当所有的指令都执行完毕之后,返回了最终的镜像 id。所有的中间步骤所产生的容器都被删除和清理了。

注意一个镜像不能超过 127 层

完成一次镜像的下载、修改和上传

以alpine-node-auto镜像为基础

  1. 下载基础镜像

    1
    docker pull mhart/alpine-node-auto
  2. 编写dockerfile

    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
    # Dockerfile.alpine
    FROM mhart/alpine-node:latest
    MAINTAINER zhanfang "fzhanxd@gmail.com"
    # If you have native dependencies, you'll need extra tools
    # RUN apk add --no-cache make gcc g++ python
    # Create app directory
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    # If your project depends on many package, you can use cnpm instead of npm
    # RUN npm install cnpm -g --registry=https://registry.npm.taobao.org
    # Install app dependencies
    COPY package.json /usr/src/app/
    RUN npm install --registry=https://registry.npm.taobao.org
    # Bundle app source
    COPY . /usr/src/app
    # Expose port
    EXPOSE 3000
    CMD [ "npm", "start" ]
  3. 创建新的镜像

    1
    docker build -t=zhanfang/alpine-koa2-startkit:v1" .

    打上 alpine-koa2-startkit:v1 的 tag

  4. 运行一个容器实例

    1
    docker run -t -i --name alpine-koa2-application zhanfang/alpine-koa2-startkit:v2
  5. 上传镜像(如果没有登录请登陆,使用login命令)

    1
    docker push zhanfang/alpine-koa2-startkit

原文地址:http://zhanfang.github.io/2016/07/22/display属性详解 转载请注明地址及作者: zhanfang

最近瞎忙了好长一段时间,一直没时间写文章,想深入学习一下css的相关属性,所以有了这篇文章,如有错误请指针。

display的所有属性

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
/* CSS 1 */
display: none;
display: inline;
display: block;
display: list-item;
/* CSS 2.1 */
display: inline-block;
display: table;
display: inline-table;
display: table-cell;
display: table-column;
display: table-column-group;
display: table-footer-group;
display: table-header-group;
display: table-row;
display: table-row-group;
display: table-caption;
/* CSS 2.1 */
/* CSS 3 */
display: inline-list-item;
display: flex;
display: box;
display: inline-flex;
display: grid;
display: inline-grid;
display: ruby;
display: ruby-base;
display: ruby-text;
display: ruby-base-container;
display: ruby-text-container;
/* CSS 3 */
/* Experimental values */
display: contents;
display: run-in;
/* Experimental values */
/* Global values */
display: inherit;
display: initial;
display: unset;

下面就display的重要属性进行讲解,并配合一些相关的例子

基本属性

display: none

none 是 CSS 1 就提出来的属性,将元素设置为none的时候既不会占据空间,也无法显示,相当于该元素不存在。

该属性可以用来改善重排与重绘,同时我也经常用它来做模态窗等效果。

display: inline

inline也是 CSS 1 提出的属性,它主要用来设置行内元素属性,设置了该属性之后设置高度、宽度都无效,同时text-align属性设置也无效,但是设置了line-height会让inline元素居中

Alt

同时从上图可以看到两个inline标签之间出现了奇怪的间隔,改间隔的原因是div换行产生的换行空白,解决办法

  • 将两个inline标签写到一行
1
2
3
<body>
<div class="test">123</div><div class="test">123</div>
</body>
  • 或者使用一点技巧
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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="main">
<div class="test">zhan</div>
<div class="test">123</div>
</div>
</body>
</html>
html{
-webkit-text-size-adjust:none;/* 使用webkit的私有属性,让字体大小不受设备终端的调整,可定义字体大小小于12px */
}
.main{
font-size:0;
*word-spacing:-1px;/* 使用word-spacing 修复 IE6、7 中始终存在的 1px 空隙,减少单词间的空白(即字间隔) */
}
.test{
display:inline;
width: 10000px;
height:10000px;
border:1px solid;
font-size:12px;
letter-spacing: normal;/* 设置字母、字间距为0 */
word-spacing: normal; /* 设置单词、字段间距为0 */
}

实测chome49浏览器只用设置父元素的font-size为0即可。

目前有很多原生的元素都是inline的,span、a、label、input、 img、 strong 和em就是典型的行内元素元素。
链接:http://www.css88.com/archives/646

display: block

设置元素为块状元素,如果不指定宽高,默认会继承父元素的宽度,并且独占一行,即使宽度有剩余也会独占一行,高度一般以子元素撑开的高度为准,当然也可以自己设置宽度和高度。

在设计的过程中有时需要设计一个div宽高都是整个屏幕,整个时候宽度很好设置,可是高度一般很难设置,因为页面一般都是可以滚动的,所以高度一般可变,所以这个时候可以使用一个小技巧,如下。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="main">
</div>
</body>
</html>
html{
height: 100%;
}
body{
height: 100%;
padding: 0;
margin:0;
}
.main{
background: red;
width: 100%;
height: 100%;
}

基本原理:div继承的是父元素body的高度,body是继承html的高度,html是继承的浏览器屏幕的高度。

display: list-item

此属性默认会把元素作为列表显示,要完全模仿列表的话还需要加上 list-style-positionlist-style-type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div>
<span>111111</span>
<span>222222</span>
<span>333333</span>
</div>
</body>
</html>
1
2
3
4
5
6
7
8
div{
padding-left:30px;
}
span{
display:list-item;
list-style:disc outside none;
}

效果图如下:
Alt

通过上面样式设置,就能仿出一个类似的列表,一定要在div上加padding,因为默认的列表之前的·在box外面

display: inline-block

inline-block为 CSS 2.1 新增的属性。 inline-block既具有block的宽高特性又具有inline的同行元素特性。 通过inline-block结合text-align: justify 还可以实现固定宽高的列表两端对齐布局,如下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="main">
<div class="col col1">111111</div>
<div class="col col2">222222</div>
<div class="col col3">333333</div>
<div class="col col1">111111</div>
<div class="col col2">222222</div>
<div class="col col3">333333</div>
<div class="col fix">&nbsp;</div>
<div class="col fix">&nbsp;</div>
<div class="col fix">&nbsp;</div>
<div class="col fix">&nbsp;</div>
<div class="col fix">&nbsp;</div>
</div>
</body>
</html>
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
body{
margin:0;
padding:0;
}
.main{
text-align:justify;
}
.col{
display: inline-block;
margin-top:10px;
width:100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
}
.col1{
background: red;
}
.col2{
background: green;
}
.col3{
background: blue;
}
.fix{
height:0;
padding:0;
overflow:hidden;
}

效果图如下:
Alt

text-align: justify 属性会使行内元素两端对齐,但是要求这些行内元素总宽度至少占满一行,所以在总宽度不足一行的时候这个属性没用,因此在最后需要加上一些占位符。

详情可以查看 张鑫旭老师的博客

Tip: inline-block会形成一个BFC

display: table

table 此元素会作为块级表格来显示(类似table),表格前后带有换行符。CSS表格能够解决所有那些我们在使用绝对定位和浮动定位进行多列布局时所遇到的问题。例如,display:table的CSS声明能够让一个HTML元素和它的子节点像table元素一样。使用基于表格的CSS布局,使我们能够轻松定义一个单元格的边界、背景等样式, 而不会产生因为使用了table那样的制表标签所导致的语义化问题。

利用table的特性,我们能够轻易的实现三栏布局,并且能够兼容IE8,如下是使用table属性,实现三栏布局的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="main">
<div class="tr tr1">
<div class="td">head1</div>
<div class="td">head2</div>
<div class="td">head3</div>
</div>
<div class="tr tr2">
<div class="td">123</div>
<div class="td">123</div>
<div class="td">123</div>
</div>
</div>
</body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.main{
display: table;
width:100%;
border-collapse: collapse;/*为表格设置合并边框模型:*/
}
.tr{
display: table-row;
border-color: inherit;
}
.tr1 .td{
height:50px;
vertical-align: middle;
}
.td{
display: table-cell;
border: 1px solid;
}
.td:nth-of-type(1){
width: 100px;
}
.td:nth-of-type(3){
width: 100px;
}

效果如下图:
Alt

CSS2.1表格模型中的元素,可能不会全部包含在除HTML之外的文档语言中。这时,那些“丢失”的元素会被模拟出来,从而使得表格模型能够正常工作。所有的表格元素将会自动在自身周围生成所需的匿名table对象,使其符合table/inline-table、table-row、table-cell的三层嵌套关系。

所以在一般情况下我们也可以不写外面的table-row元素以及table元素。

display: inline-list-item

我在MDN上面看到有这个属性,但是我实际尝试发现这个属性是不能使用的,在 http://caniuse.com/#search=inline-list-item 上面也没有找到这个元素的兼容性,所以应该是不能使用的,支持度全无。

display: flex

flex是一种弹性布局属性
注意,设为Flex布局以后,子元素的float、clear和vertical-align属性将失效。
主要属性有两大类:容器属性和项目的属性

容器属性

  • flex-direction: 属性决定主轴的方向(即项目的排列方向)。
  • flex-wrap: 默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行。
  • flex-flow: 属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
  • justify-content: 属性定义了项目在主轴上的对齐方式。
  • align-items: 属性定义项目在交叉轴上如何对齐。
  • align-content: 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

项目属性

  • order: 定义项目的排列顺序。数值越小,排列越靠前,默认为0。
  • flex-grow: 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  • flex-shrink: 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  • flex-basis: 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
  • flex: 属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
  • align-self: 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

以上关于flex的基础知识基本是从阮一峰老师那copy过来的,有兴趣的同学,可以到阮一峰老师的博客深入阅读

http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
http://www.ruanyifeng.com/blog/2015/07/flex-examples.html

实例:实现一个固定宽度但内容可变的列表

目前我有一个需求,有一个列表页,左侧固定,右侧固定,总宽度固定,但是左侧的内容可能会增加,右侧的内容也可能会增加,要求平时一行展示,增加的时候两行展示,左侧两行,右侧还是一行,并且都居中。

先上效果图,不然可能会迷糊:
Alt

为了实现上述效果,代码如下

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="main">
<ul>
<li>
<span class="col1">累积的分为:123</span>
<div class="col2">
<span>123</span>
<span>x 10</span>
<button>submit</button>
</div>
</li>
<li>
<span class="col1">累积的分为:1234</span>
<div class="col2">
<img src="http://7xltvd.com1.z0.glb.clouddn.com/css1.png" alt="">
<span class="col2-span">x 10</span>
<button>submit</button>
</div>
</li>
</ul>
</div>
</body>
</html>
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
.main{
height: 200px;
width: 300px;
border: 1px solid;
}
ul{
padding: 0px;
margin-top: 10px;
}
li{
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
margin-bottom: 10px;
border: 1px solid;
}
button{
height: 20px;
vertical-align: middle;
border:0;
background: green;
outline:none;
}
img{
width:30px;
vertical-align: middle;
}
.col2-span{
vertical-align: middle;
}
.col1{
width: 130px;
padding-left:8px;
}
.col2{
padding-right: 8px;
vertical-align: middle;
}

display: -webkit-box

由于某X5浏览器某些版本还不支持最新版的flex布局,所以为了保证良好的运行,建议还是使用display: box,box和flex布局的主要差别如下:

容器属性
  • display: box
    该显示样式的新值可将此元素及其直系子代加入弹性框模型中。Flexbox 模型只适用于直系子代。
  • box-orient
    值:horizontal | vertical | inherit
    框的子代是如何排列的?还有两个值:inline-axis(真正的默认值)和 block-axis,但是它们分别映射到水平和垂直方向。
  • box-pack
    值:start | end | center | justify
    设置沿 box-orient 轴的框排列方式。因此,如果 box-orient 是水平方向,就会选择框的子代的水平排列方式,反之亦然。
  • box-align
    值:start | end | center | baseline | stretch
    基本上而言是 box-pack 的同级属性。设置框的子代在框中的排列方式。如果方向是水平的,该属性就会决定垂直排列,反之亦然。
项目属性
  • box-flex
    值:0 | 任意整数
    该子代的弹性比。弹性比为 1 的子代占据父代框的空间是弹性比为 2 的同级属性的两倍。其默认值为 0,也就是不具有弹性。
用box改造上述例子

基本只修改了容器元素li的属性,如下所示

1
2
3
4
5
6
7
8
9
li{
display: -webkit-box;
-webkit-box-orient:horizontal;
-webkit-box-pack: justify;
-webkit-box-align: center;
padding: 10px 0;
margin-bottom: 10px;
border: 1px solid;
}

display: inline-flex

我发现在chrome条件下设置了inline-flex,其子元素全部变成了inline模式,设置flex并没有什么用,不知道是不是我写的有问题,目前没找到这个属性的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="main">
<div class="sp1">123</div>
<div class="sp1">123</div>
</div>
</body>
</html>
1
2
3
4
5
6
7
.main{
display: -webkit-inline-flex;
justify-content: center;
}
.sp1{
flex:1;
}

其他

以下属性是实验性质的,支持度都很低,不建议使用,知道就行。

  • run-in: 此元素会根据上下文作为块级元素或内联元素显示;
  • grid: 栅格模型,类似block
  • inline-grid: 栅格模型,类似inline-block
  • ruby, ruby-base, ruby-text, ruby-base-container, ruby-text-container
  • contents

背景

最近一直在做react和vue方面的研究,发现这两种解决方案所解决的问题基本是相似的:

  • 组件化:将页面分成很多组件进行编写,这样可复用性好,而且逻辑清晰,肯定是趋势
  • 路由管理:react有react-router,vue有vue-router,基本都是解决前端路由的问题,当然react拥有后台渲染的能力,所以可以前后端共享一套路由,这个比较爽,vue2即将也要推出服务器渲染了,值得期待
  • 数据管理:这块基本就是redux和vuex,这两个基本一致

但是我在做页面的时候还有一个问题怎么也绕不过去:权限管理怎么处理?

简单点说就是在用户登录和未登录时,展示的页面是不同的,有些页面可以访问,有些不能,需要做页面间的跳转。

下面就以vue为例子介绍我在权限管理方面做的尝试

另附上我使用vue-cli作为基础构建工具,结合markdown插件做的带登录、注册、在线保存功能的todolist,访问地址 http://vue.demozhan.com

权限管理以往解决办法

在以往的处理办法中,我一般使用的是服务器存储session,客户端存储cookie,当页面访问的时候会携带带有服务器sessionid的cookie信息,通过查询session做处理,已经登录的用户自动跳转,未登录的用户会跳转到登录页面。

上面这种方法适用于传统的网页跳转类型的网站,路由的相关处理其实是在服务器端,但对于单页网站,路由在前端处理的就不太合适了,因为前端页面的跳转是通过前端路由来完成的,特别是vue我发现,在地址栏输入url是完全被前端路由拦截了,根本不会向后台发送请求,react还是会发请求的(个人理解,如有不正确的地方请指出),所以无法通过携带cookie方式进行重定向来做权限管理。

vue解决前端路由的几种方式

vuex root状态树中保存登陆信息

有一种解决思路,在vuex存储登陆/认证信息,一旦登陆了state中就保留了登陆的信息。

但是这个方案存在一个问题,当用户刷新页面的时候状态树会被重置,用户登录的信息就不存在了。

异步查询状态方法

目前vue-cli-note解决权限管理的方法是通过异步查询状态的方法,即在页面初始化的时候会异步发送请求给服务器判断是否登录,目前的确可以解决权限的问题。

但这个方案也存在一个问题,页面需要发送很多不必要的http请求,我觉得这个方案不够优雅。

使用localstorage/cookie存储状态

背景

目前在用koa+react+redux搭建一个微信后台,需要用到webpack热加载的方式方便进行开发,同时参考redux官方例子的配置方式,发现process.env.NODE_ENV一直是undefined,所以有了这篇文章。

本文主要介绍express以及koa中webpack热加载的实现方式,同时解决process.env.NODE_ENV传递的问题。

express热加载

通过引入webpack热加载的中间件即可,如下所示

server.js

1
2
3
4
5
6
7
8
9
10
var webpack = require('webpack')
var webpackDevMiddleware = require('webpack-dev-middleware')
var webpackHotMiddleware = require('webpack-hot-middleware')
var config = require('./webpack.config')
var compiler = webpack(config)
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}))
app.use(webpackHotMiddleware(compiler))

webpack.config.js中引入热加载插件,添加entry入口,以及publicPath

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
var path = require('path')
var webpack = require('webpack')
module.exports = {
devtool: 'cheap-module-eval-source-map',
entry: [
'webpack-hot-middleware/client',
'./index'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/static/'
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin()
],
module: {
loaders: [
{
test: /\.js$/,
loaders: [ 'babel' ],
exclude: /node_modules/,
include: __dirname
}
]
}
}

完成以上文件的修改即可实现启动node服务器实现热加载

koa中实现热加载

在koa中如果直接使用express的加载方式,会造成每次触发请求以及返回请求都会重新打包。

解决办法:直接使用koa-webpack-dev-middleware以及koa-webpack-dev-middleware,当然阅读这两个中间件源码可以发现它们的加载方式实际上还是和express差不多,只是在执行中间件的时候就已经完成了热加载的过程,如下:

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
var expressMiddleware = require('webpack-dev-middleware');
function middleware(doIt, req, res) {
var originalEnd = res.end;
return function (done) {
res.end = function () {
originalEnd.apply(this, arguments);
done(null, 0);
};
doIt(req, res, function () {
done(null, 1);
})
}
}
module.exports = function (compiler, option) {
var doIt = expressMiddleware(compiler, option);
return function*(next) {
var ctx = this;
var req = this.req;
var runNext = yield middleware(doIt, req, {
end: function (content) {
ctx.body = content;
},
setHeader: function () {
ctx.set.apply(ctx, arguments);
}
});
if (runNext) {
yield *next;
}
};
};

在koa中的使用方式

1
2
3
4
5
6
7
8
9
10
var webpack = require('webpack');
var webpackDevMiddleware = require('koa-webpack-dev-middleware');
var webpackHotMiddleware = require('koa-webpack-hot-middleware');
var config = require('./webpack.config')
var compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(webpackHotMiddleware(compiler));

react中 process.env.NODE_ENV undefined

这个问题实际上是因为react或者前端本省并不可能获取到process.env.NODE_ENV,只能通过后台传递参数到前端才可以。

解决方法:在webpack中有一个插件可以解决这个问题

webpack.config.js(列出部分代码)

1
2
3
4
5
6
7
8
9
10
11
var env = process.env.NODE_ENV;
var config = {
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(env)
})
],
}

背景

这两天在学习redux,一直比较困惑在在dispatch action的时候是如何触发reducer的状态转移的,所以今晚好好研究了一下

redux管理状态

Alt
整个状态树store是通过createStore创建的createStore方法接收reducer函数初始化的数据(currentState),并将这2个参数并保存在store中。

action和reducer的绑定

在redux中触发状态转移一般是通过

dispatch(action(text));

或者

actions.action(text);

但是在action()中并没有指定redux,所以action是怎么调用redux状态转移的呢?

解释

实际上reducer函数的名字并没有什么用,只是为了指定子reducer的名字,便于生成rootReducer的时候使用,以及生成初始的state。

而action与reducer之间的绑定是通过action.type来实现的,同时不同的reducer只要带有相同 type 属性的都会执行。

实例

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
import { combineReducers } from 'redux';
import todoApp from './todos';
import { ADD_TODO } from '../constants/ActionTypes';
const initilState = [
{
text: 'Use Redux',
completed: false,
id: 0
}
];
function test(state = initilState, action) {
switch (action.type) {
case ADD_TODO:
let id = state.reduce((maxId, currTodo) => {
return Math.max(currTodo.id, maxId);
}, -1) + 1;
return [
{
text: action.text,
completed: true,
id: id
},
...state
];
break;
default:
return state;
}
}
const rootReducer = combineReducers({
todoApp,
test
});
export default rootReducer;

上面例子中定义了一个todoApp的reducer,并且添加了一个test的reducer,都有相同的action.type为ADD_TODO

最后调试发现产生了两个storeState,并且case ADD_TODO都执行了。

后记

今天在做异步请求数据的时候发现几个redux的小技巧

reducer中利用相同的action.type进行处理

由于相同的action.type都会在触发action的时候执行,因此可以利用action.type做类似分支的效果,见如下代码

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
const initPosts = {
isFetching: false,
didInvalidate: false,
items: []
};
function posts(state = initPosts, action) {
switch (action.type) {
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
})
break;
default:
return state;
}
}
function postsByReddit(state = {}, action) {
switch (action.type) {
case RECEIVE_POSTS:
case REQUEST_POSTS:
return Object.assign({}, state, {
[action.reddit]: posts(state[action.reddit], action)
})
default:
return state
}
}
const rootReducer = combineReducers({
selectedReddit,
postsByReddit,
})

需要进行状态的改变可以利用componentWillReceiveProps

1
2
3
4
5
6
componentWillReceiveProps(nextProps) {
if (nextProps.selectedReddit !== this.props.selectedReddit) {
const {selectedReddit, actions} = nextProps;
actions.fetchPosts(selectedReddit);
}
}

首先说明我最后还是没有使用react服务器渲染,一方面是因为我突然发现我不怎么需要服务器渲染,另一方面遇到的坑太多了,而且大多解决方法都是不断的重新安装依赖包,心太累了。

背景

目前我正在做的东西,后台已经用koa搭建好了,可是前端我想折腾一下就作死的用react,已经ant-design尝试了一下,单做前台页面还是ok的,但是一旦和后台一起就有很多问题。

  1. 路由同步怎么解决;
  2. 服务器渲染怎么解决;
  3. reflux状态管理怎么实现;

本文只讲一下第一个和第二个问题,对于服务器渲染其实还有很多讲究,哪些内容需要服务器渲染,哪些不需要(首屏服务器渲染,次屏客户端渲染),怎么实现,这些问题研究下去实在太深了,我要消化消化,此外本文主要讲解实现上的东西,原理我好多也没弄明白。

路由同步+服务器渲染

引入node中加载react的依赖
package.json

1
2
3
"babel-preset-es2015": "^6.1.18",
"babel-preset-react": "^6.1.18",
"babel-register": "^6.3.13",

server.js中加载es6和react的相关依赖

1
2
3
4
5
6
7
8
9
10
require('babel-register')({
presets: ['es2015', 'react']
});
//---------react服务器渲染及路由匹配----------
var React = require('react');
var ReactDOM = require('react-dom/server');
var ReactRouter = require('react-router');
var reactR = require('./app/routes');
var swig = require('swig');
//-----------------------------------------

编写koa中间件

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
app.use(function*(next) {
var self = this;
debug('self.path is %s', self.path);
ReactRouter.match({
routes: reactR.default,
location: self.path
}, function(err, redirectLocation, renderProps) {
debug('redirectLocation is %s', redirectLocation);
debug('err is %s', err);
if (err) {
self.status = 500;
} else if (redirectLocation) {
self.redirect(redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
var html = ReactDOM.renderToString(React.createElement(ReactRouter.RouterContext, renderProps));
debug('html page is %s', html);
var page = swig.renderFile('views/index.html', {
html: html
});
self.body = swig.renderFile('views/index.html');
} else {
self.status = 404;
}
});
yield next;
});

背景

最近一定是太“闲”了,遇到一个问题非要深究下去,最近在做koa-weixin的时候遇到几个问题,当然也是因为之前学习不够深入造成的;

  1. 默认使用koa-generic-sessionkoa-redis配合使用完成session机制,可是存在一个问题,默认cookie保存时间为一天,我认为关闭浏览器cookie就会被销毁,结果第二天打开网站发现能够登陆;
  2. 修改cookie保存时间可行,但是却始终无法使得关闭浏览器cookie失效;
  3. 使用fetch异步发送数据,无法发送cookie;

cookie及session原理

保持一个会话的方式是利用session存储在服务器,常见的存储方式是存储在内存、文件、redis和数据库,不管使用哪种方式,都需要产生一个session,并将session id存储到cookie,通过将cookie发送给客户端来保持连接,在每次客户端发送请求的时候,都会带上cookie的相关信息,其中就包括session id,在服务器端通过session id来查询session的相关信息

下面内容转载自 虫师 :

我们都知道HTTP协议本身是无状态的,客户只需要简单的向服务器来发送请求下载某些文件,客户端向服务器端发送的每次请求都是独立的。对于当前的web应用,HTTP的“无状态”,导致许多应用都不得不花费大量的精力来记录用户的操作步骤。就像我们上面介绍的第一种情况,银行职员要花费大量的精力来记忆每一位用户的存/取款记录。

程序员很快发现,如果能够提供一些按需生成的动太信息,会使web的交互能力大大增强。程序员一方面在HTML中添加表单、脚本、DOM等客户端行为,来增加web应用与客户端的交互性。另一方面在服务器端测出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议添加了文件上载、cookie 等特性。那cookie的原理与我们上面介绍的使用存折记录用户应为的方式是一样一样的。

通过前面的例子我们已经发现,通过cookie的方式存储信息,可能会存在一点定的安全性,因为所有的信息都是写在客户端的,客户可能会对这些信息进行修改或清除。然后就又出现session的方式用于保存用户行为,这种方式的原理与前面介绍银行卡的方式是一样的。

具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

cookie机制。

正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。

cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。

session机制

session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识————称为session id,如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。

保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。

经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。

Jsessionid

Jsessionid只是tomcat的对sessionid的叫法,其实就是sessionid;在其它的容器也许就不叫jsessionid了。

关闭浏览器cookie清除

cookie 参数:

  • path:表示 cookie 影响到的路径,匹配该路径才发送这个 cookie。
  • expires 和 maxAge:告诉浏览器这个 cookie 什么时候过期,expires 是 UTC 格式时间,maxAge 是 cookie 多久后过期的相对时间。当不设置这两个选项时,会产生 session cookie,session cookie 是 transient 的,当用户关闭浏览器时,就被清除。一般用来保存 session 的 session_id。
  • secure:当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。
  • httpOnly:浏览器不允许脚本操作 document.cookie 去更改 cookie。一般情况下都应该设置这个为 true,这样可以避免被 xss 攻击拿到 cookie。

所以为了使得关闭浏览器清除cookie应该不设置expires/max-age,深层的原理,当设置expires/max-age后,浏览器默认会把cookie存放到硬盘里面,所以即使关闭浏览器,甚至关闭电脑后,下次打开浏览器仍然会保存着cookie的信息。

可是查看koa-generic-session的源码可以发现:

1
2
3
4
5
6
7
const defaultCookie = {
httpOnly: true,
path: '/',
overwrite: true,
signed: true,
maxAge: 24 * 60 * 60 * 1000 //one day in ms
};

maxAge默认时间为1天,所以需要修改源码为:

1
2
3
4
5
6
7
const defaultCookie = {
httpOnly: true,
path: '/',
overwrite: true,
signed: true,
maxAge: null //one day in ms
};

如果有需要可以在index.js中设置

1
2
3
4
5
6
7
8
9
10
app.use(session({
store: redisStore(),
cookie: {
httpOnly: true,
path: '/',
overwrite: true,
signed: true,
maxAge: null //one hour in ms
}
}));

好了大功告成

fetch中cookie问题

理论上所以http请求都应该带上cookie,可是fetch比较坑

Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: ‘include’})

ajax版本

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
var key = document.getElementById('title').value;
var val = document.getElementById('lab-create-input').value;
if (key.trim() === '' || val.trim() === '') {
alert('关键字或者回复,不能为空');
} else {
var data = {
key: key,
val: val
};
var xhr = new XMLHttpRequest();
xhr.open('post', '/weixin/addK', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var result,
status = xhr.status;
if ((status >= 200 && status < 300) || status === 304) {
result = xhr.responseText;
console.log(result);
} else {
console.log("ERR", xhr.status);
}
}
};
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
}

fetch版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var key = document.getElementById('title').value;
var val = document.getElementById('lab-create-input').value;
if (key.trim() === '' || val.trim() === '') {
alert('关键字或者回复,不能为空');
} else {
var data = {
key: key,
val: val
};
data = JSON.stringify(data);
fetch('/weixin/addK', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: data
});
}

fetch坑

  • Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: ‘include’})
  • 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
  • IE8 IE9的XHR不支持CORS跨域,推荐使用fetch-jsonp

参考

简书 KeKeMars

背景

最近在做一个基于koa的微信公众平台脚手架(过一段时间会放到我的github上),由于我只有一个域名demozhan.com,现在同时有好几个web应用在上面挂载着,所以只能做一下反向代理,可是反向代理只能代理动态文件,对于静态资源貌似没有什么好的解决方法

本文针对以上问题进行逐步解决,通过修改nginx相关配置,并结合koa-router以及koa-static,就可以解决上述问题。

配置koa-static解决端口下静态文件的问题

1
2
3
var serve = require('koa-static');
// 使用./public下的静态文件
app.use(serve(__dirname + '/public'));

通过以上配置就可以将public文件夹作为静态文件夹,在请求http://localhost:3333/login/css/index.css的时候就会去查找本地文件夹

通过以上过程就解决了,不做反向代理情况下的静态文件的配置

配置nginx实现反向代理

配置nginx

1
2
3
location /weixin {
proxy_pass http://localhost:3333;
}

这个没什么好讲的,可是直接这样的操作导致整个页面都访问不到

直接反向代理请求/weixin/login/会代理到3333端口,但是请求的path依旧是/weixin/login/

解决方案:添加router前缀,保证请求链接一致

1
2
3
var router = new Router({
prefix: '/weixin'
});

通过以上方法,请求/weixin/login链接就对应router中的login

静态文件失效

通过以上配置,login页面渲染出来了,可是加载的css文件都404了。

通过调试koa-static分析原因,由于修改了router的前缀了,所以所有静态文件请求都变成/weixin/css/login.css;请求链接错误

解决方法

大概思路就是通过传入一个参数,表示要去除的路径,改变path路径,koa-static的具体原理我还没有深入

修改koa-static源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (!opts.defer) {
return function * serve(next) {
if (this.method == 'HEAD' || this.method == 'GET') {
//在默认情况下this.path = '/css/style.css' 会转变为本地.../public/css/style.css文件路径
//由于做了反向代理this.path = '/weixin/css/style.css'
//由于做了反向代理需要修改静态文件的path = '/css/style.css'
var path
if (opts.proxy) {
path = this.path.replace(opts.proxy, '');
} else {
path = this.path;
}
if (yield send(this, path, opts)) return;
}
yield* next;
};
}

汇总

index.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
var koa = require('koa');
var Router = require('koa-router');
var serve = require('koa-static');
var app = koa();
var Weixin = require('./weixin/weixin')('zhanfang');
var router = new Router({
prefix: '/weixin'
});
//路由配置
router.get('/login', Weixin.webLogin());
// 使用./public下的静态文件
app.use(serve(__dirname + '/public', {
proxy: '/weixin'
}));
app.use(logger());
app.use(router.routes())
.use(router.allowedMethods());
app.on('error', function(err) {
console.log(err);
})
app.listen(3333);

login.html的部分代码

1
2
<link rel="stylesheet" href="css/style.css" charset="utf-8">
<link rel="stylesheet" href="css/login.css" charset="utf-8">

真是比了狗了,弄了一早上react,写个最简单的组件都能出错,错误提示如下

1
2
3
4
5
6
7
8
9
ERROR in ./src/index.jsx
Module build failed: SyntaxError: /Users/zhan/Documents/study/weui/my-weui-base/src/index.jsx: Unexpected token (16:6)
14 | render() {
15 | return (
> 16 | <Button>hello wechat</Button>
| ^
17 | );
18 | }
19 | }

最后上网一看发现有人也遇到这个问题,他说babel的版本不能高于6.4,结果我把package.json改为如下形式,就通过了,简直醉了

1
2
3
"babel": "^5.8.23",
"babel-core": "^5.8.23",
"babel-loader": "^5.3.2",

今天开始使用Atom + eslint,感觉很不错

{
    // 配置ES6支持
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    },
  },
  "rules": {
          // 配置缩进
    "indent": [2, 2],
    "no-unused-vars": 2,
    "no-alert": 1
  },
  //配置支持环境
  "env": {
    "browser": true,
    "node": true,
  }
}

还可以在 package.json中进行配置,如下:

"name": "mypackage",
"version": "0.0.1",
"eslintConfig": {
    "env": {
        "browser": true,
        "node": true
    }
}

之前就用过webpack,只是一直是知其然而不知其所以然,所以决定自己配置一遍,还是遇到了一些坑

webpack的优点

  • 对 CommonJS 、 AMD 、ES6的语法做了兼容
  • 对js、css、图片等资源文件都支持打包
  • 有独立的配置文件webpack.config.js
  • 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间
  • 支持 SourceUrls 和 SourceMaps,易于调试
  • 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活
  • webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快

利用npm script启动webpack

webpack并没有提供自动刷新的功能,导致每次修改代码都必须要再执行一次webpack,因此需要使用webpack-dev-server

webpack-dev-server 提供了两种模式用于自动刷新页面:

以上说的两个页面自动刷新的模式都是指刷新整个页面,相当于点击了浏览器的刷新按钮。

webpack-dev-server 还提供了一种 –hot 模式

"scripts": {
  "build": "webpack -d",
  "dev": "webpack-dev-server -d --inline --devtool eval --progress --colors --content-base dist"
},
  • webpack-dev-server: 在 localhost:8080 建立一个 Web 服务器
  • -d: 提供source map,方便调试。
  • –inline: 自动刷新
  • –devtool eval: 为你的代码创建源地址。当有任何报错的时候可以让你更加精确地定位到文件和行号
  • –progress: 显示合并代码进度
  • –colors: 在命令行中显示颜色
  • –content-base dist:指向设置的输出目录

example

// 为了保证windows下面路径正确,最好使用path路径
var path = require('path');

module.exports = {
    // 入口文件,一般利用入口文件来引入其他模块
  entry: [
    path.resolve(__dirname, 'src/entry.js')
  ],
  // 打包后的文件
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  // 官方解释:https://webpack.github.io/docs/library-and-externals.html
  // 我认为是整个运行过程中都需要依赖的库,react应用程序运行过程中一直需要react库
  externals: {
    'react': 'React'
  },
  // 模块加载关系
  module: {
    loaders: [{
         // 对于.js结尾的文件以es6方式加载
      test: /\.js$/,
      // loader: jsx!babel = loaders: ['style', 'css']
      loader: 'jsx!babel',
      // 包含的文件,千万注意不要使用''
      include: /src/
    }, {
      test: /\.css$/,
      loader: 'style!css'
    }]
  },
  // source-map 在新版chrome下要用cmd+p调出来
  devtool: "#inline-source-map"
}

include: /src/

如果写成

include: '/src/'

没有import和export的模式下编译并不会报错,但是在如下情况就会报错

import List from './List'

错误如下:

ERROR in ./src/component/App.js
Module parse failed: /Users/zhan/Documents/React/webpack/160315_webpack_study/src/component/App.js Line 2: Unexpected token
You may need an appropriate loader to handle this file type.
| 'use strict';
| import React from "react";
|
| class App {
 @ ./src/entry.js 2:0-26

Tip

source-map还需要深入研究一下

由于最近想重新设计弹窗效果频繁设计窗口大小,以及之前的滚动效果也涉及到滚动窗大小,所以决定弄清楚。

元素大小

这部分内容可参见《JAVASCRIPT高级程序设计第三版》第320页

偏移量

  • offsetHeight:包括元素的高度、(可见的)水平滚动条高度、上&下边框高度
  • offsetWidth:包括元素的宽度、(可见的)垂直滚动条高度、左&右边框高度
  • offsetLeft:元素左边框至包含元素的左内边框之间的像素距离

这里可以解释body元素即使存在margin,它的offsetLeft也为0。 因为body元素的offsetParent是null,也就是相当于它自身,所以它的offsetLeft为0。

console.log(document.body.offsetParent);//null
console.log(document.body.offsetLeft);//0
  • offsetTop:元素上边框至包含元素的上内边框之间的像素距离

客户区大小

  • clientWidth:内容加左右padding
  • clientHeight:内容加上下padding

滚动大小

  • scrollHeight:元素内容的高度
  • scrollWidth:元素内容的宽度
  • scrollLeft:被隐藏在内容区域左侧的像素数。通过设置这个属性可以改变元素的滚动位置
  • scrollTop:被隐藏在内容区域上方的像素数。通过设置这个属性可以改变元素的滚动位置

对于body来说它如果存在滚动条页面总高度就是

document.documentElement.scrollHeight;

对于运行在混杂模式的IE,需要用doument.body代替document.documentElement

确定元素大小

利用方法getBoundingClientRect(),返回一个矩形对象,包含left、top、right、bottom。IE8之前认为左上角坐标为(2,2),其他浏览器认为是(0,0)。

获取浏览器窗口大小

最佳方法

document.documentElement.clientHeight;
document.documentElement.clientWidth;

或者如下,但要求body的magrin为0

body{
    margin:0;    
}
document.body.clientHeight;
document.body.clientWidth;

同时,除了IE以外的所有浏览器都将此信息保存在window对象中,可以用以下获取:

window.innerHeight;
window.innerWidth;

参考

  • JAVASCRIPT高级程序设计(第三版)

页面初始化加载

曾经常用的两种写法

1
2
3
4
5
6
window.onload = function(){
$(".gravatar").on('click',function(){
//...
});
//以及其他操作DOM的节点
}

第二种方法

1
2
3
4
$(document).ready(function(){
//操作DOM相关
//...
})

页面加载

页面加载就是从你输入网址+enter开始,发生的一些列过程,最终到页面显示。 从微观上分的话,页面加载有两部分
一个是以DOMContentLoaded触发为标志的DOM树的渲染完成
一个是以辅助资源img,font加载完成为触发标志的onload事件
他们两个的具体区别就是”资源的加载”这个关键点。

在获得数据响应后,页面开始解析,发生的过程为:

  1. 解析HTML结构。
  2. 加载外部脚本和样式表文件。
  3. 解析并执行脚本代码。
  4. 构造HTML DOM模型。//ready执行
  5. 加载图片等外部文件。
  6. 页面加载完毕。//onload执行

这只是,页面加载很浅的一块,前端能在页面加载上做的工作其实超级多。 要知道, 从你输入网站 + enter键后,发生的过程为:

重定向=>检查DNS缓存=> DNS解析 => TCP连接 => 发请求 => 得到响应=> 浏览器处理 => 最后onload

宏观页面加载

如果我们想深入了解宏观页面加载,需要掌握ECMA5新给出的一个API。 performance . 是不是 感觉很熟悉呢?

performance简单讲解

以前,我们来检查浏览器的时候,大部分情况下是使用

console.time(specialNum);
console.timeEnd(specialNum);

或者

new Date().getTime();
//或者
Date.now();

上面说的两种方法, 获取的精度都是毫秒级(10^-6) ,对于一些非常精确的测试,他们的作用来还是蛮有限的,而且获取数据的方式,也比较complicated.

ES5提出的performance可以获取到,微秒级别(10^-9) 。而且,能够得到后台事件的更多时间数据。

他的兼容性是IE9+ 。 觉得已经足够了。

performance.timing对象

通常,我们可以从performance.timing对象上,获得我们想要的一切时间值
alt
比如,我们获得重定向时间用:

var time = performance.timing;
var redirect = time.redirectEnd - time.redirectStart; //单位为微秒

即DOMContentLoaded事件 是在domContentLoaded那段触发的。图中所指的domContentLoaded其实分为两块, 一个是domContentLoadedEventStart和domContentLoadedEventEnd. 详见下述说明:(from 赖小赖小赖)

performance.now()

通常,我们会将该方法和Date.now()进行一个对比。

performance.now(); //输出是微秒级别
Date.now(); //输出是毫秒级别
其中Date.now()是输出 从1970年开始的毫秒数.
performance.now()参考的是从.performance.timing.navigationStart(页面开始加载)的时间, 到现在的微秒数.
这里,我们可以使用performance.now()来模拟获取DomContentLoaded的时间。

1
2
3
4
5
6
7
8
9
10
var timesnipe = performance.now();
document.addEventListener('DOMContentLoaded', function() {
console.log(performance.now() - timesnipe);
}, false);
window.addEventListener('load', function() {
console.log(performance.now() - timesnipe);
}, false);
//但是这样并不等同于,只能算作约等于
performance.timing.domContentLoadedEventStart - performance.timing.domLoading; //检测domLoadEvent触发时间

上面不相等的原因就在于,当执行script的时候,DOM其实已经开始解析DOM和页面内容, 所以会造成时间上 比 真实时间略短。另外performance还有其他几个API,比如makr,getEntries. 不过,这里因为和页面显示的关系不是很大,这里就不做过多的讲解了。 有兴趣,可以参考:赖小赖小赖
接下来,我们一步一步来看一下,页面加载的整个过程.

redirect

这是页面加载的第一步(也有可能没有). 比如,当一个页面已经迁移,但是你输入原来的网站地址的时候就会发生。
或者, 比如example.com -> m.example.com/home 。 这样耗费的时间成本是双倍的。 这里就会经过两次DNS解析,TCP连接,以及请求的发送。所以,在后台设置好正确的网址是很重要的。
如图:
alt
这里,我们可以使用.performance的属性,计算出重定向时间

redirectTime = redirectEnd - redirectStart

cache,DNS,TCP,Request,Response

如果我们的域名输入正确的话,接着,浏览器会查询本地是否有域名缓存(appCache),如果有,则不需要进行DNS解析,否则需要对域名进行解析,找到真实的IP地址,然后建立3次握手连接, 发送请求, 最后接受数据。 通常,这一部分,可以做的优化有:
发送请求的优化:加异地机房,加CDN.(加快解析request)
请求加载数据的优化:页面内容经过 gzip 压缩,静态资源 css/js 等压缩(request到response的优化)
ok~ 使用performance测试时间为:

1
2
3
4
5
6
7
8
// DNS 缓存时间
times.appcache = t.domainLookupStart - t.fetchStart;
// TCP 建立连接完成握手的时间
times.connect = t.connectEnd - t.connectStart;
//DNS 查询时间
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;
//整个解析时间
var lookup = t.responseEnd - t.fetchStart;

其实,只要对照那个图查查over,不用太关注上面的式子。使用时需要注意,performance的相关操作,最好放在onload的回调中执行,避免出现异常的bug.

jquery ready事件浅析

jquery主要做的工作就是兼容IE6,7,8实现DOMContentLoaded的效果.由于现在主流只要兼容到IE8, 剩下IE6,7我们不做过多的分析了。
目前流行的做法有两种, 一种是使用readystatechange实现,另外一种使用IE自带的doScroll方法实现.

readyStateChange

这其实是IE6,7,8的特有属性,用它来标识某个元素的加载状态。 但是现在w3c规定,只有xhr才有这个事件。 所以,这里,我们一般只能在IE中使用readyStateChange否则,其他浏览器是没有效果的。
详见:readyState兼容性分析
这样,我们模拟jquery的ready事件时就可以使用:

document.onreadystatechange = function () {
  if (document.readyState == "interactive" || document.readyState == "complete") {
        //添加回调...
  }
}

理想很丰满,现实很骨感。 事实上, 当readyState为interactive时, Dom的结构并未完全稳定,如果还有其他脚本影响DOM时, 这时候可能会造成bug。 另外为complete时, 这时候图片等相关资源已经加载完成。 这个时候模拟触发DOMContentLoaded事件,其实和onload事件触发时间并没有太久的时间距离。 这种方式兼容低版本IE还是不太可靠的。
另外提供一个doScroll方式

doScroll兼容

这是IE低版本特有的,不过IE11已经弃用了。 使用scrollLeft和scrollTop代替. doScroll 的主要作用是检测DOM结构是否问题, 通常我们会使用轮询来检测doScroll是否可用,当可用的时候一定是DOM结构稳定,图片资源还未加载的时候。
我们来看一下jquery中实现doScroll的兼容:

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
//低版本的IE浏览器,这里添加监听作为向下兼容,如果doScroll执行出现bug,也能保证ready函数的执行
document.attachEvent( "onreadystatechange", DOMContentLoaded );
window.attachEvent( "onload", jQuery.ready );
//在ready里面会对执行做判断,确保只执行一次
var top = false;
// 如果是IE且不是iframe就通过不停的检查doScroll来判断dom结构是否ready
try {
top = window.frameElement == null && document.documentElement;
} catch(e) {}
if ( top && top.doScroll ) {
(function doScrollCheck() {
if ( !jQuery.isReady ) {//ready方法没有执行过
try {
// 检查是否可以向左scroll滑动,当dom结构还没有解析完成时会抛出异常
top.doScroll("left");
} catch(e) {
//递归调用,直到当dom结构解析完成
return setTimeout( doScrollCheck, 50 );
}
//没有发现异常,表示dom结构解析完成,删除之前绑定的onreadystatechange事件
//执行jQuery的ready方法
jQuery.ready();
}
})();
}
//看看jQuery.ready()方法:
ready:function(wait) {
if (wait === true ? --jQuery.readyWait : jQuery.isReady) {
//判断页面是否已完成加载并且是否已经执行ready方法
//通过isReady状态进行判断, 保证只执行一次
return;
}
if (!document.body) {
return setTimeout(jQuery.ready);
}
jQuery.isReady = true; //指示ready方法已被执行
//这也是上面两次绑定事件的原因,会保证只执行一次
if (wait !== true && --jQuery.readyWait > 0) {
return;
}
//以下是处理ready的状态
readyList.resolveWith(document, [jQuery]);
if (jQuery.fn.trigger) {
//解除引用
jQuery(document).trigger("ready").off("ready");
}
}

以上就是jquery 兼容ready的方法。

转载

目前克服延长的方法

  • 图片精灵(Spriting)
  • 内联(Inlining)

Inlining是另外一种防止发送很多小图请求的技巧,它将图片的原始数据嵌入在CSS文件里面的URL里。而这种方案的优缺点跟Spriting很类似。

.icon1 {
    background: url(data:image/png;base64,<data>) no-repeat;
  }
.icon2 {
    background: url(data:image/png;base64,<data>) no-repeat;
  }
  • 拼接(Concatenation)
  • 分片(Sharding)

Sharding就是把你的服务分散在尽可能多的主机上

http2特点

二进制

http2是一个二进制协议。

基于二进制的http2可以使成帧的使用变得更为便捷。在HTTP1.1和其他基于文本的协议中,对帧的起始和结束识别起来相当复杂。而通过移除掉可选的空白符以及其他冗余后,再来实现这些会变得更容易。

而另一方面,这项决议同样使得我们可以更加便捷的从帧结构中分离出那部分协议本身的内容。而在HTTP1中,各个部分相互交织,犹如一团乱麻。

多路复用的流

每个单独的http2连接都可以包含多个并发的流,这些流中交错的包含着来自两端的帧。流既可以被客户端/服务器端单方面的建立和使用,也可以被双方共享,或者被任意一边关闭。在流里面,每一帧发送的顺序非常关键。接收方会按照收到帧的顺序来进行处理。

优先级和依赖性

每个流都包含一个优先级(也就是“权重”),它被用来告诉对端哪个流更重要。当资源有限的时候,服务器会根据优先级来选择应该先发送哪些流。

借助于PRIORITY帧,客户端同样可以告知服务器当前的流依赖于其他哪个流。该功能让客户端能建立一个优先级“树”,所有“子流”会依赖于“父流”的传输完成情况。

优先级和依赖关系可以在传输过程中被动态的改变。这样当用户滚动一个全是图片的页面的时候,浏览器就能够指定哪个图片拥有更高的优先级。或者是在你切换标签页的时候,浏览器可以提升新切换到页面所包含流的优先级。

头压缩

HTTP是一种无状态的协议。简而言之,这意味着每个请求必须要携带服务器需要的所有细节,而不是让服务器保存住之前请求的元数据。因为http2并没有改变这个范式,所以它也需要这样(携带所有细节)。

这也保证了HTTP可重复性。当一个客户端从同一服务器请求了大量资源(例如页面的图片)的时候,所有这些请求看起来几乎都是一致的,而这些大量一致的东西则正好值得被压缩。

当每个页面资源的个数上升的时候,cookies和请求的大小都会增加,而每个请求都会包含的cookie几乎是一模一样的。

HTTP 1.1请求的大小正变得越来越大,有时甚至会大于TCP窗口的初始大小,这会严重拖累发送请求的速度。因为它们需要等待带着ACK的响应回来以后,才能继续被发送。这也是另一个需要压缩的理由。

重置 - 后悔药

HTTP 1.1的有一个缺点是:当一个含有确切值的Content-Length的HTTP消息被送出之后,你就很难中断它了。当然,通常你可以断开整个TCP链接(但也不总是可以这样),但这样导致的代价就是需要通过三次握手来重新建立一个新的TCP连接。

一个更好的方案是只终止当前传输的消息并重新发送一个新的。在http2里面,我们可以通过发送RST_STREAM帧来实现这种需求,从而避免浪费带宽和中断已有的连接。

服务器推送

这个功能通常被称作“缓存推送”。主要的思想是:当一个客户端请求资源X,而服务器知道它很可能也需要资源Z的情况下,服务器可以在客户端发送请求前,主动将资源Z推送给客户端。这个功能帮助客户端将Z放进缓存以备将来之需。

服务器推送需要客户端显式的允许服务器提供该功能。但即使如此,客户端依然能自主选择是否需要中断该推送的流。如果不需要的话,客户端可以通过发送一个RST_STREAM帧来中止。

流量控制

http2上面每个流都拥有自己的公示的流量窗口,它可以限制另一端发送数据。如果你正好知道SSH的工作原理的话,这两者非常相似。

对于每个流来说,两端都必须告诉对方自己还有更多的空间来接受新的数据,而在该窗口被扩大前,另一端只被允许发送这么多数据。

而只有数据帧会受到流量控制。

未来利用http2加载css的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<head>
</head>
<body>
<!-- HTTP/2 push this resource, or inline it, whichever's faster -->
<link rel="stylesheet" href="/site-header.css">
<header>…</header>
<link rel="stylesheet" href="/article.css">
<main>…</main>
<link rel="stylesheet" href="/comment.css">
<section class="comments">…</section>
<link rel="stylesheet" href="/about-me.css">
<section class="about-me">…</section>
<link rel="stylesheet" href="/site-footer.css">
<footer>…</footer>
</body>

参考

负margin展示图

Alt

Static元素是没有设定成浮动的元素,下图说明了负margin对static元素的作用

当static元素的margin-top/margin-left被赋予负值时,元素将被拉进指定的方向。例如:

/* 元素向上移10px*/
#mydiv1 {margin-top:-10px;}

但如果你设置margin-bottom/right为负数,元素并不会如你所想的那样向下/右移动,而是将后续的元素拖拉进来,覆盖本来的元素。

/* 
* #mydiv1后续元素向上移10px, #mydiv1 本身不移动
*/
#mydiv1 {margin-bottom:-10px;}    

浮动元素上的负margin

考虑下以下这种情况

<div id="mydiv1">First</div>
<div id="mydiv2">Second</div>

如果给一个浮动元素加上相反方向的负margin,则会使行间距为0且内容重叠。这对于创建1列是100%宽度而其他列是固定宽度(比如100px)的自适应布局来说是非常有用的方法。

/* 应用在与浮动相反方向的负margin */
#mydiv1 {float:left; margin-right:-100px;}    

若两个元素都为浮动,且#mydiv1的元素设定margin-right为20px。这样#mydiv2会认为#mydiv1的宽度比原来宽度缩短了20px(因此会导致重叠)。但有意思的是,#mydiv1的内容不受影响,保持原有的宽度。

如果负margin等于实际宽度,则元素会被完全覆盖。这是因为元素的完全宽度等于margin,padding,border,width相加而成,所以如果负margin等于余下三者的和,那元素的实际宽度也就变成了0px。

简单2列布局

负margin也是一种创建简单2列自适应布局的好方法。2列自适应布局是一种拥有一个自适应宽度(liquid width)为100%的内容列和一个固定宽度侧边栏的布局。

<div id="content"> <p>Main content in here</p> </div> 
<div id="sidebar"> <p>I’m the Sidebar! </p> </div>

#content {width:100%; float:left; margin-right:-200px;}
#sidebar {width:200px; float:left;}    

这样你就拥有了一个简单的两列布局,即使在IE6下也能无错的运行。现在,为了避免#sidebar被#content中的文字覆盖,加上

/* 防止文本被重叠 */
#content p {margin-right:210px;}
/* 它是 200px + 10px, 10px是他们的间距*/

三栏布局

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
<style>
body{
margin: 0;
padding: 0;
}
.left , .right{
height: 300px;
width: 200px;
float: left;
}
.right{
/**/
margin-left: -200px;
background-color: red;
}
.left{
/*移动到最左端*/
margin-left: -100%;
background-color: #080808;
}
.middle{
height: 300px;
width: 100%;
float: left;
background-color: blue;
}
.middle .m{
margin-left:200px;
}
</style>
<!--放第一行-->
<div class="middle">
<div class="m">middle</div>
</div>
<div class="left">left</div>
<div class="right">right</div>

参考