美文网首页
babel.js(二):babel-standalone.js加

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

作者: 青叶小小 | 来源:发表于2021-03-07 21:14 被阅读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);
}

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

  • 获取<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:
    1. 检查<script>标签中是否含有关键字async(异步加载,之后在被允许时执行);
    2. 检查<script>标签中是否含有指定的data-plugins属性;
    3. 检查<script>标签中是否含有指定的data-presets属性;
  • 其次,判断script.src:
    1. 如果存在,则为外链,需要下载(XHR完成,具体看load方法)、加载,调用check方法;
    2. 如果不存在,则获取<script>标签中的innerHTML,构造新对象,存在result数组中;
  • 最后调用 check 方法;
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:

  • 将需要加载还未执行的,设置为已执行(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);
transformFn.png transform.png
  • 将转换后的结果赋值给script.text(outerHTML 等内容是一样的);
  • 最后,将新的<script>追加至<head>末尾;
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分析。

相关文章

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

    一、babel.js(一):babel-standalone.js(单机模式)[https://www.jians...

  • babel.js(一):babel-standalone.js(

    一、babel-standalone.js是啥? bable-standalone.js是为非NodeJS环境而生...

  • react学习(2)

    知识点 1:babel.js的作用:ES6=>ES5,jsx=>js2:小案例

  • babel.js的使用

    近期,项目经理的一些需求,打包APP放在电视上,电视的安卓版本不支持大量es6的写法,我也很无奈…………考虑买个杯...

  • Babel

    什么是 Babel Babel又名 babel.js, 是目前前端使用非常广泛的编辑器、转移器。 比如当下很多浏览...

  • 一个es6兼容性写法的问题

    一个解构语法兼容问题:在360(9.1.0) 上的版本里 在对象里解构对象,有兼容问题,用babel.js也不好使...

  • 二加六加七

    今天去银行办业务,网络上的银行app很方便,但是我还是喜欢去银行办业务,总觉得更踏实放心些。 今天上午去银行,十点...

  • 二加二之家

    曾经我写过我们仨,那年她三岁。六年过去了,如今我们又多了个虚三岁,我们四口之家常常既有欢声笑语,又有哭叫打闹,实在...

  • 二加花事

    二加是我的妹妹,自称是一个爱花的人。小时候,不管是从田野里还是从邻居家淘到的小花小草都当宝贝一样带回家小...

  • 四十加二

    LULUPARK的奇幻旅程 有时候 人需要享受极致的孤单 无论秋雨流转 还是枯叶飞散 只有在单枪匹马的时刻 才能重...

网友评论

      本文标题:babel.js(二):babel-standalone.js加

      本文链接:https://www.haomeiwen.com/subject/jsleqltx.html