@better-css-modules/core 0.1.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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/generator-BMINjrx4.cjs +178 -0
- package/dist/generator-CZGTIPYk.mjs +104 -0
- package/dist/index.cjs +98 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.mts +60 -0
- package/dist/index.mjs +86 -0
- package/dist/loader.cjs +16 -0
- package/dist/loader.d.cts +3 -0
- package/dist/loader.d.mts +4 -0
- package/dist/loader.mjs +16 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 k35o
|
|
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,60 @@
|
|
|
1
|
+
# @better-css-modules/core
|
|
2
|
+
|
|
3
|
+
Core library for better-css-modules. Provides CSS Modules parsing, type definition generation, unused class detection, file watching, and a webpack loader.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -D @better-css-modules/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
defineConfig,
|
|
16
|
+
loadConfig,
|
|
17
|
+
extractClassNames,
|
|
18
|
+
parseFile,
|
|
19
|
+
generateDts,
|
|
20
|
+
writeDts,
|
|
21
|
+
generateAll,
|
|
22
|
+
scanUnusedClasses,
|
|
23
|
+
startWatcher,
|
|
24
|
+
} from '@better-css-modules/core';
|
|
25
|
+
|
|
26
|
+
// Extract class names from CSS
|
|
27
|
+
const classNames = extractClassNames('.container { color: red; }');
|
|
28
|
+
// => ['container']
|
|
29
|
+
|
|
30
|
+
// Generate .d.ts content
|
|
31
|
+
const dts = generateDts(classNames);
|
|
32
|
+
|
|
33
|
+
// Generate all .d.ts files based on config
|
|
34
|
+
const config = await loadConfig(process.cwd());
|
|
35
|
+
await generateAll(config, process.cwd());
|
|
36
|
+
|
|
37
|
+
// Detect unused class names
|
|
38
|
+
const warnings = await scanUnusedClasses(process.cwd());
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## webpack Loader
|
|
42
|
+
|
|
43
|
+
A webpack-compatible loader is available at `@better-css-modules/core/loader`. This is used internally by `@better-css-modules/turbopack` for Turbopack integration.
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { defineConfig } from '@better-css-modules/core';
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
include: ['src/**/*.module.css'],
|
|
52
|
+
exclude: [],
|
|
53
|
+
outDir: '__generated__',
|
|
54
|
+
watch: false,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
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") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
let jiti = require("jiti");
|
|
24
|
+
let node_path = require("node:path");
|
|
25
|
+
node_path = __toESM(node_path);
|
|
26
|
+
let node_fs = require("node:fs");
|
|
27
|
+
node_fs = __toESM(node_fs);
|
|
28
|
+
let postcss = require("postcss");
|
|
29
|
+
postcss = __toESM(postcss);
|
|
30
|
+
let node_fs_promises = require("node:fs/promises");
|
|
31
|
+
node_fs_promises = __toESM(node_fs_promises);
|
|
32
|
+
let fast_glob = require("fast-glob");
|
|
33
|
+
fast_glob = __toESM(fast_glob);
|
|
34
|
+
//#region src/config.ts
|
|
35
|
+
const defaultConfig = {
|
|
36
|
+
include: ["src/**/*.module.css"],
|
|
37
|
+
exclude: [],
|
|
38
|
+
outDir: "__generated__",
|
|
39
|
+
watch: false
|
|
40
|
+
};
|
|
41
|
+
function defineConfig(config) {
|
|
42
|
+
return {
|
|
43
|
+
...defaultConfig,
|
|
44
|
+
...config
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
48
|
+
const configFileName = "better-css-modules.config";
|
|
49
|
+
for (const ext of [
|
|
50
|
+
".ts",
|
|
51
|
+
".mts",
|
|
52
|
+
".cts",
|
|
53
|
+
".js",
|
|
54
|
+
".mjs",
|
|
55
|
+
".cjs"
|
|
56
|
+
]) {
|
|
57
|
+
const configPath = node_path.default.resolve(cwd, configFileName + ext);
|
|
58
|
+
if (node_fs.default.existsSync(configPath)) {
|
|
59
|
+
const mod = await (0, jiti.createJiti)(cwd).import(configPath);
|
|
60
|
+
const loaded = mod.default ?? mod;
|
|
61
|
+
return {
|
|
62
|
+
...defaultConfig,
|
|
63
|
+
...loaded
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return defaultConfig;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/parser.ts
|
|
71
|
+
/**
|
|
72
|
+
* Extract class names from CSS file contents.
|
|
73
|
+
*/
|
|
74
|
+
function extractClassNames(css) {
|
|
75
|
+
const root = postcss.default.parse(css);
|
|
76
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
77
|
+
root.walkRules((rule) => {
|
|
78
|
+
const selectorParts = rule.selector.split(/\s*,\s*/);
|
|
79
|
+
for (const selector of selectorParts) {
|
|
80
|
+
const matches = selector.matchAll(/\.([a-zA-Z_-][\w-]*)/g);
|
|
81
|
+
for (const match of matches) classNames.add(match[1]);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return [...classNames].sort();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read a CSS Modules file and extract class names.
|
|
88
|
+
*/
|
|
89
|
+
async function parseFile(filePath) {
|
|
90
|
+
return extractClassNames(await node_fs_promises.default.readFile(filePath, "utf-8"));
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/generator.ts
|
|
94
|
+
/**
|
|
95
|
+
* Generate .d.ts content from an array of class names.
|
|
96
|
+
*/
|
|
97
|
+
function generateDts(classNames) {
|
|
98
|
+
return `declare const styles: {\n${classNames.map((name) => {
|
|
99
|
+
return ` readonly ${/^[a-zA-Z_$][\w$]*$/.test(name) ? name : `"${name}"`}: string;`;
|
|
100
|
+
}).join("\n")}\n};\nexport default styles;\n`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Generate and write a .d.ts file for a CSS Modules file.
|
|
104
|
+
*
|
|
105
|
+
* @param cssFilePath - Path to the source CSS Modules file
|
|
106
|
+
* @param classNames - Array of extracted class names
|
|
107
|
+
* @param outDir - Output directory
|
|
108
|
+
* @param cwd - Working directory
|
|
109
|
+
*/
|
|
110
|
+
async function writeDts(cssFilePath, classNames, outDir, cwd = process.cwd()) {
|
|
111
|
+
const dtsFileName = node_path.default.relative(cwd, cssFilePath) + ".d.ts";
|
|
112
|
+
const dtsPath = node_path.default.resolve(cwd, outDir, dtsFileName);
|
|
113
|
+
await node_fs_promises.default.mkdir(node_path.default.dirname(dtsPath), { recursive: true });
|
|
114
|
+
await node_fs_promises.default.writeFile(dtsPath, generateDts(classNames), "utf-8");
|
|
115
|
+
return dtsPath;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Batch-generate .d.ts files for all CSS Modules files based on the config.
|
|
119
|
+
*/
|
|
120
|
+
async function generateAll(config, cwd = process.cwd()) {
|
|
121
|
+
const files = await (0, fast_glob.default)(config.include, {
|
|
122
|
+
cwd,
|
|
123
|
+
ignore: config.exclude,
|
|
124
|
+
absolute: true
|
|
125
|
+
});
|
|
126
|
+
return await Promise.all(files.map(async (file) => {
|
|
127
|
+
return writeDts(file, await parseFile(file), config.outDir, cwd);
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
Object.defineProperty(exports, "__toESM", {
|
|
132
|
+
enumerable: true,
|
|
133
|
+
get: function() {
|
|
134
|
+
return __toESM;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
Object.defineProperty(exports, "defineConfig", {
|
|
138
|
+
enumerable: true,
|
|
139
|
+
get: function() {
|
|
140
|
+
return defineConfig;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
Object.defineProperty(exports, "extractClassNames", {
|
|
144
|
+
enumerable: true,
|
|
145
|
+
get: function() {
|
|
146
|
+
return extractClassNames;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
Object.defineProperty(exports, "generateAll", {
|
|
150
|
+
enumerable: true,
|
|
151
|
+
get: function() {
|
|
152
|
+
return generateAll;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
Object.defineProperty(exports, "generateDts", {
|
|
156
|
+
enumerable: true,
|
|
157
|
+
get: function() {
|
|
158
|
+
return generateDts;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
Object.defineProperty(exports, "loadConfig", {
|
|
162
|
+
enumerable: true,
|
|
163
|
+
get: function() {
|
|
164
|
+
return loadConfig;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
Object.defineProperty(exports, "parseFile", {
|
|
168
|
+
enumerable: true,
|
|
169
|
+
get: function() {
|
|
170
|
+
return parseFile;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
Object.defineProperty(exports, "writeDts", {
|
|
174
|
+
enumerable: true,
|
|
175
|
+
get: function() {
|
|
176
|
+
return writeDts;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createJiti } from "jiti";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import postcss from "postcss";
|
|
5
|
+
import fs$1 from "node:fs/promises";
|
|
6
|
+
import fg from "fast-glob";
|
|
7
|
+
//#region src/config.ts
|
|
8
|
+
const defaultConfig = {
|
|
9
|
+
include: ["src/**/*.module.css"],
|
|
10
|
+
exclude: [],
|
|
11
|
+
outDir: "__generated__",
|
|
12
|
+
watch: false
|
|
13
|
+
};
|
|
14
|
+
function defineConfig(config) {
|
|
15
|
+
return {
|
|
16
|
+
...defaultConfig,
|
|
17
|
+
...config
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
21
|
+
const configFileName = "better-css-modules.config";
|
|
22
|
+
for (const ext of [
|
|
23
|
+
".ts",
|
|
24
|
+
".mts",
|
|
25
|
+
".cts",
|
|
26
|
+
".js",
|
|
27
|
+
".mjs",
|
|
28
|
+
".cjs"
|
|
29
|
+
]) {
|
|
30
|
+
const configPath = path.resolve(cwd, configFileName + ext);
|
|
31
|
+
if (fs.existsSync(configPath)) {
|
|
32
|
+
const mod = await createJiti(cwd).import(configPath);
|
|
33
|
+
const loaded = mod.default ?? mod;
|
|
34
|
+
return {
|
|
35
|
+
...defaultConfig,
|
|
36
|
+
...loaded
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return defaultConfig;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/parser.ts
|
|
44
|
+
/**
|
|
45
|
+
* Extract class names from CSS file contents.
|
|
46
|
+
*/
|
|
47
|
+
function extractClassNames(css) {
|
|
48
|
+
const root = postcss.parse(css);
|
|
49
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
50
|
+
root.walkRules((rule) => {
|
|
51
|
+
const selectorParts = rule.selector.split(/\s*,\s*/);
|
|
52
|
+
for (const selector of selectorParts) {
|
|
53
|
+
const matches = selector.matchAll(/\.([a-zA-Z_-][\w-]*)/g);
|
|
54
|
+
for (const match of matches) classNames.add(match[1]);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return [...classNames].sort();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read a CSS Modules file and extract class names.
|
|
61
|
+
*/
|
|
62
|
+
async function parseFile(filePath) {
|
|
63
|
+
return extractClassNames(await fs$1.readFile(filePath, "utf-8"));
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/generator.ts
|
|
67
|
+
/**
|
|
68
|
+
* Generate .d.ts content from an array of class names.
|
|
69
|
+
*/
|
|
70
|
+
function generateDts(classNames) {
|
|
71
|
+
return `declare const styles: {\n${classNames.map((name) => {
|
|
72
|
+
return ` readonly ${/^[a-zA-Z_$][\w$]*$/.test(name) ? name : `"${name}"`}: string;`;
|
|
73
|
+
}).join("\n")}\n};\nexport default styles;\n`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate and write a .d.ts file for a CSS Modules file.
|
|
77
|
+
*
|
|
78
|
+
* @param cssFilePath - Path to the source CSS Modules file
|
|
79
|
+
* @param classNames - Array of extracted class names
|
|
80
|
+
* @param outDir - Output directory
|
|
81
|
+
* @param cwd - Working directory
|
|
82
|
+
*/
|
|
83
|
+
async function writeDts(cssFilePath, classNames, outDir, cwd = process.cwd()) {
|
|
84
|
+
const dtsFileName = path.relative(cwd, cssFilePath) + ".d.ts";
|
|
85
|
+
const dtsPath = path.resolve(cwd, outDir, dtsFileName);
|
|
86
|
+
await fs$1.mkdir(path.dirname(dtsPath), { recursive: true });
|
|
87
|
+
await fs$1.writeFile(dtsPath, generateDts(classNames), "utf-8");
|
|
88
|
+
return dtsPath;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Batch-generate .d.ts files for all CSS Modules files based on the config.
|
|
92
|
+
*/
|
|
93
|
+
async function generateAll(config, cwd = process.cwd()) {
|
|
94
|
+
const files = await fg(config.include, {
|
|
95
|
+
cwd,
|
|
96
|
+
ignore: config.exclude,
|
|
97
|
+
absolute: true
|
|
98
|
+
});
|
|
99
|
+
return await Promise.all(files.map(async (file) => {
|
|
100
|
+
return writeDts(file, await parseFile(file), config.outDir, cwd);
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { parseFile as a, extractClassNames as i, generateDts as n, defineConfig as o, writeDts as r, loadConfig as s, generateAll as t };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_generator = require("./generator-BMINjrx4.cjs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
node_path = require_generator.__toESM(node_path);
|
|
5
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
node_fs_promises = require_generator.__toESM(node_fs_promises);
|
|
7
|
+
let fast_glob = require("fast-glob");
|
|
8
|
+
fast_glob = require_generator.__toESM(fast_glob);
|
|
9
|
+
let chokidar = require("chokidar");
|
|
10
|
+
//#region src/scanner.ts
|
|
11
|
+
/**
|
|
12
|
+
* Scan TS/TSX file contents for CSS Modules class name usage.
|
|
13
|
+
* Detects patterns like styles.foo / styles["foo"] / styles['foo'].
|
|
14
|
+
*/
|
|
15
|
+
function findUsedClassNames(tsContent) {
|
|
16
|
+
const used = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const match of tsContent.matchAll(/styles\.([a-zA-Z_$][\w$]*)/g)) used.add(match[1]);
|
|
18
|
+
for (const match of tsContent.matchAll(/styles\[["']([^"']+)["']\]/g)) used.add(match[1]);
|
|
19
|
+
return used;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Identify the source file of a CSS Modules import.
|
|
23
|
+
* Detects patterns like import styles from './Foo.module.css'.
|
|
24
|
+
*/
|
|
25
|
+
function findCssModuleImports(tsContent) {
|
|
26
|
+
const imports = [];
|
|
27
|
+
for (const match of tsContent.matchAll(/import\s+\w+\s+from\s+['"]([^'"]+\.module\.css)['"]/g)) imports.push(match[1]);
|
|
28
|
+
return imports;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Scan TS/TSX files under the specified directory
|
|
32
|
+
* and detect unused CSS Modules class names.
|
|
33
|
+
*/
|
|
34
|
+
async function scanUnusedClasses(cwd) {
|
|
35
|
+
const tsFiles = await (0, fast_glob.default)([
|
|
36
|
+
"**/*.{ts,tsx,js,jsx}",
|
|
37
|
+
"!node_modules",
|
|
38
|
+
"!dist",
|
|
39
|
+
"!__generated__"
|
|
40
|
+
], {
|
|
41
|
+
cwd,
|
|
42
|
+
absolute: true
|
|
43
|
+
});
|
|
44
|
+
const warnings = [];
|
|
45
|
+
for (const tsFile of tsFiles) {
|
|
46
|
+
const content = await node_fs_promises.default.readFile(tsFile, "utf-8");
|
|
47
|
+
const cssImports = findCssModuleImports(content);
|
|
48
|
+
if (cssImports.length === 0) continue;
|
|
49
|
+
const usedNames = findUsedClassNames(content);
|
|
50
|
+
for (const cssImport of cssImports) {
|
|
51
|
+
const cssPath = node_path.default.resolve(node_path.default.dirname(tsFile), cssImport);
|
|
52
|
+
try {
|
|
53
|
+
const classNames = await require_generator.parseFile(cssPath);
|
|
54
|
+
for (const className of classNames) if (!usedNames.has(className)) warnings.push({
|
|
55
|
+
cssFile: cssPath,
|
|
56
|
+
className
|
|
57
|
+
});
|
|
58
|
+
} catch {}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return warnings;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/watcher.ts
|
|
65
|
+
function startWatcher(config, cwd = process.cwd()) {
|
|
66
|
+
const watcher = (0, chokidar.watch)(config.include, {
|
|
67
|
+
cwd,
|
|
68
|
+
ignored: config.exclude,
|
|
69
|
+
ignoreInitial: false
|
|
70
|
+
});
|
|
71
|
+
watcher.on("add", async (filePath) => {
|
|
72
|
+
await regenerate(filePath, config.outDir, cwd);
|
|
73
|
+
});
|
|
74
|
+
watcher.on("change", async (filePath) => {
|
|
75
|
+
await regenerate(filePath, config.outDir, cwd);
|
|
76
|
+
});
|
|
77
|
+
watcher.on("unlink", () => {});
|
|
78
|
+
return watcher;
|
|
79
|
+
}
|
|
80
|
+
async function regenerate(relativePath, outDir, cwd) {
|
|
81
|
+
const absolutePath = new URL(relativePath, `file://${cwd}/`).pathname;
|
|
82
|
+
try {
|
|
83
|
+
const dtsPath = await require_generator.writeDts(absolutePath, await require_generator.parseFile(absolutePath), outDir, cwd);
|
|
84
|
+
console.log(`[better-css-modules] generated: ${dtsPath}`);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(`[better-css-modules] error processing ${relativePath}:`, err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
exports.defineConfig = require_generator.defineConfig;
|
|
91
|
+
exports.extractClassNames = require_generator.extractClassNames;
|
|
92
|
+
exports.generateAll = require_generator.generateAll;
|
|
93
|
+
exports.generateDts = require_generator.generateDts;
|
|
94
|
+
exports.loadConfig = require_generator.loadConfig;
|
|
95
|
+
exports.parseFile = require_generator.parseFile;
|
|
96
|
+
exports.scanUnusedClasses = scanUnusedClasses;
|
|
97
|
+
exports.startWatcher = startWatcher;
|
|
98
|
+
exports.writeDts = require_generator.writeDts;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as chokidar from "chokidar";
|
|
2
|
+
|
|
3
|
+
//#region src/config.d.ts
|
|
4
|
+
interface Config {
|
|
5
|
+
/** Glob patterns for target files */
|
|
6
|
+
include: string[];
|
|
7
|
+
/** Exclusion patterns */
|
|
8
|
+
exclude: string[];
|
|
9
|
+
/** Output directory */
|
|
10
|
+
outDir: string;
|
|
11
|
+
/** Watch mode (for CLI) */
|
|
12
|
+
watch: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare function defineConfig(config: Partial<Config>): Config;
|
|
15
|
+
declare function loadConfig(cwd?: string): Promise<Config>;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/parser.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* Extract class names from CSS file contents.
|
|
20
|
+
*/
|
|
21
|
+
declare function extractClassNames(css: string): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Read a CSS Modules file and extract class names.
|
|
24
|
+
*/
|
|
25
|
+
declare function parseFile(filePath: string): Promise<string[]>;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/generator.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* Generate .d.ts content from an array of class names.
|
|
30
|
+
*/
|
|
31
|
+
declare function generateDts(classNames: string[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Generate and write a .d.ts file for a CSS Modules file.
|
|
34
|
+
*
|
|
35
|
+
* @param cssFilePath - Path to the source CSS Modules file
|
|
36
|
+
* @param classNames - Array of extracted class names
|
|
37
|
+
* @param outDir - Output directory
|
|
38
|
+
* @param cwd - Working directory
|
|
39
|
+
*/
|
|
40
|
+
declare function writeDts(cssFilePath: string, classNames: string[], outDir: string, cwd?: string): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Batch-generate .d.ts files for all CSS Modules files based on the config.
|
|
43
|
+
*/
|
|
44
|
+
declare function generateAll(config: Config, cwd?: string): Promise<string[]>;
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/scanner.d.ts
|
|
47
|
+
interface UnusedClassWarning {
|
|
48
|
+
cssFile: string;
|
|
49
|
+
className: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Scan TS/TSX files under the specified directory
|
|
53
|
+
* and detect unused CSS Modules class names.
|
|
54
|
+
*/
|
|
55
|
+
declare function scanUnusedClasses(cwd: string): Promise<UnusedClassWarning[]>;
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/watcher.d.ts
|
|
58
|
+
declare function startWatcher(config: Config, cwd?: string): chokidar.FSWatcher;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { type Config, type UnusedClassWarning, defineConfig, extractClassNames, generateAll, generateDts, loadConfig, parseFile, scanUnusedClasses, startWatcher, writeDts };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as chokidar from "chokidar";
|
|
2
|
+
|
|
3
|
+
//#region src/config.d.ts
|
|
4
|
+
interface Config {
|
|
5
|
+
/** Glob patterns for target files */
|
|
6
|
+
include: string[];
|
|
7
|
+
/** Exclusion patterns */
|
|
8
|
+
exclude: string[];
|
|
9
|
+
/** Output directory */
|
|
10
|
+
outDir: string;
|
|
11
|
+
/** Watch mode (for CLI) */
|
|
12
|
+
watch: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare function defineConfig(config: Partial<Config>): Config;
|
|
15
|
+
declare function loadConfig(cwd?: string): Promise<Config>;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/parser.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* Extract class names from CSS file contents.
|
|
20
|
+
*/
|
|
21
|
+
declare function extractClassNames(css: string): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Read a CSS Modules file and extract class names.
|
|
24
|
+
*/
|
|
25
|
+
declare function parseFile(filePath: string): Promise<string[]>;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/generator.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* Generate .d.ts content from an array of class names.
|
|
30
|
+
*/
|
|
31
|
+
declare function generateDts(classNames: string[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Generate and write a .d.ts file for a CSS Modules file.
|
|
34
|
+
*
|
|
35
|
+
* @param cssFilePath - Path to the source CSS Modules file
|
|
36
|
+
* @param classNames - Array of extracted class names
|
|
37
|
+
* @param outDir - Output directory
|
|
38
|
+
* @param cwd - Working directory
|
|
39
|
+
*/
|
|
40
|
+
declare function writeDts(cssFilePath: string, classNames: string[], outDir: string, cwd?: string): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Batch-generate .d.ts files for all CSS Modules files based on the config.
|
|
43
|
+
*/
|
|
44
|
+
declare function generateAll(config: Config, cwd?: string): Promise<string[]>;
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/scanner.d.ts
|
|
47
|
+
interface UnusedClassWarning {
|
|
48
|
+
cssFile: string;
|
|
49
|
+
className: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Scan TS/TSX files under the specified directory
|
|
53
|
+
* and detect unused CSS Modules class names.
|
|
54
|
+
*/
|
|
55
|
+
declare function scanUnusedClasses(cwd: string): Promise<UnusedClassWarning[]>;
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/watcher.d.ts
|
|
58
|
+
declare function startWatcher(config: Config, cwd?: string): chokidar.FSWatcher;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { type Config, type UnusedClassWarning, defineConfig, extractClassNames, generateAll, generateDts, loadConfig, parseFile, scanUnusedClasses, startWatcher, writeDts };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { a as parseFile, i as extractClassNames, n as generateDts, o as defineConfig, r as writeDts, s as loadConfig, t as generateAll } from "./generator-CZGTIPYk.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import fg from "fast-glob";
|
|
5
|
+
import { watch } from "chokidar";
|
|
6
|
+
//#region src/scanner.ts
|
|
7
|
+
/**
|
|
8
|
+
* Scan TS/TSX file contents for CSS Modules class name usage.
|
|
9
|
+
* Detects patterns like styles.foo / styles["foo"] / styles['foo'].
|
|
10
|
+
*/
|
|
11
|
+
function findUsedClassNames(tsContent) {
|
|
12
|
+
const used = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const match of tsContent.matchAll(/styles\.([a-zA-Z_$][\w$]*)/g)) used.add(match[1]);
|
|
14
|
+
for (const match of tsContent.matchAll(/styles\[["']([^"']+)["']\]/g)) used.add(match[1]);
|
|
15
|
+
return used;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Identify the source file of a CSS Modules import.
|
|
19
|
+
* Detects patterns like import styles from './Foo.module.css'.
|
|
20
|
+
*/
|
|
21
|
+
function findCssModuleImports(tsContent) {
|
|
22
|
+
const imports = [];
|
|
23
|
+
for (const match of tsContent.matchAll(/import\s+\w+\s+from\s+['"]([^'"]+\.module\.css)['"]/g)) imports.push(match[1]);
|
|
24
|
+
return imports;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Scan TS/TSX files under the specified directory
|
|
28
|
+
* and detect unused CSS Modules class names.
|
|
29
|
+
*/
|
|
30
|
+
async function scanUnusedClasses(cwd) {
|
|
31
|
+
const tsFiles = await fg([
|
|
32
|
+
"**/*.{ts,tsx,js,jsx}",
|
|
33
|
+
"!node_modules",
|
|
34
|
+
"!dist",
|
|
35
|
+
"!__generated__"
|
|
36
|
+
], {
|
|
37
|
+
cwd,
|
|
38
|
+
absolute: true
|
|
39
|
+
});
|
|
40
|
+
const warnings = [];
|
|
41
|
+
for (const tsFile of tsFiles) {
|
|
42
|
+
const content = await fs.readFile(tsFile, "utf-8");
|
|
43
|
+
const cssImports = findCssModuleImports(content);
|
|
44
|
+
if (cssImports.length === 0) continue;
|
|
45
|
+
const usedNames = findUsedClassNames(content);
|
|
46
|
+
for (const cssImport of cssImports) {
|
|
47
|
+
const cssPath = path.resolve(path.dirname(tsFile), cssImport);
|
|
48
|
+
try {
|
|
49
|
+
const classNames = await parseFile(cssPath);
|
|
50
|
+
for (const className of classNames) if (!usedNames.has(className)) warnings.push({
|
|
51
|
+
cssFile: cssPath,
|
|
52
|
+
className
|
|
53
|
+
});
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return warnings;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/watcher.ts
|
|
61
|
+
function startWatcher(config, cwd = process.cwd()) {
|
|
62
|
+
const watcher = watch(config.include, {
|
|
63
|
+
cwd,
|
|
64
|
+
ignored: config.exclude,
|
|
65
|
+
ignoreInitial: false
|
|
66
|
+
});
|
|
67
|
+
watcher.on("add", async (filePath) => {
|
|
68
|
+
await regenerate(filePath, config.outDir, cwd);
|
|
69
|
+
});
|
|
70
|
+
watcher.on("change", async (filePath) => {
|
|
71
|
+
await regenerate(filePath, config.outDir, cwd);
|
|
72
|
+
});
|
|
73
|
+
watcher.on("unlink", () => {});
|
|
74
|
+
return watcher;
|
|
75
|
+
}
|
|
76
|
+
async function regenerate(relativePath, outDir, cwd) {
|
|
77
|
+
const absolutePath = new URL(relativePath, `file://${cwd}/`).pathname;
|
|
78
|
+
try {
|
|
79
|
+
const dtsPath = await writeDts(absolutePath, await parseFile(absolutePath), outDir, cwd);
|
|
80
|
+
console.log(`[better-css-modules] generated: ${dtsPath}`);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error(`[better-css-modules] error processing ${relativePath}:`, err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { defineConfig, extractClassNames, generateAll, generateDts, loadConfig, parseFile, scanUnusedClasses, startWatcher, writeDts };
|
package/dist/loader.cjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const require_generator = require("./generator-BMINjrx4.cjs");
|
|
2
|
+
//#region src/loader.ts
|
|
3
|
+
let configPromise;
|
|
4
|
+
function getConfig(cwd) {
|
|
5
|
+
if (!configPromise) configPromise = require_generator.loadConfig(cwd);
|
|
6
|
+
return configPromise;
|
|
7
|
+
}
|
|
8
|
+
async function betterCssModulesLoader(source) {
|
|
9
|
+
const resourcePath = this.resourcePath;
|
|
10
|
+
const cwd = this.rootContext || process.cwd();
|
|
11
|
+
const config = await getConfig(cwd);
|
|
12
|
+
await require_generator.writeDts(resourcePath, require_generator.extractClassNames(source), config.outDir, cwd);
|
|
13
|
+
return source;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
module.exports = betterCssModulesLoader;
|
package/dist/loader.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { i as extractClassNames, r as writeDts, s as loadConfig } from "./generator-CZGTIPYk.mjs";
|
|
2
|
+
//#region src/loader.ts
|
|
3
|
+
let configPromise;
|
|
4
|
+
function getConfig(cwd) {
|
|
5
|
+
if (!configPromise) configPromise = loadConfig(cwd);
|
|
6
|
+
return configPromise;
|
|
7
|
+
}
|
|
8
|
+
async function betterCssModulesLoader(source) {
|
|
9
|
+
const resourcePath = this.resourcePath;
|
|
10
|
+
const cwd = this.rootContext || process.cwd();
|
|
11
|
+
const config = await getConfig(cwd);
|
|
12
|
+
await writeDts(resourcePath, extractClassNames(source), config.outDir, cwd);
|
|
13
|
+
return source;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { betterCssModulesLoader as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@better-css-modules/core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "k8o",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/k35o/better-css-modules.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
},
|
|
17
|
+
"./loader": {
|
|
18
|
+
"import": "./dist/loader.mjs",
|
|
19
|
+
"require": "./dist/loader.cjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.cjs",
|
|
23
|
+
"module": "./dist/index.mjs",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"chokidar": "5.0.0",
|
|
30
|
+
"fast-glob": "3.3.3",
|
|
31
|
+
"jiti": "2.6.1",
|
|
32
|
+
"postcss": "8.5.8"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "vp test",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
}
|
|
38
|
+
}
|