Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract CSS from SCSS and deferred lazy load in React app

I have a few SCSS theme files I want to extract to CSS files and later load them into the page. I want to be able to use contenthash for long term caching.

Since I'm using Webpack 4, I am also using mini-css-extract-plugin. I started down the path of creating a splitChunks in my webpack config.

// webpack.config.js
module.exports = {
  plugins: [
      new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: "[name].[contenthash].css",
      chunkFilename: "[id].[contenthash].css"
    })
  ],
  optimization: {
    splitChunks: {
      cacheGroups: {
        'vendor': {
            // custom commons chunk for js
        },
        'theme-a': {
            test: /theme-a.\scss/,
        },
        'theme-b': {
            test: /theme-b.\scss/,
        },
        // more themes
      }
    }
  }
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "sass-loader"
        ]
      }
    ]
  }
}

I've then tried dynamically importing the css in my app:

// app.js
class App extends React.Component {
  // constructor

  login(themeName) {
    import(/* webpackChunkName: "`${themeName}`" */ `./path/to/${themeName}.scss`).then(theme => {
      // do something with `theme`
    }
  }

  // other stuff
}

I need to be able to load that css file dynamically in login() and I'm just not sure how to reference it when it has a generated [contenthash].

TLDR: Is there a good way to both extract css and import the referenced CSS bundle to lazy load later? I'm not tied to mini-css-extract-plugin.

Edit: Created a mini-css-extract-plugin issue here.

like image 986
Fillip Peyton Avatar asked Apr 20 '18 06:04

Fillip Peyton


People also ask

Can we use both CSS and SCSS in react?

Your code seems to be alright as post React 16.8 you can use css and scss modules without configuring webpack. I would suggest you check your version of React first. If you are using a version of React < 16.8 then you would have to eject and configure your webpack in order to use css and scss modules.

Can you lazy load CSS?

Browser-level lazy-loading does not apply to CSS background images, so you need to consider other methods if you have background images to lazy-load.

Can we do lazy loading in react?

Using React Suspense ( React 16.6+ )From React 16.6+, react added React Suspense which performs lazy loading.


2 Answers

My solution ended up using extract-text-webpack-plugin. My config now looks like this:

// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ExtractThemeA = new ExtractTextPlugin({ filename: 'themeA.[hash].css', allChunks: true});

module.exports = {
  plugins: [
      ExtractThemeA,
      // more plugins for other css files
  ],
  optimization: {
    splitChunks: {
      cacheGroups: {
        // Note: No changes to splitChunks
        'vendor': {
            // custom commons chunk for js
        }
    }
  }
  module: {
    rules: [
      {
        test: /theme-a\.scss$/,
        use: ExtractThemeA.extract([ 'css-loader', 'sass-loader' ])
      },
      // more module rules for each css file needed
    ]
  }
}

Then, these chunks are available by file name in my HtmlWebpackPlugin:

<!-- HtmlWebpackPlugin Template -->
<script>
// provides me with an array of file name strings
var themesManifest = <%= htmlWebpackPlugin.files.css %>
</script>
like image 147
Fillip Peyton Avatar answered Oct 07 '22 10:10

Fillip Peyton


Sorry for my miss understanding,

You could probably just make two different scss-files and import them as needed. theme.scss admin.scss or like so

This is how I am doing scss in React right now

In App.js

import styles from '../../stylesheet/main.scss'
// could be as well
import styles1 from '../../stylesheet/theme.scss' // some file
import styles2 from '../../stylesheet/admin.scss' // some other file

const App = () => {
  <div className={styles.action_feed} >{ content }</div>
} 

In main.scss

.action_feed {
  position: fixed;
  width: 350px;
  height: auto;
  max-height: 100%;
  max-width: 100%;
  top: 0;
  left: 0;
  z-index: 9999;
}

I think you could just as well do it like so

const themeName = 'main'
import(`./stylesheet/${themeName}.scss`, (css) => {
  // meaby set this to state? not quite sure how should properly handle
  // dynamically imported css
  this.setState({ css: css.action_feed })

  // or possible this way. I have done some non-React dom manipulation
  // this way before
  document.querySelector('body').classList.add(css.action_feed)
})

<div className={this.state.css}>{ content }</div>

You should probably check out React's new Refs API as well. It might give you some nice flexibility for giving className-attr to required element.

Having set to splitChunks.chunks to all works though i think in this case anyway

like image 28
Jimi Pajala Avatar answered Oct 07 '22 11:10

Jimi Pajala