Cannot use JSX with nodejs ESM module loader - node.js

I attempted to run a simple block of code featuring React "render" method , but the browser didn't display the text.
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<h1>Hello World</h1>,document.getElementById('root'));
I'm using VS Code as editor therefore I typed the "Run and Debug Node.js" command. It came up with the warning below
(node:3004) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
Setting "type:module" in the package.json file solved the problem but on the other side another problem arised
(node:18968) ExperimentalWarning: The ESM module loader is experimental.
warning.js:32
SyntaxError: Unexpected token '<'
at Loader.moduleStrategy (internal/modules/esm/translators.js:81:18)
at async link (internal/modules/esm/module_job.js:37:21)
That won't allow me to write any tags whatsover. What am I missing and how can I solve it? Below is the index.html file and the file structure
<html>
<head>
<title> React Test</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

JSX isn't a valid JavaScript syntax and it isn't part of any ECMAScript specification as well at the current time. NodeJS supporting ESM does not mean it supports JSX natively.
JSX are expected to be transpiled into a valid JavaScript on build/compile time using tools like babel. If you are using React, the most simple way to do this is to use babel with #babel/preset-react, which will transpile all JSX into React.createElement() calls. You can then run the code generated by babel using node.
To give you a clearer picture, here is a Babel online REPL that you can play with to see how your code are transpiled by babel.
A common setup for react apps that is going to be run on the browser is like:
Use webpack to bundle your app
Configure webpack to use babel-loader so it transpiles your code into something that browsers can run
Use the generated javascript bundle on browser

I don't understand why you have .vscode folder in your src folder and it contains index.html and index.js files.
I think you need to move your index.js file from .vscode folder to src and delete .vscode folder. Or just create a new app with npm.

that folder structure came out after using create-react-app?
btw, try to return the component with React.createElement()
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* ... h1 children ... */),
React.createElement('ul', /* ... ul children ... */)
);
as docs says "The render method returns a description of what you want to see on the screen. React takes the description and displays the result. In particular, render returns a React element, which is a lightweight description of what to render. Most React developers use a special syntax called “JSX” which makes these structures easier to write. The syntax is transformed at build time to React.createElement('div')."
check this out
https://reactjs.org/docs/react-api.html#createelement
----- add on
You can now import .js file in node v12.x, in 2 steps:
after adding the following line in your package.json file:
// package.json
{
"type": "module"
}
you need to add --experimental-modules flag when launching the script:
node --experimental-modules index.js
https://nodejs.org/api/esm.html#esm_commonjs_json_and_native_modules

Related

Importing JSX with ESM Dynamic Imports in Node.js

Summary of problem
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?
A few things I've tried
Approach 1: Direct attempt
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".
Approach 2: Babeling
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.)
Approach 3: Require
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.
Code examples
Approach 1: Direct attempt
const module = await import("./jsxComponent.js")
Approach 2: Babeling
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).
Approach 3: Require
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",
},
],
],
};
Then running my node build script with the node loader set as an experimental loader: 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.

How do I integrate a Polymer + Node.JS JavaScript app with React + TypeScript

I have recently been tasked with integrating React + TypeScript with an existing starter app that contains many great Polymer Components (Predix UI Starter). I understand that this may seem non-sensical to some (after all, you could simply provide app state and logic using Polymer as a framework). However, the React library and the object oriented nature of TypeScript have a lot to offer, so what are some tips for getting started here?
I will be integrating the Predix UI Starter with the create-react-app starter. Specifically, I will enable a React Component to render() a px-dropdown polymer component. This concept and method can apply to any polymer starter app. This DOES NOT completely integrate these two starters, it is ONLY a POC for rendering a polymer component in a React render() method, using TypeScript.
Clone both of these repos. Work out of the Predix UI Starter - this will contain all of the components once you run npm install and bower install.
Ensure that the package.json in the Predix UI Starter contains all of the dependencies and devDependencies that the react app contains. In the package.json file, in the scripts object, change "start" : ..., to "start" : "react-scripts-ts start". This will serve the app to localhost using TypeScript (this is where the tsconfig.json and the tslint.json files come into play).
Copy the tsconfig.json, tslint.json, and the tsconfig.test.json files into the root directory of the Predix UI Starter.
As per the naming convention for React apps, rename the server folder to src. In the .bowerrc file, append public/ before bower_components, this will enable the public/index.html file to import polymer components generated by bower. Unless configured to do so, public/index.html will be unable to load polymer components in parent folders to the public folder. (The user may need to configure the Predix Starter to initially serve the public/index.html file).
From the react starter to the Predix starter, copy the App.tsx and the index.tsx files to the src folder, and copy the index.html file to the public folder (this file will be what the app initially serves).
Add the px-dropdown component to the JSX namespace so that you can render it within a react component by adding a global.d.ts file to the root directory with the following content:
declare namespace JSX {
interface IntrinsicElements {
'px-dropdown': any
}
}
You are now ready to render() the px-dropdown component. In the public/index.html file, import the component using the following two lines (within the <head> tag):
<link rel="import" href="bower_components/polymer/polymer.html" />
<link rel="import" href="bower_components/px-dropdown/px-dropdown.html" />
Let's pass some items to the props of the App Component. In the index.tsx file, edit the line that renders the component to look like <App items={'[{"key":"1", "val": "One"},{"key" : "2", "val": "Two"}]'}/>
The following code in App.tsx will render the px-dropdown component successfully:
class App extends React.Component<any, any> {
constructor (props: any) {
super(props);
}
render() {
...
return (
<div className="App">
...
<px-dropdown
items={this.props.items}
sort-mode="key"
button-style="default"
display-value="Select"
disable-clear>
</px-dropdown>
</div>
);
}
}
You are now ready to begin rendering Polymer components using React + TypeScript.

How to properly load photos with Node and Webpack?

Should I be loading photos in node by importing relative paths or by referencing the file URL, and how do you properly load photos from their relative path?
Currently I'm serving static files with express:
server.use(express.static(__dirname + '/../public'));
And loading photos by referencing file URLs:
<img src='/images/tom.jpg'/>
I have a webpack configuration set up to use file-loader on (png|jpg|gif), and the webpack docs say, that with file-loader, you can import photos from relative paths (https://webpack.js.org/loaders/file-loader/#src/components/Sidebar/Sidebar.jsx):
import img from './file.png'
With this configuration (this is the config I'm using):
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
}
]
}
}
However, when I try to import an image from it's relative path:
//import
import img from '../../public/images/tom.jpg';
//render
<img src={img}/>
I receive this error:
/Users/ryan/Desktop/Df/Coding/ryanchacon.io/node_modules/babel-core/lib/transformation/file/index.js:590
throw err;
^
SyntaxError:
/Users/ryan/Desktop/Df/Coding/ryanchacon.io/public/images/tom.jpg:
Unexpected character '�' (1:0)
1 | ����
But if I emit the 'import from relative path', start my server, and then add the relative import back, the image will load from its relative path. However, if I restart my server, the error throws again.
So instead I'm referencing the file URL to get around this error, but I'm not sure how I should properly load files from their relative path with my webpack/node configuration.
Currently, I'm running node v8.6.0, webpack v3.6.0, express v4.15.4, file-loader v1.1.5, and react v16.0.0.
In your server.js file, you're using server side rendering (which is awesome): ReactDOMServer.renderToString( <App /> ). The App component rendered on the server side is imported via the import App from './src/App'; statement. In the App.js file you have the JPG import statement: import img from '../public/hermes.jpg';.
Please notice that in this context, your node.js is not aware of any webpack bundle in your project, and node actually tries to import the '../public/hermes.jpg' file, which causes the error you're facing (node can only load JS modules).
So the question is, in fact, how to use file-loader in a universal/isomorphic manner. You can take a look at this example project: https://github.com/webpack/react-webpack-server-side-example that shows a webpack configuration for both the server and the client code. Theoretically, if you bundle your server code via webpack, when you run it, the JPG import statement would already be processed by the file loader and should not cause any problem. I have to admit that personally I do not think bundling server code is a good idea (bundling solves client side issues that are not really relevant on the server side).
If you insist using file-loader and bundling your image files into your client code, using server side webpack might be a good solution for you. If not, you can simply load the images from the /public folder.
For what it's worth, I believe that loading the images by using URLs is not only simpler, but also faster - the browser will download your lighter JS bundle (it's lighter because it does not contain encoded image files) and your code will start running earlier on, downloading the images from HTTP in a parallel, asynchronous manner.

node.js sharing javascript file between client and server

I want a JavaScript file to be included in my client as well as server.
Having some issues. Please help me.
Code:
require('app.js');
or
<script src='app.js'></script>
Which one should I use?
You can use Browserify to bundle your node module then import that js file with:
<script src='app.js'></script>
The require() function will now be on the global namespace and you can call the following to make the module available to use:
var app = require('app.js');
Note: in Browserify you'll need to export the module you want to import using require.

ES6 React server-side rendering, how to import a React Component?

I'm transpiling ES6 to ES5.
BabelJS for the NodeJS Express server files and server-side rendering output to a directory build/server/.
Browserify + babelify for the ReactComponents output to a build/client/bundle.js file
When trying to import a React Component from build/client/bundle.js to a build/server/ file the app crashes because I'm importing an untranspiled ReactComponent.
How could I import the ReactComponent without duplicating the code in the server (re-using the code from the client/bundle.js)?
You have a few solutions:
Your server code doesn't need to be pre-compiled. If you run it with babel-node, it will be compiled on-the-fly.
You could bundle your server code. I don't know any resource on how to do it with browserify, but here's a very good resource to get started with webpack for your backend.
You could build your client code alongside your server code.

Resources