@greenfinity/rescript-typed-css-modules 0.1.0
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/LICENSE +21 -0
- package/README.md +204 -0
- package/__tests__/.gitkeep +0 -0
- package/dist/css-to-rescript.js +26902 -0
- package/package.json +58 -0
- package/rescript.json +30 -0
- package/src/CssToRescript.bs.mjs +335 -0
- package/src/CssToRescript.res +381 -0
- package/src/require-shim.mjs +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Balázs Reé <ree@greenfinity.hu>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# rescript-typed-css-modules
|
|
2
|
+
|
|
3
|
+
Generate type-safe ReScript bindings from CSS Modules (`.module.css` / `.module.scss`) and global CSS (`.global.css` / `.global.scss`).
|
|
4
|
+
|
|
5
|
+
Only tested with Next.js and might not work with other frameworks out of the box.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Extracts class names from CSS/SCSS module and global files
|
|
10
|
+
- Generates a typed record for all class names
|
|
11
|
+
- Supports `@import` rules (imported classes are included in the generated bindings)
|
|
12
|
+
- Also enable access to non-scoped classes (e.g. `:global()` classes)
|
|
13
|
+
- Supports type-safe access to global css classes (`.global.css` / `.global.scss`)
|
|
14
|
+
- Supports recursive directory scanning
|
|
15
|
+
- Watch mode for automatic regeneration on file changes
|
|
16
|
+
- Process multiple files or directories in a single command
|
|
17
|
+
|
|
18
|
+
## File naming conventions
|
|
19
|
+
|
|
20
|
+
| Input file | Generated binding |
|
|
21
|
+
|----------------------------------|-------------------|
|
|
22
|
+
| `*.module.css` / `*.module.scss` | `*_CssModule.res` |
|
|
23
|
+
| `*.global.css` / `*.global.scss` | `*_GlobalCss.res` |
|
|
24
|
+
|
|
25
|
+
### Why global CSS support?
|
|
26
|
+
|
|
27
|
+
The use case is to access classes in a type safe way from third party css libraries, that emit HTML markup with predefined class names without the use of css modules (e.g. React Aria Components).
|
|
28
|
+
|
|
29
|
+
Global CSS files (`.global.css` / `.global.scss`) also get type-safe bindings, but they are not imported, and their class names are **not hashed**. This is useful when working with third-party libraries (such as React Aria Components) that emit HTML markup with predefined class names that you need to style.
|
|
30
|
+
|
|
31
|
+
Even though global CSS classes aren't scoped, generating typed bindings provides the benefit of compile-time checking that class names exist.
|
|
32
|
+
|
|
33
|
+
The imports are not done because these css typically has to be imported from the top of the component hierarchy. So the import has to be done manually. To get type safe access, simply rename the file to `.global.css` (or create one and import the original css from it).
|
|
34
|
+
|
|
35
|
+
(Remark: we could also provide a way to import the css automatically, but this would open issues with removing duplicates during bundling, and handling the css on route changes. NextJs does not support these use cases out of the box, so we revert to the manual import for global css.)
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
yarn add @greenfinity/rescript-typed-css-modules
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
Usage
|
|
47
|
+
$ css-to-rescript <file.module.css|scss|global.css|scss...>
|
|
48
|
+
$ css-to-rescript <directory...>
|
|
49
|
+
|
|
50
|
+
Options
|
|
51
|
+
--watch, -w Watch for changes and regenerate bindings (directories only)
|
|
52
|
+
--skip-initial, -s Skip initial compilation in watch mode
|
|
53
|
+
--output-dir, -o Directory to write generated .res files
|
|
54
|
+
(multiple files or single directory only)
|
|
55
|
+
|
|
56
|
+
Examples
|
|
57
|
+
$ css-to-rescript src/Card.module.scss
|
|
58
|
+
$ css-to-rescript src/Theme.global.css
|
|
59
|
+
$ css-to-rescript src/Button.module.css src/Card.module.scss -o src/bindings
|
|
60
|
+
$ css-to-rescript src/components
|
|
61
|
+
$ css-to-rescript src/components src/pages --watch
|
|
62
|
+
$ css-to-rescript src/components --watch --skip-initial
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
Given a CSS module:
|
|
68
|
+
|
|
69
|
+
```css
|
|
70
|
+
/* Button.module.css */
|
|
71
|
+
.btn {
|
|
72
|
+
padding: 0.5rem 1rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.btn-lg {
|
|
76
|
+
padding: 1rem 2rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.disabled {
|
|
80
|
+
opacity: 0.5;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The tool generates:
|
|
85
|
+
|
|
86
|
+
```rescript
|
|
87
|
+
// Button_CssModule.res
|
|
88
|
+
// Generated from Button.module.css
|
|
89
|
+
// Do not edit manually
|
|
90
|
+
|
|
91
|
+
type t = {
|
|
92
|
+
"btn": string,
|
|
93
|
+
"btn-lg": string,
|
|
94
|
+
"disabled": string
|
|
95
|
+
}
|
|
96
|
+
@module("./Button.module.css") external css: t = "default"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Using the bindings
|
|
100
|
+
|
|
101
|
+
```rescript
|
|
102
|
+
<button className={Button_CssModule.css["btn-lg"]}>
|
|
103
|
+
<button className={Button_CssModule.css["disabled"]}>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
You can combine multiple classes with template strings:
|
|
107
|
+
|
|
108
|
+
```rescript
|
|
109
|
+
<button className={`${Button_CssModule.css["btn"]} ${Button_CssModule.css["btn-lg"]}`}>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### CSS imports
|
|
113
|
+
|
|
114
|
+
The tool supports `@import` rules. Imported classes are included in the generated bindings:
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
/* shared.css */
|
|
118
|
+
.shared-class {
|
|
119
|
+
color: red;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
/* WithImport.module.css */
|
|
125
|
+
@import "./shared.css";
|
|
126
|
+
|
|
127
|
+
.local-class {
|
|
128
|
+
background: blue;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Generates:
|
|
133
|
+
|
|
134
|
+
```rescript
|
|
135
|
+
// WithImport_CssModule.res
|
|
136
|
+
type t = {
|
|
137
|
+
"local-class": string,
|
|
138
|
+
"shared-class": string
|
|
139
|
+
}
|
|
140
|
+
@module("./WithImport.module.css") external css: t = "default"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Global CSS
|
|
144
|
+
|
|
145
|
+
For third-party libraries that emit HTML with predefined class names (e.g. React Aria Components), you can create a `.global.css` file to get type-safe bindings without CSS Modules scoping:
|
|
146
|
+
|
|
147
|
+
```css
|
|
148
|
+
/* Theme.global.css */
|
|
149
|
+
.dark-mode {
|
|
150
|
+
background: #1a1a1a;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.light-mode {
|
|
154
|
+
background: #ffffff;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.primary-color {
|
|
158
|
+
color: blue;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Generates:
|
|
163
|
+
|
|
164
|
+
```rescript
|
|
165
|
+
// Theme_CssGlobal.res
|
|
166
|
+
type t = {
|
|
167
|
+
"dark-mode": string,
|
|
168
|
+
"light-mode": string,
|
|
169
|
+
"primary-color": string
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Class names are returned as-is (no hashing)
|
|
173
|
+
let css = ...
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Usage:
|
|
177
|
+
|
|
178
|
+
```rescript
|
|
179
|
+
// Import the CSS manually at your app root
|
|
180
|
+
%%raw(`import "./Theme.global.css"`)
|
|
181
|
+
|
|
182
|
+
// Then use type-safe class names anywhere
|
|
183
|
+
<div className={Theme_CssGlobal.css["dark-mode"]}>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## How it works
|
|
187
|
+
|
|
188
|
+
1. Parses CSS/SCSS files using PostCSS to extract class names
|
|
189
|
+
2. Generates a typed record `css` containing all class names, accessible via string keys.
|
|
190
|
+
|
|
191
|
+
## Caveats
|
|
192
|
+
|
|
193
|
+
This tool generates bindings that assume CSS Modules are processed by a bundler with CSS Modules support. Currently tested with Next.js, which applies PostCSS module transformation automatically.
|
|
194
|
+
|
|
195
|
+
If using outside of Next.js, you may need to configure your bundler (Vite, Webpack, etc.) to handle CSS Modules scoping. This has not been tested outside of Next.js.
|
|
196
|
+
|
|
197
|
+
## Requirements
|
|
198
|
+
|
|
199
|
+
- Node.js >= 22.12.0
|
|
200
|
+
- ReScript >= 12.0.0
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT
|
|
File without changes
|