rollup-plugin-react-scoped-css 1.2.7 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +135 -0
- package/CONTRIBUTING.md +182 -0
- package/README.md +246 -26
- package/dist/css/scope-css.js +12 -0
- package/package.json +14 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
- Demonstrating empathy and kindness toward other people
|
|
21
|
+
- Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
- Giving and gracefully accepting constructive feedback
|
|
23
|
+
- Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
- Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
+
community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
- The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
+
any kind
|
|
32
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
- Public or private harassment
|
|
34
|
+
- Publishing others' private information, such as a physical or email address,
|
|
35
|
+
without their explicit permission
|
|
36
|
+
- Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official e-mail address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported privately to the project maintainer on GitHub at
|
|
63
|
+
[@cmseguin](https://github.com/cmseguin). If you would rather not contact the
|
|
64
|
+
maintainer directly, you can use GitHub's
|
|
65
|
+
[report abuse](https://github.com/contact/report-abuse) form.
|
|
66
|
+
|
|
67
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
68
|
+
|
|
69
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
70
|
+
reporter of any incident.
|
|
71
|
+
|
|
72
|
+
## Enforcement Guidelines
|
|
73
|
+
|
|
74
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
75
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
76
|
+
|
|
77
|
+
### 1. Correction
|
|
78
|
+
|
|
79
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
80
|
+
unprofessional or unwelcome in the community.
|
|
81
|
+
|
|
82
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
83
|
+
clarity around the nature of the violation and an explanation of why the
|
|
84
|
+
behavior was inappropriate. A public apology may be requested.
|
|
85
|
+
|
|
86
|
+
### 2. Warning
|
|
87
|
+
|
|
88
|
+
**Community Impact**: A violation through a single incident or series of
|
|
89
|
+
actions.
|
|
90
|
+
|
|
91
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
92
|
+
interaction with the people involved, including unsolicited interaction with
|
|
93
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
94
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
95
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
|
96
|
+
ban.
|
|
97
|
+
|
|
98
|
+
### 3. Temporary Ban
|
|
99
|
+
|
|
100
|
+
**Community Impact**: A serious violation of community standards, including
|
|
101
|
+
sustained inappropriate behavior.
|
|
102
|
+
|
|
103
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
104
|
+
communication with the community for a specified period of time. No public or
|
|
105
|
+
private interaction with the people involved, including unsolicited interaction
|
|
106
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
107
|
+
Violating these terms may lead to a permanent ban.
|
|
108
|
+
|
|
109
|
+
### 4. Permanent Ban
|
|
110
|
+
|
|
111
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
112
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
113
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
114
|
+
|
|
115
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
116
|
+
community.
|
|
117
|
+
|
|
118
|
+
## Attribution
|
|
119
|
+
|
|
120
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
121
|
+
version 2.1, available at
|
|
122
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
123
|
+
|
|
124
|
+
Community Impact Guidelines were inspired by
|
|
125
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
126
|
+
|
|
127
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
128
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
129
|
+
[https://www.contributor-covenant.org/translations][translations].
|
|
130
|
+
|
|
131
|
+
[homepage]: https://www.contributor-covenant.org
|
|
132
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
133
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
134
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
135
|
+
[translations]: https://www.contributor-covenant.org/translations
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Contributing to rollup-plugin-react-scoped-css
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in improving the plugin! Contributions of every size
|
|
4
|
+
are welcome — bug reports, documentation fixes, new test cases, and code.
|
|
5
|
+
|
|
6
|
+
## Code of Conduct
|
|
7
|
+
|
|
8
|
+
This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). By
|
|
9
|
+
participating, you are expected to uphold it. Disagreement about technical
|
|
10
|
+
decisions is fine; personal attacks, harassment, and abusive behavior are not.
|
|
11
|
+
Report unacceptable behavior through the channels listed in the policy.
|
|
12
|
+
|
|
13
|
+
Anyone is free to open a PR and contribute to this project... just be civilized!
|
|
14
|
+
|
|
15
|
+
## Ways to contribute
|
|
16
|
+
|
|
17
|
+
### Reporting bugs
|
|
18
|
+
|
|
19
|
+
Before opening an issue, please search
|
|
20
|
+
[existing issues](https://github.com/cmseguin/rollup-plugin-react-scoped-css/issues)
|
|
21
|
+
to avoid duplicates. A good bug report includes:
|
|
22
|
+
|
|
23
|
+
- the plugin version (from `package.json` or `npm ls rollup-plugin-react-scoped-css`),
|
|
24
|
+
- your bundler and version (Vite, Rollup, or Rolldown), plus your Node version,
|
|
25
|
+
- a minimal reproduction — the smallest component and stylesheet that show the
|
|
26
|
+
problem, ideally as a link to a repository or a code sandbox,
|
|
27
|
+
- the actual output and the output you expected,
|
|
28
|
+
- any error output in full.
|
|
29
|
+
|
|
30
|
+
### Suggesting features
|
|
31
|
+
|
|
32
|
+
Open an issue describing the problem you are trying to solve before writing
|
|
33
|
+
code. Explain the use case and, if possible, how other libraries handle it.
|
|
34
|
+
Features that can be implemented by an existing CSS or bundler plugin are
|
|
35
|
+
usually out of scope — this plugin deliberately does not transpile JSX,
|
|
36
|
+
preprocess SCSS/LESS, or bundle CSS.
|
|
37
|
+
|
|
38
|
+
### Improving documentation
|
|
39
|
+
|
|
40
|
+
The README is the primary documentation. Fixes for typos, unclear examples, or
|
|
41
|
+
missing caveats are always appreciated and can go straight to a pull request.
|
|
42
|
+
|
|
43
|
+
## Development setup
|
|
44
|
+
|
|
45
|
+
**Requirements:** Node >= 18 (CI runs Node 24) and npm.
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
git clone https://github.com/cmseguin/rollup-plugin-react-scoped-css.git
|
|
49
|
+
cd rollup-plugin-react-scoped-css
|
|
50
|
+
npm ci
|
|
51
|
+
npm run build
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Useful scripts:
|
|
55
|
+
|
|
56
|
+
| Script | What it does |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `npm run build` | Type-checks and compiles `src/` to `dist/` with `tsc` |
|
|
59
|
+
| `npm run build:watch` | Rebuilds on change |
|
|
60
|
+
| `npm test` | Runs the Jest unit test suite |
|
|
61
|
+
| `npm run lint` | Runs ESLint (including Prettier formatting checks) |
|
|
62
|
+
| `npm run test:e2e` | Runs the browser compatibility harness (see below) |
|
|
63
|
+
|
|
64
|
+
## Project structure
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
src/
|
|
68
|
+
├── index.ts # Plugin entry point: the Vite plugin and the
|
|
69
|
+
│ # pre/post halves used by Rollup and Rolldown
|
|
70
|
+
├── ast-program.ts # Walks the compiled React output
|
|
71
|
+
├── ast-transformer.ts # Adds the data-<hash> attributes to JSX elements
|
|
72
|
+
├── ast-iterator.ts # Generic AST traversal helpers
|
|
73
|
+
├── parsers/ # Babel output parsers (classic and automatic JSX)
|
|
74
|
+
├── css/
|
|
75
|
+
│ ├── scope-css.ts # The CSS scoper and selector rewriting
|
|
76
|
+
│ └── css-features.spec.ts # CSS support matrix tests
|
|
77
|
+
└── utils/ # Shared helpers
|
|
78
|
+
e2e/ # Playwright harness for Vite, Rollup, and Rolldown
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Unit tests live next to the code they cover as `*.spec.ts`. The e2e harness
|
|
82
|
+
installs the packed plugin into isolated throwaway projects under
|
|
83
|
+
`e2e/.cache/`, so it is slow but exercises the real published artifact.
|
|
84
|
+
|
|
85
|
+
## Running tests
|
|
86
|
+
|
|
87
|
+
Unit tests are fast and should cover any new scoping logic:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
npm test
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The end-to-end harness drives a real browser against Vite 2–8, Rollup, and
|
|
94
|
+
Rolldown. It needs Node >= 20.19 and a Playwright browser:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
npm run test:e2e:install # once; installs Chromium
|
|
98
|
+
npm run test:e2e # all bundlers
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
See the [Testing](README.md#testing) section of the README for the full option
|
|
102
|
+
list. If your change touches CSS scoping, please add cases to
|
|
103
|
+
`src/css/scope-css.spec.ts` or `src/css/css-features.spec.ts` rather than
|
|
104
|
+
relying on the e2e harness alone.
|
|
105
|
+
|
|
106
|
+
## Coding standards
|
|
107
|
+
|
|
108
|
+
- **TypeScript** with `strict` mode enabled. Add types; avoid `any`.
|
|
109
|
+
- **Formatting** is enforced by Prettier through ESLint. Run `npm run lint`
|
|
110
|
+
before pushing (or `npx eslint --fix` on the files you changed).
|
|
111
|
+
- Match the style of the surrounding code: small, focused modules with
|
|
112
|
+
descriptive names.
|
|
113
|
+
- Keep the plugin's scope tight. It transforms CSS and JSX; it should not start
|
|
114
|
+
transpiling, bundling, or resolving dependencies.
|
|
115
|
+
- New behavior needs tests. New options need a JSDoc comment in the plugin
|
|
116
|
+
options type and a README entry.
|
|
117
|
+
|
|
118
|
+
## Commit messages
|
|
119
|
+
|
|
120
|
+
This project uses [Conventional Commits](https://www.conventionalcommits.org/)
|
|
121
|
+
and releases are automated with
|
|
122
|
+
[semantic-release](https://github.com/semantic-release/semantic-release). The
|
|
123
|
+
commit message controls the next version number, so it matters:
|
|
124
|
+
|
|
125
|
+
| Prefix | Use for | Effect |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| `feat:` | A new feature | Minor release |
|
|
128
|
+
| `fix:` | A bug fix | Patch release |
|
|
129
|
+
| `docs:` | Documentation only | No release |
|
|
130
|
+
| `test:` | Tests only | No release |
|
|
131
|
+
| `chore:` | Tooling, dependencies, CI | No release |
|
|
132
|
+
| `refactor:` | Code change with no behavior change | No release |
|
|
133
|
+
| `perf:` | Performance improvement | Patch release |
|
|
134
|
+
|
|
135
|
+
A scope is optional but encouraged, e.g. `fix(css): scope selectors inside
|
|
136
|
+
container queries`. A `!` after the type or scope marks a breaking change:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
feat(css)!: rename the deep selector to ::deep
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Pull requests are squash-merged, so the PR title becomes the commit message —
|
|
143
|
+
please make it conventional too.
|
|
144
|
+
|
|
145
|
+
## Pull request process
|
|
146
|
+
|
|
147
|
+
1. Fork the repository and create a topic branch from `main`
|
|
148
|
+
(`git checkout -b fix/scoped-at-rules`).
|
|
149
|
+
2. Make your change, add tests, and update the README if you changed behavior
|
|
150
|
+
or options.
|
|
151
|
+
3. Make sure the full suite passes locally:
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
npm run build && npm test && npm run lint
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
4. Open the pull request against `main`. Describe the problem, the approach,
|
|
158
|
+
and any trade-offs. Link related issues with `Closes #123`.
|
|
159
|
+
5. CI runs the build, unit tests, lint, and the e2e harness. All jobs must be
|
|
160
|
+
green before a review.
|
|
161
|
+
6. Address review feedback by pushing additional commits; the branch is
|
|
162
|
+
squash-merged when approved.
|
|
163
|
+
|
|
164
|
+
Draft pull requests are welcome if you want early feedback on an approach.
|
|
165
|
+
|
|
166
|
+
## Release process
|
|
167
|
+
|
|
168
|
+
Releases are cut automatically from `main` by semantic-release on every merge.
|
|
169
|
+
It determines the version from the commit history, publishes to npm, creates the
|
|
170
|
+
GitHub release with generated notes, and commits the new version back to the
|
|
171
|
+
repository with `[skip ci]`. Maintainers do not publish manually — merging a
|
|
172
|
+
`feat:` or `fix:` pull request is the release.
|
|
173
|
+
|
|
174
|
+
Prereleases are published under the `alpha` npm tag from `releases/<version>-alpha`
|
|
175
|
+
branches, independently of `main`. You do not need to do anything special for
|
|
176
|
+
those; contributors work against `main`.
|
|
177
|
+
|
|
178
|
+
## Questions
|
|
179
|
+
|
|
180
|
+
If anything here is unclear, open a
|
|
181
|
+
[discussion or issue](https://github.com/cmseguin/rollup-plugin-react-scoped-css/issues).
|
|
182
|
+
We would rather answer a question than have you guess.
|
package/README.md
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/rollup-plugin-react-scoped-css)
|
|
4
4
|
[](https://www.npmjs.com/package/rollup-plugin-react-scoped-css)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](CODE_OF_CONDUCT.md)
|
|
6
7
|
|
|
7
8
|
## Motivations
|
|
8
9
|
While using react in a professional context, I realized that it was lacking the scoped css feature that I learned to love from Vue and Angular. After some research I came across good plugins, but sadly were not compatible with vite and/or rollup. Thus, I decided to create this plugin.
|
|
9
10
|
|
|
10
11
|
## Requirements
|
|
11
12
|
+ **node** >= 18
|
|
12
|
-
+ **vite** >=
|
|
13
|
+
+ **vite** >= 2, or **rollup** >= 4, or **rolldown** >= 1
|
|
13
14
|
|
|
14
15
|
## How to install
|
|
15
16
|
|
|
@@ -32,19 +33,22 @@ export default defineConfig({
|
|
|
32
33
|
})
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
without vite (rollup or rolldown):
|
|
36
37
|
```js
|
|
37
|
-
// rollup.config.js
|
|
38
|
+
// rollup.config.js / rolldown.config.js
|
|
38
39
|
import { reactScopedCssPlugin } from 'rollup-plugin-react-scoped-css';
|
|
39
40
|
|
|
41
|
+
// The plugin returns two halves that must run before and after your JSX and
|
|
42
|
+
// CSS plugins.
|
|
43
|
+
const [scopedCssPre, scopedCssPost] = reactScopedCssPlugin();
|
|
44
|
+
|
|
40
45
|
export default {
|
|
41
|
-
input: 'src/
|
|
42
|
-
|
|
43
|
-
plugins: [ reactScopedCssPlugin() ]
|
|
46
|
+
input: 'src/main.tsx',
|
|
47
|
+
plugins: [scopedCssPre, /* JSX + CSS plugins go here */, scopedCssPost],
|
|
44
48
|
};
|
|
45
49
|
```
|
|
46
50
|
|
|
47
|
-
***Notes:** Because this plugin has been built with vite as its primary usecase, it doesn't transpile LESS, SCSS or other preprocessors.
|
|
51
|
+
***Notes:** Because this plugin has been built with vite as its primary usecase, it doesn't transpile JSX/TypeScript, LESS, SCSS or other preprocessors, and it doesn't process CSS imports. When you use Rollup or Rolldown directly, you need to plug in those pieces between the two halves of the plugin. See [Using without Vite](#using-without-vite-rollup-and-rolldown) for complete, working configs.*
|
|
48
52
|
|
|
49
53
|
### Customizing the plugin
|
|
50
54
|
There are a few options available to customize how the plugin works
|
|
@@ -102,17 +106,213 @@ There are a few options available to customize how the plugin works
|
|
|
102
106
|
}
|
|
103
107
|
```
|
|
104
108
|
|
|
105
|
-
###
|
|
106
|
-
|
|
109
|
+
### Using without Vite (Rollup and Rolldown)
|
|
110
|
+
The plugin works in two halves:
|
|
111
|
+
|
|
112
|
+
1. `scopedCssPre` rewrites scoped style imports such as
|
|
113
|
+
`./Component.scoped.css` to `./Component.scoped.css?scope=<hash>`.
|
|
114
|
+
2. `scopedCssPost` scopes the CSS and adds the matching `data-<hash>` attributes
|
|
115
|
+
to the compiled React output.
|
|
116
|
+
|
|
117
|
+
Vite orders these automatically through the `enforce` option. With Rollup or
|
|
118
|
+
Rolldown, `enforce` is ignored, so you must place them in the `plugins` array
|
|
119
|
+
yourself and put the following between them:
|
|
120
|
+
|
|
121
|
+
- **JSX/TypeScript transpilation first.** The second half inspects compiled
|
|
122
|
+
`React.createElement()` or `_jsx()` calls, not raw JSX. Babel (shown below) or
|
|
123
|
+
any equivalent transpiler works.
|
|
124
|
+
- **A CSS loader/emitter.** Rollup and Rolldown do not process `.css` imports
|
|
125
|
+
on their own. The bridge below reads the file, lets `scopedCssPost` transform
|
|
126
|
+
it, then emits the transformed CSS and replaces the import with an empty
|
|
127
|
+
module.
|
|
128
|
+
|
|
129
|
+
#### When is the bridge needed?
|
|
130
|
+
|
|
131
|
+
The bridge is needed when nothing else in your build turns `.css` imports into
|
|
132
|
+
JavaScript. Neither Rollup nor Rolldown does that on its own, so a plain config
|
|
133
|
+
fails as soon as it reaches the first style import.
|
|
134
|
+
|
|
135
|
+
Use the bridge when:
|
|
136
|
+
|
|
137
|
+
- you are adding the plugin to a plain Rollup or Rolldown config and want
|
|
138
|
+
minimal CSS handling — all styles collected into one stylesheet;
|
|
139
|
+
- you don't already have a CSS plugin that can load the scoped imports.
|
|
140
|
+
|
|
141
|
+
You can skip it when:
|
|
142
|
+
|
|
143
|
+
- Vite is handling CSS and plugin ordering for you;
|
|
144
|
+
- another CSS plugin already handles `.css` imports. Put that plugin between
|
|
145
|
+
`scopedCssPre` and `scopedCssPost`, and make sure it accepts the
|
|
146
|
+
`?scope=<hash>` query the plugin adds to scoped imports.
|
|
147
|
+
|
|
148
|
+
The bridge only does extraction. If you need style injection, CSS modules,
|
|
149
|
+
preprocessor support, or per-chunk styles, use a dedicated CSS plugin instead.
|
|
150
|
+
|
|
151
|
+
#### CSS bridge
|
|
152
|
+
Save this next to your config:
|
|
107
153
|
|
|
108
154
|
```js
|
|
109
|
-
|
|
155
|
+
// scoped-css-bridge.js
|
|
156
|
+
import { readFile } from 'node:fs/promises'
|
|
157
|
+
|
|
158
|
+
const stripQuery = (id) => id.split('?')[0]
|
|
159
|
+
const isCss = (id) => stripQuery(id).endsWith('.css')
|
|
160
|
+
|
|
161
|
+
export function scopedCssBridge() {
|
|
162
|
+
const styles = new Map()
|
|
163
|
+
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
name: 'scoped-css:load',
|
|
167
|
+
async load(id) {
|
|
168
|
+
if (isCss(id)) {
|
|
169
|
+
return readFile(stripQuery(id), 'utf8')
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'scoped-css:emit',
|
|
175
|
+
transform(code, id) {
|
|
176
|
+
if (!isCss(id)) return
|
|
177
|
+
styles.set(stripQuery(id), code)
|
|
178
|
+
return { code: 'export default undefined', map: null }
|
|
179
|
+
},
|
|
180
|
+
generateBundle() {
|
|
181
|
+
this.emitFile({
|
|
182
|
+
type: 'asset',
|
|
183
|
+
fileName: 'styles.css',
|
|
184
|
+
source: [...styles.values()].join('\n'),
|
|
185
|
+
})
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Rollup
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
npm i -D rollup @babel/core @babel/preset-react @babel/preset-typescript \
|
|
196
|
+
@rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve \
|
|
197
|
+
@rollup/plugin-replace
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
// rollup.config.js
|
|
202
|
+
import babel from '@rollup/plugin-babel'
|
|
203
|
+
import commonjs from '@rollup/plugin-commonjs'
|
|
204
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
|
205
|
+
import replace from '@rollup/plugin-replace'
|
|
206
|
+
import { reactScopedCssPlugin } from 'rollup-plugin-react-scoped-css'
|
|
207
|
+
import { scopedCssBridge } from './scoped-css-bridge.js'
|
|
208
|
+
|
|
209
|
+
const [scopedCssPre, scopedCssPost] = reactScopedCssPlugin()
|
|
210
|
+
const [cssLoader, cssEmitter] = scopedCssBridge()
|
|
211
|
+
|
|
110
212
|
export default {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
213
|
+
input: 'src/main.tsx',
|
|
214
|
+
output: {
|
|
215
|
+
dir: 'dist',
|
|
216
|
+
format: 'esm',
|
|
217
|
+
entryFileNames: 'main.js',
|
|
218
|
+
sourcemap: true,
|
|
219
|
+
},
|
|
220
|
+
plugins: [
|
|
221
|
+
scopedCssPre,
|
|
222
|
+
babel({
|
|
223
|
+
babelHelpers: 'bundled',
|
|
224
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
225
|
+
exclude: '**/node_modules/**',
|
|
226
|
+
presets: [
|
|
227
|
+
['@babel/preset-react', { runtime: 'automatic' }],
|
|
228
|
+
'@babel/preset-typescript',
|
|
229
|
+
],
|
|
230
|
+
}),
|
|
231
|
+
cssLoader,
|
|
232
|
+
scopedCssPost,
|
|
233
|
+
cssEmitter,
|
|
234
|
+
replace({
|
|
235
|
+
preventAssignment: true,
|
|
236
|
+
values: { 'process.env.NODE_ENV': JSON.stringify('production') },
|
|
237
|
+
}),
|
|
238
|
+
nodeResolve({ extensions: ['.js', '.jsx', '.ts', '.tsx'] }),
|
|
239
|
+
commonjs(),
|
|
240
|
+
],
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`nodeResolve` and `commonjs` are only needed if React is bundled rather than
|
|
245
|
+
loaded externally. `replace` is needed because React's CommonJS build checks
|
|
246
|
+
`process.env.NODE_ENV` at runtime.
|
|
247
|
+
|
|
248
|
+
#### Rolldown
|
|
249
|
+
|
|
250
|
+
Rolldown resolves node_modules and bundles CommonJS itself, but Babel still has
|
|
251
|
+
to run before `scopedCssPost`, and CSS has to be treated as JavaScript for the
|
|
252
|
+
bridge to take over.
|
|
253
|
+
|
|
254
|
+
```sh
|
|
255
|
+
npm i -D rolldown @babel/core @babel/preset-react @babel/preset-typescript \
|
|
256
|
+
@rollup/plugin-babel
|
|
114
257
|
```
|
|
115
258
|
|
|
259
|
+
```js
|
|
260
|
+
// rolldown.config.js
|
|
261
|
+
import { defineConfig } from 'rolldown'
|
|
262
|
+
import babel from '@rollup/plugin-babel'
|
|
263
|
+
import { reactScopedCssPlugin } from 'rollup-plugin-react-scoped-css'
|
|
264
|
+
import { scopedCssBridge } from './scoped-css-bridge.js'
|
|
265
|
+
|
|
266
|
+
const [scopedCssPre, scopedCssPost] = reactScopedCssPlugin()
|
|
267
|
+
const [cssLoader, cssEmitter] = scopedCssBridge()
|
|
268
|
+
|
|
269
|
+
export default defineConfig({
|
|
270
|
+
input: 'src/main.tsx',
|
|
271
|
+
platform: 'browser',
|
|
272
|
+
// Rolldown no longer bundles CSS itself. The bridge emits it instead.
|
|
273
|
+
moduleTypes: { '.css': 'js' },
|
|
274
|
+
transform: {
|
|
275
|
+
define: { 'process.env.NODE_ENV': JSON.stringify('production') },
|
|
276
|
+
},
|
|
277
|
+
plugins: [
|
|
278
|
+
scopedCssPre,
|
|
279
|
+
babel({
|
|
280
|
+
babelHelpers: 'bundled',
|
|
281
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
282
|
+
exclude: '**/node_modules/**',
|
|
283
|
+
presets: [
|
|
284
|
+
['@babel/preset-react', { runtime: 'automatic' }],
|
|
285
|
+
'@babel/preset-typescript',
|
|
286
|
+
],
|
|
287
|
+
}),
|
|
288
|
+
cssLoader,
|
|
289
|
+
scopedCssPost,
|
|
290
|
+
cssEmitter,
|
|
291
|
+
],
|
|
292
|
+
output: {
|
|
293
|
+
dir: 'dist',
|
|
294
|
+
format: 'esm',
|
|
295
|
+
entryFileNames: 'main.js',
|
|
296
|
+
sourcemap: true,
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
#### Notes
|
|
302
|
+
- Neither Rollup nor Rolldown generates an HTML entry. Load the emitted files
|
|
303
|
+
yourself, for example
|
|
304
|
+
`<link rel="stylesheet" href="./styles.css">` and
|
|
305
|
+
`<script type="module" src="./main.js"></script>`.
|
|
306
|
+
- If you use a CSS plugin such as `rollup-plugin-postcss` instead of the bridge,
|
|
307
|
+
make sure it accepts the `?scope=<hash>` query that `scopedCssPre` adds and
|
|
308
|
+
runs between the two halves of the plugin.
|
|
309
|
+
- Preprocessors (SCSS, LESS, etc.) still need to run before `scopedCssPost`.
|
|
310
|
+
Compile them to CSS inside the bridge (or add a preprocessor plugin between
|
|
311
|
+
`cssLoader` and `scopedCssPost`) so the scoper receives plain CSS.
|
|
312
|
+
- `scopedCssPost` must stay after your JSX transpiler and after the CSS loader,
|
|
313
|
+
and the CSS emitter must stay after `scopedCssPost`.
|
|
314
|
+
|
|
315
|
+
|
|
116
316
|
## Usage
|
|
117
317
|
|
|
118
318
|
```jsx
|
|
@@ -170,24 +370,34 @@ Be careful with descendant selectors in recursive components! For a CSS rule wit
|
|
|
170
370
|
npm test
|
|
171
371
|
```
|
|
172
372
|
|
|
173
|
-
### End-to-end
|
|
373
|
+
### End-to-end compatibility harness
|
|
374
|
+
|
|
375
|
+
The e2e harness packs the plugin with `npm pack`, installs it into isolated
|
|
376
|
+
projects, then drives a real browser against each setup:
|
|
174
377
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
378
|
+
- **Vite 2, 3, 4, 5, 6, 7, and 8** — the dev server and a production build
|
|
379
|
+
served with `vite preview`.
|
|
380
|
+
- **Rollup** — a production build using `@rollup/plugin-babel`, the CSS bridge
|
|
381
|
+
documented above, and the bundled React runtime, served by a static Node
|
|
382
|
+
server.
|
|
383
|
+
- **Rolldown** — the same production build and CSS bridge with Rolldown's
|
|
384
|
+
built-in resolver, served the same way.
|
|
178
385
|
|
|
179
386
|
Prerequisites: Node >= 20.19 and a Playwright browser.
|
|
180
387
|
|
|
181
388
|
```sh
|
|
182
389
|
npm run test:e2e:install # once; installs Chromium
|
|
183
|
-
npm run test:e2e #
|
|
390
|
+
npm run test:e2e # all bundlers (Vite matrix + Rollup + Rolldown)
|
|
391
|
+
npm run test:e2e:vite # Vite only
|
|
392
|
+
npm run test:e2e:bundlers # Rollup and Rolldown only
|
|
184
393
|
```
|
|
185
394
|
|
|
186
395
|
Options (pass-through after `--`):
|
|
187
396
|
|
|
188
397
|
```sh
|
|
189
|
-
npm run test:e2e -- --
|
|
190
|
-
npm run test:e2e -- --
|
|
398
|
+
npm run test:e2e -- --bundler rollup # vite, rollup, rolldown, or all (default)
|
|
399
|
+
npm run test:e2e -- --bundler vite --vite 7 # one Vite major
|
|
400
|
+
npm run test:e2e -- --mode dev # dev, build, or both (Vite only)
|
|
191
401
|
npm run test:e2e -- --browser firefox # chromium (default), firefox, webkit
|
|
192
402
|
npm run test:e2e -- --headed # watch the browser
|
|
193
403
|
npm run test:e2e -- --clean --force # wipe and reinstall cached installs
|
|
@@ -195,25 +405,35 @@ npm run test:e2e -- --keep-temp # keep production build output
|
|
|
195
405
|
npm run test:e2e -- --allow-broken-sourcemap
|
|
196
406
|
```
|
|
197
407
|
|
|
408
|
+
`--vite` and `--mode` only affect the Vite matrix. The Rollup and Rolldown
|
|
409
|
+
setups run production builds only, so `--mode dev` fails when either is
|
|
410
|
+
selected.
|
|
411
|
+
|
|
198
412
|
The harness asserts that:
|
|
199
413
|
|
|
200
414
|
- scoped JSX receives `data-<hash>` attributes and every element from a file
|
|
201
415
|
shares that file's hash;
|
|
202
416
|
- scoped selectors are applied, global styles stay global, and styles from one
|
|
203
417
|
component do not leak into another;
|
|
204
|
-
- computed styles match in
|
|
418
|
+
- computed styles match in Vite dev, Vite production, and the standalone
|
|
419
|
+
bundles;
|
|
205
420
|
- emitted CSS contains the scoped selectors and JS contains the matching hash;
|
|
206
421
|
- JS sourcemaps reference the original `.tsx` files and include their content;
|
|
207
|
-
- there are no page/console/network errors and
|
|
422
|
+
- there are no page/console/network errors, and Vite runs emit no
|
|
423
|
+
`SOURCEMAP_BROKEN` warnings.
|
|
208
424
|
|
|
209
|
-
|
|
210
|
-
plugin, so plugin changes trigger a reinstall. Vite
|
|
211
|
-
|
|
212
|
-
|
|
425
|
+
All installs are cached in `e2e/.cache/`; the cache key includes the packed
|
|
426
|
+
plugin, so plugin changes trigger a reinstall. Each Vite major uses the
|
|
427
|
+
`@vitejs/plugin-react` release that still declares it as a peer. Vite 8 uses
|
|
428
|
+
Rolldown rather than Rollup; `rollup` is installed in the Vite projects only to
|
|
429
|
+
satisfy this package's peer dependency. The `e2e` CI job runs
|
|
430
|
+
`npm run test:e2e`, covering all three bundlers.
|
|
213
431
|
|
|
214
432
|
## Contributing
|
|
215
433
|
Anyone is free to open a PR and contribute to this project... just be civilized!
|
|
216
434
|
|
|
435
|
+
Read [CONTRIBUTING.md](CONTRIBUTING.md) for the development setup, test commands, commit conventions, and pull request process.
|
|
436
|
+
|
|
217
437
|
|
|
218
438
|
## Credits
|
|
219
439
|
Inspired by [react-scoped-css](https://github.com/gaoxiaoliangz/react-scoped-css).
|
package/dist/css/scope-css.js
CHANGED
|
@@ -21,6 +21,13 @@ const isDeepFunctionSelector = (node) => node.type === "PseudoClassSelector" &&
|
|
|
21
21
|
*/
|
|
22
22
|
const isRootLikePseudoSelector = (node) => node.type === "PseudoClassSelector" &&
|
|
23
23
|
(node.name.toLowerCase() === "root" || node.name.toLowerCase() === "scope");
|
|
24
|
+
/**
|
|
25
|
+
* The `::view-transition-*` tree is rooted at the document root, which never
|
|
26
|
+
* receives the scope attribute. A bare occurrence can therefore never match
|
|
27
|
+
* once scoped, so it is left global just like `:root`.
|
|
28
|
+
*/
|
|
29
|
+
const isViewTransitionPseudoElement = (node) => node.type === "PseudoElementSelector" &&
|
|
30
|
+
node.name.toLowerCase().startsWith("view-transition");
|
|
24
31
|
const isCombinator = (node) => node.type === "Combinator";
|
|
25
32
|
const isNestingSelector = (node) => node.type === "NestingSelector";
|
|
26
33
|
/**
|
|
@@ -159,6 +166,11 @@ export function scopeCss(css, filename, hash, source = filename) {
|
|
|
159
166
|
if (items.some((item) => isRootLikePseudoSelector(item.data))) {
|
|
160
167
|
return;
|
|
161
168
|
}
|
|
169
|
+
// Bare view-transition pseudo-elements are also rooted at the document
|
|
170
|
+
// root, so scoping them would only produce an unmatchable selector.
|
|
171
|
+
if (items.some((item) => isViewTransitionPseudoElement(item.data))) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
162
174
|
if (items.some((item) => item.data.type === "PseudoElementSelector")) {
|
|
163
175
|
// An attribute cannot follow a pseudo-element, so it has to be
|
|
164
176
|
// prepended: `::before` -> `[hash]::before`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rollup-plugin-react-scoped-css",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Bring scoped CSS to React with Vite &
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "Bring scoped CSS to React with Vite, Rollup & Rolldown. Lightweight component-level style isolation similar to Vue and Angular.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"lint": "eslint '*/**/*.{js,jsx,ts,tsx}'",
|
|
12
12
|
"test": "jest",
|
|
13
13
|
"test:e2e": "node e2e/run.mjs",
|
|
14
|
+
"test:e2e:vite": "node e2e/run.mjs --bundler vite",
|
|
15
|
+
"test:e2e:bundlers": "node e2e/run.mjs --bundler rollup,rolldown",
|
|
14
16
|
"test:e2e:install": "playwright install chromium"
|
|
15
17
|
},
|
|
16
18
|
"repository": {
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
],
|
|
26
28
|
"keywords": [
|
|
27
29
|
"rollup",
|
|
30
|
+
"rolldown",
|
|
28
31
|
"react",
|
|
29
32
|
"css",
|
|
30
33
|
"styles",
|
|
@@ -39,6 +42,15 @@
|
|
|
39
42
|
"isolation"
|
|
40
43
|
],
|
|
41
44
|
"license": "MIT",
|
|
45
|
+
"jest": {
|
|
46
|
+
"modulePathIgnorePatterns": [
|
|
47
|
+
"<rootDir>/e2e/.cache/"
|
|
48
|
+
],
|
|
49
|
+
"testPathIgnorePatterns": [
|
|
50
|
+
"/node_modules/",
|
|
51
|
+
"<rootDir>/e2e/"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
42
54
|
"bugs": {
|
|
43
55
|
"url": "https://github.com/cmseguin/rollup-plugin-react-scoped-css/issues"
|
|
44
56
|
},
|