React hot reload not working with webpack 4 - javascript

React hot reload is not working.
In webpack entry, I have following code.
entry: {
home: [
'webpack-dev-server/client?http://0.0.0.0:8000',
'webpack/hot/dev-server', // add "only-dev-server" prevents reload on syntax errors
path.join(__dirname, 'app/home-styles.js')
],
main: [
'babel-polyfill',
path.join(__dirname, 'app/styles.js'),
path.join(__dirname, 'app/vendor.js'),
path.join(__dirname, 'app/index.js')
]
},
and in dev-server I have hot = true;
and i have below code for wrapping Provider in hot
class Root extends Component {
render() {
const { store, history } = this.props;
return (
<Provider store={store}>
<Router history={history} routes={routes(store)}/>
</Provider>
);
}
}
export default hot(module)(Root);
This is result in console, but nothing happens when there is some change. So basically it appears as if hot reload is working but.. no changes reflects. What am I missing here. ?
I am using react 16.2.* and react-hot-loader #4.0.* an webpack #4.5.*

try updating your dev server config like so
const devServerOptions = {
contentBase: './dist',
hot: true,
host: 'beta.whenidev.net',
https: true,
};
// if you're adding a dev server entry do it like this
WebpackDevServer.addDevServerEntrypoints(config, devServerOptions);

Related

loading css in asynchronously imported javascript using webpack with MiniCssExtractPlugin

i'm working on a webapp that loads it's different pages asynchronously via dynamic imports e.g.:
import {Component, h} from "preact";
import AsyncRoute from "preact-async-route";
import {Layout} from "components/layout";
import {PageLoading} from "components/page-loading";
export class Page extends Component {
render() {
return (
<Layout>
<AsyncRoute
key="doorstation"
path={"/doorstation"}
getComponent={this.getSamplePage}
loading={() => <PageLoading />}
/>
</Layout>
);
}
async getSamplePage(){
const { init, Page } = await import("modules/sample");
init();
return Page;
}
}
and in the actual imported file i do
// modules/sample/page/index.tsx
import {Component, h} from "preact";
import styles from "./styles.css";
export class Page extends Component {... components logic}
// modules/sample/index.tsx
export const init = () => // some initialization logic
export { Page } from "./Page"
Each of those pages has their own css that gets imported on their respective file. What i'm stuck now with is that it puts the resulting css in it's own chunk but when the browser build tries to import said css it just fails with an error like:
GET http://<my-ip>/341.()=%3E%225028a51a47a787d4cc85%22.css net::ERR_ABORTED 404 (Not Found)
Error: Loading CSS chunk 341 failed.
(/341.()=>"5028a51a47a787d4cc85".css)
at o.<computed>.o.<computed>.<computed>.e.push.o.<computed>.d.onerror
on inspection of the import on the console the href of resulting stylesheet tag is actually set to:
link.href = "http://<my-ip>/341.()=%3E%225028a51a47a787d4cc85%22.css"
what i expected was that webpack would resolve those paths on my dynamically imported modules but apparently this isn't the case.
for now my config looks like this:
module: {
rules: [
test: /\.css$/,
use: [
{
loader: args.mode === "production" ? MiniCssExtractPlugin.loader : "style-loader",
},
{
loader: "css-loader",
options: {
sourceMap: args.mode !== "production",
importLoaders: 1,
modules: {
localIdentName:
args.mode === "production"
? "c-[hash:base64:8]"
: "[name]-[local]-[hash:base64:4]",
},
},
},
{
loader: "postcss-loader",
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[hash:8].css",
chunkFilename: "[name].[hash:8].css",
}),
]
by just using the style-loader everything works just fine but this won't give me actual css chunks. I'm not entirely sure where or what to change so webpack will require the actual css chunk files instead of resolving the path with that... what looks like an function expression. I was already looking up the issue but so far it didn't really solve my issue since it usually revolved around undefined values or something.
Did i miss something in this config? is someone able to help?
Thank you in advance.

Vue js Prefetch components

I recently learnt about lazy loading components and started using it. Now I am trying to prefetch the lazy loaded components as well as vue-router routes. But using the chrome devtools I found that lazy loaded chunks are only loaded when we actually navigate to the lazy loaded route (in case of a vue-router route) or when the v-if evaluates to true and the component is rendered (in case of a lazy loaded component).
I have also tried using the webpackPrefetch: true magic string in the router as well as component import statement but doing that does not seem to make any difference.
Project structure:
Master-Detail layout
router config:
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
var routes = [
{
path: "/DetailPage",
component: () => import(/* webpackChunkName: "Detail-chunk" */ "AppModules/views/MyModuleName/DetailPage.vue")
},
{
path: "/MasterPage",
component: () => import("AppModules/views/MyModuleName/MasterPage.vue")
}
]
export const router = new Router({
routes: routes,
stringifyQuery(query) {
// encrypt query string here
}
});
export default router;
Master view:
<template>
<div #click="navigate">
Some text
</div>
</template>
<script>
export default {
name: "MasterPage",
methods: {
navigate() {
this.$router.push({
path: "/DetailPage",
query: {},
});
},
},
};
</script>
Details page:
<template>
<div>
<my-component v-if="showComponent" />
<div #click="showComponent = true">Show Component</div>
</div>
</template>
<script>
const MyComponent = () => import(/* webpackChunkName: "MyComponent-chunk" */ "AppCore/components/AppElements/Helpers/MyComponent");
export default {
name: "DetailPage",
components: {
MyComponent,
},
data() {
return {
showComponent: false
}
}
};
</script>
vue.js.config file:
const path = require("path");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = {
publicPath: "some-url",
outputDir: "./some/path",
chainWebpack: webapckConfig => {
webapckConfig.plugin("html").tap(() => {
return [
{
inject: true,
filename: "index.html",
template: "./public/index.html"
}
];
});
},
productionSourceMap: true,
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: "server",
generateStatsFile: false,
statsOptions: {
excludeModules: "node_modules"
}
})
],
output: {
filename: "some file name",
libraryTarget: "window"
},
module: {
rules: [
{
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: "url-loader",
options: {
limit: 50000,
fallback: "file-loader",
outputPath: "/assets/fonts",
name: "[name].[ext]?hash=[hash]"
}
}
]
}
]
},
resolve: {
alias: {
vue$: process.env.NODE_ENV == 'production' ? 'vue/dist/vue.min.js' : 'vue/dist/vue.js',
AppCore: path.resolve(__dirname, "..", "..", "AppCoreLite"),
AppModules: path.resolve(__dirname, "..", "..", "AppModulesLite")
}
}
}
};
Both the async route and component do get split into separate chunks but these chunks are not prefetched.
When I navigate to the master view, I dont see Detail-chunk.[hash].js in the network tab. It gets requested only when the navigate method in the master page is executed (this the correct lazy load behaviour without prefetch).
Now when I am on the details page, MyComponent-chunk.[hash].js is only requested when the showComponent becomes true (on click of a button)
I've also read at a few places that vue-cli v3 does has prefetch functionality enabled by default and webpack magic string is not needed. I also tried that by removing the webpackPrefetch comment but it made no difference.
I did vue-cli-service inspect and found that prefetch plugin is indeed present in the webpack config:
/* config.plugin('preload') */
new PreloadPlugin(
{
rel: 'preload',
include: 'initial',
fileBlacklist: [
/\.map$/,
/hot-update\.js$/
]
}
),
/* config.plugin('prefetch') */
new PreloadPlugin(
{
rel: 'prefetch',
include: 'asyncChunks'
}
),
UPDATE: I tried removing the prefetch webpack plugin using config.plugins.delete('prefetch'); and then using the webpack magic comment: /* webpackPrefetch: true */ but it made no difference.
How do I implement prefetch functionality?
I solved this by creating a simple prefetch component that loads after a custom amount of time.
Prefetch.vue
<script>
import LazyComp1 from "./LazyComp1.vue";
import LazyComp2 from "./LazyComp2.vue";
export default {
components:{
LazyComp1,
LazyComp2,
}
}
</script>
App.vue
<template>
<Prefech v-if="loadPrefetch"></Prefech>
</template>
<script>
export default {
components: {
Prefech: () => import("./Prefetch");
},
data() {
return {
loadPrefetch: false
}
},
mounted() {
setTimeout(() => {
this.loadPrefetch = true;
}, 1000);
}
}
</script>
Lazy loaded components are meant to be loaded only when user clicks the route. If you want to load component before it, just don't use lazy loading.
vue-router will load components to memory and swap the content of the tag dynamically even if you will use normally loaded component.
You need to implement vue-router-prefetch package for your need. Here is a working demo.
Note: From the working demo, you can notice from console.log that only page 2 is prefetched by the QuickLink component imported from vue-router-prefetch
Code :
import Vue from "vue";
import Router from "vue-router";
import RoutePrefetch from "vue-router-prefetch";
Vue.use(Router);
Vue.use(RoutePrefetch, {
componentName: "QuickLink"
});
const SiteNav = {
template: `<div>
<ul>
<li>
<router-link to="/page/1">page 1</router-link>
</li>
<li>
<quick-link to="/page/2">page 2</quick-link>
</li>
<li>
<router-link to="/page/3">page 3</router-link>
</li>
</ul>
</div>`
};
const createPage = (id) => async() => {
console.log(`fetching page ${id}`);
return {
template: `<div>
<h1>page {id}</h1>
<SiteNav />
</div>`,
components: {
SiteNav
}
};
};
const routers = new Router({
mode: "history",
routes: [{
path: "/",
component: {
template: `<div>
<h1>hi</h1>
<SiteNav />
</div>`,
components: {
SiteNav
}
}
}]
});
for (let i = 1; i <= 3; i++) {
routers.addRoutes([{
path: `/page/${i + 1}`,
component: createPage(i + 1)
}]);
}
export default routers;
I'm working on a mobile app. and wanted to load some components dynamically while showing the splash screen.
#Thomas's answer is a good solution (a Prefetch component), but it doesn't load the component in the shadow dom, and Doesn't pass Vetur validation (each component must have its template)
Here's my code:
main.vue
<template>
<loader />
</template>
<script>
import Loader from './Loader'
const Prefetch = () => import('./Prefetch')
export default {
name: 'Main',
components: {
Loader,
Prefetch
}
}
</script>
Prevetch.vue
<template>
<div id="prefetch">
<lazy-comp-a />
<lazy-comp-b />
</div>
</template>
<script>
import Vue from 'vue'
import LazyCompA from './LazyCompA'
import LazyCompB from './LazyCompB'
Vue.use(LazyCompA)
Vue.use(LazyCompB)
export default {
components: {
LazyCompA,
LazyCompB
}
}
</script>
<style lang="scss" scoped>
#prefetch {
display: none !important;
}
</style>
The loader component is loaded & rendered, then the Prefetch component can load anything dynamically.
since vue-router-prefetch didn't work for me i ended up doing it manually.
Vue 3 Example - all routes are iterated on page load and async components are loaded
const router = createRouter({
history: createWebHistory(),
routes: [{
path: '/',
component: HomeView
}, {
path: '/about',
component: () => import('./views/AboutView.vue')
}]
});
async function preloadAsyncRoutes() {
// iterate all routes and if the component is async - prefetch it!
for (const route of router.getRoutes()) {
if (!route.components) continue;
// most routes have just a "default" component unless named views are used - iterate all entries just in case
for (const componentOrImporter of Object.values(route.components)) {
if (typeof componentOrImporter === 'function') {
try {
// prefetch the component and wait until it finishes before moving to the next one
await componentOrImporter();
} catch (err) {
// ignore failing requests
}
}
}
}
}
window.addEventListener('load', preloadAsyncRoutes);

SCSS styles imported to components in app.js are being applied to all components in app.js

I am using react-router and am importing styles and components into my app.js. The issue is that any styles that applied in say Login then are somehow also applied to Home, Item, and Bag. Looking at source in chrome it shows all of the css files being loaded whenever I go to any of the pages. I am using webpack and I think this might be the issue but I have no idea what in it is causing this. Any help would be greatly appreciated!
import '../../styles/styles.scss';
import '../../styles/generalStyle.scss';
import 'bootstrap/dist/js/bootstrap';
import {
Route,
Switch,
} from 'react-router-dom';
import NotFound from './NotFound';
import Login from '../Login/Login';
import Home from '../Home/Home';
import Item from '../Item/Item';
import Bag from '../Bag/Bag';
export default function App() {
return (
<>
<main>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/item/:name" component={Item} />
<Route path="/bag" component={Bag} />
<Route component={NotFound} />
</Switch>
</main>
</>
);
}```
In ReactJS the css is not component scope. The css is global scope. so if you add a css file in any component then it will apply to whole app.
If you want css to be component scope then you could use 'CSS Modules'
This project supports CSS Modules alongside regular stylesheets using the [name].module.css file naming convention. CSS Modules allows the scoping of CSS by automatically creating a unique classname of the format [filename]_[classname]__[hash].
you could read more about this here
You need css modules to scope the styles for a particular component. Because css styles are global in nature.
Quick question:
Are you using mini-css-extract-plugin in your webpack config? If so when you build the application via webpack, all the styles in js (imported scss files) are pulled in to one single file and injected into index.html page.
First you need to convert your scss files to css modules and do the below you get what you need.
This link should help you in converting the scss files to css modules.
Example:
{
test: /\.css$/i,
loader: 'css-loader',
options: {
modules: true, // this one make existing scss to css modules
},
},
Lets assume you have convert scss to css modules. Lets see an example.
Eg: page1.scss, page2.scss to styles.css
I would suggest to split styles according to your need. Lets see an example for your above code.
Eg: vendor.css (contains bootstrap and general styles) and main.css (contains component styles)
Example: webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
function recursiveIssuer(m) {
if (m.issuer) {
return recursiveIssuer(m.issuer);
} else if (m.name) {
return m.name;
} else {
return false;
}
}
module.exports = {
entry: {
vendor: path.resolve(__dirname, 'src/vendor'),
main: path.resolve(__dirname, 'src/main'),
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
name: 'foo',
test: (m, c, entry = 'vendor') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
main: {
name: 'bar',
test: (m, c, entry = 'main') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
},
},
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
Above file is an example taken directly from mini-css-extract-plugin plugin page.
Explanation:
Above config will create two files, one for vendor.css and one for main.css. Like this you can create n number of styles for your routes and use it accordingly.
Hope this help!

Dynamic Imports: Am I missing something?

I have a React project that uses Webpack as a bundler, and I'm splitting my bundle into two chunks -- the main codebase main.js, and the vendor bundle vendor.js.
After building these bundles, main.js ends up being 45kb and vendor.js is 651kb.
One specific vendor library is 225kb and seems to be the worst offendor in the vendor imports.
I am importing this library in a page component at the top of the file:
import React from 'react';
import { ModuleA, ModuleB } from 'heavyPackage'; // 225kb import
...
const Page = ({ setThing }) => {
...
};
To try and have this heavy import loaded in a separate bundle, I tried to instead import these modules using a dynamic import.
Inside the Page component, the modules weren't actually used until a particular function was called, so I tried to import the modules within that scope rather than at the top of the file:
import React from 'react';
...
const Page = ({ setThing }) => {
...
const handleSignIn = async () => {
const scopedPackage = await import('heavyPackage');
const { moduleA, moduleB } = scopedPackage;
// use moduleA & moduleB normally here
};
};
For some reason I figured Webpack would intelligently pick up on what I'm trying to do here and separate this heavy package into its own chunk that is downloaded only when needed, but the resulting bundles were the same -- a main.js that was 45kb and a vendor.js that was 651kb. Is my line of thinking here correct and possibly my Webpack configuration is off, or am I thinking of dynamic imports in the wrong way?
edit I have Webpack configured to split the bundle using splitChunks. Here is how I have this configured:
optimization: {
chunkIds: "named",
splitChunks: {
cacheGroups: {
commons: {
chunks: "initial",
maxInitialRequests: 5,
minChunks: 2,
minSize: 0,
},
vendor: {
chunks: "initial",
enforce: true,
name: "vendor",
priority: 10,
test: /node_modules/,
},
},
},
},
Update for React 18: The code below is no longer required to split chunks/dynamically load components. Instead, you can use React.lazy with Suspense, which achieves similar results (this only works for React components, therefore any node_module imports would need to be imported within this dynamically loaded component):
const ProfilePage = React.lazy(() => import('./ProfilePage')); // Lazy-loaded
<Suspense fallback={<Spinner />}>
<ProfilePage />
</Suspense>
#Ernesto's answer offers one way of code splitting by using react-loadable with the babel-dynamic-import plugin, however, if your Webpack version is v4+ (and has a custom Webpack config set to SplitChunks by all), then you'll only need to use magic comments and a custom React component.
From the docs:
By adding [magic] comments to the import, we can do things such as name our chunk or select different modes. For a full list of these magic comments see the code below followed by an explanation of what these comments do.
// Single target
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
'module'
);
// Multiple possible targets
import(
/* webpackInclude: /\.json$/ */
/* webpackExclude: /\.noimport\.json$/ */
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
`./locale/${language}`
);
Therefore, you can create a reusable LazyLoad component like so:
import React, { Component } from "react";
import PropTypes from "prop-types";
class LazyLoad extends Component {
state = {
Component: null,
err: "",
};
componentDidMount = () => this.importFile();
componentWillUnmount = () => (this.cancelImport = true);
cancelImport = false;
importFile = async () => {
try {
const { default: file } = await import(
/* webpackChunkName: "[request]" */
/* webpackMode: "lazy" */
`pages/${this.props.file}/index.js`
);
if (!this.cancelImport) this.setState({ Component: file });
} catch (err) {
if (!this.cancelImport) this.setState({ err: err.toString() });
console.error(err.toString());
}
};
render = () => {
const { Component, err } = this.state;
return Component ? (
<Component {...this.props} />
) : err ? (
<p style={{ color: "red" }}>{err}</p>
) : null;
};
}
LazyLoad.propTypes = {
file: PropTypes.string.isRequired,
};
export default file => props => <LazyLoad {...props} file={file} />;
Then in your routes, use LazyLoad and pass it the name of a file in your pages directory (eg pages/"Home"/index.js):
import React from "react";
import { Route, Switch } from "react-router-dom";
import LazyLoad from "../components/LazyLoad";
const Routes = () => (
<Switch>
<Route exact path="/" component={LazyLoad("Home")} />
<Route component={LazyLoad("NotFound")} />
</Switch>
);
export default Routes;
On that note, React.Lazy and React-Loadable are alternatives to having a custom Webpack config or Webpack versions that don't support dynamic imports.
A working demo can be found here. Follow installation instructions, then you can run yarn build to see routes being split by their name.
Oki then, look! you have yow webpack config with the splitChunks property, also you need to add a chunkFilename property in side of the output object from webpack.
If we take for example the one generated by CRA
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// TODO: remove this when upgrading to webpack 5
futureEmitAssets: true,
// THIS IS THE ONE I TALK ABOUT
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: paths.publicUrlOrPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
// Prevents conflicts when multiple webpack runtimes (from different apps)
// are used on the same page.
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
// this defaults to 'window', but by setting it to 'this' then
// module chunks which are built will work in web workers as well.
globalObject: 'this',
},
Once you have that on yow webpack. next thing is to install a npm i -D #babel/plugin-syntax-dynamic-import and add it to your babel.config.js
module.exports = api =>
...
return {
presets: [
.....
],
plugins: [
....
"#babel/plugin-syntax-dynamic-import",
....
]
}
then last thing npm install react-loadable
create a folder called: containers. in it place all the containers
inside index.js do some like:
The loadable object have two properties
export const List = Loadable({
loader: () => import(/* webpackChunkName: "lists" */ "./list-constainer"),
loading: Loading,
});
loader: component to dynamically import
loadinh: component to display until the dynamic component is loaded.
and for last on you Router set each loadable to a route.
...
import { Lists, List, User } from "../../containers";
...
export function App (): React.ReactElement {
return (
<Layout>
<BrowserRouter>
<SideNav>
<nav>SideNav</nav>
</SideNav>
<Main>
<Header>
<div>Header</div>
<div>son 2</div>
</Header>
<Switch>
<Route exact path={ROUTE_LISTS} component={Lists} />
<Route path={ROUTE_LISTS_ID_USERS} component={List} />
<Route path={ROUTE_LISTS_ID_USERS_ID} component={User} />
<Redirect from="*" to={ROUTE_LISTS} />
</Switch>
</Main>
</BrowserRouter>
</Layout>
);
}
so then when you bundle yow code we get some like:

How to remove imported css in reactjs

I have used the following code to import css
componentWillMount() {
import('./patient-summary.css');
}
How to remove imported css from react when component is not in use. When i go back to previous screen this css gets applied there. Any idea ?
UPDATE:: Webpack config
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public/dist')
},
module: {
rules: [
{
test: /\.js?$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: "file-loader"
}
,
{
test: /\.(png|jpeg|jpg|gif|svg)$/,
loader: "file-loader"
}
]
},
devServer: {
contentBase: path.resolve(__dirname, "public"),
historyApiFallback: true,
port: 3000,
watchOptions: {
// Delay the rebuild after the first change
aggregateTimeout: 300,
// Poll using interval (in ms, accepts boolean too)
poll: 1000,
},
},
plugins: [
// Ignore node_modules so CPU usage with poll
// watching drops significantly.
new webpack.WatchIgnorePlugin([
path.join(__dirname, "node_modules")
])
],
};
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 the main code, with more explanation below. Here's my Gist.
useDisableImportedStyles.tsx
import { useEffect } from 'react'
// global list of all the StyleSheets that are touched in useDisableImportedStyles
const switchableGlobalStyleSheets: StyleSheet[] = []
// just to clarify what createUseDisableImportedStyles() returns
type useDisableImportedStyles = () => void
export const createUseDisableImportedStyles = (
immediatelyUnloadStyle: boolean = true
// if true: immediately unloads the StyleSheet when the component is unmounted
// if false: waits to unloads the StyleSheet until another instance of useDisableImportedStyles is called.This avoids a flash of unstyled content
): useDisableImportedStyles => {
let localStyleSheet: StyleSheet
return () => {
useEffect(() => {
// if there are no stylesheets, you did something wrong...
if (document.styleSheets.length < 1) return
// set the localStyleSheet if this is the first time this instance of this useEffect is called
if (localStyleSheet == null) {
localStyleSheet = document.styleSheets[document.styleSheets.length - 1]
switchableGlobalStyleSheets.push(localStyleSheet)
}
// if we are switching StyleSheets, disable all switchableGlobalStyleSheets
if (!immediatelyUnloadStyle) {
switchableGlobalStyleSheets.forEach(styleSheet => styleSheet.disabled = true)
}
// enable our StyleSheet!
localStyleSheet.disabled = false
// if we are NOT switching StyleSheets, disable this StyleSheet when the component is unmounted
if (immediatelyUnloadStyle) return () => {
if (localStyleSheet != null) localStyleSheet.disabled = true
}
})
}
}
WARNING: This is pretty finicky. You must set this up exactly or there may be unintended consequences
Conditions:
createUseDisableImportedStyles must called in global scope in the same tsx file as the imported css being targeted and the component to be lazy loaded
import React from 'react'
import { createUseDisableImportedStyles } from './useDisableImportedStyles'
import './global-styles.css'
const useDisableImportedStyles = createUseDisableImportedStyles()
export const CssComponent: React.FC<{}> = () => {
useDisableImportedStyles()
return null
}
export default CssComponent
A component using this hook should be lazy loaded:
LazyCssComponent = React.lazy(() => import('./cssComponent'))
...
<React.Suspense fallback={<></>}>
{condition && <LazyCssComponent/>}
</React.Suspense>
An exception to lazy loading might be using this in a single, normal, non-lazy component so styles are loaded on first render
NOTE: the InitialCssComponent never needs to actually render, it just needs to be imported
BUT: this will only work if there is one single .css file imported globally, otherwise, I don't know what would happen
import InitialCssComponent from './initialCssComponent'
LazyCssComponent = React.lazy(() => import('./cssComponent'))
//...
{false && <InitialCssComponent/>}
<React.Suspense fallback={<></>}>
{condition && <LazyCssComponent/>}
</React.Suspense>
GOOD LUCK!
First of all, AFAIK, you should not call any imports in componentWillMount. This means that every time a new component about to mount, this css will be loaded over and over. Instead, it must be placed at the beginning of your module.
The way that you avoid unnecessary css imports is to avoid unnecessary component imports. Hence, if your component is not called anywhere, then this css will not be loaded.
For routing, I think you will need to do some code splitting, but I am not sure if it is straightforward or the right way to do.
Link 1
Link 2

Categories

Resources