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