Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with CRA #9

Open
peguerosdc opened this issue Jun 15, 2020 · 7 comments
Open

Compatibility with CRA #9

peguerosdc opened this issue Jun 15, 2020 · 7 comments
Assignees
Labels
bug Something isn't working

Comments

@peguerosdc
Copy link

peguerosdc commented Jun 15, 2020

Hi!

This issue is already kind of open in rsuite#947, but I thought I should open the issue here (where I think it belongs) with some more details.

I am trying to use webpack-multiple-themes-compile with CRA through react-app-rewired. My packages' versions are:

webpack-merge: 4.2.2,
webpack-multiple-themes-compile: 2.0.0
react-app-rewired: 2.1.6,
react: 16.8.6

And I am trying to override CRA's configuration with the following config-overrides.js (which is a based on this repository's README):

const { override } = require('customize-cra');

const merge = require('webpack-merge');
const multipleThemesCompile = require('webpack-multiple-themes-compile');

module.exports = function override(config, env) {
  // Merge default config with themes config
  return merge(
    config,
    multipleThemesCompile({
      themesConfig: {
        green: {
          color: '#008000'
        },
        yellow: {
          color: '#ffff00'
        }
      },
      lessContent: 'body{color:@color}'
    })
  );
}

But even though the compilation is successful, the application doesn't start as I only see a blank page. I think it is because the entry points of the default config and the ones generated by multipleThemesCompile are not being merged correctly as their definitions don't look compatible. This is how they look:

/* config */
{
  entry: [ '/my/path/node_modules/react-dev-utils/webpackHotDevClient.js', '/my/path/src/index.js' ]
  // more stuff
}
/* result from multipleThemesCompile */
{
  entry: {
    green: '/my/path/node_modules/webpack-multiple-themes-compile/lib/src/less/themes/green.js',
    yellow: '/my/path/node_modules/webpack-multiple-themes-compile/lib/src/less/themes/yellow.js'
  }
  // module, plugins, optimization
}

So the resulting merged object apparently just takes the entry from the latter (I am not writing the other attributes here as they seem to be merged correctly, but as I am no webpack expert, I am not 100% sure) which would explain why my application doesn't load but the css files do seem to load.

I have tried some things like merging manually, merging the entry points with Derek-Hu/react-app-rewire-multiple-entry, but each of them throws different errors.

Is there a workaround for this or am I doing something wrong?

My final goal is to achieve runtime theme switching (like in Rsuite's website) with custom light and dark themes (i.e. light mode with base_color : green and dark mode with base_color : red).

Thanks!

@hiyangguo hiyangguo self-assigned this Jun 22, 2020
@hiyangguo hiyangguo added the bug Something isn't working label Jun 22, 2020
@hiyangguo
Copy link
Member

@peguerosdc Thank you for your feedback , I will adapter to CRA at the later.

@peguerosdc
Copy link
Author

@hiyangguo thank you very much! looking forward to it :)

@peguerosdc
Copy link
Author

Hi!

I have come up with a very hacky way to get this to work. I created a gist example if anyone is interested, but you can check the full implementation in my own project.

Again, this is very (very) hacky so if someone comes up with a cleaner implementation, it would be awesome!

As I state in my gist example, as I am messing around with the original CRA's webpack config, the first run of the application results in a blank page (but it works as expected after reloading the page) and there is a limit to the amount of themes you can add before the compiler runs out of memory (I don't know why).

Also (this is not in my gist example as this is more to my particular use case, but you can check it in my repository) I couldn't find a way to achieve the theme switching with custom colors (i.e. orange + dark mode, orange + light mode) and to use rsute's less variables in my own less files at the same time (i.e. to use @nav-item-default-hover-bg as the background of my own component), so what I did was to populate lessContent with a function called themeContent(name) which returns a very big string with all the styles' definitions where I need to use these variables and discriminates between light and dark mode based on the theme's name. You can check this with detail in my repository at subplayer/src/themes.content.js, but here is a snippet with the idea:

function themeContent(themeName) {
    const isDark = themeName.startsWith("dark")
    return (
        `
        @import '~rsuite/lib/styles/themes/${isDark ? "dark" : "default"}/index.less';

        .my_component:hover {
            background-color: @nav-item-default-hover-bg;
        }
        `
    )
}

and here it is the config I use:

multipleThemesCompile({
   themesConfig: themes,
   lessContent: (themeName, config) => themeContent(themeName),
   styleLoaders: [
      { loader: 'css-loader' },
      {
        loader: 'less-loader',
        options: {
          lessOptions: {
            javascriptEnabled: true
          }
        }
      }
    ],
    cwd: path.resolve('./')
 })

This is very very hacky too, but I couldn't find another way as using both modifyVars and webpack-multiple-themes-compile didn't work. If someone comes up with a nice solution for this as well, it'd be great.

@Phrosh
Copy link

Phrosh commented Jul 7, 2020

I also used a rather "hacky" workaround. I bypassed this plugin entirely to achieve runtime theme switching. I used "react-boilerplate", but I got the exact same problem.
Caveat: this might mess with other .less files imported by webpack. You might use a custom file ending instead of .less and adjust your loader-rule accordingly to get this working, but I didn't try, because I don't use more .less (haha) in my project so far.

First thing is, you have to check if you use the latest webpack style-loader version: "style-loader": "^1.2.1",.
Now you can tell the style-loader in your webpack config to add attributes to your <style>-tag. Add following options to your style-loader: options: { attributes: { id: 'theme' }, },, resulting in this:

rules: [
      {
        test: /\.less$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'style-loader',
            options: {
              attributes: { id: 'theme' },
            },
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                javascriptEnabled: true,
              },
            },
          },
        ],
      },
//...

Then, create a directory "themes", if you didn't already and create a .less file for each theme and an optional global file: light.less, dark.less and global.less.

Put all non-rsuite and global variables inside global.less. I used different background and foreground graphics for different themes:

@base-color: #0000ff;
.bg { background-image: @bg-image }
.fg { background-image: @fg-image }

light.less and dark.less contain the overwritten variables, as well as the themes themselves:

@import '../../node_modules/rsuite/lib/styles/constants.less';
@import '../../node_modules/rsuite/lib/styles/variables.less';
@import '../../node_modules/rsuite/lib/styles/mixins/utilities.less';
@import '../../node_modules/rsuite/lib/styles/themes/default/index.less';

@import './global.less';

@text-color:    #ff0000;
@bg-image:      url("../images/assets/bg-light-02.png");
@fg-image:      url("../images/assets/bg-light-01.png");
@import '../../node_modules/rsuite/lib/styles/constants.less';
@import '../../node_modules/rsuite/lib/styles/variables.less';
@import '../../node_modules/rsuite/lib/styles/mixins/utilities.less';
@import '../../node_modules/rsuite/lib/styles/themes/dark/index.less';

@import './global.less';

@text-color:    #00ff00;
@bg-image:      url("../images/assets/bg-dark-02.png");
@fg-image:      url("../images/assets/bg-dark-01.png");

(I used red and green text to easily see the the style change. Use whatever text color or variables you like)

Enough less for now. Now jump into your App.js or any low-level file, where you want to import your styles.
Import BOTH themes:

import '../../themes/dark.less';
import '../../themes/light.less';

just to remove the <style>-tags right after that:

const themes = {};
themes.dark = document.getElementById('theme');
themes.dark.parentElement.removeChild(themes.dark);
themes.light = document.getElementById('theme');
themes.light.parentElement.removeChild(themes.light);

We stored both our removed elements, so we are able to append them later.

Then create a switchTheme()-function:

const switchTheme = theme => {
  try {
    const rem = document.getElementById('theme');
    rem.parentElement.removeChild(rem);
    // eslint-disable-next-line no-empty
  } catch (e) {}
  document.head.appendChild(themes[theme]);
};

Try-catch block just in case something goes wrong. This function removes the existing <style> and replaces it with the one we want.
Now you just have to switchTheme('dark') or whatever and you got what you wanted. I also used react-cookies to store and restore the user's settings. I think you'll figure out the fancy stuff by yourself ;)

@dmitru
Copy link

dmitru commented Jan 26, 2021

@Phrosh your approach worked like a charm in my CRA app.

Struggling to make it work for a NextJS app though... 🤔

@dmitru
Copy link

dmitru commented Jan 27, 2021

Easy way to switch themes dynamically

Found a very easy way to switch themes dynamically, without messing with Webpack.

TL;DR:

  • in your main LESS file, use scoped @import to import both .less theme files inside CSS class selectors.
  • at run-time, attatch a classname to document.body - the scoped them styles will kick in.

Works beautifully in CRA and NextJS projects.

Example

// themes.less

@import '~rsuite/lib/styles/constants.less';
@import '~rsuite/lib/styles/variables.less';
@import '~rsuite/lib/styles/mixins/utilities.less';

.theme-dark {
  @import '~rsuite/lib/styles/themes/dark/index.less';
  @import './your-custom-variables.less';
}

.theme-light {
  @import '~rsuite/lib/styles/themes/default/index.less';
  @import './your-custom-variables.less';
}
// index.js

import './themes.less'

let currentTheme = ''

const setTheme = (theme) => {
  // skip it during SSR
  if (typeof document === 'undefined') {
    return
  }
  if (currentTheme) {
    document.body.classList.remove(`theme-${currentTheme}`)
  }
  document.body.classList.add(`theme-${theme}`)
  currentTheme = theme
}

// ... some place later - change your theme dynamically:

setTheme('dark')
// or...
setTheme('light')

The only disadvantage of this method compared to webpack-multiple-themes-compile is bundle size - which would include both themes in the resulting CSS bundle.

But the simplicity of it!


@hiyangguo Would you consider adding this method to examples and the docs?

IMO it's a very simple method compared to tweaking Webpack set-up, and for many people it could mean the difference between using RSuite or not using it due to complex multi-theme set-up.

I've spent a few hours of my life trying to make it work until I discovered this solution 🙈

@dmitru
Copy link

dmitru commented Jan 27, 2021

Update: unfortunately, the approach above breaks in a few places, e.g. for Dropdown:

// from Dropdown/styles/common.less

  & &-menu {
    position: absolute;
    // dropdown-menu zindex value is greater than dropdown-toggle
    z-index: @zindex-dropdown + 1;
    display: none; // none by default, but block on "open" of the menu
    float: left;
    box-shadow: @dropdown-shadow;
  }

This leads to the following CSS produced:

.theme-dark .rs-dropdown .theme-dark .rs-dropdown-menu {
  position: absolute;
  z-index: 6;
  display: none;
  float: left;
  box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.2), 0 4px 4px 3px rgba(0, 0, 0, 0.24);
}

Where it should be:

.theme-dark .rs-dropdown .rs-dropdown-menu {
  position: absolute;
  z-index: 6;
  display: none;
  float: left;
  box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.2), 0 4px 4px 3px rgba(0, 0, 0, 0.24);
}

And so the dropdown menu isn't styled correctly.

Trying to figure out a workaround...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants