I am working on a relatively old legacy web app. It’s a very complex system, and was built upon jQuery
and uses requirejs
for module loading. I have been slowly updating it to use ES5+ features and React
. In particular, working with React
in this app has been a bit painful. This app, you see, doesn’t have a build environment, so using webpack
and babel
aren’t available, at least not in the standard way. With a great deal of effort, it certainly can be made to work with a build environment, but my intention is to spend my energy on cleaning up the code and, when that task is complete, bringing in a standard build system. Ah, that will be great.
But, at least for today, I want to add some React
code. I’ve done it before in this app, but since I have no build system, I’ve used React.createElement()
. If you’ve tried to build anything more than a trivial Component
using React.createElement()
, you know that it gets old, fast.
So I want to just JSX, naturally. But with no build system, I wasn’t sure how to proceed. There are plenty of examples on how to use babel standalone
and mark your script
tags as "text/babel"
to have your JSX code converted to JS on the fly. But, a couple problems. First, I’m using requirejs
, and it doesn’t want to mark only some script tags as "text/babel"
(it can mark all or none). I’ve even gone so far as modifying requirejs
to only mark certain script
tags as "text/babel"
but that leads to the next problem: babel standalone
doesn’t seem to compile the scripts imported by requirejs
. I’m sure there’s a way, but I gave up on that approach.
What I really wanted was something that would just compile JSX files into JS, so I could write complex React
Components
and not worry about it.
I found some examples for using browserify
and babel
to basically do what a build system would do, but it ends up creating massive JS files with all kinds of transpiled code. Clearly I was doing this wrong.
I finally found that I can use a basic babel
transformFile()
call with some relatively simple configuration that mainly just compiles the React
JSX into React
JS with React.createElement()
calls, and really no other change to my code.
I wrote a “watcher” script for this purpose, and now just keep it running in my IDE while I work:
const args = require('minimist')(process.argv.slice(2));
const fs = require("fs/promises");
const path = require("path");
const chokidar = require("chokidar");
const babel = require("@babel/core");
const options = {
ignored: /(^|[\/\\])\../
};
async function compile(src) {
return new Promise((resolve, reject) => {
const info = path.parse(src);
const dest = path.join(info.dir, `${info.name}.js`);
babel.transformFile(src, {}, async (error, result) => {
if (error) { return reject(error) }
await fs.writeFile(dest, result.code);
console.log(`compiled ${src} -> ${dest}`);
resolve();
});
});
}
async function remove(src) {
const info = path.parse(src);
const dest = path.join(info.dir, `${info.name}.js`);
fs.unlink(dest)
.then(() => {
console.log(`removed ${dest}`);
})
.catch(error => {
// ignore
});
}
(async () => {
chokidar.watch('**/*.jsx', options).on('all', async (event, path) => {
if (event === "add" || event === "change") {
await compile(path);
}
if (event === "unlink" && args.delete) {
await remove(path);
}
});
})();
The babel configuration file (babel.config.json
) looks like this:
{
"presets": [
[
"@babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
],
"@babel/preset-react"
],
"plugins": ["@babel/plugin-syntax-class-properties"]
}
For packages, I’ve installed these in my package.json
file:
@babel/cli
@babel/core
@babel/preset-env
@babel/preset-react
chokidar minimist