Hello Habr! I'm Miloš from Badoo, and this is my first Habr post, originally published in our tech blog. Hope you like it, and please share and comment if you have any questions
So… React, amirite???
It appeared in the middle of the decade (plagued by the endless JavaScript framework wars), embraced the DOM, shocked everyone by mixing HTML with JavaScript and transformed the web development landscape beyond recognition.
All those accomplishments, without even being a framework.
Love it or hate it, React does one job really well, and that is HTML templating. Together with a great community and a healthy ecosystem, it’s not hard to see why it became one of the most popular and influential JavaScript libraries, if not the most popular one of all.
Here in the Mobile Web team, we don’t follow any strict JS frameworks - or at least, any popular ones - and we use a mix of legacy and modern technologies. Although that works well for us, manipulating DOM is usually hard, and we wanted to alleviate this by reducing the number of «manual» updates, increasing our code reuse and worrying less about memory leaks.
After some investigation, React was considered the best choice and we decided to go with it.
I joined Badoo in the middle of this process. Having bootstrapped and worked on React projects previously, I was aware of its pros and cons in practice, but migrating a mature application with hundreds of millions of users is a completely different challenge.
JSX
React mixes HTML with JavaScript in a format named JSX. Although it looks like a template language, JSX is actually just a syntax, or syntactic sugar if you will, for React calls, very similar-looking to HTML.
Our own HTML files were well organised, and most of our rendering was done as simply as template.render()
. How could we retain this order and simplicity while moving to React? To me, technical difficulties aside, one idea was obvious: replace our existing calls with JSX code.
![Translating Dust templates to JSX - 2 image](https://www.pvsm.ru/images/2019/01/23/Translating-Dust-templates-to-JSX-2.jpeg)
After some initial planning I gave it a go and wrapped up a command-line tool that performs two simple things:
- Reads templates referenced in UI (JavaScript) file
- Replace
template.render()
calls with the HTML content
Of course, this would only move us halfway, because we would still have to modify the HTML code manually. Considering the volume and number of our templates, I knew that the best approach would be something automated. The original idea sounded simple enough — and if it can be explained, it can be implemented.
After demoing the initial tool to teammates, the best feedback I got was that there is a parser available for the templating language that we used. That means that we could parse and translate code much easier than we could with regular expressions, for example. That’s when I really knew that this would work!
![Translating Dust templates to JSX - 3 image](https://www.pvsm.ru/images/2019/01/23/Translating-Dust-templates-to-JSX-3.jpeg)
Lo and behold, after several days a tool was born to convert Dust.js HTML-like templates to JSX React code. We used Dust, but with a wide availability of parsers, the process should be similar for translating any other popular templating language.
For more technical details, skip to the Open-source section below. We used tools like Esprima to parse JS code, and a PEG.js parser generator to parse Dust templates. In the very simplest of terms, it’s about translating this type of template code:
<div class="encounters {?isExpanded}is-expanded{/isExpanded}">
{?showTooltip}
<div class="tooltip">
<span>{#_t}{encounters_tooltip}{/_t}</span>
<div class="icon">
{@Icon name="icon-encounters" size="stretch" /}
</div>
</div>
{/showTooltip}
<div class="images">
{#images}
<img src="{src}">
<input type="radio" id="{id}" {?selected}checked{/selected} />
<label for="showme-{id}">
{name}
</label>
{/images}
</div>
<div class="footer">
{! encounters-footer template will be injected here !}
</div>
</div>
to its JSX code equivalent:
<div className="encounters {props.isExpanded ? 'is-expanded' : ''}">
{props.showTooltip ?
<div className="tooltip">
<span>{i18n.get('encounters_tooltip')}</span>
<div className="icon">
<Icon name="icon-encounters" size="stretch" />
</div>
</div>
: null}
<div className="images">
{props.images.map(item =>
<img src={item.src}>
<input type="radio" id={`showme-${item.id}`} defaultChecked={item.selected ? true : undefined} />
<label htmlFor={`showme-${item.id}`}>
{item.name}
</label>
)}
</div>
<div className="footer">
{/* encounters-footer template will be injected here */}
</div>
</div>
See side-by-side comparison here.
After this, our process was pretty much straightforward. We automatically converted our templates from one format to another, and everything worked as expected (thank you, automated testing). To begin with, we preserved our old template.render()
API to keep changes isolated.
Of course, with this approach you still end up with templates and not “proper” React components. The real benefit is in the fact that it’s much easier, if not trivial, to switch to React from templates that are already JSX, in most cases by simply wrapping a template code in a function call.
You might think: why not write new templates from scratch instead? The short answer is that there was nothing wrong with our old templates — we simply had a lot of them. As for rewriting them and working towards true componentisation, that’s a different story.
Some might argue that the component model is just another trend that might pass, so why commit to it? It’s hard to predict, but one possible answer is that you don’t have to. If you iterate quickly, you can try out different options, without spending too much time on any of them, until you find the format that works best for your team. That’s one of the core concepts for us at Badoo.
With the rise of ES7/8/Next, Elm and Reason, not to mention TypeScript and similar solutions, code that was once *.js
is becoming more and more indistinguishable from JavaScript, and that trend looks like it’s set to continue. Instead of being overwhelmed by it, why not use that to our advantage?
Open source
In the spirit of doing one thing well, we’ve built these internal tools in several parts:
- dust2jsx — package responsible for actual Dust to JSX translation
- ratt (React All The Things) — command line tool for reading/writing files on disk. Responsible for including referenced templates, and uses
dust2jsx
internally to transform code
We’ve even open-sourced these tools — be sure to check them out, as well as other open-source materials on our GitHub page. Please contribute or simply leave us a comment if you find them useful.
Автор: miloss