In my last post, I discussed some of the biggest lessons I learned while building my first component in ReactJS. One of those lessons revolved around Browserify, and how it was the best way to leverage component reuse.
As I moved closer to production, I realized there was one particularly nasty side effect to Browserify. Take the following code for example:
var React = require("react");
var ReactDOM = require("react-dom");
// Custom React Component
var RecipeTabs = require("components/RecipeTabs.js");
function initPage()
{
ReactDOM.render(
React.createElement(RecipeTabs),
document.getElementById('recipeTabs')
);
}
Browserify lets us use CommonJS require
syntax even though browsers don’t natively support it. One way it does this is by inlining the entire contents of your require
’d JavaScript into your script. So, in the example above, our 13-line script will suddenly grow to include all of React, all of ReactDOM and all of the code for RecipeTabs.
If we blindly require React in this manner inside every JavaScript file on our site, then we end up with a bunch of duplicate inlined code. Even worse, we are forcing users to download the same code over and over. The browser only sees unique file names and sizes — it isn’t smart enough to realize that inside those files are large chunks of repeated code. What we really want is one file that contains all of our common code, and reuse that file on every page. That way, the browser only downloads the code once; every other page load uses a cached copy.
Thankfully, Browserify natively provides a solution to this issue.
The trick is to create a single file that contains all JavaScript require
s used site-wide. This single bundle can then be included on all of your pages.
Browserify gives us an easy way to do this. For example, using gulp:
gulp.task('browserify-global', function() {
return browserify()
.require(['react','react-dom'])
.transform(envify)
.bundle()
.pipe(source('exports.js'))
.pipe(gulp.dest('./js'));
});
This creates an exports.js
file which is a bundle of React and ReactDOM. We can now include this on every page of our site — the browser will download it once and then cache it for every subsequent page.
But we still have a problem. Browserify doesn’t know about exports.js
when it processes the rest of the site’s JavaScript. It will go ahead and inline React and ReactDOM as usual wherever it’s require
’d.
The second piece to make this work is to tell Browserify to not inline certain require
’d libraries:
gulp.task('browserify-custom', function() {
return glob('./src/**/*.js', function(err, files) {
if(!err) {
var tasks = files.map(function(entry) {
return browserify({ entries: [entry] })
.external(['react','react-dom'])
.bundle()
.pipe(source(entry))
.pipe(gulp.dest('./js'));
});
}
});
});
Now, whenever Browserify encounters a require
for ‘react’ or ‘react-dom’, it won’t inline the script. But, as long as we include the exports.js
generated in the previous step, the reference will resolve, and it will be able to execute any React or ReactDOM code.
This isn’t limited to third-party JavaScript libraries either. If you have your own code that is referenced across the entire site, then you can include it:
.require(['react','react-dom', {file: './src/pn.js', expose: 'pn'}])
The expose
property specifies the name to use in your require
statements. In this case, whenever I need to reference code in pn.js
, I can simply require(‘pn’)
.
In the second step, we can now specify pn
as an external library:
.external(['react','react-dom','pn'])