Let me explain how we have achieved at Adevinta, with hundreds of frontend developers, to work with the same set of tools to build our products.
A little bit of background: what's Adevinta?
Adevinta is one of the world’s leading online classified ads businesses, active in 11 countries around the world and reaching more than 1.3 billion average monthly visits worldwide. One of those countries is Spain 🇪🇸 where, as Adevinta Spain, we have different marketplaces. For Real Estate we have Fotocasa, Habitaclia, for jobs InfoJobs, cars and motos in Coches.net, Motos.net. For generalist stuff, you could find Milanuncios.
All these marketplaces are, in fact, different products with different teams and, yeah, different developers with different opinions but, yet, very similar needs. We had spread the same company a dozen different tools to create bundles (Browserify, Webpack, manual systems…), for each we had different versions in many different repositories. And, imagine that, everyone with different configurations that supported different files in so many different ways. In one word: chaos.
The starting point
Example of the old days. A single React component in Adevinta Spain. With a bunch of files starting with a dot for config and its own Webpack configuration for creating a demo for it. Still available at https://github.com/SUI-Components/sui-autocompleted.
One year ago we decided to solve this and move forward. To do so, we iterated over the team called Enablers Frontend. This team's main objective was to grow a common frontend architecture, giving a response to all products needs while focusing on making it scalable, resilient, replicable, robust, and as much cutting-edge technology as possible.
Relying on Compatible Versioning. So developers always get the latest fixes and features for free using the major.
For this, we started creating a set of tools called SUI (Simple User Interface) to extract core functionality and tools for packages. We saw these tools like our AAAS (Architecture as a Software) and focused on following the Unix rules to make the tools as modular, independent and, replaceable as possible. We could change a whole tool always that the entry parameters and the output remained the same.
We also embraced other things: zero-config Javascript (#0CJS), to avoid dot points and config files in our projects and repositories; and Compatible Versioning to rely always on the major version, get the new features and fixes for free and be as less disruptive as possible. Both characteristics forced us to be very careful about our decisions but, after several years, I could say that it was a success.
Sean Larkin talking at the JSCamp about what zero-config means in terms of Developer Experience
@s-ui/bundler, using Webpack as the building platform
So, one of the most important things that all our projects had in common was a way to create the bundles. While some used Browserify, some used Gulp, and others started to use Webpack. We created a new tool called @s-ui/bundler to unify the way of creating it based on Webpack, as it was the de-facto way of bundling apps in the frontend.
“What if it means that you could take just by defining this tiniest package […] and instantly, without needing configuration you automatically have those setups or specifications that you need for your build systems.” — Sean T. Larkin at JsCamp 2018
This offers a set of rules based on the minimal assumptions possible. For example, support SASS and ES6 files using Babel with a babel-preset-sui. We tried to keep these assumptions as little as possible to be able to iterate fast and evolve quickly our dependencies. For example, from Webpack 1 to the actual Webpack 5 we haven’t had to do great changes and we haven’t had to create many majors of @s-ui/bundler for the users to adopt it.
But, as you would see, @s-ui/bundler is just a simple wrapper over Webpack that provides:
Zero configuration to get started with the basic config for all our products. Just put an index.html and app.js and you ready to go.
Development mode with some goodies like Hot Reloading, screen errors, and, where you could easily link external packages with a link-package parameter. The main difference with
npm link
, is that the files are compiled on the fly with babel which results in the fastest hot reloading. Compatible with SASS files as well.App Bundle, Lib mode, and Server compilations.
Built-in CLI for all the commands and also a way to analyze the bundle.
A set of optimizations for CSS and chunks.
If you’ve got the expected project structure, you’ll be able to start a development environment with a single command
So, @s-ui/bundler gives you the possibility of creating your web app in a few seconds with the minimal boilerplate but with the desired structure and technology that we want to follow in Adevinta Spain across all the projects. But, still, there’s more…
Evolving the platform, building on top of it
In Adevinta Spain we decided to embrace React and use it to build all our UI so, as you could imagine, building your app with @s-ui/bundler lets you use React as well from scratch. You just install the dependency and you’re ready to go.
All our projects are aimed to be SPAs. Also, we wanted to offer our developers a very easy way to create a Server-side rendering experience for their products. For that, we created two separated tools: @s-ui/react-initial-props and @s-ui/ssr.
@s-ui/react-initial-props, the first stone for universal apps
If you’re creating a SPA in React you might be tempted to fetch the data for your app in the useEffect
hook. So, you could control when the data is available to show a placeholder.
This means that your app will have the needed data on the client but not on the server. That means that your app might not be easily crawlable by bots.
@s-ui/react-initial-props package offers a way to make your easily your app isomorphic. It let you create a static method called getInitialProps for your page component. This method, which returns a Promise, will be loading the chunk of your app as well.
You could pass a contextFactory to create the needed context for your page. The contextFactory should be universal. loadPage will execute the getInitialProps
static method for you and deal with the async chunk.
loadHomePage has a component that will deal with the asynchronicity for you. You could even add a renderLoading to render a component while fetching the data.
As you can imagine, the method getInitialProps needs to be universal, the code should be executable in the server and the client. For that, getInitialProps receives the same parameters in both environments.
This approach is convenient as it avoids re-renders as other options like React-Transmit that caused a long time to respond, especially in the server.
Next.js has something very similar called getServerSideProps
.
@s-ui/ssr, transform your SPA in a Server-Side Rendering App
If you were using @s-ui/bundler and @s-ui/react-initial-props… what would you think if I say to you that you could have for free a server-side rendering app without writing a single line of code?
@s-ui/bundler is a big piece of our Lego. It connects well with @s-ui/react-initial-props and, if you do so, you’ll be able to connect with @s-ui/ssr to get Server-Side Rendering by writing zero lines of code
@s-ui/ssr package essentially relies on the convention used in @s-ui/react-initial-props so you don’t have to write a line of code to transform your SPA in a full app with SSR. Also, it doesn’t only generates the code but create a Docker container ready to be deployed and add some interesting features like the proposition of Google to perform dynamic rendering to do SSR for the bots and fetch the data in the client for your users.
This kind of tool wasn’t easy to create if it wasn’t built on top of a common platform. Now, you should think about them like Lego pieces that let you the possibility of building within the base a greater app by using this modular approach thanks for following some foundations.
Some frequently asked questions 🤔 and comments…
🗣 Isn’t this a simple wrapper?
Yes and no. Yes, as we’re wrapping a bunch of tools to provide it with a custom config and still, no, as they not only could have a config that would be reusable but also it has some nice additions (like some CLI interfaces to get the most of it). For example, isn’t create-react-app or standard a wrapper of tools? And yet, they're pretty powerful. Wrapping tools give a simple way of creating a project from scratch, reusing the choices, sharing the benefits of some decisions, and converging to follow the same rules across different projects.
🗣 Doesn’t this bother developer opinions about what tools to use?
I understand this question. Not every developer has this concern but, usually, seniors with plenty of years of experience don’t want others to decide the tools or architecture to use in their projects. I could empathize with this feeling. However, one of the mottos of Adevinta is “One team”.
Let me say that working as “One team” in the frontend is not easy. If the debate about “semicolons or not in Javascript” could unleash a war, imagine the limitless discussions about UI libraries, frameworks, and different architecture approaches. We try to listen and collect all the feedback before making decisions but, in the end, a decision should be made.
Also, we’re far from perfection and we’re still improving this to get the most of the developers comfortable but, still, some people could not just accept a different
🗣 But I think is better if everyone is free to choose whatever they want!
I wouldn’t like to start a discussion about convergence vs. divergence. From my point of view, both have advantages and drawbacks. In my experience in Adevinta Spain, converging in a set of tools has accelerated the development, improving the DX and the TTM (Time to Market) of features and new products. The start was not easy but the cost we assumed at the beginning has been more than compensated.
Now, this doesn’t mean this strategy could work in every company. As I explained in the beginning, Adevinta Spain has different products that are essentially the same structure: a marketplace. The main objective is to let developers focus on creating the best product possible without having to lose time in dealing with tools.
🗣 Still, there are better solutions out there!
create-react-app and Next.js would be the most similar solutions that we could adopt. Still, there’re some important differences. We needed the build our platform to be as modular as possible to be adopted by all the products step by step.
For example, we could maintain all the codebases of the old project but start using the linter, later just use the tool for testing and, maybe as the last one, use the bundler. Or maybe try to use just the react-initial-props that could give a lot of value to the user if the project already used a custom Webpack config.
NextJS, for example, provides you with a whole framework in a single piece that requires to be adopted at once. Don’t get me wrong, I love ❤️ NextJS and Vercel team but, for us, we had to build a platform for several projects at once, and being able to adopt progressively the platform was a must.
Also, when we started this journey, Next.js didn't provide as many features as it does now.. Maybe if we started now, things were would go different.
🗣 I want to discuss more of this!
Well, you’re more than invited to do so. sui-bundler and the other tools are open source. Also, just post your comment or follow the conversation on Twitter. 😊 We will be happy to hear feedback to iterate or improve the platform as every improvement we perform will be shared across all our projects. Thanks for reading!