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.
Related
(I have a Vue/Vite application but I think this question is generic to any setup.)
In my app, I have some code that I want to generalize into a separate npm library package so it can be used in multiple apps. This requires placing a file in the public folder of the app but I'm not sure what the best practice way of doing that is.
(This file is a service worker, but I think that is irrelevant to the question - this would be the same for any file that needs to have a public URL.)
I'd like the use of this library to be as clean as possible. That is, I'd like to not tell the client to "copy this file into your public folder".
I think that copy step needs to happen somewhere, though, unless there is a way to tell Vite to add a file to what it packages.
The copy step could be when running npm install, I guess. It would be nice if that could also add a line to .gitignore, too, if needed. I think this is complicated because the actual location is different whether the app is Vue, Nuxt, or some other framework.
I don't think the copy step could be when import mylib from "mylib" because that happens after the app is already packaged.
So, I think my two options are:
Tell Vite to put a particular file, unaltered, in the public folder.
Run a post install script when npm install runs. (That would have to work whether the user is on Windows, mac, or linux.)
Some other magic?
I don't know how to implement any of those, so a pointer to some documentation or a good term to search for would be appreciated, too.
Or is this just a fool's errand and I should tell the user to just copy the file to their public folder manually?
If you are using Vite, you can create an npm library with a Vite plugin in it. The documentation for Vite plugins is here, and the API that Vite's plugin API stems from is here. The code for a plugin to add to Vite's output and dev server would be
import { writeFile } from "fs";
import { resolve } from "path";
let url = "service-worker.js"
let fileContent = "file contents"
let outPath;
const myPlugin = () => ({
name: "add-service-worker",
configResolved(resolvedConfig) {
outPath = resolve(resolvedConfig.root, resolvedConfig.build.outDir, url);
},
configureServer(server) {
server.middlewares.use(`/${url}`, (req, res, next) => {
res.writeHead(200, { "Content-Type": "text/javascript" });
res.write(fileContent);
res.end();
});
},
closeBundle() {
writeFile(outPath, fileContent, { flag: "w" }, (error) => {
if (error) {
throw error;
}
console.log(`Generated file to ${outPath}`);
});
},
});
export default myPlugin;
The same could most likely be applied to a rollup or webpack plugin. However, the real easiest way would be to make this a library, and import it using
import service-worker from "service-worker"
I'm trying to build my Next.js project but it keeps giving me this error in the terminal:
Error: Build optimization failed: found page without a React Component as default export in
pages/components/context/Context
That's the React context API file, there isn't supposed to be any default export there. Is this a bug or what?
You should move your components outside the pages folder. pages/ should only be used for page components as Next.js routing is based on its structure.
Next.js has a file-system based router built on the concept of pages.
When a file is added to the pages directory it's automatically available as a route.
By default, Next.js assumes anything under the pages folder is a page component and will try to build each file as a page.
Even though the above is the default behaviour, you can configure your Next.js app to include non-page files in the pages directory.
To do so, you can modify the pageExtensions entry in the next.config.js file as shown below. Then rename your page components to have a file extension that includes .page (_document.page.js, _app.page.js, index.page.js, etc).
module.exports = {
pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js']
}
With this configuration, Next.js will ignore any file that doesn't contain .page for the purpose of building pages/API routes and routing.
In my case, I had an empty file index.js in a folder. Using Nextjs Default Router
It seems to be not declared default export keyword in context component.
Try it as follow:
const Context = ()=>{
...
}
export default Context
I had the same error.
If you comment out all other code but leave this NextJS won't get mad at you:
export default function Home1() {
return <>{/* nothing */}</>;
}
I like to keep older index files and components locally and on github so this is a nice hack. I just copy all of the existing code add it to a new file and then add 1 to it for example:
index1.js
You can also leave a comment to kind of bring you and other devs up to speed as to why you did this for example:
//good for history of index implementation and associated syntax logic
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.
I have four or five small web apps that I build and maintain for a small group at work. For the most part, they have the same layout and file structure. A lot of the files are the exact same across the apps. Here's an example index.js file that's the same in every apps' src/ directory.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
if (process.env.NODE_ENV !== 'production') {
console.log(`Environment: ${process.env.NODE_ENV}`);
}
const root = document.querySelector('#root');
ReactDOM.render(<App />, root);
Copying and pasting works fine until I need to update something. A few months ago I started changing my React class components over to functional components with hooks. The only problem is I have to make that same change for every version of that file in every site.
I tried using bit and I thought about creating a local NPM package, but those aren't ideal options. I'd have to import each file and that doesn't really work the way I need. (I guess I could import the very first index.js file, but it doesn't seem like an ideal solution.)
I want to be able to edit a file in app A and next time I go back to work on app B be able to see the changes made to the file in A and pull them into B. That way I can make each of the shared files/components dynamic enough to work for all my apps. If the changes made on the file in app A don't work for app B, I can rework the file in app B and pull the changed next time I work on app A.
I hope this makes sense. Thanks in advance for any help you can offer!
I believe git submodules could be a valid option for you, especially for push/pull changes part.
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