Skip to content

Commit b65f889

Browse files
committed
✨ Initial release
Releases the initial (`0.1.0`) version. Provides a basic implementation that emits `.d.css.ts` files with named exports for the class names in the files matching the given glob. Updates README with guidance. Adds `glob` and `css-tree`.
1 parent 947f9ba commit b65f889

File tree

4 files changed

+279
-5
lines changed

4 files changed

+279
-5
lines changed

README.md

+97-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,90 @@
22

33
Basic TypeScript declaration generator for CSS files.
44

5+
## Install
6+
7+
Install the CLI as a dev dependency.
8+
9+
```shell
10+
npm install --save-dev css-typed
11+
```
12+
13+
## Usage
14+
15+
Run `css-typed` and pass it a glob targeting your CSS files.
16+
17+
```shell
18+
npx css-typed 'src/**/*.css'
19+
```
20+
21+
This will generate `.d.css.ts` files next to the original source files.
22+
23+
> Note: A CSS module file with the name `foo.module.css` will
24+
> emit `foo.module.d.css.ts`
25+
26+
## Configuration
27+
28+
Configure TypeScript to allow arbitrary extensions (TS 5+).
29+
30+
```json
31+
{
32+
"compilerOptions": {
33+
"allowArbitraryExtensions": true
34+
}
35+
}
36+
```
37+
38+
Add `*.d.css.ts` to your `.gitignore` if appropriate.
39+
40+
```shell
41+
echo '*.d.css.ts' >> .gitignore
42+
```
43+
44+
## Recipes
45+
46+
### Run script
47+
48+
To run it as part of your build, you will likely include it as a run script,
49+
maybe as `codegen` or `pretsc`.
50+
51+
```json
52+
{
53+
"scripts": {
54+
"codegen": "css-typed 'src/**/*.css'",
55+
"pretsc": "css-typed 'src/**/*.css'",
56+
"tsc": "tsc"
57+
}
58+
}
59+
```
60+
61+
### Watch
62+
63+
The CLI does not have built-in watch support. Feel free to [nodemon] or similar.
64+
65+
```json
66+
{
67+
"scripts": {
68+
"codegen": "css-typed 'src/**/*.css'",
69+
"codegen:watch": "nodemon -x 'npm run codegen' -w src -e css"
70+
}
71+
}
72+
```
73+
74+
[nodemon]: https://www.npmjs.com/package/nodemon
75+
76+
## Details
77+
78+
This (very basic) implementation uses [glob] for file matching and [css-tree]
79+
for CSS parsing. It extracts CSS classes (`ClassSelector` in CSS Tree’s AST) and
80+
exports them as `string` constants (named exports).
81+
82+
I chose CSS Tree after a brief search because it had a nice API, good
83+
documentation, and supported CSS nesting (a requirement for my original use
84+
case).
85+
86+
[glob]: https://www.npmjs.com/package/glob
87+
[css-tree]: https://www.npmjs.com/package/css-tree
88+
589
## Motivation
690

791
[typescript-plugin-css-modules] provides a great IDE experience, but cannot
@@ -24,10 +108,21 @@ appears [abandoned][174].
24108

25109
Therefore, I wrote my own (very basic) implementation.
26110

27-
<!-- prettier-ignore-start -->
28111
[typescript-plugin-css-modules]: https://www.npmjs.com/package/typescript-plugin-css-modules
29112
[typed-css-modules]: https://www.npmjs.com/package/typed-css-modules
30113
[typed-scss-modules]: https://www.npmjs.com/package/typed-scss-modules
31114
[css-modules-loader-core]: https://www.npmjs.com/package/css-modules-loader-core
32115
[174]: https://github.com/css-modules/css-modules-loader-core/issues/174
33-
<!-- prettier-ignore-end -->
116+
117+
## Future
118+
119+
This (very basic) implementation suited my immediate needs, but I see some
120+
improvements we could make. _All naming subject to bike shedding._
121+
122+
- `ext`: Traditional (pre TS 5) extension naming with `*.css.d.ts`
123+
- `ignore`: Ignore support
124+
- `format`: Class name formatting
125+
- (Related) Gracefully handle invalid names (example: kebab case)
126+
- `outDir`: Publish to a directory instead of next to the sources
127+
- `watch`: First-class watch mode
128+
- General CLI/UX improvements

main.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env node
2+
3+
import { existsSync } from "node:fs";
4+
import { readFile, writeFile } from "node:fs/promises";
5+
import { join, parse as parsePath } from "node:path";
6+
7+
import { parse as parseCss, walk } from "css-tree";
8+
import { glob } from "glob";
9+
10+
await main(process.argv[2]);
11+
12+
async function main(pattern) {
13+
if (!pattern) {
14+
console.error(`Expected glob pattern`);
15+
process.exit(2);
16+
}
17+
18+
const files = await glob(pattern);
19+
20+
const time = new Date().toISOString();
21+
const results = await Promise.all(
22+
files.map((file) => generateDeclaration(file, time)),
23+
);
24+
25+
const errors = results.filter(Boolean);
26+
if (errors.length > 0) {
27+
console.error(`Errors encountered`, errors);
28+
process.exit(3);
29+
}
30+
31+
process.exit(0);
32+
}
33+
34+
async function generateDeclaration(path, time) {
35+
// Handle case where the file got deleted by the time we got here
36+
if (!existsSync(path)) return;
37+
38+
const css = await readFile(path, `utf8`);
39+
40+
let ts = `// Generated from ${path} by css-typed at ${time}\n\n`;
41+
42+
const ast = parseCss(css, { filename: path });
43+
walk(ast, (node) => {
44+
if (node.type === `ClassSelector`) {
45+
ts += `export const ${node.name}: string;\n`;
46+
}
47+
});
48+
49+
await writeFile(dtsPath(path), ts, `utf8`);
50+
return undefined;
51+
}
52+
53+
function dtsPath(path) {
54+
const { dir, name, ext } = parsePath(path);
55+
return join(dir, `${name}.d${ext}.ts`);
56+
}

package-lock.json

+114-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "css-typed",
3-
"version": "0.0.1",
3+
"version": "0.1.0",
44
"description": "Basic TypeScript declaration generator for CSS files",
55
"keywords": [
66
"CSS",
@@ -23,6 +23,10 @@
2323
"type": "git",
2424
"url": "git+https://github.com/connorjs/css-typed.git"
2525
},
26+
"type": "module",
27+
"bin": {
28+
"css-typed": "main.js"
29+
},
2630
"scripts": {
2731
"format": "prettier -l '**/*.{json,md}' --ignore-path .gitignore",
2832
"prepare": "is-ci || husky install",
@@ -31,10 +35,17 @@
3135
"lint-staged": {
3236
"*.{json,md}": "prettier -w"
3337
},
38+
"dependencies": {
39+
"css-tree": "^2.3.1",
40+
"glob": "^9.3.2"
41+
},
3442
"devDependencies": {
3543
"husky": "^8.0.3",
3644
"is-ci": "^3.0.1",
3745
"lint-staged": "^13.2.0",
3846
"prettier": "^2.8.6"
47+
},
48+
"engines": {
49+
"node": ">=14"
3950
}
4051
}

0 commit comments

Comments
 (0)