gemcss 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 +24 -0
- package/README.md +178 -0
- package/dist/chunk-CNXDOGPX.js +89 -0
- package/dist/chunk-DMYDRHTS.js +31 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +137 -0
- package/dist/compile-Br7cHtI1.d.ts +6 -0
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.js +24 -0
- package/dist/ts-plugin.cjs +206 -0
- package/dist/ts-plugin.d.cts +15 -0
- package/dist/vite.d.ts +20 -0
- package/dist/vite.js +60 -0
- package/dist/webpack.d.ts +13 -0
- package/dist/webpack.js +32 -0
- package/package.json +101 -0
- package/ts-plugin/package.json +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# gemcss
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/gemcss)
|
|
4
|
+
[](https://github.com/vkalinichev/gemcss/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
Type-safe BEM classes from CSS modules. Write a plain `*.module.css` in BEM
|
|
7
|
+
notation and get back an object where every block is a modifier-combinator
|
|
8
|
+
function, with types inferred from the CSS itself.
|
|
9
|
+
|
|
10
|
+
`button.module.css`
|
|
11
|
+
|
|
12
|
+
```css
|
|
13
|
+
.button { border-radius: 6px; padding: 8px 14px; }
|
|
14
|
+
.button_disabled { opacity: 0.5; pointer-events: none; }
|
|
15
|
+
.button_variant_primary { background: #2563eb; color: #fff; }
|
|
16
|
+
.button_variant_ghost { background: transparent; color: #2563eb; }
|
|
17
|
+
.button_size_s { padding: 4px 10px; font-size: 13px; }
|
|
18
|
+
.button_size_m { padding: 8px 14px; font-size: 14px; }
|
|
19
|
+
.button_size_l { padding: 12px 20px; font-size: 16px; }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`button.tsx`
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import styles from './button.module.css';
|
|
26
|
+
// styles.button: (props?: {
|
|
27
|
+
// disabled?: boolean;
|
|
28
|
+
// variant?: 'primary' | 'ghost';
|
|
29
|
+
// size?: 's' | 'm' | 'l';
|
|
30
|
+
// }) => string
|
|
31
|
+
|
|
32
|
+
<button className={styles.button({ variant: 'primary', size: 'l' })} />;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`styles.button()` returns the base class, `styles.button({ disabled: true })`
|
|
36
|
+
appends the modifier class. Class names are scoped (CSS modules), types come
|
|
37
|
+
from the CSS. A block with no modifiers is a no-arg call: `styles.root()`.
|
|
38
|
+
|
|
39
|
+
## Naming convention
|
|
40
|
+
|
|
41
|
+
The separator is a single `_`.
|
|
42
|
+
|
|
43
|
+
| Class | Meaning |
|
|
44
|
+
| ------------------------- | ------------------------------------------- |
|
|
45
|
+
| `.button` | block `button` |
|
|
46
|
+
| `.button_disabled` | boolean modifier: `disabled?: boolean` |
|
|
47
|
+
| `.button_variant_primary` | enum modifier: `variant?: 'primary' \| ...` |
|
|
48
|
+
|
|
49
|
+
Rules:
|
|
50
|
+
|
|
51
|
+
- The first segment before `_` is the block name; a block cannot contain `_`.
|
|
52
|
+
- After the block comes exactly one modifier per class: 1 segment → boolean,
|
|
53
|
+
2 segments → a `key_value` pair. More than that is a build error.
|
|
54
|
+
- The values of one enum key are collected from all classes of the block.
|
|
55
|
+
|
|
56
|
+
> Every export is a function. The base class is a no-arg call: `styles.root()`,
|
|
57
|
+
> `styles.button()`.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
One package for everything:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pnpm add -D gemcss
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Entry point | Purpose |
|
|
68
|
+
| ------------------ | ------------------------------------------------------------ |
|
|
69
|
+
| `gemcss/vite` | Vite plugin |
|
|
70
|
+
| `gemcss/webpack` | Webpack loader |
|
|
71
|
+
| `gemcss/ts-plugin` | TS language service plugin (types in the IDE) |
|
|
72
|
+
| `gemcss` (bin) | CLI: generates `.d.css.ts` for `tsc` / CI |
|
|
73
|
+
| `gemcss/runtime` | `createGems` — the tiny runtime the bundlers wire in for you |
|
|
74
|
+
|
|
75
|
+
`vite`, `webpack` and `typescript` are optional peer dependencies — you only
|
|
76
|
+
need the one whose entry point you actually use.
|
|
77
|
+
|
|
78
|
+
## Setup
|
|
79
|
+
|
|
80
|
+
### vite
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// vite.config.ts
|
|
84
|
+
import { defineConfig } from 'vite';
|
|
85
|
+
import { gemcss } from 'gemcss/vite';
|
|
86
|
+
|
|
87
|
+
export default defineConfig({
|
|
88
|
+
plugins: [gemcss()],
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### webpack
|
|
93
|
+
|
|
94
|
+
`gemcss/webpack` is the loader itself:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
// webpack.config.mjs
|
|
98
|
+
export default {
|
|
99
|
+
module: {
|
|
100
|
+
rules: [{ test: /\.module\.css$/, use: 'gemcss/webpack', type: 'javascript/auto' }],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The rule replaces the `css-loader` + `style-loader` pair for `*.module.css` —
|
|
106
|
+
do not combine them on the same test: the CSS is injected into `document.head`
|
|
107
|
+
as a `<style data-gemcss="...">` tag.
|
|
108
|
+
|
|
109
|
+
### Options (vite and webpack)
|
|
110
|
+
|
|
111
|
+
| Option | Type | Default | What it does |
|
|
112
|
+
| -------------------- | -------- | ------------------------------------ | -------------------------- |
|
|
113
|
+
| `generateScopedName` | `string` | `'[name]__[local]__[hash:base64:5]'` | Scoped class name template |
|
|
114
|
+
|
|
115
|
+
Vite: `gemcss({ generateScopedName: '…' })`. Webpack:
|
|
116
|
+
`use: { loader: 'gemcss/webpack', options: { generateScopedName: '…' } }`.
|
|
117
|
+
|
|
118
|
+
### typescript (language server)
|
|
119
|
+
|
|
120
|
+
Types for `*.module.css` in the IDE, with no declaration files on disk:
|
|
121
|
+
|
|
122
|
+
```jsonc
|
|
123
|
+
// tsconfig.json
|
|
124
|
+
{
|
|
125
|
+
"compilerOptions": {
|
|
126
|
+
"plugins": [{ "name": "gemcss/ts-plugin" }],
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
In VS Code pick "Use Workspace Version" for TypeScript, otherwise the editor
|
|
132
|
+
runs its own tsserver and never loads the plugin.
|
|
133
|
+
|
|
134
|
+
For `tsc` and CI — where language service plugins do not run — generate the
|
|
135
|
+
`.d.css.ts` declarations and enable `allowArbitraryExtensions`:
|
|
136
|
+
|
|
137
|
+
```jsonc
|
|
138
|
+
{
|
|
139
|
+
"compilerOptions": {
|
|
140
|
+
"allowArbitraryExtensions": true,
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
gemcss # generate *.module.d.css.ts next to every CSS module
|
|
147
|
+
gemcss "src/**/*.module.css" # only the given patterns
|
|
148
|
+
gemcss --watch # and regenerate on change
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| Option | What it does |
|
|
152
|
+
| ------------- | ----------------------------------------------- |
|
|
153
|
+
| `-w, --watch` | watch `*.module.css` under cwd and regenerate |
|
|
154
|
+
| `--cwd <dir>` | working directory (defaults to `process.cwd()`) |
|
|
155
|
+
| `-h, --help` | show help |
|
|
156
|
+
|
|
157
|
+
`node_modules` is always ignored. The `.d.css.ts` files are build artifacts —
|
|
158
|
+
add them to `.gitignore`. On a naming convention violation the CLI fails with a
|
|
159
|
+
`GemcssParseError` and a non-zero exit code, which makes it a usable CI check.
|
|
160
|
+
|
|
161
|
+
Both type mechanisms coexist: in the IDE the plugin wins (it intercepts the
|
|
162
|
+
`*.module.css` resolution), while compilation reads the generated `.d.css.ts`.
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
pnpm install
|
|
168
|
+
pnpm test # vitest
|
|
169
|
+
pnpm build # tsup + builds both examples (e2e)
|
|
170
|
+
pnpm check:exports # publint + are-the-types-wrong, against the real tarball
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Working examples: `examples/vite-react` (React), `examples/webpack` (vanilla
|
|
174
|
+
TS). Requires Node >= 22.14.
|
|
175
|
+
|
|
176
|
+
Releases go through [changesets](https://github.com/changesets/changesets): run
|
|
177
|
+
`pnpm changeset` alongside a change, and merging the generated Release PR
|
|
178
|
+
publishes to npm.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/core/parser.ts
|
|
2
|
+
import postcss from "postcss";
|
|
3
|
+
import selectorParser from "postcss-selector-parser";
|
|
4
|
+
var GemcssParseError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "GemcssParseError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function ctx(from) {
|
|
11
|
+
return from ? ` (in ${from})` : "";
|
|
12
|
+
}
|
|
13
|
+
function parseCss(css, opts = {}) {
|
|
14
|
+
const root = postcss.parse(css, { from: opts.from });
|
|
15
|
+
const classNames = [];
|
|
16
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17
|
+
root.walkRules((rule) => {
|
|
18
|
+
selectorParser((selectors) => {
|
|
19
|
+
selectors.walkClasses((cls) => {
|
|
20
|
+
if (!seen.has(cls.value)) {
|
|
21
|
+
seen.add(cls.value);
|
|
22
|
+
classNames.push(cls.value);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}).processSync(rule.selector);
|
|
26
|
+
});
|
|
27
|
+
return buildModule(classNames, opts.from);
|
|
28
|
+
}
|
|
29
|
+
function buildModule(classNames, from) {
|
|
30
|
+
const blocks = {};
|
|
31
|
+
for (const raw of classNames) {
|
|
32
|
+
const segments = raw.split("_");
|
|
33
|
+
const blockName = segments[0];
|
|
34
|
+
if (!blockName) {
|
|
35
|
+
throw new GemcssParseError(`Empty block name in class ".${raw}"${ctx(from)}`);
|
|
36
|
+
}
|
|
37
|
+
const block = blocks[blockName] ??= {
|
|
38
|
+
name: blockName,
|
|
39
|
+
modifiers: {}
|
|
40
|
+
};
|
|
41
|
+
const tail = segments.slice(1);
|
|
42
|
+
if (tail.length === 0) continue;
|
|
43
|
+
if (tail.length === 1) {
|
|
44
|
+
const key = tail[0];
|
|
45
|
+
if (!key) throw new GemcssParseError(`Empty modifier in class ".${raw}"${ctx(from)}`);
|
|
46
|
+
assignBool(block, key, raw, from);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (tail.length === 2) {
|
|
50
|
+
const key = tail[0];
|
|
51
|
+
const value = tail[1];
|
|
52
|
+
if (!key || !value) {
|
|
53
|
+
throw new GemcssParseError(`Empty modifier key/value in class ".${raw}"${ctx(from)}`);
|
|
54
|
+
}
|
|
55
|
+
assignEnum(block, key, value, raw, from);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
throw new GemcssParseError(
|
|
59
|
+
`Class ".${raw}" has more than one modifier segment after block "${blockName}". Allowed: block, block_bool, block_key_value.${ctx(from)}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return { blocks };
|
|
63
|
+
}
|
|
64
|
+
function assignBool(block, key, raw, from) {
|
|
65
|
+
const existing = block.modifiers[key];
|
|
66
|
+
if (existing && existing.kind === "enum") {
|
|
67
|
+
throw new GemcssParseError(
|
|
68
|
+
`Modifier "${key}" of block "${block.name}" used as both boolean (".${raw}") and enum.${ctx(from)}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
block.modifiers[key] = { kind: "bool" };
|
|
72
|
+
}
|
|
73
|
+
function assignEnum(block, key, value, raw, from) {
|
|
74
|
+
const existing = block.modifiers[key];
|
|
75
|
+
if (existing && existing.kind === "bool") {
|
|
76
|
+
throw new GemcssParseError(
|
|
77
|
+
`Modifier "${key}" of block "${block.name}" used as both enum (".${raw}") and boolean.${ctx(from)}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const mod = existing && existing.kind === "enum" ? existing : block.modifiers[key] = { kind: "enum", values: [] };
|
|
81
|
+
if (!mod.values.includes(value)) {
|
|
82
|
+
mod.values.push(value);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
GemcssParseError,
|
|
88
|
+
parseCss
|
|
89
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseCss
|
|
3
|
+
} from "./chunk-CNXDOGPX.js";
|
|
4
|
+
|
|
5
|
+
// src/core/compile.ts
|
|
6
|
+
import postcss from "postcss";
|
|
7
|
+
import postcssModules from "postcss-modules";
|
|
8
|
+
async function compile(code, id, opts = {}) {
|
|
9
|
+
let scoped = {};
|
|
10
|
+
const result = await postcss([
|
|
11
|
+
postcssModules({
|
|
12
|
+
generateScopedName: opts.generateScopedName ?? "[name]__[local]__[hash:base64:5]",
|
|
13
|
+
getJSON: (_file, json) => {
|
|
14
|
+
scoped = json;
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
]).process(code, { from: id });
|
|
18
|
+
const module = parseCss(code, { from: id });
|
|
19
|
+
return { css: result.css, scoped, module };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/core/runtime-path.ts
|
|
23
|
+
import { createRequire } from "module";
|
|
24
|
+
function runtimePath() {
|
|
25
|
+
return createRequire(import.meta.url).resolve("gemcss/runtime");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
compile,
|
|
30
|
+
runtimePath
|
|
31
|
+
};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
GemcssParseError,
|
|
4
|
+
parseCss
|
|
5
|
+
} from "./chunk-CNXDOGPX.js";
|
|
6
|
+
|
|
7
|
+
// src/cli.ts
|
|
8
|
+
import { watch as fsWatch } from "fs";
|
|
9
|
+
import { stat } from "fs/promises";
|
|
10
|
+
import { resolve as resolve2 } from "path";
|
|
11
|
+
import { parseArgs } from "util";
|
|
12
|
+
|
|
13
|
+
// src/cli/gen.ts
|
|
14
|
+
import { glob, readFile, rm, writeFile } from "fs/promises";
|
|
15
|
+
import { resolve } from "path";
|
|
16
|
+
|
|
17
|
+
// src/core/dts.ts
|
|
18
|
+
var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
19
|
+
function propKey(name) {
|
|
20
|
+
return IDENT.test(name) ? name : JSON.stringify(name);
|
|
21
|
+
}
|
|
22
|
+
function strLit(value) {
|
|
23
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
24
|
+
}
|
|
25
|
+
function propsParam(block) {
|
|
26
|
+
const keys = Object.keys(block.modifiers);
|
|
27
|
+
if (keys.length === 0) return "";
|
|
28
|
+
const fields = keys.map((k) => {
|
|
29
|
+
const mod = block.modifiers[k];
|
|
30
|
+
const type = mod.kind === "bool" ? "boolean" : mod.values.map(strLit).join(" | ");
|
|
31
|
+
return `${propKey(k)}?: ${type}`;
|
|
32
|
+
});
|
|
33
|
+
return `props?: { ${fields.join("; ")} }`;
|
|
34
|
+
}
|
|
35
|
+
function generateDts(module) {
|
|
36
|
+
const lines = ["declare const gems: {"];
|
|
37
|
+
for (const block of Object.values(module.blocks)) {
|
|
38
|
+
lines.push(` ${propKey(block.name)}(${propsParam(block)}): string;`);
|
|
39
|
+
}
|
|
40
|
+
lines.push("};");
|
|
41
|
+
lines.push("export default gems;");
|
|
42
|
+
lines.push("");
|
|
43
|
+
return lines.join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/cli/gen.ts
|
|
47
|
+
var DEFAULT_PATTERNS = ["**/*.module.css"];
|
|
48
|
+
function dtsPathFor(cssFile) {
|
|
49
|
+
return cssFile.replace(/\.css$/, ".d.css.ts");
|
|
50
|
+
}
|
|
51
|
+
async function generateOne(cssFile) {
|
|
52
|
+
const css = await readFile(cssFile, "utf8");
|
|
53
|
+
const dts = generateDts(parseCss(css, { from: cssFile }));
|
|
54
|
+
const out = dtsPathFor(cssFile);
|
|
55
|
+
await writeFile(out, dts);
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
async function removeOne(cssFile) {
|
|
59
|
+
await rm(dtsPathFor(cssFile), { force: true });
|
|
60
|
+
}
|
|
61
|
+
async function run(patterns, opts = {}) {
|
|
62
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
63
|
+
const files = glob(patterns.length ? patterns : DEFAULT_PATTERNS, {
|
|
64
|
+
cwd,
|
|
65
|
+
exclude: ["**/node_modules/**"]
|
|
66
|
+
});
|
|
67
|
+
const written = [];
|
|
68
|
+
for await (const file of files) {
|
|
69
|
+
written.push(await generateOne(resolve(cwd, file)));
|
|
70
|
+
}
|
|
71
|
+
return written;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/cli.ts
|
|
75
|
+
var USAGE = `gemcss \u2014 generate .d.css.ts declarations for CSS modules
|
|
76
|
+
|
|
77
|
+
Usage:
|
|
78
|
+
gemcss [patterns...] [options]
|
|
79
|
+
|
|
80
|
+
Options:
|
|
81
|
+
-w, --watch watch and regenerate on change
|
|
82
|
+
--cwd <dir> working directory (default: process.cwd())
|
|
83
|
+
-h, --help show this help
|
|
84
|
+
|
|
85
|
+
Default pattern: ${DEFAULT_PATTERNS.join(" ")}
|
|
86
|
+
Watch mode follows every *.module.css under cwd (node_modules ignored).`;
|
|
87
|
+
async function main() {
|
|
88
|
+
const { values, positionals } = parseArgs({
|
|
89
|
+
allowPositionals: true,
|
|
90
|
+
options: {
|
|
91
|
+
watch: { type: "boolean", short: "w" },
|
|
92
|
+
cwd: { type: "string" },
|
|
93
|
+
help: { type: "boolean", short: "h" }
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (values.help) {
|
|
97
|
+
console.log(USAGE);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const cwd = resolve2(values.cwd ?? process.cwd());
|
|
101
|
+
try {
|
|
102
|
+
const written = await run(positionals, { cwd });
|
|
103
|
+
for (const file of written) console.log(`generated ${file}`);
|
|
104
|
+
console.log(`gemcss: ${written.length} file(s)`);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
fail(err);
|
|
107
|
+
if (!values.watch) process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
if (values.watch) watch(cwd);
|
|
110
|
+
}
|
|
111
|
+
function watch(cwd) {
|
|
112
|
+
fsWatch(cwd, { recursive: true }, (_event, rel) => {
|
|
113
|
+
if (!rel || !rel.endsWith(".module.css") || rel.includes("node_modules")) return;
|
|
114
|
+
void onChange(resolve2(cwd, rel));
|
|
115
|
+
});
|
|
116
|
+
console.log(`gemcss: watching ${cwd}`);
|
|
117
|
+
}
|
|
118
|
+
async function onChange(file) {
|
|
119
|
+
try {
|
|
120
|
+
await stat(file);
|
|
121
|
+
} catch {
|
|
122
|
+
return removeOne(file);
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
console.log(`generated ${await generateOne(file)}`);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
fail(err);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function fail(err) {
|
|
131
|
+
if (err instanceof GemcssParseError) {
|
|
132
|
+
console.error(`gemcss: ${err.message}`);
|
|
133
|
+
} else {
|
|
134
|
+
console.error(err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
void main();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Props passed to a gem function: boolean flags and enum values. */
|
|
2
|
+
type GemProps = Record<string, string | boolean | undefined>;
|
|
3
|
+
/** A block's class combinator function. */
|
|
4
|
+
type Gem = (props?: GemProps) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Build the `gems` object from a list of block names and a `raw → scoped` map.
|
|
7
|
+
* Every block is a function: called with no arguments it returns the base class,
|
|
8
|
+
* called with props it appends the classes of the active modifiers.
|
|
9
|
+
*/
|
|
10
|
+
declare function createGems(blocks: string[], scoped: Record<string, string>): Record<string, Gem>;
|
|
11
|
+
|
|
12
|
+
export { type Gem, type GemProps, createGems };
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/runtime.ts
|
|
2
|
+
function createGems(blocks, scoped) {
|
|
3
|
+
const gems = {};
|
|
4
|
+
for (const name of blocks) {
|
|
5
|
+
const base = scoped[name] ?? name;
|
|
6
|
+
gems[name] = (props) => {
|
|
7
|
+
if (!props) return base;
|
|
8
|
+
let out = base;
|
|
9
|
+
for (const key in props) {
|
|
10
|
+
const v = props[key];
|
|
11
|
+
if (v === true) {
|
|
12
|
+
out += " " + (scoped[`${name}_${key}`] ?? `${name}_${key}`);
|
|
13
|
+
} else if (v != null && v !== false) {
|
|
14
|
+
out += " " + (scoped[`${name}_${key}_${v}`] ?? `${name}_${key}_${v}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return gems;
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
createGems
|
|
24
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/ts-plugin.ts
|
|
26
|
+
var import_node_path = require("path");
|
|
27
|
+
|
|
28
|
+
// src/ts-plugin/generate-dts.ts
|
|
29
|
+
var import_node_fs = require("fs");
|
|
30
|
+
|
|
31
|
+
// src/core/dts.ts
|
|
32
|
+
var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
33
|
+
function propKey(name) {
|
|
34
|
+
return IDENT.test(name) ? name : JSON.stringify(name);
|
|
35
|
+
}
|
|
36
|
+
function strLit(value) {
|
|
37
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
38
|
+
}
|
|
39
|
+
function propsParam(block) {
|
|
40
|
+
const keys = Object.keys(block.modifiers);
|
|
41
|
+
if (keys.length === 0) return "";
|
|
42
|
+
const fields = keys.map((k) => {
|
|
43
|
+
const mod = block.modifiers[k];
|
|
44
|
+
const type = mod.kind === "bool" ? "boolean" : mod.values.map(strLit).join(" | ");
|
|
45
|
+
return `${propKey(k)}?: ${type}`;
|
|
46
|
+
});
|
|
47
|
+
return `props?: { ${fields.join("; ")} }`;
|
|
48
|
+
}
|
|
49
|
+
function generateDts(module2) {
|
|
50
|
+
const lines = ["declare const gems: {"];
|
|
51
|
+
for (const block of Object.values(module2.blocks)) {
|
|
52
|
+
lines.push(` ${propKey(block.name)}(${propsParam(block)}): string;`);
|
|
53
|
+
}
|
|
54
|
+
lines.push("};");
|
|
55
|
+
lines.push("export default gems;");
|
|
56
|
+
lines.push("");
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/core/parser.ts
|
|
61
|
+
var import_postcss = __toESM(require("postcss"), 1);
|
|
62
|
+
var import_postcss_selector_parser = __toESM(require("postcss-selector-parser"), 1);
|
|
63
|
+
var GemcssParseError = class extends Error {
|
|
64
|
+
constructor(message) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = "GemcssParseError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
function ctx(from) {
|
|
70
|
+
return from ? ` (in ${from})` : "";
|
|
71
|
+
}
|
|
72
|
+
function parseCss(css, opts = {}) {
|
|
73
|
+
const root = import_postcss.default.parse(css, { from: opts.from });
|
|
74
|
+
const classNames = [];
|
|
75
|
+
const seen = /* @__PURE__ */ new Set();
|
|
76
|
+
root.walkRules((rule) => {
|
|
77
|
+
(0, import_postcss_selector_parser.default)((selectors) => {
|
|
78
|
+
selectors.walkClasses((cls) => {
|
|
79
|
+
if (!seen.has(cls.value)) {
|
|
80
|
+
seen.add(cls.value);
|
|
81
|
+
classNames.push(cls.value);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}).processSync(rule.selector);
|
|
85
|
+
});
|
|
86
|
+
return buildModule(classNames, opts.from);
|
|
87
|
+
}
|
|
88
|
+
function buildModule(classNames, from) {
|
|
89
|
+
const blocks = {};
|
|
90
|
+
for (const raw of classNames) {
|
|
91
|
+
const segments = raw.split("_");
|
|
92
|
+
const blockName = segments[0];
|
|
93
|
+
if (!blockName) {
|
|
94
|
+
throw new GemcssParseError(`Empty block name in class ".${raw}"${ctx(from)}`);
|
|
95
|
+
}
|
|
96
|
+
const block = blocks[blockName] ??= {
|
|
97
|
+
name: blockName,
|
|
98
|
+
modifiers: {}
|
|
99
|
+
};
|
|
100
|
+
const tail = segments.slice(1);
|
|
101
|
+
if (tail.length === 0) continue;
|
|
102
|
+
if (tail.length === 1) {
|
|
103
|
+
const key = tail[0];
|
|
104
|
+
if (!key) throw new GemcssParseError(`Empty modifier in class ".${raw}"${ctx(from)}`);
|
|
105
|
+
assignBool(block, key, raw, from);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (tail.length === 2) {
|
|
109
|
+
const key = tail[0];
|
|
110
|
+
const value = tail[1];
|
|
111
|
+
if (!key || !value) {
|
|
112
|
+
throw new GemcssParseError(`Empty modifier key/value in class ".${raw}"${ctx(from)}`);
|
|
113
|
+
}
|
|
114
|
+
assignEnum(block, key, value, raw, from);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
throw new GemcssParseError(
|
|
118
|
+
`Class ".${raw}" has more than one modifier segment after block "${blockName}". Allowed: block, block_bool, block_key_value.${ctx(from)}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return { blocks };
|
|
122
|
+
}
|
|
123
|
+
function assignBool(block, key, raw, from) {
|
|
124
|
+
const existing = block.modifiers[key];
|
|
125
|
+
if (existing && existing.kind === "enum") {
|
|
126
|
+
throw new GemcssParseError(
|
|
127
|
+
`Modifier "${key}" of block "${block.name}" used as both boolean (".${raw}") and enum.${ctx(from)}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
block.modifiers[key] = { kind: "bool" };
|
|
131
|
+
}
|
|
132
|
+
function assignEnum(block, key, value, raw, from) {
|
|
133
|
+
const existing = block.modifiers[key];
|
|
134
|
+
if (existing && existing.kind === "bool") {
|
|
135
|
+
throw new GemcssParseError(
|
|
136
|
+
`Modifier "${key}" of block "${block.name}" used as both enum (".${raw}") and boolean.${ctx(from)}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const mod = existing && existing.kind === "enum" ? existing : block.modifiers[key] = { kind: "enum", values: [] };
|
|
140
|
+
if (!mod.values.includes(value)) {
|
|
141
|
+
mod.values.push(value);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/ts-plugin/generate-dts.ts
|
|
146
|
+
var FALLBACK = "declare const gems: Record<string, (props?: Record<string, string | boolean>) => string>;\nexport default gems;\n";
|
|
147
|
+
function dtsForFile(fileName, read = defaultRead) {
|
|
148
|
+
const css = read(fileName);
|
|
149
|
+
if (css == null) return FALLBACK;
|
|
150
|
+
try {
|
|
151
|
+
return generateDts(parseCss(css, { from: fileName }));
|
|
152
|
+
} catch {
|
|
153
|
+
return FALLBACK;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function defaultRead(f) {
|
|
157
|
+
try {
|
|
158
|
+
return (0, import_node_fs.readFileSync)(f, "utf8");
|
|
159
|
+
} catch {
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/ts-plugin.ts
|
|
165
|
+
var MODULE_CSS = /\.module\.css$/;
|
|
166
|
+
function init({ typescript: ts }) {
|
|
167
|
+
function create(info) {
|
|
168
|
+
const host = info.languageServiceHost;
|
|
169
|
+
const read = (f) => host.readFile?.(f);
|
|
170
|
+
const origKind = host.getScriptKind?.bind(host);
|
|
171
|
+
host.getScriptKind = (fileName) => {
|
|
172
|
+
if (MODULE_CSS.test(fileName)) return ts.ScriptKind.TS;
|
|
173
|
+
return origKind ? origKind(fileName) : ts.ScriptKind.Unknown;
|
|
174
|
+
};
|
|
175
|
+
const origSnapshot = host.getScriptSnapshot.bind(host);
|
|
176
|
+
host.getScriptSnapshot = (fileName) => {
|
|
177
|
+
if (MODULE_CSS.test(fileName)) {
|
|
178
|
+
return ts.ScriptSnapshot.fromString(dtsForFile(fileName, read));
|
|
179
|
+
}
|
|
180
|
+
return origSnapshot(fileName);
|
|
181
|
+
};
|
|
182
|
+
if (host.resolveModuleNameLiterals) {
|
|
183
|
+
const origResolve = host.resolveModuleNameLiterals.bind(host);
|
|
184
|
+
host.resolveModuleNameLiterals = (literals, containingFile, ...rest) => {
|
|
185
|
+
const resolved = origResolve(literals, containingFile, ...rest);
|
|
186
|
+
return literals.map((literal, i) => {
|
|
187
|
+
const name = literal.text;
|
|
188
|
+
if (MODULE_CSS.test(name)) {
|
|
189
|
+
const fileName = name.startsWith(".") ? (0, import_node_path.resolve)((0, import_node_path.dirname)(containingFile), name) : name;
|
|
190
|
+
return {
|
|
191
|
+
resolvedModule: {
|
|
192
|
+
resolvedFileName: fileName,
|
|
193
|
+
extension: ts.Extension.Dts,
|
|
194
|
+
isExternalLibraryImport: false
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return resolved[i];
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return info.languageService;
|
|
203
|
+
}
|
|
204
|
+
return { create };
|
|
205
|
+
}
|
|
206
|
+
module.exports = init;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import tsModule from 'typescript/lib/tsserverlibrary';
|
|
2
|
+
|
|
3
|
+
interface InitArgs {
|
|
4
|
+
typescript: typeof tsModule;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* TS language service plugin: types for `import gems from './x.module.css'`
|
|
8
|
+
* without declaration files on disk. Patches the languageServiceHost:
|
|
9
|
+
* - getScriptKind -> TS for `*.module.css`
|
|
10
|
+
* - getScriptSnapshot -> the generated `.d.ts`
|
|
11
|
+
* - resolveModuleNameLiterals -> resolves the import to that virtual Dts
|
|
12
|
+
*/
|
|
13
|
+
declare function init({ typescript: ts }: InitArgs): tsModule.server.PluginModule;
|
|
14
|
+
|
|
15
|
+
export = init;
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { C as CompileOptions } from './compile-Br7cHtI1.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compile a CSS module into a gems JS module plus a virtual CSS id.
|
|
6
|
+
* A pure function — testable without Vite.
|
|
7
|
+
*/
|
|
8
|
+
declare function buildGemsModule(file: string, code: string, options?: CompileOptions, runtimeImport?: string): Promise<{
|
|
9
|
+
js: string;
|
|
10
|
+
cssId: string;
|
|
11
|
+
css: string;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* gemcss Vite plugin: `*.module.css` -> a typed `gems` object.
|
|
15
|
+
* The import resolves to a virtual JS module; the CSS goes back into the Vite
|
|
16
|
+
* pipeline as a separate virtual `.css` import so it still gets injected.
|
|
17
|
+
*/
|
|
18
|
+
declare function gemcss(options?: CompileOptions): Plugin;
|
|
19
|
+
|
|
20
|
+
export { buildGemsModule, gemcss as default, gemcss };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compile,
|
|
3
|
+
runtimePath
|
|
4
|
+
} from "./chunk-DMYDRHTS.js";
|
|
5
|
+
import "./chunk-CNXDOGPX.js";
|
|
6
|
+
|
|
7
|
+
// src/vite.ts
|
|
8
|
+
import { readFile } from "fs/promises";
|
|
9
|
+
var MODULE_CSS_RE = /\.module\.css$/;
|
|
10
|
+
var VIRTUAL_JS = "\0gemcss-js:";
|
|
11
|
+
var VIRTUAL_CSS = "\0gemcss-css:";
|
|
12
|
+
function isModuleCss(source) {
|
|
13
|
+
return MODULE_CSS_RE.test(source.split("?", 1)[0] ?? source);
|
|
14
|
+
}
|
|
15
|
+
var enc = (s) => Buffer.from(s).toString("base64url");
|
|
16
|
+
var dec = (s) => Buffer.from(s, "base64url").toString("utf8");
|
|
17
|
+
async function buildGemsModule(file, code, options = {}, runtimeImport = "gemcss/runtime") {
|
|
18
|
+
const { css, scoped, module } = await compile(code, file, options);
|
|
19
|
+
const cssId = `${VIRTUAL_CSS}${enc(file)}.css`;
|
|
20
|
+
const js = `import ${JSON.stringify(cssId)};
|
|
21
|
+
import { createGems } from ${JSON.stringify(runtimeImport)};
|
|
22
|
+
export default createGems(${JSON.stringify(Object.keys(module.blocks))}, ${JSON.stringify(scoped)});
|
|
23
|
+
`;
|
|
24
|
+
return { js, cssId, css };
|
|
25
|
+
}
|
|
26
|
+
function gemcss(options = {}) {
|
|
27
|
+
const cssStore = /* @__PURE__ */ new Map();
|
|
28
|
+
return {
|
|
29
|
+
name: "gemcss",
|
|
30
|
+
enforce: "pre",
|
|
31
|
+
async resolveId(source, importer) {
|
|
32
|
+
if (source.startsWith(VIRTUAL_JS) || source.startsWith(VIRTUAL_CSS)) return source;
|
|
33
|
+
if (isModuleCss(source)) {
|
|
34
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
35
|
+
if (resolved) return VIRTUAL_JS + enc(resolved.id);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
},
|
|
39
|
+
async load(id) {
|
|
40
|
+
if (id.startsWith(VIRTUAL_CSS)) {
|
|
41
|
+
return cssStore.get(id) ?? "";
|
|
42
|
+
}
|
|
43
|
+
if (id.startsWith(VIRTUAL_JS)) {
|
|
44
|
+
const file = dec(id.slice(VIRTUAL_JS.length));
|
|
45
|
+
this.addWatchFile(file);
|
|
46
|
+
const code = await readFile(file, "utf8");
|
|
47
|
+
const { js, cssId, css } = await buildGemsModule(file, code, options, runtimePath());
|
|
48
|
+
cssStore.set(cssId, css);
|
|
49
|
+
return js;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
var vite_default = gemcss;
|
|
56
|
+
export {
|
|
57
|
+
buildGemsModule,
|
|
58
|
+
vite_default as default,
|
|
59
|
+
gemcss
|
|
60
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LoaderContext } from 'webpack';
|
|
2
|
+
import { C as CompileOptions } from './compile-Br7cHtI1.js';
|
|
3
|
+
|
|
4
|
+
type GemcssLoaderOptions = CompileOptions;
|
|
5
|
+
/**
|
|
6
|
+
* Build the JS module for webpack: runtime CSS injection + a `gems` default export.
|
|
7
|
+
* A pure function — testable without webpack.
|
|
8
|
+
*/
|
|
9
|
+
declare function buildGemsModule(code: string, resourcePath: string, options?: CompileOptions, runtimeImport?: string): Promise<string>;
|
|
10
|
+
/** Webpack loader: `*.module.css` -> a `gems` JS module that injects its CSS at runtime. */
|
|
11
|
+
declare function gemcssLoader(this: LoaderContext<GemcssLoaderOptions>, source: string): void;
|
|
12
|
+
|
|
13
|
+
export { type GemcssLoaderOptions, buildGemsModule, gemcssLoader as default };
|
package/dist/webpack.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compile,
|
|
3
|
+
runtimePath
|
|
4
|
+
} from "./chunk-DMYDRHTS.js";
|
|
5
|
+
import "./chunk-CNXDOGPX.js";
|
|
6
|
+
|
|
7
|
+
// src/webpack.ts
|
|
8
|
+
async function buildGemsModule(code, resourcePath, options = {}, runtimeImport = runtimePath()) {
|
|
9
|
+
const { css, scoped, module } = await compile(code, resourcePath, options);
|
|
10
|
+
return `import { createGems } from ${JSON.stringify(runtimeImport)};
|
|
11
|
+
const __css = ${JSON.stringify(css)};
|
|
12
|
+
if (typeof document !== 'undefined') {
|
|
13
|
+
const __el = document.createElement('style');
|
|
14
|
+
__el.setAttribute('data-gemcss', ${JSON.stringify(resourcePath)});
|
|
15
|
+
__el.textContent = __css;
|
|
16
|
+
document.head.appendChild(__el);
|
|
17
|
+
}
|
|
18
|
+
export default createGems(${JSON.stringify(Object.keys(module.blocks))}, ${JSON.stringify(scoped)});
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
function gemcssLoader(source) {
|
|
22
|
+
const callback = this.async();
|
|
23
|
+
const options = this.getOptions?.() ?? {};
|
|
24
|
+
buildGemsModule(source, this.resourcePath, options).then(
|
|
25
|
+
(js) => callback(null, js),
|
|
26
|
+
(err) => callback(err instanceof Error ? err : new Error(String(err)))
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
buildGemsModule,
|
|
31
|
+
gemcssLoader as default
|
|
32
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gemcss",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Typed BEM classes from CSS modules — every block becomes a modifier-combinator function, with types inferred from the CSS",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"css-modules",
|
|
8
|
+
"bem",
|
|
9
|
+
"typescript",
|
|
10
|
+
"vite-plugin",
|
|
11
|
+
"webpack-loader",
|
|
12
|
+
"css",
|
|
13
|
+
"typed-css",
|
|
14
|
+
"classnames"
|
|
15
|
+
],
|
|
16
|
+
"license": "Unlicense",
|
|
17
|
+
"author": "Vladimir Kalinichev <wrumly@gmail.com>",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/vkalinichev/gemcss.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": "https://github.com/vkalinichev/gemcss/issues",
|
|
23
|
+
"homepage": "https://github.com/vkalinichev/gemcss#readme",
|
|
24
|
+
"packageManager": "pnpm@11.12.0",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.14"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"gemcss": "./dist/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
"./runtime": {
|
|
33
|
+
"types": "./dist/runtime.d.ts",
|
|
34
|
+
"default": "./dist/runtime.js"
|
|
35
|
+
},
|
|
36
|
+
"./vite": {
|
|
37
|
+
"types": "./dist/vite.d.ts",
|
|
38
|
+
"default": "./dist/vite.js"
|
|
39
|
+
},
|
|
40
|
+
"./webpack": {
|
|
41
|
+
"types": "./dist/webpack.d.ts",
|
|
42
|
+
"default": "./dist/webpack.js"
|
|
43
|
+
},
|
|
44
|
+
"./ts-plugin": {
|
|
45
|
+
"types": "./dist/ts-plugin.d.cts",
|
|
46
|
+
"default": "./dist/ts-plugin.cjs"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"ts-plugin"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "pnpm build:lib && pnpm -r --filter 'example-*' build",
|
|
55
|
+
"build:lib": "pnpm clean && tsup && node scripts/fix-ts-plugin-dts.mjs",
|
|
56
|
+
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:watch": "vitest",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"lint": "eslint .",
|
|
61
|
+
"format": "prettier --write .",
|
|
62
|
+
"check:exports": "publint --strict && attw --pack . --profile esm-only",
|
|
63
|
+
"release": "changeset publish",
|
|
64
|
+
"prepublishOnly": "pnpm build:lib"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"postcss": "^8.4.49",
|
|
68
|
+
"postcss-modules": "^6.0.1",
|
|
69
|
+
"postcss-selector-parser": "^7.0.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"typescript": "^5",
|
|
73
|
+
"vite": "^5 || ^6 || ^7",
|
|
74
|
+
"webpack": "^5"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"typescript": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"vite": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"webpack": {
|
|
84
|
+
"optional": true
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
89
|
+
"@changesets/cli": "2.31.0",
|
|
90
|
+
"@types/node": "^22.10.2",
|
|
91
|
+
"eslint": "^9.17.0",
|
|
92
|
+
"prettier": "^3.4.2",
|
|
93
|
+
"publint": "0.3.21",
|
|
94
|
+
"tsup": "^8.3.5",
|
|
95
|
+
"typescript": "^5.7.2",
|
|
96
|
+
"typescript-eslint": "^8.18.1",
|
|
97
|
+
"vite": "^6.0.0",
|
|
98
|
+
"vitest": "^2.1.8",
|
|
99
|
+
"webpack": "^5.97.1"
|
|
100
|
+
}
|
|
101
|
+
}
|