Create a gist now

Instantly share code, notes, and snippets.

@thomasboyt /react.md
Last active Sep 15, 2016

What would you like to do?
ReactJS's interesting build step

React does a really interesting thing with its build process that I haven't seen elsewhere.

React uses what appears to be CommonJS syntax in its files. For example, in ReactDOM.js:

var ReactDescriptor = require('ReactDescriptor');
var ReactDescriptorValidator = require('ReactDescriptorValidator');
var ReactLegacyDescriptor = require('ReactLegacyDescriptor');
var ReactDOMComponent = require('ReactDOMComponent');

However, in CommonJS, a non-relative import (that is, one that doesn't start with ./) is supposed to indicate a package, not a module within the same package. For example, if I do require('react') in a traditional Browserify project, it'll look for a module in node_modules/react.

None of those imported modules are in separate packages, but are within the React repo. Not only that, but they're not even in the same folder - this file is in src/browser/ReactDOM.js, but ReactDescriptor is within src/core/ReactDescriptor.js.

I knew that React eventually used Browserify to build its global bundle, so I dug in a bit further and found that before that, the jsx Grunt task builds each file to JSX and moves it to build/modules. And when I say moved, I mean it ends up as a totally flat structure - src/browser/ReactDOM.js is built to build/modules/ReactDOM.js, and src/core/ReactDescriptor.js is built to build/modules/ReactDOM.js. The JSX task also adds a prepending ./ to imports to make them relative.

At this point, the React build process creates a Browserify bundle from build/modules. However, when published to NPM, build/modules actually becomes the lib/ folder:

I can think of a few advantages to having this "flat" structure:

  • Folder organization is independent of your the module system's structure, so you can move files around all you want without breaking anything
  • In CommonJS, you avoid having complex relational lookups (e.g. no require('../../../../core/ReactDescriptor.js'))

I wonder whether the added complexity in the build step is because of those advantages, or whether Facebook has an internal build tool that uses this flat structure? I imagine it's the latter, since while the advantages are nice, they don't seem nice enough to add this extra complexity. Leaving the ./ off of each import path is also a sign that React wasn't originally written with "traditional" CJS in mind.

@insin
insin commented Oct 2, 2014

I noticed this and used it for a project just to see what it feels like. You've already called out the stuff I found useful in practice.
It didn't turn out to be that complex to implement - just calls to gulp-flatten in all tasks which move files from the src directory and this to hook up the flat requires:

  var b = browserify('./build/modules/app.js')
  glob.sync('./build/modules/*.js').forEach(function(module) {
    b.require(module, {expose: module.split('/').pop().split('.').shift()})
  })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment