babel.js(二):babel-standalone.js加

2021-03-07  本文已影响0人  青叶小小

一、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(调用栈):

call-stack.png

没有一大堆的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);
}

该方法可以理解为三段功能:

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();
}

该函数分两块:

先聊聊第2点:

load-scripts.png

聊完第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:

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);
}

该方法做了三件事:

transformFn.png transform.png image.png

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

console.png

我们看到,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出来的结果如下:

runs.png

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

上一篇 下一篇

猜你喜欢

热点阅读