We have an application on Reactjs
We have an SSR that builds the app on webpack, renders this application and returns HTML
Then on the client-side we hydrate the app
So the app is run on both server and client sides.
Problem:
We'd like to use sentry in react, so that it can be used on both client and server sides, because an error may occur in either place
What we've tried:
There are 2 modules #sentry/browser and #sentry/node.
We tried to make a module that would do:
export default isClientSide ? SentryBrowser : SentryNode
So if it's client, then use SentryBrowser. If server - SentryNode
But #sentry/node can't be run on webpack since it can't resolve node dependencies like fs, path etc.
Question:
How can we use a single Sentry interface on both client and server sides for React like #sentry/nextjs works on both sides?
Our solution was to pass in an errorHandler prop to the app. When rendering on the server we'd pass in #sentry/node's captureException, and on the client we'd pass in the #sentry/browser's one. Seemed to work just fine.
const App = ({errorHandler}) => {
..
return (<ErrorBoundary errorHandler={errorHandler}>
..
</ErrorBoundary>);
Then on server:
import { captureException as serverCaptureEx } from '#sentry/node';
hydrate(..., <App errorHandler={serverCaptureEx} />)
and on client:
import { captureException as clientCaptureEx } from '#sentry/browser';
render(..., <App errorHandler={clientCaptureEx} />)
Short answer - you can't.
Client-side is wrapped in special HOC, called in most cases Error Boundary. React passes up an error to the root component of the project and catches it and reports it to the URL. This is a browser, which is client's "server", can do requests on ethernet and so on.
If you run NextJS server, not html export (extremely important condition!), under the hood it is uses some NodeJS libraries (Express, for example). This server has nothing in common with Webpack or JS compilation and is used as middleware. As you can see, it is just a wrapper upon some route or functionality.
If you got an error on server, it is reported by NodeJS, not Webpack. So the flag somewhere in React or Webpack plugins will not help you.
One more time - client and server are different environments, browser and NodeJS server. The errors are reported by servers, not bundlers. Webpack will not help you here.
Related
I am currently making a desktop application that is a ReactJS application bundled or built with Electron and I want to use node modules that come with electron in my app, specifically exec or spawn from the child process module. My goal is to have a button press that executes a set of commands from command prompt as a sub/side process and return the stdout.
My app follows the typical app creation process as the tutorials online for first making a react app, get some dependencies and package.json alterations, get electron, make an electron.js or main.js type of file with all the basic window configs in it.
In the main entry point file, for me that is electron.js, I have:
webPreferences: {
nodeIntegration: true
},
However, I cannot use the child process node module in my react code even though I have allowed it in the create window function on electron's side. In my react component, when I do
import { exec } from 'child_process';
react seems to recognize it and displays all the exec function information when hovered over (parameters, overloads, found in child_process module, etc).
However, if I run the this example command from a button click, I get the error:
"TypeError: Object(...) is not a function". Here the error is referring to exec.
import { exec } from 'child_process';
...
const run = () => {
exec('ls -lh', (error, stdout, stderr) => {
if (error) {
console.log(`error123: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr123: ${stderr}`);
return;
}
console.log(`stdout:\n${stdout}`);
})
};
....
return (
<div>
<button onClick={run}>Run Command!</button>
</div>
)
export ....
Does anybody know how to give the react side access to the node modules within the directory or how to import/access them correctly, either directly from the react side or maybe pass them from electron to react? Or maybe I am just running it incorrectly? I am new to javascript and stuff in general so understanding the issue and debugging is really hard for me.
Thanks to Badal for putting me in the right direction.
The answer I found involves setting contextisolation to false, nodeIntegration to true, and using the ipcMain handler (electron main process) and ipcRenderer handler (rendering, in this case react, process). The first link below shows how to use the handlers (look at the newer answers in the thread) and the second link is more detailed documentation on the inner workings. Note that if you search this 'node modules in react electron app' online, a lot of people are using remote, etc. but electron.remote is for the most deprecated at v12 or 13 and onwards, using it and other deprecated modules WILL NOT tell you its deprecated. Instead it will give the fs.filereadsync or wtvr error (I forgot) because it will not be able to understand it during building. The handlers for ipcMain and ipcRenderer handle all app internal communication and processes now, as the name ipc implies.
How to import ipcRenderer in react?
https://www.electronjs.org/docs/api/ipc-main
(new changes to electron) https://www.electronjs.org/docs/breaking-changes
With this, you can have a run button to execute a certain command and use ipcrenderer to send it to the electron side of things. In the electron side (defaults to sending to the main process file, so electron or main.js), you can import whatever node modules you want and use them. For me, I made a new file, did const {exec} = require('child process'), and then had a function which executed certain command line operations. The function call to exec was placed in the ipcMain handler.
This is insecure. For my purpose, security is not a concern, but for others it might be. Make sure to perform input cleaning and other precautions that prevent people from abusing the app and doing things like making calls without proper handles that can infiltrate the system. If you are using child process, OS subsystem modules, etc. this is especially important. As long as the front end can abstract the nature of how the internal communication is performed, that should be a good start.
Electron, as a framework, is highkey changing very fast. If you want to do well in creating applications with it, I highly recommend staying very attentive with version changes. I am new to it, and through my search on this matter and more, I have seen many small but major changes to how electron's internal process works and a lot of search results yield deprecated/outdated information.
It is preferred to access Node APIs from the Main process. Hence, you need to communicate from the Renderer to the Main process to execute such actions.
To access Node APIs from the Renderer process, you also need to set contextIsolation to false along with nodeIntegration set to true. This is again very insecure, to do because
..it helps prevent the website from accessing Electron internals or the powerful APIs your preload script has access to. [1]
So try to avoid doing in production, as for development you are free to experiment.
Reference:
[1] https://www.electronjs.org/docs/tutorial/context-isolation
I'm trying to create a desktop app using ElectronJS and ReactJS.
The renderer process is bundled using webpack, since i'm using JSX in it.
When I try to import anything from electron (e.g. import electron from 'electron';, or const electron = require('electron');) in the renderer process I get these 2 errors when I either try to build it with webpack (the web part), or when I use webpack-dev-server and open the localhost URL in electron:
https://pastebin.com/WdkCcPzm
Note that I'm not using create-react-app, that bundle.js is webpack's output, App.jsx is the file I'm trying to import electron from, that I want to import electron to access the ipcRenderer variable and that I'm not attempting to import fs from the renderer process (or from the main process for that matter).
A solution I found was to bypass webpack's packing by adding this line to my index.js
eval('window.Electron = require("electron")');
and accessing electron though the variable Electron (the capital E is because vs code recognizes that as a namespace even though electron isn't imported, and thus I still get code completion)
But that's really ugly and I was hoping there was another solution.
To build bundles for the renderer process of Electron apps, webpack provides a special target parameter.
Add this to your webpack config:
target: 'electron-renderer'
See documentation: https://webpack.js.org/configuration/target/
I'm using renderToString function from react-dom (on my server side). The code looks like (+/-):
import Home from './app/containers/Home';
const app = express();
app.get('**', (req, res) => {
const html = renderToString(<Home />);
res.set('Cache-Control', 'public, max-age=600, s-maxage=1200');
res.send(html);
});
Everything goes smooth, until I try to deploy it on the server.
Example error in the console:
Error: Error parsing triggers: Cannot find module 'store/Home/actions'
When it appears?
If I change my path to some other component, which does not use any other components (only modules from node, like react or react-redux) it works correctly.
But if I try to use some component which uses other components and imports them, e.g.:
var _CreateUser = require('components/Pages/CreateUser'); (it's in the rendered component)
Now it will fail with error:
Error: Error parsing triggers: Cannot find module 'components/Pages/CreateUser'
So currently Im stuck, because I have to use my whole app on server side, not just a single component which doesn't import anything :)
Why does it work this way? Why does it fail? Is it bad webpack config fail?
Looking forward for any help. Thank you.
Note, as I said above, if I render to string some component with any imports (that doesn't use any other component in it) - the server side rendering works fine and Im able to see the renderedToString content before page loads.
Everywhere you import local modules you need to include the directory in the path otherwise it will search node_modules for a named package and ultimately fail.
require('./store/Home/actions');
Or:
import HomeActions from './store/Home/actions';
...depending on which import style you're using. An accurate directory is always needed as a part of the import/require statement.
You're using a relative path, cd into and deploy from the functions directory so it's correct.
It looks like your Home component is inside functions/app/containers/Home and you need access to the file functions/app/store/Home/actions.
From your containers/Home file, you need to go up two directories to the app folder, then go down two directories to correct file. So
import HomeActions from '../../store/Home/actions'`.
Each ../ represents going up one directory to the parent folder. We went from
functions/app/containers/Home to
functions/app/containers/ to
functions/app and then we can specify which path to continue on from there with
store/Home/actions
I have a React application created using the create-react-app module. I have recently been asked by a client to integrate with oidc. For this purpose I'm using redux-oidc, as I already have redux working in my app as well.
We managed to integrate my application into their Identity server and I'm able to sign in and get the user token stored in redux. The problem is that I'm struggling to setup silent renew in my create-react-app application as I have to add an additional entry point. Is there a way to add an additional entry point to silent_renew/index.js without ejecting create-react-app?
Currently I've create a folder called silent_renew containing an index.js file. This folder also contains a silent_renew.html file with not much in it (See: example app similar to my folder structure).
Since the landing page for silent_renew is a just a simple html page, you could bypass webpack. Just put the following file in the public folder. Also, include a copy of the oidc-client.min.js library in the same folder.
public/silent_renew.html:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="oidc-client.min.js"></script>
<script>
new Oidc.UserManager().signinSilentCallback().then()
</script>
</body>
</html>
This works at my site in the develepment config. For the production config I have the following in mind (I did not test it yet but I'm pretty confident this is the way forward...).
public/index.js
const express = require('express')
const path = require('path')
const app = express()
app.use(express.static('.'))
app.use((req, res, next) => {
if (path.extname(req.path).length > 0) {
next()
} else if (path.dirname(req.path).indexOf('silent_renew') > -1) {
req.url = '/silent_renew.html'
next()
}
else if (path.dirname(req.path).indexOf('callback') > -1) {
req.url = '/callback.html'
next()
} else {
req.url = '/index.html'
next()
}
})
app.listen(3000)
As soon as create-react-app supports multiple entry points (I hope this happens soon for enterprise login scenario's) this code becomes obsolete.
You can also take the approach of loading the main bundle in the iframe and capturing the path as mentioned here.
Then you don't need to deal with exposing a path to load the oidc client lib (oidc-client.min.js or redux-oidc.js) or dumping it's content somewhere.
index.js/ts
import * as React from 'react';
import { render } from 'react-dom';
import { processSilentRenew } from 'redux-oidc';
import App from './App';
if (window.location.pathname === '/silent-renew') {
processSilentRenew();
} else {
render(<App />, document.getElementById('root'));
}
Please note that /silent-renew request performance can be potentially negatively impacted by large files that loaded along with the application. Some thoughts on it in the comment.
I got it to work by simply adding a route instead of using a separat endpoint. My setup is a create-react-app with redux, redux-oidc & react-router.
I configured the UserManager to use
{
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ""}/silent_renew`
}
I added this route in my react-router:
<Route exact={true} path={"/silent_renew"} component={SilentRenewComponent} />
The SilentRenewComponnent is a simple function component which calls the redux-oidc function to process the redirect.
import React from "react";
import { processSilentRenew } from "redux-oidc";
export const SilentRenewComponent = () => {
processSilentRenew();
return(
<div>SilentRenewComponent</div>
);
};
As previously said, changing the webpack configuration of create-react-app would defies what the purpose of this project is, providing a way to do React with zero-configuration.
I'm afraid the solution would be to eject your app, which is irreversible.
After doing so, create the silent_renew directory at the root of your project with the index.js and index.html file, create a store for redux as seen here (you probably don't need a lot of this stuff, like sagas, the router and logger middleware, just take the loadUser logic and store creation), import the store in the src/index.js file and create a redux Provider, like this.
Then, you can modify config/webpack.config.dev.js and follow what we can see at the redux-oihc-example webpack conf. Add HtmlWebpackPlugin and CommonsChunkPlugin for silentRenew, and the additional entry point.
What's a bit upsetting about CRA is that their webpack configuration for dev and prod are totally separated and do not extend a shared one. You'll have to do this operation in both prod and dev configs, or extend one other conf file to prevent redundancy, like this for example.
I would also advice you to use another simple scaffold, CRA is good when you don't have anything special to do unlike what you want (and probably more in the future). Ejecting will create a lot of files and code that you don't even need in your own codebase. A friend and I made a minimalist one, but I'm sure there is plenty of better alternatives.
You cannot add a custom webpack loader if you haven't ejected:
We don't intend to provide Webpack specific overrides because it will be very fragile. People will start depending on specific loaders and plugins and we won't be able to improve the overall experience.
source: https://github.com/facebookincubator/create-react-app/issues/99#issuecomment-234657710
If you want to add a new specific entry file you first need to yarn eject then edit config/webpack.config.dev.js:34 and config/webpack.config.prod.js:55.
The same is valid for adding new webpack loaders.
I am really confused with the react-webpack stack. The more I read online, the more confused I get.
I am simply trying to call an API and get data into my component such as this:
'use strict';
import React from 'react';
require('styles//ListCard.scss');
class ListCardComponent extends React.Component {
render() {
return (
<div className="list-card">
<div className="list-card-details">
<p>{this.props.title}</p>
<p>{this.props.content}</p>
</div>
</div>
);
}
}
ListCardComponent.displayName = 'ListCardComponent';
export default ListCardComponent;
I currently have a backend localhost:3000 that I can call for JSON data.
My react app is running on localhost:8000. How do I connect properly connect these two to work together?
Actually there is three ways:
Use json-p
Allow CORS on the server
Configure your backend to serve static and dynamic content
Webpack is a build tool that lets you process assets (source files, images, stylesheets) into another form. It's basically an extremely flexible compile step that lets you do more powerful tasks than were possible with Grunt or Gulp. It has nothing to do with serving your code or backend.
If you have data on localhost:3000 and a React app on localhost:8000, you need to use CORS.