@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 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;
@@ -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 };
@@ -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 };
@@ -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;
@@ -0,0 +1,3 @@
1
+ //#region src/loader.d.ts
2
+ declare function betterCssModulesLoader(source: string): Promise<string>;
3
+ export = betterCssModulesLoader;
@@ -0,0 +1,4 @@
1
+ //#region src/loader.d.ts
2
+ declare function betterCssModulesLoader(source: string): Promise<string>;
3
+ //#endregion
4
+ export { betterCssModulesLoader as default };
@@ -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
+ }