Remove Unused JS/CSS bundles Code Splitting Webpack - javascript

I hope I can keep this brief.
The basic jist of my issue stems from SASS imports.
I have an App Component that handles all the routing.
In the imports is a Login Component. In this component is an a .scss import
import './Login.scss';
In this file is imports of various other .scss files.
Another route in my App component is ForgotPassword component.
Similar to Login it has a .scss import of its own.
Now, the issue is, if a user loads the Login component, it means that all the css and imports are stored in a css file and that carries over to every other component in my project.
Let's say one of the .scss imports was something like Card which styles .Card classes. I have a variable
$Card-padding: 15px !default;
In ForgetPassword I also have that import. First issue is that I now have two imports of Card and if I try to override the $Card-padding variable it will also override on Login.
I am currently using react-loadable in my App component where I handle my routes.
import React from "react";
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Loadable from 'react-loadable';
import './App.scss';
const Login = Loadable({
loader: () => import('../../login/Login'),
loading() {
return null;
}
});
const ForgotPassword = Loadable({
loader: () => import('../../forgot-password/ForgotPassword'),
loading() {
return null;
}
});
const App = () => {
return (
<div className="App">
<Router>
<div className="App-container">
<Switch>
<Route path="/login" component={() => <Login />} exact />
<Route path="/forgotpassword" component={() => <ForgotPassword />} exact />
</Switch>
</div>
</Router>
</div>
);
}
export default App;
My question really is, can I remove unused CSS with React and Webpack, or is there a better method of css containment so that it cannot be accessed outside of the component?
I feel like this is something that lots of people do, but I cannot seem to find out how to do this. There may be a term that I have missed which would lead be to the correct resources. But I am lost. I'm not a huge fan of css in js, a lot of the styling comes from another project that is similar so is much simpler to copy it over other than having to write css in js for each class and permutation.
Every question on this on here does not have an accepted answer, so I'm throwing a Hail Mary in the hopes that someone has a React/Webpack solution to this.
I had tried ExtractTextPlugin in webpack, but it never seemed to spit out the Login.scss, so I may have configured it incorrectly. I am currently using MiniCssExtractPlugin to compile my scss files.

if I try to override the $Card-padding variable it will also override
on Login.
I don't believe you should be selectively overriding sass variables.
To override a css property, why not add specific css class for that component:
// global styles
.card {
padding: $Card-padding;
}
login.scss:
$mynewpadding: 100px;
.card.login {
// override the default padding here
padding: $mynewpadding;
}
forgottenpassword.scss:
$myothernewpadding: 30px;
.card.forgottenpassword {
// overrider the default padding here
padding: $myothernewpadding;
}
imho the above would be much clearer and easier to maintain..

Related

How to avoid inheritance of .css in React

I have a react application. The App.js fragment is as follow:
import ServiceManual from './components/pages/ServiceManual'
import './App.css';
const App = () => {
return (
<>
<Router>
<Switch>
<Route path='/ServiceManual' exact component={ServiceManual} />
<Route path='/' exact component={Home} />
</Switch>
</Router>
</>
)
};
The ServiceManual component contains it's own ServiceManual.module.css
import React from 'react';
import { Container } from 'react-bootstrap';
import '../App.css';
import './ServiceManual.module.css';
export default function ServiceManual() {
return <Container fluid className="mainPage">
....
Now, my problem is that some definitions contained into the ServiceManual.module.css influence the content of my Home page.
It looks like the import of the component in the App.js build an enormous container where all the imported definition will finish.
I found on the internet the suggestion to name xxx.module.css instead of xxx.css a .css file that has to be used just in the module where it is imported. But this is not true.
When I open the browser debugger on my home page I see some .css definitions (defined in xxx.module.css) that shouldn't be there and that change the way my homepage is displayed.
Has anyone a hint, please?
The truth is, all CSS files are included in the page as long as they are imported, no matter where.
If you need different styles for different elements, give them unique class names and handle them by their class names in their various CSS scripts.

Is this the correct way of using React.Suspense?

I have several questions about React.Suspense.
Here is some example code for reference:
// components/ComponentA/index.js
import React from "react";
const style = {
display: "grid",
placeItems: "center"
};
export default function ComponentA() {
return <div style={style}>I am componentA</div>;
}
// components/ComponentB/index.js
import React from "react";
const style = {
display: "grid",
placeItems: "center"
};
export default function ComponentB() {
return <div style={style}>I am componentB</div>;
}
// App.js
import React from "react";
import "./styles.css";
const ComponentA = React.lazy(() => import("./components/ComponentA"));
const ComponentB = React.lazy(() => import("./components/ComponentB"));
export default function App() {
const [state, toggleState] = React.useReducer((state) => !state, false);
return (
<div className="App">
<button onClick={toggleState}>click to show</button>
<React.Suspense fallback={<div>Loading...</div>}>
{state ? <ComponentA /> : <ComponentB />}
</React.Suspense>
</div>
);
}
When running this code, I've noticed several things right away.
The first thing I noticed is that when the app loads for the first time, there is a split moment where you can see loading... before I am componentB is rendered.
The second thing I noticed is that if you click the button, you can, again, for a split moment see loading... before I am componentA is rendered on the screen.
I assume this is the expected result with dynamic imports.
The final thing that I noticed is that continuing to toggle the button, I never see loading... again. I assume that this is because the components have already been imported and used by the client. Which is, I also assume, expected behavior.
My three questions here are,
am I using React's dynamic imports correctly (or should I use import elsewhere) and,
when should React.Suspense and dynamic imports be used
If the components are relatively simple, do I need to even consider lazy loading components?
Sandbox for reference
For the First question -
Yes, you are using the lazy() function to import components Correctly.
For the second question -
From the React docs,
Suspense lets components “wait” for something before rendering.
It is used to improve your site's performance with regards to its First Contentful Paint and Loading times on slow or bad networks, as well as for asynchronous tasks like data fetching where your component depends on the data returned so you show a Loading.. message to the user as a Fallback.
For example, a component which I have written and used-
import React, { Component, lazy, Suspense } from "react";
import { Route, Switch } from "react-router-dom";
import Error404 from "./Error404";
import LoadingSpinner from "./LoadingSpinner";
const Dashboard = lazy(() => import("./Dashboard"));
const Employees = lazy(() => import("./EmployeesPage"));
class Container extends Component {
//handles paths '/' and '/employees' along with any unknown paths
render() {
return (
<div id="container">
<div id="background">
</div>
<div id="content">
<Switch>
<Route exact path="/">
<Suspense fallback={<LoadingSpinner />}>
<Dashboard />
</Suspense>
</Route>
<Route path="/employees">
<Suspense fallback={<LoadingSpinner />}>
<Employees />
</Suspense>
</Route>
<Route component={Error404} />
</Switch>
</div>
</div>
);
}
}
export default Container;
Here, I am lazy loading my two components Dashboard and Employees which in themselves contain many components and are relatively complex.
Lazy loading them prevents the connection to be clogged while loading the site for the first time.
These components are now loaded only when the user navigates to the specified URL by some NavLinks.
Since it's safe to assume that the user will spend some time in the Dashboard before going to the Employees page(at least in my app), this approach works fine.
For the third question -
If you have components that you are sure will be needed when the site loads, it's better to not lazy load them to keep the User Experience good.
Small components (by their bundle size) don't need lazy loading, only when you think that many components are gonna be used only rarely, then you can lazy load them in one bundle.
Lastly -
You can open up the Network tab on the developer tools of a browser and see the bundles loading in as the user requests for them (React handles that in lazy()). The Bundles are labelled like this- Network Tab screenshot
Hope you find the answer useful and any recommendations for the post are appreciated✌

Is it possible to unload dynamic css imports in react?

I have two files which i load with react.lazy and suspense:
import React, { Suspense, lazy } from "react";
import { Route, Redirect } from 'react-router-dom';
const MainLayout = lazy(() => import('Components/Layout/MainLayout'))
export const PrivateRoute = () => (
<Route render={() => {
return (
localStorage.getItem('user') != null// validation that it is a valid user
?
<Suspense fallback={<div>Loading...</div>}>
<MainLayout/>
</Suspense>
: <Redirect to={{ pathname: '/login'}} />)
}} />
)
Second:
import React, { Suspense, lazy } from "react";
const DefaultLayout = lazy(() => import('Components/Layout/DefaultLayout'))
export const PublicRoute = () => (
<Suspense fallback={<div>Loading...</div>}>
<DefaultLayout/>
</Suspense>
)
the /login path is refrencing a component (login) that is inside of the
DefaultLayout component.
Scenario:
When the user is not logged in I load the DefaultLayout component which in turn contains my login component which imports cssFile1.css.
When the user enters the credentials i forward them to a path that is contained in my PrivateRoute where in turn i have cssFile2.css
The problem here is that cssFile1.css was loaded when i was using the loginpage but when the user logs in i want to unload cssFile1.css, is this possible and if yes then how?
I found a (sort of) reasonable way to do this in React. In short, you can lazy-load React components that contain the import './style.css', and when it loads, you can capture the imported StyleSheet to toggle its StyleSheet.disabled property later.
Here's my original solution to this problem.
Here's a Gist.
Ok, This might not be the most optimum approach, but can't the whole CSS inside the cssFile1.cssbe scoped? As in all the rules are written targetting elements if they are inside a certain container with a class say 'cssFile1'.
Likewise the 2nd CSS file will target all the elements only if they are located inside a container with the class 'cssFile2'.
now all you have to do to "unload/switch" the css is changing the main container class and the respective CSS rules will apply.
One last tip is, if you are using SASS / LESS its just a matter of enclosing the rules inside a container and all the rules will be scoped upon compilation.
If I understand right, then no, it's impossible, because of Webpack imported css when React compiled, anyway, you can try to css in js libs, such a Aphrodite.

React routes not automatically updating when nested under 'smart' Redux container components

I'm trying to create an Electron app using React, React-router and Redux. What I'm finding is that my routing logic works absolutely fine when I'm nesting the switch/route logic under a purely presentational component (Page), but that I'm forced to refresh the page to see navigational changes if nested under a 'smart' container component.
Near the top of my React component hierarchy (right beneath HashRouter) I have a Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
Here, DetailPane and SideBar are both container components wrapped around presentational components of the same name.
At startup (and during hot reloads), I create my React hierarchy using this function:
export default () => (
<Router>
<Page>
<Switch>
<Route exact path='/txDefinitions/:definitionName/:fieldName' component={FieldPage}/>
<Route exact path='/txDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/rxDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/'/>
<Route component={Route404}/>
</Switch>
</Page>
</Router>
This means that <Switch>...</Switch> gets nested underneath <DetailPane>.
If I try to navigate around my app (clicking links in the side bar), I won't actually see the detail pane render the new component until I force-reload the Electron app.
However, I find that routing works as expected if I omit DetailPane from Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
{props.children}
</div>
);
}
Here is my React hierarchy without DetailPane (works fine):
Here is my React hierarchy with DetailPane (does not work right):
(Apologies for using images but I'm not sure if there's a way to copy from React devtools into clipboard - appears larger if opened in a new tab).
As I was writing this question, I realised this wouldn't be a huge issue for me because earlier refactoring had made the 'smart' version of DetailPane apparently obsolete. Using the purely presentational version of DetailPane
instead resolves this issue:
import * as React from 'react';
//import {DetailPane} from '../../containers'; // Smart/Redux
import {DetailPane} from '../../components'; // Dumb/presentational
import {SideBar} from '../../containers/';
const styles = require('./Page.scss');
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
However, I'm still curious why this doesn't work for the container component version. For reference, this is the container component version of DetailPane:
import {connect} from 'react-redux';
import {DetailPane} from '../../components';
// TODO: delete this container?
function mapStateToProps (state): {} {
return {};
}
function mapDispatchToProps (dispatch) {
// TODO.
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(DetailPane);
The connect HOC implements shouldComponentUpdate logic so if the props don't change, the component doesn't update.
To prevent this from occurring, and have the component always render, you can override the pure option in the connect call.
export default connect(mapStateToProps, mapDispatchToProps, undefined, { pure: false })(DetailPane);
See the react-redux API docs for more details.

How to create multiple page app using react

I have created a single page web app using react js. I have used webpack to create bundle of all components. But now I want to create many other pages. Most of pages are API call related. i.e. in the index.html, I have displayed content from API. I want to insert content in another page parsing data from API. Webpack compresses everything of react in a file which is bundle.js. However, the configuration of webpack is as follow:
const webpack = require('webpack');
var config = {
entry: './main.js',
output: {
path:'./',
filename: 'dist/bundle.js',
},
devServer: {
inline: true,
port: 3000
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}
module.exports = config;
Now, I am confused what kind of configuration of webpack will be for other page or what is the correct way to build multi-pages app using react.js
Preface
This answer uses the dynamic routing approach embraced in react-router v4+. Other answers may reference the previously-used "static routing" approach that has been abandoned by react-router.
Solution
react-router is a great solution. You create your pages as Components and the router swaps out the pages according to the current URL. In other words, it replaces your original page with your new page dynamically instead of asking the server for a new page.
For web apps I recommend you read these two things first:
Full Tutorial
The react-router docs; it will help you get a better understanding of how Router works.
Summary of the general approach:
1 - Add react-router-dom to your project:
Yarn
yarn add react-router-dom
or NPM
npm install react-router-dom
2 - Update your index.js file to something like:
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render((
<BrowserRouter>
<App /> {/* The various pages will be displayed by the `Main` component. */}
</BrowserRouter>
), document.getElementById('root')
);
3 - Create a Main component that will show your pages according to the current URL:
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from '../pages/Home';
import Signup from '../pages/Signup';
const Main = () => {
return (
<Switch> {/* The Switch decides which component to show based on the current URL.*/}
<Route exact path='/' component={Home}></Route>
<Route exact path='/signup' component={Signup}></Route>
</Switch>
);
}
export default Main;
4 - Add the Main component inside of the App.js file:
function App() {
return (
<div className="App">
<Navbar />
<Main />
</div>
);
}
5 - Add Links to your pages.
(You must use Link from react-router-dom instead of just a plain old <a> in order for the router to work properly.)
import { Link } from "react-router-dom";
...
<Link to="/signup">
<button variant="outlined">
Sign up
</button>
</Link>
(Make sure to install react-router using npm!)
To use react-router, you do the following:
Create a file with routes defined using Route, IndexRoute components
Inject the Router (with 'r'!) component as the top-level component for your app, passing the routes defined in the routes file and a type of history (hashHistory, browserHistory)
Add {this.props.children} to make sure new pages will be rendered there
Use the Link component to change pages
Step 1
routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
/**
* Import all page components here
*/
import App from './components/App';
import MainPage from './components/MainPage';
import SomePage from './components/SomePage';
import SomeOtherPage from './components/SomeOtherPage';
/**
* All routes go here.
* Don't forget to import the components above after adding new route.
*/
export default (
<Route path="/" component={App}>
<IndexRoute component={MainPage} />
<Route path="/some/where" component={SomePage} />
<Route path="/some/otherpage" component={SomeOtherPage} />
</Route>
);
Step 2 entry point (where you do your DOM injection)
// You can choose your kind of history here (e.g. browserHistory)
import { Router, hashHistory as history } from 'react-router';
// Your routes.js file
import routes from './routes';
ReactDOM.render(
<Router routes={routes} history={history} />,
document.getElementById('your-app')
);
Step 3 The App component (props.children)
In the render for your App component, add {this.props.children}:
render() {
return (
<div>
<header>
This is my website!
</header>
<main>
{this.props.children}
</main>
<footer>
Your copyright message
</footer>
</div>
);
}
Step 4 Use Link for navigation
Anywhere in your component render function's return JSX value, use the Link component:
import { Link } from 'react-router';
(...)
<Link to="/some/where">Click me</Link>
This is a broad question and there are multiple ways you can achieve this. In my experience, I've seen a lot of single page applications having an entry point file such as index.js. This file would be responsible for 'bootstrapping' the application and will be your entry point for webpack.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Application from './components/Application';
const root = document.getElementById('someElementIdHere');
ReactDOM.render(
<Application />,
root,
);
Your <Application /> component would contain the next pieces of your app. You've stated you want different pages and that leads me to believe you're using some sort of routing. That could be included into this component along with any libraries that need to be invoked on application start. react-router, redux, redux-saga, react-devtools come to mind. This way, you'll only need to add a single entry point into your webpack configuration and everything will trickle down in a sense.
When you've setup a router, you'll have options to set a component to a specific matched route. If you had a URL of /about, you should create the route in whatever routing package you're using and create a component of About.js with whatever information you need.
Following on from the answer above by #lucas-andrade: for react-router versions >= 6, Switch no longer exists (see https://github.com/remix-run/react-router/issues/8439). The content of step 3 becomes:
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import Home from '../pages/Home';
import Signup from '../pages/Signup';
const Main = () => {
return (
<Routes>
<Route path='/' element={Home}></Route>
<Route path='/signup' element={Signup}></Route>
</Routes>
);
}
export default Main;
The second part of your question is answered well. Here is the answer for the first part: How to output multiple files with webpack:
{
entry: {
outputone: './source/fileone.jsx',
outputtwo: './source/filetwo.jsx'
},
output: {
path: path.resolve(__dirname, './wwwroot/js/dist'),
filename: '[name].js'
},
}
This will generate 2 files: outputone.js and outputtwo.js in the target folder.
One can also use gatsby a react framework dedicated to static multipage apps.

Categories

Resources