React17系列(2)-新旧jsx转换对比(rc与babel的关系)

456 阅读3分钟

思考这个问题的起因是因为公司项目的需求使用esbuild-loader替换babel-loader后,而我们大家都知道我们在rc中写的jsx或者tsx代码,首先都会经过babel转译(rc16版本与17版本转译后会有所不同,在下面会提及),而这个babel转译的过程,是在哪里发生的,或者说这个babel是在哪里的babel,是webpack中配置的babel还是rc本身源码中内置类似babel功能的函数(猜想)?当通过webpack启动项目,jsx代码的走向是怎么走的?(估计大家会觉得这个问题是我想的太多~~~)

猜想

在使用esbuild-loader替换babel-loader之后,项目的启动和打包速度都会有所提升,所以初步猜想,rc中转译jsx代码是webpack中的babel,但是思前想后总觉得这个结论过于随意,于是想到了另一个办法来验证。验证之前,先要了解babel对于rc新旧版本中jsx转换的不同。rc官网中有提及:

https://zh-hans.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
新旧jsx转换对比

假设源代码如下:

import React from 'react';
function App() {
  return <h1>Hello World</h1>;
}

旧版本中转换如下:

import React from 'react';
function App() {
  return React.createElement('h1', null, 'Hello world');
}

新版本中转换如下:

// 由编译器引入(禁止自己引入!)
var _jsxRuntime = require("react/jsx-runtime");
function App() {
  return /*#__PURE__*/(0, _jsxRuntime.jsx)("h1", {
    children: "Hello World"
  });
}

可以看出,新版本转换后的代码已经不是使用React.createElement构建dom了,所以在rc17中,源代码无需特地引入 React 即可使用 JSX 了!(但仍需引入 React,以便使用 React 提供的 Hook 或其他导出。)

同时在官网中有提及:

目前,旧的转换的默认选项为 {"runtime": "classic"}。如需启用新的转换,你可以使用 {"runtime": "automatic"} 作为 @babel/plugin-transform-react-jsx@babel/preset-react 的选项

于是我们可以在rc中webpack.config.js中找到如下代码:

{
  test: /.(js|mjs|jsx|ts|tsx)$/,
  include: paths.appSrc,
  loader: require.resolve("babel-loader"),
  options: {
    customize: require.resolve(
      "babel-preset-react-app/webpack-overrides"
    ),
    presets: [
       [
         require.resolve("babel-preset-react-app"),
           {
             runtime: hasJsxRuntime ? "automatic" : "classic",
           },
       ],
    ],
   // ....
 }

可以看到是通过hasJsxRuntime这个立即执行函数来控制使用新旧转换的,接下来找到这个函数:

const hasJsxRuntime = (() => {
  if (process.env.DISABLE_NEW_JSX_TRANSFORM === "true") {
    return false;
  }
​
  try {
    require.resolve("react/jsx-runtime");
    return true;
  } catch (e) {
    return false;
  }
})();

验证猜想

既然我们已经找到了开关,就可以开始验证猜想了,如果转换的babel使用的是webpack中配置的babel,那么我们切换新旧版本的开关一定对代码有所影响。

  • 不改变源代码,开启旧版本的转换 -- 猜想: 页面不报错,正常运行
  • 将源代码中引入react部分删除,开启旧版本的转换 -- 猜想: 页面报错 React is not defined
  • 开启新版本的转换 -- 猜想: 页面不报错,正常运行

如何切换新旧版本开关,这里直接简单粗暴,直接控制hasJsxRuntime函数的return true或者false,结果也正如猜想一样,进行到第二步时候,页面报错如下:

rc:jsx.png

进行到第三步,开启新版本的转换,页面不报错,正常运行。

总结

通过以上,可以看出的确是webpack中的babel配置项在作用jsx代码的转换;在rc17版本中,项目启动,首先经过webpack中的babel转译jsx代码,随后将由rc自己处理,最终渲染dom节点到界面,这个处理的过程在后面的文章中会逐步介绍。

babel转译链接:有兴趣可以自己尝试,通过配置左侧options--React Runtime来实现新旧版本的转译

babeljs.io/repl#?brows…