Code Monkey home page Code Monkey logo

react-scoped-css's Introduction

React scoped CSS

CSS encapsulation solution for React

npm npm Github Repo stars npm

Why

In order to solve the problem of css encapsulation, there are two main approaches, css-modules and css-in-js. However, both of them have a very very big problem. The developer experience is not good, by which I mean you often have to write more code than you expect to achieve a simple style. With react-scoped-css, you can just write the normal css you know, while having the advantage of css encapsulation!

How does it work

Write your css in a file ends with .scoped.css (scss & sass are also supported)

/* Title.scoped.css */
.title {
  background: #999;
}

p {
  color: #ddd;
}

Import the css file

// Title.jsx
import React from 'react'
import './Title.scoped.css'

const Title = props => {
  return (
    <h1 className="title">
      <p>{props.children}</p>
    </h1>
  )
}

export default Title

Then, in the html, component with scoped css file imported has a unique data-v-<hash> attribute on the html element tag, and the css selector also has a corresponding hash like selector[data-v-<hash>]. So all the styles in Title.scoped.css are scoped to Title.jsx. The output will be something like the following.

<h1 class="title" data-v-15763057=""><p data-v-15763057="">React scoped CSS</p></h1>
.title[data-v-15763057] {
  background: #309dcf;
}
p[data-v-15763057] {
  color: #fff;
}

How to use

Use in an existing create-react-app project (which hasn't been ejected yet)

Since create-react-app doesn't allow you to change webpack and babel config. So in this scenario, you have to use craco to override webpack config. Luckily you don't have to do it manually, I created a craco plugin that can do it for you.

Setup craco following this guide

Then, install craco-plugin-scoped-css

yarn add craco-plugin-scoped-css

create a craco.config.js in your project root

module.exports = {
  plugins: [
    {
      plugin: require('craco-plugin-scoped-css'),
    },
  ],
}

Manual setup

You have to add one babel plugin and one webpack loader.

the babel plugin

yarn add babel-plugin-react-scoped-css --dev

and in your babelrc add

"plugins": ["babel-plugin-react-scoped-css"]

also note that you can define your own matching rule like this

"plugins": [
  [
    "babel-plugin-react-scoped-css",
    {
      "include": ".local.(sa|sc|c)ss$"
    }
  ]
]

If you have other plugins installed, just add it to the list, order doesn't matter.

Plugin options:

  • include(optional, RegExp, defaults to /\.scoped\.(sa|sc|c)ss$/): config which css file to be identified as scoped
  • hashSeed (optional, string): used to calculate attribute hash. (TODO: a better explanation)

the webpack loader

yarn add scoped-css-loader --dev

and in your webpack.config.js

{
  test: /\.(sc|c|sa)ss$/,
  use: [
    {
      loader: 'style-loader',
    },
    {
      loader: 'css-loader',
      options: {
        sourceMap: true,
        importLoaders: 2,
      },
    },
    // You have to put it after `css-loader` and before any `pre-precessing loader`
    { loader: 'scoped-css-loader' },
    {
      loader: 'sass-loader',
    },
  ],
},

That's it for the configuration.

About the deep selector

If you want to affect the child components, you can use the deep selector.

.parent /deep/ .child {
  ...
}

This will be converted to

.parent[data-v-15760357] .child {
  ...
}

This method might be useful when you want to override the styles of the external libraries.

However, in the recent implementation of sass (dart-sass), /deep/ is not supported. You can use ::v-deep instead. (related issue)

.parent::v-deep .child {
  ...
}

When you want to use that, make sure that the version of @vue/component-compiler-utils installed in your project's node_modules is >= 2.6.0.

Examples

Common errors

Fragment : Invalid prop data-v-xxxxxxxx supplied to React.Fragment

react-scoped-css won't add data-v attribute to <></> or <React.Fragment></React.Fragment>. But it won't know for sure that <Fragment> is a react fragment and assigned data-v- to it (related issue)

Invalid code

import React, { Fragment } from 'react'

export function MyComponent() {
  return (
    <Fragment>
      ...
    </Fragment>
  )
}

Valid code

import React from 'react'

export function MyComponent() {
  return (
    <>
      ...
    </>
  )
}
import React from 'react'

export function MyComponent() {
  return (
    <React.Fragment>
      ...
    </React.Fragment>
  )
}

react-scoped-css's People

Contributors

avantgarde95 avatar bakujun avatar dependabot[bot] avatar gaoxiaoliangz avatar ni55an avatar taranys avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

react-scoped-css's Issues

[BUG] not support scss interpolation

use interpolation in scss will error:

Syntax Error: SyntaxError

(12:11) Unknown word

  10 |   @each $side in $sides {
  11 |     @if $side == 'all' {
> 12 |       .m#{str-slice($side, 0, 1)}-#{$space} {
     |           ^
  13 |         margin: gen_spacing($space);
  14 |       }

Parcel support ?

Your repo looks amazing.

If I understand correctly, this has webpack as a dependency, hasn't it ? Or only babel?

I'm using parcel.js, any chance I can use this?

Not applying effect in components

Has some way to apply effect in my external components imported in a file with scope?

example:

import 'style.scoped.sass'
import Component from 'component'

export default function foo() {
  return (
    <Component className="class_from_style_scoped" />
  )
}

Thanks :)

:global is not wokring

How to use :global keyword to make scope global within a file?

.item {
  color:red;
}

:global(.item2) {
    color: blue;
}

Expected result is the item2 class to be global. But the exported css is

.item[data-v-9c56d123] {
  color: red;
}
[data-v-9c56d123]:global(.item2) {
  color: blue;
}

Add docs for use with `customize-cra`

customize-cra is also a pretty popular cra config customizer. I am using antd and it is recommended in the docs.

If you could explain to me the logic in the craco plugin I might be able to get it to work with this also.

about package name

In fact, this project name is more suitable as jsx-scoped-css because it is suitable for all JSX projects.

Requires babel?

It seems like this solution is tightly-coupled with babel as it only works if you add a plugin to your .babelrc file. I'm using Typescript as a transpiler. Is there any way I can use this library?

Override Antd Select

Hi, I came from here https://stackoverflow.com/a/57654191/7377225, and I wish to use this to override the styles of Select component in antd in another scoped component. However, after installing, the styles still applies to globally. Please see my code.

CustomSelect.js

import React from 'react';
import {  Select } from 'antd';
import './customSelect.scoped.css';
const { Title, Text } = Typography;
const { Option } = Select;

export default (props) => {
  return (
    <Select placeholder="">
        <Option  value={1}>1</Option>
    </Select>
  );
};

customSelect.scoped.css

.ant-select-single.ant-select-show-arrow .ant-select-selection-item,
.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder {
  padding: 0;
}
.ant-select-single.ant-select-show-arrow .ant-select-selection-item,
.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder {
  padding: 0;
}
.ant-select-single.ant-select-show-arrow .ant-select-selection-item,
.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder {
  font-size: 18px;
}
.ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
  height: 80px;
  padding: 0px;
}

Then I have tried to use it in another component like this. Both still have the same styles where I expected that only the CustomSelect component should have had the 80px height.
AnotherComponent.js

import React from 'react';
import {  Select } from 'antd';
import CustomSelect from '../components/CustomSelect/CustomSelect';

export default (props) => {
  return (
    <Select placeholder="">
        <Option  value={1}>1</Option>
    </Select>
    <CustomSelect placeholder="">
        <Option  value={1}>1</Option>
    </CustomSelect>
  );
};

feature request: mock for jest snapshots

Hello, love this babel plugin!

My only issue with it is that components with scoped css will fail Jest snapshot testing regularly.

Screen Shot 2020-05-20 at 1 46 32 PM

I'm working on getting a mock to work, but I know it's complicated with babel and such. If I get it to work, I'll add a PR to add to the documentation.

Cannot install with npm

Hi, I got this error installing it with npm 7.11.2 and nodejs 12.16.1

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: @craco/[email protected]
npm ERR! node_modules/@craco/craco
npm ERR!   @craco/craco@"6.1.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer @craco/craco@"^5.3.0" from [email protected]
npm ERR! node_modules/craco-plugin-scoped-css
npm ERR!   craco-plugin-scoped-css@"1.1.0" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

Doesn't work with React.createElement

Hey -- great package. That said, any plans to allow this to work with createElement?

When using createElement, it does add a hashed attribute, however, it doesn't correspond to the one attached to the class in the generated CSS.

Dealing with React Fragment

When using 'babel-plugin-react-scoped-css' with <Fragment>, React gives a warning in DevTools Console:

Warning: Invalid prop data-v-d2ab454f supplied to React.Fragment. React.Fragment can only have key and children props.

The component:

import React, {Component, Fragment} from 'react';

export default class Test extends Component {
  constructor (props) {
    super(props);
    this.state = {};
  }
  render () {
    return <Fragment>
      <div>Part 1</div>
      <div>Part 2</div>
    </Fragment>;
  }
};

Can this work with Parcel.js to build?

this is my build script, and it does not look like it is using to craco config to build


    "build": "parcel build src/index.js --no-source-maps ",

P.s. The reason I use this is because I want the build to just consist of 2 files. index.js & index.css. How would I achieve that with craco?

Compile bundle file

Maybe this isn't a issue, but i am struggling with this.

I am very happy with the funcionality of this library but now i have to create a bundle file for a widget that i developed.

I guess that i have to load a webpack config in the craco.config.js file, but i don't know exactly how to do that.

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: false,
      },
    },
  },

  webpack: {
    configure: {
      output: {
        filename: 'static/js/[name].bundle.js',
      },
    },
  },

  plugins: [
    {
      plugin: require('craco-plugin-scoped-css'),
    },
  ],
}

I would appreciate any information regarding this.

Is it possible to use with react-app-rewired?

This lib seem to work really well with component-scoped css and "global" css. The problem is that, according to README.md (correct me if I am wrong), there is only 2 options to set it up: we have to use a craco plugin or setup manually on ejected projects.
I personally use react-app-rewired on one of my projects. It is required for ant design to work, so if I replace react-app-rewired with craco, ant design simply stops working.
So, is it possible to setup react-scoped-css on react-app-rewired based projects or to have a plugin similar to craco-plugin-scoped-css, but for react-app-rewired?

How can I use with create-next-app?

I'm trying to use the following configuration in an application created with create-next-app:

  • .babelrc
{
  "presets": [
    "next/babel"
  ],
  "plugins": [
    [
      "babel-plugin-react-scoped-css",
      {
        "include": ".scoped.(css|scss)$"
      }
    ]
  ]
}
  • next.config.js
module.exports = {
  reactStrictMode: true,
  webpack(config) {
    config.module.rules.push({
      test: /\.(sc|c|sa)ss$/,
      use: [
        {
          loader: 'style-loader',
        },
        {
          loader: 'css-loader',
          options: {
            sourceMap: true,
            importLoaders: 2,
          },
        },
        {
          loader: 'scoped-css-loader'
        },
        {
          loader: 'sass-loader',
        },
      ],
    })

    return config
  },
}

But when I import the .scss or .css file and run my app, I have the error in my terminal:

> error - node_modules\style-loader\dist\runtime\insertStyleElement.js (5:0) @ Object.insertStyleElement
> ReferenceError: document is not defined
> null

What am I doing wrong?

This is amazing, thanks for sharing!

I love how CSS works in Vue and I have been struggling to find a solution for React that works the same way. So far so good with your solution!

Cheers!

Usage for vite

Hi,

I have my react built upon vite,

Is there any support for vite? And if yes, can the readme be updated how to implement for vite?

Disabling code splitting

I'm bundling an application for offline use, where it's more important that the entire app loads at once and stays in RAM, than it is for it to load quickly over a network connection. How do I use craco to disable code splitting?

Include component name of path in hash?

Lets say, I have css class 'foo' for components Baa and Qux... and I debug the final application with chrome dev tools...
how to I know where the css class foo[hash] comes from?

=>How can I include the name of the component in the hash to get

foo[Baa-hash] vs. foo[Qux-hash]

If I have two components with the same name in different packages, It would be good to include the full path:

foo[package_a/Baa-hash] vs. foo[package_b/Baa-hash]

data attribute is not applied to elements created from components that are not accessed directly

Basically, this

<HDialog open={open} static onClose={onClose} className="dialog">
	<HDialog.Overlay className="dialog-overlay" />
	<HDialog.Title className="dialog-title">{title}</HDialog.Title>
	<HDialog.Description className="dialog-description">{description}</HDialog.Description>
	{children}
</HDialog>

does not work, but this

const Overlay = HDialog.Overlay
const Title = HDialog.Title
const Description = HDialog.Description
const dialog = <HDialog open={open} static onClose={onClose} className="dialog">
	<Overlay className="dialog-overlay" />
	<Title className="dialog-title">{title}</Title>
	<Description className="dialog-description">{description}</Description>
	{children}
</HDialog>

does.

This has led to false bug reports like tailwindlabs/headlessui#696 because I didn't take it into a debugger and closely inspect the props being passed down

SCSS nesting doesn't work

Hi there,

first I want to say thank you for this nice lib.

In my project nesting classes doesn't work:

webpack config:

 {
                    "test": /\.(sc|c|sa)ss$/,
                    "use": [
                        {
                            "loader": "style-loader"
                        },
                        {
                            "loader": "css-loader"
                        },
                        { "loader": "scoped-css-loader" },
                        {
                            "loader": "sass-loader"
                        }
                    ]
                },

input:

navbar.scoped.scss:

.navbar {
    .navbar-item{
        color: #fff;
    }

navbar.jsx:

...
import "./navbar.scoped.scss";
...
<nav ref={this.headerRef} className="navbar">
    <NavbarBrand src={imgUrl} title={title} />
</nav>

navbarbrand.jsx:

<div className="navbar-brand">
    <NavbarLogo src={src} />
</div>

Output:

HTML:

<nav class="navbar" data-v-a866645d="">
   <div class="navbar-brand">
      <a class="logo navbar-item" href="/" aria-current="page"><img src="static/logo.png"></a>
   </div>
</nav>

CSS:

.navbar .navbar-item[data-v-a866645d]  {
    color: #fff;
}

I would expect this result:

.navbar[data-v-a866645d] .navbar-item  {
    color: #fff;
}

Not compatible with framer-motion components

Problem

When using a framer-motion component such as motion.button, the output html element does not have the data-v-hash attribute.
Thus, the styles don't even get applied at all even after importing the stylesheet e.g. import "./button.scss"

Expected behaviour

Ideally it should have the data-v-hash attribute so that the styles get applied and are scoped only to that component as intended

Steps to reproduce

  1. Follow the steps in this repository's instructions to use react-scoped-css.
  2. Install framer-motion
  3. Create a framer-motion component
  4. Try applying a scoped file to that framer-motion component e.g. `import "./button.scss". In the dev tools you can see that the stylesheet is there but the styles are not applied as the output html element does not have the hash that the styles selectors do.

Comments

I believe this is similar to issue #52 and that this package simply does not work for 3rd party library components, it only works for normal react components.

Is it possible to inherit classes from parent?

Screenshot_2

That is, the scoped is only one way.

For example:

App.jsx consists of:

  • Main.jsx - main.scoped.css / consists of:
    • Component.jsx - main.scoped.css
    • Other.jsx - main.scoped.css
  • Asd.jsx - style.scoped.css / consists of:
    • Component.jsx - style.scoped.css
    • Other.jsx - style.scoped.css

style.scoped.css and main.scoped.css have intersections in class names

how to install

do I need to install any npm module before craco if I am using CRA?
seems like scoped-css-loader needs to be installed but not in the document.

Scope in third-party interfaces.

I'm using Formio Js in React for render a Form builder, but they components aren't affected with the scope hash.

image

Only components created by me are affected correctly.

Help! and Thanks.

/deep/ selector issues with dart-sass

"react": "^18.2.0",
"react-bootstrap": "^2.5.0",
"react-datepicker": "^4.8.0",
"react-dom": "^18.2.0",
"react-i18next": "^11.18.5",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"craco-plugin-scoped-css": "^1.1.1",

node version : 16.13

Using the above dependency, the /deep/ selector cannot be used. Can you specify an alternative selector?

Error on a React Fragment

Hi ! I have an error with the scoped styles when my component is nested in a Fragment :
Invalid prop data-v-4e235659 supplied to React.Fragment

How can I avoid it ?

Thanks !

Problem with Test with jest

I have a problem trying to test a component that imports another component with .scoped.scss.
Something along these lines:

- components
   - some-component.tsx
- pages
   - some-page.tsx // this imports the some-component

When I run a test I get the error:
Cannot find module: some-component.scoped.scss?scopeId=xxxxx

I already configured jest.config with moduleNameMapper '\.(css|scss|sass)$': 'identity-obj-proxy',

I can't get it to work with RedwoodJS

Well, I used the craco plugin approach, and it only "kinda/semi" works — the styles are added but without the ids (at all). That is, all the styling becomes global... :(

targeting untagged elements

I have a prose class from tailwindcss that styles all p and a and h1 and etc. elements inside of it, but the problem is those p and a and h1 elements do not have the data-v-whatever tag that react-scoped-css adds. So I need to find a way to target those, but it appears this isn't supported. :/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.