honey-css-modules/インターフェイス
はじめに
まず作るものの全体像を把握する。今回作る必要があるのは全部で 3 つ。
*.module.cssから*.module.css.d.tsを生成する CLI ツール (cli)
*.module.css<=>*.ts横断の rename や Find All References ができるよう tsserver を拡張する TypeScript Language Server Plugin (typescript-plugin)
その TypeScript Language Server Plugin を組み込んだ VS Code Extension (vscode-extension)
これが必要なのは、typescript-plugin だけでは tsserver が*.module.cssを解析してくれないから
tsserver は通常 JavaScript ファイルや TypeScript ファイルのみを解析する
Vue.js の VS Code Extension ではactivationEventsとtypescriptServerPluginsを使うことで、tsserver が*.vueを解析するようにしている
honey-css-modules でも、これと同じことをやらないといけないはず
cli
基本は happy-css-modules を踏襲したら良かろう
こういう感じかな
code:interface.d.ts
// 設定の型
interface HCMConfig {
pattern: string;
dtsOutDir: string;
resolver?: Resolver | undefined;
alias?: Record<string, string> | undefined;
arbitraryExtensions?: boolean | undefined;
logLevel?: 'debug' | 'info' | 'silent' | undefined;
cwd?: string | undefined;
}
function defineConfig(config: HCMConfig): HCMConfig;
// 型定義ファイルを生成する処理を実行する
function runDtsGenerator(config: HCMConfig): Promise<void>;
// Resolver
interface ResolverOptions {
request: string;
};
type Resolver = (specifier: string, options: ResolverOptions) => string | false | Promise<string | false>;
interface DefaultResolverOptions {
alias?: Record<string, string> | undefined;
}
function createDefaultResolver(options: DefaultResolverOptions): Resolver;
// CSS Module ファイルをパースする部分のインターフェイス
interface CSSModuleFile {
/** ファイルの絶対パス */
filename: string;
/**
* ファイル内で定義している token の名前のリスト。
* .foo {} .bar, .baz {} というファイルなら ['foo', 'bar', 'baz'] になる。
*/
localTokens: string[];
/**
* ファイル内で @import している他の CSS Module ファイルの speficier。
* @import './a.module.css'; @import '@/b.module.css' というファイルなら、
* ['./a.module.css', '@/b.module.css'] になる。
*/
imports: string[];
}
function parseCSSModuleCode(code: string, filename: string): CSSModuleFile;
// 型定義ファイルを生成する部分のインターフェイス
interface CreateDtsCodeOptions {
resolver: Resolver,
isExternalFile: (filename: string) => boolean,
}
function createDtsCode(
cssModuleFile: CSSModuleFile,
options: CreateDtsCodeOptions,
): string;
// 型定義ファイルをファイルシステムに書き込む部分のインターフェイス
interface WriteDtsFileOption {
outDir: string;
cwd: string;
arbitraryExtensions: boolean;
}
function writeDtsFile(
dtsCode: string,
options: WriteDtsFileOption,
): Promise<void>;
happy-css-modules ではコマンドライン引数で設定を渡していたが、honey-css-modules では設定ファイルを導入して、そのファイルを経由して設定を渡したい
honey-css-modules は CLI ツールと typescript-plugin の両方があるので、2つから同じ設定を読めるようにしたという狙い
hcm.config.{js,cjs,mjs,ts.cts,mts} みたいなファイルを作って、そこに書く
code:hcm.config.js
import { defineConfig } from 'honey-css-modules';
export default defineConfig({
pattern: 'src/**.module.css',
dtsOutDir: 'generated/hcm',
alias: { '@': path.resolve(import.meta.dirname, 'src') },
});
Vite っぽくイマドキな感じにしておく
typescript-plugin
プロジェクトのルートにhcm.config.{js,...}があって、honey-css-modulesがインストールされている時だけ実行するようにしておく
それ以外のケースでは何もしない
vscode-extension
activationEventsを使って、tsserver が *.cssに反応するように
typescriptServerPluginsを使って tsserver に typescript-plugin を attach するように
TODO
編集中の壊れかけのファイルも扱えるようにしたい
token が全くないファイル
CSS 的に invalid な syntax のファイル
エディタでは壊れかけのファイルも扱えるようにして、CLI では壊れかけのファイルはエラーにしたい
パーサーにsafeオプションを追加する
表記揺れを修正する
filename/path/filePath/file
code/text
css module/css module file
オプションのデフォルト値をどこで埋めるか
上流か、下流か
下流で埋める場合、デフォルト値を定数化して共通管理するか、別個で管理するか
絶対パスを import specifier に使うのを禁止するべきか
tsc 自体禁止してないので、まあ良いのでは
node --run使ってみる
--help/--config/--version実装
CIでnode_modulesごとキャッシュ
patternやdtsOutDirもresolveConfigでcwdと結合する
dtsOutDir => outDirに rename
ディレクトリをクリーンアップしてからコード生成する
ts-plugin の E2Eテスト
先行技術