◆ 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 側に出力しようがないのでエラーになります