-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
543 lines (339 loc) · 592 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
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>BeanLee Blog</title>
<subtitle>豆子先森的部落格</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://beanlee.github.io/"/>
<updated>2021-01-12T07:51:56.389Z</updated>
<id>https://beanlee.github.io/</id>
<author>
<name>Bean Lee</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>【译】前端性能优化检查表 2020</title>
<link href="https://beanlee.github.io/posts/fe-perf-checklist-2020/"/>
<id>https://beanlee.github.io/posts/fe-perf-checklist-2020/</id>
<published>2021-01-11T07:17:46.000Z</published>
<updated>2021-01-12T07:51:56.389Z</updated>
<content type="html"><![CDATA[<p>原文地址 <a href="https://www.smashingmagazine.com/2020/01/front-end-performance-checklist-2020-pdf-pages" target="_blank" rel="noopener">https://www.smashingmagazine.com/2020/01/front-end-performance-checklist-2020-pdf-pages</a></p><p>简单对一篇完整的检查表目录做一个翻译,极力推荐阅读原文,文中也提到了大量的有关性能优化策略和方法的关键字和链接,方便查询。</p><a id="more"></a><h3 id="准备计划和指标"><a href="#准备计划和指标" class="headerlink" title="准备计划和指标"></a>准备计划和指标</h3><ul><li>建立性能文化氛围(让更多人意识到性能的重要性)</li><li>目标:比最快的竞争对手至少快 20%</li><li>选择正确的指标(选择正确的指标,不是所有指标都重要)</li><li>在能代表大多数用户群体的设备上收集数据(保证准确地数据收集)</li><li>为测试环境准备“干净的”和“符合真实用户的”相关的配置</li><li>与身边人分享性能指标(确保团队每个人都认可)</li></ul><h3 id="设定实际目标"><a href="#设定实际目标" class="headerlink" title="设定实际目标"></a>设定实际目标</h3><ul><li>响应时间在 100ms,60fps</li><li>3G 网络下的 FID<100ms,TTI<5s,速度索引<3s,关键文件大小预算<170KB(已压缩)</li></ul><h3 id="定义环境"><a href="#定义环境" class="headerlink" title="定义环境"></a>定义环境</h3><ul><li>选择并配置好项目中的构建工具</li><li>默认使用渐进增强</li><li>选择良好的性能基准</li><li>仔细评估框架和依赖(不是每个项目或者页面都需要笨重的框架和大量的依赖库)</li><li>考虑使用 PRPL 模式和应用程序外壳体系的结构</li><li>检查 API 是否存在优化空间(比如 GraghQL)</li><li>框架选择,Google AMP 还是 Facebook 的 Instant Articles?</li><li>合理运用 CDN</li></ul><h3 id="资源优化"><a href="#资源优化" class="headerlink" title="资源优化"></a>资源优化</h3><ul><li>利用 Brotli 进行纯文本压缩(你可能总听到 gzip,brotli 是 2015 年 Google 提出的无损压缩的格式)</li><li>使用响应式图片以及 webp</li><li>图片是否可以再进一步优化?</li><li>视频是否可以进行适当优化?</li><li>网络字体是否经过优化?(适当删除和裁切可以减小加载体积)</li></ul><h3 id="构建优化"><a href="#构建优化" class="headerlink" title="构建优化"></a>构建优化</h3><ul><li>设置优先事项</li><li>在生产环境中使用原生 JavaScript 模块</li><li>合理使用 tree-shaking, scope hoisting, code-splitting</li><li>考虑将一些复杂繁重的 js 放在 Web Worker (预加载数据以及 PWA)</li><li>考虑将一些复杂的计算逻辑放在 WebAssembly 中</li><li>是否正在使用提前编译器?</li><li>对于旧版浏览器仅提供旧版代码</li><li>对于 JavaScript 使用模块还是非模块模式?</li><li>通过增量解耦识别并重写旧代码</li><li>识别并且删除没有用到的代码(代码覆盖率,Chrome 工具)</li><li>缩减 JavaScript Bundle 大小</li><li>对于 JavaScript Chunks 是否使用预测性的预读取(prefetch)</li><li>针对 JavaScript 引擎有针对性的优化</li><li>CSR 还是 SSR?都要!</li><li>使用依靠自建的 lib 资源库(安全、可控)</li><li>限制第三方脚本影响</li><li>设置正确的 HTTP 缓存头(检查 expires,max-age,cache-control)</li></ul><h3 id="Delivery-Optimizations"><a href="#Delivery-Optimizations" class="headerlink" title="Delivery Optimizations"></a>Delivery Optimizations</h3><ul><li>是否异步加载了所有的异步库?</li><li>使用 IntersectionObserver 和优先级提示来延迟加载体积大的组件模块</li><li>逐步加载图像(渐进式图像加载,由模糊到清晰)</li><li>优先加载基础且重要的 CSS 资源</li><li>尝试重新组合 CSS 规则</li><li>是否对信息流有响应?</li><li>考虑使你的组件具有连接意识(公用和复用数据)</li><li>考虑使你的组件设备了解内存占用</li><li>利用 dns-prefetch 加快交付速度</li><li>善用 service workers 来缓存和网络容灾</li><li>是否正在 CDN/Edge 上使用 service worker,比如 A/B Test?</li><li>优化渲染性能</li><li>是否优化了渲染体验</li><li>是否有效阻止了重排和重绘?</li></ul><h3 id="网络和-HTTP-2"><a href="#网络和-HTTP-2" class="headerlink" title="网络和 HTTP/2"></a>网络和 HTTP/2</h3><ul><li>是否开启 OCSP stapling</li><li>是否已采用 IPV6</li><li>确保所有资源请求都是经过 HTTP/2</li><li>正确部署 HTTP/2</li><li>确保服务器和 CDN 都已支持 HTTP/2</li><li>支持 QUIC 的 HTTP(HTTP/3)</li><li>是否采用 HPACK 压缩?</li><li>确保服务器安全性(HTTPS 等)</li></ul><h3 id="测试与监控"><a href="#测试与监控" class="headerlink" title="测试与监控"></a>测试与监控</h3><ul><li>是否有话了审计工作流程(加强 CI 自动化)</li><li>是否在代理浏览器和旧版浏览器上进行测试?</li><li>是否测试了对可访问性的影响?</li><li>是否设置了持续监控</li></ul>]]></content>
<summary type="html">
<p>原文地址 <a href="https://www.smashingmagazine.com/2020/01/front-end-performance-checklist-2020-pdf-pages" target="_blank" rel="noopener">https://www.smashingmagazine.com/2020/01/front-end-performance-checklist-2020-pdf-pages</a></p>
<p>简单对一篇完整的检查表目录做一个翻译,极力推荐阅读原文,文中也提到了大量的有关性能优化策略和方法的关键字和链接,方便查询。</p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="前端" scheme="https://beanlee.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
<category term="性能" scheme="https://beanlee.github.io/tags/%E6%80%A7%E8%83%BD/"/>
</entry>
<entry>
<title>有关 SourceMap 的几点个人思考</title>
<link href="https://beanlee.github.io/posts/think-about-sourcemap/"/>
<id>https://beanlee.github.io/posts/think-about-sourcemap/</id>
<published>2020-11-18T10:48:37.000Z</published>
<updated>2021-01-12T07:50:07.032Z</updated>
<content type="html"><![CDATA[<!-- ## 有关 SourceMap 的几点个人思考 --><p>这两天在组里协助排查一个 webpack dev 环境下重载时内存溢出的问题,现象是 webpack-dev-server 启动的过程中,在 <code>node 10.xx</code> 的环境下,一旦修改文件工程自动 reload 时会出现内存溢出导致服务直接中断。</p><p>排查原因,最终发现原来是脚手架在配置开发环境的 <code>devtool</code> 配置时,为了方便开发者 DEBUG,设置成了 <code>cheap-module-source-map</code> ,在代码修改时触发自动 load,在 loader 编译映射 sourcemap 的时候<strong>溢~出~了~</strong>,本文不会说明为什么溢出,排查的过程以及解决,其实花的时间也不长,重点说明一下 sourcemap 的作用以及使用过程的几点个人思考。</p><a id="more"></a><p>异常</p><figure class="highlight shell"><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">[27849:0x102880000] 133250 ms: Mark-sweep 1377.8 (1409.0) -> 1377.8 (1409.5) MB, 366.7 / 0.0 ms (average mu = 0.076, current mu = 0.015) allocation failure scavenge might not succeed</span><br><span class="line"></span><br><span class="line"><--- JS stacktrace ---></span><br><span class="line"></span><br><span class="line">==== JS stack trace =========================================</span><br><span class="line"></span><br><span class="line">Security context: 0x1673f099e6c1 <JSObject></span><br><span class="line"> 0: builtin exit frame: stringify(this=0x1673f09919f9 <Object map = 0x1673955842a9>,0x1673fa7826f1 <undefined>,0x1673fa7826f1 <undefined>,0x1673500ffcc9 <Object map = 0x167345daabb1>,0x1673f09919f9 <Object map = 0x1673955842a9>)</span><br><span class="line"></span><br><span class="line"> 1: arguments adaptor frame: 1->3</span><br><span class="line"> 2: /* anonymous */(aka /* anonymous */) [0x1673e6f2d6a1] [/Users/xxx/node_modules/webpack/lib/SourceMap...</span><br><span class="line"></span><br><span class="line">FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory</span><br></pre></td></tr></table></figure><p>好了,上面的报错信息看过就可以忘记了。</p><p>SourceMap 在 WEB 开发过程中扮演着很重要的角色,因为被浏览器执行的代码往往与开发过程中原始代码相差很多,源码经过了很多次转译、合并、混淆、压缩等等步骤几乎不具有可读性,那么开发中调试、监控到异常定位快速与本地的源码映射匹配就成了它的唯一“使命”。</p><ul><li>HTTP Headers 支持 <code>SourceMap: <url></code> 属性,在请求时就能将映射的资源显示在浏览器的调试面板里。</li><li>现代浏览器都支持加载的文件内含有类似 <code>//# sourceMappingURL=path/to/xxx</code> 这类注释,用来明确匹配到对应的指定文件方便跟踪。</li></ul><p>那么在使用 SourceMap 应该注意什么呢?</p><h4 id="可读性"><a href="#可读性" class="headerlink" title="可读性"></a>可读性</h4><p>要发挥它的作用,就要生成的 SourceMap 文件尽量地映射成源码,方便 debug。</p><p>比如,Webpack 的 devtool 中支持很多种生成模式,其中常用的 <code>cheap-module-source-map</code> 配置,就可以映射方便定位 <code>.tsx</code> 的内容。</p><h4 id="平衡性能"><a href="#平衡性能" class="headerlink" title="平衡性能"></a>平衡性能</h4><p>生成 SourceMap 映射文件的过程,是一个转码的过程,记录两边的映射关系,那么转换时性能就成为了一个需要平衡的问题。<strong>理想情况</strong>,开发时尽可能保证转换速率,而构建时可以降低构建的性能消耗,保证文件的映射还原度。</p><h4 id="预防泄漏"><a href="#预防泄漏" class="headerlink" title="预防泄漏"></a>预防泄漏</h4><p>要避免源码泄漏,那么生成的 SourceMap 文件的存放位置就不能随意被外网访问到。</p><p>比如,异常监控系统中,为了方便开发者快速定位问题代码,在每一次构建时产生的 sourcemap 文件保存在内网指定一个位置,同时与当前上线部署版本做好匹配。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>在做 Java 开发时候反编译这个词应该很常见,也有对应的工具用来混淆和反混淆,WEB 端的 JavaScript / CSS 通常来说对任何人都是透明地,所以一些关键逻辑(比如涉及抽奖,特殊逻辑)尽量不要写在 js 脚本中,可以放在服务端或者编译成诸如 WebAssembly 一类字节码来避免一些重要逻辑泄漏。</p><p>PS.附赠一篇昨天无意中看到的总结,写的实在挺好的,和业界大佬还是有很大差距,加油吧。</p><blockquote><p>我在阿里云做前端 <a href="https://juejin.im/post/6844903808904986637" target="_blank" rel="noopener">https://juejin.im/post/6844903808904986637</a></p></blockquote><h3 id="参考文章:"><a href="#参考文章:" class="headerlink" title="参考文章:"></a>参考文章:</h3><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/SourceMap" target="_blank" rel="noopener">https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/SourceMap</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Tools/Debugger/How_to/Use_a_source_map" target="_blank" rel="noopener">https://developer.mozilla.org/zh-CN/docs/Tools/Debugger/How_to/Use_a_source_map</a></li><li><a href="https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/" target="_blank" rel="noopener">https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/</a></li><li><a href="http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html" target="_blank" rel="noopener">http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html</a></li><li><a href="https://webpack.docschina.org/configuration/devtool/" target="_blank" rel="noopener">https://webpack.docschina.org/configuration/devtool/</a></li><li><a href="https://www.timsrc.com/article/20/source-maps" target="_blank" rel="noopener">https://www.timsrc.com/article/20/source-maps</a></li></ul>]]></content>
<summary type="html">
<!-- ## 有关 SourceMap 的几点个人思考 -->
<p>这两天在组里协助排查一个 webpack dev 环境下重载时内存溢出的问题,现象是 webpack-dev-server 启动的过程中,在 <code>node 10.xx</code> 的环境下,一旦修改文件工程自动 reload 时会出现内存溢出导致服务直接中断。</p>
<p>排查原因,最终发现原来是脚手架在配置开发环境的 <code>devtool</code> 配置时,为了方便开发者 DEBUG,设置成了 <code>cheap-module-source-map</code> ,在代码修改时触发自动 load,在 loader 编译映射 sourcemap 的时候<strong>溢~出~了~</strong>,本文不会说明为什么溢出,排查的过程以及解决,其实花的时间也不长,重点说明一下 sourcemap 的作用以及使用过程的几点个人思考。</p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="前端" scheme="https://beanlee.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
<category term="构建工具" scheme="https://beanlee.github.io/tags/%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7/"/>
</entry>
<entry>
<title>UNIX 编程艺术 读书笔记 1</title>
<link href="https://beanlee.github.io/posts/note-about-unix-art-1/"/>
<id>https://beanlee.github.io/posts/note-about-unix-art-1/</id>
<published>2020-11-17T11:02:20.000Z</published>
<updated>2020-11-18T13:22:42.000Z</updated>
<content type="html"><![CDATA[<!-- ## 《UNIX 编程艺术》读书笔记 1 --><p>以下规则很受用,不分语言,甚至有一些规则可以作为日常做事的原则,日后逐条对应写一些个人的理解。</p><p>有关 UNIX 编程的规则(摘自原文):</p><a id="more"></a><blockquote><ol><li>模块原则:使用简洁的接口拼合简单的部件。</li><li>清晰原则:清晰胜于机巧。</li><li>组合原则:设计时考虑拼接组合。</li><li>分离原则:策略同机制分离,接口同引擎分离。</li><li>简洁原则:设计要简洁,复杂度能低则低。</li><li>吝啬原则:除非确无它法,不要编写庞大的程序。</li><li>透明性原则:设计要可见,以便审查和调试。</li><li>健壮原则:健壮源于透明与简洁。</li><li>表示原则:把知识叠入数据以求逻辑质朴而健壮。</li><li>通俗原则:接口设计避免标新立异。</li><li>缄默原则:如果一个程序没什么好说的,就沉默。</li><li>补救原则:出现异常时,马上退出并给出足够错误信息。</li><li>经济原则:宁花机器一分,不花程序员一秒。</li><li>生成原则:避免手工 hack,尽量编写程序去生成程序。</li><li>优化原则:雕琢前先要有原型,跑之前先学会走。</li><li>多样原则:决不相信所谓“不二法门”的断言。</li><li>扩展原则:设计着眼未来,未来总比预想来得快。</li></ol></blockquote>]]></content>
<summary type="html">
<!-- ## 《UNIX 编程艺术》读书笔记 1 -->
<p>以下规则很受用,不分语言,甚至有一些规则可以作为日常做事的原则,日后逐条对应写一些个人的理解。</p>
<p>有关 UNIX 编程的规则(摘自原文):</p>
</summary>
<category term="读书笔记" scheme="https://beanlee.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>中后台前端搭建经验总结-技术细节篇(一)</title>
<link href="https://beanlee.github.io/posts/think-about-fe-of-backsystem/"/>
<id>https://beanlee.github.io/posts/think-about-fe-of-backsystem/</id>
<published>2020-10-20T12:18:13.000Z</published>
<updated>2020-10-20T14:00:07.000Z</updated>
<content type="html"><![CDATA[<!-- ## 中后台前端搭建经验总结-技术细节篇(一)--><blockquote><p>本篇是总结一个旧项目,项目在 2019 年 Q3 开发并上线运营,经历两个月迭代后目前已交接给其他团队维护,整理个人草稿箱发现这边还没有完成的总结,补充一些内容更新至此,内容应该是顺着回想起细节,后面再补充。</p></blockquote><p>关键词:中后台系统、从零到壹</p><a id="more"></a><h3 id="项目背景"><a href="#项目背景" class="headerlink" title="项目背景"></a>项目背景</h3><p>交易指挥中心是中台基础基础组件化向智能基础组件化升级的战略项目,同时驱动中台核心业务中心的平台化搭建。一期建成优惠监控、库存、订单、商品四大指挥中心系统以及门户,实现中台系统的从零到一的系统建设。</p><p>对于我们的前端团队意义在于,积累面向 B 端中后台系统开发经验,沉淀中后组件,配合中后台系统可视化构建平台完成。</p><h3 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h3><ul><li>React + TypeScript + MobX</li><li>构建工具 jdwcli(webpack + koa dev server)</li><li>UI(LEGAO React + 部分 Ant Design)</li><li>图表框架 HightCharts</li></ul><h3 id="部分方案设计"><a href="#部分方案设计" class="headerlink" title="部分方案设计"></a>部分方案设计</h3><h4 id="菜单路由"><a href="#菜单路由" class="headerlink" title="菜单路由"></a>菜单路由</h4><p>由于中后台系统,页面功能及数据信息需要一定的访问权限,除了在数据返回之前控制以外,前端展示的权限菜单控制就很重要。</p><h4 id="权限控制与菜单"><a href="#权限控制与菜单" class="headerlink" title="权限控制与菜单"></a>权限控制与菜单</h4><h5 id="动态菜单及路由"><a href="#动态菜单及路由" class="headerlink" title="动态菜单及路由"></a>动态菜单及路由</h5><p>中后台系统其页面功能及数据信息比较敏感,在面向 C 端用户的基础上,只要控制当前基于数据安全方面的考虑,页面的访问控制非常重要。首先,在服务端接口数据保证权限校验的前提下,用户在前台访问页面所看到的菜单也需要进行权限控制。</p><p>出于上面的考虑,针对当前系统的菜单设计时可以前端架构采用<strong>动态菜单设计</strong>。用户有权限访问到的菜单通过权限接口返回,再由前端渲染。(这里没有将权限限制逻辑放在前台有两点考虑:1、HTTP 接口容易被抓去伪造,非权限菜单容易暴露;2、当前系统是基于 ERP 等方式单点登录,集成了 ERP 系统的角色控制,前台和后台没有必要做重复的权限判断逻辑;)</p><p>笔者曾在一家专注线上协同办公(OA)的软件公司,负责过一段组织模型与菜单角色模块,经历过基于 <strong>RBAC</strong> 模式的控制权限改造,从数据库设计、服务开发以及到前台实现。</p><p>前端这里要做的就是,需要同时支持两套方案</p><ul><li>第一类:前端固定路由信息(针对不敏感菜单)</li><li>第二类:通过数据接口按照权限获取相关路由</li></ul><p>上面提到的两种方案的实现,遵循<strong>统一的一套数据结构</strong>就可以实现。</p><p>实现上面的设计,依赖于 jdwcli 创建的项目模板中<strong>页面工厂 PageFactory</strong>这个类,即采用工厂模式将菜单数据缓存,通过 React.lazy 实现动态引用,这样即达到分包的特点减小初始包的大小,又可以实现菜单控制。</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"key"</span>: <span class="string">"home"</span>,</span><br><span class="line"> <span class="attr">"page"</span>: <span class="string">"home"</span>,</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"/home"</span>,</span><br><span class="line"> <span class="attr">"show"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"title"</span>: <span class="string">"首页"</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"key"</span>: <span class="string">"xxxyyy"</span>,</span><br><span class="line"> <span class="attr">"page"</span>: <span class="string">"xxxyyy"</span>,</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"/xxxyyy"</span>,</span><br><span class="line"> <span class="attr">"show"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"title"</span>: <span class="string">"XXXYYY一级菜单"</span>,</span><br><span class="line"> <span class="attr">"children"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"key"</span>: <span class="string">"yyy"</span>,</span><br><span class="line"> <span class="attr">"page"</span>: <span class="string">"yyy"</span>,</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"/yyy"</span>,</span><br><span class="line"> <span class="attr">"show"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"title"</span>: <span class="string">"yyy系统介绍"</span></span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"iframe"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"iframesrc"</span>: <span class="string">"//xxx.yyy.com"</span>,</span><br><span class="line"> <span class="attr">"key"</span>: <span class="string">"xxxIndex"</span>,</span><br><span class="line"> <span class="attr">"page"</span>: <span class="string">"xxxIndex"</span>,</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"/xxxIndex"</span>,</span><br><span class="line"> <span class="attr">"show"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"title"</span>: <span class="string">"xxx首页"</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><h5 id="迁移旧系统和页面"><a href="#迁移旧系统和页面" class="headerlink" title="迁移旧系统和页面"></a>迁移旧系统和页面</h5><p>零售中台经过多年的沉淀,内部有很多的功能系统存在,在开发初期就基本确认无法通过段时间内一一重新开发实现,迁移旧页面这个功能也需要被考虑在内。</p><p>目前比较合适的方案有两种:</p><ol><li>采用 IFRAME 内嵌;</li><li>通过菜单或链接跳转离开;</li></ol><p>那么为了满足这两种方案,在前面设计菜单的同时,在数据结构中就要增加针对内嵌和跳转离开的标示,考虑前端路由当前页面是一个 IFRAME 内嵌系统,还是一个需要点击跳转离开的菜单。</p><p>完成这点以后,要做的就是开发一个统一接受 IFRAME 链接的 Component 即可。</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><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">@withRouter</span><br><span class="line">export default class IFrameCont extends React.Component<IFrameContProps> {</span><br><span class="line"> render() {</span><br><span class="line"> const { iframeSrc } = this.props;</span><br><span class="line"> if (!iframeSrc) return null;</span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <iframe</span><br><span class="line"> title={iframeSrc}</span><br><span class="line"> src={iframeSrc}</span><br><span class="line"> width="100%"</span><br><span class="line"> height="100%"</span><br><span class="line"> ></iframe></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>BUT,上面的实现,我们似乎忽略了一个问题,我们只考虑到了用户点击菜单路由跳转过来的页面,由于我们使用的是 HASH 路由,并没有考虑如果用户在当前页面刷新或通过页面 URL 直接访问指定路由时,当前 Component 无法拿到 src 的 props 的。这里我采取的办法是:同时将 Store 中菜单信息监听到当前组件中,与当前页面 URL 中的 pathname 进行一次匹配。</p><figure class="highlight diff"><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></pre></td><td class="code"><pre><span class="line"><span class="addition">+ @inject((stores: GlobalStores) => ({</span></span><br><span class="line"><span class="addition">+ menu: stores[PAGE_STORE].menu</span></span><br><span class="line"><span class="addition">+ }))</span></span><br><span class="line"><span class="addition">+ @observer</span></span><br><span class="line">@withRouter</span><br><span class="line">export default class IFrameCont extends React.Component<IFrameContProps> {</span><br><span class="line"> render() {</span><br><span class="line"> let iframeSrc = "";</span><br><span class="line"><span class="addition">+ if (!this.props.location.state) {</span></span><br><span class="line"><span class="addition">+ try {</span></span><br><span class="line"><span class="addition">+ iframeSrc = Array.from(this.getAllMenus(this.props.menu)).filter(menuItem => {</span></span><br><span class="line"><span class="addition">+ return menuItem.path === this.props.location.pathname;</span></span><br><span class="line"><span class="addition">+ })[0].iframesrc;</span></span><br><span class="line"><span class="addition">+ } catch (error) {</span></span><br><span class="line"><span class="addition">+ console.error("error iframe src");</span></span><br><span class="line"><span class="addition">+ return null;</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"><span class="addition">+ } else {</span></span><br><span class="line"><span class="addition">+ const { menu } = this.props.location.state;</span></span><br><span class="line"><span class="addition">+ iframeSrc = menu.iframesrc;</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"></span><br><span class="line"> if (!iframeSrc) {</span><br><span class="line"> return null;</span><br><span class="line"> }</span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <iframe title={`${Math.random()}`} src={iframeSrc} width="100%" height="100%"></iframe></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 class="addition">+ private getAllMenus(pagedata: PageStore.Domain.PageType[], map?: Set<PageType>) {</span></span><br><span class="line"><span class="addition">+ return pagedata.reduce(</span></span><br><span class="line"><span class="addition">+ (prev, next) => {</span></span><br><span class="line"><span class="addition">+ prev.add(next);</span></span><br><span class="line"><span class="addition">+ if (next.children && next.children.length > 0) {</span></span><br><span class="line"><span class="addition">+ this.getAllMenus(next.children, prev);</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"><span class="addition">+ return prev;</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"><span class="addition">+ map ? map : new Set<PageType>()</span></span><br><span class="line"><span class="addition">+ );</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="数据可视化"><a href="#数据可视化" class="headerlink" title="数据可视化"></a>数据可视化</h4><p>中后台系统中,许多数据需要具有更丰富的展现形式,那么<strong>图与表</strong>的结合就是比较好的方案。</p><p>本项目中图采用 <strong>HighCharts</strong> 作为图库,考虑有两个原因:</p><ol><li>有以往使用的经验,项目紧急时期可以快速实现,并且公司内拥有采购 HighCharts 的授权(Wiki 中翻到了历史采购清单文件);</li><li>对比同类型 BizCharts、ECharts、Chartjs、D3.js 等其它图表库,HighCharts 具备官方文档丰富且详细、DEMO 实例丰富、图表种类多,页面渲染依靠 SVG 效率较高、拥有官方支持 React 版本(highcharts-react-official)、可快速产品原型中的图例、行业老大位置等特点;</li></ol><blockquote><p>这是一篇迟来的总结文章,其实如果时间允许,可以选择的方案还有很多,今天还刚刚看到 Airbnb 团队公布开源了一个新的可视化组件库。<a href="https://airbnb.io/visx/" target="_blank" rel="noopener">https://airbnb.io/visx/</a></p></blockquote><p>内部实现的有 双轴图、堆叠图、百分比堆叠图、饼图、坐标系图(散点图的延伸)实现细节以及部分问题总结详见另外一篇撰写中总结。</p><h4 id="表单实现"><a href="#表单实现" class="headerlink" title="表单实现"></a>表单实现</h4><p>对于中后台,健壮表单功能应该是必不可少的一环,由于当前项目都是比较简单的表单,目前的项目经验,大致分为两种:</p><ul><li>查询,带有复杂条件的关联查询,后面跟随查询结果</li><li>传统 Form 提交,用户填充数据提交</li></ul><p>这里提一个问题:当你看到 Form 表单会联想到什么?下面是目前我能想到的内容:</p><ul><li>输入框校验问题(正则、服务端校验、输入转义防止注入攻击)</li><li>当前表单的状态保存,以及重置(关于 Store 的控制)</li><li>交互体验(输入和可选框之间的联动,提交重置按钮出现的位置等等)</li><li>表单的某一条或某些条目需要作为动态内容可以添加、删除和修改</li></ul><p>那么这些表单能否有通过可视化拖拽,自动生成吗?答案是肯定的,同组其他的同事就在调研实现这个问题,找机会深入讨论一下。</p><h3 id="性能优化点"><a href="#性能优化点" class="headerlink" title="性能优化点"></a>性能优化点</h3><h4 id="级联组件性能"><a href="#级联组件性能" class="headerlink" title="级联组件性能"></a>级联组件性能</h4><p>优惠指挥中心系统中价格力系数查询页面,品牌级联,级联菜单由于存在一个 3K 左右个的数据由后端一次性返回,需要前端在前台把数据组装成树接口分级,再传递到级联组件中,这对于浏览器中内存计算耗时,以及组件初始化大量数据的性能都造成很大影响,在开发初期没有发现,Mock 数据只有几十个。</p><p>解决方案:就要和后端商量将查询接口调整为分级查询,逐级进行查询,避免一次数量过大。这里还可以在继续深入优化的点,就是当前二级或三级级联数据被 load 过一次以后,前端缓存在当前页面内,鼠标划回父级数据时直接拿缓存数据。<strong>(当然,缓存永远是一把双刃剑,要考虑缓存什么时间失效,什么时间生效就要具体问题具体分析。)</strong></p><h3 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h3><p>又是一个经常被讨论到的问题,各种缓存策略,网络上可以找到很多优秀详细的文章,这里简单介绍一下本项目使用的一些缓存策略。</p><ol><li>将工具、UI 库等比如 React、React-DOM、MobX,HighCharts 等,按需在其页面内静态引入,并且提前上线到 CDN ,将固定引用链接利用客户端缓存,不在更新;</li><li>页面 Nginx 响应头设置缓存时间;</li><li>配合后端检查,固定类变化特别小的数据请求,可以适当增大缓存时间,同时缓存到客户端本地;</li></ol><h3 id="开发环境与生产环境隔离"><a href="#开发环境与生产环境隔离" class="headerlink" title="开发环境与生产环境隔离"></a>开发环境与生产环境隔离</h3><p>环境的隔离,应该是工程化中比较简单且常见的问题,在从零搭建系统过程中,也不免存在这个问题,我自己的解决方案是,利用头尾系统(内部系统)建立两套文件,分别对应预发测试和正式环境。预发测试环境需要经常更新,缓存时间设置也非常短,且请求的也是预发测试接口,这在上线前需要及时更换。</p><p>这点在项目构建打包时,可以根据设置环境来进行操作。</p><p>比如:利用 Babel 编译中移除项目内冗余的 console.log,线上仅保留 error 或者其他,那就可以在项目中的 <code>.babelrc</code> 进行如下配置:</p><figure class="highlight json"><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><br><span class="line"> <span class="attr">"env"</span>: {</span><br><span class="line"> <span class="attr">"dev"</span>: {</span><br><span class="line"> <span class="attr">"plugins"</span>: [</span><br><span class="line"> [</span><br><span class="line"> <span class="string">"transform-remove-console"</span>,</span><br><span class="line"> { <span class="attr">"exclude"</span>: [<span class="string">"log"</span>, <span class="string">"debug"</span>, <span class="string">"error"</span>, <span class="string">"warn"</span>] }</span><br><span class="line"> ]</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"prod"</span>: {</span><br><span class="line"> <span class="attr">"plugins"</span>: [[<span class="string">"transform-remove-console"</span>, { <span class="attr">"exclude"</span>: [<span class="string">"error"</span>] }]]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样在 npm script 中配置 <code>BABEL_ENV=dev xxxx</code> 依次类推可以根据需要配置,对打包进行区分。</p><p><strong>注意:</strong> babelrc 中的 dev 是为了举例说明,配置成 development 和 production 当然最好,因为默认会读 NODE_ENV 环境变量的配置。</p><h3 id="多人协作"><a href="#多人协作" class="headerlink" title="多人协作"></a>多人协作</h3><p>这里不提太多,总结了一个方便的项目模板,利用 VSCode 或者 WebStorm 自身来实现保存时自动按照 <code>Prettier + ESLint -fix</code> 来格式化和自动修复部分代码,同时还可以结合 husky 在提交之前再进行一次格式化,可以一定程度地约束项目代码。</p><p>Git 提交 message ,可以利用 commitizen + cz-conventional-changelog 来呈现更统一的提交信息。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>中后台系统具有很强的业务属性,只要找到其中规律,可以抽象沉淀出一些通用的业务组件,这样一来可以为可视化搭建中后台系统积累物料,二来对于仅有的统一风格的 UI 组件库来的更有意义。</p>]]></content>
<summary type="html">
<!-- ## 中后台前端搭建经验总结-技术细节篇(一)-->
<blockquote>
<p>本篇是总结一个旧项目,项目在 2019 年 Q3 开发并上线运营,经历两个月迭代后目前已交接给其他团队维护,整理个人草稿箱发现这边还没有完成的总结,补充一些内容更新至此,内容应该是顺着回想起细节,后面再补充。</p>
</blockquote>
<p>关键词:中后台系统、从零到壹</p>
</summary>
<category term="总结" scheme="https://beanlee.github.io/tags/%E6%80%BB%E7%BB%93/"/>
</entry>
<entry>
<title>单品页手势实现记录</title>
<link href="https://beanlee.github.io/posts/page-with-gesture/"/>
<id>https://beanlee.github.io/posts/page-with-gesture/</id>
<published>2020-09-29T14:39:47.000Z</published>
<updated>2020-10-20T12:20:05.000Z</updated>
<content type="html"><![CDATA[<p>撰写中,先占个坑以免自己忘了,主要记录仿抖音交互式 H5 页手势过程实现</p>]]></content>
<summary type="html">
<p>撰写中,先占个坑以免自己忘了,主要记录仿抖音交互式 H5 页手势过程实现</p>
</summary>
<category term="前端" scheme="https://beanlee.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>Redis 入门指南(第 2 版)读书笔记</title>
<link href="https://beanlee.github.io/posts/getting-started-about-redis/"/>
<id>https://beanlee.github.io/posts/getting-started-about-redis/</id>
<published>2020-09-22T05:55:28.000Z</published>
<updated>2020-09-22T15:31:07.000Z</updated>
<content type="html"><![CDATA[<!-- ## Redis 入门指南(第 2 版) 读书笔记 --><blockquote><p>读书笔记《Redis 入门指南》 李子骅. Redis 入门指南(第 2 版)(异步图书) (Chinese Edition) (Kindle Locations 627-632). 人民邮电出版社. Kindle Edition.</p></blockquote><p>前阵子部门团建要去外地,路上大巴车来回有将近 6、7 个小时的空闲时间,排除和身边人玩 Switch 的时间,大部分时间都用来速读一本纯技术类工具书,《Redis 入门指南(第 2 版)》,粗略自己摘录并且记录一些读书笔记,记录于此,以备自己方便查阅。内容可以较多但仅限个人记录一定有所疏漏,还是建议使用到 Redis 的开发者朋友,使用 Dash 或者其他查看 API 一类工具放手边随时查阅。</p><a id="more"></a><blockquote><p>转岗做前端之前,我是一名 Java 服务端工程师,最早接触 Redis 时,是在上个部门,将 Redis 作为缓存使用(在来京东之前,只使用过 memerorycache 以及 H2 内存数据库,Oracle、MySQL, MSSQL 水平一般,大部分使用 Hibernate 封装,加入京东以后基本使用 iBatis + MySQL ),最初使用 Redis 非常初级,直接调用被封装好的 API,基本使用到 GET 和 SET 操作,当时没有太深入,速读便于回顾。</p></blockquote><h3 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h3><p>Redis (Remote Dictionary Server)顾名思义他的存储形式是以字典形式存储结构,并且允许其他应用通过 TCP 协议进行读写操作。</p><p>Redis 数据库中的所有数据都存储在内存中。由于内存的读写速度远快于硬盘,因此在读写性能上对比其他基于硬盘存储的数据库系统有非常明显的优势,按照常理,存储在内存存在一个问题,就是一旦程序退出或者硬件断电,就会导致数据丢失,不过 Redis 提供了数据持久化到硬盘的方案支持,可以异步写入硬盘,同时不影响对客户端提供数据服务。</p><h3 id="安装与启动-停止"><a href="#安装与启动-停止" class="headerlink" title="安装与启动/停止"></a>安装与启动/停止</h3><p>OS X</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install redis</span><br></pre></td></tr></table></figure><p>Linux 等</p><figure class="highlight bash"><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">wget http://download.redis.io/redisstable.tar</span><br><span class="line">tar xzf redis-stable.tar.gz</span><br><span class="line"><span class="built_in">cd</span> redis-stable</span><br><span class="line">make</span><br></pre></td></tr></table></figure><p>默认端口:<strong>6379</strong></p><table><thead><tr><th>文件名</th><th>说明</th></tr></thead><tbody><tr><td>redis-server</td><td>服务</td></tr><tr><td>redis-cli</td><td>命令行客户端</td></tr><tr><td>redis-benchmark</td><td>性能测试工具</td></tr><tr><td>redis-check-aof</td><td>AOF 文件修复工具</td></tr><tr><td>redis-check-dump</td><td>RDB 文件检查工具</td></tr><tr><td>redis-sentinel</td><td>Sentinel 服务器(v2.8 后)</td></tr></tbody></table><p>停止服务,Redis 会先断开客户端连接,然后根据配置持久化数据,最后退出。</p><p>启动时可以自定义配置 <code>redis-server path/to/redis.conf</code></p><h4 id="多数据库支持"><a href="#多数据库支持" class="headerlink" title="多数据库支持"></a>多数据库支持</h4><p>Redis 默认支持 16 个数据库,不可以自定义数据库名,只能根据编号命名,默认从 0 开始。</p><p>不支持为每一个数据库设置密码,一个客户端要么能访问所有库,要么没权限访问。</p><h3 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h3><p>Redis 与 MySQL 等关系型数据库以二维表形式的存储有非常大的差异,Redis 是 NoSQL 中一员,采用 key - value 方式存储,它的键值支持存储的类型有下面集中类型:</p><ul><li>字符串类型</li><li>散列类型</li><li>列表类型</li><li>集合类型</li><li>有序集合类型</li></ul><h4 id="字符串类型"><a href="#字符串类型" class="headerlink" title="字符串类型"></a>字符串类型</h4><p>key 和 value 都是字符串</p><p><code>SET</code> 和 <code>GET</code></p><blockquote><p>提示 Redis 对于键的命名并没有强制的要求,但比较好的实践是用“对象类型:对象 ID:对象属性”来命名一个键,如使用键 user:1:friends 来存储 ID 为 1 的用户的好友列表。对于多个单词则推荐使用“.”分隔,一方面是沿用以前的习惯(Redis 以前版本的键名不能包含空格等特殊字符),另一方面是在 rediscli 中容易输入,无需使用双引号包裹。另外为了日后维护方便,键的命名一定要有意义,如 u:1:f 的可读性显然不如 user:1:friends 好(虽然采用较短的名称可以节省存储空间,但由于键值的长度往往远远大于键名的长度,所以这部分的节省大部分情况下并不如可读性来得重要)。</p></blockquote><h4 id="散列类型"><a href="#散列类型" class="headerlink" title="散列类型"></a>散列类型</h4><p>Hash,每一个键存储字段不同。</p><figure class="highlight bash"><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">HSET key field value</span><br><span class="line">HGET key</span><br><span class="line">HMSET key field value [field value ...] <span class="comment"># 存储某一个key,某一个或多个字段的 value</span></span><br><span class="line">HMGET key field [field ...] <span class="comment"># 获取某一key的某一个或多个value</span></span><br><span class="line">HGETALL key <span class="comment"># 获取某一 key 的所有value</span></span><br></pre></td></tr></table></figure><blockquote><p>自由地为任何键增减字段而不影响其他键。</p></blockquote><h4 id="列表类型"><a href="#列表类型" class="headerlink" title="列表类型"></a>列表类型</h4><blockquote><p>KEYS 命令需要遍历数据库中所有键,出于性能考虑,一般很少在生产环境使用。</p></blockquote><p>List 可以存储一个有序的字符串列表,内部是 <strong>双向链表</strong> 实现的,所以在列表两端增加元素时间负责度都是 O(1),获取越接近两端的元素速度越快。链表的缺点是通过索引来访问某一个元素慢。</p><blockquote><p>这种特性使列表类型能非常快速地完成关系数据库难以应付的场景:如社交网站的新鲜事,我们关心的只是最新的内容,使用列表类型存储,即使新鲜事的总数达到几千万个,获取其中最新的 100 条数据也是极快的。同样因为在两端插入记录的时间复杂度是 O(1),列表类型也适合用来记录日志,可以保证加入新日志的速度不会受到已有日志数量的影响。</p></blockquote><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">LPUSH key value [value ...] <span class="comment"># 向列表左边追加</span></span><br><span class="line">RPUSH key value [value ...] <span class="comment"># 向列表右边追加</span></span><br><span class="line"></span><br><span class="line">LPOP key <span class="comment"># 从列表左边弹出一个元素</span></span><br><span class="line">RPOP key <span class="comment"># 从列表右边弹出一个元素</span></span><br><span class="line"></span><br><span class="line">LLEN key <span class="comment"># 获取列表元素数量</span></span><br><span class="line"></span><br><span class="line">LRANGE key start stop <span class="comment"># 获取列表某一段,**!!常用!!**</span></span><br></pre></td></tr></table></figure><h4 id="集合类型"><a href="#集合类型" class="headerlink" title="集合类型"></a>集合类型</h4><blockquote><p>集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型在 Redis 内部是使用值为空的散列表(hashtable)实现的,所以这些操作的时间复杂度都是 O(1)。最方便的是多个集合类型键之间还可以进行并集、交集和差集运算…</p></blockquote><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">SADD key member [member] <span class="comment"># 添加</span></span><br><span class="line">SREM key member [member] <span class="comment"># 删除</span></span><br><span class="line"></span><br><span class="line">SMEMBERS key <span class="comment"># 获取key集合中所有元素</span></span><br><span class="line">SISMEMBER key member <span class="comment"># 判断某member元素是否在key集合中 **!!效率极高,O(1)!!**</span></span><br><span class="line"></span><br><span class="line">SDIFF key [key ,,] <span class="comment"># 集合差集</span></span><br><span class="line">SINTER key [key ,,] <span class="comment"># 集合交集</span></span><br><span class="line">SUNION key [key ,,] <span class="comment"># 集合并集</span></span><br></pre></td></tr></table></figure><h4 id="有序集合"><a href="#有序集合" class="headerlink" title="有序集合"></a>有序集合</h4><p>区别于列表类型:</p><blockquote><p>(1)列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用。(2)有序集合类型是使用散列表和跳跃表(Skiplist)实现的,所以即使读取位于中间部分的数据速度也很快(时间复杂度是 O(log(N)))。(3)列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)。(4)有序集合要比列表类型更耗费内存。</p></blockquote><figure class="highlight bash"><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">ZADD key score member [score member ...] <span class="comment"># 增加元素</span></span><br><span class="line">ZSCORE key member <span class="comment"># 获取</span></span><br><span class="line"></span><br><span class="line">ZRANGE key start stop [WITHSCORES] <span class="comment"># 获取有序的某一段(从小到大)</span></span><br><span class="line">ZREVRANGE key start stop [WITHSCORES] <span class="comment"># 获取有序的某一段(从大到小)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">ZRANGEBYSCORE key min max [WITHSCORES] [LIMIToffsetcount] <span class="comment"># 按照元素从小到大顺序返回 min 和 max 之间的元素</span></span><br></pre></td></tr></table></figure><h4 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h4><p>关系型 SQL 数据库一个非常大的特点就是事务的特性,同样也是 Redis 命令的最小的执行单元,一个事务要么执行,要么不执行。(关系型事务,多个表操作,同一个事务内要么都成功,要么都失败。)</p><figure class="highlight bash"><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">MULTI</span><br><span class="line"><span class="comment"># xxx 命令</span></span><br><span class="line">EXEC</span><br></pre></td></tr></table></figure><blockquote><p>Redis 事务可以保证一个事务内的命令依次执行而不被其他命令插入。</p></blockquote><p>Redis 事务的异常处理,首先需要先明确什么原因导致执行出错。1)语法错,一旦前面有错,后面不会执行;2)运行错,一旦有错,后续的命令会继续执行;</p><blockquote><p>Redis 的事务没有关系数据库事务提供的回滚(rollback)[1]功能。为此开发者必须在事务执行出错后自己收拾剩下的摊子(将数据库复原回事务执行前的状态等)。</p></blockquote><p>WATCH 命令,WATCH 命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。</p><h4 id="过期时间"><a href="#过期时间" class="headerlink" title="过期时间"></a>过期时间</h4><figure class="highlight bash"><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">EXPIRE key seconds <span class="comment"># 单位是秒,过期时间到了以后Redis自动删除key</span></span><br><span class="line">PEXPIRE key ms <span class="comment"># 单位毫秒</span></span><br><span class="line">TTL key <span class="comment"># 剩余到期时间,单位是秒</span></span><br></pre></td></tr></table></figure><p>排序:SORT 命令,BY 参数,GET 参数,STORE 参数</p><h4 id="消息通知,任务队列"><a href="#消息通知,任务队列" class="headerlink" title="消息通知,任务队列"></a>消息通知,任务队列</h4><p><strong>生产/消费者模式</strong>,分发消息以及任务队列的实现,可以借助 Redis。</p><p>使用 Redis 实现简单的任务队列,一边 LPUSH,一边 RPOP 即可。(BRPOP 命令)</p><p>优先级队列,BRPOP key [key …] timeout</p><p><strong>发布/订阅模式</strong></p><figure class="highlight bash"><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">PUBLISH channel message</span><br><span class="line">SUBSCRIBE channel</span><br></pre></td></tr></table></figure><p>存在规则订阅,PSUBSCRIBE 命令。</p><p>节省空间方法:1)精简键名和键值;2)内部编码优化</p><h3 id="脚本"><a href="#脚本" class="headerlink" title="脚本"></a>脚本</h3><p>Lua 语言(Open Rest,Nginx 也可以使用 lua 语言,有机会学习了解一下)</p><p><strong>这里不详细记录 Lua 语法,不过有一点思考,既然 Nginx 也可以使用 Lua,那么可以就有一种场景,Nginx 通过 lua 访问 Redis 读取数据,并且用 lua 渲染模板,达到页面直出,这样应该效率很高。</strong></p><h3 id="持久化"><a href="#持久化" class="headerlink" title="持久化"></a>持久化</h3><ul><li>RDB (通过快照完成,当达到某种约定条件后自动生成一份备份并存储在硬盘上),<strong>快照原理</strong></li><li>AOF (存储非临时数据,每执行一条都会追加存储在硬盘,有一些性能影响)默认关闭,通过 <code>appendonly yes</code> 开启</li></ul><p>允许同时开启 RDB 和 AOF 两种模式。</p><h3 id="集群"><a href="#集群" class="headerlink" title="集群"></a>集群</h3><p>Redis 支持集群,可以通过主从数据库来来规避单点数据库故障导致的问题。主数据库负责读写(读写分离也可以),当写操作导致数据变化时自动将数据同步给从库,从库只读,并只接受主库同步数据。</p><p>配置文件,通过 slaveof 主库地址 主库端口 来完成主从复制的配置。</p><blockquote><p>通过复制可以实现读写分离,以提高服务器负载能力。</p></blockquote><p>关键字记录,Redis 支持<strong>哨兵</strong>,一主多从,需要自动监控 Redis 运行情况,作用:1)监控主从数据库运行正常;2)主数据库故障自动将从数据库转换成主数据库;细节待补充一篇琢磨透彻一点的分析文。</p><p>Redis 3.0 支持集群一大特点,还在学习中,这里不做细描述。(作者自己还一知半解,需要点时间和资料消化消化)</p><h3 id="Redis-管理(偏-OPS-运维)"><a href="#Redis-管理(偏-OPS-运维)" class="headerlink" title="Redis 管理(偏 OPS 运维)"></a>Redis 管理(偏 OPS 运维)</h3><p>默认 Redis 允许所有连接,可以通过 bind 绑定某一地址,可以设置数据库密码,提升安全性。</p><p>将命令重命名,确保部分影响性能或整个数据库的命令,被自定义成一个新命令。</p><h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>个人拙见:</p><ol><li>在速读的过程中,有几个想法,数据层面凭我个人经验来讲,可以分为热数据和冷数据,那么,其实无论是 Redis 这样 NoSQL 还是与 MySQL 这样各有专长的关系型数据库,可以组合来实现对前台数据的持续服务,冷数据来源于关系型,热数据来源于冷数据,定时按照 <strong>一定策略</strong> 进行更新与同步,这样既可以保证数据存储,也可以保证读写的高效</li></ol>]]></content>
<summary type="html">
<!-- ## Redis 入门指南(第 2 版) 读书笔记 -->
<blockquote>
<p>读书笔记《Redis 入门指南》 李子骅. Redis 入门指南(第 2 版)(异步图书) (Chinese Edition) (Kindle Locations 627-632). 人民邮电出版社. Kindle Edition.</p>
</blockquote>
<p>前阵子部门团建要去外地,路上大巴车来回有将近 6、7 个小时的空闲时间,排除和身边人玩 Switch 的时间,大部分时间都用来速读一本纯技术类工具书,《Redis 入门指南(第 2 版)》,粗略自己摘录并且记录一些读书笔记,记录于此,以备自己方便查阅。内容可以较多但仅限个人记录一定有所疏漏,还是建议使用到 Redis 的开发者朋友,使用 Dash 或者其他查看 API 一类工具放手边随时查阅。</p>
</summary>
<category term="读书笔记" scheme="https://beanlee.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>基于 Storybook 5 打造 Style 组件库开发与文档站建设小结</title>
<link href="https://beanlee.github.io/posts/about-storybook-5/"/>
<id>https://beanlee.github.io/posts/about-storybook-5/</id>
<published>2020-07-08T10:11:14.000Z</published>
<updated>2020-07-09T09:15:34.000Z</updated>
<content type="html"><![CDATA[<!-- ## 基于 Storybook 5 打造 Style 组件库开发与文档站建设小结 --><h3 id="写在前头"><a href="#写在前头" class="headerlink" title="写在前头"></a>写在前头</h3><p>前阵子一直和组里的小伙伴共同“造轮子”,开发并维护了一套 PC 端 React UI 组件库,经过了一段时间的折腾,组件库从之前的 0.x、1.x 再到最近发布的 2.0.beta 的一个过程,这其中很多东西值得拿出来分享和讨论,有好的有失败的,今天就把组件库开发过程中的 DEMO 实时重现以及后期文档站的建设的技术选型以及实践简单做一个记录总结和大家一起做一个讨论。</p><p>调研和选型具体细节,后面找时间再梳理输出另外一篇文章,这里介绍在使用 <code>Storybook 5</code> 的过程中的一些问题点以及经验分享。</p><blockquote><p>截止文章编写时 Storybook 6 正在进行 rc 版,作者也经历过将之前项目的 <code>5.2.x</code> 升级到 <code>5.3.x</code> 过程的阵痛,网络上关于使用 Storybook 的问题文章也比较少,除了 Storybook 官网文档以外一些问题点记录,因此成文,感兴趣可以继续阅读。有关 UI 组件库的建设,这里不做深入讨论。(就是我们暂时先不讨论 WHY 的问题,本文只讨论 HOW。)</p></blockquote><a id="more"></a><p><img src="https://img12.360buyimg.com/imagetools/jfs/t1/118549/17/12079/184635/5f068a80E504ca4be/bf613763d4f28d85.jpg" alt></p><p>首先,个人总结 <code>Storybook</code> 几点优势,也是基于下面几个优势最终决定选型它的原因</p><ul><li><strong>开发环境</strong> 预览环境整体基于 Webpack 构建,开发环境<strong>接近实际生产环境</strong>;</li><li><strong>多面手</strong> 支持技术栈类型较多,可以支持 React、Vue 等等技术栈组件展示;</li><li><strong>代码即文档</strong> 无论是开发初期我们使用的 <code>*.stories.js</code>,还是后期因为要统一文档站说明和一些 UI,我们改用 <code>MDX</code> 重写了使用说明,不过大部分 API 列表还是基于 Props 的实现定义 Interface 中 JSDoc 自动渲染,非常方便;</li><li><strong>实时性</strong> 展示环境实时可交互,通过 knob 插件可以让使用者直接修改组件属性直接看到效果;</li></ul><h3 id="支持-TypeScript"><a href="#支持-TypeScript" class="headerlink" title="支持 TypeScript"></a>支持 TypeScript</h3><p><a href="https://storybook.js.org/docs/configurations/typescript-config/#typescript-configuration-presets" target="_blank" rel="noopener">官方文档</a>,支持 TypeScript 编写组件,文档中罗列了几个选项,出于使用习惯以及 Babel 与 Microsoft 的<a href="https://iamturns.com/typescript-babel/" target="_blank" rel="noopener">合作关系</a>,推荐使用 babel-loader 方式。(虽然它很慢,所以要尽量控制编译的范围。)</p><h4 id="Tips"><a href="#Tips" class="headerlink" title="Tips"></a>Tips</h4><p>当使用 babel 编译 TypeScript 的时候,存在两个问题</p><ul><li><strong>描述文件 d.ts</strong> 组件的描述文件如何生成,babel-loader 本身不具备这个能力。</li><li><strong>BUG</strong> 文档站生成过程中,默认 storybook 配置了 devtool,导致即使当前环境是 <code>production</code> 依然会编译生成 <code>sourcemap</code>,也就需要手动将 devtool 改为 false,这里应该给 storybook 提一个 issue。<a href="https://www.webpackjs.com/loaders/babel-loader/" target="_blank" rel="noopener">文档见这里</a>,有一句说明:<strong><em>注意:sourceMap 选项是被忽略的。当 webpack 配置了 sourceMap 时(通过 devtool 配置选项),将会自动生成 sourceMap。</em></strong></li></ul><p>关于声明文件的生成有两个方案:</p><ul><li>手动通过 <code>tsc</code> 本身具备的能力,结合 <code>--emitDeclarationOnly</code>参数和输出目录来只输出 <code>*.d.ts</code></li><li>后期我们重构组件库打包方式,组内的小伙伴使用 <code>gulp</code> 一样的道理通过单独编译声明文件并输出到与组件代码同级目录</li></ul><p>上面的方案核心就是单独编译声明文件即可,即便是微软官方提供的例子也是单独输出声明文件,<a href="https://github.com/Microsoft/TypeScript-Babel-Starter" target="_blank" rel="noopener">详见这里</a>,如果小伙们有其他好办法欢迎留言讨论。</p><h3 id="文档站构建优化"><a href="#文档站构建优化" class="headerlink" title="文档站构建优化"></a>文档站构建优化</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">build-storybook -c .storybook -o docs-static</span><br></pre></td></tr></table></figure><p>从打包命令中就可以看到,构建的配置支持 <code>.storybook</code> 文件夹下配置文件,默认读取 <code>main.js</code> 作为构建的补充,这里完全就是遵循 <code>webpack</code> 的配置,比如这里 <code>webpackFinal: async (config) => { ...}</code> 这段代码中拿到当前构建的全部 <code>config</code> 配置对象,那么既然拿到这个对象,那理论上就可以调整整个的构建过程。(当然,Storybook 构建确实使用了很多的 loader 和 plugin )。</p><p>静态站的打包和部署就和一个普通静态站部署没什么区别,外挂一个 HTTP 服务即可。由于 storybook 默认将每一个 stories (也就是组件)构建时进行了分包懒加载,访问每一个 stories 的时候内嵌的 iframe 展示时都只会请求当前组件的内容,首屏加载的内容也不是很多,经过简单的优化基本就可以实现很好的首屏加载。</p><p><img src="https://img13.360buyimg.com/imagetools/jfs/t1/146280/22/2482/165942/5f06df93E577f7aaa/096270fb8e6b9611.png" alt></p><h4 id="样式覆盖取代主题定制"><a href="#样式覆盖取代主题定制" class="headerlink" title="样式覆盖取代主题定制"></a>样式覆盖取代主题定制</h4><p>当然静态站上的主题样式覆盖,Storybook 还做得不够开放,虽然它开放了定制 theme 主题的方式,但是仍有一些细节处无法完全定制,我们的实现方式是通过手写 <code>css</code> 覆盖解决。</p><ul><li>默认样式</li></ul><p><img src="https://img12.360buyimg.com/imagetools/jfs/t1/146183/7/2462/133174/5f06cc0bEd688e80b/ca52174dd85344b7.png" alt="默认样式"></p><ul><li>覆盖改造后</li></ul><p><img src="https://img14.360buyimg.com/imagetools/jfs/t1/127797/14/6710/139347/5f06c43eE15a02e73/6e9c05bf893fc14a.png" alt="定制覆盖样式"></p><h3 id="组件菜单排序"><a href="#组件菜单排序" class="headerlink" title="组件菜单排序"></a>组件菜单排序</h3><p>默认情况下,左侧组件导航区的排序是按照字母排序规则,但当你的“业务方”想要一个所谓“有规划、基于设计原则”的排序进行展示就变的有一些难度,<code>Storybook</code> 预留了预览的 <code>API</code> 中就提供了一个可以自定义排序的方式,<a href="https://storybook.js.org/docs/configurations/options-parameter/#sorting-stories" target="_blank" rel="noopener">详见文档</a>。</p><p>那么,需要我们做什么呢,那就是手动去实现一个简单的排序算法,让组件 <code>stories</code> 按照期望的方式进行排序。</p><p>这里简单描述一下个人实现思路:</p><ol><li>数据准备阶段:将业务方期望的菜单分类好,我的做法是将期望菜单顺序拷贝至 Excel 中进行排序,之后复制回编辑器批量操作,制作一个简单 <code>map</code> 作为字典,作用单一就是根据 key 返回序号;</li><li>获取组件待排序属性:将每一个组件的 <code>stories</code> 中定义的 title 拿到用作排序时,作为 key 备用在字段中查询;</li><li>编写排序规则:这里就是一个简单的排序算法,两两比较即可;</li></ol><figure class="highlight javascript"><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="comment">// preview.js</span></span><br><span class="line">...</span><br><span class="line">storySort: <span class="function">(<span class="params">a, b</span>) =></span> {</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="comment"> * 将文档内容中所有内容去除中文、改小写,去空格,再去已经排序好的数据处理</span></span><br><span class="line"><span class="comment"> * ex. 按钮 Button => button</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 排序规则 0: 相同位置 -1: 前 1: 后</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">const</span> aSinpleName = a[<span class="number">1</span>].kind</span><br><span class="line"> .replace(<span class="regexp">/[\u4e00-\u9fa5\/]/g</span>, <span class="string">""</span>)</span><br><span class="line"> .toLowerCase()</span><br><span class="line"> .trim();</span><br><span class="line"> <span class="keyword">const</span> bSinpleName = b[<span class="number">1</span>].kind</span><br><span class="line"> .replace(<span class="regexp">/[\u4e00-\u9fa5\/]/g</span>, <span class="string">""</span>)</span><br><span class="line"> .toLowerCase()</span><br><span class="line"> .trim();</span><br><span class="line"> <span class="keyword">const</span> aSortNum = getSortNumByMenu(aSinpleName); <span class="comment">// 获取字典中排序编号</span></span><br><span class="line"> <span class="keyword">const</span> bSortNum = getSortNumByMenu(bSinpleName);</span><br><span class="line"> <span class="comment">// 自有组件,直接排在最后。PS. 非官方定义组件,二等公民无奈被放在最后</span></span><br><span class="line"> <span class="keyword">if</span> (!aSortNum && bSortNum) <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (aSortNum && !bSortNum) <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> <span class="keyword">if</span> (!aSortNum && !bSortNum) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">return</span> aSortNum > bSortNum ? <span class="number">1</span> : <span class="number">-1</span>;</span><br><span class="line">},</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3 id="addon-docs-插件"><a href="#addon-docs-插件" class="headerlink" title="addon-docs 插件"></a>addon-docs 插件</h3><p>Storybook 中文档即代码功能的实现还是很有意思的,通过强大的 <code>@storybook/addon-docs</code> 插件(在 <code>5.3.x</code> 版本开始逐步废弃 <code>@storybook/addon-info</code> 插件,基本不再维护)可以实现文档中众多元素的直接渲染,我们经常用的组件有 <code>import { Meta, Story, Preview, Props, Source, Title, Subtitle } from "@storybook/addon-docs/blocks";</code> 这些,其中着重介绍 <code>Preview</code> 和 <code>Props</code> 组件。</p><h4 id="Props-组件"><a href="#Props-组件" class="headerlink" title="Props 组件"></a>Props 组件</h4><p>可以结合 JSDoc 自动生成文档,如下图:</p><p><img src="https://img11.360buyimg.com/imagetools/jfs/t1/148461/28/2481/109551/5f06d748E203df1ea/4d45ba20ed7341cf.png" alt></p><p>感兴趣的同学可以抽时间研读了一下具体实现,<a href="https://github.com/storybookjs/storybook/tree/next/addons/docs" target="_blank" rel="noopener">源码地址</a></p><p>这里描述一下自己看源码总结的实现思路,借助 JSDoc 的 parser 读取每一个参数 component 中 props 的属性的注释内容,并且与当前属性建立对应关系后 render 一个显示的 table 就自动生成了下面的文档。</p><p><img src="https://img10.360buyimg.com/imagetools/jfs/t1/123725/9/6641/52952/5f06d741Eb6425a2f/8f809553d628c970.png" alt></p><h3 id="现存问题记录"><a href="#现存问题记录" class="headerlink" title="现存问题记录"></a>现存问题记录</h3><ul><li>在暗黑模式的适配上还存在问题</li><li>演示代码段,主题不可定制(查看源码知道虽然使用了 highlight 组件但是固定死了主题,并没有入口去修改)</li><li>国际化支持不全,目前没有需求可以暂时不考虑</li></ul><h3 id="走过的弯路"><a href="#走过的弯路" class="headerlink" title="走过的弯路"></a>走过的弯路</h3><p>插件选择失误,由于最一开始的选择错误,错误地选用 addon-info 已经被废弃的插件,导致后面升级后,在静态文档站需要定制化的需求出现时,原有 addon-info 出现的问题层出不穷,不得不切换至 addon-doc 插件,索性直接使用 mdx 对所有组件文档进行了一次重写,好在大部分代码和 DEMO 可以直接复用和自动生成。</p><h3 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h3><p>当然,我们目前并没有使用 <code>Storybook</code> 的全部功能,文章的最后也会列举调研期间收集到的非常牛团队开发的组件库和文档实例,那么我们可以学习到什么呢?</p><p>首先,我们所谓的重复造出来的轮子到底是有意义?答案虽然是肯定的,业界已经有很多成熟的案例,很多公司、部门、团队都可能有自己的库,早在我们在开始开发实现之初,<strong>但是我们为什么要做呢?这就是另外一个话题。</strong>我们不深入这个话题,仅从过程中讨论,我认为不仅仅只考虑组件的设计、易用性(API 等)、稳定性(质量)、组件的抽象、以及复用性等等这些问题,同时也应该作为组件使用者的角度来思考,从适用场景出发,当使用者决定是否使用某一组件时,组件的文档等等就是一个非常重要支持点,这里刚好 <code>Storybook</code> 帮我们解决了这其中的几个问题,看到这里感兴趣的小伙伴可以留言讨论,当你的团队或者新项目在选择 UI 库时基于哪些考虑?</p><p>再者,<strong>聚焦</strong>,Storybook 团队聚焦在组件的开发环境,单一功能强大通过拼接实现更多的功能,这种思想一定要多运用在实际情况中,但是也要注意区分场景,没必要成为“为了用而用”。</p><p>最后,UI 组件库应该是前端团队,最容易想到也是最难做好的一个 KPI 产物,这其中还有很多值得思考和讨论!</p><h3 id="基于-Storybook-优秀实例"><a href="#基于-Storybook-优秀实例" class="headerlink" title="基于 Storybook 优秀实例"></a>基于 Storybook 优秀实例</h3><ul><li><a href="https://storybook.js.org/docs/examples/" target="_blank" rel="noopener">Storybook Examples</a></li><li><a href="https://wix.com/pages/wix-style-react/" target="_blank" rel="noopener">Wix Style React</a></li></ul><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://storybookjs.org" target="_blank" rel="noopener">Storybook</a></li><li><a href="https://react-styleguidist.js.org" target="_blank" rel="noopener">React Styleguidist</a></li><li><a href="https://docusaurus.io" target="_blank" rel="noopener">Docusaurus</a></li><li><a href="https://www.docz.site" target="_blank" rel="noopener">docz</a></li></ul>]]></content>
<summary type="html">
<!-- ## 基于 Storybook 5 打造 Style 组件库开发与文档站建设小结 -->
<h3 id="写在前头"><a href="#写在前头" class="headerlink" title="写在前头"></a>写在前头</h3><p>前阵子一直和组里的小伙伴共同“造轮子”,开发并维护了一套 PC 端 React UI 组件库,经过了一段时间的折腾,组件库从之前的 0.x、1.x 再到最近发布的 2.0.beta 的一个过程,这其中很多东西值得拿出来分享和讨论,有好的有失败的,今天就把组件库开发过程中的 DEMO 实时重现以及后期文档站的建设的技术选型以及实践简单做一个记录总结和大家一起做一个讨论。</p>
<p>调研和选型具体细节,后面找时间再梳理输出另外一篇文章,这里介绍在使用 <code>Storybook 5</code> 的过程中的一些问题点以及经验分享。</p>
<blockquote>
<p>截止文章编写时 Storybook 6 正在进行 rc 版,作者也经历过将之前项目的 <code>5.2.x</code> 升级到 <code>5.3.x</code> 过程的阵痛,网络上关于使用 Storybook 的问题文章也比较少,除了 Storybook 官网文档以外一些问题点记录,因此成文,感兴趣可以继续阅读。有关 UI 组件库的建设,这里不做深入讨论。(就是我们暂时先不讨论 WHY 的问题,本文只讨论 HOW。)</p>
</blockquote>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="Webpack" scheme="https://beanlee.github.io/tags/Webpack/"/>
<category term="React" scheme="https://beanlee.github.io/tags/React/"/>
</entry>
<entry>
<title>ESLint 修改点整理</title>
<link href="https://beanlee.github.io/posts/sth-about-eslint/"/>
<id>https://beanlee.github.io/posts/sth-about-eslint/</id>
<published>2020-06-03T13:39:00.000Z</published>
<updated>2020-07-05T08:57:39.000Z</updated>
<content type="html"><![CDATA[<!-- ## ESLint 修改点整理 --><p>ESLint 为团队和项目带来的益处,这里不多说,本文主要是个人近期修改项目代码一些错误修改整理的内容。</p><blockquote><p>备注:尽量使用默认 Prettier 规则,根据实际情况略有调整。项目技术栈 React, Redux, Redux-Sagas, TypeScript。</p></blockquote><a id="more"></a><h3 id="Plugin-列表"><a href="#Plugin-列表" class="headerlink" title="Plugin 列表"></a>Plugin 列表</h3><ul><li><code>"plugin:react/recommended"</code></li><li><code>"plugin:@typescript-eslint/recommended"</code></li><li><code>"prettier/@typescript-eslint"</code></li><li><code>"plugin:prettier/recommended"</code></li></ul><h3 id="ERROR-List"><a href="#ERROR-List" class="headerlink" title="ERROR List"></a>ERROR List</h3><ul><li><p>[Error] eslint@typescript-eslint/ban-ts-comment</p><p>Do not use “// @ts-nocheck” because it alters compilation errors. <a href="https://github.com/typescript-eslint/typescript-eslint/blob/v3.0.1/packages/eslint-plugin/docs/rules/ban-ts-comment.md" target="_blank" rel="noopener">Desc link</a></p><p>TypeScript 提供 <code>@ts-expect-error @ts-ignore @ts-nocheck @ts-check</code> 指令注释方式,用来改变 tsc 编译时处理文件的方式,如果大量使用此类注释影响 TypeScript 的特性,既然使用 ts 就要拥抱它的特性,lint 中默认对 <code>no-check</code> 会按照 <code>error</code> 进行提示;可以根据实际情况调整规则,改为 <code>warn</code>。</p></li><li><p>[Error] @typescript-eslint/ban-types</p><p><a href="https://github.com/typescript-eslint/typescript-eslint/blob/v3.0.1/packages/eslint-plugin/docs/rules/ban-types.md" target="_blank" rel="noopener">Desc Link</a></p><p>补充提示:</p></li></ul><figure class="highlight md"><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">Don't use <span class="code">`{}`</span> as a type. <span class="code">`{}`</span> actually means "any non-nullish value".</span><br><span class="line"></span><br><span class="line"><span class="bullet">- </span>If you want a type meaning "any object", you probably want <span class="code">`Record<string, unknown>`</span> instead.</span><br><span class="line"><span class="bullet">- </span>If you want a type meaning "any value", you probably want <span class="code">`unknown`</span> instead.</span><br></pre></td></tr></table></figure><ul><li><p>[Error] react/jsx-no-target-blank</p><p><a href="https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules/jsx-no-target-blank.md" target="_blank" rel="noopener">Desc Link</a></p><p>出于安全考虑,React 中产生新打开页面的链接,需要增加 <code>rel='noreferrer'</code> 用来保护原站。具体说明详见:<a href="https://html.spec.whatwg.org/multipage/links.html#link-type-noopener" target="_blank" rel="noopener">Link</a></p></li></ul><h3 id="WARN-List"><a href="#WARN-List" class="headerlink" title="WARN List"></a>WARN List</h3><ul><li><p>[Warn] Missing return type on function.eslint@typescript-eslint/explicit-module-boundary-types</p><p><a href="https://github.com/typescript-eslint/typescript-eslint/blob/v3.0.1/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md" target="_blank" rel="noopener">desc link</a><br>说明:针对函数的定义,建议每一个函数都要显式的表明函数返回值。这在 <code>*.jsx, *.tsx</code> 文件中,React 生命周期函数都提示,可以使用 eslint overrides 规则,只针对 <code>*.js, *.ts</code> 生效。</p></li></ul><figure class="highlight"><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><br><span class="line"> <span class="attr">"rules"</span>: {</span><br><span class="line"> // disable the rule for all files</span><br><span class="line"> "@typescript-eslint/explicit-module-boundary-types": "off"</span><br><span class="line"> },</span><br><span class="line"> "overrides": [</span><br><span class="line"> {</span><br><span class="line"> // enable the rule specifically for TypeScript files</span><br><span class="line"> "files": ["*.js", "*.ts"],</span><br><span class="line"> "rules": {</span><br><span class="line"> "@typescript-eslint/explicit-module-boundary-types": "warn"</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><ul><li>[Warn] @typescript-eslint/no-unused-vars</li></ul><p>应该是最经常遇到的一个警告,定义了变量,下文没有使用。</p><p><strong>个人建议:非关键算法或逻辑代码,当你阅读时没用,就删掉吧,可以保留注释,因为即使你想保留这段代码,以备不时之需,但是真到需要用到代码你再次阅读的时候,实现思路以及上下文联系可能已经相差很远。</strong></p><h3 id="Prettier-细节"><a href="#Prettier-细节" class="headerlink" title="Prettier 细节"></a>Prettier 细节</h3><ul><li>关于默认逗号的变化 <a href="https://prettier.io/docs/en/options.html#trailing-commas" target="_blank" rel="noopener">link</a></li></ul><p><code>Prettier</code> 自动 v2.0.0 开始,将 <code>trailingComma</code> 默认配置由 <code>none</code> 改为 <code>es5</code>,在我看来是一种很好的方式。</p><p>举个例子,当 import 多个内容、一个对象需要增加属性、一个数组追加元素,如果默认已经追加了逗号,那么就可以直接追加,而不必要移动光标去前一行手动增加一个逗号,这样就增加了一些便利性,与此同时便于快速定位甚至避免由于一个逗号的引起的错误。</p><h4 id="TS2322-自定义属性"><a href="#TS2322-自定义属性" class="headerlink" title="TS2322 自定义属性"></a>TS2322 自定义属性</h4><figure class="highlight typescript"><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="keyword">import</span> { AriaAttributes, DOMAttributes } <span class="keyword">from</span> <span class="string">"react"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "react" {</span><br><span class="line"> <span class="keyword">interface</span> HTMLAttributes<T> <span class="keyword">extends</span> AriaAttributes, DOMAttributes<T> {</span><br><span class="line"> <span class="comment">// extends React's HTMLAttributes</span></span><br><span class="line"> custom?: <span class="built_in">string</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>参考:<a href="https://dev.to/lukethacoder/use-custom-html-attribute-s-in-typescript-2co" target="_blank" rel="noopener">https://dev.to/lukethacoder/use-custom-html-attribute-s-in-typescript-2co</a></p><h4 id="TS2679"><a href="#TS2679" class="headerlink" title="TS2679"></a>TS2679</h4><p>问题实例片段代码:</p><figure class="highlight ts"><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="function"><span class="keyword">function</span>* <span class="title">loadSimilar</span>(<span class="params">skuId: <span class="built_in">string</span></span>) </span>{</span><br><span class="line"> <span class="keyword">yield</span> put({</span><br><span class="line"> <span class="keyword">type</span>: ActionTypes.REQUEST_SIMILAR.PENDING,</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> { data } = <span class="keyword">yield</span> API.querySimilar(skuId);</span><br><span class="line"> <span class="keyword">yield</span> put({</span><br><span class="line"> <span class="keyword">type</span>: ActionTypes.REQUEST_SIMILAR.SUCCESS,</span><br><span class="line"> data,</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="keyword">yield</span> put({</span><br><span class="line"> <span class="keyword">type</span>: ActionTypes.REQUEST_SIMILAR.FAILURE,</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 class="function"><span class="keyword">function</span>* <span class="title">watchSimilar</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">yield</span> takeEvery(ActionTypes.LOAD_SIMILAR, loadSimilar);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>修改后代码:</p><figure class="highlight diff"><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="deletion">- function* loadSimilar(skuId: string) {</span></span><br><span class="line"><span class="addition">+ function* loadSimilar({ skuId }: { type: string; skuId: string }) {</span></span><br><span class="line"> yield put({</span><br><span class="line"> type: ActionTypes.REQUEST_SIMILAR.PENDING,</span><br><span class="line"> });</span><br><span class="line"> try {</span><br><span class="line"> const { data } = yield API.querySimilar(skuId);</span><br><span class="line"> yield put({</span><br><span class="line"> type: ActionTypes.REQUEST_SIMILAR.SUCCESS,</span><br><span class="line"> data,</span><br><span class="line"> });</span><br><span class="line"> } catch (error) {</span><br><span class="line"> yield put({</span><br><span class="line"> type: ActionTypes.REQUEST_SIMILAR.FAILURE,</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">function* watchSimilar() {</span><br><span class="line"> yield takeEvery(ActionTypes.LOAD_SIMILAR, loadSimilar);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>解答:Redux-Saga 中 takeEvery 第二个参数是一个 action,所以定义 loadSimilar 时候需要遵循 <code>TakeableChannel<unknown></code> ,定义 type。<br>参考:<a href="https://stackoverflow.com/a/60558041" target="_blank" rel="noopener">https://stackoverflow.com/a/60558041</a></p><p><strong>切记,保证一路传递参数变量匹配,否则会出现无法赋值问题,例如下面代码:</strong></p><figure class="highlight diff"><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="deletion">- function* cartOptCheckOne({ param }: { type: string; param: any }) {</span></span><br><span class="line"><span class="addition">+ function* cartOptCheckOne({</span></span><br><span class="line"><span class="addition">+ // type = ActionTypes.OPT_CARTCHECKONE,</span></span><br><span class="line"><span class="addition">+ RequestParam,</span></span><br><span class="line"><span class="addition">+ }: {</span></span><br><span class="line"><span class="addition">+ type: string;</span></span><br><span class="line"><span class="addition">+ RequestParam: any;</span></span><br><span class="line"><span class="addition">+ }) {</span></span><br></pre></td></tr></table></figure><p>外层触发 action 传参数代码如下:</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></pre></td><td class="code"><pre><span class="line">this.props.optCartCheckOne({</span><br><span class="line"> RequestParam: requestParam,</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>如果定义 cartOptCheckOne 使用 param 就会导致传递过程中因为变量名称不同无法解构,导致传参中断。</p><h2 id="ESLint-Config"><a href="#ESLint-Config" class="headerlink" title="ESLint Config"></a>ESLint Config</h2><p>参考 <a href="https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project" target="_blank" rel="noopener">https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project</a></p>]]></content>
<summary type="html">
<!-- ## ESLint 修改点整理 -->
<p>ESLint 为团队和项目带来的益处,这里不多说,本文主要是个人近期修改项目代码一些错误修改整理的内容。</p>
<blockquote>
<p>备注:尽量使用默认 Prettier 规则,根据实际情况略有调整。项目技术栈 React, Redux, Redux-Sagas, TypeScript。</p>
</blockquote>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="Front-End" scheme="https://beanlee.github.io/tags/Front-End/"/>
<category term="React" scheme="https://beanlee.github.io/tags/React/"/>
<category term="ESLint" scheme="https://beanlee.github.io/tags/ESLint/"/>
<category term="TypeScript" scheme="https://beanlee.github.io/tags/TypeScript/"/>
</entry>
<entry>
<title>【译】Google 出品 - Understand the JavaScript SEO basics</title>
<link href="https://beanlee.github.io/posts/understand-the-javascript-seo-basics/"/>
<id>https://beanlee.github.io/posts/understand-the-javascript-seo-basics/</id>
<published>2019-07-30T14:39:20.000Z</published>
<updated>2019-07-31T05:45:55.000Z</updated>
<content type="html"><![CDATA[<h2 id="理解-JavaSript-SEO-基础"><a href="#理解-JavaSript-SEO-基础" class="headerlink" title="理解 JavaSript SEO 基础"></a>理解 JavaSript SEO 基础</h2><p>原文 <a href="https://developers.google.com/search/docs/guides/javascript-seo-basics" target="_blank" rel="noopener">https://developers.google.com/search/docs/guides/javascript-seo-basics</a></p><blockquote><p>你是否怀疑过由于 JavaScript 的问题可能会影响你的网页或者某一部分内容出现在 Google Search 的结果? 你通过我们的<a href="https://developers.google.com/search/docs/guides/fix-search-javascript" target="_blank" rel="noopener">故障排除指南</a>来了解如何解决 JavaScript 相关的这些问题。</p></blockquote><p>JavaScript 是 WEB 平台中重要的一部分,因为它能够提供由普通 web 网站向功能更强大的应用平台转变的众多特性。开发 JavaScript 驱动的 web 应用可以帮助你吸引新用户,同时可以通过 Google 搜索到你的 web 应用提供出来的有效内容来留存现有用户。当 Google Search 通过 Chromium 引擎来执行 JavaScript 时,你可以做一些下面这些事情去进行优化改进你的 Web 应用。</p><a id="more"></a><p>下面这个指南描述了 Google Search 是如何处理 JavaScript 的,同时也在展示提升 Google Search SEO 结果的最佳实践。</p><p>一段油管视频<a href="https://youtu.be/nwGY-9lwTF4" target="_blank" rel="noopener">地址</a></p><h3 id="Googlebot-如何处理-JavaScript"><a href="#Googlebot-如何处理-JavaScript" class="headerlink" title="Googlebot 如何处理 JavaScript"></a>Googlebot 如何处理 JavaScript</h3><p>Googlebot 处理 JavaScript web 应用存在三个主要阶段:</p><ol><li>Crawling 爬取</li><li>Rendering 渲染呈现</li><li>Indexing 建立索引</li></ol><p>Googlebot 爬取,渲染并索引页面的步骤。</p><p><img src="https://img10.360buyimg.com/imagetools/jfs/t1/61335/23/6032/68515/5d403cf3Ee3a8a36b/753878cf026e9c6d.png" alt></p><blockquote><p>Googlebot 会将网页排入队列,以便爬取和渲染。当页面正在等待被爬取时和正在等待等待渲染时,并不容易被用户立即察觉到。</p></blockquote><p>当 Googlebot 从爬取队列中获取一个 URL 来创建一个 HTTP 请求时,它首先会检查你的网站是否允许被爬取。Googlebot 会阅读 <strong><code>robot.txt</code></strong> 文件。如果它标记了当前 URL 不允许爬取,Googlebot 会跳过对这个 URL 的 HTTP 请求。</p><p>Googlebot 会解析在 HTML 链接中的 <code>href</code> 属性转换出来的其他 URL 的响应,同时将这些 URL 放在待爬取的队列中。如果要阻止链接的查找,可以使用 <a href="https://support.google.com/webmasters/answer/96569" target="_blank" rel="noopener">nofollow mechanism</a>。</p><p>爬取 URL 时处理 HTML 响应在传统型网站和服务端渲染页面的网站很奏效,服务端渲染网站其中的 HTTP 响应中包含了全部的内容。一些 JavaScript 网站可能会用到 <a href="https://developers.google.com/web/fundamentals/architecture/app-shell" target="_blank" rel="noopener">app shell model</a>,这些网站在初始 HTML 内容中并不包含实际的内容,Googlebot 需要在看到十几页面内容之前执行 JavaScript。</p><p>Googlebot 将需要渲染的页面排入队列,除非网站的 <a href="https://developers.google.com/search/reference/robots_meta_tag" target="_blank" rel="noopener">robots meta tag or header</a> 信息告诉 Googlebot 不要对这个页面进行索引。这个页面可能在队列中短暂停留,但是它将花费更长的时间。一旦 Googlebot 的资源允许,headless Chromium 可以渲染这个页面并且执行 JavaScript。Googlebot 会再次解析渲染完成的 HTML 链接,并且按照队列查找的 URL 继续爬取。Googlebot 也会使用渲染完成的 HTML 对页面进行索引。</p><p>请记住,<a href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web" target="_blank" rel="noopener">服务端渲染或者预加载</a>仍然是很好的解决方案,因为这个方案可以使你的网站面向用户、爬虫时响应迅速,而不是所有 bots 可以运行 JavaScript。</p><h3 id="用唯一的-title-和-snippets-描述网页"><a href="#用唯一的-title-和-snippets-描述网页" class="headerlink" title="用唯一的 title 和 snippets 描述网页"></a>用唯一的 title 和 snippets 描述网页</h3><p>唯一的,可描述的标题以及有用的 meta 描述信息可以帮助用户快速地有目的性的定位最佳的搜索结果,我们在指南中解释什么是<a href="https://support.google.com/webmasters/answer/35624#page-titles" target="_blank" rel="noopener">好的 title 和 description</a>。</p><p>你也可以使用 JavaScript 设置或者修改 title 和 meta 描述信息。</p><blockquote><p>Google Search 可以基于用户的查询展示不同的标题和描述。当标题或者说明与页面内容相关性较低,或者我们在页面中发现与搜索结果更匹配的替代方法时,就会发生这种情况。有关标题和说明的代码片段的详细信息,请参与此页面。</p></blockquote><h3 id="编写向下兼容的代码"><a href="#编写向下兼容的代码" class="headerlink" title="编写向下兼容的代码"></a>编写向下兼容的代码</h3><p>浏览器提供了很多 API 并且 JavaScript 也是一个快速进化的编程语言。Googlebot 在支持哪些 API 和 JavaScript 功能方面有一些限制。为了确保你的代码可以兼容 Googlebot,请遵循我们的 <a href="https://developers.google.com/search/docs/guides/fix-search-javascript" target="_blank" rel="noopener">JavaScript 故障指南</a>。</p><blockquote><p>如果你发现你需要的 API 缺失,我们建议使用不同的<a href="https://web.dev/codelab-serve-modern-code" target="_blank" rel="noopener">服务和 polyfills</a>。如果一些浏览器特性没有被 polyfill 补丁,我们建议你多看 polyfill 文档,了解潜在的限制。</p></blockquote><h3 id="使用有意义的-HTTP-状态码"><a href="#使用有意义的-HTTP-状态码" class="headerlink" title="使用有意义的 HTTP 状态码"></a>使用有意义的 HTTP 状态码</h3><p>在爬取页面时,Googlebot 使用 HTTP 状态码识别页面的一些问题。</p><p>你可以使用有意义的状态码告诉 Googlebot 当 前页面是否需要被爬取或者索引,比如 <code>404</code> 代表页面已经失联,<code>401</code> 代表登录前的页面。你可以使用 HTTP 状态码告诉 Googlebot 这个页面被转移到一个新的 URL ,因此索引可以被及时更新。</p><p>下面是 HTTP 的状态码列表</p><table><thead><tr><th style="text-align:left">HTTP status</th><th style="text-align:left">When to use</th></tr></thead><tbody><tr><td style="text-align:left">301/302</td><td style="text-align:left">当前页面已经跳转到新 URL</td></tr><tr><td style="text-align:left">401/403</td><td style="text-align:left">当前页面由于权限问题不可访问</td></tr><tr><td style="text-align:left">404/410</td><td style="text-align:left">当前页面不存在</td></tr><tr><td style="text-align:left">5xx</td><td style="text-align:left">服务端异常</td></tr></tbody></table><h3 id="使用-meta-robot-tag"><a href="#使用-meta-robot-tag" class="headerlink" title="使用 meta robot tag"></a>使用 meta robot tag</h3><p>你可以通过 meta robots 标签标记来阻止 Googlebot 对页面标记索引页面。举个例子,在页面中增加下面一个标签:</p><figure class="highlight html"><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"><span class="comment"><!-- Googlebot won't index this page or follow links on this page --></span></span><br><span class="line"><span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"robots"</span> <span class="attr">content</span>=<span class="string">"noindex, nofollow"</span> /></span></span><br></pre></td></tr></table></figure><p>当 Googlebot 在 robots meta 标签中遇到 <code>noindex</code>,它就不会渲染或者索引这个页面。</p><blockquote><p>使用 JavaScript 修改或删除 <code>robots meta</code> 标签可能不会如期望似的生效。如果 meta 标签包含 <code>noindex</code> ,Googlebot 会跳过渲染和 JavaScript 执行。如果你想要使用 JavaScript 修改 robots meta 标签,需要设置 <code>noindex</code>。</p></blockquote><h3 id="处理图片和懒加载内容"><a href="#处理图片和懒加载内容" class="headerlink" title="处理图片和懒加载内容"></a>处理图片和懒加载内容</h3><p>图片加载在宽带和性能方面会带来很大的消耗。一个好的策略是使用延迟加载,仅在用户即将看到图像时加载图片。为了确保更好的实现延迟加载,请遵循我们的<a href="https://developers.google.com/search/docs/guides/lazy-loading" target="_blank" rel="noopener">延迟加载指南</a>。</p>]]></content>
<summary type="html">
<h2 id="理解-JavaSript-SEO-基础"><a href="#理解-JavaSript-SEO-基础" class="headerlink" title="理解 JavaSript SEO 基础"></a>理解 JavaSript SEO 基础</h2><p>原文 <a href="https://developers.google.com/search/docs/guides/javascript-seo-basics" target="_blank" rel="noopener">https://developers.google.com/search/docs/guides/javascript-seo-basics</a></p>
<blockquote>
<p>你是否怀疑过由于 JavaScript 的问题可能会影响你的网页或者某一部分内容出现在 Google Search 的结果? 你通过我们的<a href="https://developers.google.com/search/docs/guides/fix-search-javascript" target="_blank" rel="noopener">故障排除指南</a>来了解如何解决 JavaScript 相关的这些问题。</p>
</blockquote>
<p>JavaScript 是 WEB 平台中重要的一部分,因为它能够提供由普通 web 网站向功能更强大的应用平台转变的众多特性。开发 JavaScript 驱动的 web 应用可以帮助你吸引新用户,同时可以通过 Google 搜索到你的 web 应用提供出来的有效内容来留存现有用户。当 Google Search 通过 Chromium 引擎来执行 JavaScript 时,你可以做一些下面这些事情去进行优化改进你的 Web 应用。</p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="Front-End" scheme="https://beanlee.github.io/tags/Front-End/"/>
<category term="翻译" scheme="https://beanlee.github.io/tags/%E7%BF%BB%E8%AF%91/"/>
<category term="React" scheme="https://beanlee.github.io/tags/React/"/>
</entry>
<entry>
<title>全栈的另一个选择:Serverless 无服务框架浅谈</title>
<link href="https://beanlee.github.io/posts/learn-serverless-1-perf/"/>
<id>https://beanlee.github.io/posts/learn-serverless-1-perf/</id>
<published>2019-05-27T01:49:55.000Z</published>
<updated>2019-05-27T01:54:16.000Z</updated>
<content type="html"><![CDATA[<blockquote><p>近期在准备部门内部的一个分享,思考了一些主题,最终还是决定结合自身 Web 服务端开发经验,来分享自己调研 Serverless 无服务建构的相关内容。</p></blockquote><blockquote><p>以下全文是前期准备阶段调研和整理的一部分资料,梳理总结后,在制作 Slide 过程中书写逐字稿缩减而来的。分享结束后还会继续在这部分挖掘和实践,云计算中的 Serverless 是一个非常大的主题范畴,单凭这一篇文字是没办法细说全部,笔者也在路上,感兴趣的同学可以留言讨论,持续关注。</p></blockquote><a id="more"></a><h2 id="云计算热度不减"><a href="#云计算热度不减" class="headerlink" title="云计算热度不减"></a>云计算热度不减</h2><p>当今社会技术热点一直围绕区块链、AI、机器学习等领域,它们背后离不开云计算的支撑。云计算在经历多年的发展,从基础设施到应用场景拓展和落地方面,都已经取得了长足的进步,大到国家层面的云计算中心纷纷落地,小到商业上各大云服务商和互联网IT厂商纷纷推出各自的云产品。在众多云计算解决方案中,<strong>Serverless</strong> 逐渐崭露头角,受到了很多关注并且发展迅猛,如果你也像笔者一样感兴趣想了解一下的话,那么请继续阅读吧!</p><hr><p>提到云计算的发展,我们可以先来简单了解云计算应用架构在没有出现 Serverless 概念之前的演进过程。</p><h2 id="软件应用架构和云服务的演进"><a href="#软件应用架构和云服务的演进" class="headerlink" title="软件应用架构和云服务的演进"></a>软件应用架构和云服务的演进</h2><p>首先,先来通过一张网络流传很久的 Pizza as a Service 图来了解一下几个缩写,图源来自 <a href="https://m.oursky.com/saas-paas-and-iaas-explained-in-one-graphic-d56c3e6f4606" target="_blank" rel="noopener">Medium Blog</a></p><p><img src="https://img13.360buyimg.com/imagetools/jfs/t1/34216/3/10689/429660/5ce7f56eE738c9244/5acde7a32fe6bfb2.png" alt></p><ul><li><code>IDC</code>:Internet Data Center 自建互联网数据中心,通俗讲就是硬件机房,自行维护硬件资源、物理机器、网络环境、机房温控等等等等,在开发阶段完成后,还需要进行繁重且耗时很长的部署过程。对应的就是左侧第一个传统部署的方案。</li><li><code>IaaS</code>:Infrastructure as a Service 基础设施服务,租用公共数据中心的物理机器资源,节省了一部分机器的成本。</li><li><code>PaaS</code>:Platform as a service 平台即服务,软件厂商提供一个平台,使用者只需要接入这个平台,就可以使用服务。</li><li><code>SaaS</code>:Software as a service 软件即服务,面向普通用户,用户只需要购买和使用软件就可以享受这种服务,像 Oracle 和用友财务软件、日常使用的 ERP 系统、办公自动化系统的软件等等,笔者之前所在的公司就是从事这类软件的研发,有固定的人群使用软件解决他们的问题。</li></ul><p>从左到右的演进的过程,我们可以看到的是软件应用架构在这个过程中,一步一步地将环境、硬件、部署、运维与开发进行了分离。</p><p>近期 Pizza as a Service 更新 2.0,将近期火热的 <code>CaaS</code>,<code>FaaS</code> 页加入到其中,如下图:</p><p><img src="https://img10.360buyimg.com/imagetools/jfs/t1/34915/27/8489/185047/5ce7f648E7236189c/3231bbec723c20d4.jpg" alt></p><p><code>CaaS</code> 容器即服务,它是基于以 Docker 为主的容器技术,还有由 Google 开源的 Kubernetes(K8S)进行容器自动编排等技术兴起而形成的一个服务架构。它将整个应用拆分成独立的微小的部分,实现快速部署,提供细粒度的微服务。</p><p><code>FaaS</code> 就是 Serverless 中的其中一种方案,也是我们本文关注的重点。</p><hr><p>好了,讲了一些枯燥的概念,那么我们回到我们的目前的工作岗位和内容。</p><h2 id="受限"><a href="#受限" class="headerlink" title="受限"></a>受限</h2><p>作为一名前端工程师,大家日常业务开发的工作内容是什么?</p><p>切图还原业务功能视觉稿;用浏览器脚本去完成交互;调用数据接口、完成业务数据的展现或者操作;开发 UI 组件库;开发工程化共建工具;开发应用系统;参与服务端、客户端所谓<code>大前端</code>的融合;开发一款轻量的客户端游戏等等等等。</p><blockquote><p>这些或许都是作为一名前端工程师的工作内容。<br>笔者日常工作和平时了解到前端工程师大概是以上这些内容,如果有遗漏欢迎补充讨论。</p></blockquote><p>现在请大家<strong>换一个角度思考</strong>,如果我们没有所谓公司的既有平台、如果我们正处创业初期、如果我们是一名独立开发者、如果没有从开发到部署的完善的整套流程、没有 CDN、没有数据接口,我们要如何开发一个动态数据的网站或是一个独立的应用?</p><p>根据以往的经验,在开发业务功能的过程中对于依赖的服务、数据等,我们一般话语权不高,仅仅是服务的使用者,那么我们能成为服务的生产者吗?</p><p>有的朋友会说:“我可以用 NodeJS 写服务,自行维护 SQL、NoSQL 作为数据支撑,在容器构建运行,最终对外提供服务!” <strong>NodeJS</strong> 帮助我们这些善于写 JavaScript 代码的前端工程师,拓宽工作范围和内容,笔者所在的部门内部基于 NodeJS 开发系统和应用都是很好的例子,我们可以写越来越多的服务端逻辑,来直接提供服务。</p><blockquote><p>这里简单提一下 <strong>GragphQL</strong> 这类方案不在本次分享的范围内,它的出现使得数据更具有弹性,但笔者认为 Serverless 的出现让服务的产生更彻底更直接。</p></blockquote><h3 id="同样困惑"><a href="#同样困惑" class="headerlink" title="同样困惑"></a>同样困惑</h3><p>那么,回到文中最开始说的披萨服务模型,如果不借助云服务,就会遇到这样的困惑。要选什么样的容器作为载体,什么配置的硬件机器,几核 CPU、内存、网络带宽;如果选择要选择云服务商云主机,可能有的同学会考虑,去亚马逊、微软、Google Cloud、阿里云、腾讯云、新浪云、华为云、京东云等等这样云厂商这里“选购”各种让人眼花缭乱的“配置组合”的云主机,让人非常苦恼。</p><p>既要为应用规模大小、扩容需求、安装哪一种操作系统、配合哪一种数据库、未来如何组成集群、如何做负载均衡、如何打通开发到部署流程等等等等问题而操心。即便是现在有 Docker、K8S 这样的容器技术和自动编排来辅助,他们虽然可以帮助你做到应用秒级部署,快速重复部署,也依然麻烦,仍然需要付出高昂地运维技术的学习成本,即便你是一个全能达人,你也需要花费大量的时间和精力去解决这些环境和资源的问题,这就对想快速实现一个点子开发出原型或者开发一个小型应用的你来说,像是拦在路上的不得不跨越的一座山,拖慢来你和你团队的行进速度。</p><p><img src="https://img13.360buyimg.com/imagetools/jfs/t1/31123/30/14938/62110/5cbfd86aE4d298c2d/e4d749fcf8bebbe9.png" alt></p><h3 id="利用率低下"><a href="#利用率低下" class="headerlink" title="利用率低下"></a>利用率低下</h3><p>绝大部分应用在云主机或虚拟机上线后,其 CPU 利用率、内存使用率、网络带宽的利用率都非常低,因为你在选购过程中就要为未来新增的流量做好缓冲,你的服务一旦 7X24 的上线运行,你就要为这些冗余的运行时产生的费用自掏腰包。我们现如今公司内部的基于私有云架构开发和部署也是如此的问题,都是需要开发者为这些运维相关的技术债务来埋单,有的同学也会经常收到自动监控系统发出的效率低的提示邮件。</p><p>那么既然有这么多困难,我们前端工程师还有机会吗?</p><blockquote><p>希望总是会有的!</p></blockquote><hr><h2 id="转变"><a href="#转变" class="headerlink" title="转变"></a>转变</h2><p><strong>Serverless</strong> 架构的出现,为我们提供了一个窗口。目前还没有一个普遍公认的权威定义,最新的一个定义是这样描述的。</p><blockquote><p>“无服务器架构是基于互联网的系统,其中应用开发不使用常规的服务进程。相反,它们仅依赖于第三方服务,客户端逻辑与托管远程服务之间的调用过程的组合。” – Wiki</p></blockquote><p>这个概念在 2012 年由 <a href="https://iron.io/" target="_blank" rel="noopener">iron.io</a> 公司首先提出,而在 2014 年由 AWS 发布 <strong>Lambda</strong> 时正式商业化使用,目前 AWS 在云计算市场占有率高达 44%(2017 年数据)可以说是业界大佬,同时 Microsoft、Google、IBM 等国际大型 IT 公司都有对应的云产品;在国内,像京东云、阿里云、腾讯云、华为云也都提供了基础的功能,并且据说阿里内部正在力推适合的场景从其他框架向 Serverless 架构转变,从几个阿里前端的 BLOG 、讨论、以及业界大会分享主题来看,可以看到很多人在关注这种架构。</p><p><img src="https://img14.360buyimg.com/imagetools/jfs/t1/32322/39/14955/68163/5cbfd8ceE6dca5aba/5ec35ad91a639b58.png" alt></p><h2 id="Serverless"><a href="#Serverless" class="headerlink" title="Serverless"></a>Serverless</h2><p>支持 Serverless 架构的云厂商,允许使用者可以开发服务端逻辑,并将其放在无状态的云计算容器中,由事件来进行触发,其他完全交给云来管理。</p><p>Serverless 分为两种:</p><ul><li><code>BaaS Backend as a Service 后端即服务</code> 如文件存储、数据存储、推送服务、身份统一验证等,我们平时写 NodeJS 或者 Java 都是接触不到的,不是这次讨论的主角。</li><li><code>FaaS Functions as a Service 函数即服务</code> 服务商提供一个平台,提供给用户开发、运行和管理这些函数的功能,而无需搭建和维护基础框架。这是我们关注的<strong>重点</strong>。</li></ul><h3 id="FaaS"><a href="#FaaS" class="headerlink" title="FaaS"></a>FaaS</h3><p><code>FaaS</code> 是一种事件驱动的由消息触发的函数服务。<strong>函数 Function</strong>顾名思义,计算机编程领域上的函数,有着最基础的定义,就是调用函数传入 INPUT 得到输出 OUTPUT,函数内部对于使用者可以是黑盒的,这云计算中也就可以理解成为是一种函数的<strong>服务</strong>。云厂商一般都会集成很多同步或者异步的事件源,通过订阅这些事件,去达到条件触发、定时触发来运行某一个云函数的效果。<code>FaaS</code> 允许我们上传一个完整的函数代码片段,这个函数理论上提供单一的无状态的服务,当事件触发执行这个函数的时候,它会创建实例、启动并开始执行,完成服务后等待被销毁,不存在上下文信息和状态。换言之,就是如果函数不运行,这个实例就不存在,云厂商的计费方式也发生了调整,既然没有实例存在运行,造成损耗,那么就没有产生计费。</p><p>可以直接理解为:只有当你的函数执行的时候才会按照<strong>运行次数</strong>收费,如下图,我们关心的只是应用层中的函数部分!</p><p><img src="https://img10.360buyimg.com/imagetools/jfs/t1/35669/26/5049/41753/5cbfc82cE3dcdef5a/44cd5664334d3147.png" alt></p><p>事件触发,给大家举一个类似例子,有一款老牌网络应用:</p><p><strong>IFTTT</strong>:汇聚了世界各种有趣的 API ,通过触发一件、一件事来形成链式的调用完成有趣功能。比如,当你打一辆 Uber 回家的路上,当快到家附近的时候,它会自动触发家里的灯打开,空调开启;比如,你发布一个 Instagram Photo 会自动将图片存储到 Google Photo 或者同步到微博;诸如此类,都是当发生一个事件的时候才去触发下一个事件;FaaS 可以与 BaaS 通过事件订阅来做到联动。</p><h3 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h3><p><code>AWS Lambda</code> <a href="https://amazonaws-china.com/cn/lambda/?nc2=h_m1" target="_blank" rel="noopener">官网文档</a> 和 <code>Google Cloud Functions</code> <a href="https://cloud.google.com/functions/use-cases/" target="_blank" rel="noopener">官网文档</a>,举了几个的应用场景,其中包括物联网 IoT、独立应用 、游戏后台服务引擎、 数据报表(定时)等等。</p><p>貌似看上去离前端有些远,再举一个例子,2018年微信小程序提出的云开发,我个人理解也是对 Serverless 的一次尝试,让拥有开发小程序的工程师,不必依赖传统服务端,而利用云存储、云数据库、云函数来实现自给自足。</p><p>客户端开发,可以自行将数据、文件、信息同步到云端,而又不需要过于操心运维。</p><h4 id="DEMO-Express"><a href="#DEMO-Express" class="headerlink" title="DEMO Express"></a>DEMO Express</h4><p>接触新技术时,工程师们总是喜欢拿来直接上手实验,这里笔者使用 <a href="https://serverless.com/" target="_blank" rel="noopener">serverless.com</a> 提供的 example,做了一个用于 TODO 应用的 server 端,使用 AWS Lambda, API Gateway, DynamoBD, S3 等服务,整体代码只有 200 行,其中 5 个 Function 服务,拥有 CRUD + Query 的功能。</p><p><img src="https://img11.360buyimg.com/imagetools/jfs/t1/39852/25/2208/798984/5cbfc813Ef7ba456d/0841958f9f4ad1ba.png" alt></p><p><img src="https://img10.360buyimg.com/imagetools/jfs/t1/30962/38/15042/925646/5cbfc807E3572f826/931728f36c3a020e.png" alt></p><p><img src="https://img13.360buyimg.com/imagetools/jfs/t1/40122/31/2186/98044/5cbfc86cE71db4f20/d0f40086b6e6adb0.png" alt></p><p><img src="https://img14.360buyimg.com/imagetools/jfs/t1/37237/34/5506/283277/5cbfc863Efe309042/627cbcbbd720df24.png" alt></p><p>如此以来,就拥有服务端的 API 服务,客户端就调用这几个服务,一个简单的 GTD 应用的雏形就已经完成了!</p><h3 id="FaaS-优势"><a href="#FaaS-优势" class="headerlink" title="FaaS 优势"></a>FaaS 优势</h3><p>可以说 AWS Lambda 在市面上探索和发展的最久,用户量最大,<code>Lambda</code> 目前已经可以支持 Python、Java、Go、Ruby、.Net、NodeJS 和自建环境,这几个服务端语言都有自己擅长的场景和成熟的库,可以更方便地去助力完成数据计算、机器学习、图像处理等等工作。</p><ul><li>降低成本(开发、运营成本)</li><li>计费方式按价值付费,不再为 7x24 埋单(即使遇到网络攻击,也可以设定阈值做到合理防护,不至于收到天价账单)</li><li>灵活自动拓展,无需关心扩容缩容问题</li><li>开发人员更专注核心业务</li></ul><h3 id="看上去万能?"><a href="#看上去万能?" class="headerlink" title="看上去万能?"></a>看上去万能?</h3><blockquote><p>No silver bullet. - The Mythical Man-Month</p></blockquote><p>任何解决方案都不是万能的,一定是有它适合的场景,解决适合的问题而存在。</p><h3 id="缺陷"><a href="#缺陷" class="headerlink" title="缺陷"></a>缺陷</h3><ul><li>启动延迟、不适合长时间运行,支持语言版本更新不及时(AWS NodeJS 8.10)</li><li>没有上下文环境,离线调试困难(serverless offline)</li><li>完全依赖云,貌似是一条无法回头的路(是否一切都要部署到云端,这个一直处在争论中,本文也持中立态度,要分场景适用。)</li><li>函数之间调用,目前还是比较保守的,AWS 已经提供这样的方案,但仍然不想传统服务与服务之间的调用的易用,其中一个非常严重的亟待解决问题,就是为了避免函数之间调用异常从而出现的<code>死循环</code>(函数既要做小,但是如果想提供复杂功能又不得不做的臃肿,这是一个矛盾)</li></ul><hr><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Serverless 架构带来的价值和挑战是并存的,国内的 Serverless 发展也在初期阶段,它改变了我们开发模式,也改变软件系统的一部分设计,未来发展的好坏,我们无法控制,但是我们可以保持关注,适当尝试。</p><p><img src="https://img14.360buyimg.com/imagetools/jfs/t1/40014/2/2186/75810/5cbfc87cE68b55b7a/b38bd8b96f8d177f.jpg" alt></p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li><a href="https://aws.amazon.com/cn/serverless/" target="_blank" rel="noopener">https://aws.amazon.com/cn/serverless/</a></li><li><a href="https://aws.amazon.com/cn/blogs/china/iaas-faas-serverless/" target="_blank" rel="noopener">https://aws.amazon.com/cn/blogs/china/iaas-faas-serverless/</a></li><li><a href="https://jimmysong.io/posts/what-is-serverless/" target="_blank" rel="noopener">https://jimmysong.io/posts/what-is-serverless/</a></li><li><a href="https://serverless.ink/" target="_blank" rel="noopener">https://serverless.ink/</a></li><li><a href="https://serverless.com/" target="_blank" rel="noopener">https://serverless.com/</a></li><li><a href="https://docs.azure.cn/zh-cn/azure-functions/" target="_blank" rel="noopener">https://docs.azure.cn/zh-cn/azure-functions/</a></li><li><a href="https://firebase.google.com/docs/functions/use-cases" target="_blank" rel="noopener">https://firebase.google.com/docs/functions/use-cases</a></li><li><a href="http://jolestar.com/serverless-faas-current-status-and-future/" target="_blank" rel="noopener">http://jolestar.com/serverless-faas-current-status-and-future/</a></li><li><a href="https://www.infoq.cn/article/klfShH_dWU9ooU8idvYD" target="_blank" rel="noopener">https://www.infoq.cn/article/klfShH_dWU9ooU8idvYD</a></li><li><a href="https://juejin.im/post/5caf019ff265da039444987b" target="_blank" rel="noopener">https://juejin.im/post/5caf019ff265da039444987b</a></li><li><a href="https://juejin.im/post/5c85b8e45188257dfa07da5c" target="_blank" rel="noopener">https://juejin.im/post/5c85b8e45188257dfa07da5c</a></li><li><a href="https://github.com/dt-fe/weekly/blob/master/94.%E7%B2%BE%E8%AF%BB%E3%80%8AServerless%20%E7%BB%99%E5%89%8D%E7%AB%AF%E5%B8%A6%E6%9D%A5%E4%BA%86%E4%BB%80%E4%B9%88%E3%80%8B.md" target="_blank" rel="noopener">https://github.com/dt-fe/weekly/blob/master/serverless.md</a></li></ul>]]></content>
<summary type="html">
<blockquote>
<p>近期在准备部门内部的一个分享,思考了一些主题,最终还是决定结合自身 Web 服务端开发经验,来分享自己调研 Serverless 无服务建构的相关内容。</p>
</blockquote>
<blockquote>
<p>以下全文是前期准备阶段调研和整理的一部分资料,梳理总结后,在制作 Slide 过程中书写逐字稿缩减而来的。分享结束后还会继续在这部分挖掘和实践,云计算中的 Serverless 是一个非常大的主题范畴,单凭这一篇文字是没办法细说全部,笔者也在路上,感兴趣的同学可以留言讨论,持续关注。</p>
</blockquote>
</summary>
<category term="技术" scheme="https://beanlee.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
<category term="Serverless" scheme="https://beanlee.github.io/tags/Serverless/"/>
<category term="Server" scheme="https://beanlee.github.io/tags/Server/"/>
<category term="Cloud" scheme="https://beanlee.github.io/tags/Cloud/"/>
</entry>
<entry>
<title>【译】React 中 State, Store, Static, This 的几个问题</title>
<link href="https://beanlee.github.io/posts/react-state-this-static-store/"/>
<id>https://beanlee.github.io/posts/react-state-this-static-store/</id>
<published>2018-10-31T14:40:13.000Z</published>
<updated>2019-04-23T15:49:06.000Z</updated>
<content type="html"><![CDATA[<blockquote><p>笔者最近在整理前段时间接手的其他团队的 RN 项目代码,在梳理项目中旧代码过程中,对 React 中 State Store Static This 产生疑惑,借此翻译这篇文章解惑,也分享给各位。</p></blockquote><p>原文 <a href="https://medium.freecodecamp.org/where-do-i-belong-a-guide-to-saving-react-component-data-in-state-store-static-and-this-c49b335e2a00" target="_blank" rel="noopener">https://medium.freecodecamp.org/where-do-i-belong-a-guide-to-saving-react-component-data-in-state-store-static-and-this-c49b335e2a00</a></p><p>发表时间 2016-08</p><p>作者 <a href="https://medium.freecodecamp.org/@SamCorcos" target="_blank" rel="noopener">Sam Corcos</a></p><a id="more"></a><h3 id="Where-to-Hold-React-Component-Data-state-store-static-and-this"><a href="#Where-to-Hold-React-Component-Data-state-store-static-and-this" class="headerlink" title="Where to Hold React Component Data: state, store, static, and this"></a>Where to Hold React Component Data: state, store, static, and this</h3><blockquote><p>With the advent of React and Redux, a common question has emerged:</p><blockquote><p>What should I hold in the Redux store, and what should I save in local state?</p></blockquote></blockquote><p>在开发 React 和 Redux 项目时,经常会被问到一个问题?</p><blockquote><p>我应该把什么维护在 Redux Store 中?我该在 Local state 中保存什么?</p></blockquote><blockquote><p>But this question is actually too simplistic, because there are also two other ways you can store data for use in a component: static and this.<br>Let’s go over what each of these, and when you should use them.</p></blockquote><p>然而问题非常简单,因为在 component 中你可以使用两种其他的方式储存数据:static 和 this。</p><p>让我们一起来详细了解下如何使用。</p><h3 id="Local-state-组件的本地状态"><a href="#Local-state-组件的本地状态" class="headerlink" title="Local state 组件的本地状态"></a>Local state 组件的本地状态</h3><blockquote><p>When React was first introduced, we were presented with local <strong>state</strong>. The important thing to know about local <strong>state</strong> is that when a <strong>state</strong> value changes, it triggers a re-render.<br>This state can be passed down to children as <strong>props</strong>, which allows you to separate your components between smart data-components and dumb presentational-components if you chose.</p></blockquote><p>React 刚刚面世之初,我们就注意到 local <strong>state</strong>。每当 <strong>state</strong> 值发生变化,都会触发组件重新 render,因此了解 <strong>state</strong> 是非常重要的。</p><p>当前组件的 state 会被当做 <strong>props</strong> 传递到子组件中,这个 props 允许你在数据组件和描绘型组件之间做出区分。</p><p>下面一个简单的使用 local state 计数 App 例子:</p><figure class="highlight jsx"><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">import</span> React <span class="keyword">from</span> <span class="string">'react'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(props) {</span><br><span class="line"> <span class="keyword">super</span>(props)</span><br><span class="line"> <span class="keyword">this</span>.state = {</span><br><span class="line"> counter: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.addOne = <span class="keyword">this</span>.addOne.bind(<span class="keyword">this</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> addOne() {</span><br><span class="line"> <span class="keyword">this</span>.setState({</span><br><span class="line"> counter: <span class="keyword">this</span>.state.counter + <span class="number">1</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> <button</span><br><span class="line"> onClick={ <span class="keyword">this</span>.addOne }></span><br><span class="line"> Increment</span><br><span class="line"> <<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> { this.state.counter }</span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>Your data (the value of the counter) is stored within the App component, and can be passed down its children.</p></blockquote><p>App 组件中数据被储存其中,并可以向子组件进行传递。</p><h4 id="Use-cases"><a href="#Use-cases" class="headerlink" title="Use cases"></a>Use cases</h4><blockquote><p>Assuming your counter is important to your app, and is storing data that would be useful to other components, you would not want to use local state to keep this value.<br>The current best practice is to use local state to handle the state of your user interface (UI) state rather than data. For example, using a <a href="https://reactjs.org/docs/forms.html#controlled-components" target="_blank" rel="noopener">controlled component</a> to fill out a form is a perfectly valid use of local state.<br>Another example of UI data that you could store in local state would be the currently selected tab from a list of options.</p></blockquote><p>假设计数器 Couter 对于 App 很重要,并且它正在存储其他组件的重要数据,你不会希望使用 local state 来保存数据的。</p><p>目前最佳实践是使用 local state 来处理 UI 的状态,不是使用数据。比如,使用 <a href="https://reactjs.org/docs/forms.html#controlled-components" target="_blank" rel="noopener">Controlled Components</a> 去实现一个 form 表单时使用 local state 是非常合理的。</p><p>UI 数据的另外一个例子,可以在 local state 中存储备选 options 列表中已选中的选项。</p><blockquote><p>A good way to think about when to use local state is to consider whether the value you’re storing will be used by another component. If a value is specific to only a single component (or perhaps a single child of that component), then it’s safe to keep that value in local state.</p></blockquote><blockquote><p><strong>Takeaway</strong>: keep UI state and transitory data (such as form inputs) in local state.</p></blockquote><p>思考何时使用 local state 一个好方法,是考虑兼顾你正在存储的值是否会被另外一个组件使用到。如果这个值非常明确地只在单一组件(或单一子组件)中出现,那么将它保存在 local state 中就是非常安全的做法。</p><p><strong>Takeaway</strong>:可以将 UI 状态和临时数据(form 表单输入数据)保存在 local state。</p><h3 id="Redux-store"><a href="#Redux-store" class="headerlink" title="Redux store"></a>Redux store</h3><blockquote><p>Then after some time had elapsed and everyone started getting comfortable with the idea of unidirectional data flow, we got Redux.</p></blockquote><blockquote><p>With Redux, we get a global store. This store lives at the highest level of your app and passes data down to all children. You connect to the global store with the connect wrapper and a mapStateToProps function.</p></blockquote><p>随着时间流逝,大家都在习惯这种单向数据流的思想,随着出现了 Redux。</p><p>在 Redux 中,我们有一个全局的 store,它在 App 中处于最高层级,可以将数据传递到所有子组件中。你可以使用 connect 和 mapStateToProps 方法将全局 store 和你的组件链接起来已获取数据。</p><blockquote><p>At first, people put everything in the Redux store. Users, modals, forms, sockets… you name it.</p></blockquote><blockquote><p>Below is the same counter app, but using Redux. The important thing to note is that counter now comes from <strong>this.props.counter</strong> after being mapped from <strong>mapStateToProps</strong> in the <strong>connect</strong> function, which takes the counter value from the global store and maps it to the current component’s props.</p></blockquote><p>期初,人们把所有的东西都塞进 Redux store 中。</p><p>下面是刚刚那个计数器,区别是使用了 Redux。要点是计数器在通过 <strong>connect</strong> 方法 <strong>mapStateToProps</strong> 映射之后获取 <strong>this.props.counter</strong>,这个值是从全局 store 中获取到并映射到当前组件的 props 中的。</p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span></span><br><span class="line"><span class="keyword">import</span> { connect } <span class="keyword">from</span> <span class="string">'react-redux'</span></span><br><span class="line"><span class="keyword">import</span> Actions <span class="keyword">from</span> <span class="string">'./Actions.js'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(props) {</span><br><span class="line"> <span class="keyword">super</span>(props)</span><br><span class="line"> <span class="keyword">this</span>.addOne = <span class="keyword">this</span>.addOne.bind(<span class="keyword">this</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> addOne() {</span><br><span class="line"> <span class="keyword">this</span>.props.dispatch(Actions.addOne())</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> <button</span><br><span class="line"> onClick={ <span class="keyword">this</span>.addOne }></span><br><span class="line"> Increment</span><br><span class="line"> <<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> { this.props.counter }</span></span><br><span class="line"><span class="regexp"> </</span>div></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 class="keyword">const</span> mapStateToProps = <span class="function"><span class="params">store</span> =></span> {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> counter: store.counter</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> connect(mapStateToProps)(App)</span><br></pre></td></tr></table></figure><blockquote><p>Now when you click on the button, an action is dispatched and the global <strong>store</strong> is updated. The data is handled outside of our local component and is passed down.</p></blockquote><blockquote><p>It’s worth noting that when <strong>props</strong> are updated, it also triggers a re-render—just like when you update <strong>state</strong>.</p></blockquote><p>当你点击按钮,与此链接的 action 就会被触发,同时全局的 <strong>store</strong> 就会更新。这样我们本地组件外层的数据就被操作并传递下去。</p><p><strong>props</strong> 更新是没有副作用的,只有当你更新 <strong>state</strong> 时才会触发重新渲染。</p><h4 id="Use-cases-1"><a href="#Use-cases-1" class="headerlink" title="Use cases"></a>Use cases</h4><blockquote><p>The Redux <strong>store</strong> is great for keeping application state rather than UI state. A perfect example is a user’s login status. Many of your components will need access to this information, and as soon as the login status changes, all of those components (the ones that are rendered, at least) will need to be re-rendered with the updated information.</p></blockquote><blockquote><p>Redux is also useful for triggering events for which you need access on multiple components or across multiple routes. An example of this would be a login modal, which can be triggered by a multitude of buttons all across your app. Rather than conditionally rendering a modal in a dozen places, you can conditionally render it at the top-level of your app and use a Redux action to trigger it by changing a value in the <strong>store</strong>.</p></blockquote><blockquote><p><strong>Takeaway</strong>: keep data that you intend to share across components in <strong>store</strong>.</p></blockquote><p>Redux 中 <strong>store</strong> 应该维护应用的数据状态而不是 UI 的状态。用户登录数据状态就是另外一个例子。只要登录状态改变,项目中多数组件将需要访问这个登录信息,随着信息的更新,这些获取到信息更新的组件就都会重新 render。</p><p>Redux 通常也用于事件的触发,这些事件可能是横跨多个组件或者横跨多个路由。再以登录模块举例,可以在整个应用中来触发多个事件。你可以在应用的顶层,通过使用 Redux 对 <strong>store</strong> 进行修改,并使用 action 来触发条件渲染,而不是去不同地方去单独条件渲染。</p><p><strong>Takeaway</strong>: 可以尝试在跨组件共享数据时,将数据保存进 <strong>store</strong> 。</p><h3 id="this"><a href="#this" class="headerlink" title="this"></a>this</h3><blockquote><p>One of the least utilized features when working with React is <strong>this</strong>. People often forget that React is just JavaScript with ES2015 syntax. Anything you can do in JavaScript, you can also do in React.</p></blockquote><blockquote><p>The example below is a functional counter app, similar to the two examples above.</p></blockquote><p>在 React 众多特性中 <strong>this</strong> 就是其中之一。大家通常忘记一件事,就是 React 恰恰是使用 ES2015 语法的 Javascript 实现的。任何在 Javascript 可以完成的事情,同样可以放在 React 中完成。</p><p>下面就是一个函数型计数应用,与上面两个例子相似。</p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(props) {</span><br><span class="line"> <span class="keyword">super</span>(props)</span><br><span class="line"> <span class="keyword">this</span>.counter = <span class="number">0</span></span><br><span class="line"> <span class="keyword">this</span>.addOne = <span class="keyword">this</span>.addOne.bind(<span class="keyword">this</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> addOne() {</span><br><span class="line"> <span class="keyword">this</span>.counter += <span class="number">1</span></span><br><span class="line"> <span class="keyword">this</span>.forceUpdate()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> <button</span><br><span class="line"> onClick={ <span class="keyword">this</span>.addOne }></span><br><span class="line"> Increment</span><br><span class="line"> <<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> { this.counter }</span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>We’re storing the <strong>counter</strong> value in the component and using <a href="https://facebook.github.io/react/docs/component-api.html#forceupdate" target="_blank" rel="noopener">forceUpdate()</a> to re-render when the value changes. This is because changes to anything other than <strong>state</strong> and <strong>props</strong> does not trigger a re-render.</p></blockquote><blockquote><p>This is actually an example of how you should not use <strong>this</strong>. If you find yourself using <strong>forceUpdate()</strong>, you’re probably doing something wrong. For values for which a change should trigger a re-render, you should use local <strong>state</strong> or <strong>props</strong>/Redux <strong>store</strong>.</p></blockquote><p>我们在组件中存储 <strong>counter</strong> 的值,并且在这个值发生变化的时候使用 <a href="https://facebook.github.io/react/docs/component-api.html#forceupdate" target="_blank" rel="noopener">forceUpdate()</a> 去重新渲染。这是由于没有 <strong>state</strong> 和 <strong>props</strong> 发生变化,是不会触发组件的重新渲染。</p><p>这也是一个实际的非常糟糕地不使用 <strong>this</strong> 的例子。如果你发现你自己正在使用 <strong>forceUpdate()</strong> 你就有可能犯了一个错误。期望做到值改变而触发重新 render,就应该使用 local <strong>state</strong> 或者 <strong>props</strong> 或者是 Redux <strong>store</strong>。</p><h4 id="Use-cases-2"><a href="#Use-cases-2" class="headerlink" title="Use cases"></a>Use cases</h4><blockquote><p>The use case for <strong>this</strong> is to store values for which a change should not trigger a re-render. For example, sockets are a perfect thing to store on <strong>this</strong>.</p></blockquote><p>举个例子,<strong>this</strong> 所存储的变量发生改变,但并不希望触发重新 render。比如,sockets 就和适合存储在 <strong>this</strong> 上。</p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span></span><br><span class="line"><span class="keyword">import</span> { Socket } <span class="keyword">from</span> <span class="string">'phoenix'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> componentDidMount() {</span><br><span class="line"> <span class="keyword">this</span>.socket = <span class="keyword">new</span> Socket(<span class="string">'http://localhost:4000/socket'</span>)</span><br><span class="line"> <span class="keyword">this</span>.socket.connect()</span><br><span class="line"> <span class="keyword">this</span>.configureChannel(<span class="string">"lobby"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> componentWillUnmount() {</span><br><span class="line"> <span class="keyword">this</span>.socket.leave()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> configureChannel(room) {</span><br><span class="line"> <span class="keyword">this</span>.channel = <span class="keyword">this</span>.socket.channel(<span class="string">`rooms:<span class="subst">${room}</span>`</span>)</span><br><span class="line"> <span class="keyword">this</span>.channel.join()</span><br><span class="line"> .receive(<span class="string">"ok"</span>, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Succesfully joined the <span class="subst">${room}</span> chat room.`</span>)</span><br><span class="line"> })</span><br><span class="line"> .receive(<span class="string">"error"</span>, () => {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`Unable to join the <span class="subst">${room}</span> chat room.`</span>)</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> My App</span><br><span class="line"> <<span class="regexp">/div></span></span><br><span class="line"><span class="regexp"> )</span></span><br><span class="line"><span class="regexp"> }</span></span><br><span class="line"><span class="regexp">}</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">export default App</span></span><br></pre></td></tr></table></figure><blockquote><p>Also, many people don’t realize they’re already using <strong>this</strong> all the time in their function definitions. When you define <strong>render()</strong>, you’re really defining <strong>this.prototype.render = function()</strong>, but it’s hidden behind ES2015 class syntax.</p></blockquote><blockquote><p><strong>Takeaway</strong>: use <strong>this</strong> to store things that shouldn’t trigger a re-render.</p></blockquote><p>同时,很多人并没有意识到在定义 function 时已经一直在使用 <strong>this</strong>。当你定义 <strong>render()</strong> 时,实际上是在定义 <strong>this.prototype.render = function()</strong> ,但是它是 ES2015 类定义语法的隐藏式的写法。</p><p><strong>Takeaway</strong>: 使用 <strong>this</strong> 存储变量不应该触发重新 render。</p><h3 id="Static"><a href="#Static" class="headerlink" title="Static"></a>Static</h3><blockquote><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static" target="_blank" rel="noopener"><strong>Static</strong> methods</a> and properties are perhaps the least known aspect of ES2015 classes (calm down, yes, I know they aren’t really classes under the hood), mostly because they aren’t used all that frequently. But they actually aren’t especially complicated. If you’ve used <a href="https://facebook.github.io/react/docs/reusable-components.html#prop-validation" target="_blank" rel="noopener">PropTypes</a>, you’ve already defined a <strong>static</strong> property.</p></blockquote><blockquote><p>The following two code blocks are identical. The first is how most people define PropTypes. The second is how you can define them with <strong>static</strong>.</p></blockquote><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static" target="_blank" rel="noopener"><strong>Static</strong> methods</a> 和 properties 可能是在 ES2015 类中最不为人知的一部分,主要是因为他们不太常用。然而他们并不难懂复杂。如果你已经用过 <a href="https://facebook.github.io/react/docs/reusable-components.html#prop-validation" target="_blank" rel="noopener">PropTypes</a>, 那么你已经定义过 <strong>static</strong> 属性了。</p><p>下面这两段代码片段相同。第一段是大多数人如何定义 Proptypes。第二段是你可以使用 <strong>static</strong> 定义。</p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (<span class="xml"><span class="tag"><<span class="name">div</span>></span>{ this.props.title }<span class="tag"></<span class="name">div</span>></span></span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">App.propTypes = {</span><br><span class="line"> title: React.PropTypes.string.isRequired</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> propTypes {</span><br><span class="line"> title: React.PropTypes.string.isRequired</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (<span class="xml"><span class="tag"><<span class="name">div</span>></span>{ this.props.title }<span class="tag"></<span class="name">div</span>></span></span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>As you can see, <strong>static</strong> is not all that complicated. It’s just another way to assign a value to a class. The main difference between <strong>static</strong> and <strong>this</strong> is that you do not need to instantiate the class to access the value.</p></blockquote><p>你可以看到,<strong>static</strong> 并不复杂。他仅仅是给类增加值的另外一种方式。在 <strong>static</strong> 和 <strong>this</strong> 之间主要的差异主要是不需要实例化来访问这个值。</p><figure class="highlight jsx"><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="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>() {</span><br><span class="line"> <span class="keyword">super</span>()</span><br><span class="line"> <span class="keyword">this</span>.prototypeProperty = {</span><br><span class="line"> baz: <span class="string">"qux"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">static</span> staticProperty = {</span><br><span class="line"> foo: <span class="string">"bar"</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (<span class="xml"><span class="tag"><<span class="name">div</span>></span>My App<span class="tag"></<span class="name">div</span>></span></span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> proto = <span class="keyword">new</span> App();</span><br><span class="line"><span class="keyword">const</span> proto2 = proto.prototypeProperty <span class="comment">// => { baz: "qux" }</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> stat = App.staticProperty <span class="comment">// => { foo: "bar" }</span></span><br></pre></td></tr></table></figure><blockquote><p>In the example above, you can see that to get the <strong>staticProperty</strong> value, we could just call it straight from the class without instantiating it, but to get prototypeProperty, we had to instantiate it with <strong>new App()</strong>.</p></blockquote><p>在上面的例子中,你可以看到获取 <strong>staticProperty</strong> 静态属性的值,我们仅仅调用 class 中的静态方法即可,并不需要实例化,但是如果想要获取 prototypeProperty 属性,我们就不得不使用 <strong>new App()</strong> 实例化以后才可以访问到。</p><h4 id="Use-cases-3"><a href="#Use-cases-3" class="headerlink" title="Use cases"></a>Use cases</h4><blockquote><p>Static methods and properties are rarely used, and should be used for utility functions that all components of a particular type would need.</p></blockquote><blockquote><p><strong>PropTypes</strong> are an example of a utility function where you would attach to something like a Button component, since every button you render will need those same values.</p></blockquote><blockquote><p>Another use case is if you’re concerned about over-fetching data. If you’re using GraphQL or Falcor, you can specify which data you want back from your server. This way you don’t end up receiving a lot more data than you actually need for your component.</p></blockquote><p>静态方法和属性很少使用,应作为组件中的工具函数来使用。</p><p><strong>PropTypes</strong> 就是工具函数的例子,当创建按钮组件等其他类似组件时,尽管渲染出来的每一个按钮仍然需要相同的值。</p><p>另一个应用例子就是,如果你考虑从远端 fetch 数据。如果你正使用 GraphQL 或者 Falcor,那么你可以从服务端区分想要的数据。这种方式你不需要在获取组件多余的数据。</p><figure class="highlight jsx"><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="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> requiredData = [</span><br><span class="line"> <span class="string">"username"</span>,</span><br><span class="line"> <span class="string">"email"</span>,</span><br><span class="line"> <span class="string">"thumbnail_url"</span></span><br><span class="line"> ]</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span>(<span class="xml"><span class="tag"><<span class="name">div</span>></span><span class="tag"></<span class="name">div</span>></span></span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>So in the example component above, before requesting the data for a particular component, you could quickly get an array of required values for your query with <strong>App.requiredData</strong>. This allows you to make a request without over-fetching.</p></blockquote><blockquote><p><strong>Takeaway</strong>: you’re probably never going to use <strong>static</strong>.</p></blockquote><p>在上面实例组件中,在具体得组件获取数据之前,你可以快速地通过 <strong>App.requiredData</strong> 来获取这个数据的数组。这允许你不用 over-fetching 就可以完成请求。</p><p><strong>Takeaway</strong>: 你可能永远不会用到 <strong>static</strong>。</p><h3 id="That-other-option…"><a href="#That-other-option…" class="headerlink" title="That other option…"></a>That other option…</h3><blockquote><p>There is actually another option, which I intentionally left out of the title because you should use it sparingly: you can store things in a module-scoped <strong>variable</strong>.</p></blockquote><blockquote><p>There are specific situations in which it makes sense, but for the most part you just shouldn’t do it.</p></blockquote><p>还有另外一个选择,我故意省略了标题,因为你应该谨慎使用它:你可以储存在模块作用域变量中。</p><p>这是一种行之有效的特殊方法,但是最好不要使用。</p><figure class="highlight jsx"><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">import</span> React <span class="keyword">from</span> <span class="string">'react'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> counter = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>(props) {</span><br><span class="line"> <span class="keyword">super</span>(props)</span><br><span class="line"> <span class="keyword">this</span>.addOne = <span class="keyword">this</span>.addOne.bind(<span class="keyword">this</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> addOne() {</span><br><span class="line"> counter += <span class="number">1</span></span><br><span class="line"> <span class="keyword">this</span>.forceUpdate()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> <button</span><br><span class="line"> onClick={ <span class="keyword">this</span>.addOne }></span><br><span class="line"> Increment</span><br><span class="line"> <<span class="regexp">/button></span></span><br><span class="line"><span class="regexp"> { counter }</span></span><br><span class="line"><span class="regexp"> </</span>div></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 class="keyword">export</span> <span class="keyword">default</span> App</span><br></pre></td></tr></table></figure><blockquote><p>You can see this is almost the same as using <strong>this</strong>, except that we’re storing the value outside of our component, which could cause problems if you have more than one component per file. You might want to use this for setting default values if the values are not tied to your <strong>store</strong>, otherwise using a <strong>static</strong> for default props would be better.</p></blockquote><blockquote><p>If you need to share data across components and want to keep data available to everything the module, it’s almost always better to use your Redux <strong>store</strong>.</p></blockquote><blockquote><p><strong>Takeaway</strong>: don’t use module-scoped variables if you can avoid it.</p></blockquote><p>从上面的代码你可以看到与使用 <strong>this</strong> 很相似,尤其是我们将值存储在了组件之外,如果每个文件有多个组件极有可能产生问题。如果你没有将这个值绑定在 <strong>store</strong> 上,那你可能很希望使用 this 去设定初始默认值,否则使用 <strong>static</strong> 来设置默认 props 会更好。</p><p>如果你需要跨组件之间共享数据,并且希望将这些数据维持在每一个模块都有效,那么使用 Redux <strong>store</strong> 会更好。</p><p><strong>Takeaway</strong>: 如果可以避免的话,不要使用模块作用域的变量。</p>]]></content>
<summary type="html">
<blockquote>
<p>笔者最近在整理前段时间接手的其他团队的 RN 项目代码,在梳理项目中旧代码过程中,对 React 中 State Store Static This 产生疑惑,借此翻译这篇文章解惑,也分享给各位。</p>
</blockquote>
<p>原文 <a href="https://medium.freecodecamp.org/where-do-i-belong-a-guide-to-saving-react-component-data-in-state-store-static-and-this-c49b335e2a00" target="_blank" rel="noopener">https://medium.freecodecamp.org/where-do-i-belong-a-guide-to-saving-react-component-data-in-state-store-static-and-this-c49b335e2a00</a></p>
<p>发表时间 2016-08</p>
<p>作者 <a href="https://medium.freecodecamp.org/@SamCorcos" target="_blank" rel="noopener">Sam Corcos</a></p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="Front-End" scheme="https://beanlee.github.io/tags/Front-End/"/>
<category term="React" scheme="https://beanlee.github.io/tags/React/"/>
</entry>
<entry>
<title>代码的艺术 - 章淼讲座笔记</title>
<link href="https://beanlee.github.io/posts/Art-Of-Code/"/>
<id>https://beanlee.github.io/posts/Art-Of-Code/</id>
<published>2018-10-08T02:41:48.000Z</published>
<updated>2018-10-08T02:04:33.000Z</updated>
<content type="html"><![CDATA[<p>适合新手和正在努力进阶的高年级同学阅读 :)</p><a id="more"></a><h2 id="工程师的内功修炼"><a href="#工程师的内功修炼" class="headerlink" title="工程师的内功修炼"></a>工程师的内功修炼</h2><h3 id="章淼-简介"><a href="#章淼-简介" class="headerlink" title="章淼 简介"></a>章淼 简介</h3><p>清华大学计算机博士;百度云前端技术负责人;百度 <code>Golang</code> & <code>Python</code> 技术委员会成员;</p><h3 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h3><p>对比 Google 的工程师,国内的工程师写代码的占用时间显然过多了,而不太注重提前<strong>设计</strong>;Google 工程师们在开始实现某一模块或功能时,会事先在代码库中搜索是否已经有可重用的代码,并且代码库中的代码具有完整的注释和文档。</p><h4 id="提前设计的重要性"><a href="#提前设计的重要性" class="headerlink" title="提前设计的重要性"></a>提前设计的重要性</h4><p>尽可能地<strong>提前</strong>完成两个<strong>文档</strong></p><ul><li>需求分析文档</li><li>系统设计文档</li></ul><p>原因:在未启动实现细节代码之前构思设计时发现问题的修改,对比后期真正已经开始 Coding 的时候,对比发现问题进行修改,成本要低很多。文档一般只写主要逻辑,而代码涉及更多细节。</p><blockquote><p>笔者备注:但这不是绝对的,修改是正常的,不要惧怕修改,反复尝试积累经验。</p></blockquote><ul><li>需求分析文档:主要是在定义黑盒状态,描述外在,描述 WHAT 要做什么?</li><li>系统设计文档:主要实在定义白盒状态,描述内在,描述 HOW 怎么做?</li></ul><p>两者要有区分,不要<strong>混淆</strong>,也不要混在一起写!</p><ul><li>需求分析文档的误区<br>不要过早提前构想实现细节,我们的大脑会下意识地在我们构想如何实现时遇到的各种难题,而将原本的需求分析的思考挂起;举例:导弹 vs 炸弹,两者都有摧毁目标的能力,但是很明显导弹的价值更高,重要的是制导的功能,而不是爆炸本身。</li><li>系统设计文档的误区<br>主要要写定义系统的架构、模块、接口、数据、关键算法、设计思路等等得过程记录。</li></ul><h5 id="系统架构要写什么以及方法"><a href="#系统架构要写什么以及方法" class="headerlink" title="系统架构要写什么以及方法"></a>系统架构要写什么以及方法</h5><p>概念、模型、视图等等。</p><ul><li>静:系统静态的样子,功能模块如何划分等</li><li>动:系统运转起来,各模块联动起来的样子</li><li><p>细:不同角度,不同层次去描述</p></li><li><p>每一个组件(模块、函数)保证单一性,<strong>Single purpose. 只做一件事!</strong></p></li><li>轻耦合,低内聚(避免全局变量(多处操作难以控制))</li><li>当前系统设计所受到的约束(当前设计的瓶颈在哪?比如网络、吞吐量、占用 CPU 或文件位置资源等)</li><li>需求分析是系统设计的来源</li><li>模型和抽象的思维能力很重要(涉及概念:模型、数据结构、算法等等)</li></ul><h5 id="设计接口(Interface)要注意什么?"><a href="#设计接口(Interface)要注意什么?" class="headerlink" title="设计接口(Interface)要注意什么?"></a>设计接口(Interface)要注意什么?</h5><ul><li>接口定义系统外在的功能</li><li>接口定义当前系统与外部系统之间的关系</li></ul><p>接口 Interface 定义了系统对外的接口,往往比系统实现内部细节代码更重要,不要过于草率,因为一旦定义了接口,提供出去给调用方使用,想修改就太难了。所以设计接口有重要原则,站在使用者的角度考虑问题!</p><p>两点细节:</p><ul><li>向前兼容(尽量不要接口已升级,老接口全不能用,那就不是好的接口设计)</li><li>使用方便(让调用者可以一目了然知道接口的作用,简化传参,说明返回值等等)</li></ul><h4 id="如何写代码?"><a href="#如何写代码?" class="headerlink" title="如何写代码?"></a>如何写代码?</h4><p>代码是一种表达的方式。是写给人看的,要有<strong>编程规范</strong>。</p><p>拥有编程规范的理想状态:1. 看别人代码就像看自己代码一样易懂;2. 看代码主要看逻辑,不要过多注重细节;3. 代码尽可能地不要让人去多想。</p><blockquote><p>Don’t make me think!</p></blockquote><h5 id="Moudle-模块"><a href="#Moudle-模块" class="headerlink" title="Moudle 模块"></a>Moudle 模块</h5><p>紧内聚,低耦合。单一功能。反例,定义一个 <code>utils.py</code> 内部包含诸多方法,不易懂。</p><p>模块一般可以分为两类:</p><ul><li>数据类的模块(1. 主要完成对数据的封装; 2. 对外提供的数据接口)</li><li>过程类的模块(1. 不要包含数据,可以是调用数据类的模块或者调用其他过程类模块; 2. 具备操作性质的模块)</li></ul><p>模块的重要性:<strong>1. 降低维护成本; 2. 更好地复用</strong></p><h5 id="Class-类-和-Function-函数"><a href="#Class-类-和-Function-函数" class="headerlink" title="Class 类 和 Function 函数"></a>Class 类 和 Function 函数</h5><p>两者是不同的模型,各自有各自适用的范围。</p><p>推荐方法:<strong>和类的成员无关的函数,尽量独立出去单独一个函数,不建议作为类的成员函数。</strong></p><p>面向对象思想的讨论:多态和继承,需要谨慎适用,作为 <code>Python</code> 的工程师,不太推崇 <code>Java</code> 中继承和多态,因为系统是逐渐长起来的,并不是从一开始就是一个成熟的样子,所以很难凭空去设计一个继承的关系。</p><h5 id="模块的构成"><a href="#模块的构成" class="headerlink" title="模块的构成"></a>模块的构成</h5><p>文件头(注释)</p><ul><li>模块的说明,简要功能</li><li>修改历史(时间、修改人,修改的内容)</li><li>其他特殊细节的说明</li></ul><p>函数(重要性仅次于模块)</p><ul><li>描述功能</li><li>传入参数的描述(含义、类型、限制条件等等)</li><li>返回值得描述(有足够明确的语义说明)<ul><li>逻辑判断型 check isXXX</li><li>操作型(成功 or 失败)</li><li>数据获取型(状态 + 数据)</li></ul></li><li>异常如何处理(是抛出?还是内部catch?要明确)</li><li>明确单入口和单出口(多线程开发时尤为重要)</li></ul><p>函数要尽可能的规模小,足够短(BUG 往往出现在非常长的一个函数里)</p><p>代码块的<strong>分段</strong>也很重要,分段背后是划分和逻辑表达。</p><p>代码是一种表达能力的体现,也算是文科的范畴!<strong>注释不是补出来的!</strong></p><p>命名的重要性:要准确、易懂、可读性强,尽量做到 <code>望名生义</code>。</p><p>互联网时代的系统是<strong>运营</strong>出来的。</p><p>可检测性也是尤其重要的。(埋点、监控等等)</p><blockquote><p>没有数据的收集,等于系统没有上线。</p></blockquote><p>监控不单单只有传统意义上的内存、CPU、网络、崩溃率的监控,还应有线上真实数据监控,需要有足够多的状态记录。</p><p><strong>日志是很有限的一种监控手段</strong>,并且采集日志也是一种资源耗费。推荐的手段:可以使用埋点,对外提供接口,有单独的系统调用接口进行有针对性的采集。</p><h3 id="修身"><a href="#修身" class="headerlink" title="修身"></a>修身</h3><blockquote><p>好的程序员,与<strong>工作年限</strong>无关,与<strong>学历</strong>无关</p></blockquote><h4 id="学习-思考-实践"><a href="#学习-思考-实践" class="headerlink" title="学习-思考-实践"></a>学习-思考-实践</h4><p>学习:主观意愿地学习,途径也有很多,例如书籍、开源代码、社区。忌讳夜郎自大、井底之蛙。注重培养自己学习吸收的能力,多读多看但是数量不是最终目的。</p><blockquote><p>Stay Hungry, Stay Foolish. – Steve Jobs</p></blockquote><p>思考:学习需要经过思考,形成自己的思维。</p><p>实践:《卓有成效的时间管理者 - 德鲁克》推荐阅读</p><h4 id="知识-方法-精神"><a href="#知识-方法-精神" class="headerlink" title="知识-方法-精神"></a>知识-方法-精神</h4><blockquote><p>知识过时会非常快!</p></blockquote><p>方法:分析问题、解决问题的能力尤其重要(定义问题、识别问题、定义关键问题)</p><p>精神:决定型,要做就要坚持做</p><blockquote><p>前进的道路上不能永远都是鲜花和掌声。</p></blockquote><blockquote><p>基础乃治学之根本。</p></blockquote><p>数据结构、软件工程、逻辑思维能力、研究能力,需要5-8年时间磨炼。</p><h4 id="推荐书籍"><a href="#推荐书籍" class="headerlink" title="推荐书籍"></a>推荐书籍</h4><ul><li>人月神话</li><li>代码大全</li><li>201条准则</li><li>快速开发</li><li>系统结构</li><li>操作系统</li><li>网络基础</li></ul>]]></content>
<summary type="html">
<p>适合新手和正在努力进阶的高年级同学阅读 :)</p>
</summary>
<category term="总结" scheme="https://beanlee.github.io/tags/%E6%80%BB%E7%BB%93/"/>
</entry>
<entry>
<title>VSCode 插件开发急速入门</title>
<link href="https://beanlee.github.io/posts/vscode-extands-step-0/"/>
<id>https://beanlee.github.io/posts/vscode-extands-step-0/</id>
<published>2018-06-13T07:41:48.000Z</published>
<updated>2018-06-16T03:26:44.000Z</updated>
<content type="html"><![CDATA[<p>VSCode (全称:Vistual Studio Code) 由 Microsoft 出品,其启动快速、稳定性好、占用内存较小、插件越来越丰富、社区活跃等特点,目前是个人和团队中很多小伙伴的主力代码编辑器。</p><p>废话不多说,直奔主题,本文你将了解简单的 vscode 的插件开发入门,感兴趣的同学可以继续阅读。</p><a id="more"></a><blockquote><p>以下文中提到的“插件”均为 Vistual Studio Code 的插件,Vistual Studio Code 也简称 VSCode</p></blockquote><h2 id="Start"><a href="#Start" class="headerlink" title="Start"></a>Start</h2><h3 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h3><ul><li>nodejs <a href="https://nodejs.org/en/" target="_blank" rel="noopener">link</a> 建议使用 LTS 版本,截止发文 v8.11.3</li><li>npm <a href="https://www.npmjs.com/package/npm" target="_blank" rel="noopener">link</a> 建议最新版本,截止发文 v6.1.0</li><li>yeoman <a href="http://yeoman.io/" target="_blank" rel="noopener">link</a> <code>npm install -g yo</code></li><li>generator-code <a href="https://github.com/Microsoft/vscode-generator-code" target="_blank" rel="noopener">link</a> <code>npm install -g generator-code</code></li></ul><p>做好上面的环境准备,正式进入开发,下面我会以自己刚做好的一个查询北京地区空气质量为例子,继续介绍(安心してください,代码很少,很好入门)。</p><h3 id="数据准备"><a href="#数据准备" class="headerlink" title="数据准备"></a>数据准备</h3><p>下面我们补充一下我们的例子的功能,首先明确我们要获取城市的 AQI(空气质量)信息,网络上可以搜索到直接调用 API 或者 SDK,笔者目前使用的京东云的一个产品京东万象中的一个天气免费 API,<a href="https://wx.jdcloud.com/" target="_blank" rel="noopener">链接见此</a> 注册名实名认证后可以获得一个调用应用的 KEY 既可以获取天气数据了。更多其他方面的 API 读者可以自行探索。</p><p>笔者使用的 API 是 <a href="https://wx.jdcloud.com/market/datas/26/10610" target="_blank" rel="noopener">https://wx.jdcloud.com/market/datas/26/10610</a> 参数 city 支持 中英文名称、ID 和 IP 限制 5000次/天</p><h3 id="锦囊"><a href="#锦囊" class="headerlink" title="锦囊"></a>锦囊</h3><p>已有插件所在目录如下:</p><table><thead><tr><th>os</th><th>path</th></tr></thead><tbody><tr><td>windows</td><td>%USERPROFILE%\.vscode\extensions</td></tr><tr><td>macOS</td><td>~/.vscode/extensions</td></tr><tr><td>Linux</td><td>~/.vscode/extensions</td></tr></tbody></table><p>在这个目录下我们可以看到已经安装的所有插件的代码,即使插件是使用 typescript 编写 out 文件夹也可以看到编译后的 javascript 代码,感兴趣的同学可以直接看一看自己平时最常用的插件是如何实现的。</p><h3 id="开发"><a href="#开发" class="headerlink" title="开发"></a>开发</h3><p>执行下面代码:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yo code</span><br></pre></td></tr></table></figure><p>执行后会提示几个问题,第一个问题我们选择 <code>New Extension (JavaScript)</code>,其他正常填写即可。(可以使用 yo code 来创建插件、主题、代码片段、语言支持、键盘映射、插件包,本文我们只讨论插件,其他暂且放在一边。)</p><p><img src="https://img30.360buyimg.com/uba/jfs/t20848/320/1132999703/64245/a47741fc/5b20d75eN83303d8b.png" alt></p><p>填写完成后,会自动创建文件夹并帮助初始化完成文件,我们先看下目录结构。</p><figure class="highlight bash"><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">├── CHANGELOG.md <span class="comment">## 修改记录</span></span><br><span class="line">├── README.md <span class="comment">## 插件说明 README</span></span><br><span class="line">├── extension.js <span class="comment">## 入口</span></span><br><span class="line">├── jsconfig.json <span class="comment">## JavaScript 配置</span></span><br><span class="line">├── node_modules <span class="comment">## 依赖</span></span><br><span class="line">├── package-lock.json</span><br><span class="line">├── package.json</span><br><span class="line">├── <span class="built_in">test</span></span><br><span class="line">└── vsc-extension-quickstart.md</span><br></pre></td></tr></table></figure><p>熟悉的项目文件结构,直接查看 <code>vsc-extension-quickstart.md</code> 这个文档,文中提到 <code>package.json</code> 声明当前插件和命令的配置文件,用来注册命令等配置;<code>extension.js</code> 是主入口文件,插件的具体实现代码可以放在这里;</p><p>简单了解一下两个重要文件:</p><figure class="highlight"><figcaption><span>package.json</span></figcaption><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><br><span class="line"> "name": "city-aqi", // 插件名称</span><br><span class="line"> "displayName": "city-aqi", // 插件显示名称</span><br><span class="line"> "description": "Air Quaility Index of City", // 插件描述</span><br><span class="line"> "version": "0.0.1",</span><br><span class="line"> "publisher": "beanleecode", // 插件发布者</span><br><span class="line"> ...</span><br><span class="line"> "activationEvents": [ // 活动事件列表</span><br><span class="line"> <span class="string">"onCommand:extension.sayHello"</span></span><br><span class="line"> ],</span><br><span class="line"> "main": "./extension", // 入口文件</span><br><span class="line"> "contributes": {</span><br><span class="line"> "commands": [ // 对应命令列表</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"command"</span>: <span class="string">"extension.sayHello"</span>,</span><br><span class="line"> <span class="attr">"title"</span>: <span class="string">"Hello World"</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><figure class="highlight javascript"><figcaption><span>extension.js</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// The module 'vscode' contains the VS Code extensibility API</span></span><br><span class="line"><span class="comment">// Import the module and reference it with the alias vscode in your code below</span></span><br><span class="line"><span class="keyword">const</span> vscode = <span class="built_in">require</span>(<span class="string">'vscode'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// this method is called when your extension is activated</span></span><br><span class="line"><span class="comment">// your extension is activated the very first time the command is executed</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">activate</span>(<span class="params">context</span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Use the console to output diagnostic information (console.log) and errors (console.error)</span></span><br><span class="line"> <span class="comment">// This line of code will only be executed once when your extension is activated</span></span><br><span class="line"> <span class="comment">// 这里的代码将只会在插件激活时执行一次</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Congratulations, your extension "city-aqi" is now active!'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The command has been defined in the package.json file</span></span><br><span class="line"> <span class="comment">// 定义在 package.json 中的命令在这里定义</span></span><br><span class="line"> <span class="comment">// Now provide the implementation of the command with registerCommand</span></span><br><span class="line"> <span class="comment">// 提供 registerCommand 来注册实现代码</span></span><br><span class="line"> <span class="comment">// The commandId parameter must match the command field in package.json</span></span><br><span class="line"> <span class="comment">// commandId 参数必须与 package.json 匹配</span></span><br><span class="line"> <span class="keyword">let</span> disposable = vscode.commands.registerCommand(<span class="string">'extension.sayHello'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// The code you place here will be executed every time your command is executed</span></span><br><span class="line"> <span class="comment">// 这里的代码每次执行 这个命令 的时候都会被执行</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Display a message box to the user</span></span><br><span class="line"> <span class="comment">// 显示信息框</span></span><br><span class="line"> vscode.window.showInformationMessage(<span class="string">'Hello World!'</span>);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> context.subscriptions.push(disposable);</span><br><span class="line">}</span><br><span class="line">exports.activate = activate;</span><br><span class="line"></span><br><span class="line"><span class="comment">// this method is called when your extension is deactivated</span></span><br><span class="line"><span class="comment">// 插件被停用的时候被调用</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deactivate</span>(<span class="params"></span>) </span>{</span><br><span class="line">}</span><br><span class="line">exports.deactivate = deactivate;</span><br></pre></td></tr></table></figure><p>下面我们直接用 VSCode 打开这个项目的根目录,打开 <code>extension.js</code> 按下键盘 <code>F5</code> 或启动调试,接下来就可以看到,已经启动一个新的窗口,我们按下 <code>command + shift + p</code> 或 <code>command + p 再输入 ></code> 即可调用插件,在 package.json 中已经定义过 commands 我们直接输入 <code>Hello World</code> 直接启动插件,此时可以看到 Hello World 的通知框,就可以调试代码了。</p><p>OK,下面我们来进一步实现我们的功能,调取天气 API 数据,展示。</p><figure class="highlight javascript"><figcaption><span>extension.js</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> vscode = <span class="built_in">require</span>(<span class="string">'vscode'</span>);</span><br><span class="line"><span class="keyword">const</span> axios = <span class="built_in">require</span>(<span class="string">'axios'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">activate</span>(<span class="params">context</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> cityAqi = vscode.commands.registerCommand(<span class="string">'extension.sayCityAqi'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> options = {</span><br><span class="line"> ignoreFocusOut: <span class="literal">true</span>,</span><br><span class="line"> password: <span class="literal">false</span>,</span><br><span class="line"> prompt: <span class="string">"Please type your city (eg.beijing or 北京)"</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> vscode.window.showInputBox(options).then(<span class="function">(<span class="params">value</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (value === <span class="literal">undefined</span> || value.trim() === <span class="string">''</span>) {</span><br><span class="line"> vscode.window.showInformationMessage(<span class="string">'Please type your city.'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">const</span> cityName = value.trim();</span><br><span class="line"> <span class="comment">// appkey=xxxxxxxx 替换成事先申请好的 key</span></span><br><span class="line"> axios.get(<span class="string">`https://way.jd.com/he/freeweather?city=<span class="subst">${cityName}</span>&appkey=xxxxxxxx`</span>)</span><br><span class="line"> .then(<span class="function">(<span class="params">rep</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span>(rep.data.code !== <span class="string">'10000'</span>) {</span><br><span class="line"> vscode.window.showInformationMessage(<span class="string">'Sorry, Please try again.'</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">const</span> weatherData = rep.data.result.HeWeather5[<span class="number">0</span>]</span><br><span class="line"> <span class="keyword">if</span>(weatherData.status !== <span class="string">'ok'</span>) {</span><br><span class="line"> vscode.window.showInformationMessage(<span class="string">`Sorry, <span class="subst">${weatherData.status}</span>`</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> vscode.window.showInformationMessage(<span class="string">`<span class="subst">${weatherData.basic.city}</span> 's AQI => PM25: <span class="subst">${weatherData.aqi.city.pm25}</span>, PM10: <span class="subst">${weatherData.aqi.city.pm10}</span> <span class="subst">${weatherData.aqi.city.qlty}</span>`</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"> context.subscriptions.push(cityAqi);</span><br><span class="line">}</span><br><span class="line">exports.activate = activate;</span><br><span class="line"></span><br><span class="line"><span class="comment">// this method is called when your extension is deactivated</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deactivate</span>(<span class="params"></span>) </span>{</span><br><span class="line">}</span><br><span class="line">exports.deactivate = deactivate;</span><br></pre></td></tr></table></figure><p>Tadaaa,简单的功能就完成了!:P</p><p><img src="https://img10.360buyimg.com/uba/jfs/t21937/257/1227805321/92154/d99f4ec1/5b2231f3N66d25b0d.gif" alt></p><p>代码中用到了两个 vscode windows 的 api,<em>showInputBox</em> 和 <em>showInformationMessage</em> 更多 API 详见<a href="https://code.visualstudio.com/docs/extensionAPI/vscode-api" target="_blank" rel="noopener">链接</a></p><p><img src="https://img20.360buyimg.com/uba/jfs/t23665/363/2293286/276483/f4555655/5b224074Ne999cf65.png" alt></p><p>可以看到 API 包含了很多信息,所以可以做的事还可以很多 :)</p><h3 id="打包"><a href="#打包" class="headerlink" title="打包"></a>打包</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g vsce</span><br></pre></td></tr></table></figure><p>发布前我们可以把开发好的插件打包成 <code>.vsix</code> 文件,提供给身边的小伙伴试用一下。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vsce package</span><br></pre></td></tr></table></figure><p><code>city-aqi-0.0.1.vsix</code> 就打包完成了,在插件面板上选择 从 VSIX 安装 或命令行 <a href="https://code.visualstudio.com/docs/editor/extension-gallery#_install-from-a-vsix" target="_blank" rel="noopener">具体详见文档</a> 安装完成后重新加载 VSCode 就可以使用了。</p><blockquote><p>过程中可能会提示你先修改 README.md 文件才能打包,简单描述功能即可。</p></blockquote><h3 id="发布"><a href="#发布" class="headerlink" title="发布"></a>发布</h3><p>详细完善过 README.md 和 CHANGELOG.md 并且删除了一些冗余的文件,你就可以将插件发布到微软的插件市场 Extension Marketplace 供他人下载使用了。</p><p>在发布插件之前,首先,获取 token,登录 <a href="https://docs.microsoft.com/vsts/accounts/create-account-msa-or-work-student" target="_blank" rel="noopener">Visual Studio Team Services</a> 注册并登录账户,在 Security 中找到 Personal access tokens 新建一个 token。回到命令行:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vsce create-publisher xxxx</span><br></pre></td></tr></table></figure><p><img src="https://img14.360buyimg.com/uba/jfs/t21880/250/1201945807/26913/9c7e96d9/5b2240beNfc0ae8a5.png" alt></p><blockquote><p>完成后不需要重新 login ,因为 vsce 已经帮你记录当前的 publisher。</p></blockquote><figure class="highlight bash"><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">$ vsce publish</span><br><span class="line">Publishing [email protected]...</span><br><span class="line">Successfully published [email protected]!</span><br></pre></td></tr></table></figure><p>ALL Done 🎉 </p><p><img src="https://img14.360buyimg.com/uba/jfs/t23941/205/2220837/339379/fbe1b654/5b224350Nc2e1c2d4.png" alt></p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://code.visualstudio.com/docs/extensions/overview" target="_blank" rel="noopener">官网文档 https://code.visualstudio.com/docs/extensions/overview</a></li><li><a href="https://code.visualstudio.com/docs/extensionAPI/vscode-api" target="_blank" rel="noopener">vscode-api https://code.visualstudio.com/docs/extensionAPI/vscode-api</a></li></ul><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>笔者几年前一直是 <code>Sublime Text</code> 的使用者,修改 jsp、vm、sql等等文本文件,后来出现了 <code>Atom</code> 社区也相当活跃,用户同样很多,笔者也曾试用过一段时间。</p><p><strong>合适的才是最好的</strong> 近两年 VSCode 一直是我的主力开发编辑器,它也有缺点,但是它在不断精进和完善,大厂维护更新频率也很快。</p><p>很多大牛们喜爱的 <code>Vim</code> 和 <code>Emcas</code> 笔者没有用过,甚至连尝试都没有,可能是被过于灵活的配置<strong>吓到了</strong>…</p><p>PS,最近知道一个词<strong>宜家理论</strong>,套在上面几个工具上面,应该都适用,没有最好的只有最合适自己的。现在机器上配置的 vscode 的开发环境,笔者已经不得不使用同步插件来进行备份,以免更换机器时重新配置。</p><p>VSCode 有很多方便的插件,可以帮助我们提高开发效率,以后找机会写一个推荐列表分享出来。</p>]]></content>
<summary type="html">
<p>VSCode (全称:Vistual Studio Code) 由 Microsoft 出品,其启动快速、稳定性好、占用内存较小、插件越来越丰富、社区活跃等特点,目前是个人和团队中很多小伙伴的主力代码编辑器。</p>
<p>废话不多说,直奔主题,本文你将了解简单的 vscode 的插件开发入门,感兴趣的同学可以继续阅读。</p>
</summary>
<category term="Front-End" scheme="https://beanlee.github.io/tags/Front-End/"/>
<category term="Tool" scheme="https://beanlee.github.io/tags/Tool/"/>
<category term="NodeJS" scheme="https://beanlee.github.io/tags/NodeJS/"/>
</entry>
<entry>
<title>Webpack 4 实战 React 和 Vue 项目</title>
<link href="https://beanlee.github.io/posts/something-about-webpack-4/"/>
<id>https://beanlee.github.io/posts/something-about-webpack-4/</id>
<published>2018-06-11T09:48:23.000Z</published>
<updated>2018-06-20T01:31:47.000Z</updated>
<content type="html"><![CDATA[<p>最近一直在参与小组内“造轮子”(具体内容另寻机会再详说)在开发的过程中,了解并且学习到 Webpack v4 的一些内容,趁记忆还深,汇总成文。</p><p>鉴于 Webpack 作为关键在 Google 可以搜索到很多相关的文章,网上文章也是针对各自项目和某些情况的具体方案或者介绍说明,本文也不例外,只介绍分享过程中积累的 <strong>4.0</strong> 版本的个人实战经验。</p><blockquote><p>导读:本文你将 Get 到使用 Webpack 4 从零开始分别搭建 React 16 和 Vue 2 项目,同时还有基于 Webpack 4 的一些开发和生产环境配置经验,感兴趣同学可以继续阅读。</p></blockquote><p>PS. 前半部分较为基础,有一定经验的同学可以直接跳过阅读<a href="#实战内容经验积累">后半部分实战内容</a>。</p><a id="more"></a><p>以前也翻译过两篇关于 Webpack 的文章,感兴趣的同学可以点击下面链接查看:<strong>!!! 强烈推荐 !!!</strong></p><ul><li><a href="/2018/05/02/blog-translate-web-performance-optimization-with-webpack-from-google-webpack4/">【译】Google 出品 - 利用 webpack 做 web 性能优化</a></li><li><a href="/2018/04/18/blog-translate-webpack-4-mode-and-optimization/">【译】Webpack 4 mode and optimization</a></li></ul><!-- TOC --><ul><li><a href="#Webpack-4-从“零”开始">Webpack 4 从“零”开始</a></li><li><a href="#Webpack-4-x-amp-React-16-x">Webpack 4.x & React 16.x</a><ul><li><a href="#React-项目初始化">React 项目初始化</a></li><li><a href="#进入开发阶段">进入开发阶段</a></li></ul></li><li><a href="#Webpack-4-x-amp-Vue-2-x">Webpack 4.x & Vue 2.x</a><ul><li><a href="#Vue-项目初始化">Vue 项目初始化</a></li></ul></li><li><a href="#实战内容经验积累">实战内容经验积累</a><ul><li><a href="#提取公共依赖">提取公共依赖</a></li><li><a href="#文件压缩">文件压缩</a></li><li><a href="#proxy-接口代理">proxy 接口代理</a></li><li><a href="#支持多入口">支持多入口</a></li><li><a href="#预编译-sass、引入-postcss、处理-css-压缩-和-文件分离">预编译 sass、引入 postcss、处理 css 压缩 和 文件分离</a></li><li><a href="#支持-CDN-路径替换">支持 CDN 路径替换</a></li><li><a href="#生产环境的-source-map">生产环境的 source-map</a></li><li><a href="#处理图片资源">处理图片资源</a></li><li><a href="#其他插件">其他插件</a></li></ul></li><li><a href="#小结">小结</a></li></ul><!-- /TOC --><p><em>正文开始</em></p><h2 id="Webpack-4-从“零”开始"><a href="#Webpack-4-从“零”开始" class="headerlink" title="Webpack 4 从“零”开始"></a>Webpack 4 从“零”开始</h2><p>相信提到 Webpack 无论是作为前端工程师,还是 Web 开发者都不会太陌生,它从诞生伊始就收到社区的追捧和大量的生产实践,大量的项目代码构建工具开始选择他作为主力构建工具,究竟它是什么样工具,它的官网是这样描述的:</p><blockquote><p>At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.<br>其核心就是现代 Javascript 应用的静态模块构建器。当在应用中运行 Webpack 的时候,它就会在内部构建依赖图来映射项目中的每一个所需要的模块,并生成一个或多个 bundle 文件。</p></blockquote><p>也就是说 Webpack 可以分析项目中模块之间的<strong>依赖</strong>,并将最终的结果打包成 bundle 文件,开发者只要在开发过程中做到正确的引用和正常的代码开发即可,打包的事情统统交给 Webpack 即可。</p><p>在 Webpack 逐渐进化的过程中,或多或少存在一些缺点被社区人们所诟病,比如配置繁琐、构建时间较长且占用 CPU 高、文档不完整等等问题,并且衍生出一些替代品,但 Webpack 团队和开源社区的持续不断的贡献,它在不断完善和修正,如今已经进入 <strong>v4.x.x 的时代</strong>。帮助开发者减少工作量是 4.0 的一个任务,从这个版本开始,Webpack <strong>几乎</strong> 可以做到”零“配置或少配置地来构建项目生成 Bundle 文件,下面我们就先来看一下 <code>Webpack 4</code> 的“零”。</p><p>首先,我们来创建一个 <code>demo</code> 文件夹,做一些简单的初始化信息,并本地安装 <code>webpack</code>,此时项目中没有 webpack 配置文件。</p><figure class="highlight bash"><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">mkdir webpack-4-demo</span><br><span class="line"><span class="built_in">cd</span> webpack-4-demo</span><br><span class="line">yarn init -y</span><br><span class="line">yarn add webpack --dev</span><br></pre></td></tr></table></figure><p>修改 <code>package.json</code> 文件:</p><figure class="highlight"><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">"scripts": {</span><br><span class="line"> "build": "webpack"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn build</span><br></pre></td></tr></table></figure><blockquote><p>过程中会提示是否安装 <code>webpack-cli</code> 直接敲 <code>yes</code> 即可。</p></blockquote><p>此时控制台执行结果会有如下报错:</p><figure class="highlight bash"><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">ERROR <span class="keyword">in</span> Entry module not found: Error: Can<span class="string">'t resolve '</span>./src<span class="string">' in '</span>/Users/xxxx/webpack-4-demo<span class="string">'</span></span><br><span class="line"><span class="string">npm ERR! code ELIFECYCLE</span></span><br><span class="line"><span class="string">npm ERR! errno 2</span></span><br><span class="line"><span class="string">npm ERR! [email protected] build: `webpack`</span></span><br><span class="line"><span class="string">npm ERR! Exit status 2</span></span><br></pre></td></tr></table></figure><blockquote><p>注意:我们目前没有写任何配置文件,但 Webpack 仍会提示没有找到 <code>./src</code> 目录下 module。自 v4.0 开始已经 Webpack 可以自动在不配置 entry 的情况下自动检索项目文件夹中 src 目录下的 js 文件作为入口文件进行编译了。</p></blockquote><p>接下来我们按照错误提示,在目录下创建 <code>src</code> 文件夹,并且新建一个文件 <code>index.js</code> 并且输入内容 <code>console.log('hello webpack 4')</code>,再次运行 <code>yarn build</code>。</p><p>这时可以看到编译成功,项目目录下多出一个 <code>dist</code> 文件夹,我们事先也并没有配置 <code>output</code> 输出指向,<code>Webpack</code> 默认将 bundle 好的内容,放在了 dist 文件夹内。</p><p>在执行成功的过程中,有一处警告提示:</p><figure class="highlight bash"><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">WARNING <span class="keyword">in</span> configuration</span><br><span class="line">The <span class="string">'mode'</span> option has not been <span class="built_in">set</span>, webpack will fallback to <span class="string">'production'</span> <span class="keyword">for</span> this value. Set <span class="string">'mode'</span> option to <span class="string">'development'</span> or <span class="string">'production'</span> to <span class="built_in">enable</span> defaults <span class="keyword">for</span> each environment.</span><br><span class="line">You can also <span class="built_in">set</span> it to <span class="string">'none'</span> to <span class="built_in">disable</span> any default behavior. Learn more: https://webpack.js.org/concepts/mode/</span><br></pre></td></tr></table></figure><p>回过头查看刚刚编译好的 <code>main.js</code> 文件,该文件已经直接被压缩好的,且可在生产模式下运行。</p><p>警告中提到的<code>mode</code> 就是 Webpack 4 新增的一个配置,具体内容可以见下面链接,以及笔者之前的翻译文章。</p><ul><li><a href="https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a" target="_blank" rel="noopener">【原文】Webpack 4 mode and optimization</a></li><li><a href="/2018/04/18/blog-translate-webpack-4-mode-and-optimization/">【译】Webpack 4 mode and optimization</a></li></ul><p>当然,也可以通过 <code>--mode</code> 选项来手动选择 bundle 的模式,比如 <code>webpack --mode development</code>。</p><p>自此,如果你的项目 src 目录下的内容需要 Webpack 帮你编译,输出在 dist 目录,Webpack 几乎零配置就可以直接“胜任”了。</p><h2 id="Webpack-4-x-amp-React-16-x"><a href="#Webpack-4-x-amp-React-16-x" class="headerlink" title="Webpack 4.x & React 16.x"></a>Webpack 4.x & React 16.x</h2><p>上一节简单介绍了 V4.x <strong>“零”</strong>配置的基础应用。当然,实际工作中我们的项目都会比较复杂,上面的内容远不能满足我们的需求,下面我们就以一个 <code>React 16 & Webpack 4</code> DEMO 项目为例,还原从零开始搭建基于 <code>Webpack</code> 打包编译项目的整个过程。</p><blockquote><p>Facebook 官方推出的 <code>create-react-app</code> 工具已经非常好用,但仍然需要做一些修改才可以满足实际项目上线的需求,同时我们仍希望有更多所谓<strong>个性化</strong>设置来支持项目,且截至到今天 <code>cra</code> 使用 webpack 3.8 与我们本文介绍 webpack 4 有出入,所以下面内容不再提及,对 <code>cra</code> 感兴趣的同学也可以自行搜索查看了解。</p></blockquote><h3 id="React-项目初始化"><a href="#React-项目初始化" class="headerlink" title="React 项目初始化"></a>React 项目初始化</h3><p>首先,重复上面介绍的步骤:(创建目录、安装 react、安装 webpack、安装 babel)</p><figure class="highlight bash"><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">mkdir react-webpack-demo</span><br><span class="line"><span class="built_in">cd</span> react-webpack-demo</span><br><span class="line"><span class="comment">## 初始化 package.json</span></span><br><span class="line">yarn init -y</span><br><span class="line"><span class="comment">## 安装 react</span></span><br><span class="line">yarn add react react-dom</span><br><span class="line"><span class="comment">## 安装 webpack</span></span><br><span class="line">yarn add webpack webpack-cli --dev</span><br><span class="line"><span class="comment">## 安装 babel</span></span><br><span class="line">yarn add @babel/core @babel/preset-env --dev</span><br><span class="line"><span class="comment">## 安装 babel-react</span></span><br><span class="line">yarn add @babel/preset-react <span class="string">"babel-loader@^8.0.0-beta"</span> --dev</span><br></pre></td></tr></table></figure><blockquote><p>注意:这里使用 babel 转义,此处既可以在项目根目录下创建 <code>.babelrc</code> 文件,也可以稍后在 webpack.config.js 中配置,这里我们选择在后者统一配置。</p></blockquote><p>现在我们新建一个配置文件,<code>webpack.config.js</code> 代码:</p><figure class="highlight javascript"><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="built_in">module</span>.exports = {</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">/\.jsx?$/</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"> options: { <span class="comment">// babel 转义的配置选项</span></span><br><span class="line"> babelrc: <span class="literal">false</span>,</span><br><span class="line"> presets: [</span><br><span class="line"> <span class="built_in">require</span>.resolve(<span class="string">'@babel/preset-react'</span>),</span><br><span class="line"> [<span class="built_in">require</span>.resolve(<span class="string">'@babel/preset-env'</span>), { <span class="attr">modules</span>: <span class="literal">false</span> }],</span><br><span class="line"> ],</span><br><span class="line"> cacheDirectory: <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><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>在 <code>src</code> 目录下创建 <code>App.jsx</code>:</p><figure class="highlight javascript"><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">import</span> React <span class="keyword">from</span> <span class="string">"react"</span>;</span><br><span class="line"><span class="keyword">import</span> ReactDOM <span class="keyword">from</span> <span class="string">"react-dom"</span>;</span><br><span class="line"><span class="keyword">const</span> App = <span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <div></span><br><span class="line"> <p>Hello React and Webpack<<span class="regexp">/p></span></span><br><span class="line"><span class="regexp"> </</span>div></span><br><span class="line"> );</span><br><span class="line">};</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> App;</span><br><span class="line">ReactDOM.render(<span class="xml"><span class="tag"><<span class="name">App</span> /></span>, document.getElementById("app"));</span></span><br></pre></td></tr></table></figure><p>在 <code>src</code> 下新建 <code>index.jsx</code> 内容如下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">'./App'</span>;</span><br></pre></td></tr></table></figure><p>执行 <code>yarn build</code> 等待打包结果,此时目录 <code>dist</code> 下已经打包好 bundle。</p><p>我们接着创建 html 文件,在 src 下创建 <code>index.html</code> :</p><figure class="highlight html"><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="meta"><!DOCTYPE HTML></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"zh-CN"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Hello React Webpack<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>修改 build 的配置,拷贝 html</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add html-webpack-plugin</span><br></pre></td></tr></table></figure><p>修改上面的 config:</p><figure class="highlight diff"><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></pre></td><td class="code"><pre><span class="line">const HtmlWebPackPlugin = require("html-webpack-plugin");</span><br><span class="line"></span><br><span class="line">module.exports = {</span><br><span class="line"> mode: 'production',</span><br><span class="line"> resolve: {</span><br><span class="line"> extensions: ['.js', '.jsx'],</span><br><span class="line"> },</span><br><span class="line"> module: {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: /\.jsx?$/,</span><br><span class="line"> exclude: /node_modules/,</span><br><span class="line"> use: {</span><br><span class="line"> loader: 'babel-loader',</span><br><span class="line"> options: {</span><br><span class="line"> babelrc: false,</span><br><span class="line"> presets: [</span><br><span class="line"> require.resolve('@babel/preset-react'),</span><br><span class="line"> [require.resolve('@babel/preset-env'), { modules: false }],</span><br><span class="line"> ],</span><br><span class="line"> cacheDirectory: true,</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"> plugins: [</span><br><span class="line"><span class="addition">+ new HtmlWebPackPlugin({</span></span><br><span class="line"><span class="addition">+ template: "src/index.html",</span></span><br><span class="line"><span class="addition">+ filename: "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><p>执行 <code>yarn build</code> 就可以看到已经打包好的 index.html 和 bundle js 。</p><h3 id="进入开发阶段"><a href="#进入开发阶段" class="headerlink" title="进入开发阶段"></a>进入开发阶段</h3><h4 id="dev-server"><a href="#dev-server" class="headerlink" title="dev-server"></a>dev-server</h4><p>通过 <code>webpack-dev-server</code> 搭建本地 server 服务,目前是通用的解决办法</p><p>安装依赖:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add webpack-dev-server --dev</span><br></pre></td></tr></table></figure><p>方便起见,我们在 <code>package.json</code> 的 scripts 中增加内容:</p><figure class="highlight"><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">"scripts": {</span><br><span class="line"> "start": "webpack-dev-server --mode development --open",</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行 <code>yarn start</code> 此时会运行一个 dev-server 服务,这样我们就能方便地在本地进行开发了。</p><p>在开发中我们还有一些其他的需求,比如 <code>sourceMap</code> 、修改 dev-server 配置等,所以我们可以新建一个配置 <code>webpack.config.dev.js</code> :</p><figure class="highlight diff"><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></pre></td><td class="code"><pre><span class="line">const path = require('path');</span><br><span class="line"></span><br><span class="line">module.exports = {</span><br><span class="line"> mode: 'development',</span><br><span class="line"><span class="addition">+ devtool: 'cheap-module-source-map',</span></span><br><span class="line"> resolve: {</span><br><span class="line"> extensions: ['.js', '.jsx'],</span><br><span class="line"> },</span><br><span class="line"><span class="addition">+ devServer: {</span></span><br><span class="line"><span class="addition">+ contentBase: path.join(__dirname, "./src/"),</span></span><br><span class="line"><span class="addition">+ publicPath: '/',</span></span><br><span class="line"><span class="addition">+ host: '127.0.0.1',</span></span><br><span class="line"><span class="addition">+ port: 3000,</span></span><br><span class="line"><span class="addition">+ stats: {</span></span><br><span class="line"><span class="addition">+ colors: true,</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"> },</span><br><span class="line"> module: {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> test: /\.jsx?$/,</span><br><span class="line"> exclude: /node_modules/,</span><br><span class="line"> use: {</span><br><span class="line"> loader: 'babel-loader',</span><br><span class="line"> options: { // babel 转义的配置选项</span><br><span class="line"> babelrc: false,</span><br><span class="line"> presets: [</span><br><span class="line"> require.resolve('@babel/preset-react'),</span><br><span class="line"> [require.resolve('@babel/preset-env'), { modules: false }],</span><br><span class="line"> ],</span><br><span class="line"> cacheDirectory: true,</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"> plugins: [</span><br><span class="line"> new HtmlWebPackPlugin({</span><br><span class="line"> template: 'src/index.html',</span><br><span class="line"> filename: 'index.html',</span><br><span class="line"><span class="addition">+ inject: true,</span></span><br><span class="line"> })</span><br><span class="line"> ]</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>修改 <code>package.json</code> 中 scripts 执行所需要执行的配置文件:</p><figure class="highlight"><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">"scripts": {</span><br><span class="line"> "start": "webpack-dev-server --config './webpack.config.dev.js'"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>重新执行 <code>yarn start</code> 可以看到修改配置后的 dev-server</p><h5 id="热更新-HMR"><a href="#热更新-HMR" class="headerlink" title="热更新 HMR"></a>热更新 HMR</h5><p>配置热更新就可以让我们在开发过程中,将修改后代码整页面无刷新且保持原有 state 的情况下直接反应到页面,下面我们继续修改 config.dev.js 并在 App.jsx 增加内容:</p><figure class="highlight diff"><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></pre></td><td class="code"><pre><span class="line">// webpack.config.dev.js config 部分</span><br><span class="line">devServer: {</span><br><span class="line"> ...</span><br><span class="line"><span class="addition">+ hot: true,</span></span><br><span class="line"> ...</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line">// webpack.config.dev.js plugins 部分</span><br><span class="line">...</span><br><span class="line">plugins: [</span><br><span class="line"><span class="addition">+ new webpack.HotModuleReplacementPlugin(),</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">// app.jsx 页面底部新增</span><br><span class="line">...</span><br><span class="line"><span class="addition">+ if (module.hot) {</span></span><br><span class="line"><span class="addition">+ module.hot.accept((err) => {</span></span><br><span class="line"><span class="addition">+ if (err) {</span></span><br><span class="line"><span class="addition">+ console.error('Cannot apply HMR update.', err);</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line"><span class="addition">+ });</span></span><br><span class="line"><span class="addition">+ }</span></span><br></pre></td></tr></table></figure><p>执行 <code>yarn start</code> 重新启动 server,每次修改代码后保存就可以看到控制台里重新编译的信息,浏览器中变化实际修改的内容了。</p><h2 id="Webpack-4-x-amp-Vue-2-x"><a href="#Webpack-4-x-amp-Vue-2-x" class="headerlink" title="Webpack 4.x & Vue 2.x"></a>Webpack 4.x & Vue 2.x</h2><blockquote><p>Vue 官方也推出 vue-cli 来帮助使用者快速创建项目,同时也是使用 webpack 4 来进行构建项目,通过阅读使用文档和源码,需要满足现有复杂项目的需求,我们可能也是不仅需要使用 –options 的方式同时还需要做一些定制的开发才可以,因此下面不再提及。</p></blockquote><h3 id="Vue-项目初始化"><a href="#Vue-项目初始化" class="headerlink" title="Vue 项目初始化"></a>Vue 项目初始化</h3><p>与 React 项目初始化一致(创建目录、安装 vue、安装 webpack、安装 babel)</p><figure class="highlight bash"><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">mkdir vue-webpack-demo</span><br><span class="line"><span class="built_in">cd</span> vue-webpack-demo</span><br><span class="line"><span class="comment">## 初始化 package.json</span></span><br><span class="line">yarn init -y</span><br><span class="line"><span class="comment">## 安装 webpack</span></span><br><span class="line">yarn add webpack webpack-cli --dev</span><br><span class="line"><span class="comment">## 安装 babel</span></span><br><span class="line">yarn add @babel/core @babel/preset-env --dev</span><br><span class="line"><span class="comment">## 安装 vue</span></span><br><span class="line">yarn add vue</span><br><span class="line"><span class="comment">## 安装 vue-loader</span></span><br><span class="line">yarn add vue-loader vue-template-compiler --dev</span><br></pre></td></tr></table></figure><p>在 vue-webpack-demo 文件夹下,创建 <code>index.html</code> :</p><figure class="highlight html"><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="meta"><!DOCTYPE HTML></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"zh-CN"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Webpack Vue Demo<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p>新建 src 目录,并新建 app.js 和 app.vue 文件:</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.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> App <span class="keyword">from</span> <span class="string">'./app.vue'</span></span><br><span class="line"></span><br><span class="line">Vue.config.productionTip = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> Vue({</span><br><span class="line"> render: <span class="function"><span class="params">h</span> =></span> h(App)</span><br><span class="line">}).$mount(<span class="string">'#app'</span>)</span><br></pre></td></tr></table></figure><figure class="highlight html"><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">// app.vue</span><br><span class="line"><span class="tag"><<span class="name">template</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span>></span></span><br><span class="line"> Hello Vue & Webpack</span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">template</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="keyword">export</span> <span class="keyword">default</span> {</span></span><br><span class="line"><span class="undefined">}</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">style</span>></span><span class="undefined"></span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">style</span>></span></span><br></pre></td></tr></table></figure><p>下面来增加配置文件,<code>webpack.config.js</code>:</p><figure class="highlight javascript"><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></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 class="keyword">const</span> VueLoaderPlugin = <span class="built_in">require</span>(<span class="string">'vue-loader/lib/plugin'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> resolve: {</span><br><span class="line"> alias: {</span><br><span class="line"> <span class="string">'vue$'</span>: <span class="string">'vue/dist/vue.esm.js'</span></span><br><span class="line"> },</span><br><span class="line"> extensions: [<span class="string">'*'</span>, <span class="string">'.js'</span>, <span class="string">'.vue'</span>, <span class="string">'.json'</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="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"> test: <span class="regexp">/\.vue$/</span>,</span><br><span class="line"> include: <span class="string">'/src/'</span>,</span><br><span class="line"> loader: <span class="string">'vue-loader'</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> VueLoaderPlugin(),</span><br><span class="line"> <span class="keyword">new</span> HtmlWebpackPlugin({</span><br><span class="line"> filename: <span class="string">'index.html'</span>,</span><br><span class="line"> template: <span class="string">'index.html'</span>,</span><br><span class="line"> chunks: [<span class="string">'app'</span>, <span class="string">'manifest'</span>, <span class="string">'vendor'</span>],</span><br><span class="line"> });</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>完成后执行 webpack 就可以看到 dist 下已经构建完成的项目。</p><blockquote><p>注意:vue-loader 已经更新到 v15.x ,与之前 14.x 配置方式有差异。</p></blockquote><p>用于开发 dev-server 配置与 React 基本一致,这里不重复。</p><h2 id="实战内容经验积累"><a href="#实战内容经验积累" class="headerlink" title="实战内容经验积累"></a>实战内容经验积累</h2><p>下面的内容是在笔者积累的项目优化实战经验汇总。</p><h3 id="提取公共依赖"><a href="#提取公共依赖" class="headerlink" title="提取公共依赖"></a>提取公共依赖</h3><p>为了减少一次请求文件体积过大,同时修改业务代码时不必要重复重新下载公共依赖代码,我们通常将公共依赖模块如 <code>react</code>、<code>react-dom</code>、<code>vue</code>、<code>axio</code> 等文件抽取出来独立打包。</p><blockquote><p>注意:<code>CommonsChunkPlugin</code> 在 4.0 中已经被 <code>optimization</code> 选项取代,不需要安装该插件就可以实现相同效果,见下面配置。</p></blockquote><figure class="highlight diff"><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></pre></td><td class="code"><pre><span class="line">mode: 'production',</span><br><span class="line">output: {</span><br><span class="line"> ...</span><br><span class="line"><span class="addition">+ chunkFilename: 'js/[name].[chunkhash:8].js',</span></span><br><span class="line"> ...</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line">optimization: {</span><br><span class="line"> nodeEnv: 'production',</span><br><span class="line"> ...</span><br><span class="line"> runtimeChunk: {</span><br><span class="line"> name: 'manifest', // 运行时文件</span><br><span class="line"> },</span><br><span class="line"><span class="addition">+ splitChunks: {</span></span><br><span class="line"><span class="addition">+ cacheGroups: {</span></span><br><span class="line"><span class="addition">+ commons: {</span></span><br><span class="line"><span class="addition">+ test: /[\\/]node_modules[\\/]/,</span></span><br><span class="line"><span class="addition">+ name: 'vendor', // 依赖第三方库要提取成名字是的 vendor 的文件</span></span><br><span class="line"><span class="addition">+ chunks: 'all', // 提取所有 chunks</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"> },</span><br><span class="line"> ...</span><br><span class="line">},</span><br></pre></td></tr></table></figure><p>通过上面的配置,可以将公共依赖和业务代码隔离开来。但是,也会存在一些<strong>隐患</strong>:</p><ul><li>随着项目复杂度增加,依赖库增多,<code>vendor.js</code> 的体积会越来越<strong>臃肿</strong></li><li>多页面应用项目中,不同页面仍然会加载到在本页面根本无用的公共依赖的冗余代码</li></ul><p>所以具体项目需要通过具体的需求来抽离出不同的 <code>chunks</code> 来分别引用,按需引用。</p><h3 id="文件压缩"><a href="#文件压缩" class="headerlink" title="文件压缩"></a>文件压缩</h3><p>配置 <code>mode: 'production'</code> Webpack 会使用默认插件 <code>[UglifyJs](https://github.com/webpack-contrib/uglifyjs-webpack-plugin)</code> 来进行压缩代码。</p><p>官网提到在 <code>Webpack v4</code> 以前使用内置的 <code>webpack.optimize.UglifyJsPlugin</code> 插件,在 Webpack 4 以后,开始使用 ^1.0.0 独立的版本。</p><figure class="highlight diff"><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></pre></td><td class="code"><pre><span class="line"><span class="addition">+ const UglifyJsPlugin = require('uglifyjs-webpack-plugin');</span></span><br><span class="line"><span class="addition">+ const os = require('os');</span></span><br><span class="line">...</span><br><span class="line">// webpack config</span><br><span class="line">optimization: {</span><br><span class="line"> nodeEnv: 'production',</span><br><span class="line"><span class="addition">+ minimizer: [</span></span><br><span class="line"><span class="addition">+ new UglifyJsPlugin({</span></span><br><span class="line"><span class="addition">+ cache: true, // node_modules/.cache/uglifyjs-webpack-plugin</span></span><br><span class="line"><span class="addition">+ parallel: os.cpus().length, // 并行 default:true os.cpus().length - 1</span></span><br><span class="line"><span class="addition">+ uglifyOptions: {</span></span><br><span class="line"><span class="addition">+ ecma: 5,</span></span><br><span class="line"><span class="addition">+ mangle: true,</span></span><br><span class="line"><span class="addition">+ },</span></span><br><span class="line"><span class="addition">+ sourceMap: false,</span></span><br><span class="line"><span class="addition">+ }),</span></span><br><span class="line"> ],</span><br><span class="line">},</span><br></pre></td></tr></table></figure><blockquote><p>注意:4.0 版本压缩的代码已经放在 <code>optimization</code> 下 <code>minimizer</code> 节点下。<br>备注:关于 <code>parallel</code> 选项,新版的 uglifyjs 已经支持多核 CPU 并行执行,所以已经不需要 <code>webpack-parallel-uglify-plugin</code> 插件。</p></blockquote><h3 id="proxy-接口代理"><a href="#proxy-接口代理" class="headerlink" title="proxy 接口代理"></a>proxy 接口代理</h3><p>配合 dev-server 对代理本地启动的 server 某一域名进行代理,解决服务端接口暂时满足要求、本地请求跨域等问题。</p><figure class="highlight diff"><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">devServer: {</span><br><span class="line"> ...</span><br><span class="line"><span class="addition">+ proxy: {</span></span><br><span class="line"><span class="addition">+ "/api": "https://localhost:3000",</span></span><br><span class="line"><span class="addition">+ changeOrigin: true, // 支持跨域请求</span></span><br><span class="line"><span class="addition">+ secure: true, // 支持 https</span></span><br><span class="line"><span class="addition">+ }</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>文档见<a href="https://webpack.js.org/configuration/dev-server/#devserver-proxy" target="_blank" rel="noopener">dev-server-proxy doc</a></p><h3 id="支持多入口"><a href="#支持多入口" class="headerlink" title="支持多入口"></a>支持多入口</h3><p>虽然多数情况下,我们都在开发并且维护单页面的应用,但是当遇到需要多页面的时候,我们也希望在一个项目内进行构建,目前解决办法比较<strong>粗暴</strong>,当前有 n 个入口 html 就创建 n 个 <code>HtmlWebpackPlugin</code> 插件实例。</p><figure class="highlight javascript"><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="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</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 class="comment">// 先找到项目指定目录下的所有 html 此处假设我们把入口 html 放在 src/html 下 app1.html、app2.html、app3.html</span></span><br><span class="line"><span class="keyword">const</span> appHtmlEntries = fs.readdirSync(resolveToAppRoot(<span class="string">'./src/html/'</span>))</span><br><span class="line"> .filter(<span class="function"><span class="params">f</span> =></span> f.match(<span class="regexp">/\.html?$/</span>))</span><br><span class="line"> .reduce(<span class="function">(<span class="params">acc, p</span>) =></span> <span class="built_in">Object</span>.assign(acc, { [path.basename(p).replace(<span class="regexp">/\.html?$/</span>, <span class="string">''</span>)]: path.join(resolveToAppRoot(<span class="string">'./src/html/'</span>), p) }), {});</span><br><span class="line">...</span><br><span class="line"><span class="comment">// 每一个 html 创建一个 HtmlWebpackPlugin 实例</span></span><br><span class="line"><span class="built_in">Object</span>.keys(appHtmlEntries).forEach(<span class="function">(<span class="params">name</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> pluginHtml = <span class="keyword">new</span> HtmlWebpackPlugin({</span><br><span class="line"> filename: <span class="string">`<span class="subst">${name}</span>.html`</span>,</span><br><span class="line"> template: <span class="string">`src/html/<span class="subst">${name}</span>.html`</span>,</span><br><span class="line"> chunks: [<span class="string">`<span class="subst">${name}</span>`</span>, <span class="string">'manifest'</span>, <span class="string">'vendor'</span>],</span><br><span class="line"> inject: <span class="literal">true</span>,</span><br><span class="line"> });</span><br><span class="line"> webpackConfig.plugins.push(pluginHtml);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="预编译-sass、引入-postcss、处理-css-压缩-和-文件分离"><a href="#预编译-sass、引入-postcss、处理-css-压缩-和-文件分离" class="headerlink" title="预编译 sass、引入 postcss、处理 css 压缩 和 文件分离"></a>预编译 sass、引入 postcss、处理 css 压缩 和 文件分离</h3><p>这里要注意 rules 中 loader 数组的顺序,由于 webpack 执行 rules 是从最后一个开始倒叙执行,所以我们配置的顺序也是:</p><p>预编译 sass => 处理 postcss => 处理 css => 压缩并独立 css 文件</p><p>升级到 4.0,已经<strong>不再使用</strong> <code>extract-text-webpack-plugin</code> 插件来进行文件抽取,改用 <code>MiniCssExtractPlugin</code> 插件,配合 <code>OptimizeCSSAssetsPlugin</code> 插件来压缩 css 文件。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> autoprefixer = <span class="built_in">require</span>(<span class="string">'autoprefixer'</span>);</span><br><span class="line"><span class="keyword">const</span> postcssFlexbugsFixes = <span class="built_in">require</span>(<span class="string">'postcss-flexbugs-fixes'</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><br><span class="line">...</span><br><span class="line"><span class="comment">// rules 配置</span></span><br><span class="line">{</span><br><span class="line"> test: <span class="regexp">/\.(css|sass|scss)$/</span>,</span><br><span class="line"> use: [</span><br><span class="line"> {</span><br><span class="line"> loader: MiniCssExtractPlugin.loader, <span class="comment">// 这个 loader 放在最后一个执行,将编译好的 css 独立</span></span><br><span class="line"> },</span><br><span class="line"> <span class="built_in">require</span>.resolve(<span class="string">'css-loader'</span>),</span><br><span class="line"> {</span><br><span class="line"> loader: <span class="built_in">require</span>.resolve(<span class="string">'postcss-loader'</span>),</span><br><span class="line"> options: {</span><br><span class="line"> ident: <span class="string">'postcss'</span>,</span><br><span class="line"> plugins: <span class="function"><span class="params">()</span> =></span> [</span><br><span class="line"> postcssFlexbugsFixes,</span><br><span class="line"> autoprefixer({</span><br><span class="line"> browsers: [</span><br><span class="line"> <span class="string">'>1%'</span>,</span><br><span class="line"> <span class="string">'last 4 versions'</span>,</span><br><span class="line"> <span class="string">'Firefox ESR'</span>,</span><br><span class="line"> <span class="string">'not ie < 9'</span>,</span><br><span class="line"> ],</span><br><span class="line"> flexbox: <span class="string">'no-2009'</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 class="built_in">require</span>.resolve(<span class="string">'sass-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 class="comment">// optimization 配置</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 class="keyword">const</span> cssnano = <span class="built_in">require</span>(<span class="string">'cssnano'</span>);</span><br><span class="line">...</span><br><span class="line">optimization: {</span><br><span class="line"> nodeEnv: <span class="string">'production'</span>,</span><br><span class="line"> minimizer: [</span><br><span class="line"> <span class="keyword">new</span> OptimizeCSSAssetsPlugin({</span><br><span class="line"> assetNameRegExp: <span class="regexp">/\.css$/g</span>,</span><br><span class="line"> cssProcessor: cssnano, <span class="comment">// 默认使用 cssnano 处理 css</span></span><br><span class="line"> cssProcessorOptions: {</span><br><span class="line"> reduceIdents: <span class="literal">false</span>, <span class="comment">// 禁止将 keyframes 自动更名</span></span><br><span class="line"> mergeIdents: <span class="literal">false</span>, <span class="comment">// 禁止自动合并 keyframes</span></span><br><span class="line"> discardUnused: <span class="literal">false</span>, <span class="comment">// 禁止移除掉未使用的 keyframes</span></span><br><span class="line"> autoprefixer: <span class="literal">false</span>, <span class="comment">// 禁止默认删除掉一些前缀,以减少兼容性的问题</span></span><br><span class="line"> zindex: <span class="literal">false</span>, <span class="comment">// 禁止自动转换 z-index</span></span><br><span class="line"> map: <span class="literal">false</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"><span class="comment">// 独立 css 文件</span></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">'css/[name].[contenthash:8].css'</span>,</span><br><span class="line"> }),</span><br><span class="line">],</span><br></pre></td></tr></table></figure><h3 id="支持-CDN-路径替换"><a href="#支持-CDN-路径替换" class="headerlink" title="支持 CDN 路径替换"></a>支持 CDN 路径替换</h3><p>可以将 html 中 css 和 js 的相对引用路径自动替换成配置的前缀路径,用来支持静态资源上线到具体指定的 CDN 路径来增加 app 内静态资源的下载速度。</p><figure class="highlight diff"><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">output: {</span><br><span class="line"> ...</span><br><span class="line"><span class="addition">+ publicPath: "https://...cdnpath.../assets/" // CDN 资源 URL 前缀</span></span><br><span class="line"> ...</span><br><span class="line">},</span><br></pre></td></tr></table></figure><h3 id="生产环境的-source-map"><a href="#生产环境的-source-map" class="headerlink" title="生产环境的 source-map"></a>生产环境的 source-map</h3><p>有些时候我们会遇到在生产环境中代码出现问题的情况,而本地开发却不重现,这个时候 source-map 就成了辅助解决问题的一个有利的工具,具体见下面配置:</p><figure class="highlight diff"><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">mode: 'production',</span><br><span class="line"><span class="addition">+ devtool: 'source-map',</span></span><br><span class="line">optimization: {</span><br><span class="line"> nodeEnv: 'production',</span><br><span class="line"> minimizer: [</span><br><span class="line"> new UglifyJsPlugin({</span><br><span class="line"> ...</span><br><span class="line"><span class="addition">+ sourceMap: true,</span></span><br><span class="line"> }),</span><br><span class="line"> new OptimizeCSSAssetsPlugin({</span><br><span class="line"> assetNameRegExp: /\.css$/g,</span><br><span class="line"> cssProcessor: cssnano,</span><br><span class="line"> // 默认使用 cssnano 处理 css,另外 clean-css 也提供相应的方案,但需要 4.2 版本才可用且质量和效率都没有得到验证,暂且不提</span><br><span class="line"> cssProcessorOptions: {</span><br><span class="line"> ...</span><br><span class="line"><span class="addition">+ map: { inline: false } ,</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="处理图片资源"><a href="#处理图片资源" class="headerlink" title="处理图片资源"></a>处理图片资源</h3><p>下面是关键 <code>loaders</code> 但不会列出所有配置,内容略有差异,可以根据配置 limit 大小来控制图片转 base64 或压缩等。</p><ul><li><code>file-loader</code> 文件无处理,直接拷贝</li><li><code>url-loader</code> 可以增加 base64 处理</li><li><code>svg-url-loader</code> 处理 svg 文件,也同样支持 base64</li><li><code>image-webpack-loader</code> 图片文件降质压缩</li></ul><h3 id="其他插件"><a href="#其他插件" class="headerlink" title="其他插件"></a>其他插件</h3><ul><li><code>HashedModuleIdsPlugin</code> 使用更稳定的 moudle id 生成方式</li><li><code>webpack.optimize.ModuleConcatenationPlugin</code> 插件已经不需要单独配置,Webpack 4 已经默认在生产模式下打包时内置开启优化</li></ul><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><blockquote><p><code>并不是每一个人都想成为 Webpack 配置工程师!</code></p></blockquote><p>上面引用一句我们“造轮子”时使用的一句 slogan。</p><p>虽然 <code>Webpack 4</code> 还没有达到<em>开箱即用</em>的程度(当然,开箱即用也就意味着可配置的内容有所减少,这一定是一把双刃剑),况且开箱即用也并不是它的被创造出来的初衷,简单的配置无法满足项目中的实际需求,各种各样的配置和插件配合着形成解决各种问题的不同方案,只在很多次尝试后才能达到针对某一个项目项目<em>最优</em>的配置。</p><p>然而,它<strong>只是一个工具</strong>,也许再过一段会有新的工具来取代 Webpack,But 既然你现在用到它,还是有必要花时间了解一下如何更好地让它为你、你的团队和你的项目来服务。</p><p>关于 Webpack 4 的配置经验笔者也在摸着石头过这条小河,网上也有诸多优化和解决方案,我们造的轮子也需要更多的项目和时间来帮助其成长,文中并没有面面俱到地将所有配置详细说明,或者并不是所有配置都是最优选择,也欢迎私信留言讨论。</p>]]></content>
<summary type="html">
<p>最近一直在参与小组内“造轮子”(具体内容另寻机会再详说)在开发的过程中,了解并且学习到 Webpack v4 的一些内容,趁记忆还深,汇总成文。</p>
<p>鉴于 Webpack 作为关键在 Google 可以搜索到很多相关的文章,网上文章也是针对各自项目和某些情况的具体方案或者介绍说明,本文也不例外,只介绍分享过程中积累的 <strong>4.0</strong> 版本的个人实战经验。</p>
<blockquote>
<p>导读:本文你将 Get 到使用 Webpack 4 从零开始分别搭建 React 16 和 Vue 2 项目,同时还有基于 Webpack 4 的一些开发和生产环境配置经验,感兴趣同学可以继续阅读。</p>
</blockquote>
<p>PS. 前半部分较为基础,有一定经验的同学可以直接跳过阅读<a href="#实战内容经验积累">后半部分实战内容</a>。</p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="Webpack" scheme="https://beanlee.github.io/tags/Webpack/"/>
<category term="React" scheme="https://beanlee.github.io/tags/React/"/>
<category term="Vue" scheme="https://beanlee.github.io/tags/Vue/"/>
</entry>
<entry>
<title>【译】Google 出品 - 利用 webpack 做 web 性能优化</title>
<link href="https://beanlee.github.io/posts/blog-translate-web-performance-optimization-with-webpack-from-google-webpack4/"/>
<id>https://beanlee.github.io/posts/blog-translate-web-performance-optimization-with-webpack-from-google-webpack4/</id>
<published>2018-05-02T06:35:33.000Z</published>
<updated>2018-06-20T01:31:47.000Z</updated>
<content type="html"><![CDATA[<p>作者 <a href="https://developers.google.com/web/resources/contributors/addyosmani" target="_blank" rel="noopener">Addy Osmani</a>,<a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p><p>原文 <a href="https://developers.google.com/web/fundamentals/performance/webpack/" target="_blank" rel="noopener">https://developers.google.com/web/fundamentals/performance/webpack/</a></p><blockquote><p>PS. 在 20180211 笔者翻译过一次,当时也没有完全理解和使用文中提到的优化项,近期工作中因为用到 Webpack 4.x 对生产环境进行打包,加深了一些理解,本译文对原有译文补充的 Webpack 4 内容,同时对原译文进行了校对和一些细节措辞的修改。</p></blockquote><a id="more"></a><!-- TOC --><ul><li><a href="#instroduction-介绍">Instroduction 介绍</a></li><li><a href="#decrease-front-end-size-减少前端体积">Decrease Front-end Size 减少前端体积</a><ul><li><a href="#use-the-production-mode-webpack-4-only-使用生产模式仅用于-webpack-4">Use the production mode (webpack 4 only) 使用生产模式(仅用于 webpack 4)</a></li><li><a href="#enable-minification-开启最小化">Enable minification 开启最小化</a></li><li><a href="#specify-node_envproduction-明确生产环境信息">Specify <code>NODE_ENV=production</code> 明确生产环境信息</a></li><li><a href="#use-es-modules-使用-es-模块">Use ES Modules 使用 ES 模块</a></li><li><a href="#optimize-images-优化图片">Optimize images 优化图片</a></li><li><a href="#optimize-dependencies-优化依赖">Optimize dependencies 优化依赖</a></li><li><a href="#enable-module-concatenation-for-es-modules-aka-scope-hoisting-为-es-modles-开启模块连接">Enable module concatenation for ES modules (aka scope hoisting) 为 ES modles 开启模块连接</a></li><li><a href="#use-externals-if-you-have-both-webpack-and-non-webpack-code-如果代码中包含-webpack-和非-webpack-的代码要使用-externals">Use <strong>externals</strong> if you have both webpack and non-webpack code 如果代码中包含 webpack 和非 webpack 的代码要使用 externals</a></li><li><a href="#summing-up-总结">Summing up 总结</a></li></ul></li><li><a href="#make-use-of-long-term-caching-利用好长时缓存">Make use of long-term caching 利用好长时缓存</a><ul><li><a href="#use-bundle-versioning-and-cache-headers-使用-bundle-版本和缓存头信息">Use bundle versioning and cache headers 使用 bundle 版本和缓存头信息</a></li><li><a href="#extract-dependencies-and-runtime-into-a-separate-file-将依赖和运行环境代码提取到一个单独的文件">Extract dependencies and runtime into a separate file 将依赖和运行环境代码提取到一个单独的文件</a></li><li><a href="#inline-webpack-runtime-to-save-an-extra-http-request-内联-webpack-runtime-节省额外的-http-请求">Inline webpack runtime to save an extra HTTP request 内联 webpack runtime 节省额外的 HTTP 请求</a></li><li><a href="#lazy-load-code-that-you-dont-need-right-now-懒加载">Lazy-load code that you don’t need right now 懒加载</a></li><li><a href="#split-the-code-into-routes-and-pages-拆分代码到路由和页面中">Split the code into routes and pages 拆分代码到路由和页面中</a></li><li><a href="#make-module-ids-more-stable-使用稳定的-module-ids">Make module ids more stable 使用稳定的 module ids</a></li><li><a href="#summing-up">Summing up</a></li></ul></li><li><a href="#monitor-and-analyze-the-app-监控并分析">Monitor and analyze the app 监控并分析</a><ul><li><a href="#keep-track-of-the-bundle-size-跟踪打包的体积">Keep track of the bundle size 跟踪打包的体积</a></li><li><a href="#analyze-why-the-bundle-is-so-large-分析-bundle-为什么这么大">Analyze why the bundle is so large 分析 bundle 为什么这么大</a></li><li><a href="#summing-up-小结">Summing up 小结</a></li></ul></li><li><a href="#conclusion">Conclusion</a></li></ul><!-- /TOC --><h2 id="Instroduction-介绍"><a href="#Instroduction-介绍" class="headerlink" title="Instroduction 介绍"></a>Instroduction 介绍</h2><p>作者 <a href="https://developers.google.com/web/resources/contributors/addyosmani" target="_blank" rel="noopener">Addy Osmani</a></p><p>现代 Web 应用经常用到 <code>bunding tool</code> 来创建生产环境的打包文件(例如脚本、样式等等),打包文件是需要<a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/javascript-startup-optimization" target="_blank" rel="noopener">优化</a>并<a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer" target="_blank" rel="noopener">压缩最小化</a>,同时能够被用户更快地加载。在这篇文章中,我们将会利用 <a href="https://webpack.js.org/" target="_blank" rel="noopener"><code>webpack</code></a> 来贯穿说明如何进行高效地优化网站资源。这能帮助用户更快地加载你的应用同时获得更好的体验。</p><p><img src="https://img20.360buyimg.com/uba/jfs/t15217/149/2229580840/12989/54324b99/5a815957N5bb3e0c6.png" alt="webpack-logo"></p><p>webpack 是当今最流行的打包工具之一,深入地利用它的特点去优化代码,将脚本<a href="https://developers.google.com/web/fundamentals/performance/webpack/use-long-term-caching#lazy-loading" target="_blank" rel="noopener">拆分</a>成不同的部分,同时剔除无用代码将能够保证你的应用维持最小的带宽和进程消耗。</p><p><img src="https://img14.360buyimg.com/uba/jfs/t17569/325/476871633/18187/a1e34f41/5a81597fNd77bb5b8.png" alt="code-splitting"></p><blockquote><p>Note: 我们创建了一个练习的应用来演示下面这些优化的描述。尽力抽更多的时间来练习这些 tips <a href="https://github.com/GoogleChromeLabs/webpack-training-project" target="_blank" rel="noopener"><code>webpack-training-project</code></a></p></blockquote><p>让我们从现代 web 应用中最耗费资源之一的 <code>Javascript</code> 开始。</p><ul><li><a href="#Decrease Front-end Size 减少前端体积">减小前端体积</a></li><li><a href="#Make use of long-term caching 利用好长时缓存">利用长时缓存</a></li><li><a href="#Monitor and analyze the app 监控并分析">监控并分析应用</a></li><li><a href="#Conclusion">总结</a></li></ul><hr><h2 id="Decrease-Front-end-Size-减少前端体积"><a href="#Decrease-Front-end-Size-减少前端体积" class="headerlink" title="Decrease Front-end Size 减少前端体积"></a>Decrease Front-end Size 减少前端体积</h2><p>作者 <a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p><p>当你正在优化一个应用时,首要事情就是尽可能地将它体积的减小。下面我们就来看看通过 <code>webpack</code> 如何做到减小前端体积。</p><h3 id="Use-the-production-mode-webpack-4-only-使用生产模式(仅用于-webpack-4)"><a href="#Use-the-production-mode-webpack-4-only-使用生产模式(仅用于-webpack-4)" class="headerlink" title="Use the production mode (webpack 4 only) 使用生产模式(仅用于 webpack 4)"></a>Use the production mode (webpack 4 only) 使用生产模式(仅用于 webpack 4)</h3><p>Webpack 4 介绍了一种新的<a href="https://webpack.js.org/concepts/mode/" target="_blank" rel="noopener">模式</a>,你可以将其设置成 <code>development</code> 和 <code>production</code> 用于告诉 Webpack 你正在为不同的环境打包:</p><figure class="highlight javascript"><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 class="comment">// webpack.config.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> mode: <span class="string">'production'</span>,</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>当你正在为你的应用用于生产环境编译打包时要确定开启了 <code>production</code> 模式。这样就帮助 webpack 开启类似压缩最小化代码、去除依赖库中开发环境代码等<a href="https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a" target="_blank" rel="noopener">其他</a>的优化项。</p><h5 id="Further-reading-扩展阅读"><a href="#Further-reading-扩展阅读" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h5><ul><li><a href="https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a" target="_blank" rel="noopener">What specific things the mode flag configures</a></li></ul><blockquote><p>Note: 笔者也翻译了另外一篇介绍新增 mode 的文章,感兴趣可以<a href="https://beanlee.github.io/2018/04/18/blog-translate-webpack-4-mode-and-optimization/">点击链接</a></p></blockquote><h3 id="Enable-minification-开启最小化"><a href="#Enable-minification-开启最小化" class="headerlink" title="Enable minification 开启最小化"></a>Enable minification 开启最小化</h3><blockquote><p>Note: 大部分只针对 webpack 3 如果你正在使用 webpack 4 生产模式打包,bundle 级别的最小化功能已经开启 - 你只需要配置<a href="#Loader-specific options 特定的 Loader 配置">对应 loader 选项</a>即可</p></blockquote><p>最小化就是通过去除多余空格、缩短变量名等方式压缩代码。例如:</p><figure class="highlight javascript"><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">// Original code</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">map</span>(<span class="params">array, iteratee</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> index = <span class="number">-1</span>;</span><br><span class="line"> <span class="keyword">const</span> length = array == <span class="literal">null</span> ? <span class="number">0</span> : array.length;</span><br><span class="line"> <span class="keyword">const</span> result = <span class="keyword">new</span> <span class="built_in">Array</span>(length);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (++index < length) {</span><br><span class="line"> result[index] = iteratee(array[index], index, array);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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"><span class="comment">// Minified code</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">map</span>(<span class="params">n,r</span>)</span>{<span class="keyword">let</span> t=<span class="number">-1</span>;<span class="keyword">for</span>(<span class="keyword">const</span> a=<span class="literal">null</span>==n?<span class="number">0</span>:n.length,l=<span class="built_in">Array</span>(a);++t<a;)l[t]=r(n[t],t,n);<span class="keyword">return</span> l}</span><br></pre></td></tr></table></figure><p>Webpack 支持两种方式最小化代码:<em>bundle-level</em> 最小化 和 <em>loader-specific options</em>。他们可以同时使用。</p><h4 id="Bundle-level-minification-bundle-级别的最小化"><a href="#Bundle-level-minification-bundle-级别的最小化" class="headerlink" title="Bundle-level minification bundle 级别的最小化"></a>Bundle-level minification bundle 级别的最小化</h4><p><code>Bundle-level</code> 最小化功能可以在编译完成后压缩整个 <code>bundle</code>。下面来看下它是如何工作的:</p><p>1.原始代码如下:</p><figure class="highlight javascript"><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 class="comment">// comments.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">'./comments.css'</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>2.Webpack 编译后的内容大概是下面这个样子:</p><figure class="highlight javascript"><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">// bundle.js (part of)</span></span><br><span class="line"><span class="meta">"use strict"</span>;</span><br><span class="line"><span class="built_in">Object</span>.defineProperty(__webpack_exports__, <span class="string">"__esModule"</span>, { <span class="attr">value</span>: <span class="literal">true</span> });</span><br><span class="line"><span class="comment">/* harmony export (immutable) */</span> __webpack_exports__[<span class="string">"render"</span>] = render;</span><br><span class="line"><span class="comment">/* harmony import */</span> <span class="keyword">var</span> __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(<span class="number">1</span>);</span><br><span class="line"><span class="comment">/* harmony import */</span> <span class="keyword">var</span> __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default =</span><br><span class="line">__webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>3.最小化之后的代码大概是下面这个样子:</p><figure class="highlight javascript"><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"><span class="comment">// minified bundle.js (part of)</span></span><br><span class="line"><span class="meta">"use strict"</span>;<span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params">e,n</span>)</span>{<span class="built_in">console</span>.log(<span class="string">"Rendered!"</span>)}</span><br><span class="line"><span class="built_in">Object</span>.defineProperty(n,<span class="string">"__esModule"</span>,{<span class="attr">value</span>:!<span class="number">0</span>}),n.render=t;<span class="keyword">var</span> o=r(<span class="number">1</span>);r.n(o)</span><br></pre></td></tr></table></figure><p>在 <strong>Webpack 4</strong> 中,bundle 级别的的最小化是自动开启的 - 同时在生产模式下、没有启用 bundle-level 都会开启。它是利用 <a href="https://github.com/mishoo/UglifyJS2" target="_blank" rel="noopener">UglifyJS</a> 引擎来进行最小化的。(如果你需要禁用最小化,仅仅设置开发模式或者设置 <code>optimization.minimize</code> 为 <code>false</code>。)</p><p>在 <strong>Webpack 3</strong> 中,你需要直接使用 <a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin" target="_blank" rel="noopener">UglifyJS</a> 插件。该插件是 webpack 提供的;开启并设置插件选项即可:</p><figure class="highlight javascript"><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 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"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.UglifyJsPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>Note: 在 webpack 3 中,UglifyJS 插件不能编译 ES2015+(ES6) 的代码,这就意味着你在代码中使用 <strong>classes</strong>, <strong>arrow function</strong> 或者其他新特性时,不能将他们编译成 ES5的代码,插件会抛错。<br>如果你需要编译这些新语法,就要用到 <a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin" target="_blank" rel="noopener">uglifyjs-webpack-plugin</a> package,他也是在 webpack 中捆绑一起的,但是版本更新,并且可以编译 ES2015+ 的代码。</p></blockquote><h4 id="Loader-specific-options-特定的-Loader-配置"><a href="#Loader-specific-options-特定的-Loader-配置" class="headerlink" title="Loader-specific options 特定的 Loader 配置"></a>Loader-specific options 特定的 Loader 配置</h4><p>最小化代码的第二步就是利用特定的 <a href="https://webpack.js.org/concepts/loaders/" target="_blank" rel="noopener">loader</a> 配置。配置这些 loader,你可以压缩那些不能被最小化的部分。举个例子,当你使用 <code>css-loader</code> 引入一个 css 文件时,文件会被编译成一个字符串:</p><figure class="highlight css"><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 class="comment">/* comments.css */</span></span><br><span class="line"><span class="selector-class">.comment</span> {</span><br><span class="line"> <span class="attribute">color</span>: black;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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"><span class="comment">// minified bundle.js (part of)</span></span><br><span class="line">exports=<span class="built_in">module</span>.exports=__webpack_require__(<span class="number">1</span>)(),</span><br><span class="line">exports.push([<span class="built_in">module</span>.i,<span class="string">".comment {\r\n color: black;\r\n}"</span>,<span class="string">""</span>]);</span><br></pre></td></tr></table></figure><p>这部分内容由于是字符串并没有被最小化。于是我们需要配置对应的 loader 选项来达到最小化的目的:</p><figure class="highlight javascript"><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="built_in">module</span>.exports = {</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: [</span><br><span class="line"> <span class="string">'style-loader'</span>,</span><br><span class="line"> { <span class="attr">loader</span>: <span class="string">'css-loader'</span>, <span class="attr">options</span>: { <span class="attr">minimize</span>: <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><br><span class="line">};</span><br></pre></td></tr></table></figure><h5 id="Further-reading"><a href="#Further-reading" class="headerlink" title="Further reading"></a>Further reading</h5><ul><li><a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin" target="_blank" rel="noopener">The UglifyJsPlugin docs</a> UglifyJs 插件文档</li><li>Other popular minifiers: <a href="https://github.com/webpack-contrib/babel-minify-webpack-plugin" target="_blank" rel="noopener">Babel Minify</a>, <a href="https://github.com/roman01la/webpack-closure-compiler" target="_blank" rel="noopener">Google Closure Compiler</a> 其他流行的最小化工具</li></ul><h3 id="Specify-NODE-ENV-production-明确生产环境信息"><a href="#Specify-NODE-ENV-production-明确生产环境信息" class="headerlink" title="Specify NODE_ENV=production 明确生产环境信息"></a>Specify <code>NODE_ENV=production</code> 明确生产环境信息</h3><blockquote><p>Note: 仅在 webpack 3 中生效,如果使用生产模式 webpack 4 打包,<strong>NODE_ENV=production</strong> 优化项已经开启,就可以直接跳过此小结</p></blockquote><p>减小前端体积的另外一个方法就是在代码中将 <code>NODE_ENV</code> <a href="https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them" target="_blank" rel="noopener">环境变量</a>设置为 <code>production</code> 。</p><p>Libraries 会读取 <code>NODE_ENV</code> 变量判断他们应该在那种模式下工作 - 开发模式 or 生成模式。很多库会基于这个变量有不同的表现。举个例子,当<code>NODE_ENV</code>没有设置成<code>production</code>,Vue.js 会做额外的检查并且输出一些警告:</p><figure class="highlight javascript"><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">// vue/dist/vue.runtime.esm.js</span></span><br><span class="line"><span class="comment">// …</span></span><br><span class="line"><span class="keyword">if</span> (process.env.NODE_ENV !== <span class="string">'production'</span>) {</span><br><span class="line"> warn(<span class="string">'props must be strings when using array syntax.'</span>);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// …</span></span><br></pre></td></tr></table></figure><p>React 也是类似 - 开发模式下 build 带有一些警告:</p><figure class="highlight javascript"><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">// react/index.js</span></span><br><span class="line"><span class="keyword">if</span> (process.env.NODE_ENV === <span class="string">'production'</span>) {</span><br><span class="line"> <span class="built_in">module</span>.exports = <span class="built_in">require</span>(<span class="string">'./cjs/react.production.min.js'</span>);</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">module</span>.exports = <span class="built_in">require</span>(<span class="string">'./cjs/react.development.js'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// react/cjs/react.development.js</span></span><br><span class="line"><span class="comment">// …</span></span><br><span class="line">warning$<span class="number">3</span>(</span><br><span class="line"> componentClass.getDefaultProps.isReactClassApproved,</span><br><span class="line"> <span class="string">'getDefaultProps is only used on classic React.createClass '</span> +</span><br><span class="line"> <span class="string">'definitions. Use a static property named `defaultProps` instead.'</span></span><br><span class="line">);</span><br><span class="line"><span class="comment">// …</span></span><br></pre></td></tr></table></figure><p>这些检查和警告通常在生产环境下是不必要的,但是他们仍然保留在代码中并且会增加库的体积。</p><p>在 <strong>Webpack 4</strong> 中增加 <code>optimization.nodeEnv: 'production'</code> 选项即可剔除掉它们:</p><figure class="highlight javascript"><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 (for webpack 4)</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> optimization: {</span><br><span class="line"> nodeEnv: <span class="string">'production'</span>,</span><br><span class="line"> minimize: <span class="literal">true</span>,</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>在 <strong>Webpack 3</strong> 中则使用 <a href="https://webpack.js.org/plugins/define-plugin/" target="_blank" rel="noopener"><code>DefinePlugin</code></a> :</p><figure class="highlight javascript"><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">// webpack.config.js (for webpack 3)</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"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.DefinePlugin({</span><br><span class="line"> <span class="string">'process.env.NODE_ENV'</span>: <span class="string">'"production"'</span>,</span><br><span class="line"> }),</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.UglifyJsPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p><code>optimization.nodeEnv: 'production'</code> 选项和 <code>DefinePlugin</code> 插件采用相同的方式来解决这个问题 - 这个方式就是他们将 <code>process.env.NODE_ENV</code> 替换成特定的值,下面的配置可以说明:</p><p>1.Webpack 会将所有 <code>process.env.NODE_ENV</code> 替换成 <code>"production"</code>:</p><figure class="highlight javascript"><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">// vue/dist/vue.runtime.esm.js</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">'string'</span>) {</span><br><span class="line"> name = camelize(val);</span><br><span class="line"> res[name] = { <span class="attr">type</span>: <span class="literal">null</span> };</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (process.env.NODE_ENV !== <span class="string">'production'</span>) {</span><br><span class="line"> warn(<span class="string">'props must be strings when using array syntax.'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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">// vue/dist/vue.runtime.esm.js</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">'string'</span>) {</span><br><span class="line"> name = camelize(val);</span><br><span class="line"> res[name] = { <span class="attr">type</span>: <span class="literal">null</span> };</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"production"</span> !== <span class="string">'production'</span>) {</span><br><span class="line"> warn(<span class="string">'props must be strings when using array syntax.'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>2.与此同时最小化工具会移除掉所有 <code>if</code> 的条件分支 - 由于 <code>"production" !== 'production'</code> 永远会返回 <code>false</code>,这样分支内的代码就永远不会执行:</p><figure class="highlight javascript"><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">// vue/dist/vue.runtime.esm.js</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">'string'</span>) {</span><br><span class="line"> name = camelize(val);</span><br><span class="line"> res[name] = { <span class="attr">type</span>: <span class="literal">null</span> };</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"production"</span> !== <span class="string">'production'</span>) {</span><br><span class="line"> warn(<span class="string">'props must be strings when using array syntax.'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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 class="comment">// vue/dist/vue.runtime.esm.js (without minification)</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">'string'</span>) {</span><br><span class="line"> name = camelize(val);</span><br><span class="line"> res[name] = { <span class="attr">type</span>: <span class="literal">null</span> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="Further-Reading-扩展阅读"><a href="#Further-Reading-扩展阅读" class="headerlink" title="Further Reading 扩展阅读"></a>Further Reading 扩展阅读</h5><ul><li><a href="https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them" target="_blank" rel="noopener">What “environment variables” are</a> 解释什么是环境变量</li><li>Webpack docs about: <a href="https://webpack.js.org/plugins/define-plugin/" target="_blank" rel="noopener"><code>DefinePlugin</code></a>, <a href="https://webpack.js.org/plugins/environment-plugin/" target="_blank" rel="noopener"><code>EnvironmentPlugin</code></a> Define 插件和 Environment 插件文档</li></ul><h3 id="Use-ES-Modules-使用-ES-模块"><a href="#Use-ES-Modules-使用-ES-模块" class="headerlink" title="Use ES Modules 使用 ES 模块"></a>Use ES Modules 使用 ES 模块</h3><p>下面这个方式利用 <a href="https://ponyfoo.com/articles/es6-modules-in-depth" target="_blank" rel="noopener">ES modules</a> 减小前端体积。</p><p>当你使用 ES module,webpack 有能力去做 <code>tree-shaking</code>。Tree-shaking 贯穿了整个依赖树,检查哪些依赖被使用,同时移除掉无用依赖。因此,如果你使用 ES module 方式的时候,webpack 帮你可以排除掉无用代码:</p><p>1.一个有多个 export 的文件,但是 app 只需要其中一个:</p><figure class="highlight javascript"><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">// comments.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> render = <span class="function"><span class="params">()</span> =></span> { <span class="keyword">return</span> <span class="string">'Rendered!'</span>; };</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> commentRestEndpoint = <span class="string">'/rest/comments'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">import</span> { render } <span class="keyword">from</span> <span class="string">'./comments.js'</span>;</span><br><span class="line">render();</span><br></pre></td></tr></table></figure><p>2.Webpack 分析 <code>commentRestEndPoint</code> 没有被用到,就不会在一个 bundle 中生成单独的 export:</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// bundle.js (part that corresponds to comments.js)</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, __webpack_exports__, __webpack_require__</span>) </span>{</span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> <span class="keyword">const</span> render = <span class="function"><span class="params">()</span> =></span> { <span class="keyword">return</span> <span class="string">'Rendered!'</span>; };</span><br><span class="line"> <span class="comment">/* harmony export (immutable) */</span> __webpack_exports__[<span class="string">"a"</span>] = render;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> commentRestEndpoint = <span class="string">'/rest/comments'</span>;</span><br><span class="line"> <span class="comment">/* unused harmony export commentRestEndpoint */</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>3.<a href="Enable minification 开启最小化">最小化工具</a>就会移除掉无用变量:</p><figure class="highlight javascript"><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"><span class="comment">// bundle.js (part that corresponds to comments.js)</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">n,e</span>)</span>{<span class="string">"use strict"</span>;<span class="keyword">var</span> r=<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{<span class="keyword">return</span><span class="string">"Rendered!"</span>};e.b=r})</span><br></pre></td></tr></table></figure><p>如果他们都是有 ES module 编写,就是与一些库并存时也是生效的。</p><blockquote><p>Note: 在 webpack 中,tree-shaking 没有 minifier 是无法生效的。 webpack 仅仅移除了没有被用到的 export 变量;<code>UglifyJSPlugin</code>才会移除无用代码。所以如果你编译打包时没有使用 minifier,打包后体积并不会更小。你也可以不一定使用这个插件。其他最小化的插件也支持移除 dead code(例如:<a href="https://github.com/webpack-contrib/babel-minify-webpack-plugin" target="_blank" rel="noopener">Babel Minify plugin</a> or <a href="https://github.com/roman01la/webpack-closure-compiler" target="_blank" rel="noopener">Google Closure Compiler plugin</a>)</p></blockquote><blockquote><p>Warning: 不要将 ES module 编译到 CommonJS 中。 如果你使用 Babel <code>babel-preset-env</code> or <code>babel-preset-es2015</code>,检查一下当前的配置。默认情况下, ES <code>import</code> and <code>export</code> to CommonJS <code>require</code> and <code>module.exports</code>。通过设置 option 来禁止掉<a href="https://github.com/babel/babel/tree/master/experimental/babel-preset-env" target="_blank" rel="noopener">Pass the <code>{ modules: false }</code> option</a>。</p></blockquote><h5 id="Futher-reading-扩展阅读"><a href="#Futher-reading-扩展阅读" class="headerlink" title="Futher reading 扩展阅读"></a>Futher reading 扩展阅读</h5><ul><li><a href="https://ponyfoo.com/articles/es6-modules-in-depth" target="_blank" rel="noopener">“ES6 Modules in depth”</a> 深入理解 ES6 Modules</li><li>Webpack docs <a href="https://webpack.js.org/guides/tree-shaking/" target="_blank" rel="noopener">about tree shaking</a> Webpack tree shaking 文档</li></ul><h3 id="Optimize-images-优化图片"><a href="#Optimize-images-优化图片" class="headerlink" title="Optimize images 优化图片"></a>Optimize images 优化图片</h3><p>图片基本会占局页面<a href="https://httparchive.org/reports/state-of-the-web?start=latest" target="_blank" rel="noopener">一半以上</a>体积。虽然它们不像 JavaScript 那么重要(比如它们不会阻止页面渲染),但图片仍然会占用掉一大部分带宽。可以利用 <code>url-loader</code>,<code>svg-url-loader</code> 和 <code>image-webpack-loader</code> 来进行优化。</p><p><code>url-loader</code> 允许将小的静态文件打包进 app。没有配置的话,他需要通过传递文件,将它放在编译后的打包 bundle 内并返回一个这个文件的 url。然而,如果我们注明 <code>limit</code> 选项,它将会编码成更小的文件 base64 url 并返回这个 url。这样将图片放在 Javascript 代码中,可以节省 HTTP 的请求:</p><figure class="highlight javascript"><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="built_in">module</span>.exports = {</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">/\.(jpe?g|png|gif)$/</span>,</span><br><span class="line"> loader: <span class="string">'url-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> <span class="comment">// Inline files smaller than 10 kB (10240 bytes)</span></span><br><span class="line"> limit: <span class="number">10</span> * <span class="number">1024</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><figure class="highlight javascript"><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">import</span> imageUrl <span class="keyword">from</span> <span class="string">'./image.png'</span>;</span><br><span class="line"><span class="comment">// → If image.png is smaller than 10 kB, `imageUrl` will include</span></span><br><span class="line"><span class="comment">// the encoded image: 'data:image/png;base64,iVBORw0KGg…'</span></span><br><span class="line"><span class="comment">// → If image.png is larger than 10 kB, the loader will create a new file,</span></span><br><span class="line"><span class="comment">// and `imageUrl` will include its url: `/2fcd56a1920be.png`</span></span><br></pre></td></tr></table></figure><blockquote><p>Note: 内联图片减少了独立请求的数量,这是很好的方式(<a href="https://blog.octo.com/en/http2-arrives-but-sprite-sets-aint-no-dead/" target="_blank" rel="noopener">even with HTTP/2</a>),但是会增加 bundle下载和转换的时间和内存的消耗。一定要确保不要嵌入超大图片或者较多的图片 - 否则增加的 bundle 的时间将会掩盖做成内联图片的收益。</p></blockquote><p><code>svg-url-loader</code>与<code>url-loader</code>类似 - 都是将使用 <a href="https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding" target="_blank" rel="noopener">URL encoding</a> encode 文件。这对对于 SVG 图片很奏效 - 因为 SVG 文件是文本,encoding 在体积上更有效率:</p><figure class="highlight javascript"><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></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 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">/\.svg$/</span>,</span><br><span class="line"> loader: <span class="string">'svg-url-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> <span class="comment">// Inline files smaller than 10 kB (10240 bytes)</span></span><br><span class="line"> limit: <span class="number">10</span> * <span class="number">1024</span>,</span><br><span class="line"> <span class="comment">// Remove the quotes from the url</span></span><br><span class="line"> <span class="comment">// (they’re unnecessary in most cases)</span></span><br><span class="line"> noquotes: <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><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>Note: svg-url-loader 拥有改善 IE 浏览器支持的 options,但是在其他浏览器中更糟糕。如果你需要兼容 IE 浏览器,<a href="https://github.com/bhovhannes/svg-url-loader#iesafe" target="_blank" rel="noopener">设置 iesafe: true 选项</a></p></blockquote><p><code>image-webpack-loader</code>压缩图片使之变小。它支持 JPG,PNG,GIF 和 SVG,因为我们将会使用它所有类型。</p><p>这个 loader 不会将图片嵌入在应用内,因此它必须与<code>url-loader</code>和<code>svg-url-loader</code>配合使用。避免复制粘贴到相同的 rules 中(一个用于 JPG/PNG/GIF 图片,另一个用于 SVG 图片),我们来使用<code>enforce: pre</code>作为单独的一个 rule 涵盖这个 loader:</p><figure class="highlight javascript"><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"><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 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">/\.(jpe?g|png|gif|svg)$/</span>,</span><br><span class="line"> loader: <span class="string">'image-webpack-loader'</span>,</span><br><span class="line"> <span class="comment">// This will apply the loader before the other ones</span></span><br><span class="line"> enforce: <span class="string">'pre'</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>默认 loader 设置就已经可以满足需求了 - 但如果你想要深入配置,请查看 <a href="https://github.com/tcoopman/image-webpack-loader#options" target="_blank" rel="noopener">the plugin options</a>。为了选择哪些 options 需要明确,可以查看 Addy Osmani 的 <a href="https://images.guide/" target="_blank" rel="noopener">guide on image optimization</a></p><h5 id="Further-reading-扩展阅读-1"><a href="#Further-reading-扩展阅读-1" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h5><ul><li><a href="https://stackoverflow.com/questions/201479/what-is-base-64-encoding-used-for" target="_blank" rel="noopener">“What is base64 encoding used for?”</a> Base64 编码究竟可以用在何处?</li><li>Addy Osmani’s <a href="https://images.guide/" target="_blank" rel="noopener">guide on image optimization</a> Addy Osmani 的图片优化指南</li></ul><h3 id="Optimize-dependencies-优化依赖"><a href="#Optimize-dependencies-优化依赖" class="headerlink" title="Optimize dependencies 优化依赖"></a>Optimize dependencies 优化依赖</h3><p>平均一半以上的 Javascript 体积大小来源于依赖包,并且这些可能都不是必要的。</p><p>举一个例子来说,Lodash(v4.17.4)增加了最小化代码的 72KB 大小到 bundle 中。但是如果你仅仅用到它的20个方法,大约 65 KB 代码没有用处。</p><p>另外一个例子就是 Moment.js。 V2.19.1版本最小化后有 223KB,体积巨大 - 截至2017年10月一个页面内的 Javascript 平均体积是 452KB。但是,本地文件的体积占 170KB。如果你没有用到 多语言版 Moment.js,这些文件都会没有目的地使 bundle 更臃肿。</p><p>所有这些依赖都可以被轻易优化。我们在 Github repo 收集了优化的建议,<a href="https://github.com/GoogleChromeLabs/webpack-libs-optimizations" target="_blank" rel="noopener">check it out</a>!</p><h3 id="Enable-module-concatenation-for-ES-modules-aka-scope-hoisting-为-ES-modles-开启模块连接"><a href="#Enable-module-concatenation-for-ES-modules-aka-scope-hoisting-为-ES-modles-开启模块连接" class="headerlink" title="Enable module concatenation for ES modules (aka scope hoisting) 为 ES modles 开启模块连接"></a>Enable module concatenation for ES modules (aka scope hoisting) 为 ES modles 开启模块连接</h3><blockquote><p>Note: 如果你在使用生产模式下的 webpack 4,modules concatention 已经开启,可以直接跳过本小节。</p></blockquote><p>当你构建 bundle 时,webpack 将每一个 module 封装进 function 中:</p><figure class="highlight javascript"><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">// index.js</span></span><br><span class="line"><span class="keyword">import</span> {render} <span class="keyword">from</span> <span class="string">'./comments.js'</span>;</span><br><span class="line">render();</span><br><span class="line"></span><br><span class="line"><span class="comment">// comments.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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">// bundle.js (part of)</span></span><br><span class="line"><span class="comment">/* 0 */</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, __webpack_exports__, __webpack_require__</span>) </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> <span class="built_in">Object</span>.defineProperty(__webpack_exports__, <span class="string">"__esModule"</span>, { <span class="attr">value</span>: <span class="literal">true</span> });</span><br><span class="line"> <span class="keyword">var</span> __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(<span class="number">1</span>);</span><br><span class="line"> <span class="built_in">Object</span>(__WEBPACK_IMPORTED_MODULE_0__comments_js__[<span class="string">"a"</span> <span class="comment">/* render */</span>])();</span><br><span class="line"></span><br><span class="line">}),</span><br><span class="line"><span class="comment">/* 1 */</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, __webpack_exports__, __webpack_require__</span>) </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> __webpack_exports__[<span class="string">"a"</span>] = render;</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>在以前,这么做是使 CommonJS/AMD modules 互相分离所必须的。但是,这会增加体积并且性能表现堪忧。</p><p>Webpack 2 介绍了 ES modules 的支持,不像 CommonJS 和 AMD modules 一样,而是能够不用将每一个 module 用 function 封装起来。同时 Webpack 3 利用<a href="https://webpack.js.org/plugins/module-concatenation-plugin/" target="_blank" rel="noopener"><code>ModuleConcatenationPlugin</code></a>完成这样一个 bundle,下面是例子:</p><figure class="highlight javascript"><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">// index.js</span></span><br><span class="line"><span class="keyword">import</span> {render} <span class="keyword">from</span> <span class="string">'./comments.js'</span>;</span><br><span class="line">render();</span><br><span class="line"></span><br><span class="line"><span class="comment">// comments.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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">// Unlike the previous snippet, this bundle has only one module</span></span><br><span class="line"><span class="comment">// which includes the code from both files</span></span><br><span class="line"><span class="comment">// 与前面的代码不同,这个 bundle 只有一个 module,同时包含两个文件</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// bundle.js (part of; compiled with ModuleConcatenationPlugin)</span></span><br><span class="line"><span class="comment">/* 0 */</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, __webpack_exports__, __webpack_require__</span>) </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> <span class="built_in">Object</span>.defineProperty(__webpack_exports__, <span class="string">"__esModule"</span>, { <span class="attr">value</span>: <span class="literal">true</span> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// CONCATENATED MODULE: ./comments.js</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// CONCATENATED MODULE: ./index.js</span></span><br><span class="line"> render();</span><br><span class="line"></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>看到区别了吗?在这个 bundle 中, module 0 需要 module 1 的 render 方法。使用 <code>ModuleConcatenationPlugin</code>,<code>require</code>被直接简单的替换成 require 函数,同时 module 1 被删除删除掉了。这个 bundle 拥有更少的 modules,就有更少的 modules 损耗!</p><p>在 <strong>Webpack 4</strong> 中开启这个功能,启用 <code>optimization.concatenateModules</code> 选项即可:</p><figure class="highlight javascript"><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">// webpack.config.js (for webpack 4)</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> optimization: {</span><br><span class="line"> concatenateModules: <span class="literal">true</span>,</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>在 <strong>webpack 3</strong> 中,使用 <code>ModuleConcatenationPlugin</code> 插件:</p><figure class="highlight javascript"><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 (for webpack 3)</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"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.ModuleConcatenationPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>Note:想要知道为什么这个功能不是默认启用?Concatenating modules 很棒, <a href="https://twitter.com/TheLarkInn/status/925800563144454144" target="_blank" rel="noopener">但是他会增加编译的时间同时破坏 module 的热更新</a>。这就是为什么只在生产环境中启用的原因了。</p></blockquote><h4 id="Further-reading-扩展阅读-2"><a href="#Further-reading-扩展阅读-2" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h4><ul><li>Webpack docs <a href="https://webpack.js.org/plugins/module-concatenation-plugin/" target="_blank" rel="noopener">for the ModuleConcatenationPlugin</a> ModuleConcatenationPlugin 文档</li><li><a href="https://medium.com/webpack/brief-introduction-to-scope-hoisting-in-webpack-8435084c171f" target="_blank" rel="noopener">“Brief introduction to scope hoisting”</a></li><li>Detailed description of <a href="https://medium.com/webpack/webpack-freelancing-log-book-week-5-7-4764be3266f5" target="_blank" rel="noopener">what this plugin does</a> 详述这些插件做了些什么</li></ul><h3 id="Use-externals-if-you-have-both-webpack-and-non-webpack-code-如果代码中包含-webpack-和非-webpack-的代码要使用-externals"><a href="#Use-externals-if-you-have-both-webpack-and-non-webpack-code-如果代码中包含-webpack-和非-webpack-的代码要使用-externals" class="headerlink" title="Use externals if you have both webpack and non-webpack code 如果代码中包含 webpack 和非 webpack 的代码要使用 externals"></a>Use <strong>externals</strong> if you have both webpack and non-webpack code 如果代码中包含 webpack 和非 webpack 的代码要使用 externals</h3><p>你可能拥有一个体积庞大的工程,其中一部分代码可以使用 webpack 编译,而有一些代码又不能。比如一个视频网站,播放器的 widget 可能通过 webpack 编译,但是其周围页面区域可能不是:</p><p><img src="https://img30.360buyimg.com/uba/jfs/t15334/148/2273118093/210074/8260a296/5a81599dN148751d5.png" alt="video-hosting"></p><p>如果两部分代码有相同的依赖,你可以共享这些依赖以便减少重复下载耗时。<a href="https://webpack.js.org/configuration/externals/" target="_blank" rel="noopener">the webpack’s <code>externals</code> option</a>就干了这件事 - 它用变量或者外部引用来替代 modules。</p><h4 id="如果依赖是挂载到-window-上的情况"><a href="#如果依赖是挂载到-window-上的情况" class="headerlink" title="如果依赖是挂载到 window 上的情况"></a>如果依赖是挂载到 window 上的情况</h4><p>如果你的非 webpack 代码依靠这些依赖,它们是挂载 window 上的变量,可以将依赖名称 alias 成变量名:</p><figure class="highlight javascript"><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 class="built_in">module</span>.exports = {</span><br><span class="line"> externals: {</span><br><span class="line"> <span class="string">'react'</span>: <span class="string">'React'</span>,</span><br><span class="line"> <span class="string">'react-dom'</span>: <span class="string">'ReactDOM'</span>,</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>利用这个配置,webpack 将不会打包 <code>react</code> 和 <code>react-dom</code> 包。取而代之,他们会被替换成下面这个样子:</p><figure class="highlight javascript"><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">// bundle.js (part of)</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, exports</span>) </span>{</span><br><span class="line"> <span class="comment">// A module that exports `window.React`. Without `externals`,</span></span><br><span class="line"> <span class="comment">// this module would include the whole React bundle</span></span><br><span class="line"> <span class="built_in">module</span>.exports = React;</span><br><span class="line">}),</span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, exports</span>) </span>{</span><br><span class="line"> <span class="comment">// A module that exports `window.ReactDOM`. Without `externals`,</span></span><br><span class="line"> <span class="comment">// this module would include the whole ReactDOM bundle</span></span><br><span class="line"> <span class="built_in">module</span>.exports = ReactDOM;</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="如果依赖是当做-AMD-包被加载的情况"><a href="#如果依赖是当做-AMD-包被加载的情况" class="headerlink" title="如果依赖是当做 AMD 包被加载的情况"></a>如果依赖是当做 AMD 包被加载的情况</h4><p>如果你的非 webpack 代码没有将依赖暴露挂载到 window 上,这就更复杂了。但是如果非 webpack 代码使用 AMD 包的形式消费了这些依赖,你仍然可以避免重复的代码加载两次。</p><p>具体如何做呢?将 webpack 代码编译成一个 AMD module 同时别名成一个库的 URLs:</p><figure class="highlight javascript"><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></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"> output: { <span class="attr">libraryTarget</span>: <span class="string">'amd'</span> },</span><br><span class="line"></span><br><span class="line"> externals: {</span><br><span class="line"> <span class="string">'react'</span>: { <span class="attr">amd</span>: <span class="string">'/libraries/react.min.js'</span> },</span><br><span class="line"> <span class="string">'react-dom'</span>: { <span class="attr">amd</span>: <span class="string">'/libraries/react-dom.min.js'</span> },</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>Webpack 将会把 bundle 包装进 <code>define()</code>同时让它依赖于这些URLs:</p><figure class="highlight javascript"><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"><span class="comment">// bundle.js (beginning)</span></span><br><span class="line">define([<span class="string">"/libraries/react.min.js"</span>, <span class="string">"/libraries/react-dom.min.js"</span>], <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{ … });</span><br></pre></td></tr></table></figure><p>如果非 webpack 代码使用相同的 URLs 加载依赖,这些文件将会加载一次 - 多余的请求会使用缓存。</p><blockquote><p>Note:webpack 只是替换那些 <code>externals</code> 对象中的准确匹配的 keys 的引用。这意味着如果你的代码这样写<code>import React from 'react/umd/react.production.min.js'</code>,这个库是不会被 bundle 排除掉的。这是因为 - webpack 并不知道 <code>import 'react'</code> 和 <code>import 'react/umd/react.production.min.js'</code> 是同一个库,这样比较谨慎。</p></blockquote><h5 id="Further-reading-扩展阅读-3"><a href="#Further-reading-扩展阅读-3" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h5><ul><li>Webpack docs <a href="https://webpack.js.org/configuration/externals/" target="_blank" rel="noopener">on <code>externals</code></a> externals 文档</li></ul><h3 id="Summing-up-总结"><a href="#Summing-up-总结" class="headerlink" title="Summing up 总结"></a>Summing up 总结</h3><ul><li>Enable the production mode if you use webpack 4 如果使用 webpack 4 开启生产模式</li><li>Minimize your code with the bundle-level minifier and loader options 使用 bundle 级别最小化 和 loader 选项来最小化你的代码</li><li>Remove the development-only code by replacing <code>NODE_ENV</code> with <code>production</code> 通过将 <code>NODE_ENV</code> 替换成 <code>production</code> 来移除开发期间代码</li><li>Use ES modules to enable tree shaking 启用 tree shaking</li><li>Compress images 压缩图片</li><li>Apply dependency-specific optimizations 开启依赖优化</li><li>Enable module concatenation 开启 module 连接</li><li>Use <code>externals</code> if this makes sense for you 如果有效果的话可以使用 <code>externals</code></li></ul><hr><h2 id="Make-use-of-long-term-caching-利用好长时缓存"><a href="#Make-use-of-long-term-caching-利用好长时缓存" class="headerlink" title="Make use of long-term caching 利用好长时缓存"></a>Make use of long-term caching 利用好长时缓存</h2><p>作者 <a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p><p>在做完优化应用体积之后的下一步提升应用加载时间的就是缓存。在客户端中使用缓存作为应用的一部分,这样会在每一次请求中减少重新下载的次数。</p><h3 id="Use-bundle-versioning-and-cache-headers-使用-bundle-版本和缓存头信息"><a href="#Use-bundle-versioning-and-cache-headers-使用-bundle-版本和缓存头信息" class="headerlink" title="Use bundle versioning and cache headers 使用 bundle 版本和缓存头信息"></a>Use bundle versioning and cache headers 使用 bundle 版本和缓存头信息</h3><p>做缓存通用的解决办法:</p><p>1.告诉浏览器缓存一个文件很长时间(比如一年)</p><figure class="highlight routeros"><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"><span class="comment"># Server header</span></span><br><span class="line">Cache-Control: <span class="attribute">max-age</span>=31536000</span><br></pre></td></tr></table></figure><p>Note:如果你不熟悉 <code>Cache-Control</code> 做了什么,你可以看一下 Jake Archibald 的精彩博文 <a href="https://jakearchibald.com/2016/caching-best-practices/" target="_blank" rel="noopener">on caching best practices</a></p><p>2.当文件改变需要强制重新下载时去重命名这些文件</p><figure class="highlight html"><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 class="comment"><!-- Before the change --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./index-v15.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- After the change --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./index-v16.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p>这些方法可以告诉浏览器下载这些 JS 文件,将其缓存起来。浏览器将只会在文件名发生改变时才会请求网络(或者缓存失效的情况也会请求)。</p><p>使用 webpack,也可以做同样的事,但可以使用版本号来解决,需要明确这个文件的 hash 值。使用 <a href="https://webpack.js.org/configuration/output/#output-filename" target="_blank" rel="noopener"><code>[chunkhash]</code></a> 可以将 <code>hash</code> 值包含进文件名中:</p><figure class="highlight javascript"><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 class="built_in">module</span>.exports = {</span><br><span class="line"> entry: <span class="string">'./index.js'</span>,</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">'bundle.[chunkhash].js'</span>,</span><br><span class="line"> <span class="comment">// → bundle.8e0d62a03.js</span></span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>Note: webpack 可能会生成不同的 hash 即使 bundle 相同 - 比如你重名了了一个文件或者重新在不同的操作系统下编译了一个 bundle。 <a href="https://github.com/webpack/webpack/issues/1479" target="_blank" rel="noopener">This is a bug.</a><br>如果你需要将文件名发送给客户端,也可以使用 <code>HtmlWebpackPlugin</code> 或者 <code>WebpackManifestPlugin</code>。</p></blockquote><p><a href="https://github.com/jantimon/html-webpack-plugin" target="_blank" rel="noopener"><code>HtmlWebpackPlugin</code></a> 使用起来很简单,但灵活性有一些欠缺。编译时,插件会生成一个 HTML 文件,这其中包括所有的编译后的资源文件。如果你的业务逻辑不复杂,这就非常适合你:</p><figure class="highlight html"><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 class="comment"><!-- index.html --></span></span><br><span class="line"><span class="meta"><!doctype html></span></span><br><span class="line"><span class="comment"><!-- ... --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"bundle.8e0d62a03.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p><code>WebpackManifestPlugin</code> 更灵活一些,它可以帮助你解决业务负责的部分。编译时它会生成一个 JSON 文件,这文件保存这没有 hash 值文件与有 hash 文件之间的映射。服务端利用这个 JSON 可以识别出那个文件有效:</p><figure class="highlight json"><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">// manifest.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"bundle.js"</span>: <span class="string">"bundle.8e0d62a03.js"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="Further-reading-扩展阅读-4"><a href="#Further-reading-扩展阅读-4" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h5><ul><li>Jake Archibald <a href="https://jakearchibald.com/2016/caching-best-practices/" target="_blank" rel="noopener">about caching best practices</a> cache 的最佳实践</li></ul><h3 id="Extract-dependencies-and-runtime-into-a-separate-file-将依赖和运行环境代码提取到一个单独的文件"><a href="#Extract-dependencies-and-runtime-into-a-separate-file-将依赖和运行环境代码提取到一个单独的文件" class="headerlink" title="Extract dependencies and runtime into a separate file 将依赖和运行环境代码提取到一个单独的文件"></a>Extract dependencies and runtime into a separate file 将依赖和运行环境代码提取到一个单独的文件</h3><h4 id="Dependencies-依赖"><a href="#Dependencies-依赖" class="headerlink" title="Dependencies 依赖"></a>Dependencies 依赖</h4><p>App 依赖通常情况下趋向于比实际 app 内代码中更少的变化。如果你将他们移到独立的文件中,浏览器将可以把他们独立缓存起来 - 同时不会每次 app 代码改变时重新下载。</p><blockquote><p>Key Term: 在 webpack 的技术中,利用 app 代码拆分文件被称为 <code>chunks</code>。我们后面会用到这个名词。</p></blockquote><p>为了将依赖包提取到单独的 chunk 中,下面分为三步:</p><p>1.使用 <code>[name].[chunkname].js</code> 替换<code>output</code>的文件名:</p><figure class="highlight javascript"><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></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"> output: {</span><br><span class="line"> <span class="comment">// Before</span></span><br><span class="line"> filename: <span class="string">'bundle.[chunkhash].js'</span>,</span><br><span class="line"> <span class="comment">// After</span></span><br><span class="line"> filename: <span class="string">'[name].[chunkhash].js'</span>,</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>当 webpack 构建应用时,它会用一个带有 chunk 的名称来替换 <code>[name]</code>。如果没有添加 <code>[name]</code> 部分,我们不得不通过 chunks 之间的 hash 区别来比较他们的区别 - 那就太困难了!</p><p>2.将 <code>entry</code> 转成一个对象:</p><figure class="highlight javascript"><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></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 class="comment">// Before</span></span><br><span class="line"> entry: <span class="string">'./index.js'</span>,</span><br><span class="line"> <span class="comment">// After</span></span><br><span class="line"> entry: {</span><br><span class="line"> main: <span class="string">'./index.js'</span>,</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>在这段代码中,”main” 对象是一个 chunk 的名字。这个名字将会被步骤 1 里面的 <code>[name]</code>代替。</p><p>目前为止,如果你构建一个 app,chunk 就会包括整个 app 的代码 - 就像我们没有做这些步骤一样。但是很快就会产生变化。</p><p>3.在 <strong>Webpack 4</strong> 中,在配置中增加 <code>optimization.splitChunks.chunks: 'all'</code> 即可:</p><figure class="highlight javascript"><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 (for webpack 4)</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</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><span class="line">};</span><br></pre></td></tr></table></figure><p>这个选项会开启智能代码拆分。使用这个功能,webpack 将最小化和 Gzip 前大于 30KB 的代码提取出额外的 <code>vendor</code> 代码。它同时也会提取出 common 代码 - 这些代码在打包多个 bundles 会起到作用。(例如:<a href="#Split the code into routes and pages 拆分代码到路由和页面中">通过路由拆分应用</a>)。</p><p>在 <strong>Webpack 3</strong> 中,使用 <a href="https://webpack.js.org/plugins/commons-chunk-plugin/" target="_blank" rel="noopener"><code>CommonsChunkPlugin</code></a> 插件:</p><figure class="highlight javascript"><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 (for webpack 3)</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> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> <span class="comment">// A name of the chunk that will include the dependencies.</span></span><br><span class="line"> <span class="comment">// This name is substituted in place of [name] from step 1</span></span><br><span class="line"> name: <span class="string">'vendor'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// A function that determines which modules to include into this chunk</span></span><br><span class="line"> minChunks: <span class="function"><span class="params">module</span> =></span> <span class="built_in">module</span>.context &&</span><br><span class="line"> <span class="built_in">module</span>.context.includes(<span class="string">'node_modules'</span>),</span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>插件将包括全部 <code>node_modules</code> 路径下的 modules 同时将他们移到一个单独的文件中,这个文件被称为 <code>vendor.[chunkhash].js</code>。</p><p>完成了上面的步骤,每一次 build 都会生成两个文件。浏览器会将他们单独缓存 - 以便代码发生改变时重新下载。</p><figure class="highlight bash"><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">$ webpack</span><br><span class="line">Hash: ac01483e8fec1fa70676</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 3816ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main</span><br><span class="line">./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor</span><br></pre></td></tr></table></figure><h4 id="Webpack-runtime-code-运行时代码"><a href="#Webpack-runtime-code-运行时代码" class="headerlink" title="Webpack runtime code 运行时代码"></a>Webpack runtime code 运行时代码</h4><p>不幸的是,仅仅抽取 <code>vendor</code> 是不够的。如果你试图在应用代码中修改一些东西:</p><figure class="highlight javascript"><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><br><span class="line">…</span><br><span class="line"></span><br><span class="line"><span class="comment">// E.g. add this:</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'Wat'</span>);</span><br></pre></td></tr></table></figure><p>你会注意到 <code>vendor</code> 的也会改变:</p><figure class="highlight bash"><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"> Asset Size Chunks Chunk Names</span><br><span class="line">./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight bash"><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"> Asset Size Chunks Chunk Names</span><br><span class="line">./vendor.e6ea4504d61a1cc1c60b.js 47 kB 1 [emitted] vendor</span><br></pre></td></tr></table></figure><p>这因为 webpack 打包时,一部分 modules 的代码,拥有 <a href="https://webpack.js.org/concepts/manifest/" target="_blank" rel="noopener"><em>a runtime</em></a> - 管理模块执行一部分代码。当你将代码拆分成多个文件时,这小部分代码在 chunk ids 和 匹配的文件之间开始了一个映射:</p><figure class="highlight javascript"><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 class="comment">// vendor.e6ea4504d61a1cc1c60b.js</span></span><br><span class="line">script.src = __webpack_require__.p + chunkId + <span class="string">"."</span> + {</span><br><span class="line"> <span class="string">"0"</span>: <span class="string">"2f2269c7f0a55a5c1871"</span></span><br><span class="line">}[chunkId] + <span class="string">".js"</span>;</span><br></pre></td></tr></table></figure><p>Webpack 将最新生成的 chunk 包含在这个 runtime 内,这个 chunk 就是我们代码中的 <code>vendor</code>。与此同时每一次任何 <code>chunk</code> 的修改,即使这一小部分代码也改变,也会导致整个 <code>vendor</code> <code>chunk</code> 改变。</p><p>为了解决这个问题,我们将 runtime 转义到一个独立的文件中,在 <strong>Webpack 4</strong> 中,开启 <code>optimization.runtimeChunk</code> 选项:</p><figure class="highlight javascript"><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">// webpack.config.js (for webpack 4)</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> optimization: {</span><br><span class="line"> runtimeChunk: <span class="literal">true</span>,</span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 <strong>Webpack 3</strong>中,通过 <code>CommonsChunkPlugin</code> 创建一个额外的空的 chunk:</p><figure class="highlight javascript"><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="comment">// webpack.config.js (for webpack 3)</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> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> name: <span class="string">'vendor'</span>,</span><br><span class="line"></span><br><span class="line"> minChunks: <span class="function"><span class="params">module</span> =></span> <span class="built_in">module</span>.context &&</span><br><span class="line"> <span class="built_in">module</span>.context.includes(<span class="string">'node_modules'</span>),</span><br><span class="line"> }),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// This plugin must come after the vendor one (because webpack</span></span><br><span class="line"> <span class="comment">// includes runtime into the last chunk)</span></span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> name: <span class="string">'runtime'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// minChunks: Infinity means that no app modules</span></span><br><span class="line"> <span class="comment">// will be included into this chunk</span></span><br><span class="line"> minChunks: <span class="literal">Infinity</span>,</span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>完成这一部分改变,每一次 build 都将生成三个文件:</p><figure class="highlight bash"><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">$ webpack</span><br><span class="line">Hash: ac01483e8fec1fa70676</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 3816ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main</span><br><span class="line"> ./vendor.26886caf15818fa82dfa.js 46 kB 1 [emitted] vendor</span><br><span class="line">./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime</span><br></pre></td></tr></table></figure><p>将他们反过来顺序添加到 index.html 中,你就搞定了:</p><figure class="highlight html"><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 class="comment"><!-- index.html --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./runtime.79f17c27b335abc7aaf4.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./vendor.26886caf15818fa82dfa.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./main.00bab6fd3100008a42b0.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><h5 id="Further-reading-扩展阅读-5"><a href="#Further-reading-扩展阅读-5" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h5><ul><li>Webpack guide <a href="https://webpack.js.org/guides/caching/" target="_blank" rel="noopener">on long term caching</a> webpack 关于 cache 指南</li><li>Webpack docs <a href="https://webpack.js.org/concepts/manifest/" target="_blank" rel="noopener">about webpack runtime and manifest</a> webpack 关于 runtime and manifest 文档</li><li><a href="https://medium.com/webpack/webpack-bits-getting-the-most-out-of-the-commonschunkplugin-ab389e5f318" target="_blank" rel="noopener">“Getting the most out of the CommonsChunkPlugin”</a> 使用 CommonsChunkPlugin 的最佳实践</li></ul><h3 id="Inline-webpack-runtime-to-save-an-extra-HTTP-request-内联-webpack-runtime-节省额外的-HTTP-请求"><a href="#Inline-webpack-runtime-to-save-an-extra-HTTP-request-内联-webpack-runtime-节省额外的-HTTP-请求" class="headerlink" title="Inline webpack runtime to save an extra HTTP request 内联 webpack runtime 节省额外的 HTTP 请求"></a>Inline webpack runtime to save an extra HTTP request 内联 webpack runtime 节省额外的 HTTP 请求</h3><p>为了做的更好,我们可以尽力把 webpack runtime 内联在 HTML 请求里。下面举例:</p><figure class="highlight html"><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"><span class="comment"><!-- index.html --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./runtime.79f17c27b335abc7aaf4.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p>这样做:</p><figure class="highlight html"><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 class="comment"><!-- index.html --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></span><br><span class="line"><span class="actionscript">!<span class="function"><span class="keyword">function</span><span class="params">(e)</span></span>{<span class="function"><span class="keyword">function</span> <span class="title">n</span><span class="params">(r)</span></span>{<span class="keyword">if</span>(t[r])<span class="keyword">return</span> t[r].exports;…}} ([]);</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p>这个 runtime 很小,内联它可以帮助你节省 HTTP 请求(尤其对 HTTP/1 重要;但是在 HTTP/2 就没有那么重要了,但是仍能够提高效率)。</p><p>下面就来看看如何做。</p><h4 id="如果使用-HtmlWebpackPlugin-来生成-HTML"><a href="#如果使用-HtmlWebpackPlugin-来生成-HTML" class="headerlink" title="如果使用 HtmlWebpackPlugin 来生成 HTML"></a>如果使用 HtmlWebpackPlugin 来生成 HTML</h4><p>如果使用 <a href="https://github.com/jantimon/html-webpack-plugin" target="_blank" rel="noopener"><code>HtmlWebpackPlugin</code></a> 来生成 HTML 文件,<a href="https://github.com/rohitlodha/html-webpack-inline-chunk-plugin" target="_blank" rel="noopener"><code>InlineChunkWebpackPlugin</code></a> 就足够了。</p><h4 id="如果使用自己的定制服务逻辑来生成-HTML"><a href="#如果使用自己的定制服务逻辑来生成-HTML" class="headerlink" title="如果使用自己的定制服务逻辑来生成 HTML"></a>如果使用自己的定制服务逻辑来生成 HTML</h4><h5 id="Webpack-4"><a href="#Webpack-4" class="headerlink" title="Webpack 4"></a><strong>Webpack 4</strong></h5><p>1.增加 <a href="https://github.com/danethurber/webpack-manifest-plugin" target="_blank" rel="noopener">WebpackManifestPlugin</a> 插件已知运行时 chunk:</p><figure class="highlight javascript"><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 (for webpack 4)</span></span><br><span class="line"><span class="keyword">const</span> ManifestPlugin = <span class="built_in">require</span>(<span class="string">'webpack-manifest-plugin'</span>);</span><br><span class="line"></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> ManifestPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>插件就会生成一个下面这样的文件:</p><figure class="highlight json"><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">// manifest.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"runtime~main.js"</span>: <span class="string">"runtime~main.8e0d62a03.js"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>2.将这些内容嵌入到 runtime chunk 中。例如:使用 Node.js 和 Express:</p><figure class="highlight javascript"><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"><span class="comment">// server.js</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> manifest = <span class="built_in">require</span>(<span class="string">'./manifest.json'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> runtimeContent = fs.readFileSync(manifest[<span class="string">'runtime~main.js'</span>], <span class="string">'utf-8'</span>);</span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/'</span>, (req, res) => {</span><br><span class="line"> res.send(<span class="string">`</span></span><br><span class="line"><span class="string"> …</span></span><br><span class="line"><span class="string"> <script><span class="subst">${runtimeContent}</span></script></span></span><br><span class="line"><span class="string"> …</span></span><br><span class="line"><span class="string"> `</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h5 id="Webpack-3"><a href="#Webpack-3" class="headerlink" title="Webpack 3"></a><strong>Webpack 3</strong></h5><p>1.将 <code>runtime</code> 名称改成静态的明确的文件名:</p><figure class="highlight javascript"><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 (for webpack 3)</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> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> name: <span class="string">'runtime'</span>,</span><br><span class="line"> minChunks: <span class="literal">Infinity</span>,</span><br><span class="line"> filename: <span class="string">'runtime.js'</span>,</span><br><span class="line"> <span class="comment">// → Now the runtime file will be called</span></span><br><span class="line"> <span class="comment">// “runtime.js”, not “runtime.79f17c27b335abc7aaf4.js”</span></span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>2.嵌入到 runtime.js 内容。比如:Node.js 和 Express</p><figure class="highlight javascript"><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">// server.js</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> runtimeContent = fs.readFileSync(<span class="string">'./runtime.js'</span>, <span class="string">'utf-8'</span>);</span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/'</span>, (req, res) => {</span><br><span class="line"> res.send(<span class="string">`</span></span><br><span class="line"><span class="string"> …</span></span><br><span class="line"><span class="string"> <script><span class="subst">${runtimeContent}</span></script></span></span><br><span class="line"><span class="string"> …</span></span><br><span class="line"><span class="string"> `</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="Lazy-load-code-that-you-don’t-need-right-now-懒加载"><a href="#Lazy-load-code-that-you-don’t-need-right-now-懒加载" class="headerlink" title="Lazy-load code that you don’t need right now 懒加载"></a>Lazy-load code that you don’t need right now 懒加载</h3><p>通常情况下,一个页面有或多或少的重要部分:</p><ul><li>如果你在 YouTube 上加载一个视频页面,相比评论区域你更在乎视频区域。这就是视频要比评论区域重要。</li><li>如果你在一个新闻网站打开一个报道,相比广告区域你更关心文章的内容。这就是文字比广告更重要。</li></ul><p>在这些案例中,通过仅下载最重要的部分,懒加载剩余区域能够提升最初的加载性能。使用 <a href="https://webpack.js.org/api/module-methods/#import-" target="_blank" rel="noopener">the <code>import()</code> function</a> 和 <a href="https://webpack.js.org/guides/code-splitting/" target="_blank" rel="noopener">code-splitting</a> 解决这个问题:</p><figure class="highlight javascript"><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">// videoPlayer.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">renderVideoPlayer</span>(<span class="params"></span>) </span>{ … }</span><br><span class="line"></span><br><span class="line"><span class="comment">// comments.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">renderComments</span>(<span class="params"></span>) </span>{ … }</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">import</span> {renderVideoPlayer} <span class="keyword">from</span> <span class="string">'./videoPlayer'</span>;</span><br><span class="line">renderVideoPlayer();</span><br><span class="line"></span><br><span class="line"><span class="comment">// …Custom event listener</span></span><br><span class="line">onShowCommentsClick(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">import</span>(<span class="string">'./comments'</span>).then(<span class="function">(<span class="params">comments</span>) =></span> {</span><br><span class="line"> comments.renderComments();</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p><code>import()</code>明确表示你期望动态地加载独立的 module。当 webpack 看到 <code>import('./module.js')</code>时,他就会将这个 module 移到独立的 chunk 中:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">$ webpack</span><br><span class="line">Hash: 39b2a53cb4e73f0dc5b2</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 4273ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]</span><br><span class="line"> ./main.f7e53d8e13e9a2745d6d.js 60 kB 1 [emitted] main</span><br><span class="line"> ./vendor.4f14b6326a80f4752a98.js 46 kB 2 [emitted] vendor</span><br><span class="line">./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime</span><br></pre></td></tr></table></figure><p>并且只在代码执行到 <code>import()</code> 才会下载。</p><p>这将会让 main bundle 更小,提升初始加载的时间。更重要的是改进缓存 - 如果你修改 main chunk 的代码,其他部分的 chunk 也不会受影响。</p><blockquote><p>Note: 如果使用 Babel 编译代码,你会因为 Babel 还不认识 <em>import()</em> 而遇到语法错误抛出来。可以使用 <a href="https://www.npmjs.com/package/babel-plugin-syntax-dynamic-import" target="_blank" rel="noopener"><code>syntax-dynamic-import</code></a> 解决这个错误。</p></blockquote><h5 id="Further-reading-扩展阅读-6"><a href="#Further-reading-扩展阅读-6" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h5><ul><li>Webpack docs <a href="https://webpack.js.org/api/module-methods/#import-" target="_blank" rel="noopener">for the <code>import()</code> function</a> webpack 中 import() 文档</li><li>The JavaScript proposal <a href="https://github.com/tc39/proposal-dynamic-import" target="_blank" rel="noopener">for implementing the <code>import()</code> syntax</a></li></ul><h3 id="Split-the-code-into-routes-and-pages-拆分代码到路由和页面中"><a href="#Split-the-code-into-routes-and-pages-拆分代码到路由和页面中" class="headerlink" title="Split the code into routes and pages 拆分代码到路由和页面中"></a>Split the code into routes and pages 拆分代码到路由和页面中</h3><p>如果你的应用拥有多个路由或者页面,但是代码中只有单独一个 JS 文件(一个单独的 main chunk),这看起来你正在每一个请求中节省额外的 bytes 带宽。举个例子,当用户正在访问你网站的首页:</p><p><img src="https://img10.360buyimg.com/uba/jfs/t17272/291/466883786/44644/f5b82d7c/5a8159b0N4fe9f50d.png" alt="site-home-page"></p><p>他们并不需要加载另外不同的页面上渲染文章标题的的代码 - 但是他们还是会加载到这段代码。更严重的是如果用户经常只访问首页,同时你还经常改变渲染文章标题的代码,webpack 将会对整个 bundle 失效 - 用户每次都会重复下载全部 app 的代码。</p><p>如果我们将代码拆分到页面里(或者单页面应用的路由里),用户就会只下载对他有意义的代码。更好的是,浏览器也会更好地缓存代码:当你改变首页的代码时,webpack 只会让相匹配的 chunk 失效。</p><h4 id="For-single-page-apps-对于单页面应用"><a href="#For-single-page-apps-对于单页面应用" class="headerlink" title="For single-page apps 对于单页面应用"></a>For single-page apps 对于单页面应用</h4><p>通过路由拆分带页面引用,使用 <code>import()</code>(看看 <a href="https://developers.google.com/web/fundamentals/performance/webpack/use-long-term-caching#lazy-loading" target="_blank" rel="noopener">“Lazy-load code that you don’t need right now”</a>这部分)。如果你在使用一个框架,现在已经有成熟的方案:</p><ul><li><a href="https://reacttraining.com/react-router/web/guides/code-splitting" target="_blank" rel="noopener">“Code Splitting”</a> in <code>react-router</code>‘s docs (for React)</li><li><a href="https://router.vuejs.org/en/advanced/lazy-loading.html" target="_blank" rel="noopener">“Lazy Loading Routes”</a> in <code>vue-router</code>‘s docs (for Vue.js)</li></ul><h4 id="For-traditional-multi-page-apps-对于传统的多页面应用"><a href="#For-traditional-multi-page-apps-对于传统的多页面应用" class="headerlink" title="For traditional multi-page apps 对于传统的多页面应用"></a>For traditional multi-page apps 对于传统的多页面应用</h4><p>通过页面拆分传统多页面应用,可以使用 webpack 的 <a href="https://webpack.js.org/concepts/entry-points/" target="_blank" rel="noopener"><em>entry points</em></a> 。如果你的应用有三种页面:主页、文章页、用户账户页,那就分厂三个 entries:</p><figure class="highlight javascript"><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 class="built_in">module</span>.exports = {</span><br><span class="line"> entry: {</span><br><span class="line"> home: <span class="string">'./src/Home/index.js'</span>,</span><br><span class="line"> article: <span class="string">'./src/Article/index.js'</span>,</span><br><span class="line"> profile: <span class="string">'./src/Profile/index.js'</span></span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>对于每一个 entry 文件,webpack 将构建出独立的依赖树,并且声称一个 bundle,它将通过 entry 来只包括用到的 modules:</p><figure class="highlight bash"><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">$ webpack</span><br><span class="line">Hash: 318d7b8490a7382bf23b</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 4273ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]</span><br><span class="line"> ./home.91b9ed27366fe7e33d6a.js 18 kB 1 [emitted] home</span><br><span class="line">./article.87a128755b16ac3294fd.js 32 kB 2 [emitted] article</span><br><span class="line">./profile.de945dc02685f6166781.js 24 kB 3 [emitted] profile</span><br><span class="line"> ./vendor.4f14b6326a80f4752a98.js 46 kB 4 [emitted] vendor</span><br><span class="line">./runtime.318d7b8490a7382bf23b.js 1.45 kB 5 [emitted] runtime</span><br></pre></td></tr></table></figure><p>因此,如果仅仅是文章页使用 <em>Lodash</em> ,<em>home</em> 和 <em>profile</em> 的 bundle 将不会包含 lodash - 同时用户也不会在访问首页的时候下载到这个库。</p><p>拆分依赖树也有缺点。如果两个 entry points 都用到了 <em>loadash</em> ,同时你没有在 <em>vendor</em> 移除掉依赖,两个 entry points 将包括两个重复的 <em>lodash</em> 。在 <strong>Webpack 4</strong> 中我们可以设置 <code>optimization.splitChunks.chunks: 'all'</code> 解决该问题:</p><figure class="highlight javascript"><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 (for webpack 4)</span></span><br><span class="line"><span class="built_in">module</span>.exports = {</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><span class="line">};</span><br></pre></td></tr></table></figure><p>这个选项可以开启智能拆分代码,webpack 将自动寻找 common code 并将其提取到一个单独的文件中。</p><p>在 <strong>Webpack 3</strong> 可以使用<a href="https://webpack.js.org/plugins/commons-chunk-plugin/" target="_blank" rel="noopener"><code>CommonsChunkPlugin</code></a>来解决这个问题 - 它会将通用的依赖转移到一个独立的文件中:</p><figure class="highlight javascript"><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 (for webpack 3)</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> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> <span class="comment">// A name of the chunk that will include the common dependencies</span></span><br><span class="line"> name: <span class="string">'common'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The plugin will move a module into a common file</span></span><br><span class="line"> <span class="comment">// only if it’s included into `minChunks` chunks</span></span><br><span class="line"> <span class="comment">// (Note that the plugin analyzes all chunks, not only entries)</span></span><br><span class="line"> minChunks: <span class="number">2</span>, <span class="comment">// 2 is the default value</span></span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>随意使用<code>minChunks</code>的值来找到最优的选项。通常情况下,你想要它尽可能体积小,但它会增加 chunks 的数量。举个例子,3 个 chunk,<code>minChunks</code> 可能是 2 个,但是 30 个 chunk,它可能是 8 个 - 因为如果你把它设置成 2 ,过多的 modules 将会打包进一个通用文件中,文件更臃肿。</p><h4 id="Further-reading-扩展阅读-7"><a href="#Further-reading-扩展阅读-7" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h4><ul><li>Webpack docs <a href="https://webpack.js.org/concepts/entry-points/" target="_blank" rel="noopener">about the concept of entry points</a> webpack 关于入口概念的文档</li><li>Webpack docs <a href="https://webpack.js.org/plugins/commons-chunk-plugin/" target="_blank" rel="noopener">about the CommonsChunkPlugin</a> webpack 关于 CommonChunkPlugin 插件的文档</li><li><a href="https://medium.com/webpack/webpack-bits-getting-the-most-out-of-the-commonschunkplugin-ab389e5f318" target="_blank" rel="noopener">“Getting the most out of the CommonsChunkPlugin”</a></li></ul><h3 id="Make-module-ids-more-stable-使用稳定的-module-ids"><a href="#Make-module-ids-more-stable-使用稳定的-module-ids" class="headerlink" title="Make module ids more stable 使用稳定的 module ids"></a>Make module ids more stable 使用稳定的 module ids</h3><p>当编译代码时,webpack 会分配给每一个 module 一个 ID。之后,这些 ID 就会被 <code>require()</code> 引用到 bundle 内部。你可以在编译输出的右侧在 moudle 路径之前看到这些 ID:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">$ webpack</span><br><span class="line">Hash: df3474e4f76528e3bbc9</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 2150ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]</span><br><span class="line"> ./main.4e50a16675574df6a9e9.js 60 kB 1 [emitted] main</span><br><span class="line"> ./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor</span><br><span class="line">./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight bash"><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">[0] ./index.js 29 kB {1} [built]</span><br><span class="line">[2] (webpack)/buildin/global.js 488 bytes {2} [built]</span><br><span class="line">[3] (webpack)/buildin/module.js 495 bytes {2} [built]</span><br><span class="line">[4] ./comments.js 58 kB {0} [built]</span><br><span class="line">[5] ./ads.js 74 kB {1} [built]</span><br><span class="line"> + 1 hidden module</span><br></pre></td></tr></table></figure><p>默认情况下,这些 ID 是使用计数器计算出来的(比如第一个 module 是 ID 0,第二个 moudle 就是 ID 1,以此类推)。这样的问题就在于当你新增一个 module 事,它会出现在原来 module 列表中的中间,改变后面所有 module 的 ID:</p><figure class="highlight bash"><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">$ webpack</span><br><span class="line">Hash: df3474e4f76528e3bbc9</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 2150ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.5c82c0f337fcb22672b5.js 22 kB 0 [emitted]</span><br><span class="line"> ./main.0c8b617dfc40c2827ae3.js 82 kB 1 [emitted] main</span><br><span class="line"> ./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor</span><br><span class="line">./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime</span><br><span class="line"> [0] ./index.js 29 kB {1} [built]</span><br><span class="line"> [2] (webpack)/buildin/global.js 488 bytes {2} [built]</span><br><span class="line"> [3] (webpack)/buildin/module.js 495 bytes {2} [built]</span><br></pre></td></tr></table></figure><p>↓ 我们增加一个新 module</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[4] ./webPlayer.js 24 kB {1} [built]</span><br></pre></td></tr></table></figure><p>↓ 现在看这里做了什么! <code>comments.js</code> 现在的 ID 由 4 变成了 5</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[5] ./comments.js 58 kB {0} [built]</span><br></pre></td></tr></table></figure><p>↓ <code>ads.js</code> 的 ID 由 5 变成 6</p><figure class="highlight bash"><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">[6] ./ads.js 74 kB {1} [built]</span><br><span class="line"> + 1 hidden module</span><br></pre></td></tr></table></figure><p>这将使包含或依赖于具有更改ID的模块的所有块无效 - 即使它们的实际代码没有更改。在我们的代码中,_0_ 这个 chunk 和 <em>main</em> chunk 都会失效 - 只有 <em>main</em> 才应该失效。</p><p>使用<a href="https://webpack.js.org/plugins/hashed-module-ids-plugin/" target="_blank" rel="noopener"><code>HashedModuleIdsPlugin</code></a>插件改变module ID 如何计算来解决这个问题。它利用 module 路径的 hash 来替换掉计数器:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">$ webpack</span><br><span class="line">Hash: df3474e4f76528e3bbc9</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 2150ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.6168aaac8461862eab7a.js 22.5 kB 0 [emitted]</span><br><span class="line"> ./main.a2e49a279552980e3b91.js 60 kB 1 [emitted] main</span><br><span class="line"> ./vendor.ff9f7ea865884e6a84c8.js 46 kB 2 [emitted] vendor</span><br><span class="line">./runtime.25f5d0204e4f77fa57a1.js 1.45 kB 3 [emitted] runtime</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight bash"><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">[3IRH] ./index.js 29 kB {1} [built]</span><br><span class="line">[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]</span><br><span class="line">[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]</span><br><span class="line">[LbCc] ./webPlayer.js 24 kB {1} [built]</span><br><span class="line">[lebJ] ./comments.js 58 kB {0} [built]</span><br><span class="line">[02Tr] ./ads.js 74 kB {1} [built]</span><br><span class="line"> + 1 hidden module</span><br></pre></td></tr></table></figure><p>有了这个方法,只有你重命名或者删除这个 moudle 它的 ID 才会变化。新的 modules 不会因为 module ID 互相影响。</p><p>启用这个插件,在配置中增加 <em>plugins</em>:</p><figure class="highlight javascript"><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">// webpack.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="keyword">new</span> webpack.HashedModuleIdsPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h5 id="Further-reading-扩展阅读-8"><a href="#Further-reading-扩展阅读-8" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h5><ul><li>Webpack docs <a href="https://webpack.js.org/plugins/hashed-module-ids-plugin/" target="_blank" rel="noopener">about the HashedModuleIdsPlugin</a> HashModuleIdsPlugin 插件文档</li></ul><h3 id="Summing-up"><a href="#Summing-up" class="headerlink" title="Summing up"></a>Summing up</h3><ul><li>Cache the bundle and differentiate between versions by changing the bundle name 缓存 bundle 包并通过修改 bundle 名称来做版本差异</li><li>Split the bundle into app code, vendor code and runtime 将 bundle 拆分成 app 业务代码、vendor 代码、runtime 代码</li><li>Inline the runtime to save an HTTP request 将 runtime 代码内联节省 HTTP 请求</li><li>Lazy-load non-critical code with <code>import</code> 通过 import 懒加载非必要代码</li><li>Split code by routes/pages to avoid loading unnecessary stuff 通过路由或页面拆分阻止加载不必要代码</li></ul><hr><h2 id="Monitor-and-analyze-the-app-监控并分析"><a href="#Monitor-and-analyze-the-app-监控并分析" class="headerlink" title="Monitor and analyze the app 监控并分析"></a>Monitor and analyze the app 监控并分析</h2><p>作者 <a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p><p>即使当你配置好你的 webpack 让你的应用尽可能体积较小的时候,跟踪这个应用就非常重要,同时了解里面包含了什么。除此之外,你安装一个依赖,它将让你的 app 增加两倍大小 - 但并没有注意到这个问题!</p><p>这一部分就来讲解一些能够帮助你理解你的 bundle 的工具。</p><h3 id="Keep-track-of-the-bundle-size-跟踪打包的体积"><a href="#Keep-track-of-the-bundle-size-跟踪打包的体积" class="headerlink" title="Keep track of the bundle size 跟踪打包的体积"></a>Keep track of the bundle size 跟踪打包的体积</h3><p>在开发时可以使用<a href="https://github.com/FormidableLabs/webpack-dashboard/" target="_blank" rel="noopener">webpack-dashboard</a>和命令行<a href="https://github.com/siddharthkp/bundlesize" target="_blank" rel="noopener">bundlesize</a> 来监控 app 的体积。</p><h4 id="webpack-dashboard"><a href="#webpack-dashboard" class="headerlink" title="webpack-dashboard"></a>webpack-dashboard</h4><p><a href="https://github.com/FormidableLabs/webpack-dashboard/" target="_blank" rel="noopener">webpack-dashboard</a>可以通过依赖体积大小、进程和其他细节来改进 webpack 的输出。</p><p><img src="http://img30.360buyimg.com/uba/jfs/t16294/169/2125639991/38263/ad862ba/5a8159c0N0da38a60.png" alt="webpack-dashboard"></p><p>这个 dashborad 帮助我们跟踪大型依赖 - 如果你增加一个依赖,你就立刻能在 Modules section 始终看到它!</p><p>启用这个功能,需要安装 <em>webpack-dashboard</em> 包:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install webpack-dashboard --save-dev</span><br></pre></td></tr></table></figure><p>同时在配置的 plugins 增加:</p><figure class="highlight javascript"><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 class="keyword">const</span> DashboardPlugin = <span class="built_in">require</span>(<span class="string">'webpack-dashboard/plugin'</span>);</span><br><span class="line"></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> DashboardPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>或者如果正在使用基于 Express dev server 可以使用 <code>compiler.apply()</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">compiler.apply(new DashboardPlugin());</span><br></pre></td></tr></table></figure><p>多尝试 dashboard 找出改进的地方!比如,在 modules section 滚动找到那个库体积过大,把它替换成小的可替代的库。</p><h4 id="bundlesize"><a href="#bundlesize" class="headerlink" title="bundlesize"></a>bundlesize</h4><p><a href="https://github.com/siddharthkp/bundlesize" target="_blank" rel="noopener">bundlesize</a> 可以验证 webpack assets 不超过指定的大小。通过自动化 CI 就可以知晓 app 是否变的过于臃肿:</p><p><img src="https://img30.360buyimg.com/uba/jfs/t15808/165/2111159728/110001/633e93ab/5a8159cdN1c575a1f.jpg" alt="bundlesize"></p><p>配置如下:</p><h5 id="Find-out-the-maximum-sizes-找出最大体积"><a href="#Find-out-the-maximum-sizes-找出最大体积" class="headerlink" title="Find out the maximum sizes 找出最大体积"></a><strong>Find out the maximum sizes</strong> 找出最大体积</h5><p>1.分析 app 尽可能减小体积,执行生产环境的 build。<br>2.在<code>package.json</code>中增加<code>bundlesize</code>部分:</p><figure class="highlight json"><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">// package.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"bundlesize"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"path"</span>: <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><p>3.使用<code>npx</code>执行<code>bundlesize</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx bundlesize</span><br></pre></td></tr></table></figure><p>它就会将每一个文件的 gzip 压缩后的体积打印出来:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PASS ./dist/icon256.6168aaac8461862eab7a.png: 10.89KB PASS./dist/icon512.c3e073a4100bd0c28a86.png: 13.1KB PASS./dist/main.0c8b617dfc40c2827ae3.js: 16.28KB PASS./dist/vendor.ff9f7ea865884e6a84c8.js: 31.49KB</span><br></pre></td></tr></table></figure><p>4.每一个体积增加10-20%,你将得到最大体积。这个10-20%的幅度可以让你像往常一样开发应用程序,同时警告你,当它的大小增长太多。</p><h5 id="Enable-bundlesize-启用-bundlesize"><a href="#Enable-bundlesize-启用-bundlesize" class="headerlink" title="Enable bundlesize 启用 bundlesize"></a><strong>Enable <code>bundlesize</code></strong> 启用 bundlesize</h5><p>5.安装<em>bundlesize</em>开发依赖</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install bundlesize --save-dev</span><br></pre></td></tr></table></figure><p>6.在<code>package.json</code>中的<code>bundlesize</code>部分,声明具体的最大值。对于某一些文件(比如图片),你可以单独根据文件类型来设置最大体积大小,而不需要根据每一个文件:</p><figure class="highlight json"><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">// package.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"bundlesize"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"./dist/*.png"</span>,</span><br><span class="line"> <span class="attr">"maxSize"</span>: <span class="string">"16 kB"</span>,</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"./dist/main.*.js"</span>,</span><br><span class="line"> <span class="attr">"maxSize"</span>: <span class="string">"20 kB"</span>,</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"./dist/vendor.*.js"</span>,</span><br><span class="line"> <span class="attr">"maxSize"</span>: <span class="string">"35 kB"</span>,</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>7.增加一个 npm 脚本来执行检查:</p><figure class="highlight json"><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">// package.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"check-size"</span>: <span class="string">"bundlesize"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>8.配置自动化 CI 来在每一次 push 时执行<code>npm run check-size</code>做检查。(如果你在 Github 上开发项目,直接可以使用<a href="https://github.com/siddharthkp/bundlesize#2-build-status" target="_blank" rel="noopener">integrate <code>bundlesize</code> with GitHub</a>。)</p><p>这就全部了!现在如果你运行<code>npm run check-size</code>或者 push 代码,你就会看到输出的文件是否足够小:</p><p><img src="https://img14.360buyimg.com/uba/jfs/t14890/146/2205111432/17457/fa7f748a/5a8159dcN17378d16.png" alt="bundlesize-output-success"></p><p>或者下面失败的情况</p><p><img src="https://img11.360buyimg.com/uba/jfs/t16969/198/453213154/26368/834a1c7f/5a8159e8Nc1f5ffe8.png" alt="bundlesize-output-failure"></p><h4 id="Further-reading-扩展阅读-9"><a href="#Further-reading-扩展阅读-9" class="headerlink" title="Further reading 扩展阅读"></a>Further reading 扩展阅读</h4><ul><li>Alex Russell <a href="https://infrequently.org/2017/10/can-you-afford-it-real-world-web-performance-budgets/" target="_blank" rel="noopener">about the real-world loading time we should target</a></li></ul><h3 id="Analyze-why-the-bundle-is-so-large-分析-bundle-为什么这么大"><a href="#Analyze-why-the-bundle-is-so-large-分析-bundle-为什么这么大" class="headerlink" title="Analyze why the bundle is so large 分析 bundle 为什么这么大"></a>Analyze why the bundle is so large 分析 bundle 为什么这么大</h3><p>你想要深挖 bundle 内,看看里面具体哪些 module 占用多大空间。<a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" target="_blank" rel="noopener">webpack-bundle-analyzer</a></p><blockquote><p>译者注:此处有<a href="https://developers.google.com/web/fundamentals/performance/webpack/webpack-bundle-analyzer.mp4" target="_blank" rel="noopener">视频</a>,需要科学上网,请自行观看</p></blockquote><p>(Screen recording from <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" target="_blank" rel="noopener">github.com/webpack-contrib/webpack -bundle-analyzer</a>)</p><p>webpack-bundle-analyzer 可以扫描 bundle 同时构建一个查看内部的可视化窗口。使用这个可视化工具找到过大或者不必要的依赖。</p><p>使用这个分析器,需要安装<code>webpack-bundle-analyzer</code>包:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install webpack-bundle-analyzer --save-dev</span><br></pre></td></tr></table></figure><p>在 config 中增加插件:</p><figure class="highlight javascript"><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 class="keyword">const</span> BundleAnalyzerPlugin = <span class="built_in">require</span>(<span class="string">'webpack-bundle-analyzer'</span>).BundleAnalyzerPlugin;</span><br><span class="line"></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> BundleAnalyzerPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>运行生产环境的 build 这个插件就会在浏览器中打开一个显示状态的页面。</p><p>默认情况下,这个页面会显示语法分析后的文件体积(在 bundle 出现的文件)。您可能想比较 gzip 的大小,因为这更接近实际用户的体验;使用左边的边栏来切换尺寸。</p><blockquote><p>Note: 如果你使用 <a href="https://webpack.js.org/plugins/module-concatenation-plugin/" target="_blank" rel="noopener">ModuleConcatenationPlugin</a>,它可能在webpack-bundle-analyzer输出时合并一部分 module,使得报告小一些细节。如果你使用这个插件,在执行分析的时候需要禁用掉。</p></blockquote><p>下面是报告中需要看什么:</p><ul><li><strong>Large dependencies 大型依赖</strong> 为什么体积这么大?是否有更小的替代包(比如 Preact 替代 React)?用了全部代码(比如 Moment.js 包含大量的本地变量 <a href="https://github.com/GoogleChromeLabs/webpack-libs-optimizations#moment" target="_blank" rel="noopener">that are often not used and could be dropped</a>)?</li><li><strong>Duplicated dependencies 重复依赖</strong> 是否在不同文件中看到过相同的库?(在 <strong>Webpack 4</strong> 中配置 <code>optimization.splitChunks.chunks</code>,或者在 <strong>Webpack 3</strong>中 使用 <em>CommonsChunkPlugin</em> 将他们移到一个通用文件内)亦或是在同一个库中 bundle 拥有多个版本?</li><li><strong>Similar dependencies 相似依赖</strong> 是否存在有相似功能的相似库存在?(比如<em>moment</em>和<em>date-fns</em> 或者 <em>lodash</em> 和 <em>lodash-es</em>)尽力汇总成一个。</li></ul><p>同样的,也可以看看 Sean Larkin 的文章 <a href="https://medium.com/webpack/webpack-bits-getting-the-most-out-of-the-commonschunkplugin-ab389e5f318" target="_blank" rel="noopener">great analysis of webpack bundles</a>。</p><h3 id="Summing-up-小结"><a href="#Summing-up-小结" class="headerlink" title="Summing up 小结"></a>Summing up 小结</h3><ul><li>Use <code>webpack-dashboard</code> and <code>bundlesize</code> to stay tuned of how large your app is</li><li>Dig into what builds up the size with <code>webpack-bundle-analyzer</code></li></ul><hr><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>总结:</p><ul><li><strong>剔除不必要的体积</strong> 把所有的代码都压缩最小化,剔除无用代码,增加依赖时保持谨慎小心。</li><li><strong>通过路由拆分代码</strong> 只在真正需要的时候才加载,其余部分做懒加载。</li><li><strong>缓存代码</strong> 应用程序某些部分代码更新频率低于其他部分代码,可以将这些部分拆分成文件,以便在必要时仅重新下载。</li><li><strong>跟踪体积大小</strong> 使用 <a href="https://github.com/FormidableLabs/webpack-dashboard/" target="_blank" rel="noopener">webpack-dashboard</a> 和 <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" target="_blank" rel="noopener">webpack-bundle-analyzer</a> 监控你的 app。每隔几个月重新检查一下你的应用的性能。</li></ul><p>Webpack 不仅仅是一个帮助你更快创建 app 的工具。它还帮助使你的 app 成为 <a href="https://developers.google.com/web/progressive-web-apps/" target="_blank" rel="noopener">a Progressive Web App</a> ,你的应用拥有更好的体验以及自动化的填充工具就像<a href="https://developers.google.com/web/tools/lighthouse/" target="_blank" rel="noopener">Lighthouse</a>根据环境给出建议。</p><p>不要忘记阅读 <a href="https://webpack.js.org/guides/" target="_blank" rel="noopener">webpack docs</a> - 里面提供了大量的优化相关的信息。</p><p>多多练习 <a href="https://github.com/GoogleChromeLabs/webpack-training-project" target="_blank" rel="noopener">with the training app</a>!</p>]]></content>
<summary type="html">
<p>作者 <a href="https://developers.google.com/web/resources/contributors/addyosmani" target="_blank" rel="noopener">Addy Osmani</a>,<a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p>
<p>原文 <a href="https://developers.google.com/web/fundamentals/performance/webpack/" target="_blank" rel="noopener">https://developers.google.com/web/fundamentals/performance/webpack/</a></p>
<blockquote>
<p>PS. 在 20180211 笔者翻译过一次,当时也没有完全理解和使用文中提到的优化项,近期工作中因为用到 Webpack 4.x 对生产环境进行打包,加深了一些理解,本译文对原有译文补充的 Webpack 4 内容,同时对原译文进行了校对和一些细节措辞的修改。</p>
</blockquote>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="翻译" scheme="https://beanlee.github.io/tags/%E7%BF%BB%E8%AF%91/"/>
<category term="Webpack" scheme="https://beanlee.github.io/tags/Webpack/"/>
</entry>
<entry>
<title>【译】Webpack 4 mode and optimization</title>
<link href="https://beanlee.github.io/posts/blog-translate-webpack-4-mode-and-optimization/"/>
<id>https://beanlee.github.io/posts/blog-translate-webpack-4-mode-and-optimization/</id>
<published>2018-04-18T06:26:11.000Z</published>
<updated>2018-06-20T01:31:47.000Z</updated>
<content type="html"><![CDATA[<p>原文 <a href="https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a" target="_blank" rel="noopener">https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a</a></p><p>作者 <a href="https://medium.com/@sokra" target="_blank" rel="noopener">Tobias Koppers</a></p><p>webpack 增加了一个模式配置(mode option)。下面来解释一下具体内容。</p><a id="more"></a><p><img src="https://img14.360buyimg.com/uba/s750x500_jfs/t17446/171/1782320374/354853/1568df4e/5ad6eac5Na2c1d8ee.jpg!cc_2x1" alt></p><p>webpack 4 增加了一个<code>模式</code>选项(<code>mode</code> option)。<strong>并且这个配置是必须存在的</strong>。(实际上它不是必须的配置项,但如果你省略掉没有配就会有警告。)</p><p>webpack 4 目前有<strong>两个默认配置</strong>,<code>development</code> 和 <code>production</code> 。这两个值可以在 <code>mode</code> 中设置。</p><p>设置 <code>development</code> 将会带给你最好*的开发体验,它专注于以下几点:</p><ul><li>浏览器 debug 工具</li><li>开发周期内的快速增量编译</li><li>运行时有用的错误信息</li></ul><p>(*实际上这里指最好的体验取决于你使用的方式,我们尽力在这里囊括了大多数通用部分)</p><p>设置 <code>production</code> 将会带给你在发布应用时的一系列有用的默认设置,它专注于以下几点:</p><ul><li>更小的输出包尺寸</li><li>运行时高效的代码</li><li>忽略掉只在开发时启用(<code>development-only</code>)的代码</li><li>不会暴露源码或者文件路径</li><li>简化使用打包后资源过程</li></ul><p>最后一点非常重要。它基本意味着 <code>production</code> 会提供给你优化后的资源包,但这不是完美的优化有的资源包。这里还存在着很多可优化点,但是它们会让结果更难使用。这些优化点是有意被忽略的,因为在这里,我们更看重入门的体验高于完美的优化。大部分优化点也只在大型应用上起作用。</p><hr><p>随着这个 <code>mode</code> 配置的引入,我们尝试去减少 build 时必要配置。我们尝试着去用一些默认项去覆盖通用的配置。</p><p>然而从我们的经验来看我们也知道默认配置并不适用于所有人。不同的团队有不同的需求,有时候会因为遗留代码、有时候会因为个人喜好、有时候会因为特殊的应用或者有时候使用者会认为这并不是最好通用解决方案。许多人确实想要修改默认配置以适应他们自己的项目。We got you covered. 增加 <code>mode</code> 并不意味这我们移除掉了这些配置。这里面仍然是一切皆可配置。实际上我们使内部大多数优化都是可配置的(你可以禁用掉他们)。</p><p><code>mode</code> 选项是通过在配置中设置默认项实现的。这个不会被其他配置执行的 <code>mode</code> 也不会做任何行为。</p><p>下一部分就会更深入地讨论因为<code>mode</code>或其他选项而影响的配置。</p><hr><h3 id="devtool"><a href="#devtool" class="headerlink" title="devtool"></a>devtool</h3><p>在 <code>development</code> 模式下默认是 <code>eval</code>。否则不使用 devtool。</p><p><code>eval</code> devtool 没有提供最佳的质量,但是拥有很好的性能。这就是我们选择的折中方案。看一看这些配置的文档,这可以获取更高质量的 sourcemap。</p><ul><li>📉📉📉缺点:慢,bundle 体积大</li><li>📈📈📈优点:优化 debug 的体验</li></ul><h3 id="cache"><a href="#cache" class="headerlink" title="cache"></a>cache</h3><p>只在 <code>development</code> 模式下启用,否则禁用缓存。</p><p>缓存模块可以避免在没有改变时重建。</p><p>在内存缓存只在 <code>watch</code> 模式下有用,并且我们假设你在开发时正在使用 <code>watch</code> 模式。不用缓存时,内存占用率更低。</p><ul><li>📉缺点:内存占用</li><li>📈📈📈优点:更快的增量编译</li></ul><h3 id="module-unsafeCache"><a href="#module-unsafeCache" class="headerlink" title="module.unsafeCache"></a>module.unsafeCache</h3><p>只在 <code>cache</code> 启用时启用,否则禁用。</p><p>缓存机械依赖项可以避免重新解析它们。</p><ul><li>📉缺点:内存占用,缓存入口可能错误</li><li>📈📈📈优点:更快的增量编译</li></ul><h3 id="output-pathinfo"><a href="#output-pathinfo" class="headerlink" title="output.pathinfo"></a>output.pathinfo</h3><p>只在 <code>development</code> 模式下启用,否则禁用。</p><p>这些额外的注释对于 debug 很有作用,尤其是使用 <code>eval</code> devtool。</p><ul><li>📉缺点:bundle 体积大,暴露路径信息</li><li>📈优点:提升生成代码的可阅读性</li></ul><h3 id="performance"><a href="#performance" class="headerlink" title="performance"></a>performance</h3><p>只在 <code>production</code> 模式下启用,否则禁用。</p><p>体积限制只对最小化资源起作用,同时伴随着性能开销。因此它只在生产模式下启用。</p><ul><li>📉缺点:算法消耗</li><li>📈优点:对打包 bundle 体积大小产出警告</li></ul><h3 id="optimization-removeAvailableModules"><a href="#optimization-removeAvailableModules" class="headerlink" title="optimization.removeAvailableModules"></a>optimization.removeAvailableModules</h3><p>总是开启。</p><p>当他们在父级 chunk groups 中都可用时,这些模块会被移除掉。它可以减少资源包的体积。因为有更少的代码生成,更小的资源包就意味着更快的 build 过程。</p><ul><li>📉📉缺点:算法消耗</li><li>📈📈📈优点:bundle 体积优化</li></ul><h3 id="optimization-removeEmptyChunks"><a href="#optimization-removeEmptyChunks" class="headerlink" title="optimization.removeEmptyChunks"></a>optimization.removeEmptyChunks</h3><p>总是开启。</p><p>空 chunks 会被移除掉。这些在文件系统中减少 load 会导致更快的 build。</p><ul><li>📉缺点:算法消耗</li><li>📈📈📈优点:更少的请求</li></ul><h3 id="optimization-mergeDuplicateChunks"><a href="#optimization-mergeDuplicateChunks" class="headerlink" title="optimization.mergeDuplicateChunks"></a>optimization.mergeDuplicateChunks</h3><p>总是开启。</p><p>相等的 chunks 会被合并。结果就是更少的生成代码,更快的 build。</p><ul><li>📉缺点:算法消耗</li><li>📈📈📈优点:更少的请求和下载</li></ul><h3 id="optimization-flagIncludedChunks"><a href="#optimization-flagIncludedChunks" class="headerlink" title="optimization.flagIncludedChunks"></a>optimization.flagIncludedChunks</h3><p>只在 <code>production</code> 模式下开启,否则禁用。</p><p>确定作为其他 chunks 子集的 chunks,并且这样方式对齐标记,即当加载较大 chunks 时,不必加载子集。</p><ul><li>📉缺点:算法消耗</li><li>📈📈📈优点:更少的请求和下载</li></ul><h3 id="optimization-occurrenceOrder"><a href="#optimization-occurrenceOrder" class="headerlink" title="optimization.occurrenceOrder"></a>optimization.occurrenceOrder</h3><p>只在 <code>production</code> 模式下开启,否则禁用。</p><p>提供更常用的 ids 更小(更短)的值。</p><ul><li>📉缺点:算法消耗</li><li>📈优点:bundle 体积优化</li></ul><h3 id="optimization-providedExports"><a href="#optimization-providedExports" class="headerlink" title="optimization.providedExports"></a>optimization.providedExports</h3><p>总是开启。</p><p>尽可能地确定每一个 module 的 exports。这个信息被用于其他优化或者生成代码。为了消除歧义:为 <code>export * from</code> 生成更有效率的代码。</p><ul><li>📉缺点:算法消耗</li><li>📈优点:bundle 体积优化,其他优化需求</li></ul><h3 id="optimization-usedExports"><a href="#optimization-usedExports" class="headerlink" title="optimization.usedExports"></a>optimization.usedExports</h3><p>只在 <code>production</code> 模式下开启,否则禁用。</p><p>尽可能地确定每一个 module 的 exports。它依赖于 <code>optimization.providedExports</code>。这个信息被用于其他优化或者生成代码。消除歧义:导出不会生成无用的 exports, 当所有的用途都被兼容的时候导出 names 就是零碎的单独字符定义。在最小化中 DCE 会有一处,同时移除掉无用的 exports。</p><ul><li>📉📉缺点:算法消耗</li><li>📈📈优点:bundle 体积优化</li></ul><h3 id="optimization-sideEffects"><a href="#optimization-sideEffects" class="headerlink" title="optimization.sideEffects"></a>optimization.sideEffects</h3><p>只在 <code>production</code> 模式下开启,否则禁用。</p><p>认可在 <code>package.json</code> 或 <code>rules</code> 中的 <code>sideEffects</code> 标志去消除 modules。 它依赖 <code>optimization.providedExports</code> 和 <code>optimization.usedExports</code>。这些依赖都有所开销,但是消除依赖通过减少生成代码在性能上有积极的影响。它也依赖于你自己的代码。为了更好的性能,去尝试吧。</p><ul><li>📉缺点:算法消耗</li><li>📈📈📈优点:bundle 体积优化,更少生成代码</li></ul><h3 id="optimization-concatenateModules"><a href="#optimization-concatenateModules" class="headerlink" title="optimization.concatenateModules"></a>optimization.concatenateModules</h3><p>只在 <code>production</code> 模式下开启,否则禁用。</p><p>尝试查找模块图中可以安全连接到单个模块中的段。它依赖于<code>optimization.providedExports</code> 和 <code>optimization.usedExports</code>。</p><ul><li>📉📉📉缺点:额外的语法分析,作用域分析和定义重命名</li><li>📈📈📈优点:运行时性能、bundle 体积优化</li></ul><h3 id="optimization-splitChunks"><a href="#optimization-splitChunks" class="headerlink" title="optimization.splitChunks"></a>optimization.splitChunks</h3><p>总是开启。</p><p>查找在 chunks 之间哪些 module 被共享,同时将他们拆分到独立的 chunks 中,目的是减少重复或者从 application modules 中分离 vendor modules。</p><ul><li>📉缺点:算法消耗,额外的请求</li><li>📈📈📈优点:更少生成代码,更好的缓存,更少的下载请求</li></ul><h3 id="optimization-runtimeChunk"><a href="#optimization-runtimeChunk" class="headerlink" title="optimization.runtimeChunk"></a>optimization.runtimeChunk</h3><p>总是开启。</p><p>为 webpack 运行时和 chunk manifest 创建一个独立的 chunk。这个 chunk 应该内联到 HTML 中。</p><ul><li>📉缺点:更大的 HTML 文件</li><li>📈优点:更好的缓存</li></ul><h3 id="optimization-noEmitOnErrors"><a href="#optimization-noEmitOnErrors" class="headerlink" title="optimization.noEmitOnErrors"></a>optimization.noEmitOnErrors</h3><p>只在 <code>production</code> 模式下开启,否则禁用。</p><p>当发生编译错误的时候不输出资源包。</p><ul><li>📉缺点:无法使用应用程序的起作用的部分</li><li>📈优点:没有损坏的 bundles</li></ul><h3 id="optimization-namedModules"><a href="#optimization-namedModules" class="headerlink" title="optimization.namedModules"></a>optimization.namedModules</h3><p>只在 <code>development</code> 模式下开启,否则禁用。</p><p>取代数值型 ID,提供给 module 更有用的命名。</p><ul><li>📉缺点:bundle 体积增加</li><li>📈优点:更好的错误报告和 debug</li></ul><h3 id="optimization-nodeEnv"><a href="#optimization-nodeEnv" class="headerlink" title="optimization.nodeEnv"></a>optimization.nodeEnv</h3><p><code>mode</code> 值得默认项:<code>development</code> 或者 <code>production</code>。</p><p>定义 <code>process.env.NODE_ENV</code> 成为编译时常量值。这就允许移除掉 development only 的代码。</p><ul><li>📉缺点:开发环境的代码与生产环境的代码不同</li><li>📈📈优点:减小 bundle 体积,提高运行效率</li></ul><h3 id="optimization-minimize"><a href="#optimization-minimize" class="headerlink" title="optimization.minimize"></a>optimization.minimize</h3><p>只在 <code>production</code> 模式下开启,否则禁用。</p><p>使用最小化工具来压缩输出的资源包,比如(<code>optimization.minimizer</code>默认使用的<code>uglify-js</code>)。</p><ul><li>📉📉📉缺点:编译速度降低</li><li>📈📈📈优点:减小 bundle 体积</li></ul><h3 id="optimization-portableRecords"><a href="#optimization-portableRecords" class="headerlink" title="optimization.portableRecords"></a>optimization.portableRecords</h3><p>在文件内记录时启用,否则禁用。</p><p>记录中使用的标识与上下文目录有关。</p><ul><li>📉缺点:影响速度降低</li><li>📈优点:记录与目录无关</li></ul>]]></content>
<summary type="html">
<p>原文 <a href="https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a" target="_blank" rel="noopener">https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a</a></p>
<p>作者 <a href="https://medium.com/@sokra" target="_blank" rel="noopener">Tobias Koppers</a></p>
<p>webpack 增加了一个模式配置(mode option)。下面来解释一下具体内容。</p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="Webpack" scheme="https://beanlee.github.io/tags/Webpack/"/>
<category term="构建工具" scheme="https://beanlee.github.io/tags/%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7/"/>
</entry>
<entry>
<title>移动端 HTTPS 抓包工具配置说明</title>
<link href="https://beanlee.github.io/posts/mobile-https-config/"/>
<id>https://beanlee.github.io/posts/mobile-https-config/</id>
<published>2018-03-29T10:53:55.000Z</published>
<updated>2018-06-20T01:31:47.000Z</updated>
<content type="html"><![CDATA[<p>投稿部门公众号,做了一些微调,增加了 Android 机型的一些说明。</p><p>2018-03-01 Beanlee</p><p>相信各位在移动端开发过程中一定遇到抓取数据包、拦截请求的场景,本文主要介绍移动端在针对 HTTPS 抓包时的几款软件的配置(包括<a href="#Charles">Charles</a>、<a href="#Fiddler">Fiddler</a>、<a href="#Whistle">Whistle</a>),下面举例将已 iOS 11 为例,附带 Android 机型配置截图。</p><a id="more"></a><h2 id="Charles"><a href="#Charles" class="headerlink" title="Charles"></a>Charles<b id="Charles"></b></h2><ul><li><a href="https://www.charlesproxy.com/" target="_blank" rel="noopener">官网</a></li><li>按照 license 收费,收费后终身享受升级,仅限 OS X 平台</li></ul><h3 id="HTTP-抓包配置"><a href="#HTTP-抓包配置" class="headerlink" title="HTTP 抓包配置"></a>HTTP 抓包配置</h3><h4 id="1-确保手机与主机处于同一局域网内。"><a href="#1-确保手机与主机处于同一局域网内。" class="headerlink" title="1.确保手机与主机处于同一局域网内。"></a>1.确保手机与主机处于同一局域网内。</h4><p>查看本机 IP,使用 <code>ifconfig</code> 命令查看本机 IP;或者打开系统设置查看 IP 如截图;</p><p>例如:当前本机 IP <code>192.168.191.4</code></p><p><img src="https://img20.360buyimg.com/uba/jfs/t16375/210/2520307643/222582/31ac0971/5ab0a175N3cbadb49.png.webp" alt="ifconfig"></p><p><img src="https://img10.360buyimg.com/uba/jfs/t17530/312/915989884/252348/b1b61faa/5ab0a1e7N47455815.png.webp" alt="wifi-config"></p><h4 id="2-设置手机-iOS-网络代理"><a href="#2-设置手机-iOS-网络代理" class="headerlink" title="2.设置手机 iOS 网络代理"></a>2.设置手机 iOS 网络代理</h4><p><code>设置</code> -> <code>无线局域网</code> -> <code>HTTP 代理</code> -> <code>手动</code></p><p>服务器:<code>192.168.191.4</code> 端口:<code>8888</code></p><p><img src="https://img30.360buyimg.com/uba/jfs/t19171/267/1165626521/82168/a242486c/5abdf072Nd89a456f.png.webp" alt="ios&andoird-proxy-conf"></p><h4 id="3-配置完成后,在手机端访问任何一个-App-或网页,电脑端-Charles-会弹出提示,点击-Allow"><a href="#3-配置完成后,在手机端访问任何一个-App-或网页,电脑端-Charles-会弹出提示,点击-Allow" class="headerlink" title="3.配置完成后,在手机端访问任何一个 App 或网页,电脑端 Charles 会弹出提示,点击 Allow"></a>3.配置完成后,在手机端访问任何一个 App 或网页,电脑端 Charles 会弹出提示,点击 Allow</h4><p>此时可以在应用的界面中看到 HTTP 的请求数据了。</p><p>接下来继续 HTTPS 的配置。</p><h3 id="HTTPS-抓包配置"><a href="#HTTPS-抓包配置" class="headerlink" title="HTTPS 抓包配置"></a>HTTPS 抓包配置</h3><h4 id="1-信任证书-本机"><a href="#1-信任证书-本机" class="headerlink" title="1.信任证书 本机"></a>1.信任证书 本机</h4><p>打开 Charles 后如下图操作,电脑端信任证书,<strong>注意</strong> 此处选择<code>system</code></p><p><img src="https://img13.360buyimg.com/uba/jfs/t16189/305/2463904253/229838/13248112/5ab0a246N431365b3.png.webp" alt="charles-install-root-cer"></p><p><img src="https://img30.360buyimg.com/uba/jfs/t15991/176/2548641977/433484/2e35454f/5ab0a26cN86d8f8c1.png.webp" alt="charles-install-root-cer-system"></p><h4 id="2-信任证书-手机"><a href="#2-信任证书-手机" class="headerlink" title="2.信任证书 手机"></a>2.信任证书 手机</h4><p>打开 <code>Safari</code> 地址输入 <code>192.168.191.4:8888</code> 或 <code>chls.pro/ssl</code> 点击安装,此时会弹出要求需要输入手机密码,完成后证书安装成功。</p><p>此时查看 <code>通用</code> -> <code>描述文件</code> 中 charles proxy ca 已变成<code>已验证</code>。</p><p><strong>注意</strong> 自 iOS 10.3 以上,还需要多一步操作,手动信任自定义根证书,才能确保证书安装并已启用;</p><p><code>关于本机</code> -> <code>证书信任设置</code> 启用 charles proxy ca 截图详见文章底部<a href="#tips">锦囊</a></p><p><strong>注意</strong> Android 手机不需要安装 Charles 证书 <a href="https://www.charlesproxy.com/documentation/using-charles/ssl-certificates/" target="_blank" rel="noopener">ssl-certificates</a></p><p>下面截图中是 Android 仅安装了 Fiddler 和 Whistle 的证书截图。</p><p><img src="https://img30.360buyimg.com/uba/jfs/t18598/270/1201930732/57036/69b112d/5abdf133N1ef09cfe.png.webp" alt="android-cert"></p><h4 id="3-Charles-设置启用-SSL-Proxy"><a href="#3-Charles-设置启用-SSL-Proxy" class="headerlink" title="3. Charles 设置启用 SSL Proxy"></a>3. Charles 设置启用 SSL Proxy</h4><p><code>Proxy</code>-><code>SSL Proxy setting</code> Enabled SSL Proxying</p><p>同时添加希望拦截到的域名,例如:<code>*.jd.com</code> 端口 <code>443</code></p><p><img src="https://img14.360buyimg.com/uba/jfs/t17281/140/906435195/142935/49f55aa9/5ab0a4d4Nacbc29d6.png.webp" alt="charles-ssl-config01"></p><p><img src="https://img10.360buyimg.com/uba/jfs/t19591/182/853494454/73346/71990549/5ab0a4acN9ee93450.png.webp" alt="charles-ssl-config02"></p><p><strong>配置完成</strong> : )</p><h2 id="Fiddler"><a href="#Fiddler" class="headerlink" title="Fiddler"></a>Fiddler<b id="Fiddler"></b></h2><ul><li><a href="https://www.telerik.com/fiddler" target="_blank" rel="noopener">官网</a></li><li>微软出品,免费,由于需要 .Net 库仅 Windows 平台(也有一些 Fiddler For mac / linux 但仅限 beta 版且功能不完善,这里只推荐在 Win 平台使用)</li></ul><h3 id="HTTP-抓包"><a href="#HTTP-抓包" class="headerlink" title="HTTP 抓包"></a>HTTP 抓包</h3><p>配置与 <code>Charles</code> 配置相同端口略有不同,此处不再重复。</p><p>总结几大步骤:</p><ol><li>移动端和电脑同处同一局域网内</li><li>手机或移动设备设置网络代理对应电脑端 IP 和 端口</li></ol><p><img src="https://img14.360buyimg.com/uba/jfs/t14656/65/2745558691/37041/b26d6171/5ab0add4N3bd559a6.png.webp" alt="fiddler01"></p><h3 id="HTTPS-抓包配置-1"><a href="#HTTPS-抓包配置-1" class="headerlink" title="HTTPS 抓包配置"></a>HTTPS 抓包配置</h3><ul><li>勾选拦截 HTTPS 请求</li><li>安装证书(电脑端 & 手机端)</li></ul><p><img src="https://img14.360buyimg.com/uba/jfs/t17401/70/901071906/48039/62c96f01/5ab0add7N4816f088.png.webp" alt="fiddler02"></p><h2 id="Whistle"><a href="#Whistle" class="headerlink" title="Whistle"></a>Whistle<b id="Whistle"></b></h2><ul><li><a href="https://avwo.github.io/whistle/" target="_blank" rel="noopener">官网</a></li><li>国人开发,开源 MIT 协议,基于 NodeJS 跨平台</li><li><strong>推荐</strong> 功能强大,部门内也有小伙伴分享过一次使用经验</li></ul><h3 id="工具准备"><a href="#工具准备" class="headerlink" title="工具准备"></a>工具准备</h3><ul><li><code>nodejs v0.10.0+</code></li><li><code>npm i -g whistle</code></li><li><code>w2 start</code></li></ul><p>完成上面几步操作后,此时 <code>8899</code> 端口已经常驻后台。</p><h3 id="界面"><a href="#界面" class="headerlink" title="界面"></a>界面</h3><p><img src="https://img14.360buyimg.com/uba/jfs/t17362/179/900501459/1903822/f445defb/5ab0bcecNf4b0f762.png.webp" alt="whistle"></p><h3 id="Chrome-插件"><a href="#Chrome-插件" class="headerlink" title="Chrome 插件"></a>Chrome 插件</h3><p>whistle 提供了一个基于 Chrome 的插件,可以方便切换代理,查看网络,编辑规则等,不过很遗憾作者已经将插件在 Web Store 下架,现在搜索不到,我把本地 0.10.0 版本打包成 crx 上传在 Google Driver 可以访问 <a href="https://drive.google.com/open?id=18RCuXBb_ml6OqLw-3PTSevcpJ81h-F27" target="_blank" rel="noopener">下载</a></p><p>或者改用另外一个强大的插件 <a href="https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif" target="_blank" rel="noopener">Proxy SwitchyOmega</a> 该插件也拥有强大的代理功能,同时支持自定义规则,可以自动根据域名或端口等自动选择是否启用代理,以后有机会可以分享使用 Proxy SwitchOmega 的使用经验。</p><h3 id="HTTPS-拦截配置"><a href="#HTTPS-拦截配置" class="headerlink" title="HTTPS 拦截配置"></a>HTTPS 拦截配置</h3><p><a href="https://avwo.github.io/whistle/webui/https.html" target="_blank" rel="noopener">whistle 文档 https 设置部分</a></p><h2 id="HTTPS-抓包锦囊"><a href="#HTTPS-抓包锦囊" class="headerlink" title="HTTPS 抓包锦囊"></a>HTTPS 抓包锦囊<b id="tips"></b></h2><p><strong>注意</strong> 自 iOS 10.3 以后,无论使用上面哪一种工具抓包,安装完 ca 证书以后,还需要<strong>多一步操作</strong>,才能确保证书安装并已启用;</p><p><code>通用</code> -> <code>关于本机</code> -> <code>证书信任设置</code> -> 启用相对应的证书,就可以看到工具中抓取到相应的 HTTPS 请求。</p><p><img src="https://img30.360buyimg.com/uba/jfs/t18550/32/1136654112/137090/91a83930/5abdf196N9bc228ff.png.webp" alt="ios-cer"></p><p>这一点跟苹果的 <strong>one more thing</strong> 貌似有那么一点不谋而合。</p><p><strong>注意</strong> Android 部分手机(如小米、华为)无法安装证书时候,不妨试试在电脑端将证书导出,打开手机 FTP 或者利用数据线传输到手机某个文件夹,点开 WLAN 配置 -> 高级设置 -> 安装证书 选择对应的证书安装即可。亦或是因为权限问题无法安装,打开安全隐私,选择信任 <code>未知来源</code>。</p><p>此办法对于安装测试版 APP 也管用 :)</p><p>have fun :D</p>]]></content>
<summary type="html">
<p>投稿部门公众号,做了一些微调,增加了 Android 机型的一些说明。</p>
<p>2018-03-01 Beanlee</p>
<p>相信各位在移动端开发过程中一定遇到抓取数据包、拦截请求的场景,本文主要介绍移动端在针对 HTTPS 抓包时的几款软件的配置(包括<a href="#Charles">Charles</a>、<a href="#Fiddler">Fiddler</a>、<a href="#Whistle">Whistle</a>),下面举例将已 iOS 11 为例,附带 Android 机型配置截图。</p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="移动开发" scheme="https://beanlee.github.io/tags/%E7%A7%BB%E5%8A%A8%E5%BC%80%E5%8F%91/"/>
</entry>
<entry>
<title>iOS HTTPS 抓包工具配置说明</title>
<link href="https://beanlee.github.io/posts/iOS-https/"/>
<id>https://beanlee.github.io/posts/iOS-https/</id>
<published>2018-03-01T10:53:55.000Z</published>
<updated>2018-06-20T01:31:47.000Z</updated>
<content type="html"><![CDATA[<p>相信各位在移动端开发过程中一定遇到抓取请求的场景,本文主要显示 iOS 11 在 <a href="#Charles">Charles</a>、<a href="#Fiddler">Fiddler</a>、<a href="#Whistle">Whistle</a> 下HTTPS抓包配置,欢迎留言区补充其他工具。PS.留言使用 Disqus 插件需要<code>科学上网</code>。</p><p>已经熟悉以上三个工具基础配置,仅仅需要解决抓包 iOS 下 HTTPS 问题的同学可以直接打开<a href="/2018/03/01/2018-03-01-iOS-https/#tips">锦囊</a></p><a id="more"></a><h2 id="Charles"><a href="#Charles" class="headerlink" title="Charles"></a>Charles<b id="Charles"></b></h2><ul><li><a href="https://www.charlesproxy.com/" target="_blank" rel="noopener">官网</a></li><li>按照 license 收费,收费后终身享受升级,仅限 OS X 平台</li></ul><h3 id="HTTP-抓包"><a href="#HTTP-抓包" class="headerlink" title="HTTP 抓包"></a>HTTP 抓包</h3><h4 id="1-确保手机与主机处于同一局域网内。"><a href="#1-确保手机与主机处于同一局域网内。" class="headerlink" title="1.确保手机与主机处于同一局域网内。"></a>1.确保手机与主机处于同一局域网内。</h4><p>查看本机 IP,使用 <code>ifconfig</code> 命令查看本机 IP;或者打开系统设置查看 IP 如截图;</p><p>当前本机 IP 192.168.191.4</p><p><img src="https://img20.360buyimg.com/uba/jfs/t16375/210/2520307643/222582/31ac0971/5ab0a175N3cbadb49.png.webp" alt="ifconfig"></p><p><img src="https://img10.360buyimg.com/uba/jfs/t17530/312/915989884/252348/b1b61faa/5ab0a1e7N47455815.png.webp" alt="wifi-config"></p><h4 id="2-设置手机-iOS-网络代理"><a href="#2-设置手机-iOS-网络代理" class="headerlink" title="2.设置手机 iOS 网络代理"></a>2.设置手机 iOS 网络代理</h4><p><code>设置</code> -> <code>无线局域网</code> -> <code>HTTP 代理</code> -> <code>手动</code></p><p>服务器:<code>192.168.191.4</code> 端口:<code>8888</code></p><h4 id="3-配置完成后,在手机端访问任何一个-App-或网页,本机-charles-会弹出提示,点击-Allow"><a href="#3-配置完成后,在手机端访问任何一个-App-或网页,本机-charles-会弹出提示,点击-Allow" class="headerlink" title="3.配置完成后,在手机端访问任何一个 App 或网页,本机 charles 会弹出提示,点击 Allow"></a>3.配置完成后,在手机端访问任何一个 App 或网页,本机 charles 会弹出提示,点击 Allow</h4><p>此时已经可以抓取 HTTP 数据了</p><h3 id="HTTPS-抓包"><a href="#HTTPS-抓包" class="headerlink" title="HTTPS 抓包"></a>HTTPS 抓包</h3><h4 id="1-信任证书-本机"><a href="#1-信任证书-本机" class="headerlink" title="1.信任证书 本机"></a>1.信任证书 本机</h4><p>打开 charles 后如下图操作,本机信任 charles 证书,<strong>注意</strong> 此处选择<code>system</code></p><p><img src="https://img13.360buyimg.com/uba/jfs/t16189/305/2463904253/229838/13248112/5ab0a246N431365b3.png.webp" alt="charles-install-root-cer"></p><p><img src="https://img30.360buyimg.com/uba/jfs/t15991/176/2548641977/433484/2e35454f/5ab0a26cN86d8f8c1.png.webp" alt="charles-install-root-cer-system"></p><h4 id="2-信任证书-手机"><a href="#2-信任证书-手机" class="headerlink" title="2.信任证书 手机"></a>2.信任证书 手机</h4><p>打开 <code>safari</code> 地址输入<code>192.168.191.4:8888</code>或<code>chls.pro/ssl</code>点击安装,此时会弹出要求需要输入手机密码,完成后证书安装成功。</p><p>此时查看<code>通用</code>-><code>描述文件</code>中 charles proxy ca 已变成<code>已验证</code>。</p><p><strong>注意</strong> 自 iOS 10.3 以上,还需要多一步操作,手动信任自定义根证书,才能确保证书安装并已启用;如图所示;</p><p><code>关于本机</code>-><code>证书信任设置</code>启用 charles proxy ca 截图详见<a href="/2018/03/01/2018-03-01-iOS-https/#tips">锦囊</a></p><h4 id="3-Charles-设置启用-SSL-Proxy"><a href="#3-Charles-设置启用-SSL-Proxy" class="headerlink" title="3. Charles 设置启用 SSL Proxy"></a>3. Charles 设置启用 SSL Proxy</h4><p><code>Proxy</code>-><code>SSL Proxy setting</code> Enabled SSL Proxying</p><p>同时增加希望抓取的域名,例如:<code>*.jd.com</code>端口 <code>443</code></p><p><img src="https://img14.360buyimg.com/uba/jfs/t17281/140/906435195/142935/49f55aa9/5ab0a4d4Nacbc29d6.png.webp" alt="charles-ssl-config01"></p><p><img src="https://img10.360buyimg.com/uba/jfs/t19591/182/853494454/73346/71990549/5ab0a4acN9ee93450.png.webp" alt="charles-ssl-config02"></p><p><strong>配置完成</strong> : )</p><h2 id="Fiddler"><a href="#Fiddler" class="headerlink" title="Fiddler"></a>Fiddler<b id="Fiddler"></b></h2><ul><li><a href="https://www.telerik.com/fiddler" target="_blank" rel="noopener">官网</a></li><li>微软出品,免费,由于需要 .Net 库仅 Windows 平台</li></ul><h3 id="HTTP-抓包-1"><a href="#HTTP-抓包-1" class="headerlink" title="HTTP 抓包"></a>HTTP 抓包</h3><p>配置与 <code>Charles</code> 配置相同端口略有不同,此处不再重复</p><p><img src="https://img14.360buyimg.com/uba/jfs/t14656/65/2745558691/37041/b26d6171/5ab0add4N3bd559a6.png.webp" alt="fiddler01"></p><h3 id="HTTPS-抓包配置"><a href="#HTTPS-抓包配置" class="headerlink" title="HTTPS 抓包配置"></a>HTTPS 抓包配置</h3><ul><li>勾选拦截 HTTPS 请求</li><li>安装证书(电脑端 & 手机端)</li></ul><p><img src="https://img14.360buyimg.com/uba/jfs/t17401/70/901071906/48039/62c96f01/5ab0add7N4816f088.png.webp" alt="fiddler02"></p><h2 id="Whistle"><a href="#Whistle" class="headerlink" title="Whistle"></a>Whistle<b id="Whistle"></b></h2><ul><li><a href="https://avwo.github.io/whistle/" target="_blank" rel="noopener">官网</a></li><li>国人开发,开源 MIT 协议,基于 NodeJS 跨平台</li><li><strong>推荐</strong></li></ul><h3 id="工具准备"><a href="#工具准备" class="headerlink" title="工具准备"></a>工具准备</h3><ul><li><code>nodejs v0.10.0+</code></li><li><code>npm i -g whistle</code></li><li><code>w2 start</code></li></ul><p>完成上面几步操作后,此时<code>8899</code>端口已经常驻后台。</p><h3 id="界面"><a href="#界面" class="headerlink" title="界面"></a>界面</h3><p><img src="https://img14.360buyimg.com/uba/jfs/t17362/179/900501459/1903822/f445defb/5ab0bcecNf4b0f762.png.webp" alt="whistle"></p><h3 id="Chrome-插件"><a href="#Chrome-插件" class="headerlink" title="Chrome 插件"></a>Chrome 插件</h3><p>whistle 提供了一个基于 Chrome 的插件,可以方便切换代理,查看网络,编辑规则等,不过很遗憾作者已经将插件在 Web Store 下架,现在搜索不到,我把本地 0.10.0 版本打包成 crx 上传在 Google Driver 可以访问 <a href="https://drive.google.com/open?id=18RCuXBb_ml6OqLw-3PTSevcpJ81h-F27" target="_blank" rel="noopener">下载</a></p><p>或者改用另外一个强大的插件 <a href="https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif" target="_blank" rel="noopener">Proxy SwitchyOmega</a></p><h3 id="HTTPS-拦截配置"><a href="#HTTPS-拦截配置" class="headerlink" title="HTTPS 拦截配置"></a>HTTPS 拦截配置</h3><p><a href="https://avwo.github.io/whistle/webui/https.html" target="_blank" rel="noopener">whistle 文档 https 设置部分</a></p><h2 id="iOS-11-HTTPS-抓包锦囊"><a href="#iOS-11-HTTPS-抓包锦囊" class="headerlink" title="iOS 11 HTTPS 抓包锦囊"></a>iOS 11 HTTPS 抓包锦囊<b id="tips"></b></h2><p><strong>注意</strong> 自 iOS 10.3 以后,无论使用上面哪一种工具抓包,安装完 ca 证书以后,还需要<strong>多一步操作</strong>,才能确保证书安装并已启用;</p><p><code>通用</code> -> <code>关于本机</code> -> <code>证书信任设置</code> -> 启用相对应的证书,就可以看到工具中抓取到相应的 HTTPS 请求。</p><p><img src="https://img30.360buyimg.com/uba/jfs/t15088/54/2660693956/316519/d9619a8c/5ab0a393Nc6ecb629.png.webp" alt="ios-cer"></p><p>这点跟苹果的 <strong>one more thing</strong> 貌似有那么一点不谋而合。</p><p>have fun :D</p>]]></content>
<summary type="html">
<p>相信各位在移动端开发过程中一定遇到抓取请求的场景,本文主要显示 iOS 11 在 <a href="#Charles">Charles</a>、<a href="#Fiddler">Fiddler</a>、<a href="#Whistle">Whistle</a> 下HTTPS抓包配置,欢迎留言区补充其他工具。PS.留言使用 Disqus 插件需要<code>科学上网</code>。</p>
<p>已经熟悉以上三个工具基础配置,仅仅需要解决抓包 iOS 下 HTTPS 问题的同学可以直接打开<a href="/2018/03/01/2018-03-01-iOS-https/#tips">锦囊</a></p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="移动开发" scheme="https://beanlee.github.io/tags/%E7%A7%BB%E5%8A%A8%E5%BC%80%E5%8F%91/"/>
</entry>
<entry>
<title>【译】利用 webpack 做 web 性能优化</title>
<link href="https://beanlee.github.io/posts/blog-translate-web-performance-optimization-with-webpack-from-google/"/>
<id>https://beanlee.github.io/posts/blog-translate-web-performance-optimization-with-webpack-from-google/</id>
<published>2018-02-11T06:35:33.000Z</published>
<updated>2018-06-20T01:31:47.000Z</updated>
<content type="html"><![CDATA[<p>作者 <a href="https://developers.google.com/web/resources/contributors/addyosmani" target="_blank" rel="noopener">Addy Osmani</a>,<a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p><p>原文 <a href="https://developers.google.com/web/fundamentals/performance/webpack/" target="_blank" rel="noopener">https://developers.google.com/web/fundamentals/performance/webpack/</a></p><a id="more"></a><h2 id="Instroduction-介绍"><a href="#Instroduction-介绍" class="headerlink" title="Instroduction 介绍"></a>Instroduction 介绍</h2><p>作者 <a href="https://developers.google.com/web/resources/contributors/addyosmani" target="_blank" rel="noopener">Addy Osmani</a></p><p>现代 Web 应用经常用到<code>bunding tool</code>来创建生产环境的打包文件(例如脚本、样式等),打包文件是需要优化并压缩最小化,同时能够被让用户更快地下载到。在这篇文章中,我们将会利用<code>webpack</code>来贯穿说明如何优化网站资源。这样可以帮助用户更快地加载你的应用同时获得更好的体验。</p><p><img src="https://img20.360buyimg.com/uba/jfs/t15217/149/2229580840/12989/54324b99/5a815957N5bb3e0c6.png" alt="webpack-logo"></p><p>webpack 目前是最流行的打包工具之一,深入地利用它的特点去优化代码,将脚本拆分成重要和非重要部分,还有剔除无用的代码能够保证你的应用维持最小的带宽和进程消耗。</p><p><img src="https://img14.360buyimg.com/uba/jfs/t17569/325/476871633/18187/a1e34f41/5a81597fNd77bb5b8.png" alt="code-splitting"></p><blockquote><p>Note: 我们创建了一个练习的应用来演示下面这些优化的描述。尽力抽更多的时间来练习这些 tips <a href="https://github.com/GoogleChromeLabs/webpack-training-project" target="_blank" rel="noopener"><code>webpack-training-project</code></a></p></blockquote><p>让我们从现代 web 应用中最耗费资源之一的 <code>Javascript</code> 开始。</p><ul><li>减小前端体积</li><li>利用长时缓存</li><li>监控并分析应用</li><li>总结</li></ul><hr><h2 id="Decrease-Front-end-Size-减少前端体积"><a href="#Decrease-Front-end-Size-减少前端体积" class="headerlink" title="Decrease Front-end Size 减少前端体积"></a>Decrease Front-end Size 减少前端体积</h2><p>作者 <a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p><p>当你正在优化一个应用时,首要事情就是尽可能地将它体积的减小。下面就是利用 <code>webpack</code> 如何做。</p><h3 id="Enable-minification-启用最小化"><a href="#Enable-minification-启用最小化" class="headerlink" title="Enable minification 启用最小化"></a>Enable minification 启用最小化</h3><p>最小化是通过去除多余空格、缩短变量名等方式压缩代码。例如:</p><figure class="highlight javascript"><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">// Original code</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">map</span>(<span class="params">array, iteratee</span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> index = <span class="number">-1</span>;</span><br><span class="line"> <span class="keyword">const</span> length = array == <span class="literal">null</span> ? <span class="number">0</span> : array.length;</span><br><span class="line"> <span class="keyword">const</span> result = <span class="keyword">new</span> <span class="built_in">Array</span>(length);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (++index < length) {</span><br><span class="line"> result[index] = iteratee(array[index], index, array);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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"><span class="comment">// Minified code</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">map</span>(<span class="params">n,r</span>)</span>{<span class="keyword">let</span> t=<span class="number">-1</span>;<span class="keyword">for</span>(<span class="keyword">const</span> a=<span class="literal">null</span>==n?<span class="number">0</span>:n.length,l=<span class="built_in">Array</span>(a);++t<a;)l[t]=r(n[t],t,n);<span class="keyword">return</span> l}</span><br></pre></td></tr></table></figure><p>Webpack 支持两种方式最小化代码:UglifyJS 插件和<em>loader-specific options</em>。他们可以同时使用。</p><p><a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin" target="_blank" rel="noopener">The UglifyJS plugin</a>在 bundle 层级中起作用,在编译之后压缩 bundle。下面来展示如何工作:</p><p>1.代码:<br><figure class="highlight javascript"><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 class="comment">// comments.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">'./comments.css'</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>2.Webpack 打包后大概是下面这样:<br><figure class="highlight javascript"><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">// bundle.js (part of)</span></span><br><span class="line"><span class="meta">"use strict"</span>;</span><br><span class="line"><span class="built_in">Object</span>.defineProperty(__webpack_exports__, <span class="string">"__esModule"</span>, { <span class="attr">value</span>: <span class="literal">true</span> });</span><br><span class="line"><span class="comment">/* harmony export (immutable) */</span> __webpack_exports__[<span class="string">"render"</span>] = render;</span><br><span class="line"><span class="comment">/* harmony import */</span> <span class="keyword">var</span> __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(<span class="number">1</span>);</span><br><span class="line"><span class="comment">/* harmony import */</span> <span class="keyword">var</span> __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default =</span><br><span class="line">__webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>3.使用 UglifyJS 插件压缩最小化后大概是下面这样:<br><figure class="highlight javascript"><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"><span class="comment">// minified bundle.js (part of)</span></span><br><span class="line"><span class="meta">"use strict"</span>;<span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params">e,n</span>)</span>{<span class="built_in">console</span>.log(<span class="string">"Rendered!"</span>)}</span><br><span class="line"><span class="built_in">Object</span>.defineProperty(n,<span class="string">"__esModule"</span>,{<span class="attr">value</span>:!<span class="number">0</span>}),n.render=t;<span class="keyword">var</span> o=r(<span class="number">1</span>);r.n(o)</span><br></pre></td></tr></table></figure></p><p>插件集成在 webpack 中,把它的配置在<code>plugins</code>中就可以启用:<br><figure class="highlight javascript"><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 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"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.UglifyJsPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><p>第二种方式<em>loader-specific options</em> 利用 loader options,可以压缩 Uglify 插件无法最小化的部分。举例,当你利用<code>css-loader</code>引入一个 CSS 文件时,文件会编译成一个字符串:<br><figure class="highlight css"><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 class="comment">/* comments.css */</span></span><br><span class="line"><span class="selector-class">.comment</span> {</span><br><span class="line"> <span class="attribute">color</span>: black;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>↓<br><figure class="highlight javascript"><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"><span class="comment">// minified bundle.js (part of)</span></span><br><span class="line">exports=<span class="built_in">module</span>.exports=__webpack_require__(<span class="number">1</span>)(),</span><br><span class="line">exports.push([<span class="built_in">module</span>.i,<span class="string">".comment {\r\n color: black;\r\n}"</span>,<span class="string">""</span>]);</span><br></pre></td></tr></table></figure></p><p>UglifyJS 不能压缩字符串。要压缩这段 css 内容,需要配置 <em>loader</em> :<br><figure class="highlight javascript"><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="built_in">module</span>.exports = {</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: [</span><br><span class="line"> <span class="string">'style-loader'</span>,</span><br><span class="line"> { <span class="attr">loader</span>: <span class="string">'css-loader'</span>, <span class="attr">options</span>: { <span class="attr">minimize</span>: <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><br><span class="line">};</span><br></pre></td></tr></table></figure></p><blockquote><p>Note: UglifyJS 插件不能编译 ES2015+(ES2016),这意味着如果你的 diamante 中使用类、箭头函数和一些新特性语法,不能编译成 ES5,插件会抛异常。<br>如果需要编译新语法,要使用 <a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin" target="_blank" rel="noopener">uglifyjs-webpack-plugin</a> 包。也是集成在 webpack 中相同的插件,但是更新一些,能够有能力编译 ES2015+。</p></blockquote><h4 id="Further-reading"><a href="#Further-reading" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li><a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin" target="_blank" rel="noopener">The UglifyJsPlugin docs</a></li><li>Other popular minifiers: <a href="https://github.com/webpack-contrib/babel-minify-webpack-plugin" target="_blank" rel="noopener">Babel Minify</a>, <a href="https://github.com/roman01la/webpack-closure-compiler" target="_blank" rel="noopener">Google Closure Compiler</a></li></ul><h3 id="Specify-NODE-ENV-production-明确生产环境信息"><a href="#Specify-NODE-ENV-production-明确生产环境信息" class="headerlink" title="Specify NODE_ENV=production 明确生产环境信息"></a>Specify <code>NODE_ENV=production</code> 明确生产环境信息</h3><p>减小前端体积的另外一个方法就是在代码中将<code>NODE_ENV</code><a href="https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them" target="_blank" rel="noopener">环境变量</a>设置成<code>production</code>。</p><p>Libraries 会读取<code>NODE_ENV</code>变量判断他们应该在那种模式下工作 - 开发模式 or 生成模式。很多库会基于这个变量有不同的表现。举个例子,当<code>NODE_ENV</code>没有设置成<code>production</code>,Vue.js 会做额外的检查并且输出一些警告:</p><figure class="highlight javascript"><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">// vue/dist/vue.runtime.esm.js</span></span><br><span class="line"><span class="comment">// …</span></span><br><span class="line"><span class="keyword">if</span> (process.env.NODE_ENV !== <span class="string">'production'</span>) {</span><br><span class="line"> warn(<span class="string">'props must be strings when using array syntax.'</span>);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// …</span></span><br></pre></td></tr></table></figure><p>React 也是类似 - 开发模式下 build 带有一些警告:</p><figure class="highlight javascript"><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">// react/index.js</span></span><br><span class="line"><span class="keyword">if</span> (process.env.NODE_ENV === <span class="string">'production'</span>) {</span><br><span class="line"> <span class="built_in">module</span>.exports = <span class="built_in">require</span>(<span class="string">'./cjs/react.production.min.js'</span>);</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">module</span>.exports = <span class="built_in">require</span>(<span class="string">'./cjs/react.development.js'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// react/cjs/react.development.js</span></span><br><span class="line"><span class="comment">// …</span></span><br><span class="line">warning$<span class="number">3</span>(</span><br><span class="line"> componentClass.getDefaultProps.isReactClassApproved,</span><br><span class="line"> <span class="string">'getDefaultProps is only used on classic React.createClass '</span> +</span><br><span class="line"> <span class="string">'definitions. Use a static property named `defaultProps` instead.'</span></span><br><span class="line">);</span><br><span class="line"><span class="comment">// …</span></span><br></pre></td></tr></table></figure><p>这些检查和警告通常在生产环境下是不必要的,但是他们仍然保留在代码中并且会增加库的体积。通过配置 webpack 的 <a href="https://webpack.js.org/plugins/define-plugin/" target="_blank" rel="noopener"><code>DefinePlugin</code></a> 来剔除掉:</p><figure class="highlight javascript"><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">// webpack.config.js</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"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.DefinePlugin({</span><br><span class="line"> <span class="string">'process.env.NODE_ENV'</span>: <span class="string">'"production"'</span>,</span><br><span class="line"> }),</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.UglifyJsPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p><code>DefinePlugin</code>用确定的变量替换所有存在的说明变量。利用下面配置:</p><p>1.<code>DefinePlugin</code>将用<code>"production"</code>替换到<code>process.env.NODE_ENV</code>:</p><figure class="highlight javascript"><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">// vue/dist/vue.runtime.esm.js</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">'string'</span>) {</span><br><span class="line"> name = camelize(val);</span><br><span class="line"> res[name] = { <span class="attr">type</span>: <span class="literal">null</span> };</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (process.env.NODE_ENV !== <span class="string">'production'</span>) {</span><br><span class="line"> warn(<span class="string">'props must be strings when using array syntax.'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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">// vue/dist/vue.runtime.esm.js</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">'string'</span>) {</span><br><span class="line"> name = camelize(val);</span><br><span class="line"> res[name] = { <span class="attr">type</span>: <span class="literal">null</span> };</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"production"</span> !== <span class="string">'production'</span>) {</span><br><span class="line"> warn(<span class="string">'props must be strings when using array syntax.'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>Note: 如果你偏向有通过 CLI 配置变量,可以查看一下 <a href="https://webpack.js.org/plugins/environment-plugin/" target="_blank" rel="noopener">EnvironmentPlugin</a>。它和<code>DefinePlugin</code>类似,但读环境并且自动替换<code>process.env</code>表达式。</p></blockquote><p>2.<code>UglifyJS</code>会移除掉所有<code>if</code>分支 - 因为<code>"production" !== 'production'</code>永远返回 false ,插件理解代码内的判断分支将永远不会执行:</p><figure class="highlight javascript"><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">// vue/dist/vue.runtime.esm.js</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">'string'</span>) {</span><br><span class="line"> name = camelize(val);</span><br><span class="line"> res[name] = { <span class="attr">type</span>: <span class="literal">null</span> };</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"production"</span> !== <span class="string">'production'</span>) {</span><br><span class="line"> warn(<span class="string">'props must be strings when using array syntax.'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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 class="comment">// vue/dist/vue.runtime.esm.js (without minification)</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> val === <span class="string">'string'</span>) {</span><br><span class="line"> name = camelize(val);</span><br><span class="line"> res[name] = { <span class="attr">type</span>: <span class="literal">null</span> };</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>Note: 不一定强制要求使用 <code>UglifyJSPlugin</code>。你可以使用其他不同的最小化工具,这些页支持移除无用代码(例如,the <a href="https://github.com/webpack-contrib/babel-minify-webpack-plugin" target="_blank" rel="noopener">Babel Minify plugin</a> or the <a href="https://github.com/roman01la/webpack-closure-compiler" target="_blank" rel="noopener">Google Closure Compiler plugin</a>)</p></blockquote><h4 id="Further-Reading"><a href="#Further-Reading" class="headerlink" title="Further Reading"></a>Further Reading</h4><ul><li><a href="https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them" target="_blank" rel="noopener">What “environment variables” are</a></li><li>Webpack docs about: <a href="https://webpack.js.org/plugins/define-plugin/" target="_blank" rel="noopener"><code>DefinePlugin</code></a>, <a href="https://webpack.js.org/plugins/environment-plugin/" target="_blank" rel="noopener"><code>EnvironmentPlugin</code></a></li></ul><h3 id="Use-ES-Modules-使用-ES-模块"><a href="#Use-ES-Modules-使用-ES-模块" class="headerlink" title="Use ES Modules 使用 ES 模块"></a>Use ES Modules 使用 ES 模块</h3><p>下面这个方式利用 <a href="https://ponyfoo.com/articles/es6-modules-in-depth" target="_blank" rel="noopener">ES modules</a> 减小前端体积。</p><p>当你使用 ES module,webpack 有能力去做 tree-shaking。Tree-shaking 贯穿整个依赖树,检查那些依赖被使用,移除无用依赖。因此,如果你使用 ES module 语法,webpack 可以排除掉无用代码:</p><p>1.一个有多个 export 的文件,但是 app 只需要其中一个:</p><figure class="highlight javascript"><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">// comments.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> render = <span class="function"><span class="params">()</span> =></span> { <span class="keyword">return</span> <span class="string">'Rendered!'</span>; };</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> commentRestEndpoint = <span class="string">'/rest/comments'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">import</span> { render } <span class="keyword">from</span> <span class="string">'./comments.js'</span>;</span><br><span class="line">render();</span><br></pre></td></tr></table></figure><p>2.webpack 理解 <code>commentRestEndPoint</code>没有使用,同时不能在一个 bundle 中生成单独的 export:</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// bundle.js (part that corresponds to comments.js)</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, __webpack_exports__, __webpack_require__</span>) </span>{</span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> <span class="keyword">const</span> render = <span class="function"><span class="params">()</span> =></span> { <span class="keyword">return</span> <span class="string">'Rendered!'</span>; };</span><br><span class="line"> <span class="comment">/* harmony export (immutable) */</span> __webpack_exports__[<span class="string">"a"</span>] = render;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> commentRestEndpoint = <span class="string">'/rest/comments'</span>;</span><br><span class="line"> <span class="comment">/* unused harmony export commentRestEndpoint */</span></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>3.<code>UglifyJSPlugin</code>移除无用变量:</p><figure class="highlight javascript"><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"><span class="comment">// bundle.js (part that corresponds to comments.js)</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">n,e</span>)</span>{<span class="string">"use strict"</span>;<span class="keyword">var</span> r=<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{<span class="keyword">return</span><span class="string">"Rendered!"</span>};e.b=r})</span><br></pre></td></tr></table></figure><p>如果他们都是有 ES module 编写,就是与一些库并存时也是生效的。</p><blockquote><p>Note: 在 webpack 中,tree-shaking 没有 minifier 是无法生效的。 webpack 仅仅移除了没有被用到的 export 变量;<code>UglifyJSPlugin</code>才会移除无用代码。所以如果你编译打包时没有使用 minifier,打包后体积并不会更小。你也可以不一定使用这个插件。其他最小化的插件也支持移除 dead code(例如:<a href="https://github.com/webpack-contrib/babel-minify-webpack-plugin" target="_blank" rel="noopener">Babel Minify plugin</a> or <a href="https://github.com/roman01la/webpack-closure-compiler" target="_blank" rel="noopener">Google Closure Compiler plugin</a>)</p></blockquote><blockquote><p>Warning: 不要将 ES module 编译到 CommonJS 中。 如果你使用 Babel <code>babel-preset-env</code> or <code>babel-preset-es2015</code>,检查一下当前的配置。默认情况下, ES <code>import</code> and <code>export</code> to CommonJS <code>require</code> and <code>module.exports</code>。通过设置 option 来禁止掉<a href="https://github.com/babel/babel/tree/master/experimental/babel-preset-env" target="_blank" rel="noopener">Pass the <code>{ modules: false }</code> option</a>。</p></blockquote><h4 id="Futher-reading"><a href="#Futher-reading" class="headerlink" title="Futher reading"></a>Futher reading</h4><ul><li><a href="https://ponyfoo.com/articles/es6-modules-in-depth" target="_blank" rel="noopener">“ES6 Modules in depth”</a></li><li>Webpack docs <a href="https://webpack.js.org/guides/tree-shaking/" target="_blank" rel="noopener">about tree shaking</a></li></ul><h3 id="Optimize-images-优化图片"><a href="#Optimize-images-优化图片" class="headerlink" title="Optimize images 优化图片"></a>Optimize images 优化图片</h3><p>图片基本会占局页面一半以上体积。虽然它们不像 JavaScript 那么重要(比如它们不会阻止页面渲染),但图片仍然会占用掉一大部分带宽。利用<code>url-loader</code>,<code>svg-url-loader</code>和<code>image-webpack-loader</code>来在 webpack 中进行优化。</p><p><code>url-loader</code> 允许将小静态文件打包进 app。没有配置,他需要通过 file,将它放在编译后的打包 bundle 内并返回一个这个文件的 url。然而,如果我们注明<code>limit</code>选项,它将会 encode 成更小的文件 base64 文件 url。这是可以将图片放在Javascript 代码中,同时节省 HTTP 请求:</p><figure class="highlight javascript"><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="built_in">module</span>.exports = {</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">/\.(jpe?g|png|gif)$/</span>,</span><br><span class="line"> loader: <span class="string">'url-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> <span class="comment">// Inline files smaller than 10 kB (10240 bytes)</span></span><br><span class="line"> limit: <span class="number">10</span> * <span class="number">1024</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><figure class="highlight javascript"><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">import</span> imageUrl <span class="keyword">from</span> <span class="string">'./image.png'</span>;</span><br><span class="line"><span class="comment">// → If image.png is smaller than 10 kB, `imageUrl` will include</span></span><br><span class="line"><span class="comment">// the encoded image: 'data:image/png;base64,iVBORw0KGg…'</span></span><br><span class="line"><span class="comment">// → If image.png is larger than 10 kB, the loader will create a new file,</span></span><br><span class="line"><span class="comment">// and `imageUrl` will include its url: `/2fcd56a1920be.png`</span></span><br></pre></td></tr></table></figure><blockquote><p>Note: 内联图片减少了独立请求的数量,这是很好的方式(<a href="https://blog.octo.com/en/http2-arrives-but-sprite-sets-aint-no-dead/" target="_blank" rel="noopener">even with HTTP/2</a>),但是会增加 bundle下载和转换的时间和内存的消耗。一定要确保不要嵌入超大图片或者较多的图片 - 否则增加的 bundle 的时间将会掩盖做成内联图片的收益。</p></blockquote><p><code>svg-url-loader</code>与<code>url-loader</code>类似 - 都是将使用 <a href="https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding" target="_blank" rel="noopener">URL encoding</a> encode 文件。这对对于 SVG 图片很奏效 - 因为 SVG 文件是文本,encoding 在体积上更有效率:</p><figure class="highlight javascript"><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></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 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">/\.svg$/</span>,</span><br><span class="line"> loader: <span class="string">'svg-url-loader'</span>,</span><br><span class="line"> options: {</span><br><span class="line"> <span class="comment">// Inline files smaller than 10 kB (10240 bytes)</span></span><br><span class="line"> limit: <span class="number">10</span> * <span class="number">1024</span>,</span><br><span class="line"> <span class="comment">// Remove the quotes from the url</span></span><br><span class="line"> <span class="comment">// (they’re unnecessary in most cases)</span></span><br><span class="line"> noquotes: <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><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>Note: svg-url-loader 拥有改善 IE 浏览器支持的 options,但是在其他浏览器中更糟糕。如果你需要兼容 IE 浏览器,<a href="https://github.com/bhovhannes/svg-url-loader#iesafe" target="_blank" rel="noopener">设置 iesafe: true 选项</a></p></blockquote><p><code>image-webpack-loader</code>压缩图片使之变小。它支持 JPG,PNG,GIF 和 SVG,因为我们将会使用它所有类型。</p><p>这个 loader 不会将图片嵌入在应用内,因此它必须与<code>url-loader</code>和<code>svg-url-loader</code>配合使用。避免复制粘贴到相同的 rules 中(一个用于 JPG/PNG/GIF 图片,另一个用于 SVG 图片),我们来使用<code>enforce: pre</code>作为单独的一个 rule 涵盖这个 loader:</p><figure class="highlight javascript"><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"><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 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">/\.(jpe?g|png|gif|svg)$/</span>,</span><br><span class="line"> loader: <span class="string">'image-webpack-loader'</span>,</span><br><span class="line"> <span class="comment">// This will apply the loader before the other ones</span></span><br><span class="line"> enforce: <span class="string">'pre'</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>默认 loader 设置就已经可以满足需求了 - 但如果你想要深入配置,请查看 <a href="https://github.com/tcoopman/image-webpack-loader#options" target="_blank" rel="noopener">the plugin options</a>。为了选择哪些 options 需要明确,可以查看 Addy Osmani 的 <a href="https://images.guide/" target="_blank" rel="noopener">guide on image optimization</a></p><h4 id="Further-reading-1"><a href="#Further-reading-1" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li><a href="https://stackoverflow.com/questions/201479/what-is-base-64-encoding-used-for" target="_blank" rel="noopener">“What is base64 encoding used for?”</a></li><li>Addy Osmani’s <a href="https://images.guide/" target="_blank" rel="noopener">guide on image optimization</a></li></ul><h3 id="Optimize-dependencies-优化依赖"><a href="#Optimize-dependencies-优化依赖" class="headerlink" title="Optimize dependencies 优化依赖"></a>Optimize dependencies 优化依赖</h3><p>平均一半以上的 Javascript 体积大小来源于依赖包,并且这些可能都不是必要的。</p><p>举一个例子来说,Lodash(v4.17.4)增加了最小化代码的 72KB 大小到 bundle 中。但是如果你仅仅用到它的20个方法,大约 65 KB 代码没有用处。</p><p>另外一个例子就是 Moment.js。 V2.19.1版本最小化后有 223KB,体积巨大 - 截至2017年10月一个页面内的 Javascript 平均体积是 452KB。但是,本地文件的体积占 170KB。如果你没有用到 多语言版 Moment.js,这些文件都会没有目的地使 bundle 更臃肿。</p><p>所有这些依赖都可以被轻易优化。我们在 Github repo 收集了优化的建议,<a href="https://github.com/GoogleChromeLabs/webpack-libs-optimizations" target="_blank" rel="noopener">check it out</a>!</p><h3 id="Enable-module-concatenation-for-ES-modules-aka-scope-hoisting"><a href="#Enable-module-concatenation-for-ES-modules-aka-scope-hoisting" class="headerlink" title="Enable module concatenation for ES modules (aka scope hoisting)"></a>Enable module concatenation for ES modules (aka scope hoisting)</h3><p>当你构建 bundle 时,webpack 将每一个 module 封装进 function 中:</p><figure class="highlight javascript"><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">// index.js</span></span><br><span class="line"><span class="keyword">import</span> {render} <span class="keyword">from</span> <span class="string">'./comments.js'</span>;</span><br><span class="line">render();</span><br><span class="line"></span><br><span class="line"><span class="comment">// comments.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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">// bundle.js (part of)</span></span><br><span class="line"><span class="comment">/* 0 */</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, __webpack_exports__, __webpack_require__</span>) </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> <span class="built_in">Object</span>.defineProperty(__webpack_exports__, <span class="string">"__esModule"</span>, { <span class="attr">value</span>: <span class="literal">true</span> });</span><br><span class="line"> <span class="keyword">var</span> __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(<span class="number">1</span>);</span><br><span class="line"> <span class="built_in">Object</span>(__WEBPACK_IMPORTED_MODULE_0__comments_js__[<span class="string">"a"</span> <span class="comment">/* render */</span>])();</span><br><span class="line"></span><br><span class="line">}),</span><br><span class="line"><span class="comment">/* 1 */</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, __webpack_exports__, __webpack_require__</span>) </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> __webpack_exports__[<span class="string">"a"</span>] = render;</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>在以前,这么做是使 CommonJS/AMD modules 互相分离所必须的。但是,这会增加体积并且性能表现堪忧。</p><p>Webpack 2 介绍了 ES modules 的支持,不像 CommonJS 和 AMD modules 一样,而是能够不用将每一个 module 用 function 封装起来。同时 Webpack 3 利用<a href="https://webpack.js.org/plugins/module-concatenation-plugin/" target="_blank" rel="noopener"><code>ModuleConcatenationPlugin</code></a>完成这样一个 bundle,下面是例子:</p><figure class="highlight javascript"><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">// index.js</span></span><br><span class="line"><span class="keyword">import</span> {render} <span class="keyword">from</span> <span class="string">'./comments.js'</span>;</span><br><span class="line">render();</span><br><span class="line"></span><br><span class="line"><span class="comment">// comments.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight javascript"><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">// Unlike the previous snippet, this bundle has only one module</span></span><br><span class="line"><span class="comment">// which includes the code from both files</span></span><br><span class="line"><span class="comment">// 与前面的代码不同,这个 bundle 只有一个 module,同时包含两个文件</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// bundle.js (part of; compiled with ModuleConcatenationPlugin)</span></span><br><span class="line"><span class="comment">/* 0 */</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, __webpack_exports__, __webpack_require__</span>) </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta"> "use strict"</span>;</span><br><span class="line"> <span class="built_in">Object</span>.defineProperty(__webpack_exports__, <span class="string">"__esModule"</span>, { <span class="attr">value</span>: <span class="literal">true</span> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// CONCATENATED MODULE: ./comments.js</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">data, target</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Rendered!'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// CONCATENATED MODULE: ./index.js</span></span><br><span class="line"> render();</span><br><span class="line"></span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>看到区别了吗?在这个 bundle 中, module 0 需要 module 1 的 render 方法。使用 <code>ModuleConcatenationPlugin</code>,<code>require</code>被直接简单的替换成 require 函数,同时 module 1 被删除删除掉了。这个 bundle 拥有更少的 modules,就有更少的 modules 损耗!</p><p>启用这个功能,可以在插件列表中增加<code>ModuleConcatenationPlugin</code>:<br><figure class="highlight javascript"><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 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"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.ModuleConcatenationPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><blockquote><p>Note:想要知道为什么这个功能不是默认启用?Concatenating modules 很棒, <a href="https://twitter.com/TheLarkInn/status/925800563144454144" target="_blank" rel="noopener">但是他会增加编译的时间同时破坏 module 的热更新</a>。这就是为什么只在生产环境中启用的原因了。</p></blockquote><h4 id="Further-reading-2"><a href="#Further-reading-2" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li>Webpack docs <a href="https://webpack.js.org/plugins/module-concatenation-plugin/" target="_blank" rel="noopener">for the ModuleConcatenationPlugin</a></li><li><a href="https://medium.com/webpack/brief-introduction-to-scope-hoisting-in-webpack-8435084c171f" target="_blank" rel="noopener">“Brief introduction to scope hoisting”</a></li><li>Detailed description of <a href="https://medium.com/webpack/webpack-freelancing-log-book-week-5-7-4764be3266f5" target="_blank" rel="noopener">what this plugin does</a></li></ul><h3 id="Use-externals-if-you-have-both-webpack-and-non-webpack-code-如果代码中包含-webpack-和非-webpack-的代码要使用-externals"><a href="#Use-externals-if-you-have-both-webpack-and-non-webpack-code-如果代码中包含-webpack-和非-webpack-的代码要使用-externals" class="headerlink" title="Use externals if you have both webpack and non-webpack code 如果代码中包含 webpack 和非 webpack 的代码要使用 externals"></a>Use <code>externals</code> if you have both webpack and non-webpack code 如果代码中包含 webpack 和非 webpack 的代码要使用 externals</h3><p>你可能拥有一个体积庞大的工程,其中一部分代码可以使用 webpack 编译,而有一些代码又不能。比如一个视频网站,播放器的 widget 可能通过 webpack 编译,但是其周围页面区域可能不是:</p><p><img src="https://img30.360buyimg.com/uba/jfs/t15334/148/2273118093/210074/8260a296/5a81599dN148751d5.png" alt="video-hosting"></p><p>如果两部分代码有相同的依赖,你可以共享这些依赖以便减少重复下载耗时。<a href="https://webpack.js.org/configuration/externals/" target="_blank" rel="noopener">the webpack’s <code>externals</code> option</a>就干了这件事 - 它用变量或者外部引用来替代 modules。</p><h4 id="如果依赖是挂载到-window-上的情况"><a href="#如果依赖是挂载到-window-上的情况" class="headerlink" title="如果依赖是挂载到 window 上的情况"></a>如果依赖是挂载到 window 上的情况</h4><p>如果你的非 webpack 代码依靠这些依赖,它们是挂载 window 上的变量,可以将依赖名称 alias 成变量名:</p><figure class="highlight javascript"><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 class="built_in">module</span>.exports = {</span><br><span class="line"> externals: {</span><br><span class="line"> <span class="string">'react'</span>: <span class="string">'React'</span>,</span><br><span class="line"> <span class="string">'react-dom'</span>: <span class="string">'ReactDOM'</span>,</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>利用这个配置,webpack 将不会打包 <code>react</code> 和 <code>react-dom</code>包。取而代之,他们会被替换成下面这个样子:</p><figure class="highlight javascript"><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">// bundle.js (part of)</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, exports</span>) </span>{</span><br><span class="line"> <span class="comment">// A module that exports `window.React`. Without `externals`,</span></span><br><span class="line"> <span class="comment">// this module would include the whole React bundle</span></span><br><span class="line"> <span class="built_in">module</span>.exports = React;</span><br><span class="line">}),</span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">module, exports</span>) </span>{</span><br><span class="line"> <span class="comment">// A module that exports `window.ReactDOM`. Without `externals`,</span></span><br><span class="line"> <span class="comment">// this module would include the whole ReactDOM bundle</span></span><br><span class="line"> <span class="built_in">module</span>.exports = ReactDOM;</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h4 id="如果依赖是当做-AMD-包被加载的情况"><a href="#如果依赖是当做-AMD-包被加载的情况" class="headerlink" title="如果依赖是当做 AMD 包被加载的情况"></a>如果依赖是当做 AMD 包被加载的情况</h4><p>如果你的非 webpack 代码没有将依赖暴露挂载到 window 上,这就更复杂了。但是如果非 webpack 代码使用 AMD 包的形式消费了这些依赖,你仍然可以避免重复的代码加载两次。</p><p>具体如何做呢?将 webpack 代码编译成一个 AMD module 同时别名成一个库的 URLs:</p><figure class="highlight javascript"><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></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"> output: { <span class="attr">libraryTarget</span>: <span class="string">'amd'</span> },</span><br><span class="line"></span><br><span class="line"> externals: {</span><br><span class="line"> <span class="string">'react'</span>: { <span class="attr">amd</span>: <span class="string">'/libraries/react.min.js'</span> },</span><br><span class="line"> <span class="string">'react-dom'</span>: { <span class="attr">amd</span>: <span class="string">'/libraries/react-dom.min.js'</span> },</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>Webpack 将会把 bundle 包装进 <code>define()</code>同时让它依赖于这些URLs:</p><figure class="highlight javascript"><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"><span class="comment">// bundle.js (beginning)</span></span><br><span class="line">define([<span class="string">"/libraries/react.min.js"</span>, <span class="string">"/libraries/react-dom.min.js"</span>], <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{ … });</span><br></pre></td></tr></table></figure><p>如果非 webpack 代码使用相同的 URLs 加载依赖,这些文件将会加载一次 - 多余的请求会使用缓存。</p><blockquote><p>Note:webpack 只是替换那些<code>externals</code>对象中的准确匹配的 keys 的引用。这意味着如果你的代码这样写<code>import React from 'react/umd/react.production.min.js'</code>,这个库是不会被 bundle 排除掉的。这是因为 - webpack 并不知道 <code>import 'react'</code> 和 <code>import 'react/umd/react.production.min.js'</code> 是同一个库,这样比较谨慎。</p></blockquote><h4 id="Further-reading-3"><a href="#Further-reading-3" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li>Webpack docs <a href="https://webpack.js.org/configuration/externals/" target="_blank" rel="noopener">on <code>externals</code></a></li></ul><h3 id="Summing-up-总结"><a href="#Summing-up-总结" class="headerlink" title="Summing up 总结"></a>Summing up 总结</h3><ul><li>Minimize your code with the <code>UglifyJsPlugin</code> and loader options</li><li>Remove the development-only code with the <code>DefinePlugin</code></li><li>Use ES modules to enable tree shaking</li><li>Compress images</li><li>Apply dependency-specific optimizations</li><li>Enable module concatenation</li><li>Use <code>externals</code> if this makes sense for you</li></ul><hr><h2 id="Make-use-of-long-term-caching-利用好长时缓存"><a href="#Make-use-of-long-term-caching-利用好长时缓存" class="headerlink" title="Make use of long-term caching 利用好长时缓存"></a>Make use of long-term caching 利用好长时缓存</h2><p>作者 <a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p><p>在做完优化应用体积之后的下一步提升应用加载时间的就是缓存。在客户端中使用缓存作为应用的一部分,这样会在每一次请求中减少重新下载的次数。</p><h3 id="Use-bundle-versioning-and-cache-headers-使用-bundle-版本和缓存头信息"><a href="#Use-bundle-versioning-and-cache-headers-使用-bundle-版本和缓存头信息" class="headerlink" title="Use bundle versioning and cache headers 使用 bundle 版本和缓存头信息"></a>Use bundle versioning and cache headers 使用 bundle 版本和缓存头信息</h3><p>做缓存通用的解决办法:</p><p>1.告诉浏览器缓存一个文件很长时间(比如一年)</p><figure class="highlight routeros"><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"><span class="comment"># Server header</span></span><br><span class="line">Cache-Control: <span class="attribute">max-age</span>=31536000</span><br></pre></td></tr></table></figure><p>Note:如果你不熟悉 <code>Cache-Control</code> 做了什么,你可以看一下 Jake Archibald 的精彩博文 <a href="https://jakearchibald.com/2016/caching-best-practices/" target="_blank" rel="noopener">on caching best practices</a></p><p>2.当文件改变需要强制重新下载时去重命名这些文件</p><figure class="highlight html"><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 class="comment"><!-- Before the change --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./index-v15.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- After the change --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./index-v16.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p>这些方法可以告诉浏览器下载这些 JS 文件,将其缓存起来。浏览器将只会在文件名发生改变时才会请求网络(或者缓存失效的情况也会请求)。</p><p>使用 webpack,也可以做同样的事,但可以使用版本号来解决,需要明确这个文件的 hash 值。使用 <a href="https://webpack.js.org/configuration/output/#output-filename" target="_blank" rel="noopener"><code>[chunkhash]</code></a> 可以将 <code>hash</code> 值包含进文件名中:</p><figure class="highlight javascript"><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 class="built_in">module</span>.exports = {</span><br><span class="line"> entry: <span class="string">'./index.js'</span>,</span><br><span class="line"> output: {</span><br><span class="line"> filename: <span class="string">'bundle.[chunkhash].js'</span>,</span><br><span class="line"> <span class="comment">// → bundle.8e0d62a03.js</span></span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>Note: webpack 可能会生成不同的 hash 即使 bundle 相同 - 比如你重名了了一个文件或者重新在不同的操作系统下编译了一个 bundle。 <a href="https://github.com/webpack/webpack/issues/1479" target="_blank" rel="noopener">This is a bug.</a><br>如果你需要将文件名发送给客户端,也可以使用 <code>HtmlWebpackPlugin</code> 或者 <code>WebpackManifestPlugin</code>。</p></blockquote><p><code>HtmlWebpackPlugin</code> 很简单,但灵活性欠缺一些。编译时,插件会生成一个 HTML 文件,这其中包括所有的编译后的资源文件。如果你的业务逻辑不复杂,这就非常适合你:</p><figure class="highlight html"><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 class="comment"><!-- index.html --></span></span><br><span class="line"><span class="meta"><!doctype html></span></span><br><span class="line"><span class="comment"><!-- ... --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"bundle.8e0d62a03.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p><code>WebpackManifestPlugin</code>更灵活一些,它可以帮助你解决业务负责的部分。编译时它会生成一个 JSON 文件,这文件保存这没有 hash 值文件与有 hash 文件之间的映射。服务端利用这个 JSON 可以识别出那个文件有效:</p><figure class="highlight json"><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">// manifest.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"bundle.js"</span>: <span class="string">"bundle.8e0d62a03.js"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="Further-reading-4"><a href="#Further-reading-4" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li>Jake Archibald <a href="https://jakearchibald.com/2016/caching-best-practices/" target="_blank" rel="noopener">about caching best practices</a></li></ul><h3 id="Extract-dependencies-and-runtime-into-a-separate-file-将依赖和运行环境代码提取到一个单独的文件"><a href="#Extract-dependencies-and-runtime-into-a-separate-file-将依赖和运行环境代码提取到一个单独的文件" class="headerlink" title="Extract dependencies and runtime into a separate file 将依赖和运行环境代码提取到一个单独的文件"></a>Extract dependencies and runtime into a separate file 将依赖和运行环境代码提取到一个单独的文件</h3><h4 id="Dependencies-依赖包"><a href="#Dependencies-依赖包" class="headerlink" title="Dependencies 依赖包"></a>Dependencies 依赖包</h4><p>App 依赖通常情况下趋向于比实际 app 内代码中更少的变化。如果你将他们移到独立的文件中,浏览器将可以把他们独立缓存起来 - 同时不会每次 app 代码改变时重新下载。</p><blockquote><p>Key Term: 在 webpack 的技术中,利用 app 代码拆分文件被称为 <code>chunks</code>。我们后面会用到这个名词。</p></blockquote><p>为了将依赖包提取到单独的 chunk 中,下面分为三步:</p><p>1.使用<code>[name].[chunkname].js</code>替换<code>output</code>的文件名:</p><figure class="highlight javascript"><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></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"> output: {</span><br><span class="line"> <span class="comment">// Before</span></span><br><span class="line"> filename: <span class="string">'bundle.[chunkhash].js'</span>,</span><br><span class="line"> <span class="comment">// After</span></span><br><span class="line"> filename: <span class="string">'[name].[chunkhash].js'</span>,</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>当 webpack 构建应用时,它会用一个带有 chunk 的名称来替换<code>[name]</code>。如果没有添加<code>[name]</code>部分,我们不得不通过 chunks 之间的 hash 区别来比较他们的区别 - 那就太难了!</p><p>2.将<code>entry</code>转成一个对象:</p><figure class="highlight javascript"><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></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 class="comment">// Before</span></span><br><span class="line"> entry: <span class="string">'./index.js'</span>,</span><br><span class="line"> <span class="comment">// After</span></span><br><span class="line"> entry: {</span><br><span class="line"> main: <span class="string">'./index.js'</span>,</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>在这段代码中,”main” 对象是一个 chunk 的名字。这个名字将会被步骤 1 里面的 <code>[name]</code>代替。目前为止,如果你构建一个 app,chunk 就会包括整个 app 的代码 - 就像我们没有做这些步骤一样。但是很快就会产生变化。</p><p>3.添加 <a href="https://webpack.js.org/plugins/commons-chunk-plugin/" target="_blank" rel="noopener"><code>CommonsChunkPlugin</code></a>:</p><figure class="highlight javascript"><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="built_in">module</span>.exports = {</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> <span class="comment">// A name of the chunk that will include the dependencies.</span></span><br><span class="line"> <span class="comment">// This name is substituted in place of [name] from step 1</span></span><br><span class="line"> name: <span class="string">'vendor'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// A function that determines which modules to include into this chunk</span></span><br><span class="line"> minChunks: <span class="function"><span class="params">module</span> =></span> <span class="built_in">module</span>.context &&</span><br><span class="line"> <span class="built_in">module</span>.context.includes(<span class="string">'node_modules'</span>),</span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>插件将包括全部<code>node_modules</code>路径下的 modules 同时将他们移到一个单独的文件中,这个文件被称为 <code>vendor.[chunkhash].js</code>。</p><p>完成了上面的步骤,每一次 build 都会生成两个文件。浏览器会将他们单独缓存 - 以便代码发生改变时重新下载。</p><figure class="highlight bash"><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">$ webpack</span><br><span class="line">Hash: ac01483e8fec1fa70676</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 3816ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main</span><br><span class="line">./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor</span><br></pre></td></tr></table></figure><h4 id="Webpack-runtime-code"><a href="#Webpack-runtime-code" class="headerlink" title="Webpack runtime code"></a>Webpack runtime code</h4><p>不幸的是,仅仅抽取<code>vendor</code>是不够的。如果你试图在应用代码中修改一些东西:</p><figure class="highlight javascript"><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><br><span class="line">…</span><br><span class="line"></span><br><span class="line"><span class="comment">// E.g. add this:</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'Wat'</span>);</span><br></pre></td></tr></table></figure><p>你会注意到<code>vendor</code>的 hash 值也会改变:</p><figure class="highlight bash"><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"> Asset Size Chunks Chunk Names</span><br><span class="line">./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight bash"><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"> Asset Size Chunks Chunk Names</span><br><span class="line">./vendor.e6ea4504d61a1cc1c60b.js 47 kB 1 [emitted] vendor</span><br></pre></td></tr></table></figure><p>发生这样的事是因为 webpack 打包时,一部分 modules 的代码,拥有 <a href="https://webpack.js.org/concepts/manifest/" target="_blank" rel="noopener"><em>a runtime</em></a> - 管理模块执行一部分代码。当你将代码拆分成多个文件时,这小部分代码在 chunk ids 和 匹配的文件之间开始了一个映射:</p><figure class="highlight javascript"><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 class="comment">// vendor.e6ea4504d61a1cc1c60b.js</span></span><br><span class="line">script.src = __webpack_require__.p + chunkId + <span class="string">"."</span> + {</span><br><span class="line"> <span class="string">"0"</span>: <span class="string">"2f2269c7f0a55a5c1871"</span></span><br><span class="line">}[chunkId] + <span class="string">".js"</span>;</span><br></pre></td></tr></table></figure><p>Webpack 将最新生成的 chunk 包含在这个 runtime 内,这个 chunk 就是我们代码中的<code>vendor</code>。与此同时每一次任何 <code>chunk</code> 的改变,这一小部分代码也改变,导致整个 <code>vendor</code> <code>chunk</code> 也会改变。</p><p>为了解决这个问题,我们将 runtime 转义到一个独立的文件中,通过<code>CommonsChunkPlugin</code>创建一个额外的空的 chunk:</p><figure class="highlight javascript"><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="comment">// webpack.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="keyword">new</span> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> name: <span class="string">'vendor'</span>,</span><br><span class="line"></span><br><span class="line"> minChunks: <span class="function"><span class="params">module</span> =></span> <span class="built_in">module</span>.context &&</span><br><span class="line"> <span class="built_in">module</span>.context.includes(<span class="string">'node_modules'</span>),</span><br><span class="line"> }),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// This plugin must come after the vendor one (because webpack</span></span><br><span class="line"> <span class="comment">// includes runtime into the last chunk)</span></span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> name: <span class="string">'runtime'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// minChunks: Infinity means that no app modules</span></span><br><span class="line"> <span class="comment">// will be included into this chunk</span></span><br><span class="line"> minChunks: <span class="literal">Infinity</span>,</span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>完成这一部分改变,每一次 build 都将生成三个文件:</p><figure class="highlight bash"><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">$ webpack</span><br><span class="line">Hash: ac01483e8fec1fa70676</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 3816ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main</span><br><span class="line"> ./vendor.26886caf15818fa82dfa.js 46 kB 1 [emitted] vendor</span><br><span class="line">./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime</span><br></pre></td></tr></table></figure><p>将他们反过来顺序添加到 index.html 中,你就搞定了:</p><figure class="highlight html"><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 class="comment"><!-- index.html --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./runtime.79f17c27b335abc7aaf4.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./vendor.26886caf15818fa82dfa.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./main.00bab6fd3100008a42b0.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><h4 id="Further-reading-5"><a href="#Further-reading-5" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li>Webpack guide <a href="https://webpack.js.org/guides/caching/" target="_blank" rel="noopener">on long term caching</a></li><li>Webpack docs <a href="https://webpack.js.org/concepts/manifest/" target="_blank" rel="noopener">about webpack runtime and manifest</a></li><li><a href="https://medium.com/webpack/webpack-bits-getting-the-most-out-of-the-commonschunkplugin-ab389e5f318" target="_blank" rel="noopener">“Getting the most out of the CommonsChunkPlugin”</a></li></ul><h3 id="Inline-webpack-runtime-to-save-an-extra-HTTP-request-内联-webpack-runtime-节省额外的-HTTP-请求"><a href="#Inline-webpack-runtime-to-save-an-extra-HTTP-request-内联-webpack-runtime-节省额外的-HTTP-请求" class="headerlink" title="Inline webpack runtime to save an extra HTTP request 内联 webpack runtime 节省额外的 HTTP 请求"></a>Inline webpack runtime to save an extra HTTP request 内联 webpack runtime 节省额外的 HTTP 请求</h3><p>为了做的更好,尽力把 webpack runtime 内联在 HTML 请求里。下面举例:</p><figure class="highlight html"><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"><span class="comment"><!-- index.html --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./runtime.79f17c27b335abc7aaf4.js"</span>></span><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p>这样做:</p><figure class="highlight html"><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 class="comment"><!-- index.html --></span></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="undefined"></span></span><br><span class="line"><span class="actionscript">!<span class="function"><span class="keyword">function</span><span class="params">(e)</span></span>{<span class="function"><span class="keyword">function</span> <span class="title">n</span><span class="params">(r)</span></span>{<span class="keyword">if</span>(t[r])<span class="keyword">return</span> t[r].exports;…}} ([]);</span></span><br><span class="line"><span class="undefined"></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure><p>这个 runtime 很小,内联它可以帮助你节省 HTTP 请求(尤其对 HTTP/1 重要;但是在 HTTP/2 就没有那么重要了,但是仍能够提高效率)。</p><p>下面就来看看如何做。</p><h4 id="如果使用-HtmlWebpackPlugin-来生成-HTML"><a href="#如果使用-HtmlWebpackPlugin-来生成-HTML" class="headerlink" title="如果使用 HtmlWebpackPlugin 来生成 HTML"></a>如果使用 HtmlWebpackPlugin 来生成 HTML</h4><p>如果使用 <a href="https://github.com/jantimon/html-webpack-plugin" target="_blank" rel="noopener"><code>HtmlWebpackPlugin</code></a> 来生成 HTML 文件,<a href="https://github.com/rohitlodha/html-webpack-inline-chunk-plugin" target="_blank" rel="noopener"><code>InlineChunkWebpackPlugin</code></a> 就足够了。</p><h4 id="如果使用自己的定制服务逻辑来生成-HTML"><a href="#如果使用自己的定制服务逻辑来生成-HTML" class="headerlink" title="如果使用自己的定制服务逻辑来生成 HTML"></a>如果使用自己的定制服务逻辑来生成 HTML</h4><p>1.将 <code>runtime</code> 名称改成静态的明确的文件名:</p><figure class="highlight javascript"><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="built_in">module</span>.exports = {</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> name: <span class="string">'runtime'</span>,</span><br><span class="line"> minChunks: <span class="literal">Infinity</span>,</span><br><span class="line"> filename: <span class="string">'runtime.js'</span>,</span><br><span class="line"> <span class="comment">// → Now the runtime file will be called</span></span><br><span class="line"> <span class="comment">// “runtime.js”, not “runtime.79f17c27b335abc7aaf4.js”</span></span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>2.以方便的方式将 runtime.js 嵌入进去。比如:Node.js 和 Express</p><figure class="highlight javascript"><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">// server.js</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> runtimeContent = fs.readFileSync(<span class="string">'./runtime.js'</span>, <span class="string">'utf-8'</span>);</span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/'</span>, (req, res) => {</span><br><span class="line"> res.send(<span class="string">`</span></span><br><span class="line"><span class="string"> …</span></span><br><span class="line"><span class="string"> <script><span class="subst">${runtimeContent}</span></script></span></span><br><span class="line"><span class="string"> …</span></span><br><span class="line"><span class="string"> `</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="懒加载"><a href="#懒加载" class="headerlink" title="懒加载"></a>懒加载</h3><p>有时候,页面拥有或多或少的部分:</p><ul><li>如果你在 YouTube 上加载一个视频页面,相比评论区域你更在乎视频区域。这就是视频要比评论区域重要。</li><li>如果你在一个新闻网站打开一个报道,相比广告区域你更关心文章的内容。这就是文字比广告更重要。</li></ul><p>在这些案例中,通过仅下载最重要的部分,懒加载剩余区域能够提升最初的加载性能。使用 <a href="https://webpack.js.org/api/module-methods/#import-" target="_blank" rel="noopener">the <code>import()</code> function</a> 和 <a href="https://webpack.js.org/guides/code-splitting/" target="_blank" rel="noopener">code-splitting</a> 解决这个问题:</p><figure class="highlight javascript"><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">// videoPlayer.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">renderVideoPlayer</span>(<span class="params"></span>) </span>{ … }</span><br><span class="line"></span><br><span class="line"><span class="comment">// comments.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">renderComments</span>(<span class="params"></span>) </span>{ … }</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">import</span> {renderVideoPlayer} <span class="keyword">from</span> <span class="string">'./videoPlayer'</span>;</span><br><span class="line">renderVideoPlayer();</span><br><span class="line"></span><br><span class="line"><span class="comment">// …Custom event listener</span></span><br><span class="line">onShowCommentsClick(<span class="function"><span class="params">()</span> =></span> {</span><br><span class="line"> <span class="keyword">import</span>(<span class="string">'./comments'</span>).then(<span class="function">(<span class="params">comments</span>) =></span> {</span><br><span class="line"> comments.renderComments();</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p><code>import()</code>明确表示你期望动态地加载独立的 module。当 webpack 看到 <code>import('./module.js')</code>时,他就会将这个 module 移到独立的 chunk 中:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">$ webpack</span><br><span class="line">Hash: 39b2a53cb4e73f0dc5b2</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 4273ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]</span><br><span class="line"> ./main.f7e53d8e13e9a2745d6d.js 60 kB 1 [emitted] main</span><br><span class="line"> ./vendor.4f14b6326a80f4752a98.js 46 kB 2 [emitted] vendor</span><br><span class="line">./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime</span><br></pre></td></tr></table></figure><p>并且只在代码执行到 <code>import()</code> 才会下载。</p><p>这将会让 main bundle 更小,提升初始加载的时间。更重要的是改进缓存 - 如果你修改 main chunk 的代码,其他部分的 chunk 也不会受影响。</p><blockquote><p>Note: 如果使用 Babel 编译代码,你会因为 Babel 还不认识 <em>import()</em> 而遇到语法错误抛出来。可以使用 <a href="https://www.npmjs.com/package/babel-plugin-syntax-dynamic-import" target="_blank" rel="noopener"><code>syntax-dynamic-import</code></a> 解决这个错误。</p></blockquote><h4 id="Further-reading-6"><a href="#Further-reading-6" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li>Webpack docs <a href="https://webpack.js.org/api/module-methods/#import-" target="_blank" rel="noopener">for the <code>import()</code> function</a></li><li>The JavaScript proposal <a href="https://github.com/tc39/proposal-dynamic-import" target="_blank" rel="noopener">for implementing the <code>import()</code> syntax</a></li></ul><h3 id="Split-the-code-into-routes-and-pages-拆分代码到路由和页面中"><a href="#Split-the-code-into-routes-and-pages-拆分代码到路由和页面中" class="headerlink" title="Split the code into routes and pages 拆分代码到路由和页面中"></a>Split the code into routes and pages 拆分代码到路由和页面中</h3><p>如果你的应用拥有多个路由或者页面,但是代码中只有单独一个 JS 文件(一个单独的 main chunk),这看起来你正在每一个请求中节省额外的 bytes 带宽。举个例子,当用户正在访问你网站的首页:</p><p><img src="https://img10.360buyimg.com/uba/jfs/t17272/291/466883786/44644/f5b82d7c/5a8159b0N4fe9f50d.png" alt="site-home-page"></p><p>他们并不需要加载另外不同的页面上渲染文章标题的的代码 - 但是他们还是会加载到这段代码。更严重的是如果用户经常只访问首页,同时你还经常改变渲染文章标题的代码,webpack 将会对整个 bundle 失效 - 用户每次都会重复下载全部 app 的代码。</p><p>如果我们将代码拆分到页面里(或者单页面应用的路由里),用户就会只下载对他有意义的代码。更好的是,浏览器也会更好地缓存代码:当你改变首页的代码时,webpack 只会让相匹配的 chunk 失效。</p><h4 id="For-single-page-apps-对于单页面应用"><a href="#For-single-page-apps-对于单页面应用" class="headerlink" title="For single-page apps 对于单页面应用"></a>For single-page apps 对于单页面应用</h4><p>通过路由拆分带页面引用,使用 <code>import()</code>(看看 <a href="https://developers.google.com/web/fundamentals/performance/webpack/use-long-term-caching#lazy-loading" target="_blank" rel="noopener">“Lazy-load code that you don’t need right now”</a>这部分)。如果你在使用一个框架,现在已经有成熟的方案:</p><ul><li><a href="https://reacttraining.com/react-router/web/guides/code-splitting" target="_blank" rel="noopener">“Code Splitting”</a> in <code>react-router</code>‘s docs (for React)</li><li><a href="https://router.vuejs.org/en/advanced/lazy-loading.html" target="_blank" rel="noopener">“Lazy Loading Routes”</a> in <code>vue-router</code>‘s docs (for Vue.js)</li></ul><h4 id="For-traditional-multi-page-apps-对于传统的多页面应用"><a href="#For-traditional-multi-page-apps-对于传统的多页面应用" class="headerlink" title="For traditional multi-page apps 对于传统的多页面应用"></a>For traditional multi-page apps 对于传统的多页面应用</h4><p>通过页面拆分传统多页面应用,可以使用 webpack 的 <a href="https://webpack.js.org/concepts/entry-points/" target="_blank" rel="noopener"><em>entry points</em></a> 。如果你的应用有三种页面:主页、文章页、用户账户页,那就分厂三个 entries:</p><figure class="highlight javascript"><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 class="built_in">module</span>.exports = {</span><br><span class="line"> entry: {</span><br><span class="line"> home: <span class="string">'./src/Home/index.js'</span>,</span><br><span class="line"> article: <span class="string">'./src/Article/index.js'</span>,</span><br><span class="line"> profile: <span class="string">'./src/Profile/index.js'</span></span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>对于每一个 entry 文件,webpack 将构建出独立的依赖树,并且声称一个 bundle,它将通过 entry 来只包括用到的 modules:</p><figure class="highlight bash"><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">$ webpack</span><br><span class="line">Hash: 318d7b8490a7382bf23b</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 4273ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]</span><br><span class="line"> ./home.91b9ed27366fe7e33d6a.js 18 kB 1 [emitted] home</span><br><span class="line">./article.87a128755b16ac3294fd.js 32 kB 2 [emitted] article</span><br><span class="line">./profile.de945dc02685f6166781.js 24 kB 3 [emitted] profile</span><br><span class="line"> ./vendor.4f14b6326a80f4752a98.js 46 kB 4 [emitted] vendor</span><br><span class="line">./runtime.318d7b8490a7382bf23b.js 1.45 kB 5 [emitted] runtime</span><br></pre></td></tr></table></figure><p>因此,如果仅仅是文章页使用 <em>Lodash</em> ,<em>home</em> 和 <em>profile</em> 的 bundle 将不会包含 lodash - 同时用户也不会在访问首页的时候下载到这个库。</p><p>拆分依赖树也有缺点。如果两个 entry points 都用到了 <em>loadash</em> ,同时你没有在 <em>vendor</em> 移除掉依赖,两个 entry points 将包括两个重复的 <em>lodash</em> 。我们可以使用<a href="https://webpack.js.org/plugins/commons-chunk-plugin/" target="_blank" rel="noopener"><code>CommonsChunkPlugin</code></a>来解决这个问题 - 它会将通用的依赖转移到一个独立的文件中:</p><figure class="highlight javascript"><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="built_in">module</span>.exports = {</span><br><span class="line"> plugins: [</span><br><span class="line"> <span class="keyword">new</span> webpack.optimize.CommonsChunkPlugin({</span><br><span class="line"> <span class="comment">// A name of the chunk that will include the common dependencies</span></span><br><span class="line"> name: <span class="string">'common'</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The plugin will move a module into a common file</span></span><br><span class="line"> <span class="comment">// only if it’s included into `minChunks` chunks</span></span><br><span class="line"> <span class="comment">// (Note that the plugin analyzes all chunks, not only entries)</span></span><br><span class="line"> minChunks: <span class="number">2</span>, <span class="comment">// 2 is the default value</span></span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>随意使用<code>minChunks</code>的值来找到最优的选项。通常情况下,你想要它尽可能体积小,但它会增加 chunks 的数量。举个例子,3 个 chunk,<code>minChunks</code> 可能是 2 个,但是 30 个 chunk,它可能是 8 个 - 因为如果你把它设置成 2 ,过多的 modules 将会打包进一个通用文件中,文件更臃肿。</p><h4 id="Further-reading-7"><a href="#Further-reading-7" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li>Webpack docs <a href="https://webpack.js.org/concepts/entry-points/" target="_blank" rel="noopener">about the concept of entry points</a></li><li>Webpack docs <a href="https://webpack.js.org/plugins/commons-chunk-plugin/" target="_blank" rel="noopener">about the CommonsChunkPlugin</a></li><li><a href="https://medium.com/webpack/webpack-bits-getting-the-most-out-of-the-commonschunkplugin-ab389e5f318" target="_blank" rel="noopener">“Getting the most out of the CommonsChunkPlugin”</a></li></ul><h3 id="Make-module-ids-more-stable-让-module-ide-更稳定"><a href="#Make-module-ids-more-stable-让-module-ide-更稳定" class="headerlink" title="Make module ids more stable 让 module ide 更稳定"></a>Make module ids more stable 让 module ide 更稳定</h3><p>当编译代码时,webpack 会分配给每一个 module 一个 ID。之后,这些 ID 就会被<code>require()</code>引用到 bundle 内部。你可以在编译输出的右侧在 moudle 路径之前看到这些 ID:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">$ webpack</span><br><span class="line">Hash: df3474e4f76528e3bbc9</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 2150ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]</span><br><span class="line"> ./main.4e50a16675574df6a9e9.js 60 kB 1 [emitted] main</span><br><span class="line"> ./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor</span><br><span class="line">./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime</span><br></pre></td></tr></table></figure><p>↓</p><figure class="highlight bash"><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">[0] ./index.js 29 kB {1} [built]</span><br><span class="line">[2] (webpack)/buildin/global.js 488 bytes {2} [built]</span><br><span class="line">[3] (webpack)/buildin/module.js 495 bytes {2} [built]</span><br><span class="line">[4] ./comments.js 58 kB {0} [built]</span><br><span class="line">[5] ./ads.js 74 kB {1} [built]</span><br><span class="line"> + 1 hidden module</span><br></pre></td></tr></table></figure><p>默认情况下,这些 ID 是使用计数器计算出来的(比如第一个 module 是 ID 0,第二个 moudle 就是 ID 1,以此类推)。这样的问题就在于当你新增一个 module 事,它会出现在原来 module 列表中的中间,改变后面所有 module 的 ID:</p><figure class="highlight bash"><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">$ webpack</span><br><span class="line">Hash: df3474e4f76528e3bbc9</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 2150ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.5c82c0f337fcb22672b5.js 22 kB 0 [emitted]</span><br><span class="line"> ./main.0c8b617dfc40c2827ae3.js 82 kB 1 [emitted] main</span><br><span class="line"> ./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor</span><br><span class="line">./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime</span><br><span class="line"> [0] ./index.js 29 kB {1} [built]</span><br><span class="line"> [2] (webpack)/buildin/global.js 488 bytes {2} [built]</span><br><span class="line"> [3] (webpack)/buildin/module.js 495 bytes {2} [built]</span><br></pre></td></tr></table></figure><p>↓ 我们增加一个新 module</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[4] ./webPlayer.js 24 kB {1} [built]</span><br></pre></td></tr></table></figure><p>↓ 现在看这里做了什么!<code>comments.js</code>现在的 ID 由 4 变成了 5</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[5] ./comments.js 58 kB {0} [built]</span><br></pre></td></tr></table></figure><p>↓ <code>ads.js</code> 的 ID 由 5 变成 6</p><figure class="highlight bash"><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">[6] ./ads.js 74 kB {1} [built]</span><br><span class="line"> + 1 hidden module</span><br></pre></td></tr></table></figure><p>这将使包含或依赖于具有更改ID的模块的所有块无效 - 即使它们的实际代码没有更改。在我们的代码中,_0_这个 chunk 和 <em>main</em> chunk 都会失效 - 只有<em>main</em>才应该失效。</p><p>使用<a href="https://webpack.js.org/plugins/hashed-module-ids-plugin/" target="_blank" rel="noopener"><code>HashedModuleIdsPlugin</code></a>插件改变module ID 如何计算来解决这个问题。它利用 module 路径的 hash 来替换掉计数器:</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">$ webpack</span><br><span class="line">Hash: df3474e4f76528e3bbc9</span><br><span class="line">Version: webpack 3.8.1</span><br><span class="line">Time: 2150ms</span><br><span class="line"> Asset Size Chunks Chunk Names</span><br><span class="line"> ./0.6168aaac8461862eab7a.js 22.5 kB 0 [emitted]</span><br><span class="line"> ./main.a2e49a279552980e3b91.js 60 kB 1 [emitted] main</span><br><span class="line"> ./vendor.ff9f7ea865884e6a84c8.js 46 kB 2 [emitted] vendor</span><br><span class="line">./runtime.25f5d0204e4f77fa57a1.js 1.45 kB 3 [emitted] runtime</span><br></pre></td></tr></table></figure><p>↓ Here</p><figure class="highlight bash"><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">[3IRH] ./index.js 29 kB {1} [built]</span><br><span class="line">[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]</span><br><span class="line">[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]</span><br><span class="line">[LbCc] ./webPlayer.js 24 kB {1} [built]</span><br><span class="line">[lebJ] ./comments.js 58 kB {0} [built]</span><br><span class="line">[02Tr] ./ads.js 74 kB {1} [built]</span><br><span class="line"> + 1 hidden module</span><br></pre></td></tr></table></figure><p>有了这个方法,只有你重命名护着删除这个 moudle 它的 ID 才会变化。新的 modules 不会因为 module ID 互相影响。</p><p>启用这个插件,在配置中增加 <em>plugins</em>:</p><figure class="highlight javascript"><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">// webpack.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="keyword">new</span> webpack.HashedModuleIdsPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="Further-reading-8"><a href="#Further-reading-8" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li>Webpack docs <a href="https://webpack.js.org/plugins/hashed-module-ids-plugin/" target="_blank" rel="noopener">about the HashedModuleIdsPlugin</a></li></ul><h3 id="Summing-up"><a href="#Summing-up" class="headerlink" title="Summing up"></a>Summing up</h3><ul><li>Cache the bundle and differentiate between them by changing their names</li><li>Split the bundle into app code, vendor code and runtime</li><li>Inline the runtime to save an HTTP request</li><li>Lazy-load non-critical code with <code>import</code></li><li>Split code by routes/pages to avoid loading unnecessary stuff</li></ul><hr><h2 id="Monitor-and-analyze-the-app-监控并分析"><a href="#Monitor-and-analyze-the-app-监控并分析" class="headerlink" title="Monitor and analyze the app 监控并分析"></a>Monitor and analyze the app 监控并分析</h2><p>作者 <a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p><p>即使当你配置好你的 webpack 让你的应用尽可能体积较小的时候,跟踪这个应用就非常重要,同时了解里面包含了什么。除此之外,你安装一个依赖,它将让你的 app 增加两倍大小 - 但并没有注意到这个问题!</p><p>这一部分就来讲解一些能够帮助你理解你的 bundle 的工具。</p><h3 id="Keep-track-of-the-bundle-size-跟踪打包的体积"><a href="#Keep-track-of-the-bundle-size-跟踪打包的体积" class="headerlink" title="Keep track of the bundle size 跟踪打包的体积"></a>Keep track of the bundle size 跟踪打包的体积</h3><p>在开发时可以使用<a href="https://github.com/FormidableLabs/webpack-dashboard/" target="_blank" rel="noopener">webpack-dashboard</a>和命令行<a href="https://github.com/siddharthkp/bundlesize" target="_blank" rel="noopener">bundlesize</a> 来监控 app 的体积。</p><h4 id="webpack-dashboard"><a href="#webpack-dashboard" class="headerlink" title="webpack-dashboard"></a>webpack-dashboard</h4><p><a href="https://github.com/FormidableLabs/webpack-dashboard/" target="_blank" rel="noopener">webpack-dashboard</a>可以通过依赖体积大小、进程和其他细节来改进 webpack 的输出。</p><p><img src="http://img30.360buyimg.com/uba/jfs/t16294/169/2125639991/38263/ad862ba/5a8159c0N0da38a60.png" alt="webpack-dashboard"></p><p>这个 dashborad 帮助我们跟踪大型依赖 - 如果你增加一个依赖,你就立刻能在 Modules section 始终看到它!</p><p>启用这个功能,需要安装 <em>webpack-dashboard</em> 包:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install webpack-dashboard --save-dev</span><br></pre></td></tr></table></figure><p> 同时在配置的 plugins 增加:</p><figure class="highlight javascript"><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 class="keyword">const</span> DashboardPlugin = <span class="built_in">require</span>(<span class="string">'webpack-dashboard/plugin'</span>);</span><br><span class="line"></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> DashboardPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>或者如果正在使用基于 Express dev server 可以使用 <code>compiler.apply()</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">compiler.apply(new DashboardPlugin());</span><br></pre></td></tr></table></figure><p>多尝试 dashboard 找出改进的地方!比如,在 modules section 滚动找到那个库体积过大,把它替换成小的可替代的库。</p><h4 id="bundlesize"><a href="#bundlesize" class="headerlink" title="bundlesize"></a>bundlesize</h4><p><a href="https://github.com/siddharthkp/bundlesize" target="_blank" rel="noopener">bundlesize</a> 可以验证 webpack assets 不超过指定的大小。通过自动化 CI 就可以知晓 app 是否变的过于臃肿:</p><p><img src="https://img30.360buyimg.com/uba/jfs/t15808/165/2111159728/110001/633e93ab/5a8159cdN1c575a1f.jpg" alt="bundlesize"></p><p>配置如下:</p><h5 id="Find-out-the-maximum-sizes-找出最大体积"><a href="#Find-out-the-maximum-sizes-找出最大体积" class="headerlink" title="Find out the maximum sizes 找出最大体积"></a><strong>Find out the maximum sizes</strong> 找出最大体积</h5><p>1.分析 app 尽可能减小体积,执行生产环境的 build。<br>2.在<code>package.json</code>中增加<code>bundlesize</code>部分:</p><figure class="highlight json"><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">// package.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"bundlesize"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"path"</span>: <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><p>3.使用<code>npx</code>执行<code>bundlesize</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx bundlesize</span><br></pre></td></tr></table></figure><p>它就会将每一个文件的 gzip 压缩后的体积打印出来:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PASS ./dist/icon256.6168aaac8461862eab7a.png: 10.89KB PASS./dist/icon512.c3e073a4100bd0c28a86.png: 13.1KB PASS./dist/main.0c8b617dfc40c2827ae3.js: 16.28KB PASS./dist/vendor.ff9f7ea865884e6a84c8.js: 31.49KB</span><br></pre></td></tr></table></figure><p>4.每一个体积增加10-20%,你将得到最大体积。这个10-20%的幅度可以让你像往常一样开发应用程序,同时警告你,当它的大小增长太多。</p><h5 id="Enable-bundlesize-启用-bundlesize"><a href="#Enable-bundlesize-启用-bundlesize" class="headerlink" title="Enable bundlesize 启用 bundlesize"></a><strong>Enable <code>bundlesize</code></strong> 启用 bundlesize</h5><p>5.安装<em>bundlesize</em>开发依赖</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install bundlesize --save-dev</span><br></pre></td></tr></table></figure><p>6.在<code>package.json</code>中的<code>bundlesize</code>部分,声明具体的最大值。对于某一些文件(比如图片),你可以单独根据文件类型来设置最大体积大小,而不需要根据每一个文件:</p><figure class="highlight json"><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">// package.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"bundlesize"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"./dist/*.png"</span>,</span><br><span class="line"> <span class="attr">"maxSize"</span>: <span class="string">"16 kB"</span>,</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"./dist/main.*.js"</span>,</span><br><span class="line"> <span class="attr">"maxSize"</span>: <span class="string">"20 kB"</span>,</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"path"</span>: <span class="string">"./dist/vendor.*.js"</span>,</span><br><span class="line"> <span class="attr">"maxSize"</span>: <span class="string">"35 kB"</span>,</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>7.增加一个 npm 脚本来执行检查:</p><figure class="highlight json"><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">// package.json</span><br><span class="line">{</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"check-size"</span>: <span class="string">"bundlesize"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>8.配置自动化 CI 来在每一次 push 时执行<code>npm run check-size</code>做检查。(如果你在 Github 上开发项目,直接可以使用<a href="https://github.com/siddharthkp/bundlesize#2-build-status" target="_blank" rel="noopener">integrate <code>bundlesize</code> with GitHub</a>。)</p><p>这就全部了!现在如果你运行<code>npm run check-size</code>或者 push 代码,你就会看到输出的文件是否足够小:</p><p><img src="https://img14.360buyimg.com/uba/jfs/t14890/146/2205111432/17457/fa7f748a/5a8159dcN17378d16.png" alt="bundlesize-output-success"></p><p>或者下面失败的情况</p><p><img src="https://img11.360buyimg.com/uba/jfs/t16969/198/453213154/26368/834a1c7f/5a8159e8Nc1f5ffe8.png" alt="bundlesize-output-failure"></p><h4 id="Further-reading-9"><a href="#Further-reading-9" class="headerlink" title="Further reading"></a>Further reading</h4><ul><li>Alex Russell <a href="https://infrequently.org/2017/10/can-you-afford-it-real-world-web-performance-budgets/" target="_blank" rel="noopener">about the real-world loading time we should target</a></li></ul><h3 id="Analyze-why-the-bundle-is-so-large-分析-bundle-为什么这么大"><a href="#Analyze-why-the-bundle-is-so-large-分析-bundle-为什么这么大" class="headerlink" title="Analyze why the bundle is so large 分析 bundle 为什么这么大"></a>Analyze why the bundle is so large 分析 bundle 为什么这么大</h3><p>你想要深挖 bundle 内,看看里面具体哪些 module 占用多大空间。<a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" target="_blank" rel="noopener">webpack-bundle-analyzer</a></p><p>(Screen recording from <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" target="_blank" rel="noopener">github.com/webpack-contrib/webpack -bundle-analyzer</a>)</p><p>webpack-bundle-analyzer 可以扫描 bundle 同时构建一个查看内部的可视化窗口。使用这个可视化工具找到过大或者不必要的依赖。</p><p>使用这个分析器,需要安装<code>webpack-bundle-analyzer</code>包:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install webpack-bundle-analyzer --save-dev</span><br></pre></td></tr></table></figure><p>在 config 中增加插件:</p><figure class="highlight javascript"><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 class="keyword">const</span> BundleAnalyzerPlugin = <span class="built_in">require</span>(<span class="string">'webpack-bundle-analyzer'</span>).BundleAnalyzerPlugin;</span><br><span class="line"></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> BundleAnalyzerPlugin(),</span><br><span class="line"> ],</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>运行生产环境的 build 这个插件就会在浏览器中打开一个显示状态的页面。</p><p>默认情况下,这个页面会显示语法分析后的文件体积(在 bundle 出现的文件)。您可能想比较 gzip 的大小,因为这更接近实际用户的体验;使用左边的边栏来切换尺寸。</p><blockquote><p>Note: 如果你使用 <a href="https://webpack.js.org/plugins/module-concatenation-plugin/" target="_blank" rel="noopener">ModuleConcatenationPlugin</a>,它可能在webpack-bundle-analyzer输出时合并一部分 module,使得报告小一些细节。如果你使用这个插件,在执行分析的时候需要禁用掉。</p></blockquote><p>下面是报告中需要看什么:</p><ul><li><strong>大型依赖</strong> 为什么体积这么大?是否有更小的替代包(比如 Preact 替代 React)?用了全部代码(比如 Moment.js 包含大量的本地变量 <a href="https://github.com/GoogleChromeLabs/webpack-libs-optimizations#moment" target="_blank" rel="noopener">that are often not used and could be dropped</a>)?</li><li><strong>重复依赖</strong> 是否在不同文件中看到相同的库?(使用<em>CommonsChunkPlugin</em>将他们移到一个通用文件内)亦或是在同一个库中 bundle 拥有多个版本?</li><li><strong>相似依赖</strong> 是否存在有相似功能的相似库存在?(比如<em>moment</em>和<em>date-fns</em> 或者 <em>lodash</em> 和 <em>lodash-es</em>)尽力汇总成一个。</li></ul><p>同样的,也可以看看 Sean Larkin 的文章 <a href="https://medium.com/webpack/webpack-bits-getting-the-most-out-of-the-commonschunkplugin-ab389e5f318" target="_blank" rel="noopener">great analysis of webpack bundles</a>。</p><h3 id="Summing-up-1"><a href="#Summing-up-1" class="headerlink" title="Summing up"></a>Summing up</h3><ul><li>Use <code>webpack-dashboard</code> and <code>bundlesize</code> to stay tuned of how large your app is</li><li>Dig into what builds up the size with <code>webpack-bundle-analyzer</code></li></ul><hr><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>总结一下:</p><ul><li><strong>剔除不必要的体积</strong> 把所有的都压缩,剔除无用代码,增加依赖是保持谨慎小心。</li><li><strong>通过路由拆分代码</strong> 只在真正需要的时候才加载,其他的部分做来加载。</li><li><strong>缓存代码</strong> 应用程序的某些部分更新频率低于其他部分,将这些部分拆分成文件,以便在必要时仅重新下载。</li><li><strong>跟踪体积大小</strong> 使用 <a href="https://github.com/FormidableLabs/webpack-dashboard/" target="_blank" rel="noopener">webpack-dashboard</a> 和 <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" target="_blank" rel="noopener">webpack-bundle-analyzer</a> 监控你的 app。每隔几个月重新检查一下你的应用的性能。</li></ul><p>Webpack 不仅仅是一个帮助你更快创建 app 的工具。它还帮助使你的 app 成为 <a href="https://developers.google.com/web/progressive-web-apps/" target="_blank" rel="noopener">a Progressive Web App</a> ,你的应用拥有更好的体验以及自动化的填充工具就像<a href="https://developers.google.com/web/tools/lighthouse/" target="_blank" rel="noopener">Lighthouse</a>根据环境给出建议。</p><p>不要忘记阅读 <a href="https://webpack.js.org/guides/" target="_blank" rel="noopener">webpack docs</a> - 里面提供了大量的优化的信息。</p><p>多多练习 <a href="https://github.com/GoogleChromeLabs/webpack-training-project" target="_blank" rel="noopener">with the training app</a>!</p>]]></content>
<summary type="html">
<p>作者 <a href="https://developers.google.com/web/resources/contributors/addyosmani" target="_blank" rel="noopener">Addy Osmani</a>,<a href="https://developers.google.com/web/resources/contributors/iamakulov" target="_blank" rel="noopener">Ivan Akulov</a></p>
<p>原文 <a href="https://developers.google.com/web/fundamentals/performance/webpack/" target="_blank" rel="noopener">https://developers.google.com/web/fundamentals/performance/webpack/</a></p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="翻译" scheme="https://beanlee.github.io/tags/%E7%BF%BB%E8%AF%91/"/>
<category term="Webpack" scheme="https://beanlee.github.io/tags/Webpack/"/>
</entry>
<entry>
<title>【译】NPM vs Bower vs Browserify vs Gulp vs Grunt vs Webpack</title>
<link href="https://beanlee.github.io/posts/NPM-vs-Bower-vs-Browserify-vs-Gulp-vs-Grunt-vs-Webpack/"/>
<id>https://beanlee.github.io/posts/NPM-vs-Bower-vs-Browserify-vs-Gulp-vs-Grunt-vs-Webpack/</id>
<published>2016-06-17T06:35:33.000Z</published>
<updated>2018-06-20T01:31:47.000Z</updated>
<content type="html"><![CDATA[<p>这是一篇在 Stack overflow 看到的一篇问题和回答,对于自己有一些解惑</p><a id="more"></a><p><a href="http://stackoverflow.com/questions/35062852/npm-vs-bower-vs-browserify-vs-gulp-vs-grunt-vs-webpack" target="_blank" rel="noopener">Source Link</a></p><h2 id="作者问题"><a href="#作者问题" class="headerlink" title="作者问题"></a>作者问题</h2><blockquote><p>描述:我正在试着总结关于最流行的 Javascript 包管理器,打包器和任务执行器的知识。如果有错误请纠正我。</p></blockquote><ul><li><code>npm</code> & <code>bower</code> 是包管理工具。他们只是将依赖文件下载,并不知道在下载这些文件的基础上如何编译项目。他们知道的是在获取所有依赖之后去调用 <code>webpack</code>/<code>gulp</code>/<code>grunt</code> 。</li><li><code>bower</code> 很像 <code>npm</code> ,但是编译依赖树不在行(不像 <code>npm</code> 那样递归着进行)。意味着 <code>npm</code> 获取每一依赖(可能获取几次相同的文件),然而 <code>bower</code> 期望你手动去包含子依赖。有时候 <code>bower</code> 和 <code>npm</code> 可以一起被使用,分别作用前端和后端(在前端每一mb都很重要)。</li><li><code>glup</code> 和 <code>gulp</code> 是任务运行器,目的是将能够被自动化的所有事任务自动化执行。(比如编译css/sass,处理图片,打包还有最小化混淆代码)。</li><li><code>grunt</code> vs <code>gulp</code> (就像<code>maven</code> vs <code>gradle</code> 或是 配置 vs 编码)。Grunt 是基于分离独立的任务配置的,每一个任务开始/处理/关闭文件。Gulp 需要少量代码,并且基于 node streams,那允许它编译链(w/o重复打开相同文件)而且执行很快。</li><li><code>webpack</code> (<code>webpack-dev-server</code>) 对于我来说,它是任务执行器随着变化热加载,那些允许你忘记关于所有js/css的监视器。</li><li><code>npm</code>/<code>bower</code>+plugin 可以替代任务运行器。他们的能力经常交叉因此如果你需要使用 <code>gulp</code>/<code>grunt</code> 在 npm+plugin 之上时存在着不同的潜在影响。但是任务运行器处理复杂任务定义的更好(比如 “在每一个编译打包,从ES6转义成ES5,在所有浏览器仿真器上运行,制作镜像还有通过ftp部署到dropbox”)。</li><li><code>browserify</code> 允许为浏览器打包node模块。<code>browserify</code> vs <code>node's require</code> 就像 <a href="https://addyosmani.com/writing-modular-js/" target="_blank" rel="noopener">AMD vs CommonJS</a></li></ul><h3 id="Questions"><a href="#Questions" class="headerlink" title="Questions"></a>Questions</h3><ul><li>什么是 webpack 和 webpack-dev-server ?官方文档说它是模块打包器,但对于我而言,它知识任务运行工具。有什么不同?</li><li>你在哪里用到 browserify ?我们能不能和 node/ES6 一样做?</li><li>你什么时候在 npm + plugins 基础上使用 <code>gulp</code>/<code>grunt</code> ?</li></ul><h2 id="Beat-Answer"><a href="#Beat-Answer" class="headerlink" title="Beat Answer"></a>Beat Answer</h2><p><code>Webpack</code> 和 <code>Browerify</code> 做了很多相同的工作,比如用于在一个浏览器环境钟打包你的模块。这个模块就是一个 Node 特征,它不在浏览器中存在,并且 ES6 中的 <code>import</code> 还没有在任何浏览器中实现,这就是为什么需要被打包。但是,他们在很多方式上是有区别的,<code>Webpack</code> 默认提供很多工具(比如代码拆分),<code>Browerify</code> 只能在下载插件之后才能做这些,但是使用这两种都能实现相似的效果。它取决于个人习惯(我常使用 <code>Webpack</code> )。<code>Webpack</code> 不是一个任务运行器,它只是你通过CLI或任务运行器直接运行文件的一个中间过程器。</p><p><code>Webpack-dev-server</code> 提供类似于 <code>Browser-sync</code> - 它是一个你可以将你的 app 部署的服务,并且验证你的前端开发进程直接通过 dev-server 自动刷新浏览器或者在没有热部署的情况下传播变化(比如 React components)。</p><p>我为了项目的完整和简单的任务编写已经使用<code>Gulp</code>,但是我后来发现我既不需要<code>Gulp</code>也不需要<code>Grunt</code>。所有我需要的都可以使用<code>npm</code>组合脚本去运行第三方工具利用它们的 API 完成。在<code>Gulp</code>,<code>Grunt</code>和<code>npm script</code>之间选择取决于你的需要、JS 经验和你工作时的开发经验。</p><p>当然<code>Gulp</code>中的任务是易读的,甚至与JS不是很相似,它还是引用和学习另一个工具,并且我个人倾向于缩小我的依赖并且保持简单。另外一面,使用npm组合脚本和运行文件(配置和执行<code>Webpack</code>文件中函数)替代这些任务是更具有挑战性的。但是重要的是他们三个的结果是相同的。</p><p>举例说我建议你看一看 <a href="https://github.com/kriasoft/react-starter-kit" target="_blank" rel="noopener">react starter project</a> ,它可以向你很好的展示<code>npm</code>组合脚本,<code>Webpack</code>和<code>browser-sync</code>。即使你可以处理你的源文件,如果你愿意,你可以使用<code>Gulp</code>或者<code>Grunt</code>运行你的开发服务,我更喜欢第一个选项。</p>]]></content>
<summary type="html">
<p>这是一篇在 Stack overflow 看到的一篇问题和回答,对于自己有一些解惑</p>
</summary>
<category term="前端技术" scheme="https://beanlee.github.io/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
<category term="翻译" scheme="https://beanlee.github.io/tags/%E7%BF%BB%E8%AF%91/"/>
<category term="Webpack" scheme="https://beanlee.github.io/tags/Webpack/"/>
<category term="NPM" scheme="https://beanlee.github.io/tags/NPM/"/>
</entry>
</feed>