I'm exclusively using ESM in my Node.js project and trying to find a way to dynamically import JSX.
I'm making a custom static site generator for my website and want to render React components to markup with renderToStaticMarkup()
, but to achieve this, I first need to successfully import the components to then run this method.
Does anyone know a way to dynamically import JSX in ESM Node.js?
I.e., to make await import("./jsxComponent.js")
work?
When I dynamically import the .js
file containing the component, I receive the error message: SyntaxError: Unexpected token '<'
. Seems import()
cannot parse JSX out of the box.
If I change the file extension of the .js
file to .jsx
, I unsurprisingly receive the error message Unknown file extension ".jsx"
.
Back in the CommonJS heyday of Node.js, I'd use @babel/register
, @babel/preset-env
, and @babel/preset-react
in a separate file with its last line invoking require()
on another .js
file that, inside itself, would then require()
the component. I'm not entirely clued up on how each Babel preset or plugin works, but this did the trick back then allowing me to require()
components to then render them to markup. Unfortunately, this doesn't work when using ESM-only packages in an ESM-only project because the moment I use @babel/register
my ESM-only packages complain and break.
I've tried using @babel/core
to transform the file before it's invoked inside import()
. I've done this by using the transformFileSync
method, but this created the error message: Error [ERR_MODULE_NOT_FOUND]: Cannot find package '"use strict"
. Inside the options object of transformFileSync
I used babel-plugin-dynamic-import-node
as a plugin and @babel/register
, @babel/preset-env
, and @babel/preset-react
as presets.
I've tried also using @babel/core
's transformSync
method by passing in the JSX code directly (rather than just the file path of the JSX-containing file), and this created the error message: Error: ENOENT: no such file or directory, open 'import Header from "./src/components/header.js";
(note: there IS a file at ./src/components/header.js
- it is one of the components being imported inside another component.)
Other approaches online recommend using require()
instead of import()
, but as I said, this is an ESM-only project using ESM-only packages and so the error message I receive when trying this is require is not defined
, as one would expect.
const module = await import("./jsxComponent.js")
const module = await import(
babelCore.transformFileSync("./jsxComponent.js", {
presets: [
"@babel/preset-env",
[
"@babel/preset-react",
{
runtime: "automatic",
},
],
],
plugins: ["dynamic-import-node"],
}).code
);
(Let me know if you want me to post any more code examples from my tests with Babel).
const module = require("./jsxComponent.js")
I was able to import JSX in my ESM-only project by:
Installing @node-loader/babel
(see GitHub repo)
Installing @babel/core
and @babel/preset-react
Creating babel.config.js
in my root directory with the following setup:
export default {
presets: [
[
"@babel/preset-react",
{
runtime: "automatic",
},
],
],
};
node --experimental-loader @node-loader/babel ./lib/build.js
I was then able to successfully use const component = await import("./jsxComponent.js")
in my node build scripts and pass the component to reactDOMServer's renderToStaticMarkup(component())
method by invoking it as a function.