This repository has been archived by the owner on Mar 4, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.html
940 lines (882 loc) · 45.9 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
<!doctype html>
<!--suppress RequiredAttributes -->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Breaking Down Your React App</title>
<link rel="stylesheet" href="css/reveal.css">
<link rel="stylesheet" href="css/theme/pahund.css">
<link rel="stylesheet" href="lib/css/zenburn.css">
<!-- Printing and PDF exports -->
<script>
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match(/print-pdf/gi) ? 'css/print/pdf.css' : 'css/print/paper.css';
document.getElementsByTagName('head')[0].appendChild(link);
</script>
</head>
<body>
<img id="ebay-logo" src="images/ebay-tech-logo-wide-light-bgr-small.png" alt="eBay Tech Logo"
title="eBay Tech Logo">
<div class="reveal">
<div class="slides">
<!-- Introduction -->
<section>
<img data-src="images/Breaking_Down_Title.png" class="stretch">
<p>
Patrick Hund | Lead Frontend Developer | <a target="_blank" href="https://twitter.com/wiekatz">@wiekatz</a>
</p>
<aside class="notes">
Hi everyone, my name is Patrick. I work at eBay in Berlin.
I'm very excited to be here today to talk about breaking down your React app!
</aside>
</section>
<section data-background-image="images/Green_Field.png" data-background-size="contain">
<aside class="notes">
Can anyone tell me what this is?<br><br>
This is what every developer loves to have when starting a new project: a nice green field,
everything is possible, the magical chance to get things right this time. To use state-of-the-art
technology, to build something beautiful that will stand the test of time.
And, of course, make the customers very, very happy.
</aside>
</section>
<section data-background-image="images/Castle_Phase_1.png" data-background-size="contain">
<aside class="notes">
So, tadaa, our first release. It’s all good, software craftsmanship at its best. Servers running
smoothly, high code coverage, only a few bugs and loose ends.
</aside>
</section>
<section data-background-image="images/Castle_Phase_2.png" data-background-size="contain">
<aside class="notes">
So let’s add some more features. Our app is coming along great!
</aside>
</section>
<section data-background-image="images/Castle_Phase_3.png" data-background-size="contain">
<aside class="notes">
And some more features… but what’s this? Bug rate is increasing? It’s getting harder and harder to
understand what’s going on in the code. We should fix that! And fix it we will! Eventually! When
we’re done with the next release, or perhaps the one after that, because rolling out those new
features is super important!
</aside>
</section>
<section data-background-image="images/Castle_Phase_4.png" data-background-size="contain">
<aside class="notes">
OK, now the developers are fixing some of the problems, but it’s starting to look more and more
like we’re just fire fighting, patching up what actually should be torn down and rebuilt from
scratch.<br><br>
In the many years I’ve been working as a software developer, I’ve seen this happen time and time
again. There’s nothing I’m quite as passionate about as finding ways to prevent this phenomenon
known as:
</aside>
</section>
<section data-background-image="images/Castle_Phase_4_Background.png" data-background-size="contain">
<h1>Software Entropy</h1>
<h2>Accumulation of Technical Debt</h2>
<aside class="notes">
Software Entropy.<br><br>
While software is actively being worked on, its complexity is constantly increasing, technical
debt is accumulated, the software becomes harder and harder to modify without breaking things. At
some point, the cost of fixing it is higher than the cost of rewriting it from scratch.
</aside>
</section>
<section>
<figure class="stretch">
<img data-src="images/Tech_Debt_Graph.png">
<figcaption>
(source: <a
target="_blank"
href="https://www.informatik-aktuell.de/entwicklung/methoden/langlebige-architekturen-technische-schulden-erkennen-und-beseitigen.html">Dr.
Carola Lilienthal</a>)
</figcaption>
</figure>
<aside class="notes">
To prevent this, you have to refactor your code base periodically. It’s not just about fixing
bugs, it’s about keeping your code clean, about making it consistent.<br><br>
It’s OK to make some debt, but you have to make sure you have recurring phases of paying off that
debt, lest it become too great and make cleaning up too costly.
</aside>
</section>
<section>
<img class="stretch" data-src="images/Tech_Debt_Graph_With_Looping.png">
<aside class="notes">
Otherwise you'll get to a point where the cost of maintaining your code base and adding new
features is so high that there is no other way than doing a full rewrite.
</aside>
</section>
<section>
<img data-src="images/React_Logo.png" class="stretch">
<aside class="notes">
So what does all that have to do with React?<br><br>
What makes React so special to me is that it is based on components. You break down your
application into neat little parts and work on them separately. You compose your application from
building blocks, like Lego bricks.
</aside>
</section>
<section data-background-image="images/Lego_Webpage_Disassembled.png" data-background-size="contain">
<aside class="notes">
If you use React right, you can create a set of loosely coupled components, with well defined
purpose.<br><br>
You can use that set of building blocks to build your pages. You can work on each component
separately, you can even split them into independent npm packages and versionize them.
</aside>
</section>
<section data-background-image="images/Lego_Webpage_Building_1.png" data-background-size="contain">
<aside class="notes">
So let's build our website: here's the footer…
</aside>
</section>
<section data-background-image="images/Lego_Webpage_Building_2.png" data-background-size="contain">
<aside class="notes">
…here are some posts from our discussion forum…
</aside>
</section>
<section data-background-image="images/Lego_Webpage_Building_3.png" data-background-size="contain">
<aside class="notes">
…here's a side bar…
</aside>
</section>
<section data-background-image="images/Lego_Webpage_Building_4.png" data-background-size="contain">
<aside class="notes">
…a search form to search the posts…
</aside>
</section>
<section data-background-image="images/Lego_Webpage_Assembled.png" data-background-size="contain">
<aside class="notes">
…and a page header on top.<br><br>
So let's say we don't like the header anymore, it looks sooo 2017 and after all, it's 2018.
</aside>
</section>
<section data-background-image="images/Lego_Webpage_Fancy_Assembling.png" data-background-size="contain">
<aside class="notes">
We can throw away the old heder and replace it with a shiny, new one.
</aside>
</section>
<section data-background-image="images/Lego_Webpage_Fancy_New_Header.png" data-background-size="contain">
<aside class="notes">
In the pre-component age, with a tightly coupled, monolithic application, this was expensive
and time consuming, much less now.<br><br>
React gives us the chance to make this dream come true, but it does not come automatically
</aside>
</section>
<section>
<h3>Big Ball of Mud</h3>
<img class="stretch" data-src="images/Big_Ball_of_Mud.png">
<h4>React Component Spaghetti</h4>
<aside class="notes">
Just because you are using React doesn't mean you have a clean, well structured codebase
automatically. You can create a “big ball of mud" with React just as well as you can with jQuery
or anything else.
</aside>
</section>
<section>
<h2>Divide and Conquer</h2>
<img class="stretch" data-src="images/Breaking_Down_Logo_Only.png" style="margin: -40px 0">
<aside class="notes">
So what are some best practices to leverage the power of components in your React application?
How do you actually break down your React app?<br><br>
Let's do a quick recap of the most basic practices that we use at eBay, and I'm sure most
of you do in your work, too.
</aside>
</section>
<section>
<img data-src="images/Presentational_And_Container_Components.png" class="stretch">
<h3>Separate presentational<br>and container components</h3>
<aside class="notes">
In React, you can happily mix up your UI logic, your business logic and your JSX template
code. That doesn't mean you should, though – it's the shortest path to the before mentioned
steaming bowl of spaghetti code.<br><br>
Instead, you create make your presentational components “dumb” by spoon feeding them with
the props they should display, and you wrap them in container components, or “smart”
components, that prepare those props.<br><br>
Higher order components or render props are commonly used patterns for these. I personally
love to use Andrew Clarke's recompose library.
</aside>
</section>
<!--section>
<pre class="box-shadow stretch"><code data-trim data-noescape>
import { compose, withState, withHandlers, lifecycle } from 'recompose';
import fetchData from './fetchData';
import DumbComponent from './DumbComponent';
const enhance = compose(
withState('data', 'setData', null),
withHandlers({
load({ setData }) {
return async () => {
setData(null);
const data = await fetchData();
setData(data);
};
}
}),
lifecycle({
componentDidMount() {
this.props.load();
}
})
);
export default enhance(DumbComponent);
</code></pre>
<aside class="notes">
Here's a quick code example that shows how you can use Recompose to create a
container component that loads some data when it mounts and then passes that
data to a wrapped presentational component, whose only job is displaying that data.
</aside>
</section-->
<section>
<img data-src="images/storybook-screenshot.png" class="stretch box-shadow">
<h3>Organize presentational components<br>in a separate style guide</h3>
<aside class="notes">
Once you've separated presentational components, you can use them to create a living style
guid, using tools like Storybook or React Styleguidist.<br><br>
Here's a screenshot from the styleguide we use for our website.
</aside>
</section>
<!--section>
<div class="PH_TODO">monorepos</div>
</section-->
<!--section>
<img data-src="images/Architecture_Schema_API_and_App.png" class="stretch">
<h3>Separate application server and API server</h3>
</section-->
<section>
<h3>Separation of Concerns</h3>
<figure class="stretch">
<img data-src="images/Separation_of_Concerns_20th_Century.png">
<figcaption>
(source: <a target="_blank" href="http://speakerdeck.com/didoo/let-there-be-peace-on-css">Cristiano
Rastelli</a>)
</figcaption>
</figure>
<aside class="notes">
So let's talk about “separation of concerns”, a term you probably remember from your computer
science classes, but what does it actually mean from a frontend developer's perspective?<br><br>
In the early days of web programming, frontend developers tended to think it means:
You have HTML, which is concerned with stucturing the information you display on a web page,
you have CSS, which is concerned with making it pretty, and you have JavaScript to make it
wiggle when you click on it, or whatever.<br><br>
But we live in the 21st century now, what some people call the component age, and there is
a better way to define separation of concerns:
</aside>
</section>
<section>
<h3>Separation of Concerns</h3>
<figure class="stretch">
<img data-src="images/Separation_of_Concerns.png"
onclick="this.src = 'images/Separation_of_Concerns_JS.png';">
<figcaption>
(source: <a target="_blank" href="http://speakerdeck.com/didoo/let-there-be-peace-on-css">Cristiano
Rastelli</a>)
</figcaption>
</figure>
<aside class="notes">
The concerns that should be separated are the features of the app, the lego bricks that I'm
building my app of, each containing their own HTML, CSS and JavaScript<br><br>
Although, with React, the graphic perhaps should look more like this (<em>click right graph</em>)<br><br>
At this point I have to admit I stepped into the trap of this old school way of thinking
of separating concerns not by feature, but by type of tool. Let me show you what I mean.
</aside>
</section>
<section>
<div style="display: flex; align-items: center; justify-content: space-between" class="stretch">
<div class="box-shadow" style="overflow: hidden; width: 525px; height: 667px;">
<div style="overflow-y: scroll; overflow-x: hidden; width: 525px; height: 100%; background: #fff;">
<img data-src="images/Dir_Structure_Before_Blurred.png"
onclick="this.src = 'images/Dir_Structure_Before.png';"
style="margin: 0; width: 525px; height: 1854px; max-width: inherit; max-height: inherit">
</div>
</div>
<div style="width: 32px"></div>
<div class="box-shadow" style="overflow: hidden; width: 525px; height: 667px">
<div style="overflow-y: scroll; overflow-x: hidden; width: 525px; height: 100%; background: #fff;">
<img data-src="images/Dir_Structure_After_Blurred.png"
onclick="this.src = 'images/Dir_Structure_After.png';"
style="margin: 0; width: 525px; height: 1854px; max-width: inherit; max-height: inherit">
</div>
</div>
</div>
<aside class="notes">
This is how we organized the JavaScript modules of our app at first
(<em>click left box</em>). As you can see, we are using Redux, so we created directories
for our presentational components, our containers, our actions, our reducers and selectors,
all nicely sorted by module type, and at the root, one source and one test directory for
unit tests, which has the same structure as the source directory<br><br>
This is basically the same way of organizing things in the old days, when separation of concerns
meant separating HTML, CSS and JavaScript.<br><br>
It's OK for a small app, but as the codebase grows, you wind up with directories with
hundreds of modules. <br><br>
My colleagues and I got together and discussed a better way to organize our code, this is
what we came up with: (<em>click right box</em>)<br><br>
We put each feature of our website in its own directory, so in this example taken from our
discussion forum, we have one directory for a feature that displays a list of discussion boards,
and another one for a feature that displays a list of discussion threads within a board.
Each has its own components, reducers, actions, and so one, and each directory has its
own index.js file that defines a clear interface to the parent application that uses the feature.<br><br>
This was the first step on our journey to achieving properly decoupled modules. Or should me
even call them modules? That term is already used for many, many things, so we came up with
our own term to call them:
</aside>
</section>
<section>
<h1>Bricks</h1>
<img class="stretch" data-src="images/Breaking_Down_Logo_Only.png" style="margin: -40px 0">
<aside class="notes">
Bricks – the building blocks of our web applications, like putting Lego bricks together.
</aside>
</section>
<!--section>
<img data-src="images/Fractals.png" class="stretch">
</section-->
<section>
<img data-src="images/Lego_Webpage_Hacker_News.png" class="stretch">
<aside class="notes">
It's about time to get more practical and show some actual code. I'll use an example brick
I've made throughout the rest of my talk. It shows the latest headlines from Hacker News.
My Hacker News brick is a little application within my main application that uses Redux to
display some data to the user.
</aside>
</section>
<section>
<iframe data-src="https://codesandbox.io/embed/mok6364lmx?hidenavigation=1&view=preview"
class="stretch box-shadow"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
<aside class="notes">
It looks like this. It preloads its data from the global Redux state, and when you
hit the “Update” button, it updates its content from the Hacker News API.
</aside>
</section>
<section>
<video controls class="stretch">
<source data-src="images/Redux_Movie.mp4" type="video/mp4"/>
</video>
<aside class="notes">
For those who are not familiar with how Redux works, let's do a quick recap:
(<em>start video</em>)<br><br>
The container component gets a prop “top stories” from the Redux state.
Except it doesn't, top stories is null at this point.<br><br>
So the container renders a spinner on the webpage and dispatches an action to start
loading the top stories.<br><br>
Data loading is a side effect which happens asynchronously, this is handled by a saga,
which gets triggered by the action. It fetches the data from the Hacker News API
and then dispatches another action with the data as payload<br><br>
This is picked up by the reducer, which produces a new version of the Redux state
with the data.<br><br>
The container is connected to the state and reacts to the state change by
replacing the spinner component with the presentational component that displays the data.
</aside>
</section>
<section data-transition="slide-in fade-out">
<div class="columns-container">
<div class="column-one-third" style="margin: 0 -50px 0 0">
<img data-src="images/Five_Stud_Lego_Brick_0.png">
</div>
<div class="column-two-thirds">
<h4 class="nocaps">index.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
export { default as HackerNews } from './components';
export * from './actions';
export { default as reducer } from './reducer';
export { default as saga } from './sagas';
export { default as selectors } from './selectors';
</code></pre>
</div>
</div>
<aside class="notes">
There is a contract we have about our bricks: each brick shall have one index.js
file in its root directory which provides exports of the five parts of the brick
that need to be integrated in the parent application.
</aside>
</section>
<section data-transition="fade-in fade-out">
<div class="columns-container">
<div class="column-one-third" style="margin: 0 -50px 0 0">
<img data-src="images/Five_Stud_Lego_Brick_1.png">
</div>
<div class="column-two-thirds">
<h4 class="nocaps">index.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
<mark>export { default as HackerNews } from './components';</mark>
export * from './actions';
export { default as reducer } from './reducer';
export { default as saga } from './sagas';
export { default as selectors } from './selectors';
</code></pre>
</div>
</div>
<aside class="notes">
Each brick exports one main React component, which is usually a container
connected to the Redux store. In case of our Hacker News Brick, this is the
component that renders the box with the latest news.
</aside>
</section>
<section data-transition="fade-in fade-out">
<div class="columns-container">
<div class="column-one-third" style="margin: 0 -50px 0 0">
<img data-src="images/Five_Stud_Lego_Brick_2.png">
</div>
<div class="column-two-thirds">
<h4 class="nocaps">index.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
export { default as HackerNews } from './components';
<mark>export * from './actions';</mark>
export { default as reducer } from './reducer';
export { default as saga } from './sagas';
export { default as selectors } from './selectors';
</code></pre>
</div>
</div>
<aside class="notes">
Then each brick exports one or more action creator functions, which allow the
parent app – or even other bricks – to send signals to our brick to make them
do things. In case of our HackerNews brick, there is an action that triggers
updating the Hacker News stories.
</aside>
</section>
<section data-transition="fade-in fade-out">
<div class="columns-container">
<div class="column-one-third" style="margin: 0 -50px 0 0">
<img data-src="images/Five_Stud_Lego_Brick_3.png">
</div>
<div class="column-two-thirds">
<h4 class="nocaps">index.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
export { default as HackerNews } from './components';
export * from './actions';
<mark>export { default as reducer } from './reducer';</mark>
export { default as saga } from './sagas';
export { default as selectors } from './selectors';
</code></pre>
</div>
</div>
<aside class="notes">
Each brick has a reducer that updates the Redux state in response to actions
being dispatched. This reducer is combined with the reducer of the parent
application.
</aside>
</section>
<section data-transition="fade-in fade-out">
<div class="columns-container">
<div class="column-one-third" style="margin: 0 -50px 0 0">
<img data-src="images/Five_Stud_Lego_Brick_4.png">
</div>
<div class="column-two-thirds">
<h4 class="nocaps">index.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
export { default as HackerNews } from './components';
export * from './actions';
export { default as reducer } from './reducer';
<mark>export { default as saga } from './sagas';</mark>
export { default as selectors } from './selectors';
</code></pre>
</div>
</div>
<aside class="notes">
Each brick has one Redux saga that is run by the parent application. Sagas
are for handling side effects such as data loading through a Redux middleware.
</aside>
</section>
<section data-transition="fade-in slide-out">
<div class="columns-container">
<div class="column-one-third" style="margin: 0 -50px 0 0">
<img data-src="images/Five_Stud_Lego_Brick_5.png">
</div>
<div class="column-two-thirds">
<h4 class="nocaps">index.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
export { default as HackerNews } from './components';
export * from './actions';
export { default as reducer } from './reducer';
export { default as saga } from './sagas';
<mark>export { default as selectors } from './selectors';</mark>
</code></pre>
</div>
</div>
<aside class="notes">
Finally, each brick exports an object with selector functions that
are used to pick data from the Redux state. Why not just access the
Redux state directly?
</aside>
</section>
<section>
<img class="stretch" data-src="images/State_And_Components.png">
<aside class="notes">
Well, there is always only one global Redux state as single source of
truth. This is one of the big advantages of Redux: you can look at the
state at any point to see exactly what's going on. Your Hacker News
brick has its own place in that state. If we access the state from
the HackerNews component directly, we'll have to make sure that
the path to the data in the state is always exactly the same –
in this case modules > hackerNews. By using selectors, we can put the
state of our brick anywhere, and we only have to change a single line of
code to do it:
</aside>
</section>
<section data-transition="slide-in fade-out">
<h4 class="nocaps">registerSelectors.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
import { registerSelectorsForUseWithGlobalState } from "@modular-toolkit/selectors";
import { selectors as hackerNews } from "@modular-toolkit/demo-module";
const selectorMapping = {
<mark>"modules.hackerNews": hackerNews</mark>
};
export default () => {
for (const [path, selectors] of Object.entries(selectorMapping)) {
registerSelectorsForUseWithGlobalState(path, selectors);
}
};
</code></pre>
<aside class="notes">
In this code snipped taken from the demo I've showed you,
we define the path for our bricks with a path string.
</aside>
</section>
<section data-transition="fade-in fade-out">
<h4 class="nocaps">registerSelectors.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
import { registerSelectorsForUseWithGlobalState } from "@modular-toolkit/selectors";
import { selectors as <mark>hackerNews</mark> } from "@modular-toolkit/demo-module";
const selectorMapping = {
<mark>"bricks.widgets.hackerNews": hackerNews</mark>
};
export default () => {
for (const [path, selectors] of Object.entries(selectorMapping)) {
registerSelectorsForUseWithGlobalState(path, selectors);
}
};
</code></pre>
<aside class="notes">
In this code snipped taken from the demo I've showed you,
we define the path for our bricks with a path string.
</aside>
</section>
<section data-transition="fade-in fade-out">
<h4 class="nocaps">registerSelectors.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
import { <mark>registerSelectorsForUseWithGlobalState</mark> } from "@modular-toolkit/selectors";
import { selectors as hackerNews } from "@modular-toolkit/demo-module";
const selectorMapping = {
"bricks.widgets.hackerNews": hackerNews
};
export default () => {
for (const [path, selectors] of Object.entries(selectorMapping)) {
<mark>registerSelectorsForUseWithGlobalState</mark>(path, selectors);
}
};
</code></pre>
<aside class="notes">
We used a little trick to be able to do this:
in the registerSelectorsForUseWithGlobalState function, which is
just eight lines of code, we store the path as a property
of the selector function. In the brick's container component,
instead of using Redux's connect function directly, we use
out own version which reads this path and “rebases” the
selectors to point to the correct part of the state.
</aside>
</section>
<section data-transition="fade-in fade-out">
<h4 class="nocaps">rootReducer.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
import { combineReducers } from "redux";
import { reducer as <mark>hackerNews</mark> } from "@modular-toolkit/demo-module";
export default combineReducers({
bricks: combineReducers({
<mark>hackerNews</mark>
})
});
</code></pre>
<aside class="notes">
So we've seen how to connect the Brick's selectors. To be able to
use the brick in our app, we have to do two more things:
first, add the brick's reducer to the app's reducers. We are using
the combineReducers function from the Redux library here to be
able to deep nest our reducer, and combine it with reducers from
other bricks, or from the parent application.
</aside>
</section>
<section data-transition="fade-in slide-out">
<h4 class="nocaps">configureStore.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
import { applyMiddleware, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./rootReducer";
import { <mark>saga</mark> } from '@modular-toolkit/demo-module';
export default () => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(<mark>saga</mark>);
return store;
};
</code></pre>
<aside class="notes">
The last piece of the puzzle that's missing to wire up our brick
is running its saga, which we do using the Redux saga middleware.
</aside>
</section>
<section data-transition="slide-in slide-out">
<iframe data-src="https://codesandbox.io/embed/v2137r7k7?hidenavigation=1&module=%2Fsrc%2Fpages%2Fhome%2Fcomponents%2FHomePage.js"
class="stretch box-shadow"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
<aside class="notes">
So that's it! Now I can just drop my HackerNews components anywhere in my
page, just as if it were a simple presentational component. It will load
its data automatically when it is mounted, or I could dispatch an update
action to make it update.
</aside>
</section>
<section>
<img class="stretch" data-src="images/Lego_Webpage_Assembled.png">
<aside class="notes">
So there you have it. Now we can build a large, complex platform and split it
up into little pieces, different developers, even different teams can work
independently on the bricks. They are distributed as versionized npm modules.
It's all good, right?
</aside>
</section>
<section>
<div class="columns-container">
<div class="column-one-half">
<img data-src="images/Confused_Jackie.png">
</div>
<div class="column-one-half">
<ul>
<li>Complicated “wiring up” of selectors, sagas and reducers</li>
<li>No code splitting</li>
</ul>
</div>
</div>
<aside class="notes">
Not quite, there were two major drawbacks to our “Bricks architecture”<br><br>
The wiring up of selectors, sagas and selectors in multiple places in the
parent application is complicated and tedious. Redux is famous for requiring tons
of boilerplate code, but do we really have to make that even worse?<br><br>
The second problem is more serious: with the initialization I've just showed you,
we have to import everything from all the bricks at application start. For a small
app, that's not a problem, but for a large app, this will mean a having a huge JavaScript
bundle for the users to download, that contains code for parts of the website they
may very well never see. It would be nice to be able to use webpack's code splitting
feature to create many smaller bundles, for example one per page or route.
</aside>
</section>
<section>
<img data-src="images/Tech_Debt_Graph_No_Red.png">
<aside class="notes">
So when it was time again to take a break from building new features and
take care of accumulated technical debt, we got together and figured out a
better way to integrate our bricks.
</aside>
</section>
<section data-transition="slide-in fade-out">
<h4 class="nocaps">configureStore.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
import { applyMiddleware, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './rootReducer';
import hackerNews from '@modular-toolkit/demo-module';
import { <mark>BrickManager</mark> } from '@modular-toolkit/bricks/BrickManager';
export default () => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, sagaMiddleware);
const brickManager = new <mark>BrickManager</mark>({ store, reducer, sagaMiddleware });
brickManager.installBricks({
'bricks.hackerNews': hackerNews
});
return store;
};
</code></pre>
<aside class="notes">
What we came up with is a new tool that we call the brick manager.
It gets initialized with the parent application's Redux store, its root reducer
(without any brick reducers) and the Redux saga middleware.
</aside>
</section>
<section data-transition="fade-in slide-out">
<h4 class="nocaps">configureStore.js</h4>
<pre class="box-shadow"><code data-trim data-noescape>
import { applyMiddleware, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './rootReducer';
import <mark>hackerNews</mark> from '@modular-toolkit/demo-module';
import { BrickManager } from '@modular-toolkit/bricks/BrickManager';
export default () => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, sagaMiddleware);
const brickManager = new BrickManager({ store, reducer, sagaMiddleware });
brickManager.installBricks({
'bricks.hackerNews': <mark>hackerNews</mark>
});
return store;
};
</code></pre>
<aside class="notes">
Instead of exporting reducer, saga and selectors separately from the brick,
we now have a default export that gives us an object with these. We can
pass this to the brick manager's install brick method to, well, install the
brick. And the nice thing is, this doesn't necessarily have to happen in
the configure store module, it can be anywhere in our app, at any time.
</aside>
</section>
<section data-transition="slide-in slide-out">
<iframe data-src="https://codesandbox.io/embed/6175jwk3jz?module=%2Fsrc%2Fpages%2Fgists%2Fcomponents%2FGistsPageContainer.js"
class="stretch box-shadow"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
<aside class="notes">
Remember the Recomposed container component I've showed you earlier?
This is where Recompose really shines:<br><br>
In this version of my demo, I've created a context provider that gives me access
to the bricks manager anywhere in my application. This way, I can use a higher order
component to simply plug in my brick anywhere, like this
(<em>add withBricks in container and Gists component in page component</em>)
</aside>
</section>
<section data-transition="slide-in slide-out">
<iframe data-src="https://codesandbox.io/embed/04xrv2zwwv?module=%2Fsrc%2Fpages%2Fgists%2Fcomponents%2FGistsPageContainer.js"
class="stretch box-shadow"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
<aside class="notes">
So, let's take this one step further and use the new React 16.6 features lazy and Suspense
to lazy load not only the GistsPage component, but also out brick!<br><br>
This way, webpack's code splitting feature will create separate bundles for all the
code that's used on the Gists page, including out brick, with its reducer, saga, selectors.<br><br>
Nice! Although that higher order component I'm using does look pretty complicated.
Luckily, with the next React version, we can use this revolutionary new feature called hooks.
</aside>
</section>
<section data-transition="slide-in slide-out">
<iframe data-src="https://codesandbox.io/embed/9y6w47q10o?module=%2Fsrc%2Fpages%2Fgists%2Fcomponents%2FGistsPage.js"
class="stretch box-shadow"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
<aside class="notes">
Now I can create a hook, let's call it <em>useBrick</em> and simply pop that into my page component
before I render my Gists brick. It doesn't get any easier than this!
</aside>
</section>
<!-- Summary, End -->
<section>
<img class="stretch" data-src="images/Pretty_Lego_Castle.png">
<aside class="notes">
So this is how we build our beautiful Lego brick castles at eBay.
I hope it was an inspiration to you to always go that one step further,
to break down your React app and battle code entropy and technical debt!
</aside>
</section>
<section>
<img style="height: 20vh" data-src="images/Shameless_Plug.png">
<p>
<a href="https://github.com/technology-ebay-de/modular-toolkit">github.com/technology-ebay-de/modular-toolkit</a>
</p>
<code data-noescape data-trim>
npm install --save <mark>@modular-toolkit</mark>/bricks
</code>
<aside class="notes">
One last thing: if you're interested in using the bricks system we've created
in your own application, we have an open source library for you. It's
called modular-toolkit and contains the brick manager I've shown you,
along with some other useful tools and a demo that shows how to put it all together.
</aside>
</section>
<section>
<h2>Thank you!</h2>
<table class="credits">
<tr>
<td>
<p class="name">Ninja Maaß
<p>
<p class="function">Frontend Developer</p>
</td>
<td>
<p class="name">Daniel Schäfer</p>
<p class="function">Frontend Developer</p>
</td>
<td>
<p class="name">Eike Schulte-Kersmecke</p>
<p class="function">Backend Developer</p>
</td>
</tr>
<tr>
<td>
<p class="name">Anja Kunkel</p>
<p class="function">Backend Developer</p>
</td>
<td>
<p class="name">Torsten Walter</p>
<p class="function">Frontend Developer</p>
</td>
<td>
<p class="name">Juho Vepsäläinen</p>
<p class="function">Dev Consultant</p>
</td>
</tr>
<tr>
<td>
<p class="name">Mike Krüger</p>
<p class="function">Quality Assurance</p>
</td>
<td>
<p class="name">Hajo Skwirblies</p>
<p class="function">Site Operations</p>
</td>
<td>
<p class="name">Florian Stefan</p>
<p class="function">Architect</p>
</td>
</tr>
<tr>
<td>
<p class="name">Christoph Springer</p>
<p class="function">Team Lead</p>
</td>
<td>
<p class="name">Julia Thiele</p>
<p class="function">Product Owner</p>
</td>
<td>
<p class="name">Jakob Gehring</p>
<p class="function">Product Owner</p>
</td>
</tr>
</table>
<p>
View this presentation online: <a target="_blank" href="http://bit.ly/breakdownreact">http://bit.ly/breakdownreact</a>
</p>
<p class="small">
Patrick Hund | Lead Frontend Developer | <a target="_blank" href="https://twitter.com/wiekatz">@wiekatz</a><br>
Copyright © 2018 mobile.de GmbH
</p>
<aside class="notes">
That's it from me, thank you for listening, and a special thank you to my team at eBay!
</aside>
</section>
</div>
</div>
<script src="lib/js/head.min.js"></script>
<script src="js/reveal.js"></script>
<script>
Reveal.initialize({
width: 1280,
height: 720,
history: true,
dependencies: [
{ src: 'plugin/markdown/marked.js' },
{ src: 'plugin/markdown/markdown.js' },
{ src: 'plugin/notes/notes.js', async: true },
{ src: 'socket.io/socket.io.js', async: true },
{ src: 'plugin/notes-server/client.js', async: true },
{
src: 'plugin/highlight/highlight.js',
async: true,
callback() {
// noinspection ES6ModulesDependencies, JSUnresolvedVariable
hljs.initHighlightingOnLoad();
}
}
]
});
</script>
</body>
</html>