Introducing rbx: React, Bulma, đ
rbx is a new UI framework for React built on top of the Bulma CSS flexbox framework. Itâs massively powerful, unassumingly light (<10kb), and endlessly extensible. The gold standard for a UI Framework is one provides not too little and not too much. I think rbx nails that. đ
To get started quickly, read the beautiful docs, or npm i rbx
.
Itâs core feature is the as
prop â allowing any component to render as any other component. Think of it as syntactic sugar for higher-order-components.
Every component also supports ref
forwarding. Therefore, you can build upon the awesome power of other packages in your React ecosystem.
Major features:
- comprehensive Bulma implementation
- written in TypeScript
- endlessly extensible
- minimal, yet feature rich
- unopinionated (plays well with other packages)
Give it a whirl
A note on compositionâŚ
Itâs expected that youâll end up finding these components to be semantic (e.g. Navbar.Burger
is a ⌠burger for the Navbar), but generally un-stitched. For example, it takes a few more lines to compose a fully featured Navbar
than in other UI Frameworks, but this is a feature not a bug (hint: read on, but this buys users a lot of power).
The reasoning behind this is that rbx was designed to give you and your opinions preference when building. In the language of Atomic Design, you could consider this to be a collection of atoms and molecules. You donât need to stitch your own layouts together to get started, but when you get down to it, youâll find that youâre naturally composing re-usable patterns with this framework.
The story
When I set out to build rbx, Iâd already written the TypeScript typings for react-bulma-components, but was unsatisfied with them. I found many bugs and errors that existed solely because that framework wasnât typed. In my perspective, the only way to build a first class, modern UI Framework for JavaScript in 2018/2019 is with TypeScript.
I also wanted the packageâs users to have incredible documentation, and tests that are informative to use.
In short, I hard forked react-bulma-components and promptly re-wrote the package (completely, at least four times!) in an effort to slim it down to the core essence of what I believe a UI Framework ought to be â a cohesive set of simple components that can (and should) be a great starting point for users.
Architecture goals (the good)
Having used other UI Frameworks like ant-design, react-bootstrap, and react-semantic-ui, I was weary of opinionated design architectures. For example, while antdâs form controls are nice, an integration with a best-of-breed form library like Formik becomes difficult. These UI Frameworks are clingy and controlling. Practically speaking, that makes usersâ relationships with them a bit tedious.
The first goal was to build a UI Framework that was neither too-little nor too-much.
Perhaps this is because Iâm not a native of the JavaScript world, but an immigrant to it, that I see comprehensive, switch-army knife solutions as a red flag. Theyâre fast at getting you 80% of the way there, but immensely troublesome when you need to finish your work. You end up either becoming a zealot of the package architectâs world-view on everything from design to form implementation to icons, or figure out how to strip away enough of the bloat to be productive again.
rbx, then, comes with no icons(!), and only a few components have state. Even those components with state can be easily controlled (via their managed
prop) and can instead directed by external state management tools, like redux or mobx (or anything else).
The second goal was to use a very strict configuration to force clean code.
When I seriously began with JavaScript, I found myself a victim of foolish mistakes. So I decided I to get some help from tooling. The UI Framework isnât opinionated, but Iâve come to rely heavily on third party tools to keep the code on the rails.
To stay in the lane and create a stable package for users this package utilizes an opinionated code formatter (prettier), a robust test suite (jest and enzyme), and a very strict TSLint config that prevents simple, subtle, and yet consequential mistakes.
The third goal was to build on documentation and examples as first-class citizens of the package â just like tests.
Simply put, tests exist alongside the code (in __tests__
folders), docs exist alongside the code (in __docs__
folders), and examples are located at the root in the examples
directory.
Upon each build by the continuous integration server (TravisCI), documentation must compile and the examples must pass their tests, too. This is so that the examples and documentation remain up-to-date.
The process from a build perspective is straight-forward, but perhaps untraditional:
- run unit tests (tests in
**/__tests__/*.test.tsx?
) - build a distribution (to
dist
) - test the examples (in
examples
) against the new build - build the docs into a production build
Any of those steps can fail, and if they do, it will fail the entire build.
The fourth (and final) goal was to make rbx simple and customizable.
Bulma is already comprehensive. If you want to customize it via SASS, you can. rbx will support you â and help you maintain great typing support in TypeScript and regular JS (via PropTypes).
For example, if you want to introduce a new color to the palette, itâs about 10 lines of JavaScript (and another 10 in TypeScript if youâre using that language). Check the examples folder for with-customization
.
This also works for adding sizings to individual components, introducing new breakpoints, and much much more.
Challenges (the bad and ugly)
In my 90 days of building rbx, much was challenging. But, by far, the hardest part was getting packages to play nicely with themselves and each other.
Here are a few examples:
- Babel 7 had trouble properly parsing normal TypeScript syntax that was key to my package until 7.2.0. Even then, I was forced to enable
--isolatedModules
to get other tooling to work (i.e. no re-exporting of types). đ§ - VSCode wouldnât show all my TSLint errors until I abandoned the VSCode TypeScript extension in favor of Microsoftâs typescript-tslint-plugin. đ
- Greenkeeper likes to update
package-lock.json
files, but unfortunately itâs difficult to identify errors when builds are successful, but the underlying tools fail in development. đ - NPM Scripts are great for simple tasks, but become very difficult to manage for complex builds (and Gulp ended up being too much of a hassleâŚback to Makefiles). đ
- Rollup is great for building a UMD distribution, but I found that using it to build ESM code from TypeScript code was hassle than it was worth (unless you want everything to be a top-level export). đ
- TypeScriptâs path mapping feature is awesome, but if you compile your code it wonât convert those paths during transpilation. In short, if youâre building a library, you canât rely on that feature and you should just use relative paths for everything in. đ¤Ž
- Inferring types and documentation with react-docgen-typescript might work for simple use-cases. But in reality, it falls over pretty quick. I ended up just building my own PropsTable for documentation.
- Bulma is great 99.9% of the time, but even it has problems â like using non-standard HTML attributes (which React ultimately strips away).
Learnings
The key to building rbx with TypeScript was diving in head-first and learning to swim along the way. At a high-level, TypeScript bills itself as an incrementally-adoptable solution, but in practice it feels like an all-or-nothing language.
As you grow in TypeScript and add knowledge and tools to your toolbelt, youâll be reluctant to pick up too much, too quickly. However most features of the TypeScript toolbox are actually critical â and bugs spring up when you cheat (for example, using any
as a type). Youâll quickly realize that any benefit or gain when using TypeScript is rapidly diminished by untyped or partially untyped code.
In building a publicly-consumable UI Framework, every error, warning, and intermittent issue that I put off ended up coming back to haunt me as I attempted to release rbx. And there were many. Ultimately, these were bugs that needed to be fixed, but it often required significant re-factoring and re-implementation to figure out what was wrong. And occasionaly, use of the great git bisect
.
Having a good community is critical to building a good piece of software, and Iâm especially thankful to the help of the #typescript
community on Reactiflux (especially Jessica â thank you).
Conclusion
The architecture paid off, and Iâm really happy with rbx and in particular itâs core: the ForwardRefAsExotic
. Iâve built a package in a style that ought to be the standard for how UI Frameworks for React are created â minimal, unopinionated, yet exhaustively extendable.
I admit, the success of this package depends on the willingness of users to consume a UI Framework that has a narrow scope â theyâll need to bring their own favorite packages (like Formik) and icons (like Font Awesome). For better or worse, this probably means that Iâll leave some beginners behind and appeal to the more intermediate and advanced crowds. However, I believe this is actually a great UI Framework for beginners too. The examples in the root of the repo show just how simple and powerful this framework really is.
Iâd love for your help in contributions, and I welcome all support. Thanks!