Why doesn't React provide a transpiler?

Viewed 1168

I'm learning about implementing React projects without create-react-app.

In every example I've studied, Babel is used to transpile JSX into JavaScript.

It seems strange to me that the React team would rely entirely on a third-party tool for their framework to function. (I am aware one can utilize the React library by writing "vanilla" JavaScript code, but that is not the common practice.)

There had to be a way to transpile React prior to the React team relying on Babel

Is there no other way to transpile JSX into JavaScript?

Why doesn’t the React team provide a tool to transpile JSX into JavaScript?

2 Answers

I will try to answer both questions. Since I write various code generators, I think my reasoning will be useful.

Why doesn't React provide a transpiler?

I believe that the main reason for this is not even so much that JXS appeared earlier. The main problem with the code now is its standards, which are still not fully supported by browsers. Thus, Babel is needed primarily for interpreting JS and only then JSX. And since he is so needed, why not delegate this responsibility to it.

Is there no other way to transpile JSX into JavaScript?

On the one hand - no, but not all that bad. I think the React team could quickly write their own transpiler. I say this because although there are some difficulties, they are so insignificant that it is not possible or too hard. Of course, these words can be perceived as unsubstantiated, so I chose a middle ground between writing a transpiler here and giving an example of code.

Of course, code transpilation is usually done at the development stage and not at runtime, but I will give exactly the transpilation runtime in the snippet. This is similar to when you wrap your JSX code in a script tag with type="text/babel", and for this also includes the babel file

<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

In order to get rid of goal, you can make another type of script and throw out babel.

Here is an example that I will try to create and I came up with a new type of script "text/react" and everything so that it does not start ahead of time with errors:

<script type="text/react">
  const App = () => {
    const clickHanler = ()=> {
      alert("you clicked me")
    }
    return (
      <button onClick={clickHanler} style={{color:'green'}}>Click me</button>
    );
  };
  ReactDOM.render(<App />, document.getElementById("root"));
</script>

Now you need to go through a few steps.

1 Find this script on the page.

2 Transpile it.

3 Run for execution.

To do this, in another script, I will place several functions, very simplified.

window.addEventListener('DOMContentLoaded', function () {
  return transformScriptTags();
}, false);

We need to catch the moment when we can start transpiling by running transformScriptTags.

function disableScriptTags() {
  window.removeEventListener('DOMContentLoaded', transformScriptTags);
}

We will eventually disable this handler.

The transpiler function will contain:

1 The way to extract tags, in fact, I used recursion for this, but the given code will be without it.

2 Next, we transform the result into an object tree with the structure:

{
  tagName : ''
  children : [
      ...
    ],
  attributes : {}
} 

3 Then we recursively put everything together in also tree like this:

React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello world'
);

4 We replace in the old script and insert it as a new one.

That's all, here's an example:

function transformScriptTags(scriptTags) {
  //https://stackoverflow.com/questions/68607607/why-my-state-is-not-re-rendering-when-my-state-changes-in-this-reducer-in-redux

  const code =  document.querySelector('#code');
  const oldScript =  document.querySelector('script[type="text/react"]');
  const reTag = /<(?:([A-Za-z0-9]*) ([^>]*)>(.*?)<\/\1>|[A-Za-z0-9]* [^>]*\/>)/;
  const unwrap = e => e.match(/^\{.*\}$/) ? e.slice(1, -1) : e;
  const wrapText = e => e.match(/^\{.*\}$/) ? e : `'${e}'`;
  const ObjectToReact = (json)  =>{
    const result = ''
    const {tagName, attributes, children} = json;
    return `React.createElement(
      '${tagName}'${!attributes || Object.entries(attributes).length === 0 
        ? ', null'
        : `, {${Object.entries(attributes).reduce((acc,[key, value]) => acc += `${key}:${unwrap(value)},`, '')}}`}
      ${children ? children.map(e =>  e.match(reTag) ? `,${ObjectToReact(e)}`: `,${wrapText(e)}` ) :''})`
  }
  const allScriptText = oldScript.innerHTML
  const matched = allScriptText.match(reTag)
  const element = {
    tagName : matched[1],
    children : [matched[3]],
    attributes : matched[2].split(' ')
      .reduce((acc,e) => ({...acc, [e.split('=')[0]]:e.split('=')[1]}), {})
  }
  const reactElement =  ObjectToReact(element)

  const newScriptText = allScriptText
    .replace(matched[0], reactElement)
    .replace('<App />', 'App()')

  const newScript = document.createElement('script');
  newScript.innerHTML = newScriptText;
  disableScriptTags();
  document.body.removeChild(oldScript);
  document.body.appendChild(newScript);   
  code.textContent=newScriptText;
}
function disableScriptTags() {
  window.removeEventListener('DOMContentLoaded', transformScriptTags);
}
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin ></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin ></script>

<div id="root"></div>
<pre id="code"></pre>

<script type="text/javascript">
  window.addEventListener('DOMContentLoaded', function () {
    return transformScriptTags();
  }, false);
</script>

<script type="text/react">
  const App = () => {
    const clickHanler = ()=> {
      alert("you clicked me")
    }
    return (
      <button onClick={clickHanler} style={{color:'green'}}>Click me</button>
    );
  };
  ReactDOM.render(<App />, document.getElementById("root"));
</script>

The underlying reason is fairly simple, JSX predates React. JSX was made public along with React, but originally it was used (in a slightly different form) inside Facebook before React ever came along.

As to why the JSX team chose to use Babel, while this is subjective speculation, building a good transpiler is really hard, and at that time Babel was already very common. Adding new functionality by integrating with Babel is considerably less work than building a transpiler from scratch. Likewise, it's less effort for the library consumers: adding a new plugin to your existing setup is easier than integrating with a whole separate toolchain.

From the planning perspective, Facebook has contributed both to Babel's finances and the project itself considerably, reducing worries that the project might simply disappear from underneath them.

Related