sparkle-design-cli 2.4.2 → 2.5.0-beta.2
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/CONTRIBUTING.md +60 -0
- package/README.md +48 -504
- package/bin/sparkle-design.js +86 -31
- package/docs/anti-patterns.md +92 -0
- package/docs/config.md +133 -0
- package/docs/manual-setup.md +117 -0
- package/docs/plugins.md +120 -0
- package/docs/theming.md +122 -0
- package/lib/anti-pattern-rules.js +564 -9
- package/lib/check.js +245 -19
- package/lib/load-plugins.js +6 -0
- package/lib/path-utils.js +120 -0
- package/lib/plugin-api.js +17 -1
- package/lib/rules-report.js +216 -0
- package/lib/spacing-scale.js +228 -0
- package/lib/stop-hook.js +158 -7
- package/lib/token-migration.js +888 -0
- package/package.json +7 -5
- package/templates/sparkle-variables/.github/workflows/validate.yml +21 -0
- package/templates/sparkle-variables/README.md +151 -0
- package/templates/sparkle-variables/colors.json +87 -87
- package/templates/sparkle-variables/gray.json +70 -70
- package/templates/sparkle-variables/radius.csv +1 -1
- package/templates/sparkle-variables/scripts/validate-tokens.mjs +734 -0
- package/templates/sparkle-variables/sparkle-design.template.css +1107 -373
package/docs/plugins.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# アンチパターン検知の拡張(プラグイン)
|
|
2
|
+
|
|
3
|
+
他のコンポーネントライブラリ(社内拡張など)に固有のアンチパターン検知を、`sparkle-design-cli` 本体に変更を加えず追加できる拡張機構があります。
|
|
4
|
+
|
|
5
|
+
## 仕組み
|
|
6
|
+
|
|
7
|
+
プラグインを提供するパッケージは、自身の `package.json` に検知ルールのエントリを宣言します:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"name": "@your-org/your-design-extensions",
|
|
12
|
+
"sparkleCli": {
|
|
13
|
+
"antiPatterns": "./anti-patterns/index.js"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
エントリファイルは `defineAntiPatternPlugin` を使ってプラグインを default export します:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { defineAntiPatternPlugin } from 'sparkle-design-cli/plugin';
|
|
22
|
+
|
|
23
|
+
export default defineAntiPatternPlugin({
|
|
24
|
+
groups: [
|
|
25
|
+
{
|
|
26
|
+
id: 'your-org-foocard-nesting',
|
|
27
|
+
check: {
|
|
28
|
+
// 省略時は 'error'(--strict で exit 1)。'warning' / 'info' は報告のみ。
|
|
29
|
+
severity: 'error',
|
|
30
|
+
// 省略時は ['source'](.js/.jsx/.ts/.tsx のみ)。CSS も見たいなら 'css' を足す。
|
|
31
|
+
targets: ['source'],
|
|
32
|
+
description: 'FooCard を別の FooCard でラップしないでください。',
|
|
33
|
+
recommendation: '入れ子表示が必要なら FooStack を使ってください。',
|
|
34
|
+
pattern: /<FooCard[^>]*>[\s\S]*?<FooCard/g,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
manualReviewReminders: [
|
|
39
|
+
{
|
|
40
|
+
id: 'your-org-foocard-color',
|
|
41
|
+
message: 'FooCard の color token が brand に揃っているか確認してください。',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`severity` を省略すると `error` になります(severity 導入前と同じ挙動)。未知の値を渡した場合は警告を出して `error` にフォールバックします — タイポで意図せず CI をすり抜ける/落とすのを防ぐためです。
|
|
48
|
+
|
|
49
|
+
## 複雑な検出(`match` とヘルパー)
|
|
50
|
+
|
|
51
|
+
単純な正規表現では安全に書けない検出(accessible name の有無、prop の組み合わせ、JSX 式を意識した走査など)には、`check.pattern` の代わりに `check.match` を使います。`match` は `(content, helpers)` で呼ばれ、第 2 引数の `helpers` に JSX パースヘルパーが **CLI から注入** されます。各パッケージがパース処理を再実装して同じ false-positive / negative を踏むのを防ぐ仕組みです。
|
|
52
|
+
|
|
53
|
+
`match` が返す要素に `recommendation` を含めると、**マッチ 1 件ごとに違う推奨**を出せます(省略時はルール既定の `check.recommendation`)。移行ルールのように「置換先が箇所ごとに変わる」検出で使います。
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
// sparkle-design-cli を import しない(plain object を default export)
|
|
57
|
+
export default {
|
|
58
|
+
groups: [
|
|
59
|
+
{
|
|
60
|
+
id: 'your-org-avatar-accessible-name',
|
|
61
|
+
check: {
|
|
62
|
+
description: 'Avatar に accessible name がありません。',
|
|
63
|
+
recommendation: 'aria-label か alt を指定してください。',
|
|
64
|
+
// helpers = { matchOpeningTags, hasProp, isMultipleTypeProp, findOpeningTagEnd }
|
|
65
|
+
match: (content, { matchOpeningTags, hasProp }) =>
|
|
66
|
+
matchOpeningTags(
|
|
67
|
+
content,
|
|
68
|
+
'Avatar',
|
|
69
|
+
(props) =>
|
|
70
|
+
!hasProp(props, 'src') &&
|
|
71
|
+
!hasProp(props, 'aria-label') &&
|
|
72
|
+
!hasProp(props, 'aria-labelledby')
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
注入されるヘルパー(`check.match` の第 2 引数):
|
|
81
|
+
|
|
82
|
+
| ヘルパー | 用途 |
|
|
83
|
+
| ----------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
84
|
+
| `matchOpeningTags(content, tagName, predicate)` | `<Tag ...>` / `<Tag ... />` を走査し predicate が真のものを `{ index, text }` で返す |
|
|
85
|
+
| `hasProp(propsBlock, propName)` | prop が指定されているかを厳密判定(`aria-*` / `data-*` を誤検出しない) |
|
|
86
|
+
| `isMultipleTypeProp(propsBlock)` | `type` が静的に `"multiple"` か判定(動的式は `false`) |
|
|
87
|
+
| `findOpeningTagEnd(content, startIdx)` | 低レベル: 開きタグの閉じ `>` のオフセット(文字列 / JSX 式を考慮)。無ければ `-1` |
|
|
88
|
+
|
|
89
|
+
> 💡 `match` を使うプラグインは(上の `pattern` 例の `defineAntiPatternPlugin` import と違い)`sparkle-design-cli` を **import せず** plain object として default export してください。`helpers` はランタイムで CLI が注入するため import は不要で、生 JS のまま配布され npx 経由で実行される consumer 環境でも確実に解決されます。`pattern` と `match` は排他で、どちらか一方のみ指定します。
|
|
90
|
+
|
|
91
|
+
## 自動 discovery
|
|
92
|
+
|
|
93
|
+
`sparkle-design-cli check` 実行時、CLI は consumer プロジェクトの `package.json` の `dependencies` / `devDependencies` / `peerDependencies` / `optionalDependencies` を走査し、`sparkleCli.antiPatterns` を宣言しているパッケージを発見次第ロードしてビルトインのルールにマージします。
|
|
94
|
+
|
|
95
|
+
- プラグインパッケージが install されていなければ何も起きません
|
|
96
|
+
- ロードや評価で失敗したプラグインは warn を出してスキップし、残りのルールで check は継続されます
|
|
97
|
+
- ルール ID はビルトイン・他プラグインと名前空間を共有するため、`your-org-` のようなプレフィックスを付けて衝突を避けてください
|
|
98
|
+
|
|
99
|
+
> ⚠️ **信頼境界**: プラグインのエントリファイルは `import()` で **任意の JavaScript を実行** します。これは Node.js の通常のパッケージ依存と同じ性質ですが、`sparkle-design-cli check` 実行時に走るコードが増えるという点で意識しておく必要があります。プラグインは信頼できる org / 著者のパッケージのみインストールしてください。
|
|
100
|
+
|
|
101
|
+
## 契約仕様の確認
|
|
102
|
+
|
|
103
|
+
最新の契約仕様は CLI から直接出力できます:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# 契約 shape のドキュメントを出力
|
|
107
|
+
npx --yes sparkle-design-cli plugin-spec
|
|
108
|
+
|
|
109
|
+
# 現在のプロジェクトで discover されたプラグインを一覧
|
|
110
|
+
npx --yes sparkle-design-cli plugin-spec --list
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
完全な型定義は `node_modules/sparkle-design-cli/lib/plugin-api.js` の JSDoc を参照してください。
|
|
114
|
+
|
|
115
|
+
> [!TIP]
|
|
116
|
+
> 契約仕様の最新版は `npx sparkle-design-cli plugin-spec` が出力します。導入済みプラグインは `plugin-spec --list`、そのプラグインが実際に追加したルールは `rules` で確認できます。
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
[← README に戻る](../README.md)(docs/plugins.md)
|
package/docs/theming.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# 複数のテーマ配色を 1 デプロイでサポートしたい場合
|
|
2
|
+
|
|
3
|
+
管理画面と一般ユーザー画面で配色を出し分けたい、テナントごとに固定の配色バリアントを切り替えたいなど、「少数(数種類程度)の固定配色を 1 つのデプロイでサポートする」ケースの推奨パターンです。**「画面ごとに読み込む CSS を切り替える」場合と「1 つの画面内でランタイムに切り替える」場合とで推奨パターンが異なる**ので、まずどちらのケースかを判断してください。
|
|
4
|
+
|
|
5
|
+
## ケース A: 画面(ビルド)ごとにバリアントが固定される場合 → config を分割して複数の CSS を生成する
|
|
6
|
+
|
|
7
|
+
管理画面 / 一般ユーザー画面でレイアウトごと entry CSS を分けられる、role や tenant に応じて import する CSS を切り替えられる、など「1 画面につき常に 1 バリアントだけを読み込む」運用ができる場合の推奨パターンです。
|
|
8
|
+
|
|
9
|
+
`generate` は `-c/--config` と `-o/--output` を組み合わせることで、任意の config から任意の出力先へ CSS を生成できます。バリアントの数だけ config ファイルを用意し、バリアントの数だけ `generate` を実行してください(1 config に複数テーマをまとめて生成する機能はありませんが、CI やスクリプトで複数回呼び出せば十分に運用できます)。
|
|
10
|
+
|
|
11
|
+
`config/sparkle.admin.json`:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"primary": "blue",
|
|
16
|
+
"font-pro": "Inter",
|
|
17
|
+
"font-mono": "JetBrains Mono",
|
|
18
|
+
"radius": "md"
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`config/sparkle.employee.json`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"primary": "green",
|
|
27
|
+
"font-pro": "Inter",
|
|
28
|
+
"font-mono": "JetBrains Mono",
|
|
29
|
+
"radius": "md"
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx sparkle-design-cli generate -c ./config/sparkle.admin.json -o ./src/styles/sparkle-admin.css
|
|
35
|
+
npx sparkle-design-cli generate -c ./config/sparkle.employee.json -o ./src/styles/sparkle-employee.css
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
あとは consumer 側で、表示するバリアントに応じてどちらの CSS を読み込むかを切り替えるだけです(例: role / tenant を判定できるレイアウト単位で `import "./sparkle-admin.css"` と `import "./sparkle-employee.css"` を出し分ける、Next.js のルートグループごとに読み込む entry CSS を分ける、など)。
|
|
39
|
+
|
|
40
|
+
**重要: この 2 つの CSS を同時に import してはいけません。** これはファイルサイズ最適化の話ではなく **correctness 上のハード制約** です。`generate` が出力する CSS は `@theme inline { ... }` という Tailwind v4 の at-rule を含みますが、Tailwind の仕様上 `@theme`(`@theme inline` を含む)は **stylesheet の top-level にしか置けず、かつ 1 種類のトークン定義として扱われます**。2 つの `generate` 出力を同時 import すると、両方の `@theme inline` が同じ top-level スコープで衝突し、後に読み込んだ方の値で utility class(`bg-primary-500` 等)の意味がビルド時に固定されてしまいます(`data-*` 属性の付け外しのようなランタイム操作では変わりません)。**1 画面につき常に 1 バリアントだけを読み込む**構成でのみ、この方式は安全に使えます。
|
|
41
|
+
|
|
42
|
+
「1 つの画面内で属性の付け外しだけでテーマを切り替えたい(ページ遷移や再ビルドを伴わない)」場合は、このケース A ではなく次のケース B を使ってください。
|
|
43
|
+
|
|
44
|
+
## ケース B: 単一バンドル内でランタイムに切り替えたい場合 → `generate --scope`
|
|
45
|
+
|
|
46
|
+
SPA の 1 つの JS バンドル内で、`<html data-tenant-theme="admin">` のような属性の付け外しだけで配色を切り替えたい(ページ遷移・再デプロイを伴わない)場合のパターンです。ケース A の「config 分割 + 複数 `generate` の CSS を同時 import する」は、上記の理由(`@theme inline` の衝突)でこのケースには使えません。
|
|
47
|
+
|
|
48
|
+
Tailwind v4 では、この種のランタイム切り替え(dark mode 等)を公式に次のパターンでサポートしています([Tailwind公式ドキュメント](https://tailwindcss.com/docs/colors#using-css-variables)より):
|
|
49
|
+
|
|
50
|
+
```css
|
|
51
|
+
@import 'tailwindcss';
|
|
52
|
+
|
|
53
|
+
:root {
|
|
54
|
+
--acme-canvas-color: oklch(0.967 0.003 264.542);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[data-theme='dark'] {
|
|
58
|
+
--acme-canvas-color: oklch(0.21 0.034 264.665);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@theme inline {
|
|
62
|
+
--color-canvas: var(--acme-canvas-color);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
ポイントは、`@theme inline` は top-level に **1 つだけ** 置いたまま、実際に切り替えたい値は `@theme` を使わない普通の `:root` / `[data-attr]` スコープ付きセレクタで **参照先の CSS 変数** を上書きする、という構成です。`generate --scope` はこの「参照先を差し替える」役割を、`sparkle.config.json` の解決ロジックを再利用しながら自動生成します。
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# ベース(デフォルト表示)となるバリアントは今まで通り通常の generate
|
|
70
|
+
npx sparkle-design-cli generate -c ./config/sparkle.employee.json -o ./src/styles/sparkle-employee.css
|
|
71
|
+
|
|
72
|
+
# 追加バリアントは --scope で「セマンティックトークンだけをセレクタでラップした差分 CSS」を生成する
|
|
73
|
+
npx sparkle-design-cli generate -c ./config/sparkle.admin.json \
|
|
74
|
+
--scope '[data-tenant-theme="admin"]' \
|
|
75
|
+
-o ./src/styles/sparkle-admin-scope.css
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
/* entry CSS: 両方を同時に import してよい */
|
|
80
|
+
@import 'tailwindcss';
|
|
81
|
+
@import './sparkle-employee.css'; /* @theme inline を含む通常の generate 出力(ベース) */
|
|
82
|
+
@import './sparkle-admin-scope.css'; /* --scope の出力。@theme inline は含まない */
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<!-- ランタイムでこの属性を付け外しするだけで配色が切り替わる。ページ遷移・再ビルド不要 -->
|
|
87
|
+
<html data-tenant-theme="admin"></html>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`--scope` の出力は次の性質を持ちます:
|
|
91
|
+
|
|
92
|
+
- `@theme inline` には一切触れない(ベース側の `generate` 出力にある 1 つだけがそのまま有効であり続ける)ので、ケース A のような衝突は起きません。
|
|
93
|
+
- `--color-primary-*` 等の **セマンティックトークン** を、admin config の解決結果に基づいて実値(`oklch(...)`)までリテラル化した上で `--scope` のセレクタの中に閉じ込めます。プリミティブ(`--color-blue-*` 等)ではなくセマンティックトークンを上書き対象にしているのがポイントで、デフォルトでは `primary` と `info` が同じプリミティブ(例: `blue`)を共有しているため、もしプリミティブ側を上書きしてしまうと `primary` を変えたつもりが `info` まで巻き込んで変わってしまいます。`--scope` はこの巻き込みが起きないよう、セマンティック層で解決してから出力します。
|
|
94
|
+
- `--radius-*` / `--shadow-*` は普遍的な共有スケール値(テナント間で衝突しない)なので `var()` 参照のまま出力されます。`config.radius` がバリアントごとに異なれば、その解決結果もちゃんと反映されます。
|
|
95
|
+
- フォント import・`@source`・SparkleHead.tsx・globals.css パッチ等、ドキュメント全体に関わる**出力**は行いません。これらはベース側の通常 `generate` が既に担っているため、バリアントごとに重複させる必要はありません。ただし `font-pro` / `font-mono` 等の **validation 自体**は通常の `generate` と同様に適用されます(出力しないだけで、config の妥当性チェックは変わりません)。
|
|
96
|
+
- `-o/--output` の指定が必須です(既定の `sparkle-design.css` を誤って上書きしないため)。`--strict` / `--globals-path` とは併用できません(`--scope` はグローバル CSS のパッチを行わないため)。
|
|
97
|
+
- 現状 `primary` は 7 色パレットからの選択のみ対応しています。7 色にないカスタムブランドカラーを `--scope` の変数だけで表現したい場合は、`extend.custom-css` の要領で手動でセレクタ配下に `--color-primary-*` を定義してください(`--scope` の出力とマージして使えます)。
|
|
98
|
+
- **v2.4.1 以降**: ベース側 `generate` が出す `@theme inline` の各宣言は、プリミティブへの直接参照ではなく同名のセマンティック変数への自己参照(例: `--color-primary-500: var(--color-primary-500)`)になっています。Tailwind v4 は `@theme inline` の宣言右辺をそのまま compiled utility class にインライン展開する仕様のため、これが無いと `.bg-primary-500` 等の compiled utility は最初からプリミティブ変数だけを参照し、`--scope` がセマンティック変数だけを上書きしても utility class には一切反映されません(v2.4.0 はこの自己参照が無く、`--scope` の上書きが compiled utility に効かない状態でした)。v2.4.1 以降を使ってください。
|
|
99
|
+
|
|
100
|
+
このパターンは、次の「非推奨: 属性スコープでの Tailwind クラス上書き」が抱えていた脆さも同時に解消します。`--scope` が上書きするのは Sparkle が公開しているトークン契約(`--color-primary-*` 等の CSS 変数)だけであり、ユーティリティクラス名やコンポーネント内部の specificity には一切依存しません。そのため、Sparkle 側のコンポーネント実装(クラス名の付け方や compound attribute selector 等)が変わっても、`--scope` の出力が壊れることはありません。
|
|
101
|
+
|
|
102
|
+
## 非推奨: 単一 CSS + 属性スコープでの Tailwind クラス上書き
|
|
103
|
+
|
|
104
|
+
`sparkle-design.css` を 1 本のまま、`[data-theme="xxx"] .bg-primary-500 { ... }` のような属性セレクタで Tailwind ユーティリティクラスを個別に上書きする方式は **推奨しません**。
|
|
105
|
+
|
|
106
|
+
- コンポーネント側の実装詳細(クラス名の付け方や CSS の specificity)に依存するため、Sparkle 側の内部実装(例: 状態別カラーリングで compound attribute selector を使っているコンポーネント)が変わると、`!important` を使っても上書きが効かなくなることがあります。上書き対象は Sparkle が公開している契約(config / トークン)ではなく非公開の実装詳細なので、バージョンアップのたびに壊れるリスクを consumer 側が抱え続けることになります。
|
|
107
|
+
- 上書きが必要なクラス/コンポーネントが増えるたびに、手動でセレクタを足していく必要があり、考慮漏れに気づきにくいです。
|
|
108
|
+
|
|
109
|
+
上記のリスクを踏まえると、テーマ切り替えは「ケース A: config 分割」または「ケース B: `generate --scope`」+ `extend.custom-css` によるトークンレベルの上書き(cascade 順で確実に勝つ)で完結させる方が、Sparkle のバージョンアップに対しても壊れにくく、見通しの良い実装になります。
|
|
110
|
+
|
|
111
|
+
## トレードオフ
|
|
112
|
+
|
|
113
|
+
| 観点 | ケース A: config 分割(推奨・画面ごと固定) | ケース B: `generate --scope`(推奨・単一バンドルでランタイム切替) | 属性スコープ上書き(非推奨) |
|
|
114
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| 適用シーン | 1 画面につき常に 1 バリアントだけを読み込める | 1 バンドル内で属性の付け外しだけで切り替えたい | (非推奨のため参考情報) |
|
|
116
|
+
| ファイルサイズ | バリアントの数だけ CSS が増える(各 CSS は 7 色パレット全体を含む)。1 画面 1 バリアントのみ読み込む運用なら実質的な増分は小さい | ベース CSS 1 本 + バリアントごとの軽量な差分 CSS(セマンティックトークンのみ) | 単一 CSS + 上書き分の差分のみで増分は小さい |
|
|
117
|
+
| FOUC リスク | サーバー側 / ビルド時にバリアントを決定できるならほぼ無し | 属性付与のタイミング次第でリスクがある(初期描画後に属性が付くと一瞬デフォルト配色が見える) | クライアント側で属性を付与するタイミング次第でリスクがある |
|
|
118
|
+
| 保守性 | Sparkle が公開している config / トークンだけに依存するため、コンポーネント内部実装の変更に影響されにくい | 同左(トークンだけに依存し、ユーティリティクラス名や specificity に依存しない) | コンポーネントの内部実装(クラス名・specificity)に依存するため、Sparkle 側の変更で上書きが効かなくなるリスクがある |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
[← README に戻る](../README.md)(docs/theming.md)
|