You might not need a build toolchain
Since the introduction of ES6, the front-end development space has seen an explosion of build tools and task runners. From Grunt, to Gulp, to Webpack, to Parcel, to Rollup…the list goes on. Build toolchains have gained popularity because they can be configured to compile/transpile code, optimise assets, and maximise the cross-browser compatibility of your website.
However, do you realise that you can build a website using React WITHOUT a build toolchain? I know, right!
Today, you can build a website and run it cross-browser (including legacy browsers) without the need for an additional build step.
This post looks at how to do this in practice, and the pros and cons of a build toolchain-less approach.
What I am not using
I am not using the following tools;
- Webpack or any of the 30 trillion plugins or loaders.
- Babel.
- SCSS. CSS is actually pretty good these days.
- Gatsby or another other static site builder
What I am using
I am using the following;
- React, ReactDOM from a CDN
- A http-server, conveniently called `http-server`
- Up to date Google Chrome
- Traceur, for maximum legacy browser support
“Hello, World!”
We have to start with the obligatory “Hello, World!” example, as required by the industry as a whole.
I start by creating an empty folder, and then an index.html
file with the following content;
I added the development builds of React, which are unoptimised, to help debug any errors we might encounter as we go along.
Now we have React on the page, let’s define a component and render it.
Note we have to write pure React code here because writing JSX requires a build step, which we do not have.
In the interest of keeping things simple, I have added the above script block directly underneath our <div id="root"></div>
element. We will tidy this up shortly.
Now as I have http-server
installed globally (you can run npm i -g http-server
in your terminal to do this) I simply run the following command to start my server;
http-server -o -p 4455
This starts a new instance of the web server, on port `4455` and automatically opens the browser.

And there we have it, our React application running in the browser with no prior build step.
How far can we take this? Let’s find out.
ES6 JavaScript modules
Google Chrome has fantastic, albeit little utilised, support for ES6 modules. Let’s take a minute to tidy up our code.
I now add a new file called index.js
and move our ReactDOM render logic to it.
Then move our App
functional component to a new file called app.js
.
I update my app.js
file as follows;
Here is what our project now looks like;

Return to the browser, and refresh. You will probably see a blank screen and a console error

Uncaught SyntaxError: Unexpected identifier
The error states that Chrome has encountered language that it does not understand. But hey, wait, I just said that Chrome has fantastic support for ES6 modules!? We have to make a small change to turn this functionality on.
Head back to index.html
and update the script
tag as follows;
<script type="module" src="index.js"></script>
The type=module
attribute tells Chrome that this script is an ES6 module.
Refresh the page, and everything should be working again.
Going a little further
We have a taste for where this is going now, so how about we add a little interactivity to the page?
Let’s build a simple counter with an increment and decrement button. This requires us to work with local state and requires us to attach events to the DOM to respond to user input.

Define a class
called Counter
, as follows;
The above code should be very familiar, as I have written it in the exact same way that I would were I using a build toolchain. As Google Chrome has almost full support for ES6 language features such as class
, destructuring, and arrow functions, we do not have to write our code in any particularly special way to get things working.
Note: There is a new language feature that is currently Stage-3 in the TC39 release process (so we can expect widespread support and adoption soon) called Class field declarations for JavaScript which will enable us to re-write this in a more succinct way. For now, though, we are stuck with having to bind
our function context and define our state
in the constructor
.
Styling
Nothing ground-breaking here. CSS has been a part of the web browser basically since the beginning of time, so we have excellent browser support across the board.
We will not go into great detail here, but people typically use SCSS because;
- SCSS has compile time variables
- SCSS allows nesting
- SCSS supports functions/mixins
Well I say;
- CSS has runtime variables which can be updated dynamically (waaaay better)
- Nesting is awful, you probably shouldn’t be nesting (most of the time!)
- CSS has many built in functions, but I admit it, you cannot create your own (yet!)
You can add styling directly in the header using a <style>
tag, or reference an external stylesheet using <link>
tag.
What about legacy browsers like Internet Explorer though?
Internet Explorer? Never heard of it.
Ugh, ok then.
IE is a terrible browser and has terrible support for everything, especially ES6. Microsoft are doing their best to kill IE off altogether, but many people & companies will stick with it to the bitter end. According to w3counter, IE11 currently has about 3.5% of the global market share, so too much to ignore at this point.
Support for IE 11 can be achieved using Traceur. Traceur is an on-the-fly transpiler that enables you to run ES6 code in browsers that do not support it.
To maximise cross browser compatibility, you can add the Traceur CDN scripts to your header, as follows;
Now when you open your page in IE11, the application should still be working as it was in Google Chrome.

So honestly, do I need a build toolchain?
You definitely can build a website today without a build toolchain. Evergreen browsers have excellent support for ES6+ and support is getting better all the time. With an in-browser compiler like Traceur, you can vastly increase cross browser support and get things working better in legacy browsers.
Pro: Your code will be waaaaay smaller, and probably a lot more performant. When using an ES6+ to ES5 compiler (like Babel or TypeScript), polyfills are automatically injected into your bundles to ensure cross browser support. Polyfills are often heavy and take time to be executed by the browser. The polyfills are used regardless of whether the browser supports the feature you are using natively, or not. When not using a build toolchain, the only code that runs in your browser is code you write, and your library code (React).
Con: You cannot use JSX. This is massive. JSX greatly simplifies our code and makes it a lot easier to read and understand. Not being able to use JSX natively in the browser is a huge loss.
Kinda-a-con-kinda-a-pro: You cannot bundle and minify your code. You leave it up to the browser to decide when to download your JavaScript files. The browser will make a new HTTP request for each JavaScript module in your application, because they are not bundled together into one big file. This is both a pro and a con. The old version of the HTTP protocol, version 1.1, supported a maximum 7 simultaneous connections with the web server, meaning that once all 7 connections were open, the browser would have to wait for a request to resolve before continuing. When loading many resources at once this caused render blocking and contributed to web pages loading more slowly. However, version 2 of HTTP (which has good support), has no limit on the number of simultaneous connections, meaning this limitation should be removed. The jury is out as to whether the old technique of bundling your assets is faster or not compared to downloading many smaller fragment files.
Summary
Thanks to advancements in browser technology and the standardisation of the JavaScript standard (ECMAScript), it is possible and feasible to build a React application today without a build toolchain. When not using a build toolchain, you can avoid shipping code to the browser that is not necessary to run your application. Cross browser support can be vastly improved by using an on-the-fly compiler Traceur, which will convert ES6 to ES5 at runtime.
Finally
Realistically, most of the time you probably will want to use a build toolchain, because you will want to take advantage of the latest language features that not every evergreen browser will have support for.
The code for this project can be found on GitHub.
Get In Touch
Did you enjoy this story or want to discuss it further with me? Why not send me a message on Twitter!
Or check out my personal blog
Thank You!