Windows で css modules の composes が動かない
◆ Windows のパス解決がおかしいのでそれ用の設定をして対処
composes
css modules の機能を見ていると composes という機能を見つけましたextends みたいなものですが sass? とかそのあたりの altCSS の extends だとたしか extends と書いたところに継承先のプロパティを全部出力するというような方法でした
composes だとそこには何も出力されず JavaScript に渡されるクラス名の対応のところに 両方のクラス名が半角スペース区切りで渡されます
.x {
color: red;
}
.y {
composes: x;
font-weight: bold;
}
と書いた場合に
.x_123 {
color: red;
}
.y_234 {
font-weight: kbold;
}
のように .y のほうには .x で定義した内容は含まれません
JavaScript で import した場合に
import styles from "./example.css"
console.log(styles)
// { x: "x_123", y: "x_123 y_234" }
のように y に 2 つのクラス名が存在します
`<div class="${styles.y}"></div>`
と クラス名を設定すれば両方が含まれるので両方とものスタイルがあたるというものです
composes は別のファイルも指定できます
.z {
composes: x from "./example.css";
}
Windows でやると
ということでやってみたのですが:import("./module1.css") {
i__imported_flex_0: flex;
}
のようなものが出てきてちゃんと継承できていません
使ったのは parcel の postcss-modules パッケージです
[package.json]
{
"devDependencies": {
"postcss-modules": "^1.3.2",
"cssnano": "^4.1.0"
}
}
[.postcssrc]
{
"modules": true
}
[index.js]
import styles from "./module2.css"
console.log(styles)
[module1.css]
.flex {
display: flex;
}
.grid {
display: grid;
}
[module2.css]
.class1 {
color: red;
composes: flex from "./module1.css";
}
.class2 {
background: blue;
}
出力
[dist/index.css]
:import("./module1.css") {
i__imported_flex_0: flex;
}
._class1_1tiw7_1 {
color: red;
}
._class2_1tiw7_6 {
background: blue;
}
styles の中身
{
"class1": "_class1_1tiw7_1 i__imported_flex_0",
"class2": "_class2_1tiw7_6"
}
一応
import styles1 from "./module1.css"
import styles2 from "./module2.css"
にして両方 import させてみましたが dist/index.css の上に module1.css の中身が来るだけです
一応出力できていてそれっぽい見た目なので 仕様変更があって :import が未対応とかかな?と思ったのですが コンソールの方をみるとエラーがありました
{ Error: ENOENT: no such file or directory, open 'C:\C:\tmp\share\test\parcel\module1.css'
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'C:\\C:\\tmp\\share\\test\\\\parcel\\module1.css' }
開こうとしているパスがおかしくて C:\\ が 2 回あります
Linux でやってみるとエラーもなく composes できていました
使ってる parcel やパッケージバージョンは同じです
原因
原因を調べてみたら postcss-modules パッケージの依存パッケージの css-modules-loader-core パッケージの file-system-loader.js ファイルのfileRelativePath = path.resolve( path.join( this.root, relativeDir ), newPath )
の処理で C:\\C:\\ になっていました
https://github.com/css-modules/css-modules-loader-core/blob/master/src/file-system-loader.js#L38
this.root には 「/」 が入っています
Windows の場合はルートディレクトリが 「/」 ではないので
> path.join("/", "C:\\file.txt")
'\\C:\\file.txt'
> path.resolve(path.join("/", "C:\\file.txt"))
'C:\\C:\\file.txt'
という動きになります
この処理が Windows を考えられてないので Windows でも動くようにすればいいです
対処方法
join を resolve にすると> path.resolve("/", "C:\\file.txt")
'C:\\file.txt'
と動きそうですが join と resolve では微妙に意味が違うので 2 つの目の引数によっては逆に問題が出るかもしれません
それに yarn install (npm install) だけで使えるようにしたいので できる限りライブラリを修正するのは避けたいです
今回の場合は this.root のほうが外部から渡されるものなのでこっちを修正することを考えます
FileSystemLoader を呼び出してるところは postcss-modules パッケージの getLoader 関数でした
function getLoader(opts, plugins) {
const root = typeof opts.root === "undefined" ? "/" : opts.root;
return typeof opts.Loader === "function"
? new opts.Loader(root, plugins)
: new FileSystemLoader(root, plugins);
}
opts.root が未設定だから 「/」 になってるようです
opts がどこから来るのか探してみると ユーザの .postcssrc の設定ファイルでした
なので
{
"modules": true,
"plugins": {
"postcss-modules": {root: ""}
}
}
と書けば 空文字との join になって正常な Windows のパスでファイルをオープンできます
これでやってみるとちゃんと composes が動きました
composes でできないこと
便利な composes なのですが extends のようなその場に別の定義を書き出す処理ではないので<div class="${styles.elem}">
<button></button>
</div>
.elem button {
composes: button from "./button.css"
}
こういうことはできません
composes を使っているところがクラスや id でなく button というタグ名です
これだと JavaScript 側に出力しようがないのでエラーになります