I have been learning and reading vue source code for some time. Recently, I have been trying to learn React. Since react is not used in the current project, I don’t want to learn its API all at once (it will still be forgotten if I don’t use it for a long time), so I plan to change the learning method this time. Understand the process of parsing and the idea of the whole framework.
For each point of learning and in-depth, will be in the form of the output of the article, mainly for the learning of the content of the record (so it seems that the content is a little too much), convenient for myself to refer to and review later.
Starting from the demo
Before this, there were several tests on the verge of getting started with React, and each time they ended up writing a simple demo. I’m sure the following one will be familiar, and this article will start from there.
npx create-react-app my-app
cd my-app
npm install
npm start
Copy the code
Then you should be able to run (if the environment and installation are all right), simplify the code, and ponder the code that follows, which you’ve seen and written many times before.
The code is as follows:
import React from 'react';
function App() {
return (
<div>
<h1>good good study, day day up</h1>
</div>
);
}
export default App;
Copy the code
At first glance, the APP returns something that looks a lot like HTML, which I’m sure many of you know is JSX syntax. So here’s the question:
JSX syntax writing template, how to generate the real DOM?
By analogy, let’s look at the implementation of template-ast-code-vnode-dom in Vue.
Take a quick look at Vue’s parse process
Template to AST and ast to code
- Vue source code compiler ideas parse those things
- Vue source parse
Here is a simple example to illustrate the process of parse
The template is as follows:
<template>
<div>
<h1>good good study, day day up</h1>
</div>
</template>
Copy the code
The generated ast is as follows:
// The simplified version is mainly to look at the structure
{
/ /...
parent: undefined.children: [{parent: {
/ /...
tag: "div".type: 1
},
children: [
// ...
text: "good good study, day day up".type: 3].tag: "h1".type: 1}].tag: "div".type: 1
}
Copy the code
The generated code is as follows:
with(this) {return _c(
'div',
[
_c(
'h1',
[_v("good good study, day day up")]])}Copy the code
The resulting render function looks like this.
Let’s look at the React implementation. If you look directly at the main.chunk.js file after NPM star, you can see the following code (simplified version) :
function App() {
return createElement(
"div",
{
__source: {
fileName: _jsxFileName,
lineNumber: 5
},
__self: this
},
createElement(
"h1",
{
__source: {
fileName: _jsxFileName,
lineNumber: 6
},
__self: this
},
"good good study, day day up")); }Copy the code
Compare the code generated by Vue, and you’ll find that it looks very similar, so here’s a summary:
React converts the JSX template we wrote into the corresponding function through a layer of transformation.
So this is the end? Now, how does JSX translate?
JSX conversion process
If you know the Vue parse process, conversion takes place at compile time: compileToFunctions(which are basically templates to render functions) are executed at the first $mount.
For React and ReactDOM, I can’t find any conversion code. And as you can see from the main.chunk.js code, the JSX we wrote has been converted to the corresponding function. So before that, the transformation is done.
Anyway, we’re using the Babel parser (what is Babel and what does Babel do), so let’s first find the configuration in the project.
Find the entry to the Babel configuration
Since I am not very familiar with the engineering configuration and engineering, I have searched here for a long time. If you want to find the entrance of Babel configuration, you need to execute it first (it is better to find a demo project to execute it, this command is irreversible).
yarn eject
Copy the code
Find/config/webpack. Config. Js, the code is as follows:
module: {{test: /\.(js|mjs|jsx|ts|tsx)$/.include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [[require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack? -svgo,+ref! [path]',},},},],cacheDirectory: true.cacheCompression: isEnvProduction,
compact: isEnvProduction,
},
},
{
test: /\.(js|mjs)$/.exclude: /@babel(? / : \ | \ \} {1, 2) the runtime /.loader: require.resolve('babel-loader'),
options: {
babelrc: false.configFile: false.compact: false.presets: [[require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true},]].cacheDirectory: true.cacheCompression: isEnvProduction,
sourceMaps: false,}}Copy the code
I have tried the source code of each parser, but there are still many difficulties (various references). Here is also a different way to learn the process of parsing.
Try writing a JSX plug-in by hand.
Handwriting JSX plug-in
If you do a web search here, you’ll probably find a bunch of code for the Babel plugin, and I found a basic example here.
An example of a plug-in for Console
Here is the code for a plug-in that processes logs as console.log:
const babel = require('@babel/core')
const t = require('babel-types') const code = 'const a = 3 * 103.5 * 0.8;log(a);
const b = a + 105 - 12;
log(b); 'const visitor = {CallExpression(path) {// Here to check if notlogFunction execution statements are not processedif(path.node.callee.name ! = ='log') return// T kalxpression and T emberExpression stand for generation oftypePath. replaceWith denotes to replace the node. Here we only change the value of the first parameter of CallExpression, the second parameter uses its own original content, ReplaceWith (T. allExpression(T. emberExpression(T. identifier(t.'console'), t.identifier('log')),
path.node.arguments
))
}
}
const result = babel.transform(code, {
plugins: [{
visitor: visitor
}]
})
console.log(result.code)
Copy the code
Treatment results:
Const a = 3 * 103.5 * 0.8; console.log(a); const b = a + 105 - 12; console.log(b);Copy the code
After looking at the code, you should have a pretty good idea of how to write the plug-in, which looks like this: Code is first parsed into the AST, and then traverses the entire AST tree. Each node has its own specific properties. Handlers for the plug-in’s ViStor object are called during parsing. All the plug-in has to do is make changes to the resolution results where appropriate (here it is CallExpression), and where it is qualified (here it is path.node.callee.name === ‘log’). Once you know how it works, try writing a plug-in for JSX parsing.
Follow the example:
const code = `
var html = <div>
<h1>good good study, day day up</h1>
</div>
`
const visitor = {
}
const result = babel.transform(code, {
plugins: [
{
visitor: visitor
}
]
})
console.log(result.code)
Copy the code
The general structure is as follows. The desired target code corresponds to the following output:
var html = React.createElement(
"div",
null,
React.createElement("h1", null, "good good study, day day up"))Copy the code
Plugin-syntax-jsx: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax: syntax
Introducing the plug-in, the modified code is as follows:
babel.transform(code, {
plugins: [
'@babel/plugin-syntax-jsx',
{
visitor: visitor
}
]
})
Copy the code
The results are as follows:
var html = <div>
<h1>good good study, day day up</h1>
</div>;
Copy the code
Here you can see that we can recognize the JSX template normally, but the output is not what we need, we need to convert it to our function. The next step is to find the right time.
Look for opportunities
Here we just know that we can recognize it normally, but in the process of parsing, what exactly does the corresponding AST look like?
Here is also a recommended site, astexplorer.net/
Here you can see the structure of the entire AST tree (I haven’t looked at the process of parseHTML in Vue, but I’ll spend a little more time looking at the process of BABal generating AST). We should soon find the key information we want — JSXElement. Compare AST and key information above, for this example, let’s think about ‘appropriate timing’ — JSXElement variable assignment:
- VariableDeclarator
init.type === 'JSXElement'
Add ‘timing’ code
Add ‘timing’ and the code looks like this:
const babel = require('@babel/core')
const code = ` var html =
good good study, day day up
`
const visitor = {
VariableDeclarator(path) {
if (path.node.init.type === 'JSXElement') {console.log('start')
// deal }}}const result = babel.transform(code, {
plugins: [
'@babel/plugin-syntax-jsx',
{
visitor: visitor
}
]
})
console.log(result.code)
Copy the code
The results are as follows:
start
var html = <div>
<h1>good good study, day day up</h1>
</div>;
Copy the code
Of course, this is only input tag information, there is a lot of other node information, other information is JSX syntax rules, such as loops, classes, conditional statements, logic code and so on. This article only does the simple implementation. The next thing to do is to integrate the node information and generate the corresponding function code.
The generated code
. To be continued
(This involves the use of babel-types. Since I am not familiar with this block, THIS article will be updated here.)
conclusion
Now that we know about JSX’s parse process, how does it differ from vue’s parse process?
- Vue generates the corresponding rendering functions during the compile phase. React generates the corresponding functions during the Babel parsing phase
- Those of you who have seen the vue parse phase of the source code should know that Vue does a lot of things to deal with the browser’s “weird” behavior (to keep it consistent with the browser’s behavior).
canBeLeftOpenTag
Tags such as: p, will complete the closing tag, that is, you can write it like normal HTMLtemplate
. React JSX has many syntax rules, such asclass
He has to write aclassName
, the space after the line break before the tag is ignored, etc. (I am still learning JSX syntax, and will continue to improve this distinction later).
As for the second difference, it can be seen that, if the original HTML project wants to migrate to VUE or React, the cost of migrating to VUE is much lower. Vue not only maintains the unification with HTML specification in writing method, but also in handling browser special behavior. If react is to be migrated, the transformation cost may be high.
The above is just a beginner’s subjective view of React. More features and pros and cons need in-depth study.