-
Notifications
You must be signed in to change notification settings - Fork 0
/
rss.xml
1816 lines (1767 loc) · 223 KB
/
rss.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
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Blog Name]]></title><description><![CDATA[Lorem ipsum dolor sit amet, ius sonet omnesque facilisis an, etiam mediocrem ad ius. Ius ei graece altera, at suavitate adolescens per.]]></description><link>http://acyort.github.io</link><generator>RSS for Node</generator><lastBuildDate>Thu, 04 Feb 2016 07:17:11 GMT</lastBuildDate><atom:link href="http://acyort.github.io/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Thu, 04 Feb 2016 07:17:09 GMT</pubDate><item><title><![CDATA[天猫双11前端分享系列(七):如何精确识别终端]]></title><link>http://acyort.github.io/posts/2016/01/127363187.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2016/01/127363187.html</guid><dc:creator><![CDATA[maisui99]]></dc:creator><pubDate>Mon, 18 Jan 2016 16:00:00 GMT</pubDate><description><![CDATA[<p>首先,要先说声抱歉,因为,其实目前我们还没有做到精确地做到识别99%的终端设别,其中原因,一部分是因为终端类型和UA实在难以覆盖,另外一部分原因也是因为使用了一些错误的识别策略。</p>
<p>注1:后面会大量出现detector,其实就是我们给内部终端识别工具起的一个名字。
注2:天猫页面一直在实施一个url对应多份不同终端的页面,所以终端识别非常重要。</p>
<h2 id="-">在哪一层进行识别</h2>
<h3 id="-">初期方案</h3>
<p>由于当时处于业务mobile页面发展的初期,且大部分页面还在基于php进行开发,所以诞生了detector的第一个版本,php版本。识别逻辑也比较简单,纯正则匹配UA。</p>
<p>后来,node业务渐渐的增加,我们又重写了一份node版本,其中为了保持终端识别能力的一致,用于匹配UA的正则统一放到了一份json文件里,php和node都统一来读这一份文件。</p>
<p>正则主要还是收集了github上各种比较成熟的识别方案综合出来的。</p>
<h3 id="-">改造期</h3>
<p>在2014年底,为了保证pad用户的访问质量,我们对终端识别的工具进行了非常大的改造。其中,一直在坚持的一点就是将识别能力放到服务端进行。</p>
<p>当时面临的一个难题是,安卓pad和安卓phone之间的UA并没有差异,特别是4.2之前的版本,无法通过UA进行识别,但是又希望能够让用户在安卓pad上看到更合适的PC版本,我们设计并产出了MED的终端硬件信息获取方案。</p>
<h4 id="med">MED</h4>
<p>MED的运行逻辑其实很简单:用户第一次访问的时候,在nginx端插入一个脚本,计算设备宽高、像素宽高、是否支持触摸等信息,然后记录到cookie中,第二次访问的时候,nginx就可以拿到用户的终端信息了。</p>
<p>于是,我们就可以知道用户的物理宽度了。可惜这里埋了一些坑。</p>
<ol>
<li>物理宽度的计算用到screen.width/screen.height,但是不同厂商的安卓设备,在不同的浏览器或者webview下给了各种不同的值,而并不是屏幕的分辨率。</li>
<li>安卓手机的屏幕越做越大,和PAD之间的分界线越来越模糊。</li>
</ol>
<h4 id="nginx-detector">nginx-detector</h4>
<p>由于nginx端包含了拿到终端硬件信息的能力,那么这里就有两个选择</p>
<ol>
<li>将拿到的信息写到http头里,转发给应用的php/node detector</li>
<li>直接在nginx层进行识别,将识别结果转发给下游的应用</li>
</ol>
<p>其实,这里并没有太多的纠结,服务端语言太多,针对各种不同的语言维护一份实在不太现实。于是,我们选择在nginx层做这些事情,这里用到了开源的tengine模块<a href="http://tengine.taobao.org/document/http_user_agent.html">http_user_agent</a>。</p>
<p>具体的识别规则也是从正则切换到了nginx配置文件。整个流程就优化为</p>
<ol>
<li>nginx对UA进行解析</li>
<li>解析完成后,nginx再结合硬件信息,如果物理设备宽度较大,则识别为pad</li>
</ol>
<p>这个方案逐渐部署到了各个应用上,支持了包括频道、活动、搜索等应用的终端识别,也顺利经过了双11的考验。</p>
<h3 id="-">惊喜</h3>
<p>nginx层做解析带来一个惊喜,就是原本只有一个url一份缓存的方案,由于天猫一个url对应的是多个端不同的内容,无法进行缓存。</p>
<p>在nginx层面能够识别用户终端后,我们可以让一个url针对多份缓存副本,从而实现在cdn上可以直接经过nginx转发请求到用户终端对应的副本。</p>
<h2 id="-">策略变更</h2>
<p>双11结束之后,我这边对已有方案进行了梳理,nginx方案已经暴露了一些问题,更新nginx配置文件成本相对发布前端文件或者后端文件都略高,且很多安卓phone都反馈了访问时看到了pc版本的页面。</p>
<p>前者的问题在于维护成本,后者的原因是来自前面提到的浏览器提供数值问题。</p>
<p>目前识别策略还是遵守安卓UA规范,包含android + mobile则判断是Phone,android不带phone就是pad,也算是面对未来的解决方案了。</p>
<p>最后,如果有更好的识别方案或者建议,欢迎找我沟通。</p>
]]></description></item><item><title><![CDATA[天猫双11前端分享系列(六):大规模 Node.js 应用(续)]]></title><link>http://acyort.github.io/posts/2015/12/120967352.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/12/120967352.html</guid><dc:creator><![CDATA[dead-horse]]></dc:creator><pubDate>Thu, 14 Jan 2016 16:00:00 GMT</pubDate><description><![CDATA[<p><a href="https://github.com/tmallfe/tmallfe.github.io/issues/28">前一篇文章</a>讲述了我们是怎样应用 Node.js 解决模板渲染的实际问题的,而这一篇我们来看看天猫是如何一步步将 node 推广到各个业务线上的。</p>
<hr>
<p>前面讲述了我们通过 node 在今年双十一中承担了大量的页面渲染工作,包括:</p>
<ul>
<li>天猫首页、大部分天猫频道页、双十一会场以及所有天猫的活动页面都全部基于 node 应用提供服务。</li>
<li>商品详情、店铺和搜索页等主流程链路上,以及天猫超市和天猫会员等业务线上的页面渲染。</li>
<li>提供给内部运营小二的天猫页面搭建平台 web 层基于 node 进行开发,双十一期间在此平台上搭建了超过 1000+ 个双十一相关活动页面。</li>
</ul>
<p>单单看上面列出来的内容可能无法很直观的感受到到底 node 在天猫覆盖了多少业务。大家拿出手机,用浏览器打开天猫首页:</p>
<p><img src="https://os.alipayobjects.com/rmsportal/PXWLjltQzUEYogT.png" alt=""></p>
<p><strong><em>从天猫无线首页上点进的任何一个天猫的链接,包括搜索后的列表页、每一个店铺或者商品详情页,都经由 node 渲染产生。</em></strong> </p>
<p>罗马不是一天建成的,天猫将 node 覆盖到如此广的业务范围也是通过一年多的时间慢慢的渗透改造完成。这篇文章想和大家分享一下天猫是如何一步步将页面渲染部分通过 node 替换掉 php 和 java 的。</p>
<h2 id="-">契机</h2>
<p>在一年半之前,天猫的所有的活动页面、首页和频道页都是基于 TMS 搭建,由 php 在一个独特的 CDN 集群上进行渲染,然而由于之前 php 系统已经没有人维护了,且各种业务共享同一个环境导致 php 版本一直停留在很老的版本无法升级,性能和安全性上有各种问题。</p>
<p>此时亟需一个新的系统来取代旧的 php 体系,而 node 当时已经在业界和公司内慢慢的被应用起来了,特别是在阿里内部,已经有较为成熟的开发环境(包括私有 npm 服务、与内部其他系统的打通、与发布和监控体系的打通),而在做模板渲染层而言,node 可以很容易的做到前后端共享模板语言,加上性能也不差,前端又比较熟悉,所以最终我们选择了基于 node 进行改造。</p>
<p>于是我们基于 node 快速的开发了第一个版本的 wormhole(node 渲染容器),并将天猫的首页迁移到了这套系统上。迁移完成之后,我们对新(node)老(php)首页做了一个性能对比:</p>
<p><img src="https://os.alipayobjects.com/rmsportal/WaOeOXWNqKwCvMy.png" alt="">
<img src="https://os.alipayobjects.com/rmsportal/VsQOpWJdLFRfUtJ.png" alt=""></p>
<p>尽管这个结果有一部分因素是因为老系统功能上比新系统要复杂,但是也在很大一定程度上说明了采用 node 的新架构来做这件事情是没有问题的。</p>
<h2 id="-">小考</h2>
<p>每年的双十一都是对天猫整个技术架构的一次考验,而 2014 年的双十一对于天猫的 node 来说也是一次非常重要的小考:我们在双十一前把天猫首页改成了 node 版本并全量发布了。当时我们在 CDN 的一个独立集群上同时部署了 php 和 node 两套系统,将天猫首页的业务迁移上了 node,而其他的页面仍然采用 php 进行渲染。</p>
<p><img src="https://os.alipayobjects.com/rmsportal/uXJwsRwxDsKVAks.png" alt=""></p>
<p>在双十一当天零点的流量高峰中,node 的表现非常稳定,在同样的环境下,可以说是完胜之前的 php 系统。在此之后,我们终于有足够的底气对老板说:我们要把天猫的 view 这一层全部交给 node。</p>
<h2 id="-">规模化之路</h2>
<p>在天猫首页上经过了 14 年双十一的考验之后,我们对 node 是否能够支撑天猫的业务场景已经没有疑虑了,剩下的问题就是如何大规模的将 node 应用到天猫的各个业务上去。</p>
<p>首先我们解决的第一件事情是将天猫前端的模块化开发体系和资源加载方案融入到 node 中,然后推广到各个业务线上,然后又基于 node 构建了一个模块化页面搭建平台,打通前端、运营和后端数据产出系统,承接了天猫所有活动页和频道页等强运营需求的页面。具体的技术方案大家可以查看前一篇文章:<a href="https://github.com/tmallfe/tmallfe.github.io/issues/28">天猫双11前端分享系列(四):大规模 Node.js 应用</a>。</p>
<p>通过这一系列的技术改造之后,我们<strong><em>把所有新的业务需求全部使用 node 进行渲染</em></strong>,然后将之前所有用 php 渲染的页面迁移到 node 之上,仅仅用了几个月时间,基本将天猫移动端的 web 页面和所有的活动页、频道页都迁移完成。</p>
<h2 id="-">双十一大考</h2>
<p>经过大半年时间的重构和迁移,到今年年中的时候,天猫的大部分消费者端的页面都已经跑在了 node 之上,这时又要开始准备新一年的双十一了,比起去年只有一个天猫首页而言,对 node 的压力大了不止一个数量级。</p>
<p>我们评估基于今年的访问量,如果我们再把天猫首页、活动页直接放在 CDN 上进行渲染,对于 CDN 的机器成本来说是不可接受的,而且随着机器数量的增加,对于文件同步系统的压力也越来越大,效率越来越低。因此我们和 CDN 团队合作,将所有的活动页面从直接渲染模式迁移到缓存化 CDN + 源站的模式,并在这个模式下<a href="https://github.com/tmallfe/tmallfe.github.io/issues/28">对 node 应用的监控和稳定性上做了非常多的工作</a>。
而对于那些应用集群上的业务,我们也统一做了版本更新、监控完善和性能压测容量评估,保证各个业务方使用的 node 容器的稳定性。</p>
<p>最终,在今年的双十一中,node 支撑了天猫消费者端在无线设备上绝大多数的 web 页面渲染,PC 上除了核心链路外的绝大部分页面渲染工作,并且表现非常稳定,未出现任何一起由 node 引发的线上故障,而基于缓存化 CDN 的活动页面渲染服务,支撑双十一零点高峰的访问量也毫无压力,用少量的机器完成了去年双十一巨量投入才解决的问题。</p>
<h2 id="end">End</h2>
<p>记得在去年刚开始在天猫推动 node 的时候做了一个 slide 和大老板汇报,中间有一页是介绍业界是如何使用 node 的:</p>
<p><img src="https://os.alipayobjects.com/rmsportal/oVfqRNBysQYwpDR.png" alt=""></p>
<p>在一年多之后的今天,天猫的 node 应用经过这次双十一的考验之后,相信也完全有资格出现在这页 slide 之上了!</p>
]]></description></item><item><title><![CDATA[天猫双11前端分享系列(五):解密2015狂欢城]]></title><link>http://acyort.github.io/posts/2015/12/119858572.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/12/119858572.html</guid><dc:creator><![CDATA[maisui99]]></dc:creator><pubDate>Tue, 01 Dec 2015 16:00:00 GMT</pubDate><description><![CDATA[<p><img src="http://img.alicdn.com/tps/i2/TB114vtKFXXXXa8XVXXfZKX1FXX-960-540.jpg" alt="截图"></p>
<h2 id="-">性能</h2>
<h3 id="canvas-cache">Canvas Cache</h3>
<p>Canvas Cache就是使用一个额外的Canvas来保存已经绘制过的内容,下一次使用的时候直接从这个Canvas上读取,这样就可以大大减少Canvas的绘制次数,例如原先首屏绘制次数约为75左右,使用cache后的次数约为28,减少了62.67%,在三四环会更明显,因为没有动画,所有内容都可以cache。</p>
<p>实测设备越低端性能提升越明显,下面是一个页面在不同平台下的消耗时间对比:</p>
<table>
<thead>
<tr>
<th>设备</th>
<th>不使用cache</th>
<th>使用cache</th>
<th>比值</th>
</tr>
</thead>
<tbody>
<tr>
<td>PC</td>
<td>16ms</td>
<td>14ms</td>
<td>87.5%</td>
</tr>
<tr>
<td>Moto X</td>
<td>75ms</td>
<td>56ms</td>
<td>74.67%</td>
</tr>
<tr>
<td>Moto G</td>
<td>246ms</td>
<td>127ms</td>
<td>51.62%</td>
</tr>
<tr>
<td>iPhone5</td>
<td>170ms</td>
<td>45ms</td>
<td>26.47%</td>
</tr>
</tbody>
</table>
<p>从结果看效果还是很明显的,而且这个只是缓存了6次绘制的结果,实际使用中会缓存个数约为50左右,效果会更明显。</p>
<p>一开始使用一个Canvas直接缓存所有内容,后来发现Canvas大小是有限制的,然后就实现了一个自动切片成多个Canvas Cache的方案,这套cache方案后面会集成到<a href="http://hilo-js.github.io/">Hilo</a>中。</p>
<h3 id="-hilo-http-hilo-js-github-io-"><a href="http://hilo-js.github.io/">Hilo</a> 定制优化</h3>
<p>针对Canvas的最主要优化方案就是尽量减少Canvas API的调用,在对狂欢城做了大量profile后,发现<a href="http://hilo-js.github.io/">Hilo</a>中每次drawImage都会调用<code>ctx.save();ctx.translate(x, y);ctx.drawImage(...);ctx.restore();</code>,这里<a href="http://hilo-js.github.io/">Hilo</a>主要是为了保证在所有情况(例如缩放,旋转等)下均不出错,所以才这样处理,但是再狂欢城中并不需要做旋转等复杂的变换,所以将这里的绘制直接改为使用<code>ctx.drawImage</code>来实现。这样可以节省大量运行时间,因为在狂欢城基本上全是图片!</p>
<p>实测性能提升非常明显,下面是消耗时间对比:</p>
<table>
<thead>
<tr>
<th>设备</th>
<th>优化前</th>
<th>优化后</th>
<th>比值</th>
</tr>
</thead>
<tbody>
<tr>
<td>PC</td>
<td>30ms</td>
<td>15ms</td>
<td>50%</td>
</tr>
<tr>
<td>Moto X</td>
<td>138ms</td>
<td>76ms</td>
<td>55.07%</td>
</tr>
<tr>
<td>Moto G</td>
<td>435ms</td>
<td>216ms</td>
<td>49.66%</td>
</tr>
<tr>
<td>iPhone5</td>
<td>225ms</td>
<td>152ms</td>
<td>67.56%</td>
</tr>
</tbody>
</table>
<h3 id="-">视窗内渲染,懒加载及加载限流</h3>
<ul>
<li>视窗内渲染,就是只渲染可视区域的元素,以减少绘制消耗</li>
<li>懒加载,就是图片资源一开始是不加载的,在用户滚动到附近区域的时候才加载,减少网络请求</li>
<li>加载限流,由于使用懒加载机制,当用户快速滑动到比较远的区域时,会瞬间触发大量资源的加载,这个时候会发现页面变得非常卡,加载完成后变好了,所以就使用了限流的方案,限制同时只能加载4个图片,并实时调整加载顺序,优先加载用户当前可见区域的图片</li>
</ul>
<h3 id="-">地皮拼合绘制场景</h3>
<ul>
<li>使用地皮拼合的方案减少了很多图片资源,因为大量图片是重用的</li>
<li>由于图片的内存占用是根据图片尺寸转换为2的N次方,然后计算大小,所以图片尺寸越大占用内存可能导致指数级增长,狂欢城中的图片都是小图(地图区块都是256以下,其他基本上也是512以下),所以内存占用上会小很多</li>
</ul>
<h3 id="-">低端设备降级</h3>
<p>在低端设备上使用1倍图片,减少内存占用,并且不显示动画。</p>
<h2 id="-">开发效率</h2>
<ul>
<li>前期开发了一个地图编辑器,用于编辑地图,因为地图布局变化过好几次,有了这个还是节省了很多时间的</li>
<li>PC &amp; 无线大量逻辑共用,大大减少了开发成本。</li>
<li>自动化,因为在PC和无线都有高清(2倍)和普通两种方案,所以图片总共会有4种尺寸,而大部分图片都是一样的,这样如果让设计师导出4种尺寸的话,将会是巨大的工作量,而且要更换素材(很频繁)也会发现很麻烦。所以写了一个自动处理图片的gulp任务,可以自动生成多种尺寸的图片,然后压缩、上传到cdn,最后生成一个imgs.js的文件,使用只需要依赖这个js,然后以原始文件名引用即可,非常方便快捷,大大减少了维护大量图片的工作量。</li>
<li>同样less中的图片也是使用上面的自动化的结果进行转换的,使用方式很简单<code>background-image: cdn-url(&#39;island-brand-bg-pc.png&#39;);</code>这样最后会变成<code>background-image: url(&#39;//gw.alicdn.com/tfscom/TB1urfGKXXXXXXBaXXX_pYDSXXX-937-595.png&#39;);</code>,这样写less的时候就不需要关心图片地址问题,图片上传问题,图片压缩等问题。</li>
</ul>
<h2 id="-">感悟</h2>
<ul>
<li>自动化是好东西,能大大减少工作量</li>
</ul>
<h2 id="-">天猫前端团队招聘</h2>
<p>如果你看了这篇文章,对加入天猫前端团队有意向的,可以发简历到[email protected],招聘要求见:<a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504">https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504</a></p>
]]></description></item><item><title><![CDATA[天猫双11前端分享系列(四):大规模 Node.js 应用]]></title><link>http://acyort.github.io/posts/2015/11/119413587.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/11/119413587.html</guid><dc:creator><![CDATA[dead-horse]]></dc:creator><pubDate>Tue, 26 Jan 2016 16:00:00 GMT</pubDate><description><![CDATA[<p>在刚刚过去的 15 年天猫双十一中,Node.js(后文简称 node) 大放异彩,不仅帮助前端团队快速、高效的解决双十一各个业务上的页面渲染问题,同时在性能和稳定性上也表现非常出色,大大降低了双十一硬件成本的同时,在整个双十一期间未出现任何一起由 node 引发的线上故障。</p>
<h2 id="-">覆盖业务</h2>
<p>经过一年时间的改造和推进,到 15 年双十一的时候,已经有大量的业务都有了 node 的身影,基本上天猫大部分的 web 页面都是通过 node 渲染出来:</p>
<ul>
<li>天猫首页、大部分天猫频道页、双十一会场以及所有天猫的活动页面都全部基于 node 应用提供服务。</li>
<li>商品详情、店铺和搜索页等主流程链路上,以及天猫超市和天猫会员等业务线上的页面渲染。</li>
<li>提供给内部运营小二的天猫页面搭建平台 web 层基于 node 进行开发,双十一期间在此平台上搭建了超过 1000+ 个双十一相关活动页面。</li>
</ul>
<h2 id="-">工作职责</h2>
<p>在上述覆盖了 node 的业务中,node 在其中扮演了多种角色:</p>
<h3 id="-web-">完整的 web 应用</h3>
<p>天猫页面搭建平台即是一个由 node 负责整个 web 端包括业务逻辑和模板渲染等工作的应用。基于支付宝的 node web 框架 chair,通过 hsf 调用和淘宝共建的页面数据存储的接口,用 node 完成业务逻辑处理、页面渲染和前端接口。</p>
<h3 id="-">轻量级的模板渲染容器</h3>
<p>通过 node 整合前端的天猫组件规范 MUI,开发了一套专注于模板渲染的 node 容器(wormhole),通过这个 node 容器,前端可以专注于展现层的开发,统一前端的本地和线上的代码运行环境,也让后端摆脱了繁琐的套模板工作,专注于提供数据接口。同时这套容器基于天猫的模块化规范,横向打通了各个业务和应用之间的模块共享。</p>
<p><img src="https://img.alicdn.com/tps/TB1Ev7fKFXXXXcMXVXXXXXXXXXX-1150-868.png" alt=""></p>
<p>基于这个模板容器,我们完成了商品详情、店铺、搜索页以及超市等业务线上的前后端分离工作,大大提升了前端的开发效率,并有效降低了前后端沟通成本。</p>
<h3 id="-">页面渲染服务</h3>
<p>同样基于天猫前端的组件规范 MUI 和模板渲染的 node 容器,我们完成了一套模块化搭建页面的系统,同时开发并运维了一个用来渲染基于模块搭建的页面的服务,同时这个服务和阿里的 cache CDN 打通,在保证满足业务需求的前提下,降低消耗的计算资源。</p>
<p>基于这个服务,在双十一中提供了 900+ 活动页面的渲染,以及天猫首页和各个频道页的渲染工作,天猫的所有营销引流页面基本都由这个服务提供页面。</p>
<h2 id="-">进入正题</h2>
<p>上面讲了许多我们用 node 做了什么,以及覆盖了那些业务,现在我们来看看,到底我们是怎样用 node 解决实际的业务需求的。</p>
<p>拿这次双十一的会场页举例:</p>
<ol>
<li>用户在不同的终端环境下访问 <a href="https://1111.tmall.com">https://1111.tmall.com</a> 这个网址,请求会直接来到 CDN 上。</li>
<li>CDN 对用户的终端环境进行判断,并在内存中找到对应终端的缓存文件返回,若未命中缓存,则继续往下执行。</li>
<li>CDN 将请求转发到 node 渲染服务,根据终端类型选择不同的页面响应(pc 页面,h5 页面, react-native 页面)。CDN 响应用户请求,并缓存页面。</li>
</ol>
<p>在上述流程中,我们看到同一个 url 对应到后端其实是完全不同的页面输出内容,为了达到这个目的,我们和 CDN 团队一起做了许多工作:</p>
<ol>
<li>开发了一个 tengine-detector 组件,通过请求的 <code>user-agent</code> 以及约定的一些 cookie 信息,判断用户的终端类型。并部署到 CDN 上,让 CDN 拥有了终端判断的能力。</li>
<li>用户请求到 CDN 上之后,CDN 会根据用户的终端类型分类,设置一个请求头,例如: <code>detector: pc</code> 表明这个请求的终端设备是 PC 上的浏览器。</li>
<li>渲染服务获取到这个头之后,根据 url 和设备类型选择不同的页面返回。返回时设置 <a href="http://www.w3.org/Protocols/HTTP/Issues/vary-header.html"><code>vary</code></a> 为 <code>detector</code>,保证 CDN 根据不同的设备类型缓存不同页面。</li>
</ol>
<p>上面提到会根据终端类型对于同一个 url 返回不同的页面,而这些页面其实都是通过一个基于 node 开发的天猫页面搭建平台用模块搭建的。在这个平台上,超过 95% 的模块都拥有 pc 和无线两个版本,本次双十一所有用到的模块都有 react native 的版本。运营只需搭建 PC 上的页面,就会自动生成无线以及 react native 的页面。基于这套方案,我们通过 70+ 高质量的模块,让运营同学完成了超过 900+ 活动页面的搭建。</p>
<p>再深入一点,我们如何来完成这些页面或者是模块的呢?首先,我们希望让前端开发做什么?</p>
<ul>
<li><strong>编写模板</strong></li>
<li>拿到数据(并处理),和模板进行结合</li>
<li>拿到请求上下文,时间、环境等系统变量来确定不同的展现</li>
<li>管理前端资源和依赖</li>
</ul>
<p>我们在 xtemplate 模板引擎的基础上进行扩展,让前端通过编写 xtemplate 模板,在 context 中注入一些必需的页面上下文,扩展 xtemplate 的语法,支持引入前端资源。基于这套模板,我们可以在拿到数据后渲染得到完整的页面,基本满足了开发页面在功能上的所有需求。</p>
<p>但是页面中其实有非常多重复性的内容,我们完全可以把他们抽象成一个个的模块,让页面通过模块化的方式来基于模块搭建,在这个过程中我们需要解决几个问题。</p>
<ol>
<li>模块版本和静态资源版本的管理:页面可能引用几十个模块,而这些模块依赖的静态资源有重复、有冲突,因此我们会通过一份统一的 <code>seed</code> 来进行依赖版本的管理,每一个模块在发布的时候都会打包好自身的依赖关系,而在将所有的模块组合成页面的时候,将所有模块的依赖表重新进行合并和去重,最终保证页面引用的模块和静态资源唯一。同时我们在模板中通过扩展引入了 <code>FELoader</code>(天猫的静态资源加载器),收集页面的所有静态资源,combo 后插入到页头(css)或者页尾(js)。</li>
<li>模块如何拿到相应的数据:对于模块而言,他并不需要知道被哪个页面引用了,所有的页面在引用模块的时候需要将模块所需的数据传递进去。而所有的模块开发者需要编写一份模块需要数据的 JSON Schema 描述,通过这份描述文件,搭建平台、投放系统以及其他使用这个模块的人都能够知道要为这个模块产生什么格式的数据。</li>
<li>配套的搭建平台和数据投放平台来让运营自由组合所有的模块生成页面,并为页面上的每一个模块进行数据投放。</li>
</ol>
<p>解决完上述问题之后,我们将每一个页面都变成了以下几个部分:</p>
<ol>
<li>一份页面的描述文件,声明了这个页面依赖的所有模块,以及渲染这些模块所需的数据的地址。</li>
<li>一系列相互独立的模块。</li>
<li>一份包含页面上所有模块需要的数据的数据文件。</li>
</ol>
<p>最终,我们的渲染服务会根据 URL 和请求的终端环境,找到对应的页面描述文件,请求相应的数据,合并所有的模板渲染成为 HTML 页面。</p>
<p>当我们完成了 web 页面的模块化搭建之后回头再看,是不是 react native(RN) 的页面也能够搭建呢?我们只需要所有的模块都有对应的 react native 版本,就可以像搭建 web 的 html 一样搭建渲染出 RN 需要的 js 了!所以本次双十一使用的所有模块都有 RN 版本,并有多个会场采用了 RN 进行搭建,取得了非常不错的效果,在接下来的双十二中,我们所有的会场都会支持 RN,而这一切对于搭建会场的运营来说都是完全透明的。</p>
<h3 id="-">稳定性保障</h3>
<p>在阿里,所有的双十一相关应用都需要面临的一个大问题就是稳定性,为了保证能够在几亿用户买买买的时候不掉链子,任何一个应用都需要花很大的精力来保障它的稳定性,node 的应用也一样。</p>
<p>对于 node 应用自身而言,我们首先要保证它有充足的测试,通过 mocha + istanbul ,尽可能让测试覆盖每一个功能点和边缘情况。</p>
<p><img src="https://img.alicdn.com/tps/TB1THkrKFXXXXaJXFXXXXXXXXXX-649-100.png" alt=""></p>
<p>需要有完善的监控和报警。在阿里内部,我们已经有了内部的监控系统,对于 node 应用而言,只需要按照要求的格式打印的日志,或者通过自己编写日志采集脚本,就可以轻松的搞定监控和报警。</p>
<ul>
<li>错误日志监控:通过采集脚本采集上来并分类,并设置单机报警和阈值和集群报警的阈值,在异常出现时能够及时发现。</li>
<li>系统状态监控:内存、CPU、load 等的监控,并设置报警阈值,当系统状态异常时能够及时发现。</li>
<li>应用状态监控:QPS、响应时间以及所有的远程调用记录,时刻了解系统的负载和各个依赖节点的服务状态。</li>
</ul>
<p>同时,对于 node 应用,我们可以使用阿里云团队提供的 <a href="http://alinode.aliyun.com/">alinode</a> ,他们可以提供更多 node 的日志和监控,并提供了在线的 profiler 和快照功能,方便排查线上异常和性能优化。</p>
<p><img src="https://img.alicdn.com/tps/TB1cjECKFXXXXc8XXXXXXXXXXXX-914-1123.png" alt=""></p>
<p>尽管我们可以对自身的代码做各种测试、各种监控,但是在一个复杂的系统中,各种上下游依赖非常复杂,网络情况也很复杂,这个时候为了保证稳定性,我们还有许多的工作要做。</p>
<h4 id="-">没有单点</h4>
<p>假设一个机房的光缆被挖断了,或者机房所在的城市大规模断电了,然后整个天猫的大部分页面都不能访问了,这明显不能接受,所以我们需要在多个城市的多个机房部署我们的服务。如果存放模板文件或者数据文件的服务挂了怎么办?多个节点,主备读取,同时对所有的文件都加上磁盘文件容灾。对外提供服务的整条链路上的每一个依赖都不能够出现单点问题。</p>
<h4 id="-">弱化依赖</h4>
<p>在排除完单点问题之后,我们再来审视我们的服务,是不是所有的依赖在挂掉后就无法正常服务了?是否我们对于每个依赖异常都有容灾的方案,弱化掉整条链路上的依赖。</p>
<h4 id="-">预案自动化</h4>
<p>对于每一个可能出现问题的环节,我们都需要有针对性的预案,如果这个预案需要人工去执行,就需要思考能否做到自动化。在 node 渲染服务中,可能有各个缓解出问题,链路上的所有预案都要能够自动切换:</p>
<ul>
<li>CDN 回源到多个机房,当某个机房异常时能够通过健康检查自动剔除。</li>
<li>当源站 load 过高时,服务自动切换到静态版本不做渲染。</li>
<li>当模板或者数据的存储节点挂了,通过健康检查自动剔除。</li>
<li>...</li>
</ul>
<h2 id="-">总结</h2>
<p>再回过头来看看在天猫我们使用 node 做的事情,不一定很牛逼,但是确实是在天猫现在的业务场景下,一个相对较优的使用方案,不论是在解决前端开发效率、还是提升服务质量方面,都发挥了很重要的作用。而经过了这次双十一的考验,我们也认为它<strong><em>已经是一个很成熟的工具</em></strong>,可以帮助我们更好的完成我们的工作。</p>
<p>node 只是工具,在每一个具体的业务场景下都有最合适的使用方法,而随着业务的发展,node 能做的事情也在变化,我们期望它能在之后能在更多的场景下落地。:)</p>
<h2 id="-">天猫前端团队招聘</h2>
<p>如果你看了这篇文章,对加入天猫前端团队有意向的,可以发简历到[email protected],招聘要求见:<a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504">https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504</a></p>
]]></description></item><item><title><![CDATA[天猫双11前端分享系列(三):浅谈 React Native与双11]]></title><link>http://acyort.github.io/posts/2015/11/118407227.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/11/118407227.html</guid><dc:creator><![CDATA[maisui99]]></dc:creator><pubDate>Mon, 01 Feb 2016 16:00:00 GMT</pubDate><description><![CDATA[<h2 id="-">目标</h2>
<p>希望能透过 react-native 的动态性,将 react native 的优势带入客户端,如手淘、手猫,让使用客户端浏览体验更佳,并且保持动态性,快速协助响应业务。
斑马(页面搭建平台) 是一套让非技术人员也能自行搭建页面的 CMS 系统,基于Node实现,由天猫自主开发,此系统支持 PC/Mobile 页面,React Native 整入后,让页面搭建上同步产出 PC/Mobile/Native 版本。</p>
<h2 id="-">模块构建</h2>
<h3 id="-">以应用为单位,以頁面为单位</h3>
<p>React Native 原设定为应用级别,让整个应用都使用 React Native,但对于手淘、手猫这类应用已经有大量业务跑在线上,无法进行一次性的迁移,在 @一渡、@隐风 等人的努力下,将原先以应用为单位细化以页面为单位,让使用上更佳灵活,让部分页面使用,不需要侵入整个系统。</p>
<h3 id="react-native-">React Native 模块化與 斑马 结合</h3>
<p>模块经由服务端 wormhole 透过 xtemplate 模版语言,将页面上使用到的模块、打底数据、 页面基本设置模块合并后让终端载入,客户端 React Native 容器载入后即可渲染页面。一般页面在使用 8~12 个模块含打抵数据文件大小 gzip 后约 80kb,透过 CDN 加载在 3G/4G/WIFI 下都可达到1秒内渲染完成。</p>
<p>React Native 在开发完成到上线这段期间必须要经过打包过程,在与 @正霖 一同努力后将打包工具做了几层细化。</p>
<ul>
<li>将基础模块、业务模块分开打包<ul>
<li>基础模块:一般非 minify 前大约 5 万行代码,现已透过 package app 预载入客户端中。</li>
<li>业务代码:一般约数千行,每次根据不同页面重新加载</li>
</ul>
</li>
<li>模块分开打包。<ul>
<li>模块打包同时只打包模块自身业务代码,并将模块依赖关系产出,在服务端 wormhole 进行相同模块 去重,让页面文件大小最佳化。</li>
<li>提供更多接口让其他应用整合</li>
<li>目前提供命令行工具以及 js 端提供 promise 接口,让后续其他特殊应用想要使用同时更加便利。</li>
</ul>
</li>
<li>页面基本设置模块编译工具<ul>
<li>由于 页面基本设置模块也是 React Native 的部份代码,因此也是需要打包,并在打包同时插入 xtemplate 语法,让服务端 wormhole 识别哪些模块必须要插入,模块合并于代码中的哪个区块。</li>
</ul>
</li>
</ul>
<h3 id="-">不同角色各自发挥价值</h3>
<p>模块开发者专注在高质量模块开发,数据投放交由数据后端系统,运营根据需求选择模块、填入数据,量化产出页面,让各种频道、营销活动快速搭建。同时产出 PC/Mobile web/Native 页面,让不同平台都能拥有最佳使用体验。</p>
<h2 id="-">错误处理、监控、性能、埋点</h2>
<p>目前天猫这边在React的应用中处理了包括容器初始化的监控,接入了与客户端Native一致的业务埋点系统和错误监控系统,可查看每一条 JS 错误完整的 stack 以及 RN 容器错误的详细信息,并且相同的错误会被归类在一起,方便统计错误占比。其中 JS 错误分为严重、不严重两种,其中严重错误可能会影响 UI 崩坏或页面渲染异常,通过报警加上错误信息可以更快速的排错</p>
<h2 id="-">基础组件支持</h2>
<p>目前基础组件设计都是以 web 模式靠拢,如 web 的 A 标签,RN 上也有完全相同的组件,在参数、行为上也是完全间容。</p>
<p>目前天猫自己开发了包括:</p>
<ul>
<li>通用逻辑组件,包括埋点等监控</li>
<li>LazyloadView,ScrollView/ListView 增加 onScrollEnd, 懒加载图片、懒加载组件、通知組件已被載入功能</li>
<li>Button,如同 web 使用的 A 标签,包含跳转、埋点、优惠券功能,懒加载内容功能</li>
<li>LoadingView,加载中占位用 loading</li>
<li>Image,圖片組件,整合 CrossImage 於其中</li>
<li>Grid,布局组件
...</li>
</ul>
<h2 id="-11-">双11期间结果</h2>
<p>双11期间ReactNative上线共 30 天,从数据上看来,多数状况下首屏性能是优于 web,尤其在 web 端 缓存未命中状况下。另外在 UI 操作体验上,React Native 基本都能达到 60 fps 的流畅体验。</p>
<p>就双11后也还有很多优化点持续进行:</p>
<ol>
<li>内存问题:就双11所使用的 0.8.0 版本看来仍然不够理想,无法在正确的时间点适当的释放内存。</li>
<li>js 加载重复模块:目前已在进行优化,考量使用类似前端 loader 的方式将 js 异步载入,以便在客户端缓存相同模块</li>
<li>android 支持:由于 React Native 0.14 开始有对于 android 较完善支持,目前也在针对这块与 ios 的 api 落差抹平。</li>
</ol>
<h2 id="-">心得</h2>
<p>就双11的这次 React Native ,让我们看到了 React Native 不再只是能够针对应用、页面级别的开发,也可以如 CMS 方式量化的产生内容。当然我们也不满足于当前的状态,还是有相当多的优化点可以进行,让更多业务切入、保持开源、活络的社群,以及最重要的是要能够为业务产生更大价值。</p>
<h2 id="-">天猫前端团队招聘</h2>
<p>如果你看了这篇文章,对加入天猫前端团队有意向的,可以发简历到[email protected],招聘要求见:<a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504">https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504</a></p>
]]></description></item><item><title><![CDATA[天猫双11前端分享系列(二):天猫双11页面服务容灾方案大揭秘]]></title><link>http://acyort.github.io/posts/2015/11/118404440.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/11/118404440.html</guid><dc:creator><![CDATA[maisui99]]></dc:creator><pubDate>Tue, 01 Dec 2015 16:00:00 GMT</pubDate><description><![CDATA[<p>会场活动页,承载了促销商品导流功能,是消费者的购物入口,在双11活动中的地位可谓重中之重。保障活动页的快速稳定可用,是非常非常重要的。这次天猫双11会场页面渲染由wormhole来承担(wormhole本身会在后续的文章中详细介绍),下面介绍一下wormhole的容灾方案。</p>
<h2 id="-">技术方案</h2>
<h3 id="-">动态降频</h3>
<p>wormhole主要消耗性能的地方就在模板引擎渲染这部分,在并发访问量大的情况下,频繁的模板渲染会导致系统负载急剧飙升,导致响应延迟。为了保证大并发量下,足够快速的响应,针对的做了动态降频方案,具体的见下图:</p>
<p><img src="http://img4.tbcdn.cn/L1/461/1/9f1d33b9ef10b0d06d86b22f43f8692359001973.png" alt=""></p>
<p>整个渲染策略就是,定时备份页面到OSS集群,每次请求过来,都会去判断当前系统Load是否过载,若果过载则直接读取上次备份的页面返回,而不使用模板引擎渲染,达到动态降低系统负载,快速响应的目的。</p>
<h3 id="cdn-">CDN兜底</h3>
<p>动态降频能够保证大部分情况下的快速响应;但是,如果wormhole集群全部当机,则也无能为了。为了确保双11万无一失,还得有一招后手。为此,我们做了一个兜底方案,见下图:</p>
<p><img src="http://img3.tbcdn.cn/L1/461/1/65480a900d17fef08c539b478a12122300bbe3de.png" alt=""></p>
<p>同样类似于第一个方案,也会定时备份页面到OSS集群,不同的是,这次备份到另一个异地的OSS机房,以防止OSS服务因不可抗力挂掉;如果发生了最极端的情况,源站全部挂掉,由当天的值班人员,手工切换CDN指向已经备份了的OSS文件,保障页面可访问。</p>
<h3 id="-">监控</h3>
<p>wormhole基于Node平台开发,我们知道Node平台长期以来,对内存使用监控这块一直很薄弱;对线上服务内存泄露排查基本无从入手;Node服务就像一个黑盒,只能祈祷不要出错,出错也只能使用万能的重启大法。为了弥补这一块,alinode团队推出了监控系统,重点解决这些痛点问题。</p>
<h2 id="-">总结</h2>
<ul>
<li>没有完美的方案,必须要结合具体场景做调整。必要时,人工干预也是需要的。</li>
<li>wormhole服务超级稳定,一条容灾方案也没有执行。</li>
</ul>
<h2 id="-">天猫前端团队招聘</h2>
<p>如果你看了这篇文章,对加入天猫前端团队有意向的,可以发简历到[email protected],招聘要求见:<a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504">https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504</a></p>
]]></description></item><item><title><![CDATA[天猫双11前端分享系列(一):活动页面的性能优化]]></title><link>http://acyort.github.io/posts/2015/11/118403371.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/11/118403371.html</guid><dc:creator><![CDATA[maisui99]]></dc:creator><pubDate>Sun, 24 Jan 2016 16:00:00 GMT</pubDate><description><![CDATA[<h2 id="-">数据结果</h2>
<p>无线优先从去年开始推行,今年更是全面无线化,双11无线业务成交拿到了不错的结果,性能也迈出了一大步,对比去年双十一页面整体load时间提升了2s秒左右,秒开率达到了70%;</p>
<p>去年双11活动会场埋点几个页面的性能,onload均值在4.7s左右(实际情况应该在3-4秒),导致跳失率非常高。</p>
<p>今年双十一后的数据情况:</p>
<table>
<thead>
<tr>
<th>2G平均加载完成时间</th>
<th>3G平均加载完成时间</th>
<th>4G平均加载完成时间</th>
<th>Wi-Fi平均加载完成时间</th>
<th>wifi页面秒开占比</th>
</tr>
</thead>
<tbody>
<tr>
<td>4s</td>
<td>4s</td>
<td>2s</td>
<td>2s</td>
<td>70%</td>
</tr>
</tbody>
</table>
<h2 id="-">做了什么</h2>
<h3 id="-">体积优化</h3>
<ul>
<li>全局图片开关管控,针对商品、店铺、页头、入口图等图片通过开关全局系数裁剪压缩处理,降低页面图片整体体积;</li>
<li>zCache打包,js和css离线化,减少固定大资源阻塞和请求时间耗损;</li>
</ul>
<h3 id="-">请求优化</h3>
<ul>
<li>通过全局开关控制,针对走节点懒加载模块图片做域名收敛管控,降低Mobile端的http建连和dns握手的成本;</li>
<li>常用图标iconfont化,减少请求;</li>
<li>节点懒加载接入,避免非首屏dom载入;</li>
<li>空背景图请求修复,避免资源耗损;</li>
<li>模块小图片base64化,减少不必要的请求;</li>
</ul>
<h3 id="-">渲染优化</h3>
<ul>
<li>gif动画去处和部分模块高度计算有误兼容避免引起重绘性能耗损;</li>
</ul>
<h2 id="-">为什么这么做</h2>
<h3 id="-">体积优化价值</h3>
<p>对比去年双11和以往活动提升最明显的地方在于,针对所有图片均作了裁剪压缩处理,由于活动业务的特殊性,和目前在源头没能控制住图片的大小,往往一张页头图片或运营从detail页提取的商品图片就能达到300k,整体页面体积能超过1M(首屏600k左右),而现在通过CDN的裁剪压缩后一张图片大小能缩小70%左右,针对所有图片处理后页面整体体积和效率缩减至少一半,以一个简单双十一页面为例:</p>
<ul>
<li>压缩处理前,首屏体积520k,finish时间5.83秒</li>
<li>压缩处理后,首屏体积315k,finish时间2.87秒</li>
</ul>
<p>预加载是这次手淘新发起的解决方案,将页面中静态资源预加载到手淘客户端,减少这些静态资源请求,这套方案也正好解决了,天猫目前繁杂的业务下诞生的一些固定大资源的问题。详细会在相关文章中再详细介绍</p>
<h3 id="-">请求优化重点</h3>
<ul>
<li>域名收敛:在无线端http建连和dns握手决定资源加载速度,cdn域名分发方法反而不适用,同时手淘httpdns服务在启动的时候就会对白名单的域名进行域名解析,返回对应服务的最近ip(各运营商),端口号,协议类型,心跳 等信息,使用收敛后白名单中的域名,在手淘下返回会提升资源加载速度。</li>
<li>图片base64和iconfont合并:很多常用小图标大家针对自己模块都做了合并或单独处理,这样带来的问题是模块搭建完页面后,需要花费不必要的时间加载图片,无线下那怕一张0.5k的小图片,也可能会花费1s的时间去请求,影响页面的load速度。</li>
<li>空白请求的去除:模块换肤中很多背景图片,使用的空请求,实际上空请求也是会花费请求时间,
空白请求也会耗费时间。</li>
</ul>
<h3 id="-">优化中的痛点</h3>
<ol>
<li>由于目前预加载无法解combo,而造势、预热期间模块发布比较频繁,影响预加载后的命中,特别是全局模块,直接会导致页面升级发布后失效(无法命中),无法作为长期方案</li>
<li>脚本体积过大,目前基础脚本文件大小在100k上,占了我们规范标准的一半以上体积。</li>
</ol>
<h2 id="-">关于体会</h2>
<p>目前天猫的页面基本上都还在基于KISSY搭建,原来的目的是为了保持PC/Mobile端技术的一致性和简单性,提高工作效率和工程化能力。而这在全面无线化的今天,已经成为一个瓶颈,这也是天猫后续技术发展需要解决的一个非常重要的问题。</p>
<p>性能这块活动目前做的远远不够,看向手淘,还有太多太多的东西要做,相比繁杂的业务压力,确实需要缓缓,放慢手中的业务,将性能和品质提升上去。</p>
<h2 id="-">天猫前端团队招聘</h2>
<p>如果你看了这篇文章,对加入天猫前端团队有意向的,可以发简历到[email protected],招聘要求见:<a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504">https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504</a></p>
]]></description></item><item><title><![CDATA[天猫双11前端分享系列]]></title><link>http://acyort.github.io/posts/2015/11/118400866.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/11/118400866.html</guid><dc:creator><![CDATA[maisui99]]></dc:creator><pubDate>Mon, 18 Jan 2016 16:00:00 GMT</pubDate><description><![CDATA[<p>天猫作为阿里双11的主战场,承担了数以百计的页面,亿级的访问及各类复杂的系统包括搜索、商品详情、交易等。</p>
<p>为了双11,我们做了N多的准备,包括</p>
<ol>
<li>页面性能优化</li>
<li>手机客户端上ReactNative的应用</li>
<li>Node服务的搭建</li>
<li>前端业务的监控</li>
<li>页面搭建系统的建设
....</li>
</ol>
<p>虽然由于XX的原因,每一项技术都不能讲到特别细致,但是我们会努力做到让你们看完有所收获。</p>
<p>目录:</p>
<ol>
<li><a href="https://github.com/tmallfe/tmallfe.github.io/issues/25">天猫双11前端分享系列(一):活动页面的性能优化</a></li>
<li><a href="https://github.com/tmallfe/tmallfe.github.io/issues/26">天猫双11前端分享系列(二):天猫双11页面服务容灾方案大揭秘</a></li>
<li><a href="https://github.com/tmallfe/tmallfe.github.io/issues/27">天猫双11前端分享系列(三):浅谈 React Native与双11</a></li>
<li><a href="https://github.com/tmallfe/tmallfe.github.io/issues/28">天猫双11前端分享系列(四):大规模 Node.js 应用</a></li>
<li><a href="https://github.com/tmallfe/tmallfe.github.io/issues/29">天猫双11前端分享系列(五):解密2015狂欢城</a></li>
<li><a href="https://github.com/tmallfe/tmallfe.github.io/issues/30">天猫双11前端分享系列(六):大规模 Node.js 应用(续)</a></li>
<li><a href="https://github.com/tmallfe/tmallfe.github.io/issues/32">天猫双11前端分享系列(七):如何精确识别终端</a></li>
</ol>
<p>接下来这几天,我们会把内部整理出来的总结分享到这边,尽请期待。</p>
<h2 id="-">广告时间</h2>
<p>有兴趣来天猫一起提升技术的,可以发简历到[email protected],<a href="https://job.alibaba.com/zhaopin/position_detail.htm?positionId=3504">招聘要求</a></p>
]]></description></item><item><title><![CDATA[轻松入门React和Webpack]]></title><link>http://acyort.github.io/posts/2015/06/84446634.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/06/84446634.html</guid><dc:creator><![CDATA[LingyuCoder]]></dc:creator><pubDate>Tue, 02 Feb 2016 16:00:00 GMT</pubDate><description><![CDATA[<p>最近在学习React.js,之前都是直接用最原生的方式去写React代码,发现组织起来特别麻烦,之前听人说用Webpack组织React组件得心应手,就花了点时间学习了一下,收获颇丰</p>
<h2 id="-react">说说React</h2>
<p>一个组件,有自己的结构,有自己的逻辑,有自己的样式,会依赖一些资源,会依赖某些其他组件。比如日常写一个组件,比较常规的方式:</p>
<p>- 通过前端模板引擎定义结构
- JS文件中写自己的逻辑
- CSS中写组件的样式
- 通过RequireJS、SeaJS这样的库来解决模块之间的相互依赖,
那么在React中是什么样子呢?</p>
<h3 id="-">结构和逻辑</h3>
<p>在React的世界里,结构和逻辑交由JSX文件组织,React将模板内嵌到逻辑内部,实现了一个JS代码和HTML混合的JSX。</p>
<h4 id="-">结构</h4>
<p>在JSX文件中,可以直接通过<code>React.createClass</code>来定义组件:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> CustomComponent = React.creatClass({
render: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-keyword">return</span> (&lt;div className=<span class="hljs-string">"custom-component"</span>&gt;&lt;/div&gt;);
}
});
</code></pre>
<p>通过这种方式可以很方便的定义一个组件,组件的结构定义在render函数中,但这并不是简单的模板引擎,我们可以通过js方便、直观的操控组件结构,比如我想给组件增加几个节点:</p>
<pre><code class="lang-javascript">var CustomComponent = React.creatClass({
render: function(){
var $nodes = [<span class="hljs-string">'h'</span>,<span class="hljs-string">'e'</span>,<span class="hljs-string">'l'</span>,<span class="hljs-string">'l'</span>,<span class="hljs-string">'o'</span>].map(function(str){
return (&lt;span&gt;{str}&lt;/span&gt;);
});
return (&lt;div className=<span class="hljs-string">"custom-component"</span>&gt;{$nodes}&lt;/div&gt;);
}
});
</code></pre>
<p>通过这种方式,React使得组件拥有灵活的结构。那么React又是如何处理逻辑的呢?</p>
<h4 id="-">逻辑</h4>
<p>写过前端组件的人都知道,组件通常首先需要相应自身DOM事件,做一些处理。必要时候还需要暴露一些外部接口,那么React组件要怎么做到这两点呢?</p>
<h5 id="-">事件响应</h5>
<p>比如我有个按钮组件,点击之后需要做一些处理逻辑,那么React组件大致上长这样:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> ButtonComponent = React.createClass({
render: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-keyword">return</span> (&lt;button&gt;屠龙宝刀,点击就送&lt;/button&gt;);
}
});
</code></pre>
<p>点击按钮应当触发相应地逻辑,一种比较直观的方式就是给button绑定一个<code>onclick</code>事件,里面就是需要执行的逻辑了:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getDragonKillingSword</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">//送宝刀</span>
}
<span class="hljs-keyword">var</span> ButtonComponent = React.createClass({
render: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-keyword">return</span> (&lt;button onclick=<span class="hljs-string">"getDragonKillingSword()"</span>&gt;屠龙宝刀,点击就送&lt;/button&gt;);
}
});
</code></pre>
<p>但事实上<code>getDragonKillingSword()</code>的逻辑属于组件内部行为,显然应当包装在组件内部,于是在React中就可以这么写:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> ButtonComponent = React.createClass({
getDragonKillingSword: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-comment">//送宝刀</span>
},
render: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-keyword">return</span> (&lt;button onClick={<span class="hljs-keyword">this</span>.getDragonKillingSword}&gt;屠龙宝刀,点击就送&lt;/button&gt;);
}
});
</code></pre>
<p>这样就实现内部事件的响应了,那如果需要暴露接口怎么办呢?</p>
<h5 id="-">暴露接口</h5>
<p>事实上现在<code>getDragonKillingSword</code>已经是一个接口了,如果有一个父组件,想要调用这个接口怎么办呢?</p>
<p>父组件大概长这样:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> ImDaddyComponent = React.createClass({
render: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-keyword">return</span> (
&lt;div&gt;
<span class="hljs-comment">//其他组件</span>
&lt;ButtonComponent /&gt;
<span class="hljs-comment">//其他组件</span>
&lt;/div&gt;
);
}
});
</code></pre>
<p>那么如果想手动调用组件的方法,首先在ButtonComponent上设置一个<code>ref=&quot;&quot;</code>属性来标记一下,比如这里把子组件设置成<code>&lt;ButtonComponent ref=&quot;getSwordButton&quot;/&gt;</code>,那么在父组件的逻辑里,就可以在父组件自己的方法中通过这种方式来调用接口方法:</p>
<pre><code class="lang-javascript"><span class="hljs-selector-tag">this</span><span class="hljs-selector-class">.refs</span><span class="hljs-selector-class">.getSwordButton</span><span class="hljs-selector-class">.getDragonKillingSword</span>();
</code></pre>
<p>看起来屌屌哒~那么问题又来了,父组件希望自己能够按钮点击时调用的方法,那该怎么办呢?</p>
<h5 id="-">配置参数</h5>
<p>父组件可以直接将需要执行的函数传递给子组件:</p>
<pre><code class="lang-javascript"><span class="hljs-tag">&lt;<span class="hljs-name">ButtonComponent</span> <span class="hljs-attr">clickCallback</span>=<span class="hljs-string">{this.getSwordButtonClickCallback}</span>/&gt;</span>
</code></pre>
<p>然后在子组件中调用父组件方法:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> ButtonComponent = React.createClass({
render: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-keyword">return</span> (&lt;button onClick={<span class="hljs-keyword">this</span>.props.clickCallback}&gt;屠龙宝刀,点击就送&lt;/button&gt;);
}
});
</code></pre>
<p>子组件通过<code>this.props</code>能够获取在父组件创建子组件时传入的任何参数,因此<code>this.props</code>也常被当做配置参数来使用</p>
<p>屠龙宝刀每个人只能领取一把,按钮点击一下就应该灰掉,应当在子组件中增加一个是否点击过的状态,这又应当处理呢?</p>
<h5 id="-">组件状态</h5>
<p>在React中,每个组件都有自己的状态,可以在自身的方法中通过<code>this.state</code>取到,而初始状态则通过<code>getInitialState()</code>方法来定义,比如这个屠龙宝刀按钮组件,它的初始状态应该是没有点击过,所以<code>getInitialState</code>方法里面应当定义初始状态<code>clicked: false</code>。而在点击执行的方法中,应当修改这个状态值为<code>click: true</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> ButtonComponent = React.createClass({
getInitialState: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-comment">//确定初始状态</span>
<span class="hljs-keyword">return</span> {
clicked: <span class="hljs-literal">false</span>
};
},
getDragonKillingSword: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-comment">//送宝刀</span>
<span class="hljs-comment">//修改点击状态</span>
<span class="hljs-keyword">this</span>.setState({
clicked: <span class="hljs-literal">true</span>
});
},
render: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{
<span class="hljs-keyword">return</span> (&lt;button onClick={<span class="hljs-keyword">this</span>.getDragonKillingSword}&gt;屠龙宝刀,点击就送&lt;/button&gt;);
}
});
</code></pre>
<p>这样点击状态的维护就完成了,那么render函数中也应当根据状态来维护节点的样式,比如这里将按钮设置为<code>disabled</code>,那么render函数就要添加相应的判断逻辑:</p>
<pre><code class="lang-javascript">render: function(){
var clicked = this.state.clicked<span class="hljs-comment">;</span>
<span class="hljs-keyword">if</span>(clicked)
<span class="hljs-keyword">return</span> (&lt;<span class="hljs-keyword">button</span> disabled=<span class="hljs-string">"disabled"</span> <span class="hljs-keyword">onClick</span>={this.getDragonKillingSword}&gt;屠龙宝刀,点击就送&lt;/<span class="hljs-keyword">button</span>&gt;)<span class="hljs-comment">;</span>
<span class="hljs-keyword">else</span>
<span class="hljs-keyword">return</span> (&lt;<span class="hljs-keyword">button</span> <span class="hljs-keyword">onClick</span>={this.getDragonKillingSword}&gt;屠龙宝刀,点击就送&lt;/<span class="hljs-keyword">button</span>&gt;)<span class="hljs-comment">;</span>
}
</code></pre>
<h4 id="-">小节</h4>
<p>这里简单介绍了通过JSX来管理组件的结构和逻辑,事实上React给组件还定义了很多方法,以及组件自身的生命周期,这些都使得组件的逻辑处理更加强大</p>
<h3 id="-">资源加载</h3>
<p>CSS文件定义了组件的样式,现在的模块加载器通常都能够加载CSS文件,如果不能一般也提供了相应的插件。事实上CSS、图片可以看做是一种资源,因为加载过来后一般不需要做什么处理。</p>
<p>React对这一方面并没有做特别的处理,虽然它提供了Inline Style的方式把CSS写在JSX里面,但估计没有多少人会去尝试,毕竟现在CSS样式已经不再只是简单的CSS文件了,通常都会去用Less、Sass等预处理,然后再用像postcss、myth、autoprefixer、cssmin等等后处理。资源加载一般也就简单粗暴地使用模块加载器完成了</p>
<h3 id="-">组件依赖</h3>
<p>组件依赖的处理一般分为两个部分:组件加载和组件使用</p>
<h4 id="-">组件加载</h4>
<p>React没有提供相关的组件加载方法,依旧需要通过<code>&lt;script&gt;</code>标签引入,或者使用模块加载器加载组件的JSX和资源文件。</p>
<h4 id="-">组件使用</h4>
<p>如果细心,就会发现其实之前已经有使用的例子了,要想在一个组件中使用另外一个组件,比如在<code>ParentComponent</code>中使用<code>ChildComponent</code>,就只需要在<code>ParentComponent</code>的<code>render()</code>方法中写上<code>&lt;ChildComponent /&gt;</code>就行了,必要的时候还可以传些参数。</p>
<h3 id="-">疑问</h3>
<p>到这里就会发现一个问题,React除了只处理了结构和逻辑,资源也不管,依赖也不管。是的,React将近两万行代码,连个模块加载器都没有提供,更与Angularjs,jQuery等不同的是,他还不带啥脚手架...没有Ajax库,没有Promise库,要啥啥没有...</p>
<h4 id="-dom">虚拟DOM</h4>
<p>那它为啥这么大?因为它实现了一个虚拟DOM(Virtual DOM)。虚拟DOM是干什么的?这就要从浏览器本身讲起</p>
<p>如我们所知,在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶——RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好)分离开的,但为了方便JS操作DOM结构,渲染引擎会暴露一些接口供JavaScript调用。由于这两块相互分离,通信是需要付出代价的,因此JavaScript调用DOM提供的接口性能不咋地。各种性能优化的最佳实践也都在尽可能的减少DOM操作次数。</p>
<p>而虚拟DOM干了什么?它直接用JavaScript实现了DOM树(大致上)。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM结构,React又通过在这个虚拟DOM上实现了一个 diff 算法找出最小变更,再把这些变更写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,而且由于减少了实际DOM操作次数,性能会有较大提升</p>
<p><strong>道理我都懂,可是为什么我们没有模块加载器?</strong></p>
<p>所以就需要Webpack了</p>
<h2 id="-webpack">说说Webpack</h2>
<h3 id="-webpack-">什么是Webpack?</h3>
<p>事实上它是一个打包工具,而不是像RequireJS或SeaJS这样的模块加载器,通过使用Webpack,能够像Node.js一样处理依赖关系,然后解析出模块之间的依赖,将代码打包</p>
<h3 id="-webpack">安装Webpack</h3>
<p>首先得有Node.js</p>
<p>然后通过<code>npm install -g webpack</code>安装webpack,当然也可以通过gulp来处理webpack任务,如果使用gulp的话就<code>npm install --save-dev gulp-webpack</code></p>
<h3 id="-webpack">配置Webpack</h3>
<p>Webpack的构建过程需要一个配置文件,一个典型的配置文件大概就是这样</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> webpack = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>);
<span class="hljs-keyword">var</span> commonsPlugin = <span class="hljs-keyword">new</span> webpack.optimize.CommonsChunkPlugin(<span class="hljs-string">'common.js'</span>);
<span class="hljs-keyword">module</span>.exports = {
entry: {
entry1: <span class="hljs-string">'./entry/entry1.js'</span>,
entry2: <span class="hljs-string">'./entry/entry2.js'</span>
},
output: {
path: __dirname,
filename: <span class="hljs-string">'[name].entry.js'</span>
},
resolve: {
extensions: [<span class="hljs-string">''</span>, <span class="hljs-string">'.js'</span>, <span class="hljs-string">'.jsx'</span>]
},
<span class="hljs-keyword">module</span>: {
loaders: [{
test: <span class="hljs-regexp">/\.js$/</span>,
loader: <span class="hljs-string">'babel-loader'</span>
}, {
test: <span class="hljs-regexp">/\.jsx$/</span>,
loader: <span class="hljs-string">'babel-loader!jsx-loader?harmony'</span>
}]
},
plugins: [commonsPlugin]
};
</code></pre>
<p>这里对Webpack的打包行为做了配置,主要分为几个部分:</p>
<ul>
<li>entry:指定打包的入口文件,每有一个键值对,就是一个入口文件</li>
<li>output:配置打包结果,path定义了输出的文件夹,filename则定义了打包结果文件的名称,filename里面的<code>[name]</code>会由entry中的键(这里是entry1和entry2)替换</li>
<li>resolve:定义了解析模块路径时的配置,常用的就是extensions,可以用来指定模块的后缀,这样在引入模块时就不需要写后缀了,会自动补全</li>
<li>module:定义了对模块的处理逻辑,这里可以用loaders定义了一系列的加载器,以及一些正则。当需要加载的文件匹配test的正则时,就会调用后面的loader对文件进行处理,这正是webpack强大的原因。比如这里定义了凡是<code>.js</code>结尾的文件都是用<code>babel-loader</code>做处理,而<code>.jsx</code>结尾的文件会先经过<code>jsx-loader</code>处理,然后经过<code>babel-loader</code>处理。当然这些loader也需要通过<code>npm install</code>安装</li>
<li>plugins: 这里定义了需要使用的插件,比如commonsPlugin在打包多个入口文件时会提取出公用的部分,生成common.js</li>
</ul>
<p>当然Webpack还有很多其他的配置,具体可以参照它的<a href="http://webpack.github.io/docs/configuration.html#entry">配置文档</a></p>
<h3 id="-">执行打包</h3>
<p>如果通过<code>npm install -g webpack</code>方式安装webpack的话,可以通过命令行直接执行打包命令,比如这样:</p>
<pre><code class="lang-shell">$webpack --config webpack.config.js
</code></pre>
<p>这样就会读取当前目录下的webpack.config.js作为配置文件执行打包操作</p>
<p>如果是通过gulp插件gulp-webpack,则可以在gulpfile中写上gulp任务:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> gulp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'gulp'</span>);
<span class="hljs-keyword">var</span> webpack = <span class="hljs-built_in">require</span>(<span class="hljs-string">'gulp-webpack'</span>);
<span class="hljs-keyword">var</span> webpackConfig = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./webpack.config'</span>);
gulp.task(<span class="hljs-string">"webpack"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">return</span> gulp
.src(<span class="hljs-string">'./'</span>)
.pipe(webpack(webpackConfig))
.pipe(gulp.dest(<span class="hljs-string">'./build'</span>));
});
</code></pre>
<h3 id="-">组件编写</h3>
<h4 id="-babel-">使用Babel提升逼格</h4>
<p>Webpack使得我们可以使用Node.js的CommonJS规范来编写模块,比如一个简单的Hello world模块,就可以这么处理:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> React = <span class="hljs-built_in">require</span>(<span class="hljs-string">'react'</span>);
<span class="hljs-keyword">var</span> HelloWorldComponent = React.createClass({
displayName: <span class="hljs-string">'HelloWorldComponent'</span>,
render: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">return</span> (<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Hello world<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>)</span>;
}
});
<span class="hljs-built_in">module</span>.exports = HelloWorldComponent;
</code></pre>
<p>等等,这和之前的写法没啥差别啊,依旧没有逼格...程序员敲码要有geek范,要逼格than逼格,这太low了。现在都ES6了,React的代码也要写ES6,<code>babel-loader</code>就是干这个的。<a href="https://babeljs.io/">Babel</a>能够将ES6代码转换成ES5。首先需要通过命令<code>npm install --save-dev babel-loader</code>来进行安装,安装完成后就可以使用了,一种使用方式是之前介绍的在<code>webpack.config.js</code>的loaders中配置,另一种是直接在代码中使用,比如:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> HelloWorldComponent = <span class="hljs-built_in">require</span>(<span class="hljs-string">'!babel!jsx!./HelloWorldComponent'</span>);
</code></pre>
<p>那我们应当如何使用Babel提升代码的逼格呢?改造一下之前的HelloWorld代码吧:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-type">React</span> from <span class="hljs-symbol">'reac</span>t';
export <span class="hljs-keyword">default</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span> </span>{
constructor() {
<span class="hljs-keyword">super</span>();
<span class="hljs-keyword">this</span>.state = {};
}
render() {
<span class="hljs-keyword">return</span> (&lt;div&gt;<span class="hljs-type">Hello</span> <span class="hljs-type">World</span>&lt;/div&gt;);
}
}
</code></pre>
<p>这样在其他组件中需要引入HelloWorldComponent组件,就只要就可以了:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> HelloWorldComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'./HelloWorldComponent'</span>
</code></pre>
<p>怎么样是不是更有逼格了?通过import引入模块,还可以直接定义类和类的继承关系,这里也不再需要<code>getInitialState</code>了,直接在构造函数<code>constructor</code>中用<code>this.state = xxx</code>就好了</p>
<p>Babel带来的当然还不止这些,在其帮助下还能尝试很多优秀的ES6特性,比如箭头函数,箭头函数的特点就是内部的this和外部保持一致,从此可以和<code>that</code>、<code>_this</code>说再见了</p>
<pre><code class="lang-javascript">[<span class="hljs-symbol">'H</span>', <span class="hljs-symbol">'e</span>', <span class="hljs-symbol">'l</span>', <span class="hljs-symbol">'l</span>', <span class="hljs-symbol">'o</span>'].map((<span class="hljs-name">c</span>) =&gt; {
return (<span class="hljs-name">&lt;span&gt;</span>{c}&lt;/span&gt;)<span class="hljs-comment">;</span>
})<span class="hljs-comment">;</span>
</code></pre>
<p>其他还有很多,具体可以参照<a href="https://babeljs.io/docs/learn-es6/">Babel的学习文档</a></p>
<h4 id="-">样式编写</h4>
<p>我是一个强烈地Less依赖患者,脱离了Less直接写CSS就会出现四肢乏力、不想干活、心情烦躁等现象,而且还不喜欢在写Less时候加前缀,平常都是gulp+less+autoprefixer直接处理的,那么在Webpack组织的React组件中要怎么写呢?</p>
<p><strong>没错,依旧是使用loader</strong></p>
<p>可以在<code>webpack.config.js</code>的loaders中增加Less的配置:</p>
<pre><code class="lang-javascript">{
<span class="hljs-attribute">test</span>: /\.less$/,
loader: <span class="hljs-string">'style-loader!css-loader!autoprefixer-loader!less-loader'</span>
}
</code></pre>
<p>通过这样的配置,就可以直接在模块代码中引入Less样式了:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-type">React</span> from <span class="hljs-symbol">'reac</span>t';
require('./<span class="hljs-type">HelloWorldComponent</span>.less');
export <span class="hljs-keyword">default</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span> </span>{
constructor() {
<span class="hljs-keyword">super</span>();
<span class="hljs-keyword">this</span>.state = {};
}
render() {
<span class="hljs-keyword">return</span> (&lt;div&gt;<span class="hljs-type">Hello</span> <span class="hljs-type">World</span>&lt;/div&gt;);
}
}
</code></pre>
<h4 id="-">其他</h4>
<p>Webpack的loader为React组件化提供了很多帮助,像图片也提供了相关的loader:</p>
<pre><code class="lang-javascript">{ <span class="hljs-attribute">test</span>: /\.png$/, loader: <span class="hljs-string">"url-loader?mimetype=image/png"</span> }
</code></pre>
<p>更多地loader可以移步<a href="https://github.com/webpack/docs/wiki/list-of-loaders">webpack的wiki</a></p>
<p>##在Webpack下实时调试React组件</p>
<p>Webpack和React结合的另一个强大的地方就是,在修改了组件源码之后,不刷新页面就能把修改同步到页面上。这里需要用到两个库<code>webpack-dev-server</code>和<code>react-hot-loader</code>。</p>
<p>首先需要安装这两个库,<code>npm install --save-dev webpack-dev-server react-hot-loader</code></p>
<p>安装完成后,就要开始配置了,首先需要修改entry配置:</p>
<pre><code class="lang-javascript"><span class="hljs-selector-tag">entry</span>: {
<span class="hljs-attribute">helloworld</span>: [
<span class="hljs-string">'webpack-dev-server/client?http://localhost:3000'</span>,
<span class="hljs-string">'webpack/hot/only-dev-server'</span>,
<span class="hljs-string">'./helloworld'</span>
]
},
</code></pre>
<p>通过这种方式指定资源热启动对应的服务器,然后需要配置<code>react-hot-loader</code>到loaders的配置当中,比如我的所有组件代码全部放在scripts文件夹下:</p>
<pre><code class="lang-javascript">{
<span class="hljs-attribute">test</span>: /\.js?$/,
loaders: [<span class="hljs-string">'react-hot'</span>, <span class="hljs-string">'babel'</span>],
include: [path.<span class="hljs-built_in">join</span>(__dirname, <span class="hljs-string">'scripts'</span>)]
}
</code></pre>
<p>最后配置一下plugins,加上热替换的插件和防止报错的插件:</p>
<pre><code class="lang-javascript"><span class="hljs-string">plugins:</span> [
<span class="hljs-keyword">new</span> webpack.HotModuleReplacementPlugin(),
<span class="hljs-keyword">new</span> webpack.NoErrorsPlugin()
]
</code></pre>
<p>这样配置就完成了,但是现在要调试需要启动一个服务器,而且之前配置里映射到<code>http://localhost:3000</code>,所以就在本地3000端口起个服务器吧,在项目根目录下面建个server.js:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> webpack = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>);
<span class="hljs-keyword">var</span> WebpackDevServer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack-dev-server'</span>);
<span class="hljs-keyword">var</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./webpack.config'</span>);
<span class="hljs-keyword">new</span> WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: <span class="hljs-literal">true</span>,
historyApiFallback: <span class="hljs-literal">true</span>
}).listen(<span class="hljs-number">3000</span>, <span class="hljs-string">'localhost'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err, result</span>) </span>{
<span class="hljs-keyword">if</span> (err) <span class="hljs-built_in">console</span>.log(err);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Listening at localhost:3000'</span>);
});
</code></pre>
<p>这样就可以在本地3000端口开启调试服务器了,比如我的页面是根目录下地<code>index.html</code>,就可以直接通过<code>http://localhost:3000/index.html</code>访问页面,修改React组件后页面也会被同步修改,这里貌似使用了websocket来同步数据。图是一个简单的效果:</p>
<p><img src="http://skyinlayerblog.qiniudn.com/blog/test.gif" alt="Alt text"></p>
<h2 id="-">结束</h2>
<p>React的组件化开发很有想法,而Webpack使得React组件编写和管理更加方便,这里只涉及到了React和Webpack得很小一部分,还有更多的最佳实践有待在学习的路上不断发掘</p>
]]></description></item><item><title><![CDATA[3D互动游戏实践]]></title><link>http://acyort.github.io/posts/2015/05/73342947.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/05/73342947.html</guid><dc:creator><![CDATA[hlissnake]]></dc:creator><pubDate>Thu, 11 Jun 2015 16:00:00 GMT</pubDate><description><![CDATA[<h2 id="-">神奇的第三维度</h2>
<p>很多技术同学都是游戏玩家,3D游戏无疑是画面最棒、投入感最真实、最让人投入的。</p>
<p>说起3D,前端工程师们应该都很熟悉,CSS3对3D支持非常好,除部分低端Android机器外,性能和效果都不错。今天来分享下如何基于HTML5陀螺仪,来实现3D虚拟现实效果。</p>
<h2 id="-">移动端虚拟现实</h2>
<p>虚拟现实大家肯定都了解。VR视觉增强的电影、游戏,市面上已经有很多了。</p>
<p>我们这里的VR,就是简单的用手机屏幕来当 虚拟摄像机,让你来“观察”四周,感觉仿佛置身于虚拟环境里。我们团队有两个互动应用</p>
<p>星辰大海:<a href="http://www.tmall.com/go/chn/common/tgp-startui.php">http://www.tmall.com/go/chn/common/tgp-startui.php</a> (把活动取消的提示叉掉就行:) ) </p>
<p><img src="http://gtms02.alicdn.com/tps/i2/TB1E02nHXXXXXXAXXXXzmJQZVXX-424-416.png_200x200.jpg" alt="星辰大海"> <img src="http://gtms04.alicdn.com/tps/i4/TB1pIvgHXXXXXX3XpXXtADpRFXX-719-1280.png_300x300.jpg" alt="">,</p>
<p>汽车内景: <a href="http://m.laiwang.com/market/laiwang/tmall-vr-car.php?carid=2">http://m.laiwang.com/market/laiwang/tmall-vr-car.php?carid=2</a></p>
<p><img src="http://img.taobaocdn.com/tfscom/TB1qxWZHFXXXXalXXXXwu0bFXXX.png" alt="汽车内景"><img src="http://gtms04.alicdn.com/tps/i4/TB1FgLoHXXXXXcYaXXXMFsULXXX-678-912.png_300x300.jpg" alt=""></p>
<p>这是天猫互动在 “陀螺仪感应” 结合 “虚拟3D技术” 的一次尝试,事实证明在某些特定商品(比如汽车)上效果非常好。</p>
<p>如果你看完Demo很感兴趣,那接下来让我一步一步分解这里面涉及到的所有内容。</p>
<h2 id="-">矩阵</h2>
<p>计算机3D图形和矩阵密切相关,图形API接口也都直接使用矩阵,下面简单列举下矩阵一些简单概念</p>
<h4 id="css3-transform">CSS3 transform</h4>
<p>Transform2d/3d 封装了最基本的变换操作。每个变换都可以转化为矩阵。我们只说虚拟现实涉及的几个3D变换</p>
<ul>
<li><p>rotateX()</p>
<p> <img src="http://gtms02.alicdn.com/tps/i2/TB1FtsUHpXXXXXDapXX7wxsHpXX-203-72.png" alt="1"></p>
</li>
<li><p>rotateY()</p>
<p> <img src="http://gtms01.alicdn.com/tps/i1/TB1E8J_HFXXXXcgaXXX7wxsHpXX-203-72.png" alt="2"></p>
</li>
<li><p>rotateZ()</p>
<p> <img src="http://gtms04.alicdn.com/tps/i4/TB1LK0zHFXXXXXtapXXqItsHpXX-203-71.png" alt=""></p>
</li>
<li><p>scale3d(sx, sy, sz)</p>
<p> <img src="http://www.w3.org/TR/css3-transforms/scale3d.png" alt=""></p>
</li>
</ul>
<p>以上都是<a href="http://zh.wikipedia.org/wiki/%E6%AD%A3%E4%BA%A4%E7%9F%A9%E9%98%B5">正交矩阵</a>,简单说就是坐标系原点不变。</p>
<ul>
<li><p>translate3d 使坐标原点变换,因此使用<a href="http://zh.wikipedia.org/wiki/%E4%BB%BF%E5%B0%84%E5%8F%98%E6%8D%A2">&quot;仿射矩阵&quot;</a>来描述</p>
<p> <img src="http://www.w3.org/TR/css3-transforms/translate3d.png" alt=""></p>
</li>
<li><p>rotate3d(x, y, z, a) 这个比较特殊,描述的不是矩阵,而是<a href="http://zh.wikipedia.org/wiki/%E5%9B%9B%E5%85%83%E6%95%B8">&quot;四元数&quot;</a></p>
</li>
</ul>
<p>详细信息可查看<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function">https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function</a></p>
<p>以汽车内景Demo为例,旋转+透视点距离,使用了rotate+translateZ,手指缩放使用了scale3d。</p>
<h4 id="-">矩阵不满足乘法交互率</h4>
<p>多个矩阵变换叠加起来,就是是矩阵相乘。一个很重要的概念:矩阵不满足乘法交互率!这就意味着变换顺序的不同,直接导致最终结果千差万别。</p>
<p>通俗的讲就是:每一次变换都是相对上一次变换来做的,参考的坐标系时刻都在变化,无论2D、3D里都一样;</p>
<p>所以:translateZ() rotateX() rotateY() 和 rotateY() rotateX() translateZ() 得出的结果完全相反。</p>
<p>下一步我们要做的就是:如何将手机陀螺仪的数据正确反映出来!</p>
<h2 id="-">陀螺仪</h2>
<h4 id="-">欧拉角</h4>
<p>说陀螺仪之前,一定要先说这个概念 <a href="http://zh.wikipedia.org/wiki/%E6%AC%A7%E6%8B%89%E8%A7%92">欧拉角</a>。 欧拉角广泛应用于 <a href="http://en.wikipedia.org/wiki/Euler_angles#Tait.E2.80.93Bryan_angles">航空航天领域</a>,当然还有我们最熟悉的 手机陀螺仪方位感应器 <a href="http://w3c.github.io/deviceorientation/spec-source-orientation.html">deviceorientaiton</a></p>
<p>欧拉角描述3D空间里的方位,陀螺仪监听接口返回 alpha、beta、gamma 就是标准欧拉角方位。(这是手机端,欧拉角官方名称是 heading,pitch、bank)</p>
<p>两个不同的旋转顺序:(heading:45,bank:90) 和(bank:90,pitch:45)在效果是一致的,一个刚体的方位,可以表示成欧拉角多种不同的旋转顺序。也因为欧拉角的不唯一性,会产生“万向锁”的问题。</p>
<h4 id="-">限制性欧拉角</h4>
<p>为了保证唯一性,就有了“限制性欧拉角”这个概念。任何一个方位的描述,是按 alpha, beta, gamma 顺序旋转来得出的方位角度的。可以看成三个旋转正交矩阵,顺序相乘得出变换后的坐标,看下面的动态图,来帮助理解</p>
<p><img src="http://upload.wikimedia.org/wikipedia/commons/8/85/Euler2a.gif" alt=""></p>
<p>先绕蓝色Z轴旋转,得出alpha,然后绕绿色轴旋转,得出beta,最后绕红色轴旋转,得出gamma;</p>
<p>最后这张示意图一目了然:
<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Plane.svg/400px-Plane.svg.png" alt=""></p>
<p>限制性欧拉角有一些特性:</p>
<ul>
<li>取值范围:alpha:0-360,beta:+-90,gamma:+-180</li>
<li>beta = +-90时,既手机翻转,alpha、gamma会瞬间 +-180;</li>
</ul>
<p>欧拉角可参考这本书:<a href="http://book.douban.com/subject/1400419/">3D数学基础:图形与游戏开发 第十章</a></p>
<h2 id="-">代码实现:</h2>
<p>前面全是介绍概念,接下来才是正题。相信我,真正的代码远没有你想象中复杂。</p>
<p>现在我们已知了限制性欧拉角三个方位:alpha、beta、gamma,接下来的工作就是转换成矩阵,提供给你所使用的图像API。</p>
<p>我们使用 CSS3 rotate3d,来操作一个已建模的正立方体,关于如何使用DIV+ Perspective3d 来构建一个3D立方体,又是另外一个话题了,但其实也很简单。大家可以看上面汽车Demo的样式。相关内容会在下期“伪3D”专题中说明</p>
<p>alpha、beta、gamma 一一对应 rotateZ()、rotateX()、rotateY(),相对于我们的Z轴向上的世界坐标系而言。</p>
<p>所以欧拉角方位最终的矩阵变换公式是:</p>
<p><img src="http://w3c.github.io/deviceorientation/equation13a.png" alt=""></p>
<p>使用CSS3就意味着不用关心矩阵,除非你想用 matrix3d()。但矩阵相乘是顺序相关的,所以你必须关注每个变换的顺序。代码超简单就是这样.....</p>
<pre><code>style.webkitTransform = [<span class="hljs-string">'rotateZ(Zdeg) '</span>,<span class="hljs-string">'rotateX(Xdeg) '</span>,<span class="hljs-string">'rotateY(Ydeg)'</span>].<span class="hljs-keyword">join</span>(<span class="hljs-string">''</span>);
</code></pre><p>最终的效果应该是,你所看的立方体相对于环境,位置是不变的。</p>
<p>发现不对?呵呵,没错,因为陀螺仪返回的是手机相对于世界坐标系的方位。</p>
<h4 id="-">相对屏幕坐标系的逆矩阵</h4>
<p>何为虚拟现实,就是你在屏幕中看到的物体,相对于环境是不动的,只是你的摄像机角度变了而已。而图形API所做的变换,都是相对手机屏幕的。下面是一段比较绕的逻辑:</p>
<p>陀螺仪的矩阵变换最终是 ZXY 相乘。这是相对世界坐标系,你的手机屏幕按照这个矩阵变换到现在的方位,但是屏幕中的物体,被施加的矩阵变换是相对屏幕坐标系的,为了让它相对于世界坐标系保持不变。所以最终图形API所需要的矩阵变换,是ZXY相反的方向,也就是它的逆矩阵!</p>
<p>ZXY将顺序颠倒相乘,YXZ 就能得到相应的逆矩阵。所以!我们最终的代码应该是:</p>
<pre><code>Style.webkitTransform = [<span class="hljs-string">'rotateY(Ydeg) '</span>, <span class="hljs-string">'rotateX(Xdeg) '</span>, <span class="hljs-string">'rotateZ(Zdeg)'</span>].<span class="hljs-keyword">join</span>(<span class="hljs-string">''</span>);
</code></pre><p>大功告成!</p>
<h4 id="-">基于两轴的变换</h4>
<p>Android同学可能发现上面的汽车Demo,只能用滑屏操作,因为大部分Android机器的陀螺仪非常不稳定+不精确,抱歉了!</p>
<p>手指滑动逻辑也很简单,因为只改变了两个轴的旋转,代码如下:</p>
<pre><code>style.webkitTransform = <span class="hljs-string">'rotateZ(0) rotateX(Xdeg) rotateY(Ydeg)'</span><span class="hljs-comment">;</span>
</code></pre><p>注意这里的变换顺序也是不能改的,不然直接影响到你的交互。然后给X轴角度做个+-90°的取值范围就能防止颠倒效果。</p>
<h4 id="-api">切换不同的图形API</h4>
<p>如果你不使用CSS3,那这些矩阵计算都得自己代码实现。我们完全可以使用webGL来渲染整个立方体,除了图形API不同,webGL所需要的变换矩阵完全一致;</p>
<p>WebGL是不二的选择,而且可以构建更加复杂的球体来渲染全景,这时候素材就需要一张全景图片。不使用框架的话,会有点复杂,我们采用Three.js来构建我们的webGL版本</p>
<p>上代码:</p>
<ul>
<li>新建一个球体 Geometry,使用Threejs.BackSide 内部渲染时,为了消除材质的镜像显示,要设置一个scaleX(-1),也就是实现左右颠倒</li>
</ul>
<pre><code><span class="hljs-keyword">var</span> geometry = <span class="hljs-keyword">new</span> THREE.SphereGeometry(perspective, <span class="hljs-number">100</span>, <span class="hljs-number">100</span>);
geometry.applyMatrix( <span class="hljs-keyword">new</span> THREE.Matrix4().makeScale( <span class="hljs-number">-1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span> );
</code></pre><ul>
<li>设置全景图素材</li>
</ul>
<pre><code><span class="hljs-keyword">var</span> material = <span class="hljs-built_in">new</span> THREE.MeshBasicMaterial({
<span class="hljs-keyword">map</span>: texture,
overdraw: <span class="hljs-number">1</span>,
side: THREE.BackSide
});
<span class="hljs-keyword">var</span> mesh = <span class="hljs-built_in">new</span> THREE.Mesh(geometry, material);
</code></pre><ul>
<li>箭头deviceorientation事件,构建Euler对象,因为Threejs是左手坐标系,和CSS3坐标系不同,所以Y、Z轴顺序需要颠倒</li>
</ul>
<pre><code>euler.set( beta * <span class="hljs-built_in">Degree</span>, alpha * <span class="hljs-built_in">Degree</span>, gamma * <span class="hljs-built_in">Degree</span>,<span class="hljs-string">'YXZ'</span> )<span class="hljs-comment">;</span>
</code></pre><ul>
<li>Euler转换为四元数(quaternion)</li>
</ul>
<pre><code><span class="hljs-selector-tag">camera</span><span class="hljs-selector-class">.quaternion</span><span class="hljs-selector-class">.setFromEuler</span>( <span class="hljs-selector-tag">euler</span> );
</code></pre><ul>
<li>Three.js Demo, 滑轮/双指缩放可以更改摄像机FV</li>
</ul>
<p><a href="http://g.alicdn.com/tmapp/vr-car/1.1.1/demo/webgl.html">http://g.alicdn.com/tmapp/vr-car/1.1.1/demo/webgl.html</a></p>
<p><img src="http://gtms03.alicdn.com/tps/i3/TB1xJOwHpXXXXbqapXXZ6GBKFXX-150-150.png" alt=""></p>
<h2 id="-">陀螺仪的其他应用</h2>
<ul>
<li>指南针:需要计算真实世界里手机头部的向量坐标,无需逆矩阵,ZXY即可,然后计算这个向量在水平面上的投影坐标;</li>
<li>游戏操控:<ul>
<li>根据 欧拉角 来计算游戏中摄像机的角度变换,应用场景广泛;</li>
<li>根据 设备方位 + socket即时通信,实现无线鼠标的效果;</li>
</ul>
</li>
<li>虚拟重力:类似指南针原理,计算手机底部的向量坐标。可以结合物理引擎来做一些重力游戏;</li>
<li>方位手势:用户可获得左右翻转、上下翻转的手势体验;</li>
</ul>
<h2 id="-">总结</h2>
<p>以上就是基于手机陀螺仪的虚拟现实原理。我数学功底不扎实,很多描述不是很详细,如果你还是不太理解,欢迎随时来讨论。</p>
<p>前端工程师作为一个产品中人机交互的第一道门槛,创造性的交互方式、富有画面感的效果,能起对产品起到很积极的作用。个人认为掌握前沿的图形显示技术,对产品体验、技能提升都有很大帮助的。</p>
]]></description></item><item><title><![CDATA[react-native 与 react-web 的融合]]></title><link>http://acyort.github.io/posts/2015/04/69724348.html</link><guid isPermaLink="true">http://acyort.github.io/posts/2015/04/69724348.html</guid><dc:creator><![CDATA[6174]]></dc:creator><pubDate>Mon, 12 Oct 2015 16:00:00 GMT</pubDate><description><![CDATA[<h2 id="-">关于</h2>
<p>对于react-native在实际中的应用, facebook官方的说法是react-native是为多平台提供共同的开发方式,而不是说一份代码,多处使用。 然后一份代码能够多处使用还是很有意义的,我所了解到的已经在尝试做这件事情的:</p>
<blockquote>
<ol>
<li><a href="https://medium.com/@jviereck/modularise-css-the-react-way-1e817b317b04">modularise-css-the-react-way</a></li>
<li><a href="https://github.com/js-next/react-style">react-style</a></li>
<li><a href="https://github.com/raphamorim/native-css">native-css</a> </li>
</ol>
</blockquote>
<p>现阶段大家都是在摸索中,且react-native 还不够成熟,为此我也想通过一个实际的例子提前探究一下共享代码的可行性。 </p>
<p>下面是我以SampleApp做的一个简单demo, 先奉献上截图:</p>
<blockquote>
<p> <strong>web 版本</strong>
<img src="http://gtms02.alicdn.com/tps/i2/TB1KhiiHFXXXXbBXpXXhV2lQVXX-512-743.png" alt="">
<strong>react-native版本</strong>
<img src="http://gtms04.alicdn.com/tps/i4/TB1Fxx6HFXXXXXdXVXX66HwTVXX-487-801.png" alt=""></p>
</blockquote>
<h2 id="-">初步想法</h2>
<h3 id="-">组件</h3>
<p>react-native基本上是View套上Text这样来布局,为了做web和native的兼容,我们得提供继承版本的View ,针对不同的平台返回不同做兼容,我们将提供:</p>
<ol>
<li>Share.View -&gt; View (reac-native = View , web = div)</li>
<li>Share.P + Share.Span -&gt; Text (Text在react-native中分为块级别和inline级别所以得用两个元素来区分)</li>
</ol>
<h3 id="-">样式</h3>
<p>我们知道react-native的样式是css很小的一个子集,大概支持50种属性,为了做到web和native使用同样地样式,那么我的想法是:</p>
<ol>
<li>使用css文件来编写样式,通过编译的方式生产不同平台需要的样式</li>
<li>对于web,使用auto-prefixel处理,生产web兼容的css代码</li>
<li>对于react-native,生成对应的styles.js</li>
<li>css的写法用OOCSS的方式</li>
</ol>
<p>这样做的另外一个原因是,因为css是全集,react-native是子集,全集到子集可以通过删减来处理,但是如果想通过子集到全集就会很麻烦(react-style就是通过react-native来生成css)。 且这样做还有很多好处,例如我们可以支持react-native里边不支持的css写法,例如<code>padding: a b c d;</code> 这种写法很容易得到兼容。</p>
<blockquote>
<p>其实这里,无论react-native还是react-web都支持<code>style={}</code>这样的写法. 上面例子中的web截图其实是没有引用css的,但inline样式对于web来说并不是优选。 后面也做了通过react-native的css到web的css的尝试, 那种方案在样式上可以完全基于react-native来写,直接兼容web。</p>
</blockquote>
<h2 id="-">实现思路</h2>
<p>首先大概整理一下我们需要解决的问题:</p>
<ol>
<li>如何区分web和native</li>
<li>js如何对应不同的平台来编译,因为react-native使用的是自己的依赖管理packager</li>
<li>css如何编译为js</li>
<li>代码结构应该是怎样的</li>
</ol>
<h3 id="-web-native">问题一 : 如何区分web和native</h3>
<p>react-native 里边会有window变量吗?我试了一下,是有的,那window变量里边不可能有location,document之类的吧, 借着这种想法,可用如下方法来区分native和web</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">var</span> isNative = !<span class="hljs-built_in">window</span>.location;
</code></pre>
<h3 id="-">问题二:如何对应不同平台打包</h3>
<p>对于react-native,是通过packager来打包的,具体的实现和逻辑可以随时查看packager的readme文档。 那我们怎么将适用于native的代码打包成web的代码,首先想到的是<code>browserify</code>, <code>webpack</code>。 都是遵循commonJs规范,个人更喜欢前者, 用它来应该足以满足需求。 </p>
<h3 id="-css-js">问题三: css如何编译为js</h3>
<p>前面提到了<code>native-css</code> , 可以用它来帮助我们完成打包。 </p>
<h3 id="-">问题四:代码结构应该是怎样的</h3>
<p>web和native的代码都写在同一个地方,如何做区分呢? 这个问题当然最好就是不做区分,或者就像女生的衣服,期望是越少越好,但永远不可能木有(猥琐了:-】)。 </p>
<p>我设想中的一个最简模型的目录结构,web和ios有不同的入口,web和ios有单独的目录, 组件共享, 如下:</p>
<pre><code>├── compo<span class="hljs-selector-class">.js</span> <span class="hljs-comment">// 我们会使用到得公共组件</span>
├── styles<span class="hljs-selector-class">.css</span> <span class="hljs-comment">// compo的样式文件</span>
├── index<span class="hljs-selector-class">.web</span><span class="hljs-selector-class">.js</span> <span class="hljs-comment">// web 入口</span>
├── index<span class="hljs-selector-class">.ios</span><span class="hljs-selector-class">.js</span> <span class="hljs-comment">// ios 入口</span>
├── shared<span class="hljs-selector-class">.js</span> <span class="hljs-comment">// 做兼容的共享变量文件</span>
├── ios <span class="hljs-comment">// ios 目录</span>
└── web <span class="hljs-comment">// web 目录</span>
├── index<span class="hljs-selector-class">.html</span> <span class="hljs-comment">// web 页面</span>
├── index<span class="hljs-selector-class">.web</span><span class="hljs-selector-class">.js</span> <span class="hljs-comment">// 打包过后的js</span>
└── react<span class="hljs-selector-class">.js</span> <span class="hljs-comment">// react.js依赖</span>
</code></pre><p>好像很复杂的样子, 其实相对于原本的SampleApp,只是多了<code>index.web.js</code> , <code>web目录</code>, <code>shared</code>三者。 然后style通过style.css来描述。 </p>
<h2 id="-">具体实现</h2>
<p>我们已经整理了具体的实现思路,下面是我就会直接吐出我的实现代码, 重点的地方都会在源码里边有注释 </p>
<h4 id="-">先看应用代码:</h4>
<p><strong>ios入口:index.ios.js</strong></p>
<pre><code class="lang-javascript"> <span class="hljs-comment">/**
* Sample React Native App
* https://github.com/facebook/react-native
*/</span>
<span class="hljs-meta"> 'use strict'</span>;
<span class="hljs-keyword">var</span> React = <span class="hljs-built_in">require</span>(<span class="hljs-string">'react-native'</span>);
<span class="hljs-keyword">var</span> Compo = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./compo'</span>);
React.AppRegistry.registerComponent(<span class="hljs-string">'ShareCodeProject'</span>, () =&gt; Compo);
</code></pre>
<p><strong>web入口:index.web.js</strong> </p>
<pre><code class="lang-javascript"> <span class="hljs-comment">/**
* for web
*/</span>
<span class="hljs-keyword">var</span> Compo = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./compo'</span>);
React.render(&lt;Compo /&gt;, <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'App'</span>));
</code></pre>
<p><strong>样例组件:compo.js</strong></p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// 依赖的公共库,通过它获取兼容的组件</span>
<span class="hljs-keyword">var</span> Share = <span class="hljs-keyword">require</span>(<span class="hljs-string">'./shared'</span>);
<span class="hljs-comment">// styles是style.css build过后生成的style.js</span>
<span class="hljs-keyword">var</span> styles = <span class="hljs-keyword">require</span>(<span class="hljs-string">'./styles'</span>);
<span class="hljs-keyword">var</span> React = Share.React;
<span class="hljs-keyword">var</span> {
View,
P,
Span
} = Share;
<span class="hljs-keyword">var</span> Compo = React.createClass({
render: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> (
&lt;View style={styles.container}&gt;
&lt;P style={styles.welcome}&gt;
Welcome to React Native!
&lt;/P&gt;
&lt;P style={styles.instructions}&gt;
To get started, edit index.ios.js
&lt;/P&gt;
&lt;P style={styles.instructions}&gt;
Press Cmd+R to reload,{<span class="hljs-string">'\n'</span>}
Cmd+Control+Z <span class="hljs-keyword">for</span> dev menu
&lt;/P&gt;
&lt;/View&gt;
);
}
});
module.exports = Compo;
</code></pre>
<p><strong>组件样式: style.css</strong></p>
<pre><code class="lang-css"> <span class="hljs-comment">/**
* 大家可能发现了css的写法还是小驼峰,是的不是横杠,暂时我们还是以这种方式处理
* native-css 目测不支持横杠,(自己重写native-css相对来说是比较容易的,完全可以做到css兼容到react-native的css子集)
*/</span>