Creating a React component for your website without create-react-app
create-react-app is an amazing tool for starting a React project, but not every React work you could do benefits from it.
You have a website or application that is server-rendered by a framework like Django, or by a CMS, or maybe it’s a static website; on your website you want to have a portion of a page that displays a dynamic component or widget that users can interact with and you would love to be able to easily leverage the Javascript+React ecosystem to build this widget. And why not, it would be awesome if that was a reusable component that you could show on different pages without hassle.
Of course this looks like the perfect situation for a small, extendable library like React. jQuery still works fine in these situations, but that’s out of scope here.
Part of my work is developing themes for Plone CMS and I found myself in a situation like this multiple times. My first go at solving this problem was to create a brand new app with create-react-app (CRA), ejecting, changing webpack config in order to build the output in a way that was comfortable for me to include in the CMS, finding a good way to integrate the folder that CRA creates inside my CMS resources repo… and doing all of this is not effortless.
It’s not CRA’s fault though, it’s just that it’s designed to do something slightly different: building a complete app from scratch, with a lot of webpack magic hidden under the hood.
Back to our problem. I experimented with different tools like webpack and rollup and went a little more into the details of how CRA was making things easy for us, and I found out that actually the tools and configurations needed in order to build a simple widget are not that many and are very often the same. That’s why configuring CRA to do just what I needed felt like somewhat wasted work. By starting from scratch, on the other hand, I could get to a point where the configuration is simple enough to understand and easy to customize where needed.
The simple boilerplate
I prepared a repository with a few boilerplate examples. You can find it here:
There is a folder for each boilerplate example inside the repo. Let’s start with the simple one, which contains all of the required elements to make it work and to configure the output as needed.
The src
folder is where the React code lives. You can nest as many folders with as many widgets you want in here. The important thing is that you have an entrypoint for each separate widget that calls ReactDOM.render
to render its root element, like this:
import React from 'react';
import ReactDOM from 'react-dom';
import MyRootComponent from './MyRootComponent';ReactDOM.render(
<MyRootComponent />,
document.getElementById('root')
);
The webpack.config.js
file is where you can tweak or expand most of the configuration in order to add more functionality, other build tools etc. Every part has comments in the repo with explanations. Let’s see the most important ones here.
entry
: this is where you tell webpack all the different entrypoints you have, this means all the separate scripts you want to build for your sites.
entry: {
widget: path.resolve(__dirname, './src/widget.jsx'),
},
Every entry has to be a path to the root source file I described above, which contains the ReactDOM.render
call.
devServer
: webpack-dev-server is the tool that watches for your file changes and builds them on the fly, while also serving them directly in order to make the development experience nice and easy. And you know it already if you used CRA or any other similar tools.
While the default behaviour of webpack-dev-server is to keep the built files in memory and serve them from there, this is a limitation in our situation. For example, I usually want my CMS to serve these files directly, so I can see them working together with the CMS content seamlessly.
devServer: {
devMiddleware: {
writeToDisk: true,
},
},
This is where we tell webpack-dev-server to also write built files on disk when in development mode. Since the default output name is [name].js
, where name is the name of the entrypoint that we set above, it will generate a file called widget.js
in my example. We will now need to serve this file as a simple js file (plus a css file if you added styles) in our site. It will be ready with Hot Module Replacement even if not served directly by webpack-dev-server and will grant a good developer experience.
externals
: this is where you can tell webpack to exclude libraries from the bundle it generates. This is especially useful to avoid bundling libraries that would be therefore added multiple times in a page, if you add more than one root component or widget built this way.
externals: {
react: 'React',
'react-dom': 'ReactDOM',
}
In other words: if you create more than one entrypoint in this repo, you are creating separated components that work independently. You can add more than one on the same web page, but if you don’t add React and ReactDOM in the externals section, you will end up having more than one copy of React loaded in your page.
If you do this, on the other hand, you will have to add these two libraries to the page separately. You can easily copy and paste their unpkg links from the React documentation.
The last thing we need to do is adding our built script to the page after the two React libraries, and adding an empty HTML element that will be used for rendering the app (a simple <div id="root"></div>
is good for the example above).
The complete boilerplate
In the repo linked above there is also a complete example, which works in the same way but also adds several more configurations that are not needed but I find extremely useful. That one is in fact almost identical to how I configure the real projects I do.
Compared to the simple one, it adds:
- eslint + prettier + stylelint
- postcss with autoprefixer + cssnano
- sass + css modules
- dotenv
.npmignore
file for npm publishing
In order to gain the most benefits from this configuration, you need to have your editor set up with plugins for eslint, prettier and stylelint, at least.
Ok, so what do I do now?
Feel free to download the example in the repo and fiddle with them freely, maybe they can be helpful for your next project, who knows :)
If you feel like doing so, use them for one of your projects and let me know if they helped. If you have any questions, leave a comment here or open an issue on Github!