🔖
モノレポUI Lib開発におけるCSSモジュールの活用
背景
- UIライブラリの開発で複数のUIコンポーネントから利用される共通パーツを定義する
- UIコンポーネントや共通パーツはそれぞれ独自のスタイルを持つことがある。
- UIコンポーネントは独立してビルド、パブリッシュができる
目的
- 各UIコンポーネントのビルド時に、共通コンポーネントのCSSを特定の命名規則に基づいてUIコンポーネント用のスタイルとしてビルドし、共通コンポーネントに対するCSSが複数UIコンポーネント併用時に干渉しないようにする。
- Elementに対するスタイルの適用をjsで制御する際に、開発者がスタイリング部分の実装をするとき適用可能なスタイルについて型定義を参照できるようにする
レポジトリイメージ
プロジェクト
.
├── build_all.sh
├── inputControls
│ └── textField
│ ├── README.md
│ ├── dist
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── tsconfig.json
│ └── vite.config.ts
├── mediaElements
│ └── video
├── shared
│ ├── README.md
│ ├── components
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ ├── tsconfig.json
│ └── utils
UIコンポーネント
textField/vite.config.ts
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";
import * as path from 'path'
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'TextField',
fileName: (format) => `index.${format}.js`,
formats: ['es', 'umd'],
},
},
css: {
modules: {
localsConvention: 'camelCaseOnly',
generateScopedName: (name) => { //プラグインごとの命名規則
const prefix = 'ex-text-field';
return `${prefix}-${name}`;
}
}
},
plugins: [dts(), cssInjectedByJsPlugin()],// cssのバンドル
server: {
open: true,
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@shared': path.resolve(__dirname, '../../shared'), //共通コンポーネントのインクルード
}
}
});
UIコンポーネントのビルドフローイメージ
共通コンポーネントのCSS型定義生成
npm i typed-css-modules
css module file イメージ
shared/components/box/styles.module.css
/* Input */
.input {
padding: 0.5em;
border: 1px solid #ccc;
border-radius: 6px;
box-sizing: border-box;
background-color: inherit;
color: inherit;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
package.json スクリプトイメージ
shared/package.json
{
"scripts": {
"css-types": "tcm -p 'components/**/*.module.css' --camelCase"
},
}
Run
npm run css-types
Result
declare const styles: {
readonly "input": string;
};
export = styles;
まとめ
上記のような構造で共通コンポーネント開発時にはcss moduleに定義したクラスを`import styles from 'styles.module.css'の形式でインポートして型定義つきでts内部で利用しつつ、UIコンポーネントのビルド時には、そのコンポーネントの名前でビルドできる。
開発上は共通コンポーネントを定義しつつ、パブリッシュはUIコンポーネント単位で行う場合には上記で良いが、アセットリストとして複数のUIコンポーネントを含む一つのライブラリとするのであればUIコンポーネントごとに独自のCSSクラスとして定義し直すビルドは不要。
Discussion