Babel Standalone works on dynamically appended JSX code when Babel Standalone is provided as SRC attribute of SCRIPT tag. It does not work when it is appended to the DOM using JavaScript. This isn't true for React libraries, which work fine with dynamically appended JSX when the React libraries are appended dynamically.
First, here is the JSX file (react.jsx):
class HelloMessage extends React.Component {
render() {
return <h1>Hello</h1>;
}
}
ReactDOM.render(<HelloMessage/>, document.getElementById('app'));
Next, here is the HTML that makes use of it. Notice that the React libraries and the JSX file are being added dynamically; this works correctly. However, Babel Standalone only transpiles the JSX when Babel Standalone is added as the SRC of a SCRIPT element, not when it is appended using the same JavaScript that appends the rest of the JS/JSX. Toggle between the two states by remming out one Babel Standalone invocation and un-remming the other. I want to append the Babel Standalone dynamically...what is the problem?
<!doctype html>
<html>
<head>
<title>Inscrutable Babel Problem</title>
</head>
<body>
<div>
<p>Babel works only when added as a src of a SCRIPT element; it fails when the script is appended dynamically. HELLO will display below when it works.</p>
</div>
<div id=app></div>
<script>
function loadScript(src, type) {
var script = document.createElement('script');
script.src = src;
if (type) {
script.type = type;
}
script.async = false;
document.body.appendChild(script);
}
loadScript('https://unpkg.com/react@16/umd/react.production.min.js');
loadScript('https://unpkg.com/react-dom@16/umd/react-dom.production.min.js');
// You'll need to place the REACT.JSX code above in a locally hosted file to reproduce the results:
loadScript('react.jsx', 'text/babel');
// To toggle: rem the following line and un-rem the corresponding SCRIPT tag below
loadScript('https://unpkg.com/babel-standalone@6/babel.min.js');
</script>
<!-- To toggle: un-rem the the following SCRIPT tag and rem the last loadScript() call in the JavaScript above -->
<!--
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
-->
</body>
</html>
babel-standalone listens for the DOMContentLoaded
event to fire, which causes it to append a transpiled version of any JSX script blocks to the HEAD
. At the end of DOM loading, if the HEAD
does not have a SCRIPT
, then fire the DOMContentLoaded
event again; below is a code block that does the trick.
NB: This is a kludge fix specific to babel-standalone; if any other library in use responds to DOMContentLoaded as well, its code may run a second time, which may not be desirable.
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
if (document.querySelectorAll('head script').length === 0) {
window.dispatchEvent(new Event('DOMContentLoaded'));
}
}
}
The full code above edited to work is therefore:
<!doctype html>
<html>
<head>
<title>Inscrutable Babel Problem</title>
<meta charset="utf-8">
</head>
<body>
<div>
<p>Babel runs when the DOMContentLoaded event is fired. HELLO will display below when it works.</p>
</div>
<div id=app></div>
<script>
function loadScript(src, type) {
var script = document.createElement('script');
script.src = src;
if (type) {
script.type = type;
}
script.async = false;
document.body.appendChild(script);
}
loadScript('https://unpkg.com/react@16/umd/react.production.min.js');
loadScript('https://unpkg.com/react-dom@16/umd/react-dom.production.min.js');
loadScript('javascript/react.jsx', 'text/babel');
loadScript('https://unpkg.com/babel-standalone@6/babel.min.js');
// Listen for completion of DOM loading; if no SCRIPT element has been added
// to the HEAD, fire the DOMContentLoaded event again:
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
if (document.querySelectorAll('head script').length === 0) {
window.dispatchEvent(new Event('DOMContentLoaded'));
}
}
}
</script>
</body>
</html>