import is not working nodeJs application React SSR - node.js

I'm trying to implement SSR for a React application I started with creating a server directory that contains 3 files (bootstrap, index, renderer)
bootstrap.js contains babel configs to transpile to es5
index.js create an express application and express Router
renderer.js is responsible for rendering React application to string and send it as html to client.
bootstap.js =>
require('ignore-styles');
require('#babel/register')({
ignore: [
function (filePath) {
return !filePath.includes('node_modules');
}
],
presets: [
[
"#babel/preset-env",
{
"modules": false
}
],
'#babel/preset-react',
'#babel/flow'
],
plugins: [
[
"#babel/plugin-transform-runtime",
{
"regenerator": true
}
],
"#babel/plugin-proposal-object-rest-spread",
"#babel/plugin-syntax-dynamic-import",
"react-loadable/babel",
"#babel/plugin-proposal-class-properties",
"dynamic-import-node"
]
});
require('./index');
index.js =>
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
dotenv.config();
const express = require('express');
const serverRenderer = require('./middleware/renderer');
const PORT = process.NODE_ENV === 'development' ? 3000 : 7160;
const path = require('path');
const app = express();
app.use(cookieParser());
const router = express.Router();
const routes = require('../src/router/appRoutes').default;
router.use(express.static(
path.resolve(__dirname, '..', 'build'),
{ maxAge: '30d' },
));
routes.map(path => app.get(path, serverRenderer));
app.use(router);
app.listen(PORT, (error) => {
if (error) {
return console.log('something bad happened', error);
}
console.log("listening on " + PORT + "...");
});
but when I run
NODE_ENV=production node server/bootstrap.js
this command to start server side application I get this error
import dotenv from 'dotenv';
^^^^^^
SyntaxError: Unexpected identifier
at Module._compile (internal/modules/cjs/loader.js:723:23)
at Module._compile (/Users/amirtahani/projects/uneed/node_modules/pirates/lib/index.js:99:24)
at Module._extensions..js (internal/modules/cjs/loader.js:789:10)
at Object.newLoader [as .js] (/Users/amirtahani/projects/uneed/node_modules/pirates/lib/index.js:104:7)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Module.require (internal/modules/cjs/loader.js:692:17)
at require (internal/modules/cjs/helpers.js:25:18)
at Object.<anonymous> (/Users/amirtahani/projects/uneed/server/bootstrap.js:34:1)
and the weird thing is the same code is working on another project.
and here is my devDependencies
"devDependencies": {
"#babel/core": "7.6.4",
"#babel/plugin-proposal-class-properties": "7.5.5",
"#babel/plugin-proposal-object-rest-spread": "7.6.2",
"#babel/plugin-syntax-dynamic-import": "7.2.0",
"#babel/plugin-transform-classes": "7.5.5",
"#babel/plugin-transform-modules-commonjs": "7.6.0",
"#babel/plugin-transform-runtime": "7.6.2",
"#babel/preset-env": "7.6.3",
"#babel/preset-flow": "7.0.0",
"#babel/preset-react": "7.6.3",
"#babel/register": "7.6.2",
"babel-cli": "6.26.0",
"babel-plugin-dynamic-import-node": "2.3.0",
"babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
"flow-bin": "0.102.0",
"ignore-styles": "5.0.1"
}
any ideas?

"import --from --" is ES15 syntax, however node.js uses commonJS module syntax. So you need to install and configure webpack. Babel just trans-piles the new generation javascript into the old javascript code. However webpack is going to bundle your application code into a single file and your server file will be executed through that bundle.js.
For server-side rendering you need 2 bundle.js. one for client and another one for server. HTML files without javascript have no functionality.
if we start from server, this is how we write our code into the index.js.
const renderToString=require("react-dom/server").renderToString //specifically created for server
const Home=require("./components/Home").default //home component
const React=require("react")
const express=require("express")
const app=express()
app.get("/",(req,res)=>{
const content =renderToString(<Home/>)
res.send(content)
})
This is how we render our code to the browser for "/" route. there are 2 flaws here. First one, we are sending file without javascript code. Any functionality in Home component will not work. For example, if you had button inside Home component with click event, that click event will not work. Because server is not shipping down any javascript code. Second flaw is we used jsx here:
const content =renderToString(<Home/>). so when node executes this file, it will not recognize this syntax and will give error.
to fix those 2 issues, we need webpack. webpack will transform index.js into a file which we specify the name and its location. In server-side, we usually name the file bundle.js inside public folder. So when we start the server with node or nodemon, we will execute public/bundle.js NOT the index.js file.
So we need to reorganize the code inside index.js and this time since webpack will transform the code, we can use "import" syntax.
import React from "react";
import { renderToString } from "react-dom/server";
import Home from "./components/Home"
import React from "react"
import express from "express";
const app=express()
app.use(express.static("public")) //This will make public folder publicly available so we can ship it down to the browser.
app.get("/",(req,res)=>{
const content=renderToString(<Home/>)
//I used template strings ``
const html= `
<html>
<head></head>
<body>
<div id="root">${content}</div>
<script src="bundle.js"> </script>
//since we are sending a file, express will look for bundle.js inside the public folder. So we do not need to write relative path or absolute path.
</body></html> `
res.send(html)
})
Now we need a file to configure webpack. We name it webpack.config.js in the root of the app.
const path=require("path")
module.exports={
//in server side we keep client and server logic inside src folder
entry:"./src/index.js", //relative path
mode:"development",
output:{filename:bundle.js,
path:path.resolve(__dirname,"build")},
//absolute path. that is why we use native node module path. also you do not need to create build folder. webpack will create automatically
module:{rules:[{test:/\.js$/,
loader:"babel-loader",
exclude:/node_modules/,
options:{presets:["#babel/preset-env","#babel/preset-react"]}}]}
}
lastly in package.json
"scripts": {
"dev:server": "nodemon --watch build --exec \"node build/bundle.js\"",
"dev:build-server": "webpack --config webpack.server.js --watch",
"dev:build-client": "webpack --config webpack.client.js --watch"
},
in dev:server we watch "build" folder for changes.(build folder is in the root of app.) then we execute "bundle.js" file inside the build directory.
so to give an answer to your question, this is the basic of server-side part of isomorphic javascript app.

I installed node LTS (v12.13.0) but got the same error when used import instead of require. It seems that es6 imports are still an experimental feature, even in the latest node version.
If you want to test this feature, you need to do these steps:
Add "type": "module" in your package.json
Run your server with this flag: --experimental-modules. e.g. NODE_ENV=production node --experimental-modules server/bootstrap.js

import and export syntax is still experimental in node js latest versions, but there is a work around to this issue.
try to add "type": "module" in your package.json file and change your .js bundle extension to .mjs in your webpack config and run your generated file with this command:
NODE_ENV=production node --experimental-modules server/bootstrap.mjs
also i recommend you to read this page.

First check if the dotenv package is installed or not. If not, you can install it in the following command -
npm i --save dotenv
In order to use dotenv, you don't need to import it and then configure it.
Instead use the following syntax -
require('dotenv').config()

Related

My React App Unit Tests Jest is breaking: function(module,exports,require,__dirname,__filename,jest) Cannot use import statement outside a module

I'm facing a problem when trying to run the Jest tests (NextJs app) with my component library.
My React library
I'm using this command to build the React library:
"build-esm": "tsc --project tsconfig.build.json",
"build-cjs": "tsc --project tsconfig.build.json --module commonjs --outDir lib/cjs",
"build": "rm -fr lib/ && npm run build-esm && npm run build-cjs"
Will generate it:
package.json:
(...)
"main": "./lib/cjs/index.js",
"module": "./lib/esm/index.js",
"types": "./lib/esm/index.d.ts",
(...)
My "Nextjs client project" (that will use the lib as a dependency):
jest.config.js
// jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './'
});
// Add any custom config to be passed to Jest
/** #type {import('jest').Config} */
const customJestConfig = {
// Add more setup options before each test is run
setupFilesAfterEnv: ['./jest.setup.js'],
// if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
moduleDirectories: ['node_modules'],
testEnvironment: 'jest-environment-jsdom',
transformIgnorePatterns: ['<rootDir>/node_modules/']
};
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig);
console error:
(...)/node_modules/nanoid/index.browser.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { urlAlphabet } from './url-alphabet/index.js'
^^^^^^
SyntaxError: Cannot use import statement outside a module
8 | var react_window_1 = require("react-window");
9 | var react_window_infinite_loader_1 = __importDefault(require("react-window-infinite-loader"));
> 10 | var nanoid_1 = require("nanoid");
I appreciate any support

React component bundle for consuming by browser and node (react hooks issue)

I have seriously headache because of this issue.
Source code - https://github.com/marekkobida/stackoverflow
UPDATE - without React hooks everything works...
Issue
I am trying to build a single bundle (react component) for browser and node via webpack bundler for SSR purposes. Node should swallow that bundle as described here:
const React = require("react");
const ReactDOMServer = require("react-dom/server");
const Test = require("./public/index.js").default; // ✅ React Component (works)
ReactDOMServer.renderToString(React.createElement(Test)); // Error
but an error appears:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
BUT browser version works. However after adding externals into webpack configuration, the node version works and browser does not.
React component file before bundling via webpack bundler
import React from "react";
import ReactDOM from "react-dom";
function Test() {
const [_, __] = React.useState(1);
return React.createElement("div", null, _); // ✅ <div>1</div> (works)
}
if (typeof window !== "undefined") { // because of UMD
ReactDOM.render(React.createElement(Test), document.getElementById("index")); // ✅ (works)
}
export default Test; // ✅
webpack configuration file
...
{
entry: "./index.js", // File described above
// The node version will work but the browser version will stop working after uncommenting the lines below
//
// externals: {
// react: 'react',
// 'react-dom': 'react-dom'
// },
mode: "development",
output: {
filename: "index.js",
globalObject: "this",
libraryTarget: "umd",
path: path.resolve("./public"),
publicPath: "",
},
},
...
BUT browser version works. However after adding externals into webpack configuration, the node version works and browser does not.
This maybe considered a workaround, but you could use two separate webpack configs, one for the node and the other for the browser version.
Edit:
Also for the node version not working: You're only have a require for ReactDOMServer, but not for ReactDom itself, maybe that is a problem, too.
I'm using this form, maybe give it a try:
externals: {
"react": {
"commonjs": "react",
"commonjs2": "react",
"amd": "react",
"root": "React"
},
"react-dom": {
"commonjs": "react-dom",
"commonjs2": "react-dom",
"amd": "react-dom",
"root": "ReactDom"
},
},

Babel-node ignores or misbehaves when I add ignore option to babel.config.js

am using the command
npx babel-node --ignore=' ' --extensions='.ts,.tsx,.js,.jsx,.es6,.es' test.js
to compile the script named test.js .
test.js imports import template from 'lodash-es/template'; and I would like it to be compiled too.
Adding ignore or include or exclude options regex at babel.config.js doesn't work.
How do i add an inline --ignore option ({ ignore: [/node_modules\/(?!lodash-es)/] }) like babel.config.js?
Why does { ignore: [/node_modules\/(?!lodash-es)/] } work with #babel/register and not with babel.config.js ?
Below is test.js:
import fs from 'fs';
import template from 'lodash-es/template';
console.log(template);
export default function () {
};
package.json:
"#babel/cli": "^7.7.7",
"#babel/core": "^7.7.7",
"#babel/node": "^7.8.4",
Problem seems to be similar to https://github.com/facebook/jest/issues/6229
I resolved the issue with: --ignore="/node_modules\/(?\!lodash-es)/"
npx babel-node --config-file="./babel.config.js" --ignore="/node_modules\/(?\!lodash-es)/" --extensions='.ts,.tsx,.js,.jsx,.es6,.es' test.js
using --ignore=' ' will also work, but you may start getting
[BABEL] Note: The code generator has deoptimised the styling of {filename} as it exceeds the max of 500KB.
because Babel will compile all imported node_modues, which is quite expensive.

node throw error when use async/await syntax.but it works well with import/export syntax

nodemon throw the error when use async/await syntax:
**/node_modules/#babel/runtime/helpers/esm/asyncToGenerator.js:17
export default function _asyncToGenerator(fn) {
^^^^^^
SyntaxError: Unexpected token export
but it works well with import/export syntax.
package.json
{
"scripts": {
"dev": "nodemon --exec babel-node server/index.js",
}
"dependencies": {
"#babel/polyfill": "^7.2.5",
},
"devDependencies": {
"#babel/cli": "^7.2.3",
"#babel/core": "^7.2.2",
"#babel/node": "^7.2.2",
"#babel/preset-env": "^7.2.3",
}
}
.babelrc
{
"presets": [
"#babel/preset-env"
]
}
asyncToGenerator.js
function asyncGeneratorStep(...) { ... }
export default function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
But I think it probably goes wrong because of my babel setting.
BTW, when I use typeof, it throws the same error
**/node_modules/#babel/runtime/helpers/esm/typeof.js:3
export default function _typeof(obj) {
^^^^^^
SyntaxError: Unexpected token export
update 6/12, 2020:
structure:
src (vue app)
server (express app)
|---- src
|---- babel.config.js
|---- index.js
package.json
babel.config.js
In this project, I have two babel config, one is for the vue's app, and another is for the express. What I want is running these apps at the project's root path.
And in the beginning, my script about running express is
nodemon --exec babel-node server/index.js
It can run express, but it gets the wrong babel config(project/babel.config.js)
And the solution is just to point out the specific path which babel confg you want to use(project/server/babel.config.js). So the correct script to run the express is
nodemon --exec babel-node --config-file ./server/babel.config.js server/index.js",
answering your comment:
nodemon doesn't know about babelrc (and it shouldn't). And babel (AFAIK) doesn't allow you to select the babelrc file that you want to use.
I think that you should merge your babelrc files and set the env flag when running babel-node. Like this: babeljs.io/docs/en/6.26.3/babelrc#env-option
Another option would be to make a script that renames the babelrc file each time the app is reloaded, or something like that (I don't understand why you need 2 .babelrc files)
In an answer no longer visible (probably deleted by a moderator) I read that there are more .babelrc files in the project.
From babel docs it seems that the .babelrc needs to be in the same directory of the subpackage. I suggest you to read that doc, probably you can find the solution that better fits your requirements.
Sorry for the vague answer, but due to the lack of details in your question (server/index.js file content, directories structure, etc.) I can't do better.

Register Node Module Manually

Question:
I have a project in TypeScript that uses several APIs I don't have access to on my computer (they exist on the web). The code will compile fine locally since I have all the APIs in foo.d.ts files, and so the system knows they exist somewhere.
However, I want to unit test parts of the code with a NodeJS app. I can import the code into node just fine, but whenever I reach code that imports a module from a definition file, I get the following error:
Error: Cannot find module 'messages'
at Function.Module._resolveFilename (module.js:527:15)
at Function.Module._load (module.js:476:23)
at Module.require (module.js:568:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (~/dev/repos/sample_typescript_fail/App.js:3:18)
at Module._compile (module.js:624:30)
at Object.Module._extensions..js (module.js:635:10)
at Module.load (module.js:545:32)
at tryModuleLoad (module.js:508:12)
at Function.Module._load (module.js:500:3)
...
This makes sense, since that code is just defined locally, and does not exist.
Can I manually register modules to NodeJS, like
Registry.register('messages', () => {...});
so that I can compile and test with polyfills?
Here's an example app
package.json
{
"name": "sample_typescript_declare_issue",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"start": "ts-node index.ts"
},
"author": "",
"license": "MIT"
}
index.ts
import {App} from "./App";
console.log("Starting program");
// How do I fake "import {MessageSender} from "messages";"
// here so that I can run this node app as a test?
let app: App = new App();
console.log("Ending program");
App.ts
import {MessageSender} from "messages";
export class App {
constructor() {
let messageSender: MessageSender = new MessageSender();
messageSender.sendMessage("foo!");
}
}
node_modules/#types/messages/index.d.ts
export = Messages;
export as namespace Messages;
declare module Messages {
class MessageSender {
constructor();
sendMessage(message: any): void;
}
}
Running Example App
Running with npm start gives the error message above.
Running tsc *.tsc compiles just fine.
Other things I've tried
Updating package.json to include a bin:
{
"name": "sample_typescript_declare_issue",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"start": "ts-node index.ts"
},
"author": "",
"license": "MIT",
"bin": {
"messages": "./polyfills/messages/index.ts"
}
}
As you mentioned, compiling works fine - this is just a question of availability of .d.ts files.
What you want to do is alter module import at runtime, in other words alter the behaviour of the nodejs require function since
import {MessageSender} from "messages";
will be transpiled in javascript (ES6) to something like
const messages_1 = require("messages");
...
messages_1.MessageSender
To modify that behaviour, the first thing that springs to mind is to use the deprecated - but still available - require.extensions object.
When running locally you must first inject something like
require.extensions['.js'] = (module, filename) => {
if (filename === 'messages') {
// then load mock module/polyfill using the passed module object
// see (https://nodejs.org/api/modules.html#modules_the_module_object)
}
};
The doc says there are better alternatives but fails to clearly mention any.
Another possibility is to look at projects like sandboxed-module which should help (I have not tested it)

Resources