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:
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:
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.
Addendum: Jonathan Buchanan replied to the original version of this post. He used the same strategy for a personal project, by combining gulp-flatten and this code snippet:
The expose
option exposes each of the modules in the flattened folder as a non-prefixed module (allowing you to require('Foo')
instead of require('./Foo')
).