-
Notifications
You must be signed in to change notification settings - Fork 3
/
atom.xml
356 lines (250 loc) · 219 KB
/
atom.xml
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>跟着流年去旅行的个人博客</title>
<link href="/atom.xml" rel="self"/>
<link href="https://jack-cool.github.io/"/>
<updated>2019-12-14T14:41:45.757Z</updated>
<id>https://jack-cool.github.io/</id>
<author>
<name>feng shuan</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>谁说前端不需要学习docker?</title>
<link href="https://jack-cool.github.io/2019/12/14/%E8%B0%81%E8%AF%B4%E5%89%8D%E7%AB%AF%E4%B8%8D%E9%9C%80%E8%A6%81%E5%AD%A6%E4%B9%A0docker?/"/>
<id>https://jack-cool.github.io/2019/12/14/谁说前端不需要学习docker?/</id>
<published>2019-12-14T05:07:25.000Z</published>
<updated>2019-12-14T14:41:45.757Z</updated>
<content type="html"><![CDATA[<p><img src="/images/dockeroverview.png" alt="docker platform"></p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><h3 id="macos-安装-docker"><a href="#macos-安装-docker" class="headerlink" title="macos 安装 docker"></a>macos 安装 docker</h3><ul><li>使用 Homebrew 安装<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">brew -v</span><br><span class="line">sudo brew update</span><br><span class="line">brew cask install update</span><br></pre></td></tr></table></figure></li></ul><ul><li>手动下载安装<br>官网下载即可<code>https://docs.docker.com/docker-for-mac/install/</code></li></ul><h3 id="centos-安装-docker"><a href="#centos-安装-docker" class="headerlink" title="centos 安装 docker"></a>centos 安装 docker</h3><ul><li>搭建 centos 虚拟机<ul><li>下载<code>virtualbox</code>, 地址<code>https://www.virtualbox.org/wiki/Downloads</code></li></ul><ul><li>下载<code>vagrant</code>, 地址<code>https://www.vagrantup.com/downloads.html</code></li></ul></li></ul><ul><li><p>基于 vagrant 构建</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">vagrant init centos/7</span><br><span class="line">vagrant up</span><br><span class="line">vagrant ssh</span><br><span class="line">sudo yum update</span><br><span class="line">exit</span><br><span class="line">vagrant status</span><br><span class="line">vagrant halt</span><br><span class="line">vagrant destroy</span><br></pre></td></tr></table></figure></li><li><p>在 centos 上安装 docker</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine</span><br><span class="line"></span><br><span class="line">$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2</span><br><span class="line"></span><br><span class="line">$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo</span><br><span class="line"></span><br><span class="line">$ sudo yum install docker-ce docker-ce-cli containerd.io</span><br><span class="line"></span><br><span class="line">$ sudo systemctl start docker</span><br><span class="line"></span><br><span class="line">$ sudo docker run hello-world</span><br></pre></td></tr></table></figure></li></ul><h2 id="docker-machine"><a href="#docker-machine" class="headerlink" title="docker machine"></a>docker machine</h2><ul><li><p>使用 docker-machine 创建 virtualbox 虚拟机</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">docker-machine version</span><br><span class="line">docker-machine create demo</span><br><span class="line">docker-machine ls</span><br><span class="line">docker-machine ssh demo</span><br><span class="line">docker-machine stop demo</span><br><span class="line">docker-machine env demo</span><br><span class="line">eval $(docker-machine env demo)</span><br></pre></td></tr></table></figure></li><li><p>让 docker-machine 和阿里云搞 cp<br>参考<code>https://github.com/AliyunContainerService/docker-machine-driver-aliyunecs</code></p></li></ul><a id="more"></a><h2 id="docker-底层"><a href="#docker-底层" class="headerlink" title="docker 底层"></a>docker 底层</h2><p><img src="/images/dockerorigin.png" alt="docker platform"></p><h3 id="docker-platform"><a href="#docker-platform" class="headerlink" title="docker platform"></a>docker platform</h3><ul><li>docker 提供了一个开发,打包,运行 app 的平台</li><li>把 app 和底层 infrastructure 隔离开来</li></ul><h3 id="docker-engine"><a href="#docker-engine" class="headerlink" title="docker engine"></a>docker engine</h3><ul><li><p>后台进程(dockerd)</p></li><li><p>REST API Server</p></li><li><p>CLI 接口(docker)</p></li></ul><h3 id="docker-architecture"><a href="#docker-architecture" class="headerlink" title="docker architecture"></a>docker architecture</h3><p><img src="/images/dockerArchite.png" alt="docker architecture"></p><h3 id="底层技术支持"><a href="#底层技术支持" class="headerlink" title="底层技术支持"></a>底层技术支持</h3><ul><li><p>Namespaces: 做隔离 pid,net,ipc,mnt,uts</p></li><li><p>Control groups: 做资源限制</p></li><li><p>Union file systems: Container 和 image 的分层</p></li></ul><h2 id="docker-image-镜像"><a href="#docker-image-镜像" class="headerlink" title="docker image(镜像)"></a>docker image(镜像)</h2><ul><li>操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。</li><li>Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。 镜像不包含任何动态数据,其内容在构建之后也不会被改变。</li><li>Docker 设计时,就充分利用 Union FS 的技术,将其设计为 分层存储的架构 。 镜像实际是由多层文件系统联合组成。</li><li>镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。</li></ul><h2 id="docker-container-容器"><a href="#docker-container-容器" class="headerlink" title="docker container(容器)"></a>docker container(容器)</h2><ul><li>镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等 。</li><li>容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储,容器也是如此。</li><li>容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。</li><li>按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据 ,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此, 使用数据卷后,容器可以随意删除、重新 run ,数据却不会丢失。</li></ul><h2 id="docker-仓库(Repository)"><a href="#docker-仓库(Repository)" class="headerlink" title="docker 仓库(Repository)"></a>docker 仓库(Repository)</h2><ul><li>镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。</li><li>一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。所以说:镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。</li><li>通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签.。</li></ul><p>这里补充一下<code>Docker Registry 公开服务</code>和<code>私有 Docker Registry</code>的概念:</p><ul><li>Docker Registry 公开服务 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。</li><li>最常使用的 Registry 公开服务是官方的 Docker Hub ,这也是默认的 Registry,并拥有大量的高质量的官方镜像,网址为:hub.docker.com/ 。在国内访问 Docker Hub 可能会比较慢国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 时速云镜像库、网易云镜像服务、DaoCloud 镜像市场、阿里云镜像库等。</li><li>除了使用公开服务外,用户还可以在 本地搭建私有 Docker Registry 。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。</li></ul><h2 id="docker-常用指令"><a href="#docker-常用指令" class="headerlink" title="docker 常用指令"></a>docker 常用指令</h2><h3 id="镜像操作"><a href="#镜像操作" class="headerlink" title="镜像操作"></a>镜像操作</h3><table><thead><tr><th style="text-align:left">功能</th><th style="text-align:left">命令</th></tr></thead><tbody><tr><td style="text-align:left">拉取镜像</td><td style="text-align:left">docker pull [镜像名称:版本]</td></tr><tr><td style="text-align:left">镜像列表</td><td style="text-align:left">docker images</td></tr><tr><td style="text-align:left">删除镜像</td><td style="text-align:left">docker rmi[镜像名称:版本]</td></tr><tr><td style="text-align:left">镜像操作记录</td><td style="text-align:left">docker history [镜像名称:版本]</td></tr><tr><td style="text-align:left">给镜像设置新的仓库</td><td style="text-align:left">docker tag [镜像名称:版本][新镜像名称:新版本]</td></tr><tr><td style="text-align:left">查看镜像详细</td><td style="text-align:left">docker inspect [镜像名称:版本]</td></tr><tr><td style="text-align:left">搜索镜像</td><td style="text-align:left">docker search [关键字]</td></tr><tr><td style="text-align:left">仓库登录</td><td style="text-align:left">docker login</td></tr></tbody></table><h3 id="容器操作"><a href="#容器操作" class="headerlink" title="容器操作"></a>容器操作</h3><table><thead><tr><th style="text-align:left">功能</th><th style="text-align:left">命令</th></tr></thead><tbody><tr><td style="text-align:left">启动容器并进入</td><td style="text-align:left">docker run -ti –name [容器名称][镜像名称:版本] bash</td></tr><tr><td style="text-align:left">容器列表</td><td style="text-align:left">docker ps -a</td></tr><tr><td style="text-align:left">容器提交为新的镜像</td><td style="text-align:left">docker commit [容器名称] my_image:v1.0</td></tr><tr><td style="text-align:left">容器后台运行</td><td style="text-align:left">docker run -d –name [容器名称][镜像名称:版本] bash -c “echo hello world”</td></tr><tr><td style="text-align:left">容器结束后自动删除</td><td style="text-align:left">docker run –rm –name [容器名称][镜像名称:版本] bash -c “echo hello world”</td></tr><tr><td style="text-align:left">删除容器</td><td style="text-align:left">docker rm [容器名称]</td></tr><tr><td style="text-align:left">进入容器 exec</td><td style="text-align:left">docker exec -ti [容器名称] bash</td></tr><tr><td style="text-align:left">进入容器 attach</td><td style="text-align:left">docker attach [容器名称]</td></tr><tr><td style="text-align:left">停止容器</td><td style="text-align:left">docker stop [容器名称]</td></tr><tr><td style="text-align:left">Docker 日志</td><td style="text-align:left">docker logs [容器名称]</td></tr><tr><td style="text-align:left">查看容器详细</td><td style="text-align:left">docker inspect [容器名称]</td></tr><tr><td style="text-align:left">查看容器最近一个进程</td><td style="text-align:left">docker top [容器名称]</td></tr><tr><td style="text-align:left">docker top [容器名称]</td><td style="text-align:left">docker restart [容器名称]</td></tr><tr><td style="text-align:left">暂停一个容器进程</td><td style="text-align:left">docker pause [容器名称]</td></tr><tr><td style="text-align:left">取消暂停</td><td style="text-align:left">docker unpause [容器名称]</td></tr><tr><td style="text-align:left">终止容器</td><td style="text-align:left">docker kill [容器名称]</td></tr><tr><td style="text-align:left">端口映射</td><td style="text-align:left">docker run -ti –name [容器名称] -p 8080:80 [镜像名称:版本] bash</td></tr></tbody></table><h3 id="内存限制"><a href="#内存限制" class="headerlink" title="内存限制"></a>内存限制</h3><table><thead><tr><th style="text-align:left">参数</th><th style="text-align:left">简介</th></tr></thead><tbody><tr><td style="text-align:left">-m, - -memory</td><td style="text-align:left">内存限制,格式:数字+单位,单位可以是 b, k, m, g,最小 4M</td></tr></tbody></table><h3 id="CPU-限制"><a href="#CPU-限制" class="headerlink" title="CPU 限制"></a>CPU 限制</h3><table><thead><tr><th style="text-align:left">参数</th><th style="text-align:left">简介</th></tr></thead><tbody><tr><td style="text-align:left">– -cpuset-cpus=””</td><td style="text-align:left">允许使用的 CPU 集</td></tr><tr><td style="text-align:left">-c,- -cpu-shares=0</td><td style="text-align:left">CPU 共享权值</td></tr></tbody></table><h3 id="dockerfile-指令"><a href="#dockerfile-指令" class="headerlink" title="dockerfile 指令"></a>dockerfile 指令</h3><table><thead><tr><th style="text-align:left">命令</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left">FROM</td><td style="text-align:left">基于这个 Image 开始</td><td style="text-align:left">FROM nginx:latest</td></tr><tr><td style="text-align:left">ENV</td><td style="text-align:left">环境变量</td><td style="text-align:left">ENV localfile /usr/local/nginx</td></tr><tr><td style="text-align:left">RUN</td><td style="text-align:left">新层中执行命令</td><td style="text-align:left">RUN /bin/bash -c ‘source $HOME/.bashrc; echo $HOME</td></tr><tr><td style="text-align:left">LABEL</td><td style="text-align:left">设置 metadata</td><td style="text-align:left">LABEL version=”1.0”</td></tr><tr><td style="text-align:left">MAINTAINER</td><td style="text-align:left">维护者 (deprecated)</td><td style="text-align:left">maintainer=”feng shuan<a href="mailto:fengshuan95@gmail.com" target="_blank" rel="noopener">fengshuan95@gmail.com</a>“</td></tr><tr><td style="text-align:left">EXPOSE</td><td style="text-align:left">声明容器监听端口</td><td style="text-align:left">EXPOSE 80 443</td></tr><tr><td style="text-align:left">ADD</td><td style="text-align:left">添加文件</td><td style="text-align:left">ADD ./dist \${foo}/html</td></tr><tr><td style="text-align:left">COPY</td><td style="text-align:left">复制文件</td><td style="text-align:left">COPY ./dist \${foo}/html</td></tr><tr><td style="text-align:left">ENTRYPOINT</td><td style="text-align:left">容器启动时执行指令</td><td style="text-align:left"></td></tr><tr><td style="text-align:left">CMD</td><td style="text-align:left">容器启动时执行指令默认值</td><td style="text-align:left">CMD [“-la”]</td></tr><tr><td style="text-align:left">WORKDIR</td><td style="text-align:left">设置工作目录</td><td style="text-align:left">WORKDIR /path/to/workdir</td></tr><tr><td style="text-align:left">VOLUME</td><td style="text-align:left">挂载点</td><td style="text-align:left">VOLUME [“/data”]</td></tr><tr><td style="text-align:left">USER</td><td style="text-align:left">指定操作用户</td><td style="text-align:left">USER www</td></tr></tbody></table>]]></content>
<summary type="html">
<p><img src="/images/dockeroverview.png" alt="docker platform"></p>
<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><h3 id="macos-安装-docker"><a href="#macos-安装-docker" class="headerlink" title="macos 安装 docker"></a>macos 安装 docker</h3><ul>
<li>使用 Homebrew 安装<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">brew -v</span><br><span class="line">sudo brew update</span><br><span class="line">brew cask install update</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li>手动下载安装<br>官网下载即可<code>https://docs.docker.com/docker-for-mac/install/</code></li>
</ul>
<h3 id="centos-安装-docker"><a href="#centos-安装-docker" class="headerlink" title="centos 安装 docker"></a>centos 安装 docker</h3><ul>
<li>搭建 centos 虚拟机<ul>
<li>下载<code>virtualbox</code>, 地址<code>https://www.virtualbox.org/wiki/Downloads</code></li>
</ul>
<ul>
<li>下载<code>vagrant</code>, 地址<code>https://www.vagrantup.com/downloads.html</code></li>
</ul>
</li>
</ul>
<ul>
<li><p>基于 vagrant 构建</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">vagrant init centos/7</span><br><span class="line">vagrant up</span><br><span class="line">vagrant ssh</span><br><span class="line">sudo yum update</span><br><span class="line">exit</span><br><span class="line">vagrant status</span><br><span class="line">vagrant halt</span><br><span class="line">vagrant destroy</span><br></pre></td></tr></table></figure>
</li>
<li><p>在 centos 上安装 docker</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine</span><br><span class="line"></span><br><span class="line">$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2</span><br><span class="line"></span><br><span class="line">$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo</span><br><span class="line"></span><br><span class="line">$ sudo yum install docker-ce docker-ce-cli containerd.io</span><br><span class="line"></span><br><span class="line">$ sudo systemctl start docker</span><br><span class="line"></span><br><span class="line">$ sudo docker run hello-world</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h2 id="docker-machine"><a href="#docker-machine" class="headerlink" title="docker machine"></a>docker machine</h2><ul>
<li><p>使用 docker-machine 创建 virtualbox 虚拟机</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">docker-machine version</span><br><span class="line">docker-machine create demo</span><br><span class="line">docker-machine ls</span><br><span class="line">docker-machine ssh demo</span><br><span class="line">docker-machine stop demo</span><br><span class="line">docker-machine env demo</span><br><span class="line">eval $(docker-machine env demo)</span><br></pre></td></tr></table></figure>
</li>
<li><p>让 docker-machine 和阿里云搞 cp<br>参考<code>https://github.com/AliyunContainerService/docker-machine-driver-aliyunecs</code></p>
</li>
</ul>
</summary>
<category term="devops" scheme="https://jack-cool.github.io/categories/devops/"/>
<category term="docker" scheme="https://jack-cool.github.io/tags/docker/"/>
<category term="devops" scheme="https://jack-cool.github.io/tags/devops/"/>
</entry>
<entry>
<title>你应该知道的浏览器渲染原理</title>
<link href="https://jack-cool.github.io/2019/09/04/%E4%BD%A0%E5%BA%94%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86/"/>
<id>https://jack-cool.github.io/2019/09/04/你应该知道的浏览器渲染原理/</id>
<published>2019-09-04T13:59:36.000Z</published>
<updated>2019-09-04T14:15:52.000Z</updated>
<content type="html"><![CDATA[<!-- 你应该知道的浏览器渲染原理 --><p>浏览器作为前端开发中必不可少的一项,在日常开发中扮演了重要的角色。不管你是画页面(咦,怎么有点切图仔的感觉),还是用 Chrome Devtools 调试。但有时候仅仅会用,可能还不太够。</p><p>浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分,一是渲染引擎,另一个是 JS 引擎。</p><p>渲染引擎在不同的浏览器中也不都是相同的。目前市面上常见的浏览器内核可以分为这四种:</p><ul><li>Trident(IE)</li><li>Gecko(火狐)</li><li>Blink(Chrome、Opera)</li><li>Webkit(Safari)</li></ul><p>本文以 webkit 为例,大概阐述一下浏览器的渲染过程。</p><h2 id="页面加载过程"><a href="#页面加载过程" class="headerlink" title="页面加载过程"></a>页面加载过程</h2><p>在介绍浏览器渲染过程之前,我们先简单介绍下页面的加载过程,有助于更好理解后续渲染过程。</p><p>大概流程如下:</p><ul><li>浏览器根据 DNS 服务器得到域名的 IP 地址</li><li>向这个 IP 的机器发送 HTTP 请求</li><li>服务器收到、处理并返回 HTTP 请求</li><li>浏览器得到返回内容</li></ul><p>例如在浏览器输入<code>https://github.com/Jack-cool</code>,然后经过 DNS 解析,<code>github.com</code>对应的 IP 是<code>192.30.253.112</code>(不同时间、地点对应的 IP 可能会不同)。然后浏览器向该 IP 发送 HTTP 请求。</p><p>服务端接收到 HTTP 请求,然后经过计算(向不同的用户推送不同的内容),返回 HTTP 请求,返回的内容如下:</p><!-- 图片 --><p><img src="/images/response.png" alt="alt"></p><p>其实就是一堆 HMTL 格式的字符串,因为只有 HTML 格式浏览器才能正确解析,这是 W3C 标准的要求。接下来就是浏览器的渲染过程。</p><h2 id="浏览器渲染过程"><a href="#浏览器渲染过程" class="headerlink" title="浏览器渲染过程"></a>浏览器渲染过程</h2><!-- 图片 --><p><img src="/images/render.png" alt="alt"></p><a id="more"></a><p>浏览器渲染过程大体分为如下三部分:</p><ul><li><p>浏览器会解析三个东西:</p><ul><li><p>一是 HTML/SVG/XHTML,HTML 字符串描述了一个页面的结构,浏览器会把 HTML 结构字符串解析转换 DOM 树形结构。</p><!-- 图片 --><p><img src="/images/html.gif" alt="alt"></p></li><li><p>二是 CSS,解析 CSS 会产生 CSS 规则树,它和 DOM 结构比较像。</p><!-- 图片 --><p><img src="/images/css.png" alt="alt"></p></li></ul></li></ul><ul><li><p>三是 Javascript 脚本,等到 Javascript 脚本文件加载后, 通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。</p><!-- 图片 --><p><img src="/images/script.gif" alt="alt"></p></li><li><p>解析完成后,浏览器引擎会通过 DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。</p><ul><li><p>Rendering Tree 渲染树并不等同于 DOM 树,渲染树只会包括需要显示的节点和这些节点的样式信息。</p></li><li><p>CSS 的 Rule Tree 主要是为了完成匹配并把 CSS Rule 附加上 Rendering Tree 上的每个 Element(也就是每个 Frame)。</p></li><li><p>然后,计算每个 Frame 的位置,这又叫 layout 和 reflow 过程。</p></li></ul></li><li><p>最后通过调用操作系统 Native GUI 的 API 绘制。</p></li></ul><h2 id="浏览器渲染过程细节"><a href="#浏览器渲染过程细节" class="headerlink" title="浏览器渲染过程细节"></a>浏览器渲染过程细节</h2><h3 id="构建-DOM"><a href="#构建-DOM" class="headerlink" title="构建 DOM"></a>构建 DOM</h3><p>浏览器会遵守一套步骤将 HTML 文件转换为 DOM 树。宏观上,可以分为几个步骤:</p><!-- 图片 --><p><img src="/images/dom.png" alt="alt"></p><h3 id="构建-CSSOM"><a href="#构建-CSSOM" class="headerlink" title="构建 CSSOM"></a>构建 CSSOM</h3><p>DOM 会捕获页面的内容,但浏览器还需要知道页面如何展示,所以需要构建 CSSOM。</p><p>构建 CSSOM 的过程与构建 DOM 的过程非常相似,当浏览器接收到一段 CSS,浏览器首先要做的是识别出 Token,然后构建节点并生成 CSSOM。</p><!-- 图片 --><p><img src="/images/cssom.png" alt="alt"></p><h3 id="构建渲染树"><a href="#构建渲染树" class="headerlink" title="构建渲染树"></a>构建渲染树</h3><p>当我们生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。</p><!-- 图片 --><p><img src="/images/renderTree.png" alt="alt"></p><p>在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息。</p><p>我们或许有个疑惑:<code>浏览器如果渲染过程中遇到JS文件怎么处理</code>?</p><p>渲染过程中,如果遇到<code><script></code>就停止渲染,执行 JS 代码。因为浏览器有 GUI 渲染线程与 JS 引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。JavaScript 的加载、解析与执行会阻塞 DOM 的构建,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停构建 DOM,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复 DOM 构建。</p><p>也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。</p><p>JS 文件不只是阻塞 DOM 的构建,它会导致 CSSOM 也阻塞 DOM 的构建。</p><p>原本 DOM 和 CSSOM 的构建是互不影响,井水不犯河水,但是一旦引入了 JavaScript,CSSOM 也开始阻塞 DOM 的构建,只有 CSSOM 构建完毕后,DOM 再恢复 DOM 构建。</p><p>这是什么情况?</p><p>这是因为 JavaScript 不只是可以改 DOM,它还可以更改样式,也就是它可以更改 CSSOM。因为不完整的 CSSOM 是无法使用的,如果 JavaScript 想访问 CSSOM 并更改它,那么在执行 JavaScript 时,必须要能拿到完整的 CSSOM。所以就导致了一个现象,如果浏览器尚未完成 CSSOM 的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和 DOM 构建,直至其完成 CSSOM 的下载和构建。也就是说,<code>在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM</code>。</p><!-- 图片 --><p><img src="/images/block.png" alt="alt"></p><h3 id="布局与绘制"><a href="#布局与绘制" class="headerlink" title="布局与绘制"></a>布局与绘制</h3><p>当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。</p><p>布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。</p><p>布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。</p><h3 id="性能优化策略"><a href="#性能优化策略" class="headerlink" title="性能优化策略"></a>性能优化策略</h3><p>基于上面介绍的浏览器渲染原理,DOM 和 CSSOM 结构构建顺序,初始化可以对页面渲染做些优化,提升页面性能。</p><ul><li><p>JS 优化: <code><script></code> 标签加上 defer 属性 和 async 属性 用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。</p><ul><li>defer 属性: 用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。</li><li>async 属性: HTML5 新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。</li></ul></li><li><p>CSS 优化: <code><link></code> 标签的 rel 属性 中的属性值设置为 preload 能够让你在你的 HTML 页面中可以指明哪些资源是在页面加载完成后即刻需要的。</p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>综上所述,我们得出这样的结论:</p><ul><li>浏览器工作流程:构建 DOM -> 构建 CSSOM -> 构建渲染树 -> 布局 -> 绘制。</li><li>CSSOM 会阻塞渲染,只有当 CSSOM 构建完毕后才会进入下一个阶段构建渲染树。</li><li>通常情况下 DOM 和 CSSOM 是并行构建的,但是当浏览器遇到一个不带 defer 或 async 属性的 script 标签时,DOM 构建将暂停,如果此时又恰巧浏览器尚未完成 CSSOM 的下载和构建,由于 JavaScript 可以修改 CSSOM,所以需要等 CSSOM 构建完毕后再执行 JS,最后才重新 DOM 构建。</li></ul>]]></content>
<summary type="html">
<!-- 你应该知道的浏览器渲染原理 -->
<p>浏览器作为前端开发中必不可少的一项,在日常开发中扮演了重要的角色。不管你是画页面(咦,怎么有点切图仔的感觉),还是用 Chrome Devtools 调试。但有时候仅仅会用,可能还不太够。</p>
<p>浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分,一是渲染引擎,另一个是 JS 引擎。</p>
<p>渲染引擎在不同的浏览器中也不都是相同的。目前市面上常见的浏览器内核可以分为这四种:</p>
<ul>
<li>Trident(IE)</li>
<li>Gecko(火狐)</li>
<li>Blink(Chrome、Opera)</li>
<li>Webkit(Safari)</li>
</ul>
<p>本文以 webkit 为例,大概阐述一下浏览器的渲染过程。</p>
<h2 id="页面加载过程"><a href="#页面加载过程" class="headerlink" title="页面加载过程"></a>页面加载过程</h2><p>在介绍浏览器渲染过程之前,我们先简单介绍下页面的加载过程,有助于更好理解后续渲染过程。</p>
<p>大概流程如下:</p>
<ul>
<li>浏览器根据 DNS 服务器得到域名的 IP 地址</li>
<li>向这个 IP 的机器发送 HTTP 请求</li>
<li>服务器收到、处理并返回 HTTP 请求</li>
<li>浏览器得到返回内容</li>
</ul>
<p>例如在浏览器输入<code>https://github.com/Jack-cool</code>,然后经过 DNS 解析,<code>github.com</code>对应的 IP 是<code>192.30.253.112</code>(不同时间、地点对应的 IP 可能会不同)。然后浏览器向该 IP 发送 HTTP 请求。</p>
<p>服务端接收到 HTTP 请求,然后经过计算(向不同的用户推送不同的内容),返回 HTTP 请求,返回的内容如下:</p>
<!-- 图片 -->
<p><img src="/images/response.png" alt="alt"></p>
<p>其实就是一堆 HMTL 格式的字符串,因为只有 HTML 格式浏览器才能正确解析,这是 W3C 标准的要求。接下来就是浏览器的渲染过程。</p>
<h2 id="浏览器渲染过程"><a href="#浏览器渲染过程" class="headerlink" title="浏览器渲染过程"></a>浏览器渲染过程</h2><!-- 图片 -->
<p><img src="/images/render.png" alt="alt"></p>
</summary>
<category term="浏览器" scheme="https://jack-cool.github.io/categories/%E6%B5%8F%E8%A7%88%E5%99%A8/"/>
<category term="浏览器渲染" scheme="https://jack-cool.github.io/tags/%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93/"/>
</entry>
<entry>
<title>深入理解浏览器的缓存机制</title>
<link href="https://jack-cool.github.io/2019/09/02/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6/"/>
<id>https://jack-cool.github.io/2019/09/02/深入理解浏览器的缓存机制/</id>
<published>2019-09-02T14:08:47.000Z</published>
<updated>2019-09-02T14:26:14.000Z</updated>
<content type="html"><![CDATA[<p>都 9102 年了,你还不懂浏览器的缓存机制?没错,我之前对这块是相当的薄弱,也逐渐在开发中感受到了缓存的作用。缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。</p><p>既然缓存这么强,你还有什么理由不去深入了解一下呢?</p><p>Let’s got it!</p><p><img src="/images/mindMapping.png" alt="alt"></p><h2 id="缓存位置"><a href="#缓存位置" class="headerlink" title="缓存位置"></a>缓存位置</h2><p>从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。</p><ul><li>Service Worker</li><li>Memory Cache</li><li>Disk Cache</li><li>Push Cache</li></ul><a id="more"></a><h3 id="Service-Worker"><a href="#Service-Worker" class="headerlink" title="Service Worker"></a>Service Worker</h3><p>Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。</p><h3 id="Memory-Cache"><a href="#Memory-Cache" class="headerlink" title="Memory Cache"></a>Memory Cache</h3><p>Memory Cache 也就是内存中的缓存,主要包含的是当前页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放(一旦我们关闭 Tab 页面,内存中的缓存也就被释放了)。</p><p>内存缓存中有一块重要的缓存资源是 preloader 相关指令(例如<code><link rel="prefetch"></code>)众所周知 preloader 的相关指令已经是页面优化的常见手段之一,它可以一边解析 js/css 文件,一边网络请求下一个资源。</p><h3 id="Disk-Cache"><a href="#Disk-Cache" class="headerlink" title="Disk Cache"></a>Disk Cache</h3><p>Disk Cache 也就是存储在硬盘中的缓存,读取速度虽然慢点,但是什么都能存储到磁盘中,与 Memory Cache 相比,优势是容量和存储时效性。</p><p>在所有浏览器缓存中,Disk Cache 覆盖面基本上是最大的。它会根据 HTTP Header 中的字段判断哪些资源缓存(不用慌,关于 HTTP 的协议头中的缓存字段,会在下面详细介绍的),哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。</p><blockquote><p>浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?</p></blockquote><p>关于这点,网上说法不一,不过以下两点比较靠得住:</p><ul><li>对于大文件来说,大概率是不存储在内存中的</li><li>当前系统内存使用率高的话,文件优先存进硬盘</li></ul><h3 id="Push-Cache"><a href="#Push-Cache" class="headerlink" title="Push Cache"></a>Push Cache</h3><p>Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂。</p><p>如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。</p><p>为了性能上的考虑,大部分的接口都应该选择好缓存策略,通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。</p><h2 id="缓存过程分析"><a href="#缓存过程分析" class="headerlink" title="缓存过程分析"></a>缓存过程分析</h2><p>浏览器与服务器通信的方式为应答模式,即: 浏览器发起 HTTP 请求 >> 服务器响应该请求,那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。具体过程如下图:</p> <!-- 图片 --><p><img src="/images/cacheProcess.png" alt="alt"></p><p>由上图我们可以知道:</p><ul><li>浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识。</li><li>浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。</li></ul><p>以上两点是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取。下面说一下浏览器缓存的使用规则。根据是否需要向服务器重新发起 HTTP 请求将缓存过程分为两个部分,分别是强缓存和协商缓存。</p><h2 id="强缓存"><a href="#强缓存" class="headerlink" title="强缓存"></a>强缓存</h2><p>强缓存: 不会向服务器发起请求,直接从缓存中读取资源,在 chrome 控制台的 Network 选项中可以看到该请求返回 200 的状态码,并且<code>size显示from disk cache</code>或<code>from memory cache</code>。强缓存可以通过设置两种 HTTP Header 实现: Expires 和 Cache-Control</p><p>1、 Expires</p><p>缓存过期时间,用来指定资源到期的时间,是服务端的具体时间点。也就是说,Expires=max-age + 请求时间,需要和 Last-modified 结合使用。Expires 是 Web 服务器响应消息头字段,在响应 http 请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。</p><p>Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。</p><p>2、 Cache-Control</p><p>在 HTTP/1.1 中,Cache-Control 是最重要的规则,主要用于控制网页缓存。</p><p>Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:</p><!-- 图片 --><p><img src="/images/cacheControl.png" alt="alt"></p><ul><li><p><code>public</code>: 所有内容都将被缓存(客户端和代理服务器都可缓存)。具体来说响应可被任何中间节点缓存,如 Browser <– proxy1 <– proxy2 <– Server,中间的 proxy 可以缓存资源,下次再请求同一资源,proxy1 直接把自己缓存的东西给 Browser 而不再向 proxy2 要。</p></li><li><p><code>private</code>: 所有内容只有客户端可以缓存,Cache-Control 的默认取值。具体来说,表示中间节点不允许缓存,对于 Browser <– proxy1 <– proxy2 <– Server,proxy 会老老实实把 Server 返回的数据发送给 proxy1,自己不缓存任何数据。当下次 Browser 再次请求时 proxy 会做好请求转发而不是自作主张给自己缓存的数据</p></li><li><p><code>no-cache</code>: 客户端缓存内容,是否使用缓存则需要经过协商缓存来验证决定。表示不使用 Cache-Control 的缓存控制方式来做前置验证,而是使用 Etag 或者 Last-Modified 字段来控制缓存。需要注意的是,no-cache 这个名字有点误导。设置了 no-cache 之后,并不是说浏览器就不再缓存数据,而是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致。</p></li><li><p><code>no-store</code>: 所有内容都不会被缓存,即不使用强缓存,也不使用协商缓存</p></li><li><p><code>max-age</code>: max-age=xxx(xxx is numeric)表示缓存内容将在 xxx 秒后失效</p></li><li><p><code>s-maxage</code>: 同 max-age 作用一样,只在代理服务器中生效。max-age 用于普通缓存,而 s-maxage 用于代理缓存。s-maxage 的优先级高于 max-age。如果存在 s-maxage,则会覆盖掉 max-age 和 Expires header</p></li><li><p><code>max-stale</code>: 能容忍的最大过期时间。max-stale 指令标识了客户端愿意接受一个已经过期了的响应。如果指定了 max-stale 的值,则最大容忍时间为对应的秒数。如果没有指定,那么说明浏览器愿意接收任何 age 的响应(age 表示响应由源站生成或确认的时间与当前时间的差值)。</p></li><li><p><code>min-fresh</code>: 能够容忍的最小新鲜度。min-fresh 标识了客户端不愿意接受新鲜度不多于当前的 age 加上 min-fresh 设定的时间之和的响应</p></li></ul><!-- 图片 --><p><img src="/images/processMap.png" alt="alt"></p><p>3、 Expires 和 Cache-Control 两者对比</p><p>其实这两者差别不大,区别就在于 Expires 是 http1.0 的产物,Cache-Control 是 http1.1 的产物,两者同时存在的话,Cache-Control 优先级高于 Expires;在某些不支持 HTTP1.1 的环境下,Expires 就会发挥用处。所以 Expires 其实是过时的产物,现阶段它的存在只是一种兼容性的写法。</p><p>强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。</p><h2 id="协商缓存"><a href="#协商缓存" class="headerlink" title="协商缓存"></a>协商缓存</h2><p>协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:</p><ul><li>协商缓存生效,返回 304 和 Not Modified</li></ul><!-- 图片 --><p><img src="/images/effective.png" alt="alt"></p><ul><li>协商缓存成功,返回 200 和请求结果</li></ul><!-- 图片 --><p><img src="/images/invalid.png" alt="alt"></p><p>协商缓存可以通过设置两种 HTTP Header 实现: Last-Modified 和 ETag</p><p>1、 Last-Modified 和 If-Modified-Since</p><p>浏览器在第一次访问资源时,服务器返回资源的同时,在 response header 中添加 Last-Modified 的 header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和 header:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Last-Modified: Mon, 02 Sep 2019 18:10:00 GMT</span><br></pre></td></tr></table></figure><p>浏览器下一次请求这个资源,浏览器检测到有 Last-Modified 这个 header,于是添加 If-Modified-Since 这个 header,值就是 Last-Modified 中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回 304 和空的响应体,直接从缓存读取,如果 If-Modified-Since 的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和 200。</p><!-- 图片 --><p><img src="/images/lastModified.png" alt="alt"></p><p>但是 Last-Modified 存在一些弊端:</p><ul><li><p>如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源;</p></li><li><p>因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源。</p></li></ul><p>既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag 和 If-None-Match。</p><p>2、 ETag 和 If-None-Match</p><p>Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识 (由服务器生成),只要资源有变化,Etag 就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的 Etag 值放到 request header 里的 If-None-Match 里,服务器只需要比较客户端传来的 If-None-Match 跟自己服务器上该资源的 ETag 是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现 ETag 匹配不上,那么直接以常规 GET 200 回包形式将新的资源(当然也包括了新的 ETag)发给客户端;如果 ETag 是一致的,则直接返回 304 知会客户端直接使用本地缓存即可。</p><!-- 图片 --><p><img src="/images/eTag.png" alt="alt"></p><p>3、 两者之间对比</p><ul><li><p>首先在精确度上,Etag 要优于 Last-Modifie,Last-Modified 的时间单位是秒,如果某个文件在 1 秒内改变了多次,那么他们的 Last-Modified 其实并没有体现出来修改,但是 Etag 每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的 Last-Modified 也有可能不一致;</p></li><li><p>第二在性能上,Etag 要逊于 Last-Modified,毕竟 Last-Modified 只需要记录时间,而 Etag 需要服务器通过算法来计算出一个 hash 值;</p></li><li><p>第三在优先级上,服务器校验优先考虑 Etag。</p></li></ul><h2 id="缓存机制"><a href="#缓存机制" class="headerlink" title="缓存机制"></a>缓存机制</h2><p>强制缓存优先于协商缓存进行,若强制缓存 (Expires 和 Cache-Control) 生效则直接使用缓存,若不生效则进行协商缓存 (Last-Modified / If-Modified-Since 和 Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回 200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回 304,继续使用缓存。具体流程图如下:</p><!-- 图片 --><p><img src="/images/mechanism.png" alt="alt"></p><p>看到这里,不知道你是否存在这样一个疑问:如果什么缓存策略都没设置,那么浏览器会怎么处理?</p><p>对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。</p><h2 id="实际场景应用缓存策略"><a href="#实际场景应用缓存策略" class="headerlink" title="实际场景应用缓存策略"></a>实际场景应用缓存策略</h2><ul><li>频繁变动的资源</li></ul><p>对于频繁变动的资源,首先需要使用 Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。</p><ul><li>不常变化的资源</li></ul><p>通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名 (或者路径) 中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。</p><h2 id="用户行为对浏览器缓存的影响"><a href="#用户行为对浏览器缓存的影响" class="headerlink" title="用户行为对浏览器缓存的影响"></a>用户行为对浏览器缓存的影响</h2><p>所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:</p><ul><li><p>打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求;</p></li><li><p>普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用 (如果匹配的话)。其次才是 disk cache;</p></li><li><p>强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache), 服务器直接返回 200 和最新内容。</p></li></ul>]]></content>
<summary type="html">
<p>都 9102 年了,你还不懂浏览器的缓存机制?没错,我之前对这块是相当的薄弱,也逐渐在开发中感受到了缓存的作用。缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。</p>
<p>既然缓存这么强,你还有什么理由不去深入了解一下呢?</p>
<p>Let’s got it!</p>
<p><img src="/images/mindMapping.png" alt="alt"></p>
<h2 id="缓存位置"><a href="#缓存位置" class="headerlink" title="缓存位置"></a>缓存位置</h2><p>从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。</p>
<ul>
<li>Service Worker</li>
<li>Memory Cache</li>
<li>Disk Cache</li>
<li>Push Cache</li>
</ul>
</summary>
<category term="浏览器" scheme="https://jack-cool.github.io/categories/%E6%B5%8F%E8%A7%88%E5%99%A8/"/>
<category term="缓存" scheme="https://jack-cool.github.io/tags/%E7%BC%93%E5%AD%98/"/>
<category term="浏览器" scheme="https://jack-cool.github.io/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/"/>
</entry>
<entry>
<title>前端应该晓得的web登录</title>
<link href="https://jack-cool.github.io/2019/08/27/%E5%89%8D%E7%AB%AF%E5%BA%94%E8%AF%A5%E6%99%93%E5%BE%97%E7%9A%84web%E7%99%BB%E5%BD%95/"/>
<id>https://jack-cool.github.io/2019/08/27/前端应该晓得的web登录/</id>
<published>2019-08-27T13:22:16.000Z</published>
<updated>2019-08-27T13:32:46.000Z</updated>
<content type="html"><![CDATA[<h3 id="为什么会有登录这回事"><a href="#为什么会有登录这回事" class="headerlink" title="为什么会有登录这回事"></a>为什么会有登录这回事</h3><p>首先这是因为 HTTP 是无状态的协议。所谓无状态就是在两次请求之间服务器不会保存任何的数据。所以,登录就是用某种方法让服务器在多次请求之间能够识别出你,而不是每次发请求都得带上用户名密码这样的识别身份的信息。从登录成功到登出的这个过程,服务器一直维护了一个可以识别出用户信息的数据结构,广义上来说,这个过程就叫做 session,也就是保持了一个会话。</p><h3 id="常见的两种登录"><a href="#常见的两种登录" class="headerlink" title="常见的两种登录"></a>常见的两种登录</h3><h4 id="服务器-session-客户端-sessionId"><a href="#服务器-session-客户端-sessionId" class="headerlink" title="服务器 session + 客户端 sessionId"></a>服务器 session + 客户端 sessionId</h4><!-- 图片 --><p><img src="/images/session.png" alt="alt"></p><ul><li>客户端带着用户名和密码去访问/login 接口,服务器端收到后校验用户名和密码,校验正确就会在服务器端存储一个 sessionId 和 session 的映射关系。</li><li>服务器端返回 response,并且将 sessionId 以 set-cookie 的方式种在客户端,这样,sessionId 就存在了客户端。这里要注意的是,将 sessionId 存在 cookie 并不是一种强制的方法,而是大家一般都这么做,而且发请求的时候符合 domain 和 path 的时候,会自动带上 cookie,省去了手动塞的过程。</li><li>客户端发起非登录请求时,假如服务器给了 set-cookie,浏览器会自动在请求头中添加 cookie</li><li>服务器接收请求,分解 cookie,验证信息,核对成功后返回 response 给客户端</li></ul><h4 id="token"><a href="#token" class="headerlink" title="token"></a>token</h4><blockquote><p>前面说到 sessionId 方式的本质是把用户信息维护在 serve 端,token 方式就是把用户的状态信息加密成一串 token 传给前端,然后每次发请求时把 token 带上,传回给服务端。服务端收到请求之后,解析 token 并且验证相关信息</p></blockquote><p>token 也称作令牌,由 uid+time+sign+固定参数。</p><p>token 的认证方式类似于临时的证书签名,并且是一种服务端无状态的认证方式,非常适合于 REST API 的场景。所谓无状态就是服务端并不会保存身份认证相关的数据。<br><a id="more"></a><br>组成:</p><ul><li>uid: 用户唯一身份标识</li><li>time: 当前时间的时间戳</li><li>sign: 签名,使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接</li><li>固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查库</li></ul><p>认证流程:</p><ul><li>用户登录,成功后服务器返回 Token 给客户端</li><li>客户端收到数据后保存在客户端</li><li>客户端再次访问服务器,将 token 放入 headers 中</li><li>服务器端采用 filter 过滤器校验。检验成功后则返回请求数据,校验失败则返回错误码</li></ul><p>业界通用的加密方式是<code>jwt(json web token)</code></p><h5 id="JWT"><a href="#JWT" class="headerlink" title="JWT"></a>JWT</h5><p>JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "姓名": "张三",</span><br><span class="line"> "角色": "管理员",</span><br><span class="line"> "到期时间": "2019年8月27日0点0分"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认证用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。</p><p>服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。</p><p>JWT 的格式大致如下:</p><!-- 图片 --><p><img src="/images/jwt.png" alt="alt"></p><p>它是一个很长的字符串,中间用点(.)分隔成三个部分。</p><p>JWT 的三个部分依次如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Header(头部)</span><br><span class="line">Payload(负载)</span><br><span class="line">Signature(签名)</span><br></pre></td></tr></table></figure><p>下面简单介绍一下这三部分:</p><ul><li>Header</li></ul><p>Header 部分是一个 JSON 对象,描述 JWT 的元数据。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "alg": "HS256",</span><br><span class="line"> "typ": "JWT"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面代码中,<code>alg</code>属性表示签名的算法,默认是 HMAC SHA256(写成 HS256);<code>typ</code>属性表示这个令牌(token)的类型(type),JWT 令牌统一写为<code>JWT</code>。</p><p>最后,将上面的 JSON 对象使用<code>Base64URL</code>算法转成字符串。</p><ul><li>Payload</li></ul><p>Payload 部分也是一个 JSON 对象,里面放的是用户的信息,用来存放实际需要传递的数据。JWT 规定了 7 个官方字段,供选用。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">iss (issuer):签发人</span><br><span class="line">exp (expiration time):过期时间</span><br><span class="line">sub (subject):主题</span><br><span class="line">aud (audience):受众</span><br><span class="line">nbf (Not Before):生效时间</span><br><span class="line">iat (Issued At):签发时间</span><br><span class="line">jti (JWT ID):编号</span><br></pre></td></tr></table></figure><p>注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。</p><p>这个 JSON 对象也要使用<code>Base64URL</code>算法转成字符串。</p><ul><li>Signature</li></ul><p>Signature 部分是对前两部分的签名,防止数据篡改。</p><p>首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">HMACSHA256(</span><br><span class="line"> base64UrlEncode(header) + "." +</span><br><span class="line"> base64UrlEncode(payload),</span><br><span class="line"> secret)</span><br></pre></td></tr></table></figure><p>算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。即最终的 jwt 为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jwt = base64url(header) + "." + base64url(payload) + "." + signature</span><br></pre></td></tr></table></figure><!-- 图片 --><p><img src="/images/jwtType.jpg" alt="alt"></p><p>jwt 可以放在 response 中返回,也可以放在 cookie 中返回,这都是具体的返回方式,并不重要。<br>客户端发起请求时,官方推荐放在 HTTP header 中:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Authorization: Bearer <token></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h3 id="为什么会有登录这回事"><a href="#为什么会有登录这回事" class="headerlink" title="为什么会有登录这回事"></a>为什么会有登录这回事</h3><p>首先这是因为 HTTP 是无状态的协议。所谓无状态就是在两次请求之间服务器不会保存任何的数据。所以,登录就是用某种方法让服务器在多次请求之间能够识别出你,而不是每次发请求都得带上用户名密码这样的识别身份的信息。从登录成功到登出的这个过程,服务器一直维护了一个可以识别出用户信息的数据结构,广义上来说,这个过程就叫做 session,也就是保持了一个会话。</p>
<h3 id="常见的两种登录"><a href="#常见的两种登录" class="headerlink" title="常见的两种登录"></a>常见的两种登录</h3><h4 id="服务器-session-客户端-sessionId"><a href="#服务器-session-客户端-sessionId" class="headerlink" title="服务器 session + 客户端 sessionId"></a>服务器 session + 客户端 sessionId</h4><!-- 图片 -->
<p><img src="/images/session.png" alt="alt"></p>
<ul>
<li>客户端带着用户名和密码去访问/login 接口,服务器端收到后校验用户名和密码,校验正确就会在服务器端存储一个 sessionId 和 session 的映射关系。</li>
<li>服务器端返回 response,并且将 sessionId 以 set-cookie 的方式种在客户端,这样,sessionId 就存在了客户端。这里要注意的是,将 sessionId 存在 cookie 并不是一种强制的方法,而是大家一般都这么做,而且发请求的时候符合 domain 和 path 的时候,会自动带上 cookie,省去了手动塞的过程。</li>
<li>客户端发起非登录请求时,假如服务器给了 set-cookie,浏览器会自动在请求头中添加 cookie</li>
<li>服务器接收请求,分解 cookie,验证信息,核对成功后返回 response 给客户端</li>
</ul>
<h4 id="token"><a href="#token" class="headerlink" title="token"></a>token</h4><blockquote>
<p>前面说到 sessionId 方式的本质是把用户信息维护在 serve 端,token 方式就是把用户的状态信息加密成一串 token 传给前端,然后每次发请求时把 token 带上,传回给服务端。服务端收到请求之后,解析 token 并且验证相关信息</p>
</blockquote>
<p>token 也称作令牌,由 uid+time+sign+固定参数。</p>
<p>token 的认证方式类似于临时的证书签名,并且是一种服务端无状态的认证方式,非常适合于 REST API 的场景。所谓无状态就是服务端并不会保存身份认证相关的数据。<br>
</summary>
<category term="前端技术" scheme="https://jack-cool.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="session" scheme="https://jack-cool.github.io/tags/session/"/>
<category term="cookie" scheme="https://jack-cool.github.io/tags/cookie/"/>
<category term="JWT" scheme="https://jack-cool.github.io/tags/JWT/"/>
</entry>
<entry>
<title>Hooks与React生命周期的关系</title>
<link href="https://jack-cool.github.io/2019/08/24/Hooks%E4%B8%8EReact%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%9A%84%E5%85%B3%E7%B3%BB/"/>
<id>https://jack-cool.github.io/2019/08/24/Hooks与React生命周期的关系/</id>
<published>2019-08-24T03:40:24.000Z</published>
<updated>2019-08-27T13:30:50.000Z</updated>
<content type="html"><![CDATA[<!-- Hooks与React生命周期的关系 --><p>React 生命周期很多人都了解,但通常我们所了解的都是 单个组件 的生命周期,但针对 Hooks 组件、多个关联组件(父子组件和兄弟组件) 的生命周期又是怎么样的喃?你有思考和了解过吗,接下来我们将完整的了解 React 生命周期。</p><h2 id="Hooks-组件"><a href="#Hooks-组件" class="headerlink" title="Hooks 组件"></a>Hooks 组件</h2><p><code>函数组件</code>的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。</p><p>但是引入<code>Hooks</code><br>之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念</p><p>即:Hooks 组件(使用了 Hooks 的函数组件)有生命周期,而函数组件(未使用 Hooks 的函数组件)是没有生命周期的</p><p>下面,是具体的 class 与 Hooks 的生命周期对应关系</p><ul><li>constructor<br>函数组件不需要构造函数,我们可以通过调用<code>useState</code>来初始化 state</li><li>getDerivedStateFromProps<br>一般情况下,我们不需要使用它,我们可以在<code>渲染过程中更新state</code>,以达到实现 getDerivedStateFromProps 的目的</li><li>shouldComponentUpdate<br>可以用<code>React.memo</code>包裹一个组件对它的 props 进行浅比较<br><code>const Comp = React.memeo((props) => { // 具体的组件 })</code><br>注意: React.memo 等价于 PureComponent,它只浅比较 props,这里也可以使用 useMemo 优化每一个节点</li><li>render<br>这是函数组件体本身<a id="more"></a></li><li><p>componentDidMount<br>可以使用<code>useEffect</code>处理副作用<br><code>useEffect(() => { // 在componentDidMount执行的内容 }, [count]) // 仅在count更改时更新</code></p></li><li><p>componentWillUnmount<br>相当于 useEffect 里面返回的 cleanup 函数<br><code>useEffect(() => { // 需要在componentDidMount执行的内容 return function cleanup() { // 需要在componentWillUnmount执行的内容 } })</code></p></li><li><p>componentDidCatch && getDerivedStateFromError<br>目前还没有这些方法的 Hook 等价写法</p></li></ul><p>大致汇总成表格如下:</p><table><thead><tr><th>class 组件</th><th style="text-align:left">Hooks 组件</th></tr></thead><tbody><tr><td>constructor</td><td style="text-align:left">useState</td></tr><tr><td>getDerivedStateFromProps</td><td style="text-align:left">useState 里面 update 函数</td></tr><tr><td>shouldComponentUpdate</td><td style="text-align:left">useMemo</td></tr><tr><td>render</td><td style="text-align:left">函数本身</td></tr><tr><td>componentDidMount</td><td style="text-align:left">useEffect</td></tr><tr><td>componentDidUpdate</td><td style="text-align:left">useEffect</td></tr><tr><td>componentWillUnmount</td><td style="text-align:left">useEffect 里面返回的函数</td></tr><tr><td>componentDidCatch</td><td style="text-align:left">无</td></tr><tr><td>getDerivedStateFromError</td><td style="text-align:left">无</td></tr></tbody></table><h2 id="单个组件的生命周期"><a href="#单个组件的生命周期" class="headerlink" title="单个组件的生命周期"></a>单个组件的生命周期</h2><p>组件的生命周期可以分为三个阶段:</p><ul><li>挂载阶段</li><li>组件更新阶段</li><li>挂载阶段</li></ul><h3 id="v16-3-之前"><a href="#v16-3-之前" class="headerlink" title="v16.3 之前"></a>v16.3 之前</h3><ul><li>挂载阶段 - constructor - componentWillMount - render: react 最重要的步骤,创建虚拟 dom,进行 diff 算法,更新 dom 树都在此进行 - componentDidMount</li><li>组件更新阶段 - componentWillReceiveProps - shouldComponentUpdate - componentWillUpdate - render - componentDidUpdate</li><li>卸载阶段 - componentWillUnmount</li></ul><p>这种生命周期会存在一个问题,那就是当更新复杂组件的最上层组件时,调用栈会很长,如果再进行复杂的操作,就可能长时间阻塞主线程,带来不好的用户体验。</p><p>所以 v16.3 引入了新的 API 来解决这个问题</p><h3 id="v16-3-之后"><a href="#v16-3-之后" class="headerlink" title="v16.3 之后"></a>v16.3 之后</h3><ul><li><code>static getDerivedStateFromProps</code>: 该函数在挂载阶段和组件更新阶段都会执行,即每次获取新的 props 或 state 之后都会被执行,在挂载阶段用来代替 componentWillMount;在组件更新阶段配合 componentDidUpdate,可以覆盖 componentWillReceiveProps 的所有用法。</li></ul><p>同时它是一个静态函数,所以函数体内不能访问 this,会根据 nextProps 和 prevState 计算出预期的状态改变,返回结果会被送给 setState,返回 null 则说明不需要更新 state,并且这个返回是必须的。</p><ul><li><code>getSnapshotBeforeUpdate</code>: 该函数会在 render 之后,DOM 更新前被调用,用于读取最新的 DOM 数据。</li></ul><p>返回一个值,作为 componentDidUpdate 的第三个参数。配合 componentDidUpdate,可以覆盖 componentWillUpdate 的所有用法。</p><p>即更新后的生命周期为:</p><ul><li><p>挂载阶段 - constructor - static getDerivedStateFromProps - render - componentDidMount</p></li><li><p>更新阶段 - static getDerivedStateFromProps - shouldComponentUpdate - render - getSnapshotBeforeUpdate - componentDidUpdate</p></li><li><p>卸载阶段 - componentWillUnmount</p></li></ul>]]></content>
<summary type="html">
<!-- Hooks与React生命周期的关系 -->
<p>React 生命周期很多人都了解,但通常我们所了解的都是 单个组件 的生命周期,但针对 Hooks 组件、多个关联组件(父子组件和兄弟组件) 的生命周期又是怎么样的喃?你有思考和了解过吗,接下来我们将完整的了解 React 生命周期。</p>
<h2 id="Hooks-组件"><a href="#Hooks-组件" class="headerlink" title="Hooks 组件"></a>Hooks 组件</h2><p><code>函数组件</code>的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。</p>
<p>但是引入<code>Hooks</code><br>之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念</p>
<p>即:Hooks 组件(使用了 Hooks 的函数组件)有生命周期,而函数组件(未使用 Hooks 的函数组件)是没有生命周期的</p>
<p>下面,是具体的 class 与 Hooks 的生命周期对应关系</p>
<ul>
<li>constructor<br>函数组件不需要构造函数,我们可以通过调用<code>useState</code>来初始化 state</li>
<li>getDerivedStateFromProps<br>一般情况下,我们不需要使用它,我们可以在<code>渲染过程中更新state</code>,以达到实现 getDerivedStateFromProps 的目的</li>
<li>shouldComponentUpdate<br>可以用<code>React.memo</code>包裹一个组件对它的 props 进行浅比较<br><code>const Comp = React.memeo((props) =&gt; { // 具体的组件 })</code><br>注意: React.memo 等价于 PureComponent,它只浅比较 props,这里也可以使用 useMemo 优化每一个节点</li>
<li>render<br>这是函数组件体本身
</summary>
<category term="前端技术" scheme="https://jack-cool.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="Hooks" scheme="https://jack-cool.github.io/tags/Hooks/"/>
<category term="React" scheme="https://jack-cool.github.io/tags/React/"/>
</entry>
<entry>
<title>tsconfig.json 配置详解</title>
<link href="https://jack-cool.github.io/2019/08/05/tsconfig-json-%E9%85%8D%E7%BD%AE%E8%AF%A6%E8%A7%A3/"/>
<id>https://jack-cool.github.io/2019/08/05/tsconfig-json-配置详解/</id>
<published>2019-08-05T13:48:05.000Z</published>
<updated>2019-08-05T13:57:16.000Z</updated>
<content type="html"><![CDATA[<p>因为工作中用到了 typescript,抽时间,先大致介绍一下 tsconfig 配置文件,完整的 ts 学习还是建议从官方教程出发,玩儿的愉快</p><h2 id="tsconfig-json-配置"><a href="#tsconfig-json-配置" class="headerlink" title="tsconfig.json 配置"></a>tsconfig.json 配置</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="string">"compilerOptions"</span>: {</span><br><span class="line"> <span class="comment">/* Basic Options */</span></span><br><span class="line"> <span class="string">"target"</span>: <span class="string">"es5"</span> <span class="comment">/* target用于指定编译之后的版本目标: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */</span>,</span><br><span class="line"> <span class="string">"module"</span>: <span class="string">"commonjs"</span> <span class="comment">/* 用来指定要使用的模块标准: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */</span>,</span><br><span class="line"> <span class="string">"lib"</span>: [<span class="string">"es6"</span>, <span class="string">"dom"</span>] <span class="comment">/* lib用于指定要包含在编译中的库文件 */</span>,</span><br><span class="line"> <span class="string">"allowJs"</span>: <span class="literal">true</span>, <span class="comment">/* allowJs设置的值为true或false,用来指定是否允许编译js文件,默认是false,即不编译js文件 */</span></span><br><span class="line"> <span class="string">"checkJs"</span>: <span class="literal">true</span>, <span class="comment">/* checkJs的值为true或false,用来指定是否检查和报告js文件中的错误,默认是false */</span></span><br><span class="line"> <span class="string">"jsx"</span>: <span class="string">"preserve"</span>, <span class="comment">/* 指定jsx代码用于的开发环境: 'preserve', 'react-native', or 'react'. */</span></span><br><span class="line"> <span class="string">"declaration"</span>: <span class="literal">true</span>, <span class="comment">/* declaration的值为true或false,用来指定是否在编译的时候生成相应的".d.ts"声明文件。如果设为true,编译每个ts文件之后会生成一个js文件和一个声明文件。但是declaration和allowJs不能同时设为true */</span></span><br><span class="line"> <span class="string">"declarationMap"</span>: <span class="literal">true</span>, <span class="comment">/* 值为true或false,指定是否为声明文件.d.ts生成map文件 */</span></span><br><span class="line"> <span class="string">"sourceMap"</span>: <span class="literal">true</span>, <span class="comment">/* sourceMap的值为true或false,用来指定编译时是否生成.map文件 */</span></span><br><span class="line"> <span class="string">"outFile"</span>: <span class="string">"./"</span>, <span class="comment">/* outFile用于指定将输出文件合并为一个文件,它的值为一个文件路径名。比如设置为"./dist/main.js",则输出的文件为一个main.js文件。但是要注意,只有设置module的值为amd和system模块时才支持这个配置 */</span></span><br><span class="line"> <span class="string">"outDir"</span>: <span class="string">"./"</span>, <span class="comment">/* outDir用来指定输出文件夹,值为一个文件夹路径字符串,输出的文件都将放置在这个文件夹 */</span></span><br><span class="line"> <span class="string">"rootDir"</span>: <span class="string">"./"</span>, <span class="comment">/* 用来指定编译文件的根目录,编译器会在根目录查找入口文件,如果编译器发现以rootDir的值作为根目录查找入口文件并不会把所有文件加载进去的话会报错,但是不会停止编译 */</span></span><br><span class="line"> <span class="string">"composite"</span>: <span class="literal">true</span>, <span class="comment">/* 是否编译构建引用项目 */</span></span><br><span class="line"> <span class="string">"incremental"</span>: <span class="literal">true</span>, <span class="comment">/* Enable incremental compilation */</span></span><br><span class="line"> <span class="string">"tsBuildInfoFile"</span>: <span class="string">"./"</span>, <span class="comment">/* Specify file to store incremental compilation information */</span></span><br><span class="line"> <span class="string">"removeComments"</span>: <span class="literal">true</span>, <span class="comment">/* removeComments的值为true或false,用于指定是否将编译后的文件中的注释删掉,设为true的话即删掉注释,默认为false */</span></span><br><span class="line"> <span class="string">"noEmit"</span>: <span class="literal">true</span>, <span class="comment">/* 不生成编译文件,这个一般比较少用 */</span></span><br><span class="line"> <span class="string">"importHelpers"</span>: <span class="literal">true</span>, <span class="comment">/* importHelpers的值为true或false,指定是否引入tslib里的辅助工具函数,默认为false */</span></span><br><span class="line"> <span class="string">"downlevelIteration"</span>: <span class="literal">true</span>, <span class="comment">/* 当target为'ES5' or 'ES3'时,为'for-of', spread, and destructuring'中的迭代器提供完全支持 */</span></span><br><span class="line"> <span class="string">"isolatedModules"</span>: <span class="literal">true</span>, <span class="comment">/* isolatedModules的值为true或false,指定是否将每个文件作为单独的模块,默认为true,它不可以和declaration同时设定 */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Strict Type-Checking Options */</span></span><br><span class="line"> <span class="string">"strict"</span>: <span class="literal">true</span> <span class="comment">/* strict的值为true或false,用于指定是否启动所有类型检查,如果设为true则会同时开启下面这几个严格类型检查,默认为false */</span>,</span><br><span class="line"> <span class="string">"noImplicitAny"</span>: <span class="literal">true</span>, <span class="comment">/* noImplicitAny的值为true或false,如果我们没有为一些值设置明确的类型,编译器会默认认为这个值为any,如果noImplicitAny的值为true的话。则没有明确的类型会报错。默认值为false */</span></span><br><span class="line"> <span class="string">"strictNullChecks"</span>: <span class="literal">true</span>, <span class="comment">/* strictNullChecks为true时,null和undefined值不能赋给非这两种类型的值,别的类型也不能赋给他们,除了any类型。还有个例外就是undefined可以赋值给void类型 */</span></span><br><span class="line"> <span class="string">"strictFunctionTypes"</span>: <span class="literal">true</span>, <span class="comment">/* strictFunctionTypes的值为true或false,用于指定是否使用函数参数双向协变检查 */</span></span><br><span class="line"> <span class="string">"strictBindCallApply"</span>: <span class="literal">true</span>, <span class="comment">/* 设为true后会对bind、call和apply绑定的方法的参数的检测是严格检测的 */</span></span><br><span class="line"> <span class="string">"strictPropertyInitialization"</span>: <span class="literal">true</span>, <span class="comment">/* 设为true后会检查类的非undefined属性是否已经在构造函数里初始化,如果要开启这项,需要同时开启strictNullChecks,默认为false */</span></span><br><span class="line"> <span class="string">"noImplicitThis"</span>: <span class="literal">true</span>, <span class="comment">/* 当this表达式的值为any类型的时候,生成一个错误 */</span></span><br><span class="line"> <span class="string">"alwaysStrict"</span>: <span class="literal">true</span>, <span class="comment">/* alwaysStrict的值为true或false,指定始终以严格模式检查每个模块,并且在编译之后的js文件中加入"use strict"字符串,用来告诉浏览器该js为严格模式 */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Additional Checks */</span></span><br><span class="line"> <span class="string">"noUnusedLocals"</span>: <span class="literal">true</span>, <span class="comment">/* 用于检查是否有定义了但是没有使用的变量,对于这一点的检测,使用eslint可以在你书写代码的时候做提示,你可以配合使用。它的默认值为false */</span></span><br><span class="line"> <span class="string">"noUnusedParameters"</span>: <span class="literal">true</span>, <span class="comment">/* 用于检查是否有在函数体中没有使用的参数,这个也可以配合eslint来做检查,默认为false */</span></span><br><span class="line"> <span class="string">"noImplicitReturns"</span>: <span class="literal">true</span>, <span class="comment">/* 用于检查函数是否有返回值,设为true后,如果函数没有返回值则会提示,默认为false */</span></span><br><span class="line"> <span class="string">"noFallthroughCasesInSwitch"</span>: <span class="literal">true</span>, <span class="comment">/* 用于检查switch中是否有case没有使用break跳出switch,默认为false */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Module Resolution Options */</span></span><br><span class="line"> <span class="string">"moduleResolution"</span>: <span class="string">"node"</span>, <span class="comment">/* 用于选择模块解析策略,有'node'和'classic'两种类型' */</span></span><br><span class="line"> <span class="string">"baseUrl"</span>: <span class="string">"./"</span>, <span class="comment">/* baseUrl用于设置解析非相对模块名称的基本目录,相对模块不会受baseUrl的影响 */</span></span><br><span class="line"> <span class="string">"paths"</span>: {}, <span class="comment">/* 用于设置模块名称到基于baseUrl的路径映射 */</span></span><br><span class="line"> <span class="string">"rootDirs"</span>: [], <span class="comment">/* rootDirs可以指定一个路径列表,在构建时编译器会将这个路径列表中的路径的内容都放到一个文件夹中 */</span></span><br><span class="line"> <span class="string">"typeRoots"</span>: [], <span class="comment">/* typeRoots用来指定声明文件或文件夹的路径列表,如果指定了此项,则只有在这里列出的声明文件才会被加载 */</span></span><br><span class="line"> <span class="string">"types"</span>: [], <span class="comment">/* types用来指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载进来 */</span></span><br><span class="line"> <span class="string">"allowSyntheticDefaultImports"</span>: <span class="literal">true</span>, <span class="comment">/* 用来指定允许从没有默认导出的模块中默认导入 */</span></span><br><span class="line"> <span class="string">"esModuleInterop"</span>: <span class="literal">true</span> <span class="comment">/* 通过为导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性 */</span>,</span><br><span class="line"> <span class="string">"preserveSymlinks"</span>: <span class="literal">true</span>, <span class="comment">/* 不把符号链接解析为其真实路径,具体可以了解下webpack和nodejs的symlink相关知识 */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Source Map Options */</span></span><br><span class="line"> <span class="string">"sourceRoot"</span>: <span class="string">""</span>, <span class="comment">/* sourceRoot用于指定调试器应该找到TypeScript文件而不是源文件位置,这个值会被写进.map文件里 */</span></span><br><span class="line"> <span class="string">"mapRoot"</span>: <span class="string">""</span>, <span class="comment">/* mapRoot用于指定调试器找到映射文件而非生成文件的位置,指定map文件的根路径,该选项会影响.map文件中的sources属性 */</span></span><br><span class="line"> <span class="string">"inlineSourceMap"</span>: <span class="literal">true</span>, <span class="comment">/* 指定是否将map文件的内容和js文件编译在同一个js文件中,如果设为true,则map的内容会以//# sourceMappingURL=然后拼接base64字符串的形式插入在js文件底部 */</span></span><br><span class="line"> <span class="string">"inlineSources"</span>: <span class="literal">true</span>, <span class="comment">/* 用于指定是否进一步将.ts文件的内容也包含到输入文件中 */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Experimental Options */</span></span><br><span class="line"> <span class="string">"experimentalDecorators"</span>: <span class="literal">true</span> <span class="comment">/* 用于指定是否启用实验性的装饰器特性 */</span></span><br><span class="line"> <span class="string">"emitDecoratorMetadata"</span>: <span class="literal">true</span>, <span class="comment">/* 用于指定是否为装饰器提供元数据支持,关于元数据,也是ES6的新标准,可以通过Reflect提供的静态方法获取元数据,如果需要使用Reflect的一些方法,需要引入ES2015.Reflect这个库 */</span></span><br><span class="line"> }</span><br><span class="line"> <span class="string">"files"</span>: [], <span class="comment">// files可以配置一个数组列表,里面包含指定文件的相对或绝对路径,编译器在编译的时候只会编译包含在files中列出的文件,如果不指定,则取决于有没有设置include选项,如果没有include选项,则默认会编译根目录以及所有子目录中的文件。这里列出的路径必须是指定文件,而不是某个文件夹,而且不能使用* ? **/ 等通配符</span></span><br><span class="line"> <span class="string">"include"</span>: [], <span class="comment">// include也可以指定要编译的路径列表,但是和files的区别在于,这里的路径可以是文件夹,也可以是文件,可以使用相对和绝对路径,而且可以使用通配符,比如"./src"即表示要编译src文件夹下的所有文件以及子文件夹的文件</span></span><br><span class="line"> <span class="string">"exclude"</span>: [], <span class="comment">// exclude表示要排除的、不编译的文件,它也可以指定一个列表,规则和include一样,可以是文件或文件夹,可以是相对路径或绝对路径,可以使用通配符</span></span><br><span class="line"> <span class="string">"extends"</span>: <span class="string">""</span>, <span class="comment">// extends可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置,继承来的文件的配置会覆盖当前文件定义的配置。TS在3.2版本开始,支持继承一个来自Node.js包的tsconfig.json配置文件</span></span><br><span class="line"> <span class="string">"compileOnSave"</span>: <span class="literal">true</span>, <span class="comment">// compileOnSave的值是true或false,如果设为true,在我们编辑了项目中的文件保存的时候,编辑器会根据tsconfig.json中的配置重新生成文件,不过这个要编辑器支持</span></span><br><span class="line"> <span class="string">"references"</span>: [], <span class="comment">// 一个对象数组,指定要引用的项目</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>因为工作中用到了 typescript,抽时间,先大致介绍一下 tsconfig 配置文件,完整的 ts 学习还是建议从官方教程出发,玩儿的愉快</p>
<h2 id="tsconfig-json-配置"><a href="#tsconfig-json-配置" class=
</summary>
<category term="编程语言" scheme="https://jack-cool.github.io/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/"/>
<category term="typescript" scheme="https://jack-cool.github.io/tags/typescript/"/>
</entry>
<entry>
<title>webpack4打造极致开发环境</title>
<link href="https://jack-cool.github.io/2019/08/05/webpack4%E6%89%93%E9%80%A0%E6%9E%81%E8%87%B4%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/"/>
<id>https://jack-cool.github.io/2019/08/05/webpack4打造极致开发环境/</id>
<published>2019-08-05T13:26:09.000Z</published>
<updated>2019-08-11T13:33:18.000Z</updated>
<content type="html"><![CDATA[<p>Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader 的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。</p><p>webpack 已经是大部分前端项目打包工具的首选,grunt、glup、browserify 等逐渐沦为辅助甚至完全被替代。在 grunt、glup、browserify 等已经相当火了之后,webpack 长江后浪推前浪,把前辈们拍死在沙滩上,实力惊人。</p><p>本文主要从 webpack4.x 入手,会对平时常用的 Webpack 配置一一讲解,各个功能点都有对应的详细例子,所以本文也比较长,但如果你能动手跟着本文中的例子完整写一次,相信你会觉得 Webpack 也不过如此。</p><h2 id="安装-webpack-的几种方式"><a href="#安装-webpack-的几种方式" class="headerlink" title="安装 webpack 的几种方式"></a>安装 webpack 的几种方式</h2><ul><li><code>global</code>(全局):通过 <code>webpack index.js</code> 运行</li><li><code>local</code>(项目维度安装):通过 <code>npx webpack index.js</code> 运行</li></ul><h2 id="知识点总结"><a href="#知识点总结" class="headerlink" title="知识点总结"></a>知识点总结</h2><a id="more"></a><ul><li>避免全局安装 webpack(针对多个项目采用不同的 webpack 版本进行打包的场景),可采用<code>npx</code>,具体可参考<code>http://www.ruanyifeng.com/blog/2019/02/npx.html</code></li><li><code>npx webpack --config 配置文件名</code>可指定 webpack 配置文件(默认为 webpack.config.js)</li><li>mode 选项(可能的值为 none、development 或 production(默认值))配置用于提供模式配置选项告诉 webpack 相应的使用其内置的优化。具体可参考<code>https://webpack.js.org/configuration/mode/#root</code></li><li>npm scripts:通过配置 package.json 里面的 scripts 字段,直接运行 webpack 即可。等价于<code>yarn run bundle -> webpack</code></li><li>file-loader 可打包处理<code>eot|svg|ttf|woff</code>等字体文件</li></ul><h2 id="entry-入口"><a href="#entry-入口" class="headerlink" title="entry(入口)"></a>entry(入口)</h2><h3 id="单一入口"><a href="#单一入口" class="headerlink" title="单一入口"></a>单一入口</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = {</span><br><span class="line"> entry: {</span><br><span class="line"> main: <span class="string">"./src/index.js"</span></span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="多入口"><a href="#多入口" class="headerlink" title="多入口"></a>多入口</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = {</span><br><span class="line"> entry: {</span><br><span class="line"> main: <span class="string">"./src/index.js"</span>,</span><br><span class="line"> sub: <span class="string">"./src/sub.js"</span></span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h2 id="output-输出"><a href="#output-输出" class="headerlink" title="output(输出)"></a>output(输出)</h2><h3 id="默认配置"><a href="#默认配置" class="headerlink" title="默认配置"></a>默认配置</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = {</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">'bundle.js'</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = config;</span><br></pre></td></tr></table></figure><h3 id="多个入口起点"><a href="#多个入口起点" class="headerlink" title="多个入口起点"></a>多个入口起点</h3><blockquote><p>如果配置创建了多个单独的 “chunk”(例如,使用多个入口起点或使用像 CommonsChunkPlugin 这样的插件),则应该使用占位符(substitutions)来确保每个文件具有唯一的名称。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line">{</span><br><span class="line"> entry: {</span><br><span class="line"> main: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> sub: <span class="string">'./src/sub.js'</span></span><br><span class="line"> },</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">'[name].js'</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写入到硬盘:./dist/main.js, ./dist/sub.js</span></span><br></pre></td></tr></table></figure><h3 id="高级进阶"><a href="#高级进阶" class="headerlink" title="高级进阶"></a>高级进阶</h3><blockquote><p>使用 cdn</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line">{</span><br><span class="line"> entry: {</span><br><span class="line"> main: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> sub: <span class="string">'./src/sub.js'</span></span><br><span class="line"> },</span><br><span class="line"> output: {</span><br><span class="line"> publicPath: <span class="string">'http://cdn.example.com'</span></span><br><span class="line"> filename: <span class="string">'[name].js'</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写入到http://cdn.example.com/main.js, http://cdn.example.com/sub.js</span></span><br></pre></td></tr></table></figure><h2 id="loaders"><a href="#loaders" class="headerlink" title="loaders"></a>loaders</h2><blockquote><p>webpack 可以使用 loader 来预处理文件。这允许你打包除 JavaScript 之外的任何静态资源。</p></blockquote><h3 id="file-loader"><a href="#file-loader" class="headerlink" title="file-loader"></a>file-loader</h3><ul><li>file-loader 可以解析项目中的 url 引入(不仅限于 css),根据我们的配置,将图片拷贝到相应的路径,再根据我们的配置,修改打包后文件引用路径,使之指向正确的文件。</li><li>默认情况下,生成的文件的文件名就是文件内容的 MD5 哈希值并会保留所引用资源的原始扩展名。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.(jpg|png|gif)$/</span>,</span><br><span class="line"> use: {</span><br><span class="line"> loader: <span class="string">"file-loader"</span>,</span><br><span class="line"> options: {</span><br><span class="line"> name: <span class="string">"[name]_[hash].[ext]"</span>,</span><br><span class="line"> outputPath: <span class="string">"images/"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">];</span><br></pre></td></tr></table></figure><h3 id="url-loader"><a href="#url-loader" class="headerlink" title="url-loader"></a>url-loader</h3><ul><li>url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。</li><li>url-loader 把资源文件转换为 URL,file-loader 也是一样的功能。不同之处在于 url-loader 更加灵活,它可以把小文件转换为 base64 格式的 URL,从而减少网络请求次数。url-loader 依赖 file-loader。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.(jpg|png|gif)$/</span>,</span><br><span class="line"> use: {</span><br><span class="line"> loader: <span class="string">"url-loader"</span>,</span><br><span class="line"> options: {</span><br><span class="line"> name: <span class="string">"[name]_[hash].[ext]"</span>,</span><br><span class="line"> outputPath: <span class="string">"images/"</span>,</span><br><span class="line"> limit: <span class="number">204800</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">];</span><br></pre></td></tr></table></figure><h3 id="css-loader"><a href="#css-loader" class="headerlink" title="css-loader"></a>css-loader</h3><ul><li>只负责加载 css 模块,不会将加载的 css 样式应用到 html</li><li>importLoaders 用于指定在 css-loader 前应用的 loader 的数量</li><li>查询参数 modules 会启用 CSS 模块规范</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>: {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.css$/</span>,</span><br><span class="line"> use: [<span class="string">"style-loader"</span>, <span class="string">"css-loader"</span>]</span><br><span class="line"> }</span><br><span class="line"> ];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="style-loader"><a href="#style-loader" class="headerlink" title="style-loader"></a>style-loader</h3><ul><li>负责将 css-loader 加载到的 css 样式动态的添加到 html-head-style 标签中</li><li>一般建议将 style-loader 与 css-loader 结合使用</li></ul><h3 id="sass-loader"><a href="#sass-loader" class="headerlink" title="sass-loader"></a>sass-loader</h3><h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><p><code>yarn add sass-loader node-sass webpack --dev</code></p><ul><li>node-sass 和 webpack 是 sass-loader 的 peerDependency,因此能够精确控制它们的版本。</li><li>loader 执行顺序:从下至上,从右至左</li><li>通过将 style-loader 和 css-loader 与 sass-loader 链式调用,可以立刻将样式作用在 DOM 元素。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line">...</span><br><span class="line"><span class="built_in">module</span>: {</span><br><span class="line"> rules: [{</span><br><span class="line"> test: <span class="regexp">/\.scss$/</span>,</span><br><span class="line"> use: [{</span><br><span class="line"> loader: <span class="string">"style-loader"</span> <span class="comment">// 将 JS 字符串生成为 style 节点</span></span><br><span class="line"> }, {</span><br><span class="line"> loader: <span class="string">"css-loader"</span> <span class="comment">// 将 CSS 转化成 CommonJS 模块</span></span><br><span class="line"> }, {</span><br><span class="line"> loader: <span class="string">"sass-loader"</span> <span class="comment">// 将 Sass 编译成 CSS</span></span><br><span class="line"> }]</span><br><span class="line"> }]</span><br><span class="line">}</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="postcss-loader"><a href="#postcss-loader" class="headerlink" title="postcss-loader"></a>postcss-loader</h3><ul><li>webpack4 中使用 postcss-loader 代替 autoprefixer,给 css3 样式加浏览器前缀。具体可参考<code>https://blog.csdn.net/u014628388/article/details/82593185</code></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.scss$/</span>,</span><br><span class="line"> use: [</span><br><span class="line"> <span class="string">'style-loader'</span>,</span><br><span class="line"> <span class="string">'css-loader'</span>,</span><br><span class="line"> <span class="string">'sass-loader'</span>,</span><br><span class="line"> <span class="string">'postcss-loader'</span></span><br><span class="line"> ],</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//postcss.config.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="built_in">require</span>(<span class="string">'autoprefixer'</span>)({ <span class="attr">browsers</span>: [<span class="string">'last 2 versions'</span>] }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h2 id="plugins"><a href="#plugins" class="headerlink" title="plugins"></a>plugins</h2><blockquote><p>plugin 可以在 webpack 运行到某个时刻的时候,帮你做一些事情</p></blockquote><h3 id="HtmlWebpackPlugin"><a href="#HtmlWebpackPlugin" class="headerlink" title="HtmlWebpackPlugin"></a>HtmlWebpackPlugin</h3><ul><li>HtmlWebpackPlugin 会在打包结束后,自动生成一个 html 文件,并把打包生成的 js 自动引入到这个 html 文件中</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> HtmlWebpackPlugin = <span class="built_in">require</span>(<span class="string">'html-webpack-plugin'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line">...</span><br><span class="line">plugins: [</span><br><span class="line"> <span class="keyword">new</span> HtmlWebpackPlugin({</span><br><span class="line"> template: <span class="string">'src/index.html'</span></span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="clean-webpack-plugin"><a href="#clean-webpack-plugin" class="headerlink" title="clean-webpack-plugin"></a>clean-webpack-plugin</h3><ul><li>clean-webpack-plugin 插件用来清除残留打包文件,特别是文件末尾添加了 hash 之后,会导致改变文件内容后重新打包时,文件名不同而内容越来越多。</li><li>新版本中的 clean-webpack-plugin 仅接受一个对象,默认不需要传任何参数。具体可参考<code>https://blog.csdn.net/qq_23521659/article/details/88353708</code></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> { CleanWebpackPlugin } = <span class="built_in">require</span>(<span class="string">'clean-webpack-plugin'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line">...</span><br><span class="line">plugins: [</span><br><span class="line"> <span class="keyword">new</span> CleanWebpackPlugin()</span><br><span class="line"> ],</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">'bundle.js'</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h3 id="SplitChunksPlugin"><a href="#SplitChunksPlugin" class="headerlink" title="SplitChunksPlugin"></a>SplitChunksPlugin</h3><ul><li>具体概念可参考<code>https://juejin.im/post/5af15e895188256715479a9a</code></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">splitChunks: {</span><br><span class="line"> chunks: <span class="string">"async"</span>,</span><br><span class="line"> minSize: <span class="number">30000</span>,</span><br><span class="line"> minChunks: <span class="number">1</span>,</span><br><span class="line"> maxAsyncRequests: <span class="number">5</span>,</span><br><span class="line"> maxInitialRequests: <span class="number">3</span>,</span><br><span class="line"> automaticNameDelimiter: <span class="string">'~'</span>,</span><br><span class="line"> name: <span class="literal">true</span>,</span><br><span class="line"> cacheGroups: {</span><br><span class="line"> vendors: {</span><br><span class="line"> test: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line"> priority: <span class="number">-10</span></span><br><span class="line"> },</span><br><span class="line"> <span class="keyword">default</span>: {</span><br><span class="line"> minChunks: <span class="number">2</span>,</span><br><span class="line"> priority: <span class="number">-20</span>,</span><br><span class="line"> reuseExistingChunk: <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="MiniCssExtractPlugin"><a href="#MiniCssExtractPlugin" class="headerlink" title="MiniCssExtractPlugin"></a>MiniCssExtractPlugin</h3><blockquote><p>将 CSS 提取为独立的文件的插件,对每个包含 css 的 js 文件都会创建一个 CSS 文件,支持按需加载 css 和 sourceMap</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> MiniCssExtractPlugin = <span class="built_in">require</span>(<span class="string">"mini-css-extract-plugin"</span>);</span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> MiniCssExtractPlugin({</span><br><span class="line"> <span class="comment">// Options similar to the same options in webpackOptions.output</span></span><br><span class="line"> <span class="comment">// both options are optional</span></span><br><span class="line"> filename: <span class="string">"[name].css"</span>,</span><br><span class="line"> chunkFilename: <span class="string">"[id].css"</span></span><br><span class="line"> })</span><br><span class="line"> ],</span><br><span class="line"> <span class="built_in">module</span>: {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.scss$/</span>,</span><br><span class="line"> use: [</span><br><span class="line"> {</span><br><span class="line"> loader: MiniCssExtractPlugin.loader</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> loader: <span class="string">"css-loader"</span>,</span><br><span class="line"> options: {</span><br><span class="line"> importLoaders: <span class="number">2</span> <span class="comment">// 用于指定在 css-loader 前应用的 loader 的数量</span></span><br><span class="line"> <span class="comment">// modules: true // 查询参数 modules 会启用 CSS 模块规范</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> <span class="string">"sass-loader"</span>,</span><br><span class="line"> <span class="string">"postcss-loader"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.css$/</span>,</span><br><span class="line"> use: [</span><br><span class="line"> {</span><br><span class="line"> loader: MiniCssExtractPlugin.loader</span><br><span class="line"> },</span><br><span class="line"> <span class="string">"css-loader"</span>,</span><br><span class="line"> <span class="string">"postcss-loader"</span></span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="OptimizeCSSAssetsPlugin"><a href="#OptimizeCSSAssetsPlugin" class="headerlink" title="OptimizeCSSAssetsPlugin"></a>OptimizeCSSAssetsPlugin</h3><blockquote><p>webpack5 可能会内置 CSS 压缩器,webpack4 需要自己使用压缩器,可以使用 optimize-css-assets-webpack-plugin 插件。 设置 optimization.minimizer 覆盖 webpack 默认提供的,确保也指定一个 JS 压缩器</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> UglifyJsPlugin = <span class="built_in">require</span>(<span class="string">"uglifyjs-webpack-plugin"</span>);</span><br><span class="line"><span class="keyword">const</span> MiniCssExtractPlugin = <span class="built_in">require</span>(<span class="string">"mini-css-extract-plugin"</span>);</span><br><span class="line"><span class="keyword">const</span> OptimizeCSSAssetsPlugin = <span class="built_in">require</span>(<span class="string">"optimize-css-assets-webpack-plugin"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> optimization: {</span><br><span class="line"> minimizer: [</span><br><span class="line"> <span class="keyword">new</span> UglifyJsPlugin({</span><br><span class="line"> cache: <span class="literal">true</span>,</span><br><span class="line"> parallel: <span class="literal">true</span>,</span><br><span class="line"> sourcMap: <span class="literal">true</span></span><br><span class="line"> }),</span><br><span class="line"> <span class="keyword">new</span> OptimizeCSSAssetsPlugin({})</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> MiniCssExtractPlugin({</span><br><span class="line"> filename: <span class="string">"[name].css"</span>,</span><br><span class="line"> chunkFilename: <span class="string">"[id].css"</span></span><br><span class="line"> })</span><br><span class="line"> ],</span><br><span class="line"> <span class="built_in">module</span>: {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.css$/</span>,</span><br><span class="line"> use: [MiniCssExtractPlugin.loader, <span class="string">"css-loader"</span>]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h2 id="devtool"><a href="#devtool" class="headerlink" title="devtool"></a>devtool</h2><h3 id="source-map"><a href="#source-map" class="headerlink" title="source map"></a>source map</h3><blockquote><p>source map 就是对打包生成的代码与源代码的一种映射,主要是为了方便定位问题和排查问题。devtool 关键有 eval、cheap、module、inline 和 source-map 这几块,具体可参考文档:<code>https://www.webpackjs.com/configuration/devtool/</code></p></blockquote><ul><li>development 环境参考配置: <code>'cheap-module-eval-source-map'</code></li><li>production 环境参考配置: <code>'cheap-module-source-map'</code></li></ul><h3 id="webpack-dev-server"><a href="#webpack-dev-server" class="headerlink" title="webpack-dev-server"></a>webpack-dev-server</h3><blockquote><p>webpack-dev-server 提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。具体可参考<code>https://www.webpackjs.com/guides/development/#%E4%BD%BF%E7%94%A8-webpack-dev-server</code></p></blockquote><h4 id="接口代理-请求转发"><a href="#接口代理-请求转发" class="headerlink" title="接口代理(请求转发)"></a>接口代理(请求转发)</h4><blockquote><p>如果你有单独的后端开发服务器 API,并且希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用。dev-server 使用了非常强大的 <code>http-proxy-middleware</code> 包。常用于接口请求转发。具体参考<code>https://www.webpackjs.com/configuration/dev-server/#devserver-proxy</code></p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">devServer: {</span><br><span class="line"> contentBase: <span class="string">"./dist"</span>,</span><br><span class="line"> open: <span class="literal">true</span>,</span><br><span class="line"> hot: <span class="literal">true</span>,</span><br><span class="line"> hotOnly: <span class="literal">true</span>,</span><br><span class="line"> proxy: {</span><br><span class="line"> <span class="string">"/api"</span>: {</span><br><span class="line"> target: <span class="string">"https://other-server.example.com"</span>,</span><br><span class="line"> pathRewrite: {<span class="string">"^/api"</span> : <span class="string">""</span>},</span><br><span class="line"> secure: <span class="literal">false</span>,</span><br><span class="line"> bypass: <span class="function"><span class="keyword">function</span>(<span class="params">req, res, proxyOptions</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (req.headers.accept.indexOf(<span class="string">"html"</span>) !== <span class="number">-1</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Skipping proxy for browser request."</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"/index.html"</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> },</span><br></pre></td></tr></table></figure><h4 id="解决单页面路由问题"><a href="#解决单页面路由问题" class="headerlink" title="解决单页面路由问题"></a>解决单页面路由问题</h4><blockquote><p>当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html<br>通过传入以下启用:</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">historyApiFallback: <span class="literal">true</span>;</span><br></pre></td></tr></table></figure><p>通过传入一个对象,比如使用 rewrites 这个选项,此行为可进一步地控制:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">historyApiFallback: {</span><br><span class="line"> rewrites: [</span><br><span class="line"> { <span class="attr">from</span>: <span class="regexp">/^\/$/</span>, <span class="attr">to</span>: <span class="string">"/views/landing.html"</span> },</span><br><span class="line"> { <span class="attr">from</span>: <span class="regexp">/^\/subpage/</span>, <span class="attr">to</span>: <span class="string">"/views/subpage.html"</span> },</span><br><span class="line"> { <span class="attr">from</span>: <span class="regexp">/./</span>, <span class="attr">to</span>: <span class="string">"/views/404.html"</span> }</span><br><span class="line"> ];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="webpack-dev-middleware"><a href="#webpack-dev-middleware" class="headerlink" title="webpack-dev-middleware"></a>webpack-dev-middleware</h3><blockquote><p>webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// server.js</span></span><br><span class="line"><span class="comment">// 使用webpack-dev-middleware</span></span><br><span class="line"><span class="comment">// https://www.webpackjs.com/guides/development/#%E4%BD%BF%E7%94%A8-webpack-dev-middleware</span></span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">"webpack"</span>);</span><br><span class="line"><span class="keyword">const</span> webpackDevMiddleware = <span class="built_in">require</span>(<span class="string">"webpack-dev-middleware"</span>);</span><br><span class="line"><span class="keyword">const</span> config = <span class="built_in">require</span>(<span class="string">"./webpack.config.js"</span>);</span><br><span class="line"><span class="keyword">const</span> complier = webpack(config);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line"></span><br><span class="line">app.use(</span><br><span class="line"> webpackDevMiddleware(complier, {</span><br><span class="line"> publicPath: config.output.publicPath</span><br><span class="line"> })</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">3000</span>, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"server is running"</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="Hot-Module-Replacement"><a href="#Hot-Module-Replacement" class="headerlink" title="Hot Module Replacement"></a>Hot Module Replacement</h3><blockquote><p>模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>);</span><br><span class="line">...</span><br><span class="line">devServer: {</span><br><span class="line"> contentBase: <span class="string">'./dist'</span>,</span><br><span class="line"> open: <span class="literal">true</span>,</span><br><span class="line"> hot: <span class="literal">true</span>,</span><br><span class="line"> hotOnly: <span class="literal">true</span></span><br><span class="line">},</span><br><span class="line">plugins: [</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">new</span> webpack.HotModuleReplacementPlugin()</span><br><span class="line">],</span><br></pre></td></tr></table></figure><p>如果已经通过 HotModuleReplacementPlugin 启用了模块热替换(Hot Module Replacement),则它的接口将被暴露在 module.hot 属性下面。通常,用户先要检查这个接口是否可访问,然后再开始使用它。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">module</span>.hot) {</span><br><span class="line"> <span class="built_in">module</span>.hot.accept(<span class="string">"./library.js"</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 使用更新过的 library 模块执行某些操作...</span></span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="bundle-分析"><a href="#bundle-分析" class="headerlink" title="bundle 分析"></a>bundle 分析</h3><blockquote><p>借助一些官方推荐的可视化分析工具,可对打包后的模块进行分析以及优化</p></blockquote><ul><li><code>webpack-chart</code>: webpack 数据交互饼图</li><li><code>webpack-visualizer</code>: 可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的</li><li><code>webpack-bundle-analyzer</code>: 一款分析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户</li></ul><h3 id="Preloading、Prefetching"><a href="#Preloading、Prefetching" class="headerlink" title="Preloading、Prefetching"></a>Preloading、Prefetching</h3><blockquote><p>prefetch:会等待核心代码加载完成后,页面带宽空闲后再去加载 prefectch 对应的文件;preload:和主文件一起去加载</p></blockquote><ul><li>可以使用谷歌浏览器 Coverage 工具查看代码覆盖率(ctrl+shift+p > show coverage)</li><li>使用异步引入 js 的方式可以提高 js 的使用率,所以 webpack 建议我们多使用异步引入的方式,这也是 splitChunks.chunks 的默认值是”async”的原因</li><li>使用魔法注释 /_ webpackPrefetch: true _/ ,这样在主要 js 加载完,带宽有空闲时,会自动下载需要引入的 js</li><li>使用魔法注释 /_ webpackPreload: true _/,区别是 webpackPrefetch 会等到主业务文件加载完,带宽有空闲时再去下载 js,而 preload 是和主业务文件一起加载的</li></ul><h2 id="babel"><a href="#babel" class="headerlink" title="babel"></a>babel</h2><h3 id="babel-编译-es6、jsx-等"><a href="#babel-编译-es6、jsx-等" class="headerlink" title="babel 编译 es6、jsx 等"></a>babel 编译 es6、jsx 等</h3><ul><li>@babel/core babel 核心模块</li><li>@babel-preset-env 编译 es6 等</li><li>@babel/preset-react 转换 jsx</li><li>@babel/plugin-transform-runtime 避免 polyfill 污染全局变量,减少打包体积</li><li>@babel/polyfill es6 内置方法和函数转化垫片</li><li>@babel/runtime</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>: {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.js$/</span>,</span><br><span class="line"> exclude: <span class="regexp">/node_modules/</span>,</span><br><span class="line"> use: {</span><br><span class="line"> loader: <span class="string">"babel-loader"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>新建.babelrc 文件</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="string">"presets"</span>: [<span class="string">"@babel/preset-env"</span>, <span class="string">"@babel/preset-react"</span>],</span><br><span class="line"> <span class="string">"plugins"</span>: [<span class="string">"@babel/plugin-transform-runtime"</span>]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="按需引入-polyfill"><a href="#按需引入-polyfill" class="headerlink" title="按需引入 polyfill"></a>按需引入 polyfill</h3><p>在 src 下的 index.js 中全局引入@babel/polyfill 并写入 es6 语法,但是这样有一个缺点:<br>全局引入@babel/polyfill 的这种方式可能会导入代码中不需要的 polyfill,从而使打包体积更大,修改.babelrc 配置</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="string">`yarn add core-js@2 @babel/runtime-corejs2 --dev`</span></span><br><span class="line"></span><br><span class="line">{</span><br><span class="line"> <span class="string">"presets"</span>: [</span><br><span class="line"> [</span><br><span class="line"> <span class="string">"@babel/preset-env"</span>, {</span><br><span class="line"> <span class="string">"useBuiltIns"</span>: <span class="string">"usage"</span></span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> <span class="string">"@babel/preset-react"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="string">"plugins"</span>: [<span class="string">"@babel/plugin-transform-runtime"</span>]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这就配置好了按需引入。配置了按需引入 polyfill 后,用到 es6 以上的函数,babel 会自动导入相关的 polyfill,这样能大大减少打包编译后的体积。</p><h3 id="babel-runtime-和-babel-polyfill-的区别"><a href="#babel-runtime-和-babel-polyfill-的区别" class="headerlink" title="babel-runtime 和 babel-polyfill 的区别"></a>babel-runtime 和 babel-polyfill 的区别</h3><blockquote><p>参考<code>https://www.jianshu.com/p/73ba084795ce</code></p></blockquote><ul><li>babel-polyfill 会”加载整个 polyfill 库”,针对编译的代码中新的 API 进行处理,并且在代码中插入一些帮助函数</li><li>babel-polyfill 解决了 Babel 不转换新 API 的问题,但是直接在代码中插入帮助函数,会导致污染了全局环境,并且不同的代码文件中包含重复的代码,导致编译后的代码体积变大。 Babel 为了解决这个问题,提供了单独的包 babel-runtime 用以提供编译模块的工具函数, 启用插件 babel-plugin-transform-runtime 后,Babel 就会使用 babel-runtime 下的工具函数</li><li>babel-runtime 适合在组件,类库项目中使用,而 babel-polyfill 适合在业务项目中使用。</li></ul><h2 id="高级概念"><a href="#高级概念" class="headerlink" title="高级概念"></a>高级概念</h2><h3 id="tree-shaking-js"><a href="#tree-shaking-js" class="headerlink" title="tree shaking(js)"></a>tree shaking(js)</h3><blockquote><p>tree shaking 可清除代码中无用的 js 代码,只支持 import 方式引入,不支持 commonjs 的方式引入<br>mode 是 production 的无需配置,下面的配置是针对 development 的</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line">optimization: {</span><br><span class="line"> usedExports: <span class="literal">true</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// package.json</span></span><br><span class="line"><span class="string">"sideEffects"</span>: <span class="literal">false</span>,</span><br></pre></td></tr></table></figure><h3 id="Code-Spliting"><a href="#Code-Spliting" class="headerlink" title="Code Spliting"></a>Code Spliting</h3><blockquote><p>代码分割,和 webpack 无关</p></blockquote><ul><li>同步代码(需在 webpack.config.js 中配置 optimization)</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">'lodash'</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(_.join([<span class="string">'a'</span>,<span class="string">'b'</span>,<span class="string">'c'</span>], <span class="string">'****'</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在webpack.base.js里做相关配置</span></span><br><span class="line">optimization: {</span><br><span class="line"> splitChunks: {</span><br><span class="line"> chunks: <span class="string">'all'</span></span><br><span class="line"> }</span><br><span class="line"> },</span><br></pre></td></tr></table></figure><ul><li>异步代码(无需任何配置,但需安装<code>@babel/plugin-syntax-dynamic-import</code>包)</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getComponent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">import</span>(<span class="string">"lodash"</span>).then(<span class="function">(<span class="params">{ <span class="keyword">default</span>: _ }</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> element = <span class="built_in">document</span>.createElement(<span class="string">"div"</span>);</span><br><span class="line"> element.innerHTML = _.join([<span class="string">"Jack"</span>, <span class="string">"Cool"</span>], <span class="string">"-"</span>);</span><br><span class="line"> <span class="keyword">return</span> element;</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">getComponent().then(<span class="function"><span class="params">el</span> =></span> {</span><br><span class="line"> <span class="built_in">document</span>.body.appendChild(el);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="Caching-缓存"><a href="#Caching-缓存" class="headerlink" title="Caching(缓存)"></a>Caching(缓存)</h3><blockquote><p>通过使用 output.filename 进行文件名替换,可以确保浏览器获取到修改后的文件。[hash] 替换可以用于在文件名中包含一个构建相关(build-specific)的 hash,但是更好的方式是使用 [contenthash] 替换,当文件内容发生变化时,[contenthash]也会发生变化</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">output: {</span><br><span class="line"> filename: <span class="string">"[name].[contenthash].js"</span>,</span><br><span class="line"> chunkFilename: <span class="string">'[name].[contenthash].chunk.js'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Shimming"><a href="#Shimming" class="headerlink" title="Shimming"></a>Shimming</h3><blockquote><p>webpack 编译器(compiler)能够识别遵循 ES2015 模块语法、CommonJS 或 AMD 规范编写的模块。然而,一些第三方的库(library)可能会引用一些全局依赖(例如 jQuery 中的 \$)。这些库也可能创建一些需要被导出的全局变量。这些“不符合规范的模块”就是 shimming 发挥作用的地方</p></blockquote><ul><li>shimming 全局变量(第三方库)(ProvidePlugin 相当于一个垫片)</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line">+ <span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">module</span>.exports = {</span><br><span class="line"> entry: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">'bundle.js'</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line">- }</span><br><span class="line">+ },</span><br><span class="line">+ plugins: [</span><br><span class="line">+ <span class="keyword">new</span> webpack.ProvidePlugin({</span><br><span class="line">+ _: <span class="string">'lodash'</span></span><br><span class="line">+ })</span><br><span class="line">+ ]</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><ul><li>细粒度 shimming(this 指向 window)(需要安装 imports-loader 依赖)</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"> <span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">module</span>.exports = {</span><br><span class="line"> entry: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">'bundle.js'</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line"> },</span><br><span class="line">+ <span class="built_in">module</span>: {</span><br><span class="line">+ rules: [</span><br><span class="line">+ {</span><br><span class="line">+ test: <span class="built_in">require</span>.resolve(<span class="string">'index.js'</span>),</span><br><span class="line">+ use: <span class="string">'imports-loader?this=>window'</span></span><br><span class="line">+ }</span><br><span class="line">+ ]</span><br><span class="line">+ },</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.ProvidePlugin({</span><br><span class="line"> join: [<span class="string">'lodash'</span>, <span class="string">'join'</span>]</span><br><span class="line"> })</span><br><span class="line"> ]</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><h3 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h3><blockquote><p>webpack 命令行环境选项 –env 允许您传入任意数量的环境变量。您的环境变量将可访问 webpack.config.js。例如,–env.production 或–env.NODE_ENV=local</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">webpack --env.NODE_ENV=local --env.production --progress</span><br></pre></td></tr></table></figure><p>使用环境变量必须对 webpack 配置进行一项更改。通常,module.exports 指向配置对象。要使用该 env 变量,必须转换 module.exports 为函数:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">"path"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">env</span> =></span> {</span><br><span class="line"> <span class="comment">// Use env.<YOUR VARIABLE> here:</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"NODE_ENV: "</span>, env.NODE_ENV); <span class="comment">// 'local'</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Production: "</span>, env.production); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> entry: <span class="string">"./src/index.js"</span>,</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">"bundle.js"</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">"dist"</span>)</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="library-打包配置"><a href="#library-打包配置" class="headerlink" title="library 打包配置"></a>library 打包配置</h3><blockquote><p>除了打包应用程序代码,webpack 还可以用于打包 JavaScript library<br>用户应该能够通过以下方式访问 library:</p></blockquote><ul><li>ES2015 模块。例如 import library from ‘library’</li><li>CommonJS 模块。例如 require(‘library’)</li><li>全局变量,当通过 script 脚本引入时</li></ul><p>我们打包的 library 中可能会用到一些第三方库,诸如 lodash。现在,如果执行 webpack,你会发现创建了一个非常巨大的文件。如果你查看这个文件,会看到 lodash 也被打包到代码中。在这种场景中,我们更倾向于把 lodash 当作 peerDependency。也就是说,用户应该已经将 lodash 安装好。因此,你可以放弃对外部 library 的控制,而是将控制权让给使用 library 的用户。这可以使用 externals 配置来完成:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// webpack.config.js</span></span><br><span class="line"> <span class="keyword">var</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">module</span>.exports = {</span><br><span class="line"> entry: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> output: {</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line"> filename: <span class="string">'webpack-numbers.js'</span></span><br><span class="line">- }</span><br><span class="line">+ },</span><br><span class="line">+ externals: {</span><br><span class="line">+ lodash: {</span><br><span class="line">+ commonjs: <span class="string">'lodash'</span>,</span><br><span class="line">+ commonjs2: <span class="string">'lodash'</span>,</span><br><span class="line">+ amd: <span class="string">'lodash'</span>,</span><br><span class="line">+ root: <span class="string">'_'</span></span><br><span class="line">+ }</span><br><span class="line">+ }</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><p>对于用途广泛的 library,我们希望它能够兼容不同的环境,例如 CommonJS,AMD,Node.js 或者作为一个全局变量。为了让你的 library 能够在各种用户环境(consumption)中可用,需要在 output 中添加 library 属性:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// webpack.config.js</span></span><br><span class="line"> <span class="keyword">var</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">module</span>.exports = {</span><br><span class="line"> entry: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> output: {</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line">- filename: <span class="string">'library.js'</span></span><br><span class="line">+ filename: <span class="string">'library.js'</span>,</span><br><span class="line">+ library: <span class="string">'library'</span></span><br><span class="line"> },</span><br><span class="line"> externals: {</span><br><span class="line"> lodash: {</span><br><span class="line"> commonjs: <span class="string">'lodash'</span>,</span><br><span class="line"> commonjs2: <span class="string">'lodash'</span>,</span><br><span class="line"> amd: <span class="string">'lodash'</span>,</span><br><span class="line"> root: <span class="string">'_'</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><p>当你在 import 引入模块时,这可以将你的 library bundle 暴露为名为 webpackNumbers 的全局变量。为了让 library 和其他环境兼容,还需要在配置文件中添加 libraryTarget 属性。这是可以控制 library 如何以不同方式暴露的选项。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">var</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">module</span>.exports = {</span><br><span class="line"> entry: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> output: {</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line"> filename: <span class="string">'library.js'</span>,</span><br><span class="line">+ library: <span class="string">'library'</span>,</span><br><span class="line">+ libraryTarget: <span class="string">'umd'</span></span><br><span class="line"> },</span><br><span class="line"> externals: {</span><br><span class="line"> lodash: {</span><br><span class="line"> commonjs: <span class="string">'lodash'</span>,</span><br><span class="line"> commonjs2: <span class="string">'lodash'</span>,</span><br><span class="line"> amd: <span class="string">'lodash'</span>,</span><br><span class="line"> root: <span class="string">'_'</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><p>我们还需要通过设置 package.json 中的 main 字段,添加生成 bundle 的文件路径。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line">{</span><br><span class="line"> ...</span><br><span class="line"> <span class="string">"main"</span>: <span class="string">"dist/library.js"</span>,</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="PWA-打包配置"><a href="#PWA-打包配置" class="headerlink" title="PWA 打包配置"></a>PWA 打包配置</h3><blockquote><p>渐进式网络应用程序(Progressive Web Application - PWA),是一种可以提供类似于原生应用程序(native app)体验的网络应用程序(web app)。PWA 可以用来做很多事。其中最重要的是,在离线(offline)时应用程序能够继续运行功能。这是通过使用名为 Service Workers 的网络技术来实现的<br>添加 workbox-webpack-plugin 插件,并调整 webpack.config.js 文件:</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install workbox-webpack-plugin --save-dev</span><br></pre></td></tr></table></figure><p>webpack.config.js</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"> <span class="keyword">const</span> HtmlWebpackPlugin = <span class="built_in">require</span>(<span class="string">'html-webpack-plugin'</span>);</span><br><span class="line"> <span class="keyword">const</span> CleanWebpackPlugin = <span class="built_in">require</span>(<span class="string">'clean-webpack-plugin'</span>);</span><br><span class="line">+ <span class="keyword">const</span> WorkboxPlugin = <span class="built_in">require</span>(<span class="string">'workbox-webpack-plugin'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">module</span>.exports = {</span><br><span class="line"> entry: {</span><br><span class="line"> app: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> print: <span class="string">'./src/print.js'</span></span><br><span class="line"> },</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> CleanWebpackPlugin([<span class="string">'dist'</span>]),</span><br><span class="line"> <span class="keyword">new</span> HtmlWebpackPlugin({</span><br><span class="line">- title: <span class="string">'Output Management'</span></span><br><span class="line">+ title: <span class="string">'Progressive Web Application'</span></span><br><span class="line">- })</span><br><span class="line">+ }),</span><br><span class="line">+ <span class="keyword">new</span> WorkboxPlugin.GenerateSW({</span><br><span class="line">+ <span class="comment">// 这些选项帮助 ServiceWorkers 快速启用</span></span><br><span class="line">+ <span class="comment">// 不允许遗留任何“旧的” ServiceWorkers</span></span><br><span class="line">+ clientsClaim: <span class="literal">true</span>,</span><br><span class="line">+ skipWaiting: <span class="literal">true</span></span><br><span class="line">+ })</span><br><span class="line"> ],</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">'[name].bundle.js'</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">'dist'</span>)</span><br><span class="line"> }</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><p>注册 Service Worker</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">'lodash'</span>;</span><br><span class="line"> <span class="keyword">import</span> printMe <span class="keyword">from</span> <span class="string">'./print.js'</span>;</span><br><span class="line"></span><br><span class="line">+ <span class="keyword">if</span> (<span class="string">'serviceWorker'</span> <span class="keyword">in</span> navigator) {</span><br><span class="line">+ <span class="built_in">window</span>.addEventListener(<span class="string">'load'</span>, () => {</span><br><span class="line">+ navigator.serviceWorker.register(<span class="string">'/sw.js'</span>).then(<span class="function"><span class="params">registration</span> =></span> {</span><br><span class="line">+ <span class="built_in">console</span>.log(<span class="string">'SW registered: '</span>, registration);</span><br><span class="line">+ }).catch(<span class="function"><span class="params">registrationError</span> =></span> {</span><br><span class="line">+ <span class="built_in">console</span>.log(<span class="string">'SW registration failed: '</span>, registrationError);</span><br><span class="line">+ });</span><br><span class="line">+ });</span><br><span class="line">+ }</span><br></pre></td></tr></table></figure><p>现在来进行测试。停止服务器并刷新页面。如果浏览器能够支持 Service Worker,你应该可以看到你的应用程序还在正常运行。然而,服务器已经停止了服务,此刻是 Service Worker 在提供服务。</p><h3 id="TypeScript-打包配置"><a href="#TypeScript-打包配置" class="headerlink" title="TypeScript 打包配置"></a>TypeScript 打包配置</h3><blockquote><p>可参考<code>https://www.webpackjs.com/guides/typescript/</code>或<code>https://webpack.js.org/guides/typescript/</code></p></blockquote><ul><li>安装 ts 依赖<code>npm install --save-dev typescript ts-loader</code></li><li>增加 tsconfig.json 配置文件</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="string">"compilerOptions"</span>: {</span><br><span class="line"> <span class="string">"outDir"</span>: <span class="string">"./dist/"</span>,</span><br><span class="line"> <span class="string">"noImplicitAny"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="string">"module"</span>: <span class="string">"es6"</span>,</span><br><span class="line"> <span class="string">"target"</span>: <span class="string">"es5"</span>,</span><br><span class="line"> <span class="string">"jsx"</span>: <span class="string">"react"</span>,</span><br><span class="line"> <span class="string">"allowJs"</span>: <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>webpack.config.js 添加对 ts/tsx 语法支持(ts-loader)</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">"path"</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> entry: <span class="string">"./src/index.ts"</span>,</span><br><span class="line"> <span class="built_in">module</span>: {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: <span class="regexp">/\.tsx?$/</span>,</span><br><span class="line"> use: <span class="string">"ts-loader"</span>,</span><br><span class="line"> exclude: <span class="regexp">/node_modules/</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> resolve: {</span><br><span class="line"> extensions: [<span class="string">".tsx"</span>, <span class="string">".ts"</span>, <span class="string">".js"</span>]</span><br><span class="line"> },</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">"bundle.js"</span>,</span><br><span class="line"> path: path.resolve(__dirname, <span class="string">"dist"</span>)</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><ul><li>当从 npm 安装第三方库时,一定要牢记同时安装这个库的类型声明文件。可以从 TypeSearch 中找到并安装这些第三方库的类型声明文件。如<code>npm install --save-dev @types/lodash</code></li></ul><h2 id="webpack-性能优化"><a href="#webpack-性能优化" class="headerlink" title="webpack 性能优化"></a>webpack 性能优化</h2><ul><li>及时更新 node、yarn、webpack 等的版本</li><li>在尽可能少的模块上应用 loader</li><li>plugin 尽可能精简并确保可靠(选用社区已验证的插件)</li><li>resolve 参数合理配置(具体参考<code>https://www.webpackjs.com/configuration/resolve/</code>)</li><li>使用 DllPlugin 提高打包速度</li><li>控制包文件大小(tree shaking / splitChunksPlugin)</li><li>thread-loader,parallel-webpack,happypack 多进程打包</li><li>合理利用 sourceMap</li><li>结合<code>stats.json</code>分析打包结果(bundle analyze)</li><li>开发环境内存编译</li><li>开发环境无用插件剔除</li></ul>]]></content>
<summary type="html">
<p>Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader 的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。</p>
<p>webpack 已经是大部分前端项目打包工具的首选,grunt、glup、browserify 等逐渐沦为辅助甚至完全被替代。在 grunt、glup、browserify 等已经相当火了之后,webpack 长江后浪推前浪,把前辈们拍死在沙滩上,实力惊人。</p>
<p>本文主要从 webpack4.x 入手,会对平时常用的 Webpack 配置一一讲解,各个功能点都有对应的详细例子,所以本文也比较长,但如果你能动手跟着本文中的例子完整写一次,相信你会觉得 Webpack 也不过如此。</p>
<h2 id="安装-webpack-的几种方式"><a href="#安装-webpack-的几种方式" class="headerlink" title="安装 webpack 的几种方式"></a>安装 webpack 的几种方式</h2><ul>
<li><code>global</code>(全局):通过 <code>webpack index.js</code> 运行</li>
<li><code>local</code>(项目维度安装):通过 <code>npx webpack index.js</code> 运行</li>
</ul>
<h2 id="知识点总结"><a href="#知识点总结" class="headerlink" title="知识点总结"></a>知识点总结</h2>
</summary>
<category term="web工程化" scheme="https://jack-cool.github.io/categories/web%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
<category term="webpack4" scheme="https://jack-cool.github.io/tags/webpack4/"/>
</entry>
<entry>
<title>react-native开发过程梳理</title>
<link href="https://jack-cool.github.io/2019/08/04/react-native%E5%BC%80%E5%8F%91%E8%BF%87%E7%A8%8B%E6%A2%B3%E7%90%86/"/>
<id>https://jack-cool.github.io/2019/08/04/react-native开发过程梳理/</id>
<published>2019-08-04T15:02:28.000Z</published>
<updated>2019-08-04T15:15:46.000Z</updated>
<content type="html"><![CDATA[<p>之前在用 rn 开发跨平台应用时,仅是开发环境配置(运行基础的 hello world),就耗费了我大量的时间,今天整理一下,希望给打算入 rn 神坑的小伙伴一个指引(我已弃坑,准备去搞 flutter 了)。以下是 windows 平台的,mac 平台请绕路。。</p><h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><blockquote><p>配置可参考<code>https://reactnative.cn/docs/0.40/getting-started.html</code> <code>https://reactnative.cn/docs/getting-started.html</code> <code>https://facebook.github.io/react-native/docs/getting-started</code></p></blockquote><a id="more"></a><p>1、下载<code>Chocolatey</code>(windows 包管理器),用于安装<code>python2</code>、<code>jdk8</code>、<code>node</code>。安装方式</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin</span><br></pre></td></tr></table></figure><ul><li>jdk 安装与环境变量配置: <code>https://jingyan.baidu.com/article/6dad5075d1dc40a123e36ea3.html</code></li></ul><p>2、设置 npm 镜像</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm config set registry https://registry.npm.taobao.org --global</span><br><span class="line">npm config set disturl https://npm.taobao.org/dist --global</span><br></pre></td></tr></table></figure><p>3、安装 React Native 的命令行工具(react-native-cli)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g react-native-cli</span><br></pre></td></tr></table></figure><p>4、 android studio 安装(下载地址: <code>https://developer.android.google.cn/</code>)<br>安装界面中选择“custom”选项,确保选中了以下几项:</p><ul><li>Android SDK</li><li>Android SDK Platform</li><li>Performance (Intel ® HAXM)</li><li>Android Virtual Device</li></ul><p>5、ANDROID_HOME 环境变量配置</p><p>6、将 Android SDK 的 Tools 目录添加到 PATH 变量中<br>可以把 Android SDK 的 tools 和 platform-tools 目录添加到 PATH 变量中,以便在终端中运行一些 Android 工具</p><p>7、创建新项目<br>使用 React Native 命令行工具来创建一个名为”AwesomeProject”的新项目:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">react-native init AwesomeProject --version 0.55.4</span><br></pre></td></tr></table></figure><p>8、下载夜神模拟器<br>模拟器相关配置可参考<code>https://www.cnblogs.com/piaobodewu/p/9786513.html</code></p><ul><li>启动模拟器</li><li>cmd 进入夜神模拟器安装目录的 bin 目录下<code>D:\Program Files\Nox\bin</code>执行<code>nox_adb devices</code>,然后应该就有模拟器的名字了,一般都是 127.0.0.1:62001</li><li>cmd 进去 android 的 SDK 的 platform-tools 目录下<code>D:\android_sdk\platform-tools</code>执行命令<code>adb.exe connect 127.0.0.1:62001</code>连接模拟器</li><li>进入项目根目录将项目打包安装到模拟器<code>react-native run-android</code></li></ul><p>9、启动项目(项目根目录打包安装到模拟器)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">react-native run-android</span><br></pre></td></tr></table></figure><p>初始化项目运行可能会遇见的问题</p><ul><li>真机红屏报错: <code>https://www.cnblogs.com/piaobodewu/p/9889460.html</code></li><li>创建工程 gradle-2.4-all 包下载问题: <code>https://blog.csdn.net/u010411264/article/details/53636956</code></li></ul>]]></content>
<summary type="html">
<p>之前在用 rn 开发跨平台应用时,仅是开发环境配置(运行基础的 hello world),就耗费了我大量的时间,今天整理一下,希望给打算入 rn 神坑的小伙伴一个指引(我已弃坑,准备去搞 flutter 了)。以下是 windows 平台的,mac 平台请绕路。。</p>
<h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><blockquote>
<p>配置可参考<code>https://reactnative.cn/docs/0.40/getting-started.html</code> <code>https://reactnative.cn/docs/getting-started.html</code> <code>https://facebook.github.io/react-native/docs/getting-started</code></p>
</blockquote>
</summary>
<category term="前端技术" scheme="https://jack-cool.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="react-native" scheme="https://jack-cool.github.io/tags/react-native/"/>
</entry>
<entry>
<title>vue路由动态权限控制</title>
<link href="https://jack-cool.github.io/2019/08/04/vue%E8%B7%AF%E7%94%B1%E5%8A%A8%E6%80%81%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6/"/>
<id>https://jack-cool.github.io/2019/08/04/vue路由动态权限控制/</id>
<published>2019-08-04T07:54:12.000Z</published>
<updated>2019-08-04T10:04:40.000Z</updated>
<content type="html"><![CDATA[<p>前一段时间公司做了一个 oa 系统,涉及到菜单权限相关的。经过调研(又称 google),发现 vue2.2.0 之后添加的 addRoutes 功能甚是好用。整个过程踩了很多坑,因此简单整理了以下。</p><h3 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h3><ul><li>创建 vue 实例的时候将 vue-router 挂载,但这个时候 vue-router 挂载一些登录或者不用权限的公用的页面</li><li>当用户登录后,获取用户角色 role,将 role 和路由表的每个页面需要的权限作比较,生成最终用户可访问的路由表</li><li>调用 router.addRoutes(store.getters.addRouters)添加用户可访问的路由</li><li>使用 vuex 管理路由表,根据 vuex 中可访问的路由渲染侧边栏组件<a id="more"></a></li></ul><h4 id="router-js"><a href="#router-js" class="headerlink" title="router.js"></a>router.js</h4><blockquote><p>通过<code>vue-router</code>官方的 meta 标签来标示该页面能访问的权限有哪些。比如<code>meta: { role: ['admin','ceo'] }</code>表示该页面只有 admin 和 ceo 才有进入的权限。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// router.js</span></span><br><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span>;</span><br><span class="line"><span class="keyword">import</span> VueRouter <span class="keyword">from</span> <span class="string">"vue-router"</span>;</span><br><span class="line">Vue.use(VueRouter);</span><br><span class="line"><span class="keyword">import</span> Login <span class="keyword">from</span> <span class="string">'@/views/login'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//所有权限通用路由表</span></span><br><span class="line"><span class="comment">//如首页和登录页和一些不用权限的公用页面</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> constantRouterMap = [</span><br><span class="line">{ <span class="attr">path</span>: <span class="string">'/login'</span>, <span class="attr">component</span>: Login },</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment">//异步挂载的路由</span></span><br><span class="line"><span class="comment">//动态需要根据权限加载的路由表</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> asyncRouterMap = [</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">"/achievements"</span>,</span><br><span class="line"> name: <span class="string">"achievements"</span>,</span><br><span class="line"> component: achievements,</span><br><span class="line"> children: [</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">"employeeAchievements"</span>,</span><br><span class="line"> name: <span class="string">"employeeAchievements"</span>,</span><br><span class="line"> component: <span class="function"><span class="params">()</span> =></span></span><br><span class="line"> <span class="keyword">import</span>(<span class="string">"@/views/achievements/components/employeeAchievements"</span>),</span><br><span class="line"> meta: {</span><br><span class="line"> roles: [<span class="string">"ceo"</span>]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">"departmentAchievements"</span>,</span><br><span class="line"> name: <span class="string">"departmentAchievements"</span>,</span><br><span class="line"> component: <span class="function"><span class="params">()</span> =></span></span><br><span class="line"> <span class="keyword">import</span>(<span class="string">"@/views/achievements/components/departmentAchievements"</span>),</span><br><span class="line"> meta: {</span><br><span class="line"> roles: [<span class="string">"department_head"</span>]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">"subordinateAchievements"</span>,</span><br><span class="line"> name: <span class="string">"subordinateAchievements"</span>,</span><br><span class="line"> component: <span class="function"><span class="params">()</span> =></span></span><br><span class="line"> <span class="keyword">import</span>(<span class="string">"@/views/achievements/components/subordinateAchievements"</span>),</span><br><span class="line"> meta: {</span><br><span class="line"> roles: [<span class="string">"direct_supervisor"</span>, <span class="string">"project_manger"</span>, <span class="string">"general_manager"</span>]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">"strategyAchievements"</span>,</span><br><span class="line"> name: <span class="string">"strategyAchievements"</span>,</span><br><span class="line"> component: <span class="function"><span class="params">()</span> =></span></span><br><span class="line"> <span class="keyword">import</span>(<span class="string">"@/views/achievements/components/strategyAchievements"</span>),</span><br><span class="line"> meta: {</span><br><span class="line"> roles: [<span class="string">"ceo"</span>]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">"personalAchievements"</span>,</span><br><span class="line"> name: <span class="string">"personalAchievements"</span>,</span><br><span class="line"> component: <span class="function"><span class="params">()</span> =></span></span><br><span class="line"> <span class="keyword">import</span>(<span class="string">"@/views/achievements/components/personalAchievements"</span>),</span><br><span class="line"> meta: {</span><br><span class="line"> roles: [<span class="string">"employee"</span>]</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">"projectAchievements"</span>,</span><br><span class="line"> name: <span class="string">"projectAchievements"</span>,</span><br><span class="line"> component: <span class="function"><span class="params">()</span> =></span></span><br><span class="line"> <span class="keyword">import</span>(<span class="string">"@/views/achievements/components/projectAchievements"</span>),</span><br><span class="line"> meta: {</span><br><span class="line"> roles: [<span class="string">"project_manger"</span>, <span class="string">"general_manager"</span>]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> path: <span class="string">"*"</span>,</span><br><span class="line"> redirect: <span class="string">"/achievements"</span></span><br><span class="line"> }</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实例化vue的时候只挂载constantRouterMap</span></span><br><span class="line"><span class="keyword">const</span> createRouter = <span class="function"><span class="params">()</span> =></span> <span class="keyword">new</span> VueRouter({</span><br><span class="line">scrollBehavior: <span class="function"><span class="params">()</span> =></span> ({ <span class="attr">y</span>: <span class="number">0</span> }),</span><br><span class="line">routes: constantRouterMap</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过addRoutes动态添加的路由,如何动态删除(用户权限变化或用户登出)</span></span><br><span class="line"><span class="comment">// 参考`https://juejin.im/post/5c92ff94f265da6128275a85#heading-4` `https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465`</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> router = createRouter()</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">resetRouter</span>(<span class="params"></span>) </span>{</span><br><span class="line"><span class="keyword">const</span> newRouter = createRouter()</span><br><span class="line">router.matcher = newRouter.matcher</span><br><span class="line">}</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> router</span><br></pre></td></tr></table></figure><h4 id="main-js"><a href="#main-js" class="headerlink" title="main.js"></a>main.js</h4><blockquote><p>在<code>vue-router</code>的全局前置守卫<code>router.beforeEach</code>中获取用户对应权限信息,并用 addRoutes 动态添加可访问路由表</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">"vue"</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">"@/router"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">"./App.vue"</span>;</span><br><span class="line"><span class="keyword">import</span> store <span class="keyword">from</span> <span class="string">"@/store"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> iView <span class="keyword">from</span> <span class="string">"iview"</span>;</span><br><span class="line">Vue.use(iView);</span><br><span class="line"></span><br><span class="line">router.beforeEach(<span class="keyword">async</span> (to, <span class="keyword">from</span>, next) => {</span><br><span class="line"> <span class="comment">// to and from are both route objects. must call `next`.</span></span><br><span class="line"> <span class="keyword">const</span> hasRoles = store.state.user.roles && store.state.user.roles.length > <span class="number">0</span>; <span class="comment">// 判断是否有用户角色信息</span></span><br><span class="line"> <span class="keyword">if</span> (hasRoles) {</span><br><span class="line"> <span class="comment">// 有用户权限信息,说明所有可访问路由已生成,如访问没权限页面会自动进入404页面</span></span><br><span class="line"> next();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 没有用户权限信息,先拉取用户信息</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> roles = <span class="keyword">await</span> store.dispatch(<span class="string">"user/getInfo"</span>); <span class="comment">// 获取到用户权限信息</span></span><br><span class="line"> <span class="comment">// eslint-disable-next-line</span></span><br><span class="line"> <span class="keyword">const</span> accessRoutes = <span class="keyword">await</span> store.dispatch(</span><br><span class="line"> <span class="comment">// 生成可访问的路由表</span></span><br><span class="line"> <span class="string">"permission/generateRoutes"</span>,</span><br><span class="line"> roles</span><br><span class="line"> );</span><br><span class="line"> router.addRoutes(accessRoutes); <span class="comment">// 动态添加可访问路由表</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (to.name === <span class="string">"/"</span>) {</span><br><span class="line"> next({ <span class="attr">name</span>: <span class="string">"home"</span>, <span class="attr">replace</span>: <span class="literal">true</span> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> next({ ...to, <span class="attr">replace</span>: <span class="literal">true</span> }); <span class="comment">// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record</span></span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.log(err);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line">router.afterEach(<span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =></span> {</span><br><span class="line"> iView.LoadingBar.finish();</span><br><span class="line"> <span class="built_in">window</span>.scrollTo(<span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h4 id="store-permission-js"><a href="#store-permission-js" class="headerlink" title="store/permission.js"></a>store/permission.js</h4><blockquote><p>通过用户的权限和之前在 router.js 里面 asyncRouterMap 的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// store/permission.js</span></span><br><span class="line"><span class="keyword">import</span> { constantRouterMap, asyncRouterMap } <span class="keyword">from</span> <span class="string">"@/router"</span>; <span class="comment">// 引入静态路由和动态路由</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">hasPermission</span>(<span class="params">roles, route</span>) </span>{</span><br><span class="line"> <span class="comment">// 遍历判断是否有进入该页面的权限</span></span><br><span class="line"> <span class="keyword">if</span> (route.meta && route.meta.roles) {</span><br><span class="line"> <span class="keyword">return</span> roles.some(<span class="function"><span class="params">role</span> =></span> route.meta.roles.includes(role));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepCopy</span>(<span class="params">source</span>) </span>{</span><br><span class="line"> <span class="comment">// 深拷贝</span></span><br><span class="line"> <span class="keyword">if</span> (!source) {</span><br><span class="line"> <span class="keyword">return</span> source;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">let</span> sourceCopy = source <span class="keyword">instanceof</span> <span class="built_in">Array</span> ? [] : {};</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> item <span class="keyword">in</span> source) {</span><br><span class="line"> sourceCopy[item] =</span><br><span class="line"> <span class="keyword">typeof</span> source[item] === <span class="string">"object"</span> ? deepCopy(source[item]) : source[item];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> sourceCopy;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">filterAsyncRoutes</span>(<span class="params">routes, roles</span>) </span>{</span><br><span class="line"> <span class="comment">// 过滤生成可访问路由表</span></span><br><span class="line"> <span class="keyword">const</span> res = [];</span><br><span class="line"> routes.forEach(<span class="function"><span class="params">route</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> tmp = { ...route };</span><br><span class="line"> <span class="keyword">if</span> (hasPermission(roles, tmp)) {</span><br><span class="line"> <span class="keyword">if</span> (tmp.children) {</span><br><span class="line"> tmp.children = filterAsyncRoutes(tmp.children, roles);</span><br><span class="line"> }</span><br><span class="line"> res.push(tmp);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> namespaced: <span class="literal">true</span>,</span><br><span class="line"> state: {</span><br><span class="line"> routes: deepCopy(constantRouterMap),</span><br><span class="line"> addRoutes: []</span><br><span class="line"> },</span><br><span class="line"> getters: {},</span><br><span class="line"> mutations: {</span><br><span class="line"> SET_ROUTES: <span class="function">(<span class="params">state, routes</span>) =></span> {</span><br><span class="line"> state.addRoutes = routes;</span><br><span class="line"> state.routes = deepCopy(constantRouterMap.concat(routes));</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> actions: {</span><br><span class="line"> generateRoutes({ commit }, roles) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =></span> {</span><br><span class="line"> <span class="keyword">let</span> accessedRoutes = filterAsyncRoutes(asyncRouterMap, roles);</span><br><span class="line"> commit(<span class="string">"SET_ROUTES"</span>, deepCopy(accessedRoutes));</span><br><span class="line"> resolve(accessedRoutes);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="store-user-js"><a href="#store-user-js" class="headerlink" title="store/user.js"></a>store/user.js</h4><blockquote><p>获取用户对应的 role 之前,需要先拿到 token。详细流程如下:</p></blockquote><ul><li>登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个 token,拿到 token 之后(我会将这个 token 存贮到 cookie 中,保证刷新页面后能记住用户登录状态),前端会根据 token 再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)。</li><li>权限验证:通过 token 获取用户对应的 role,动态根据用户的 role 算出其对应有权限的路由,通过 router.addRoutes 动态挂载这些路由。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> {</span><br><span class="line"> namespaced: <span class="literal">true</span>,</span><br><span class="line"> state: {</span><br><span class="line"> token: <span class="string">""</span>,</span><br><span class="line"> isHr: <span class="literal">true</span>,</span><br><span class="line"> roles: []</span><br><span class="line"> },</span><br><span class="line"> getters: {},</span><br><span class="line"> mutations: {</span><br><span class="line"> SET_TOKEN: <span class="function">(<span class="params">state, token</span>) =></span> {</span><br><span class="line"> state.token = token;</span><br><span class="line"> },</span><br><span class="line"> SET_ROLES: <span class="function">(<span class="params">state, roles</span>) =></span> {</span><br><span class="line"> state.roles = roles;</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> actions: {</span><br><span class="line"> getInfo({ commit, state }) {</span><br><span class="line"> <span class="comment">// 获取用户信息接口,拿到服务端返回的token、用户名、用户权限等信息</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> rolesFromSeivice = [<span class="string">"ceo"</span>]; <span class="comment">// 从接口拿到的用户权限信息(用户角色)</span></span><br><span class="line"> commit(<span class="string">"SET_ROLES"</span>, rolesFromSeivice);</span><br><span class="line"> resolve(rolesFromSeivice);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>前一段时间公司做了一个 oa 系统,涉及到菜单权限相关的。经过调研(又称 google),发现 vue2.2.0 之后添加的 addRoutes 功能甚是好用。整个过程踩了很多坑,因此简单整理了以下。</p>
<h3 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h3><ul>
<li>创建 vue 实例的时候将 vue-router 挂载,但这个时候 vue-router 挂载一些登录或者不用权限的公用的页面</li>
<li>当用户登录后,获取用户角色 role,将 role 和路由表的每个页面需要的权限作比较,生成最终用户可访问的路由表</li>
<li>调用 router.addRoutes(store.getters.addRouters)添加用户可访问的路由</li>
<li>使用 vuex 管理路由表,根据 vuex 中可访问的路由渲染侧边栏组件
</summary>
<category term="前端技术" scheme="https://jack-cool.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="vue" scheme="https://jack-cool.github.io/tags/vue/"/>
</entry>
<entry>
<title>流年博客的Hello World</title>
<link href="https://jack-cool.github.io/2019/01/11/hello-world/"/>
<id>https://jack-cool.github.io/2019/01/11/hello-world/</id>
<published>2019-01-11T15:18:56.000Z</published>
<updated>2019-01-11T15:18:56.000Z</updated>
<content type="html"><![CDATA[<h3 id="你好,我是流年,进阶全栈的路上···"><a href="#你好,我是流年,进阶全栈的路上···" class="headerlink" title="你好,我是流年,进阶全栈的路上···"></a>你好,我是流年,进阶全栈的路上···</h3>]]></content>
<summary type="html">
<h3 id="你好,我是流年,进阶全栈的路上···"><a href="#你好,我是流年,进阶全栈的路上···" class="headerlink" title="你好,我是流年,进阶全栈的路上···"></a>你好,我是流年,进阶全栈的路上···</h3>
</summary>
</entry>
</feed>