{"id":135,"date":"2021-04-23T09:29:51","date_gmt":"2021-04-23T14:29:51","guid":{"rendered":"https:\/\/www.grizzly-hills.com\/?p=135"},"modified":"2021-04-23T09:29:51","modified_gmt":"2021-04-23T14:29:51","slug":"react-in-a-legacy-app","status":"publish","type":"post","link":"https:\/\/www.grizzly-hills.com\/index.php\/2021\/04\/23\/react-in-a-legacy-app\/","title":{"rendered":"React in a Legacy App"},"content":{"rendered":"\n<p>I am working on a relatively old legacy web app. It&#8217;s a very complex system, and was built upon <code>jQuery<\/code> and uses <code>requirejs<\/code> for module loading. I have been slowly updating it to use ES5+ features and <code>React<\/code>. In particular, working with <code>React<\/code> in this app has been a bit painful. This app, you see, doesn&#8217;t have a build environment, so using <code>webpack<\/code> and <code>babel<\/code> aren&#8217;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.<\/p>\n\n\n<p>But, at least for today, I want to add some <code>React<\/code> code. I&#8217;ve done it before in this app, but since I have no build system, I&#8217;ve used <strong><code>React.createElement()<\/code><\/strong>. If you&#8217;ve tried to build anything more than a trivial <code>Component<\/code> using <code>React.createElement()<\/code>, you know that it gets old, fast.<\/p>\n\n\n<p>So I want to just JSX, naturally. But with no build system, I wasn&#8217;t sure how to proceed. There are plenty of examples on how to use <code>babel standalone<\/code> and mark your <code>script<\/code> tags as <code>\"text\/babel\"<\/code> to have your JSX code converted to JS on the fly. But, a couple problems. First, I&#8217;m using <code>requirejs<\/code>, and it doesn&#8217;t want to mark <em>only some<\/em> script tags as <code>\"text\/babel\"<\/code> (it can mark all or none). I&#8217;ve even gone so far as modifying <code>requirejs<\/code> to only mark certain <code>script<\/code> tags as <code>\"text\/babel\"<\/code> but that leads to the next problem: <code>babel standalone<\/code> doesn&#8217;t seem to compile the scripts imported by <code>requirejs<\/code>. I&#8217;m sure there&#8217;s a way, but I gave up on that approach.<\/p>\n\n\n<p>What I really wanted was something that would just compile JSX files into JS, so I could write complex <code>React<\/code> <code>Components<\/code> and not worry about it.<\/p>\n\n\n<p>I found some examples for using <code>browserify<\/code> and <code>babel<\/code> 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.<\/p>\n\n\n<p>I finally found that I can use a basic <code>babel<\/code> <code>transformFile()<\/code> call with some relatively simple configuration that mainly just compiles the <code>React<\/code> JSX into <code>React<\/code> JS with <code>React.createElement()<\/code> calls, and really no other change to my code.<\/p>\n\n\n<p>I wrote a &#8220;watcher&#8221; script for this purpose, and now just keep it running in my IDE while I work:<\/p>\n\n\n<pre class=\"wp-block-preformatted\"> const args = require('minimist')(process.argv.slice(2));\n const fs = require(\"fs\/promises\");\n const path = require(\"path\");\n const chokidar = require(\"chokidar\");\n const babel = require(\"@babel\/core\");\n const options = {\n &nbsp; ignored: \/(^|[\\\/\\\\])\\..\/\n };\n async function compile(src) {\n &nbsp; return new Promise((resolve, reject) =&gt; {\n &nbsp; &nbsp; const info = path.parse(src);\n &nbsp; &nbsp; const dest = path.join(info.dir, `${info.name}.js`);\n &nbsp; &nbsp; babel.transformFile(src, {}, async (error, result) =&gt; {\n &nbsp; &nbsp; &nbsp; if (error) { return reject(error) }\n &nbsp; &nbsp; &nbsp; await fs.writeFile(dest, result.code);\n &nbsp; &nbsp; &nbsp; console.log(`compiled ${src} -&gt; ${dest}`);\n &nbsp; &nbsp; &nbsp; resolve();\n &nbsp; &nbsp; });\n &nbsp; });\n }\n async function remove(src) {\n &nbsp; const info = path.parse(src);\n &nbsp; const dest = path.join(info.dir, `${info.name}.js`);\n &nbsp; fs.unlink(dest)\n &nbsp; &nbsp; .then(() =&gt; {\n &nbsp; &nbsp; &nbsp; console.log(`removed ${dest}`);\n &nbsp; &nbsp; })\n &nbsp; &nbsp; .catch(error =&gt; {\n &nbsp; &nbsp; &nbsp; \/\/ ignore\n &nbsp; &nbsp; });\n }\n (async () =&gt; {\n &nbsp; chokidar.watch('**\/*.jsx', options).on('all', async (event, path) =&gt; {\n &nbsp; &nbsp; if (event === \"add\" || event === \"change\") {\n &nbsp; &nbsp; &nbsp; await compile(path);\n &nbsp; &nbsp; }\n &nbsp; &nbsp; if (event === \"unlink\" &amp;&amp; args.delete) {\n &nbsp; &nbsp; &nbsp; await remove(path);\n &nbsp; &nbsp; }\n &nbsp; });\n })(); <\/pre>\n\n\n<p>The babel configuration file (<code>babel.config.json<\/code>) looks like this:<\/p>\n\n\n<pre class=\"wp-block-preformatted\">{\n   \"presets\": [\n     [\n       \"@babel\/env\",\n       {\n         \"targets\": {\n           \"edge\": \"17\",\n           \"firefox\": \"60\",\n           \"chrome\": \"67\",\n           \"safari\": \"11.1\"\n         },\n         \"useBuiltIns\": \"usage\",\n         \"corejs\": \"3.6.5\"\n       }\n     ],\n     \"@babel\/preset-react\"\n   ],\n   \"plugins\": [\"@babel\/plugin-syntax-class-properties\"]\n }<\/pre>\n\n\n<p>For packages, I&#8217;ve installed these in my <code>package.json<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-preformatted\">@babel\/cli\n@babel\/core\n@babel\/preset-env\n@babel\/preset-react\nchokidar minimist <\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I am working on a relatively old legacy web app. It&#8217;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, &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.grizzly-hills.com\/index.php\/2021\/04\/23\/react-in-a-legacy-app\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;React in a Legacy App&#8221;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,5,6],"tags":[7,12,13,14,18,22],"class_list":["post-135","post","type-post","status-publish","format-standard","hentry","category-coding","category-modern-javascript","category-react","tag-babel","tag-javascript","tag-jquery","tag-jsx","tag-react","tag-webpack"],"_links":{"self":[{"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/posts\/135","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/comments?post=135"}],"version-history":[{"count":0,"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/posts\/135\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/media?parent=135"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/categories?post=135"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.grizzly-hills.com\/index.php\/wp-json\/wp\/v2\/tags?post=135"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}