A Christmas Treats for you!

We’re happy to announce that we’re open sourcing Treats, our internal React framework.

Felix Tan
Tokopedia Engineering
13 min readDec 25, 2018

--

Co-authored with: Martino CK , Alchrist L, Fadli T, William C, Salman AF and other Treats core contributor team members.

Treats — Tokopedia React Development Kits

Howdy fellow engineers and beloved readers, first of all, we would like to say: Merry Christmas!

This Christmas feels very special to us because at this special time of the year, we’re able to share and give back to the Open Source community. Tokopedia had always been standing on the shoulder of the giants, we build stuff to solve problems with many open source technologies, so it is only natural for us to give back to the community itself.

We’re happy to announce that we’re open sourcing Treats. Treats is a React framework that provides server-side rendering, code-splitting, localization, state-management, etc with zero configuration experience for beginners but easily configurable for power users.

But first, let’s start with The Why

You might ask, why another React framework? Isn’t there already so many React frameworks out there in the open, why reinventing the wheel? Well, here’s the story behind it all!

CMS or Custom? (Commit Strip) http://www.commitstrip.com/en/2015/04/03/cms-or-custom/

Before we adopted React as our de-facto Javascript frontend framework, our web frontend codebase were mainly written in combination of Perl, jQuery and some AngularJS 1.x on a single monolithic repository that is shared with more than 300 other fellow software engineer nakamas. With the monolithic stacks, each team only have two scheduled deployments per-week to avoid conflicts between deployment and any unwanted regression. With project at Tokopedia’s scale and our need for agility to stay ahead of the competition, this technology stack and workflow created some dire problems:

  1. Scheduled deployment means slower iteration and unnecessary wait before teams can release their feature.
  2. Doing Test Driven Development (TDD) is hard on our current stack, no unit tests and integration tests led to unwanted regression and many unpredictable bugs.
  3. Refactoring becomes another unnecessary challenge, because we have to skim and figure out what our spaghetti codes are doing to avoid breaking them.
  4. Maintaining two separate codebases (Perl and AngularJS) are unavoidable for interactive web application that needs server-side rendering (for SEO purpose, etc).
  5. Web app performance were bad because let alone thinking about performance, it is already magical for us to ship our hard to test, hard to maintain frontend codes to production.

The make it better 1.0

Two years ago, when Spotify Model and Tribe system were introduced in Tokopedia, things were getting messier. Each tribe had their own ideas about how we should improve our frontend codebase and there were no standards at how we should do it. Some tribes used Go with Go template as their frontend engine, some uses React with modifying different variations of starter kits that we could find on the market, AngularJS and Go template, some even tried to marry VueJS with Go template. Everything were sticked and glued using NGINX proxy, server-side include, and God knows what else.

When We Leave Coders to do Their own Thing (Commit Strip) http://www.commitstrip.com/en/2017/03/16/when-we-leave-coders-to-do-their-own-thing/

Treats first version were initially one of the variations of React framework that we used back then to migrate our Perl+jQuery+AngularJS codes to React. The difference is, Treats were built from scratch rather than modifying other framework. By building it from scratch, we can modify it as much as we want to cater our needs without any restrictions.

[GIF] Showcase of some pages that’s powered by Treats initial version

There’s still enough rooms in this world for another React framework, let’s build one! — React hipsters on the team

After running it well and stable enough on the first iteration of Treats for months, we decided that it would be a great idea to ship our codes as a full-fledged framework so it would be available for everyone who wants to migrate their codebase to React.

One framework to serve us all

The problem with creating a framework on an already ongoing sporadic improvements is to be able to accommodate as many if not every needs of every person in the room, yet making sure the core of the framework itself isn’t too bloated with so many non-trivial implementations that should be done on the application level rather than on the framework itself. When we’re building a framework that would be used across teams as large and diverse as Tokopedia’s, we also need to make it easy to push updates to each of the application that’s built on top of the framework.

We could just throw a skeleton starter kit to each team and let them build their app based on that, but it would be such a hassle to copy and update the starter kit files every time there’s an update needs to be pushed from the core platform team and it isn’t restrictive enough because each team can still change the code on several places that should be framework level codes to make their code “just works”.

We could also build a single monolithic app and let each team contribute to the app, but then, we’ll be going back to square one and produce another monolith that’s hard to release and even worse, there’s a high chance that an error on some module that’s unrelated to your module can cause your module or even the whole application to break, because you know, SPA and stuff.

With these considerations in mind, Treats were built to be a framework that:

  1. Provide users with list of curated essential dependencies to kickstart their projects such as i18n, state management, graphQL client, head tag management and build tools.
  2. Can work with zero configuration but flexible enough for power users to configure their project to suits their needs.
  3. Easily extensible and all non-basic functionalities should be loosely coupled from the core framework code.
  4. Painless update — update should be as simple as bumping Treats dependency on user’s package.json.

Meet Treats (and its ecosystem)

Treats itself officially consists of several different packages:

  1. treats — The framework itself, this package is the reason all the magics are possible.
  2. create-treats-app — A simple à la create-react-app code generator to help users kickstart their Treats project.
  3. eslint-config-treats — An integrated Eslint config for Treats application development that extends Airbnb and Prettier style guide.
  4. stylelint-config-treats — A Stylelint config for Treats application development.
  5. @treats/addons-base — Official Treats add-ons package that we recommends.

Get started with Treats

Okay, let’s get started with Treats! To kickstart your Treats project, we recommend create-treats-app:

$ npm install -g create-treats-app
$ create-treats-app
$ cd /YOUR/APP/DIRECTORY
$ yarn start

Here’s the directory that you would find when you first created a new Treats project:

Initial Treats Project Structure

Usually all your codes would live under the src directory. There, you’ll see several directories, one of them are _route directory with module.js and route.js file under it. They can be used to register your page and its routing configuration, they’re a perfect example of filesystem hooks. Treats uses filesystem hooks as user’s API to extends its core implementation. By convention, all of these filesystem hooks should live under directories that’s prefixed with _. There’s many other filesystem hooks that you could provide to extend Treats. We’ve already covered about what filesystem hooks are available in Treats on Filesystem Hooks section of Treats documentation.

treats.config.js file can be used to modify Treats build configuration. With this file, you can add aliases, extending Treats Webpack, Babel, Jest and PostCSS configuration, so you wouldn’t stuck with the default build tools setup.

Core Features

Server-side rendering

Treats are fully server-side rendered by default, to populate your component’s data you can use getInitialState from your root page component to fetch or populate your component’s data.

Setting getInitialState for root page component

Code-splitting

There’s a known “hardship” in React community when we’re talking about keeping code-splitting, hot-module replacement and server-side rendering cooperate together. But thanks to react-universal-component and react-hot-loader that we use to build AsyncLoader and AsyncComponent we don’t have to worry about them anymore, all you need to do is wrap your code with these components and it’ll just work.

AsyncLoader that was build on top of react-universal-component act as the code-splitting entry for our async bundle
AsyncComponent that was build on top of react-hot-loader act as the hot-module-replacement enabler for our async bundler

Localization

Localization in Treats are using the popular react-intl library under the hood. React-intl uses FormatJS syntax that we think is very straightforward to use to write translations, you should checkout their docs here. We already do the setup for IntlProvider and other magical things that needs to be done when using react-intl, all you need to do is to fill up the translations according to your needs.

English version of our app will render this
Indonesian version of our app will render this
It’s time to use our translations!

We enabled English and Indonesian as the default language but feel free to adjust them to your needs. Want to adjust the language(s) ? Head on to the Localization section of Treats documentation.

State management

Redux is currently one of the most popular state-management for modern web apps and it is very popular among React community, that’s why we bundled Redux with Treats as one of our main way to do state-management (the other way is using apollo-link-state in conjunction with Apollo client directly without any other state-management library).

With Treats, you’ll only need to take care about your reducer and Redux middleware that you’ll use, all setup for redux provider, etc and all the hassle to hydrate your client-side redux had already been handled by us.

What middleware(s) you want to use today, sir?
Insert reducer to continue..

You might not need Redux in your application, if that’s the case then you can just not include Redux on your Treats build, you can do this by setting the build configuration for Redux on treats.config.js to false like so:

Boom! All those Redux codes just got nuked!

GraphQL client

In short, GraphQL is a query language that let you define what datas that you need and only fetch those datas that you’ve defined in your query, GraphQL usually sits on a single endpoint that aggregates multiple APIs behind it so clients doesn’t have to know the implementation behind it, clients should only care about how it interact with the GraphQL endpoint itself.

To interact with GraphQL endpoints, we can either build our own GraphQL client with plain POST request or use several that’s already available on the market. In Treats, we use Apollo as our GraphQL client of choice. Instead of providing tons of configuration that we usually provide when using Apollo client, we can just use 2 filesystem hooks (or less) if we didn’t need to alter the default configuration:

Inject your GraphQL endpoint URL
Do you need apollo-link-state resolvers? Then add them here! It’s fine if you don’t tho!

To use our GraphQL queries on React components, we could just import any react-apollo’s component from @treats/graphql :

Witness the magic of Apollo GraphQL!

But wait! power users might want to build their own Apollo configuration, how can they do that? Fret not! We’re going to use yet another filesystem hook to do that:

Power users, hang on tight! custom configuration to the rescue!

To make creating Apollo links easier, we provided Apollo links generator that’s already packed with several built-in links, the detail of these built-in links can be found on the documentation.

Just like Redux, if you didn’t want to use GraphQL client, you could make its code goes away by setting the build configuration for it to false:

GraphQL client codes just got nuked!

CSS framework

With Treats, you’re not stuck with single CSS framework implementation. We supported CSS modules with plain CSS, SASS and LESS out-of-the-box. For SASS and LESS users, all you need to do is just install the binary for CSS framework that you’ll use (node-sass or less) and it’ll just works. We also supported CSS-in-JS framework usage by providing simple API to extends Treats server-side rendering functionality. You could take a look at our examples on Treats repository for more.

Choose your flavor, or mix them maybe?

Unit testing

Treats bundled Jest+Enzyme as the main testing framework, we’ve also provided several utilities from Enzyme as global function that can be called from your test files like shallow, mount and render. To make testing with react-intl easier, we also provided shallowWithIntl, mountWithIntl and renderWithIntl that automatically integrates with Treats react-intl implementation.

Unit test your code or it didn’t work!

To ran the unit tests, you can use treats test. Under the hood, treats test command would call our pre-configured Jest, so all Jest CLI options should work with it, for example:

$ treats test --watchAll --clearCache

By default, we provided a built-in integrated configuration for Jest, but you could also extend our default configuration with treats.config.js :

Lemme teach you how to test!

Code generator

As lazy coder ourselves, we found out that in the process of developing a React app, there’s so much boilerplate codes that we need to wrote before actually writing our essential business logics. That’s why we included a simple code generator that eliminates this unnecessary process. To use code generator:

$ treats generate <GENERATOR_NAME|PATH_TO_TEMPLATE_DIRECTORY>
[GIF] Ain’t nobody got time to wrote those boilerplate codes!

There’s several built-in generator template in Treats that you can use:

  1. component — Generates React component boilerplates.
  2. redux — Generates Redux boilerplates (action creator, reducer, thunks, etc).
  3. test — Generates Jest test boilerplates.
  4. helper — Generates Treats helper object boilerplates.
  5. middleware — Generates Treats middleware object boilerplates.

You can also build your own generator template to speed up your workflow. Head on to Building Your Own Generator section of Treats documentation for more.

Advanced Features

Helper and middleware

To extend Treats server-side functionality, we could use helper and middleware.

Relationship of Treats, helper and middleware when serving a request

Middleware are functions that sits between request/response cycle that allows for interjection of code between the cycle. Request and response context are passed on to each middleware calls, that’s why middleware are usually useful if we want to add/modify data that would be passed on to our React components as props, GraphQL clients, etc at the end of the request cycle. Example of a middleware: localization middleware, login middleware, etc.

To provide middleware and Treats with additional functionalities, we could use helpers to act as bridges/adapter to interact with external systems, implementations, etc. For example: Redis client, Database client, Circuit-breaker instances etc.

To learn more about helper and middleware, head on to helper and middleware section of the docs.

Add-ons System

To make the capabilities and customizability of Treats limitless but keeping the core framework itself not too bloated, we could extend Treats functionality with add-ons. Add-ons can consist of helper, middleware, components and/or generator template. Since all compilation happens inside Treats internal, add-ons author doesn’t have to compile their codes with Webpack, Rollup, etc. They can be shipped as is under an NPM package. We recommends to ship your Treats add-ons under @treats scoped NPM package to make browsing for Treats add-ons easier. We’ve also covered about building simple add-ons on the documentation, feel free to check it out!

PS: For more advanced usages, power-ups and gotchas, don’t forget to go to Treats documentation!

Conclusion

It turns out that having our own home-grown React framework was good for our frontend-engineering agility, when we first migrated our Perl codes to React, it would take weeks just to modify, adjust or build boilerplate codes to suit our needs. Using Treats, it’ll only take couple of minutes to setup the configuration and then you could deploy it within another couple of minutes. We even have our own version of create-treats-app that generates all boilerplates for our common server config, deployment scripts, etc. With Treats, deployment, error handling and monitoring also became more predictable, since we’re having the same basic building blocks, whenever we encounter a problem, we could discuss the solutions across teams rather than trying to figure it out by ourself.

The Make it Better 2.0

Make it better had always been one of our DNA as Nakama at Tokopedia. Whilst Treats had already been used to sped up frontend engineering process at Tokopedia, Treats itself are still under very active development, so there’s still so much improvements that could be done with it.

For example, right now, we’re currently working on how to make all applications that’s built on top of Treats can be shipped separately on the background, but it can be combined on the client-side to form a single page application that just works (or if you’re into some gimmick jargons, you could call it micro-frontend 😏). We believe this improvement are a win-win solution for both engineers and users at Tokopedia, because we’re allowing ourself to ship code with agility without sacrificing user experience on the surface.

So what’s next? Treats with VueJS or Angular? combining generated static site page and fully SSR-ed web app into one single page application? making unicorns out of stardusts? 🤔 Well, let’s keep those wild imaginations flowing! Since we’re also inviting you to make Treats better with us! You could look at our list of Github issues or please suggest one if you’ve any improvement ideas. If you have some difficulties while building your stuff with Treats, please don’t hesitate to report it too. If you’re more on to creating your own Treats add-ons, that would also be great, but please don’t forget to let us know, since we’re always keen to see what people build with our technologies!

And if you’re interested in democratizing commerce in Indonesia through technologies and always looking forward towards bigger and bigger problem to solve it and make things better while building great products like Treats, checkout our careers page! Let’s make it happen make it better together! Ciao!

--

--