{"id":14990674,"url":"https://github.com/klimashkin/css-modules-theme","last_synced_at":"2025-04-12T02:42:30.926Z","repository":{"id":49155778,"uuid":"145489574","full_name":"klimashkin/css-modules-theme","owner":"klimashkin","description":"🚀 Theme composition for CSS Modules done simple/fast/complete","archived":false,"fork":false,"pushed_at":"2021-06-25T23:33:15.000Z","size":120,"stargazers_count":46,"open_issues_count":2,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-25T22:21:40.981Z","etag":null,"topics":["css","css-modules","react","theming"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/klimashkin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-21T01:30:55.000Z","updated_at":"2025-02-24T22:03:00.000Z","dependencies_parsed_at":"2022-08-25T20:12:36.425Z","dependency_job_id":null,"html_url":"https://github.com/klimashkin/css-modules-theme","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klimashkin%2Fcss-modules-theme","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klimashkin%2Fcss-modules-theme/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klimashkin%2Fcss-modules-theme/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klimashkin%2Fcss-modules-theme/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/klimashkin","download_url":"https://codeload.github.com/klimashkin/css-modules-theme/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248036064,"owners_count":21037092,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","css-modules","react","theming"],"created_at":"2024-09-24T14:20:34.384Z","updated_at":"2025-04-12T02:42:30.901Z","avatar_url":"https://github.com/klimashkin.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![CSS Modules Theme](https://raw.githubusercontent.com/klimashkin/css-modules-theme/master/logo.svg?sanitize=true \"CSS Modules Theme\")\n\n[![Written in TypeScript](https://img.shields.io/badge/Written%20in-TypeScript-4e6ef2.svg)](https://www.typescriptlang.org)\n[![Maintained with Lerna](https://img.shields.io/badge/Maintained%20with-Lerna-ff9100.svg)](https://lernajs.io)\n[![License: MIT](https://img.shields.io/badge/License-MIT-00c853.svg)](https://raw.githubusercontent.com/klimashkin/css-modules-theme/master/LICENSE)\n\n- [CSS Modules](#css-modules)\n- [Theming](#theming)\n- [Principles](#principles)\n- [Packages](#core)\n  - [Core](#core)\n    * [Composition types](#composition-types)\n    * [composeTheme()](#composethemeoptions)\n  - [React](#react)\n    * [composeThemeFromProps()](#composethemefrompropsowntheme-propsOrContext-options)\n    * [mixThemeWithProps()](#mixthemewithprops)\n- [Other Libraries](#other-libraries)\n- [Bundling](#bundling)\n- [Contribution](#contribution)\n\n## CSS Modules\nA [CSS Module](https://github.com/css-modules/css-modules) is just a CSS file in which all class names and animation names are transformed on build time to be scoped locally by default.\nCSS Modules give you a very powerful way to write ***statically compiled***, ***isolated*** and ***composable*** css selectors, so it become a natural choice in the world of componentized front-end development, like in React.\n* Statically compiled, because you generate result css file(s) and corresponding javascript mapping only once on compilation time when you build your assets. That is the main difference between CSS Modules and CSS-in-JS approach, in the first case you get regular css files, in the second you generate css definition in browser during runtime on each component render. Static CSSOM tree is always faster than dynamically generated one - browsers have spent more than 20 years optimizing that work. With CSS Modules you produce final css in build time using loaders for popular bundlers, like for [weback](https://github.com/webpack-contrib/css-loader/#modules) or [rollup](https://github.com/egoist/rollup-plugin-postcss#modules).\n* Isolated, because your bundler generates uniq css class names on bundle creation time according to a naming rule you specify. That is called 'local scope', which is the corner stone of CSS, and you get it automatically.\n* Composable, because in certain cases you can compose (concatenate) classnames from the same or different files on bundle creation time and avoid utilizing cpu in runtime.\n\nLet's recap on what CSS Module is. On one side it’s just a standard css file, imported value of which on JS side becomes a simple flat object that maps names you give classes during development with the real (generated) class names from that final css file.\nFor instance, imagine you have the following `Button.css` code:\n```css  \n.button {  \n  display: inline-block;\n}  \n.primary {  \n  background-color: green;  \n}  \n.secondary {  \n  background-color: blue;  \n}  \n```  \nFrom the above css definition, depending on css-loader setting (when using a module bundler like 'webpack'), we might get generated css like this: \n```css  \n.aa {  \n  display: inline-block;\n}  \n.ab {  \n  background-color: green;  \n}  \n.ac {  \n  background-color: blue;  \n}  \n```\nAnd in JavaSctipt you get the following mapping `styles` object after doing `import styles from './Button.css'`:  \n```javascript  \n{ \n  button: 'aa',\n  primary: 'ab',\n  secondary: 'ac',\n}\n```\nSo, class names that we wrote in css file become keys of a mapping object, while real classname are automatically generated and become values of that object. That's it!\nAnd in your, let's say, react `Button.js` component you can use it this way:\n```javascript  \nimport cx from 'classnames';\nimport PropTypes from 'prop-types';\nimport styles from './Button.css';\n\nclass Button {\n  static propTypes = {\n    type: PropTypes.oneOf(['primary', 'secondary']),\n  }\n  static defaultProps = {\n    type: 'primary',\n  }\n  render() {\n    return \u003cbutton type=\"submit\" className={cx(styles.button, styles[this.props.type])}/\u003e\n  }\n}\n```\nWhich will be rendered into:\n```html\n\u003cbutton type=\"submit\" class=\"aa ab\"/\u003e\n```\nHave you noticed how we took the real classnames from `styles` object simply by accessing property using classnames that we gave in css definition?\nMoreover, in case of getting corresponding classname for the passed `type` property we can simply use brackets notation if possible type names are the same as given classnames in css. \nThat is superuseful - no ternaries, conditions or superfluous `cx({primary: type === 'primary', secondary: type === 'secondary'})` are needed, which is good for performance and reasoning.\n\n---\nExample above illustrates two first concepts of CSS Modules - local scope and build time transformation. Let's illustrate third one - [composition](https://github.com/css-modules/css-modules#composition).\nOur Button component will always have some type, which means both `.button` and either `.primary` or `.secondary` classes will be applied. Currently we have to concatenate them in runtime on each render, but we can do better. Let's use `composes` keyword in our `Button.css`:\n```css  \n.button {  \n  display: inline-block;\n}  \n.primary {\n  composes: button; \n  background-color: green;  \n}  \n.secondary {\n  composes: button;\n  background-color: blue;  \n}  \n``` \nOur generated css will still remain the same:\n```css  \n.aa {\n  display: inline-block;\n}\n.ab {\n  background-color: green;  \n}\n.ac {\n  background-color: blue;  \n}  \n```\nBut our `styles` object will become:\n```javascript  \n{ \n  button: 'aa',\n  primary: 'ab aa',\n  secondary: 'ac aa',\n}\n```\nA-ha! So if we insert `styles.primary` or `styles.secondary` it will actually insert two real classnames: `ab aa` or `ab ac`, and we can simplify our component to:\n```javascript  \nimport PropTypes from 'prop-types';\nimport styles from './Button.css';\n\nclass Button {\n  static propTypes = {\n    type: PropTypes.oneOf(['primary', 'secondary']),\n  }\n  static defaultProps = {\n    type: 'primary',\n  }\n  render() {\n    return \u003cbutton type=\"submit\" className={styles[this.props.type]}/\u003e\n  }\n}\n```\nProduced html will still be the same:\n```html\n\u003cbutton type=\"submit\" class=\"aa ab\"/\u003e\n``` \nWe don't need `cx` in that case anymore! With CSS Modules, if two or more class names always go along with each other, you can compose them in one right in css and they will be concatenated on compilation time!\nAnd you get a little performance boost in runtime for free, you just need to design your css right. And it's not as insignificant as it might seem, you'll appreciate that on complex pages with thousands of small rendered components. You are welcome!\n\n## Theming\nLocal scope brings a challenge: how to style children component from parent if they are isolated and their real classnames are unknown during development process?\nTheming is the answer.\n\nHow will a page that uses Button component modify its `primary` styling if it doesn't know real corresponding `ab` classname? With React, we can create a boolean prop in a Button component for each possible style modification. Adding extra prop(s) for css logic can be managable for simple components. As the complexity grows, a component's props combination can grow exponentially making prop management challenging.\n\nThe solution to avoid overwhelming a component with boolean props is to take two css-module objects, own one and the from parent, and merge them together to get a final object. The first object can be called original theme, or *own theme* of the component, and the parent one can be called injecting theme since we mix it into the first one. \n\n## Principles\n**@css-modules-theme** is a project based on two simple ideas:\n\n* First, themes composition must be fast. That means, no producing class instance on each composition, [no hocs](#other-libraries), no fancy multistep map/reduces or third-party helpers, just a few straightforward classic JS loops with minimum transformations. \n\n* Second, result of that composition should be weakly cached for given parameters and shared between different calls. If we render component that renders another themeable component many times, in vast majority of cases parent component will pass the same theme object to the child, thus result theme can be composed only once, cached and reused by other components from that cache.\nTo achieve that @css-modules-theme puts injected theme as a key into WeakMap (to free up memory when injected theme is not needed anymore) and composed theme along with options into a value of that map. \nSo when you call composeTheme for the first time, it will do the composition and put the result into cache, and all consequent calls with the same arguments will just return the same result from that cache.\nFrom the table example mentioned in [Other Libraries](#other-libraries) section, the new table implementation with **css-modules-theme** was able to reduce the number of compositions from 761 to 42 and total page rendering time (with all content) by **30%**.\n\n* Third, deliverables are written in TypeScript and covered with all kind of tests using Jest.\n\nProject includes two (for now) scoped packages: [@css-modules-theme/core](https://github.com/klimashkin/css-modules-theme/tree/master/packages/core) and [@css-modules-theme/react](https://github.com/klimashkin/css-modules-theme/tree/master/packages/react)\n\n## Core\nMain package that performs all types of composition and which is used by other packages.\n\n* [npm](https://www.npmjs.com/package/@css-modules-theme/core): `npm install @css-modules-theme/core`\n* [yarn](https://yarnpkg.com/en/package/@css-modules-theme/core): `yarn add @css-modules-theme/core`\n* cdn: Exposed as `cssModulesThemeCore`\n  * [Unpkg](https://unpkg.com/@css-modules-theme/core@2.1.2/dist/core.umd.js): `\u003cscript src=\"https://unpkg.com/@css-modules-theme/core@2.1.2/dist/core.umd.js\"\u003e\u003c/script\u003e`\n  * [JSDelivr](https://cdn.jsdelivr.net/npm/@css-modules-theme/core@2.1.2/dist/core.umd.js): `\u003cscript src=\"https://cdn.jsdelivr.net/npm/@css-modules-theme/core@2.1.2/dist/core.umd.js\"\u003e\u003c/script\u003e`\n\n1.8kb module that represents pretty simple singleton which creates WeakMap for caching composed themes and exposes the following method\n\n#### Composition types\nAs `import {Compose} from '@css-modules-theme/core';`\n\n`Compose` is an object (enum) that exposes available composition methods with following values:\n   - `Compose.Merge` - Default way that assigns classnames from current `theme` to the previous one, and concatenate classnames which exist in both themes.\n   - `Compose.Assign` - Also assigns classnames from curent `theme` to previous one, like Object.assign, so if classname exists in both, latter takes precedence\n   - `Compose.Replace` - Just use current theme\n\n#### `composeTheme([options])`\nAs `import {composeTheme} from '@css-modules-theme/core';`\n\nFunction that returns a new theme as a result of composition of themes in an array of options. Takes the following arguments\n\n - `options` *(Object)* - option object, one per each theme, with the following properties:\n   - `theme` *(Object)* - Theme object to compose with previous one.\n   - [`prefix`] *(String)* - Prefix to filter and strip out properties in current `theme` that don't satisfy that prefix, before composition.\n   - [`compose`] *(String)* - Method of composition of current `theme` with previous one (for second and following options). Available values are exported by [`Compose`](#compose). If `compose` in current oprions object is absent, it will be taken from the previous or default one.\n   - [`noCache = false`] *(Boolean)* - In case you generate current `theme` dynamically (for instance, on each render), there is no reason to cache result, since there might be too many variation of outcome. In that case you can set `noCache` to `true` to skip putting result into cache and looking it up.\n   - [`noParseComposes = false`] *(Boolean)* - `composeTheme` will try to detect [`composes`](https://github.com/css-modules/css-modules#composition) rules in css. Set it to `false` if you don't use `composes` and want to safe some cpu\n\n### Examples\nAssume we have an Icon component with following theme:\n```javascript\nconst iconStyle = {\n  'icon': 'x',\n  'small': 'y',\n  'medium': 'z'\n}\n```\nand a Button component which wants to render Icon component and pass following theme to it:\n```javascript\nconst buttonStyle = {\n  'button': 'a',\n  'primary': 'b',\n  'secondary': 'c',\n  'icon-icon': 'd',\n  'icon-small': 'e'\n}\n```\nNow let's compose them using different options and see the outcome:\n```javascript\ncomposeTheme([{theme: iconStyle}, {theme: buttonStyle}]) =\u003e\n{\n  'icon': 'x',\n  'small': 'y',\n  'medium': 'z'\n  'button': 'a',\n  'primary': 'b',\n  'secondary': 'c',\n  'icon-icon': 'd',\n  'icon-small': 'e'\n}\n```\n```javascript\ncomposeTheme([{theme: iconStyle}, {theme: buttonStyle, prefix: 'icon-'}]) =\u003e\n{\n  'icon': 'x d',\n  'small': 'y e',\n  'medium': 'z'\n}\n```\n```javascript\ncomposeTheme([{theme: iconStyle}, {theme: buttonStyle, prefix: 'icon-', compose: Compose.Assign}]) =\u003e\n{\n  'icon': 'd',\n  'small': 'e',\n  'medium': 'z'\n}\n```\n```javascript\ncomposeTheme([{theme: iconStyle}, {theme: buttonStyle, prefix: 'icon-', compose: Compose.Replace}]) =\u003e\n{\n  'icon': 'd',\n  'small': 'e',\n}\n```\n```javascript\ncomposeTheme([{theme: iconStyle, compose: Compose.Replace}, {theme: buttonStyle, prefix: 'icon-'}]) =\u003e\n{\n  'icon': 'd',\n  'small': 'e',\n}\n```\n```javascript\ncomposeTheme([{theme: iconStyle, compose: Compose.Replace}, {theme: buttonStyle, prefix: 'icon-', compose: Compose.Assign}]) =\u003e\n{\n  'icon': 'd',\n  'small': 'e',\n  'medium': 'z'\n}\n```\n\n## React\nPackage that makes calling [composeTheme](#composeTheme-options) easier in React components, so you can just pass props/context to the methods below and they will map theme specific props with [composeTheme](#composeTheme-options) arguments.\n\n* [npm](https://www.npmjs.com/package/@css-modules-theme/react): `npm install @css-modules-theme/react`\n* [yarn](https://yarnpkg.com/en/package/@css-modules-theme/react): `yarn add @css-modules-theme/react`\n* cdn: Exposed as `cssModulesThemeReact`\n  * [Unpkg](https://unpkg.com/@css-modules-theme/react@2.1.2/dist/react.umd.js): `\u003cscript src=\"https://unpkg.com/@css-modules-theme/react@2.1.2/dist/react.umd.js\"\u003e\u003c/script\u003e`\n  * [JSDelivr](https://cdn.jsdelivr.net/npm/@css-modules-theme/react@2.1.2/dist/react.umd.js): `\u003cscript src=\"https://cdn.jsdelivr.net/npm/@css-modules-theme/react@2.1.2/dist/react.umd.js\"\u003e\u003c/script\u003e`\n\n#### `composeThemeFromProps(ownTheme, propsOrContext, [options])`\nAs `import {composeThemeFromProps} from '@css-modules-theme/react'`;\n\n*Parameters:*\n\n - `ownTheme` *(Object)* - First CSS modules object, used as an origin theme for composition\n - `propsOrContext` *(Object|Array)* - Standard react props object or array of props and context objects with the following properties:\n   - [`theme`] *(Object)* - Maps to `theme` in `composeTheme`\n   - [`themePrefix`] *(String)* - Maps to `prefix` in `composeTheme`\n   - [`themeCompose`] *(String)* - Maps to `compose` in `composeTheme`\n   - [`themeNoCache`] *(Boolean)* - Maps to `noCache` in `composeTheme`\n   - [`themeNoParseComposes`] *(Boolean)* - Maps to `noParseComposes` in `composeTheme`\n - [`options`] *(Object)* - options for `ownTheme`\n   - [`compose`] *(String)* - Default composition method for `composeTheme` if there is no `props.themeCompose` passed\n   - [`prefix`] *(String)* - Goes directly to `composeTheme`\n   - [`noCache = false`] *(Boolean)* - Default `noCache` flag if there is no `props.themeNoCache` passed.\n   - [`noParseComposes = false`] *(Boolean)* - Default `noParseComposes` flag if there is no `props.themeNoParseComposes` passed.\n\n### Examples\nAssume we have a themeable Icon component. Default composition for it is `replace` declared in the render method of Icon component, but Button overrides it with `merge` declared as themeCompose='merge' in Button component. Button will use prefix `icon-` in own Button.css to concatenate the matching Icon classnames in Icon.css.\nAs a result of using `merge`, Button will render the bigger green Icon during the merge declaration by adding own classname to Icon's large selector definition.(e.g {large: \"Icon_c Button_z\"})\nWe can call `composeThemeFromProps` many times during the lifecycle of the component (we call it in `handleClick` sometime after `render`), result will always be taken from cache as long as `props.theme*` are the same\n```css\n/** Icon.css **/\n.icon { width: 20px; }\n.svg { color: red; }\n.large { width: 30px; }\n.small { width: 15px; }\n\n/** Button.css **/\n.button { width: 100px; }\n.large { width: 200px; }\n.small { width: 50px; }\n.icon-svg { color: green; }\n.icon-large { width: 40px; }\n```\n```javascript\nimport {composeThemeFromProps} from '@css-modules-theme/react';\nimport iconStyles from './Icon.css';\nimport buttonStyles from './Button.css';\n\nclass Icon extends Component {\n  handleClick() {\n     // We can call composeThemeFromProps(iconStyles, this.props) many times here, it will just return the same result from cache\n    const theme = composeThemeFromProps(iconStyles, this.props);\n    \n    console.log(theme.icon)\n  }\n  \n  render() {\n    const theme = composeThemeFromProps(iconStyles, this.props, {compose: 'replace'});\n    \n    /* In case of call from Button final theme object would look like\n    theme = {\n      icon: \"Icon_a\",\n      svg: \"Icon_b Button_x\",\n      large: \"Icon_c Button_z\",\n      small: \"Icon_d\",\n    }\n    */\n        \n    return \u003cdiv className={theme.icon} onClick={this.handleClick}\u003e{this.props.icon}\u003c/div\u003e;\n  }\n}\n\nclass Button extends Component {  \n  render() {    \n    return (\n      \u003cbutton type=\"button\"\u003e\n        \u003cIcon theme={buttonStyles} themePrefix=\"icon-\" themeCompose=\"merge\"/\u003e\n        {this.props.text}\n      \u003c/button\u003e\n    );\n  }\n}\n```\n\n---\nIf we want to use composed `theme` in many lifecycle hooks or, for instance, in methods that can be called dozens of times quickly,\nlike in react-motion, we can manually check for changing theme props and compose a state `theme` in `getDerivedStateFromProps`.\nBy keeping theme in state, searching through the cache in hot functions can be avoided.\n```javascript\nimport {composeThemeFromProps} from '@css-modules-theme/react';\nimport styles from './styles.css';\n\nexport default class extends Component {\n  constructor(props) {\n    super(props);\n    \n    this.state = {motionConfig: {...}};\n    this.interpolateMotion = this.interpolateMotion.bind(this);\n  }\n  \n  static getDerivedStateFromProps(nextProps, prevState) {\n    if (nextProps.theme !== prevState.injectTheme) {\n      return {injectTheme: nextProps.theme, theme: composeThemeFromProps(styles, nextProps)};\n    }\n    \n    return null;\n  }\n  \n  interpolateMotion(config) {\n    return (\n      \u003cdiv className={this.state.theme.text} style={{width: `${config.width}px`, height: `${config.height}px`}}\u003e\n        {this.props.content}\n      \u003c/div\u003e\n    );\n  }\n\n  render() {\n    return (\n      \u003cMotion style={this.state.motionConfig}\u003e\n        {this.interpolateMotion}\n      \u003c/Motion\u003e\n    );\n  }\n}\n```\n\n#### `mixThemeWithProps`\nAs `import {mixThemeWithProps} from '@css-modules-theme/react'`;\n\nWhat if your component just takes some properties from own `props` and pass all the rest down to another component as is. In that case you'd need to take all `theme*` props out, something like this:\n```javascript\nrender() {\n  let {size, onClick, theme, themePrefix, themeCompose, themeNoCache, ...elementProps} = this.props;\n  \n  theme = composeThemeFromProps(styles, this.props);\n  elementProps.className = theme.main;\n  \n  ...\n  \n  return (\n    \u003cdiv {...elementProps}\u003e\n      ...\n    \u003c/div\u003e\n  );\n}\n```\nSo you need to list all possible `theme*` props that parent can specify for `composeThemeFromProps`, to destructure them out because they are not valid for a child component. But what if `@css-modules-theme/react` will add more props in the future? It's pretty annoying to manually list them all.\n\nFor that case `mixThemeWithProps` has been created. It's a simple wrapper on top of `composeThemeFromProps` (and has exactly the same signature) that takes out all `theme*` props for you and mix composed `theme` in the result props object. So you can destructure only props you really need.\n```javascript\nrender() {\n  const {size, onClick, theme, ...elementProps} = mixThemeWithProps(styles, this.props);\n  \n  ...\n  \n  return (\n    \u003cdiv className={theme.main}\u003e\n      ...\n    \u003c/div\u003e\n  );\n}\n```\n\n## Other Libraries\nSeveral projects emerged in the past few years, one of which is [react-css-themr](https://github.com/FriendsOfReactJS/react-css-themr). It is focused on theming in React, but the way of merging themes from parent component into child is pretty generic and flexible. It solves the theming problem!  \nUnfortunately, it does it by wrapping all components with HOCs.\nWhy \"unfortunately\"? Because using HOC leads to a significant performance degradation on big projects. \nLet's imagine we have a big React application with hundreds of basic components like Link, Button, Icon, Tag, Menu, Popup, etc... All of those components have their own css-module with styling, and each of them should be themeable. React is powerful because it gives us easy components composition. If you want Button or MenuItem be a link with href, you just make them render Link component in their render methods with passing theme so Link would look like Button or MenuItem. Many basic components might render Icon inside them and style (theme) that Icon differently. Now let's imagine you have a lot of content on a page, for instance Table with 50 rows and 10 columns (not big), and in each cell you have basic component that renders inside some content plus another basic component and so on. You end up with 500 cells that renders let's say ~2000 lightweight basic components. All of them are themeable, so each of them have HOC on top plus forwardRef, and now you have ~6000 components to render. You will start to cry while profiling rendering in Performance tab of DevTools noticing that the first render of your page takes hundreds of milliseconds.\n\nBut let's step back and think for a second. What does HOC do? It creates component instance for each wrapped instance of your component and merge your style object with theme object from a parent component instance. If we have 2000 identical components with different content, it will do it 2000 times. Hm, that doesn't sound right, because result theme object will be essentially the same for all of them. The nature of HOC multiplies isolated instances of your components by 2-3 times.\n\nWhat if we could generate result theme for given two themes only once and then just reuse it in all similar components?\nAnd with `css-modules-theme` [we can and we get it automatically](#principles).\n\n## Bundling\n\nModules under @css-modules-theme namespace are built with [rollup](https://github.com/rollup/rollup) and distributed through package managers ([npm](https://www.npmjs.com/settings/css-modules-theme/packages), [yarn](https://yarnpkg.com/en/package/@css-modules-theme/react), cdn's) with `dist/` folder that contains several bundles for different targets:\n* dist/[*name*].umd.js - Universal minified module, transpiled down to ES5 code, and can be fetched and used by any browser or nodejs as is, available on cdns \n* dist/[*name*].cjs.js - classic CommonJS bundle transpiled down to ES5 code\n* dist/[*name*].es.js - ES6-module bundle transpiled down to ES5 code\n* dist/[*name*].es2015.js - ES6-module bundle transpiled down to ES6 (2015) code, where, for instance, object rest/spread is transpiled to Object.assign\n* dist/[*name*].es2018.js - ES6-module bundle transpiled down to ES9 (2018) code, which basicall means no transpilation at the moment\n* dist/[*name*].es2019.js - ES6-module bundle transpiled down to ES10 (2019) code, which basicall means no transpilation at the moment\n\nEach of them has corresponding field in `package.json`:\n```json\n  \"browser\": \"dist/name.umd.js\",\n  \"main\": \"dist/name.cjs.js\",\n  \"module\": \"dist/name.es.js\",\n  \"es2015\": \"dist/name.es2015.js\",\n  \"es2018\": \"dist/name.es2018.js\",\n  \"es2019\": \"dist/name.es2019.js\",\n```\n\nIf you write simple website, support variety of browsers and prefer to insert script tags to the html head, then you can embed desired module like that: `\u003cscript src=\"https://unpkg.com/@css-modules-theme/react@@2.1.2/dist/react.umd.js\"\u003e\u003c/script\u003e`.\n\nBut if you, more likely, use bundlers to build your applications, like [webpack](https://github.com/webpack/webpack), then better choice would be to require corresponding module for a target that you need. If you compile your app bundle down to ES5, then webpack config can look like that\n```javascript\n...\nresolve: {\n  mainFields: ['module', 'browser', 'main'],\n  ...\n}\n```\nso webpack will search for existence of ES module first for better bundling.\n\nIf you compile for modern browsers that [support](http://kangax.github.io/compat-table/es2016plus/) ES2015+ you can specify:\n```javascript\nmainFields: ['es2015', 'module', 'browser', 'main'],\n````\nor if latest browsers:\n```javascript\nmainFields: ['es2018', 'es2017', 'es2016', 'es2015', 'module', 'browser', 'main'],\n````\nYou get the idea. You can compile your application into several bundles with different compilation levels and have different webpack configs for them with different set of `mainFields`, to give to the modern browsers almost pure non compiled code, that takes less space and have fewer transformations, which leads to better performance.\n\n## Contribution\n\nYou are always welcome to: create github issues, make fixes or improvements in code, types, tests.\nClone project and run `npm install \u0026\u0026 npm run bootstrap` to start working.\nIf you using IDE, like WebStorm, add `--build` option to the typescript setup.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fklimashkin%2Fcss-modules-theme","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fklimashkin%2Fcss-modules-theme","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fklimashkin%2Fcss-modules-theme/lists"}