At the time I write this series, the latest React release version is v15.6.1
.
Find your favorite directory and clone the React repo:
git clone git@github.com:facebook/react.git
cd react
In order to be consisted with documents, we checkout the branch for v15:
git checkout 15-stable
Why not use
master
? Because master branch is used to develop the latest React code, it's different from what we use in the daily project. Butmaster
branch has many new features and refactors, so you can read it after reading this series.
Let's try to build it:
npm install
npm run build
You may get the error Current node version is not supported for development
, that's because your node version is not supported by React. React only support 4.x || 5.x || 6.x || 7.x
at the time of this series. I use node v8.1.3, so I need to install 7.x:
npm install -g n
n lts
Okay, now run install and build again.
Open build
folder, look through those subfolders, you can find many familiar files, like react-with-addons.min.js
.
That's enough for warming up, now turn to our first mission.
React is a big project, so the first thing we need to do is finding its entry.
Open your favorite editor and drag react
folder into it. I use Sublime Text 3.
For an npm project, we can simply open package.json
and find scripts
:
"scripts": {
"build": "grunt build",
"linc": "git diff --name-only --diff-filter=ACMRTUB `git merge-base HEAD master` | grep '\\.js$' | xargs eslint --",
"lint": "grunt lint",
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
"test": "jest",
"flow": "flow",
"prettier": "node ./scripts/prettier/index.js write"
}
The npm run build
command runs grunt build
, so open Gruntfile.js
and search build
:
The task build
contains many sub tasks, you can search their name to see the detail.
For example, let's search build-modules
:
It calls gulp to do the work. Open gulpfile.js
and search react:modules
:
Go back to Gruntfile.js
and search browserify:basic
, no registerTask
? Search browserify
:
The config of browserify tasks is imported from grunt/config/browserify
and executed by grunt/tasks/browserify
. Open grunt/config/browserify
and search basic
:
Wow! Here is the entry and other configure.
Now you know how to trace the build tasks, here is the list of them:
- delete-build-modules delete all modules generated in last build
- build-modules build all js files
- version-check check whether the version of
react
,react-dom
,react-test-renderer
,src/ReactVersion.js
equals to the version inpackage.json
- browserify:basic build
react.js
- browserify:addons build
react-with-addons.js
- browserify:min build
react.min.js
- browserify:addonsMin build
react-with-addons.min.js
- browserify:dom build
react-dom.js
- browserify:domMin build
react-dom.min.js
- browserify:domServer build
react-dom-server.js
- browserify:domServerMin build
react-dom-server.min.js
- browserify:domFiber build
react-dom-fiber.js
- browserify:domFiberMin build
react-dom-fiber.min.js
- npm-react:release build the npm project of React
- npm-react:pack pack the react npm project into a
.tgz
file - npm-react-dom:release build the npm project of ReactDOM
- npm-react-dom:pack pack the react-dom npm project into a
.tgz
file - npm-react-test:release build the npm project of React test render
- npm-react-test:pack pack the react-test-renderer npm project into a
.tgz
file - compare_size' compare and output the size before and after gzip
There are 3 main projects:
- React: with or without addons
- ReactDOM: including
ReactDOMServer
andReactDOMFiber
- ReactTest: used for testing
This series focuses on React itself, so we ignore the ReactTest
parts. As for fiber, it's the next generation architecture of React and still in progress now, so we will also ignore that part. If you want to learn fiber, read this article after reading this series.
The main targets for this series are React
and ReactDOM
(without fiber).
Let's sum up the architecture of build process.
Is that enough for build process?
No. Let's look at build-modules
again.
Why should React build modules? Why not just set an entry and generate the final JS file? There must be some reasons to do this.
Let's figure it out.
Go back to gulpfile.js
and find the react:modules
task:
Scroll up, you can find the definition of paths.react.src
:
Let's open the first one src/umd/ReactUDMEntry.js
:
Uh, a simple file, nothing strange.
But...wait! Look at that require
:
Remember that in Node.js's document, it says:
Without a leading '/', './', or '../' to indicate a file, the module must either be a core module or is loaded from a node_modules folder.
Obviously, React
is neither a core module nor an npm project
inside node_modules
!
Maybe we can find the generated file after run build-modules
and compare them.
Read the gulp task react:modules
again, it stores the output file in paths.react.lib
, which comes to be build/node_modules/react/lib
. Go to that directory:
Oh man, seems all files are generated to this lib
directory. Open ReactUDMEntry.js
:
It's ./React
now! And yes, there is a React.js
file in the same directory.
We can guess what happens here. The build-modules
task extracts all JS files that match the path.react.src
, modify their require()
part according to some rules and put them in the path.react.lib
directory.
We need to figure out those rules.
Let's try to find the source React.js
file. After some clicks, I find it's in src/isomorphic
directory. Open it:
What's that in the comment?
@providesModule React
Hey, I bet this is the rule we are looking for. Each module has this line in their comments. the task will walk through all JS files, build a map based on this and rewrite the path in require()
.
Read the task again:
It reads all files, passes them through babel, calls stripProvidesModule()
, calls flatten()
and writes them to the destination.
After reading the code, we know stripProvidesModule()
just remove that special comment line and flatten()
flat all files into the same directory.
So the key part is babel(babelOptsReact)
. Search babelOptsReact
:
babelPluginModules
is imported from node_modules/fbjs-scripts/babel-6/rewrite-modules.js
. Open it:
This function does the map. It first checks if the input module is in the map if so returns the corresponding value, otherwise, adds the prefix which is ./
by default.
So what's that map?
Go back to babelOptsReact
, the map is moduleMapReact
:
It combines moduleMapBase
with five addons. The moduleMapBase
contains five modules for compatibility and require(fbjs/module-map)
. Open mode_modules/fbjs/module-map
:
It's a map for all utils inside fbjs
package.
So the moduleMapReact
contains:
- five modules for compatibility
- fbjs utils
- five React addons
No modules marked by @providesModule
?!
Short answer: yes.
I have the same feeling as you that maybe I ignore some function calls or require()
statements. But in the end, I understand what happens here.
All modules are flattened to the same directory, so there is no need to map them, just use the default ./
prefix!
Okay, so you Facebook guys just add those special comments and remove them during building.
Now we know how build-modules
works. But why it exists?
I will leave this as a practice for you, just read another task, and see how simple it is with the files generated by build-modules
.
Is that enough for build process?
Yes.
We have figured out the build architecture and how build-modules
works. In next article, we will see a small demo and try to understand how render()
works.
Choose a task to trace and tell what it does, where are the output files. For example, browserify:addons
.
If there are no build-modules
, how to modify those require()
s and how to do the building?