Skip to content

Commit

Permalink
✨ Support configuration file (#28)
Browse files Browse the repository at this point in the history
Adds support for specifying options via a configuration file instead of command line arguments. Uses `lilconfig` and `js-yaml` under the hood.

|          | Before |  After  | Diff |
| :------: | -----: | :-----: | :--: |
|  packed  | 4.4 kB |  5.5 kB | +25% |
| unpacked | 9.5 kB | 12.6 kB | +33% |

The increase in download size brings the package back to pre-bundling size.
Note: This includes increased README documentation size.

dist/main.js: 3.2kb

Improves pipeline tests with bash unofficial strict mode, which fixes potential for false positives. Removes `-latest` from OS variable in test matrix.

+semver:minor
  • Loading branch information
connorjs authored Aug 4, 2024
1 parent c0ae91c commit 4eae464
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 37 deletions.
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

0 comments on commit 4eae464

Please sign in to comment.