Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions .github/workflows/pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,19 @@ jobs:

Test:
name: Test (${{ matrix.node }} | ${{ matrix.platform.os }})
needs: Build # Verify ci-build first
defaults:
run:
shell: bash
runs-on: ${{ matrix.platform.os }}
runs-on: ${{ matrix.platform.os }}-latest
strategy:
matrix:
node:
- 20.x
- 22.x
platform:
- os: ubuntu-latest
- os: macos-latest
- os: windows-latest
- os: ubuntu
- os: macos
- os: windows
fail-fast: false

steps:
Expand All @@ -77,29 +76,52 @@ jobs:
- name: Build
run: npm run build

- name: "Test 1: Default case"
- name: "Test 1: default case"
run: |
scripts/test.sh foo
scripts/test.sh foo '' '*.css'

- name: "Test 2: localsConvention, first position"
if: success() || failure()
run: |
scripts/test.sh casing/casing "--localsConvention camelCaseOnly" casing/camelCaseOnly
scripts/test.sh casing/casing '' '--localsConvention camelCaseOnly *.css' casing/camelCaseOnly

- name: "Test 3: localsConvention, second position"
if: success() || failure()
run: |
scripts/test.sh casing/casing "" casing/camelCaseOnly "--localsConvention camelCaseOnly"
scripts/test.sh casing/casing '' '*.css --localsConvention camelCaseOnly' casing/camelCaseOnly

- name: "Test 4: relative outdir"
if: success() || failure()
run: |
scripts/test.sh foo "--outdir generated" "" "" generated/
scripts/test.sh foo '' '--outdir generated *.css' '' generated/

- name: "Test 5: absolute outdir"
if: success() || failure()
run: |
scripts/test.sh foo "-o $GITHUB_WORKSPACE/generated" "" "" "$GITHUB_WORKSPACE"/generated/
scripts/test.sh foo "" "-o $GITHUB_WORKSPACE/generated *.css" "" "$GITHUB_WORKSPACE"/generated/
# Note: This test uses double quotes, which expands differently.

- name: "Test 6: json file config"
if: success() || failure()
run: |
scripts/test.sh foo csstypedrc.json

- name: "Test 7: yaml file config"
if: success() || failure()
run: |
scripts/test.sh foo csstypedrc.yaml '' '' generated/

- name: "Test 8: custom config path"
if: success() || failure()
run: |
scripts/test.sh foo css-typed-rc.yaml '-c .config/css-typed-rc.yaml'

- name: "Test 9: mjs file config"
if: matrix.platform.os != 'windows' && (success() || failure())
# Do not run on Windows due to Windows-only ESM import bug.
# This _could_ be an issue with css-typed, but could be a test/deps issue.
run: |
scripts/test.sh foo custom-config-path.config.mjs '-c .config/custom-config-path.config.mjs'

Publish:
if: ${{ github.ref == 'refs/heads/main' }}
Expand Down
49 changes: 44 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ TypeScript declaration generator for CSS files.
<summary><strong>Table of Contents</strong></summary>

- [Usage](#usage)
- [Options](#options)
- [Recipes](#recipes)
- [Motivation](#motivation)
- [Contributing](#contributing)
Expand Down Expand Up @@ -69,9 +70,35 @@ echo '*.d.css.ts' >> .gitignore
The following table lists the options `css-typed` supports.
Also run `css-typed -h` on the command line.

| CLI option | Default | Description |
| :------------------: | :----------: | :----------------------------- |
| `--localsConvention` | `dashesOnly` | Style of exported class names. |
| CLI option | Default | Description |
| :------------------: | :----------: | :------------------------------------- |
| `-c` or `--config` | Heuristics | Custom path to the configuration file. |
| `--localsConvention` | `dashesOnly` | Style of exported class names. |

### config

`css-typed` supports loading options from a configuration file instead of using command line arguments.
To load from a custom path, use the `-c` or `--config` option.
By default, `css-typed` looks in the following locations.
Extensionless "rc" files can have JSON or YAML format.

- Package file: `css-typed` property in `package.json` or `package.yaml`
- Root rc files: `.csstypedrc` with no extension or one of `json`, `yaml`, `yml`, `js`, `cjs`, or `mjs`
- Config folder rc files: `.config/csstypedrc` with no extension or one of `json`, `yaml`, `yml`, `js`, `cjs`, or `mjs`
- Root config files: `css-typed.config` with an extension of `js`, `cjs`, or `mjs`

<details>
<summary>Look under the hood</summary>

Under the hood, `css-typed` uses [lilconfig] to load configuration files.
It supports YAML files via [js-yaml].

See [src/config.ts](src/config.ts) for the implementation.

</details>

[lilconfig]: https://www.npmjs.com/package/lilconfig
[js-yaml]: https://www.npmjs.com/package/js-yaml

### localsConvention

Expand All @@ -89,11 +116,12 @@ The default matches CSS naming practices (`kebab-case`).

> **IMPORTANT**
>
> Note that the non-`*Only` values MAY have TypeScript bugs.
> Note that `camelCase` and `dashes` MAY have TypeScript bugs.
> TypeScript 5.6 may help with the named exports for these.
>
> If you encounter a bug, please file an issue.
> In the mean-time, consider using `camelCaseOnly` instead (or `dashesOnly` which is the default).
> In the mean-time, consider using `camelCaseOnly` instead.
> (Or `dashesOnly` which is the default.)

## Recipes

Expand Down Expand Up @@ -145,6 +173,7 @@ declare module "*.module.css" {
Both depend on [css-modules-loader-core], which appears [abandoned][174].

Therefore, I wrote my own (very basic) implementation.
See [§Implementation details](#implementation-details) for more information.

[typescript-plugin-css-modules]: https://www.npmjs.com/package/typescript-plugin-css-modules
[typed-css-modules]: https://www.npmjs.com/package/typed-css-modules
Expand All @@ -161,7 +190,17 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md).
This (very basic) implementation uses [glob] for file matching and [css-tree] for CSS parsing.
It extracts CSS classes (`ClassSelector` in CSS Tree’s AST) and exports them as `string` constants (named exports).

The CSS-file class name is modified for JS export according to the [localsConvention](#localsconvention) option.
The implementation matches PostCSS.

I chose CSS Tree after a brief search because it had a nice API, good documentation, and supported CSS nesting (a requirement for my original use case).

`css-typed` uses [Commander.js][commander] for command line parsing and [lilconfig] for configuration file loading.

The “brand” image/logo combines the public CSS 3 and TypeScript logos with a basic plus icon in between.
See [css-typed.svg](images/css-typed.svg).

[glob]: https://www.npmjs.com/package/glob
[css-tree]: https://www.npmjs.com/package/css-tree
[commander]: https://www.npmjs.com/package/commander
[lilconfig]: https://www.npmjs.com/package/lilconfig
1 change: 1 addition & 0 deletions fixtures/config/css-typed-rc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pattern: "*.css"
1 change: 1 addition & 0 deletions fixtures/config/csstypedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "pattern": "*.css" }
2 changes: 2 additions & 0 deletions fixtures/config/csstypedrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pattern: "*.css"
outdir: generated
1 change: 1 addition & 0 deletions fixtures/config/custom-config-path.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default { pattern: `*.css` };
35 changes: 27 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@
"commander": "^12.1.0",
"css-tree": "^2.3.1",
"glob": "^11.0.0",
"js-yaml": "^4.1.0",
"lilconfig": "^3.1.2",
"lodash.camelcase": "^4.3.0"
},
"devDependencies": {
"@connorjs/tsconfig": "~0.3.0",
"@types/css-tree": "^2.3.8",
"@types/js-yaml": "^4.0.9",
"@types/lodash.camelcase": "^4.3.9",
"@types/node": "^20.14.14",
"esbuild": "~0.23.0",
Expand All @@ -64,6 +67,7 @@
"lint-staged": "^15.2.8",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"type-fest": "^4.23.0",
"typescript": "^5.5.4",
"vitest": "^2.0.5"
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ const result = await build({
});

const analysis = await analyzeMetafile(result.metafile);
console.log(analysis);
console.info(analysis);
27 changes: 18 additions & 9 deletions scripts/test.sh
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
#!/usr/bin/env bash
set -eo pipefail # Removed `-u` which failed on macos for `options`
IFS=$' ' # We want space splitting for this script

# $1 is the input name, relative to `fixtures`. Required.
input=$1

# $2 is the standard (before) options. Defaults to "".
IFS=" " read -r -a beforeOpts <<< "${2:-}"
# $2 is the config file name, relative to `fixtures/config`. Defaults to $1.yaml.
config=${2:-$1.yaml}

# $3 is the output name, relative to `fixtures`. Defaults to $1.
output=${3:-$1}
# $3 is the options. Defaults to "".
read -r -a options <<< "${3:-}"

# $4 is the after options. Use an array. Defaults to "".
IFS=" " read -r -a afterOpts <<< "${4:-}"
# $4 is the output name, relative to `fixtures`. Defaults to $1.
output=${4:-$1}

# $5 is the path prefix for output. Defaults to "".
prefix=${5:-}

# Run from $RUNNER_TEMP for auto-cleanup.
cp fixtures/${input}.css $RUNNER_TEMP/test.css
cp fixtures/${output}.d.css.ts $RUNNER_TEMP/expected.d.css.ts

rm -rf "${RUNNER_TEMP:?}/.config"
if [ -f fixtures/config/${config} ]; then
mkdir -p $RUNNER_TEMP/.config
cp fixtures/config/${config} $RUNNER_TEMP/.config/${config}
fi

pushd $RUNNER_TEMP > /dev/null || exit

# `./dist/main.js` is executing local `css-typed` as if installed (same as `bin`).
# But it is `$GITHUB_WORKSPACE/dist/main.js` b/c we `cd $RUNNER_TEMP`.
echo "css-typed ${beforeOpts[*]} \"*.css\" ${afterOpts[*]}"
echo "css-typed " "${options[@]}"
# shellcheck disable=SC2068
$GITHUB_WORKSPACE/dist/main.js ${beforeOpts[@]} "*.css" ${afterOpts[@]}
$GITHUB_WORKSPACE/dist/main.js ${options[@]}

# Use `diff` to compare the files.
# Use `-I '//.*'` to ignore the first line (comment) which has generated path and timestamp.
diff --color=auto --strip-trailing-cr -uI "//.*" expected.d.css.ts ${prefix}test.d.css.ts
diff --color=always --strip-trailing-cr -uI "//.*" expected.d.css.ts ${prefix}test.d.css.ts
Loading