一、babel.js(一):babel-standalone.js(单机模式)
二、babel.js(二):babel-standalone.js加载分析
一、引入
<html>
<body>
<script src="../../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<div id="container"></div> <!-- 在浏览器上运行后,会显示“Hello, chris!” -->
<script type="text/babel">
ReactDOM.render(
<div id='app'>Hello, chris!</div>,
document.getElementById('container')
);
</script>
</body>
</html>
二、执行
(以下代码分析将会以Github源码 babel-standalone 与编译过后的代码结合着阅读)
浏览器加载HTML主文档,当完全下载 & 加载 babel-standalone.js (webpack打包,UMD库)后,就开始执行。
先来看看,执行时,浏览器的Call Stack(调用栈):

没有一大堆的babel方法调用,前后两个匿名函数,然后就调用 React.createElementWithValidation (dev下只是检查,最终调用 React.createElement,这里不展开,React源码会分析讲解)。
2.1 第一个anonymous函数
// Listen for load event if we're in a browser and then kick off finding and
// running of scripts with "text/babel" type.
if (typeof window !== 'undefined' && window && window.addEventListener) {
// 源码如下
// window.addEventListener('DOMContentLoaded', () => transformScriptTags(), false);
// 下面的代码是编译过后的代码
window.addEventListener('DOMContentLoaded', function () {
return transformScriptTags();
}, false);
}
代码版本很简单,因为是采用UMD库,babel-standalone加载后会立即执行(我们通过代码所在行数也能知道,是 webpack[0]号代码块),前面是一大堆的初始化Babel plugins & prests,以及一些方法(loadBuiltin、processOptions、transform、transformFromAst),接下来就是上面这段代码:监听DOM内容被完全加载!
大部分人知晓 load ,但其闺蜜方法 DOMContentLoader 可能有些人不知道,load 与 DOMContentLoader 区别:
- load:
MDN的解释:load 应该仅用于检测一个完全加载的页面 当一个资源及其依赖资源已完成加载时,将触发load事件【意思是页面的html、css、js、图片等资源都已经加载完之后才会触发 load 事件】- DOMContentLoader:
MDN的解释:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载【意思是HTML下载、解析完毕之后就触发】
2.2 transformScriptTags函数
/**
* Transform <script> tags with "text/babel" type.
* @param {Array} scriptTags specify script tags to transform, transform all in the <head> if not given
*/
function transformScriptTags(scriptTags) {
// 源码 runScripts(transform, scriptTags);
// 下面的代码是编译过后的代码
(0, _transformScriptTags.runScripts)(transform, scriptTags);
}
这个方法就在2.1匿名函数的下面,这段代码直接调用 runScripts 方法:第一个参数是 transform 函数,第二个参数的值是 'undefined';
2.3 runScripts函数
/**
* Run script tags with type="text/jsx".
* @param {Array} scriptTags specify script tags to run, run all in the <head> if not given
*/
export function runScripts(transformFn, scripts) {
headEl = document.getElementsByTagName('head')[0];
if (!scripts) {
scripts = document.getElementsByTagName('script');
}
// Array.prototype.slice cannot be used on NodeList on IE8
const jsxScripts = [];
for (let i = 0; i < scripts.length; i++) {
const script = scripts.item(i);
// Support the old type="text/jsx;harmony=true"
const type = script.type.split(';')[0];
// scriptTypes = ["text/jsx","text/babel"]
if (scriptTypes.indexOf(type) !== -1) {
jsxScripts.push(script);
}
}
if (jsxScripts.length === 0) {
return;
}
loadScripts(transformFn, jsxScripts);
}
该方法可以理解为三段功能:
- 获取<head>标签里的子元素(<link>、<script>);如果为空则获取所有<script>形成一个数组;
- 遍历scripts数组,找出所有type为jsx或babel的<script>并放入新数组jsxScripts;(jsx块是老的语法,不建议使用)
- 如果jsxScripts大于0则调用 loadScripts方法;
2.4 loadScripts函数
/**
* Loop over provided script tags and get the content, via innerHTML if an
* inline script, or by using XHR. Transforms are applied if needed. The scripts
* are executed in the order they are found on the page.
*/
function loadScripts(transformFn, scripts) {
const result = [];
const count = scripts.length;
// 函数内部方法,loadScripts最后一行会调用,并进入到run方法中
function check() {
var script, i;
for (i = 0; i < count; i++) {
script = result[i];
if (script.loaded && !script.executed) {
script.executed = true;
run(transformFn, script);
} else if (!script.loaded && !script.error && !script.async) {
break;
}
}
}
scripts.forEach((script, i) => {
const scriptData = {
// script.async is always true for non-JavaScript script tags
async: script.hasAttribute('async'),
error: false,
executed: false,
plugins: getPluginsOrPresetsFromScript(script, 'data-plugins'),
presets: getPluginsOrPresetsFromScript(script, 'data-presets'),
};
if (script.src) {
result[i] = {
...scriptData,
content: null,
loaded: false,
url: script.src,
};
load(
script.src,
content => {
result[i].loaded = true;
result[i].content = content;
check();
},
() => {
result[i].error = true;
check();
}
);
} else {
result[i] = {
...scriptData,
content: script.innerHTML,
loaded: true,
url: null,
};
}
});
check();
}
该函数分两块:
- 内部方法check,loadScripts最后一行会调用;
- scripts.forEach 遍历;
先聊聊第2点:
- 首先,构造scriptData:
- 检查<script>标签中是否含有关键字async(异步加载,之后在被允许时执行);
- 检查<script>标签中是否含有指定的data-plugins属性;
- 检查<script>标签中是否含有指定的data-presets属性;
- 其次,判断script.src:
- 如果存在,则为外链,需要下载(XHR完成,具体看load方法)、加载,调用check方法;
- 如果不存在,则获取<script>标签中的innerHTML,构造新对象,存在result数组中;
- 最后调用 check 方法;

聊完第2点,我们再来看看 check 做了什么事:
function check() {
var script, i;
// count = scripts.length
for (i = 0; i < count; i++) {
script = result[i];
if (script.loaded && !script.executed) {
script.executed = true;
run(transformFn, script);
} else if (!script.loaded && !script.error && !script.async) {
break;
}
}
}
遍历result:
- 将需要加载还未执行的,设置为已执行(script.executed = true),并调用run方法执行;
- 其它将跳过;
2.5 run函数
/**
* Appends a script element at the end of the <head> with the content of code,
* after transforming it.
*/
function run(transformFn, script) {
const scriptEl = document.createElement('script');
scriptEl.text = transformCode(transformFn, script);
headEl.appendChild(scriptEl);
}
该方法做了三件事:
- 构造新的<script>标签块;
- 调用transformCode翻译script.content(即innerHTML);


- 将转换后的结果赋值给script.text(outerHTML 等内容是一样的);
- 最后,将新的<script>追加至<head>末尾;

我们可以在控制台直接console,打印结果:

我们看到,ReactDOM.render第一个参数被转成了React.createElement,之后,会触发调用React.createElement
至此,babel-standalone.js的整个执行过程,分析完毕!
我们可以看到,babel目前直接将JSX语法的HTML代码转成了React的element,所以,再未使用React而使用到了JSX语法时,IDE会提示 import React from 'react';
三、写在总后
如果我们有多级JSX语法的HTML嵌套呢?
<html>
<body>
<script src="../../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<div id="container"></div> <!-- 在浏览器上运行后,会显示“Hello, chris!” -->
<script type="text/babel">
ReactDOM.render(
<div id='app'>
Hello, chris!
<h1>
title
</h1>
</div>,
document.getElementById('container')
);
</script>
</body>
</html>
那么,babel会transform出来的结果如下:

div 和 h1 两个标签,各自被转成了 ReactElement (调用 React.createElement 函数),之后的分析,详看 React源码学习之React.createElement分析。
网友评论