@bestcss/core 0.1.0
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/LICENSE +21 -0
- package/dist/class-name.d.ts +4 -0
- package/dist/class-name.d.ts.map +1 -0
- package/dist/class-name.js +26 -0
- package/dist/class-name.js.map +1 -0
- package/dist/class-rename.d.ts +18 -0
- package/dist/class-rename.d.ts.map +1 -0
- package/dist/class-rename.js +37 -0
- package/dist/class-rename.js.map +1 -0
- package/dist/css.d.ts +8 -0
- package/dist/css.d.ts.map +1 -0
- package/dist/css.js +11 -0
- package/dist/css.js.map +1 -0
- package/dist/dedupe.d.ts +12 -0
- package/dist/dedupe.d.ts.map +1 -0
- package/dist/dedupe.js +81 -0
- package/dist/dedupe.js.map +1 -0
- package/dist/imports.d.ts +9 -0
- package/dist/imports.d.ts.map +1 -0
- package/dist/imports.js +37 -0
- package/dist/imports.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/keyframes.d.ts +32 -0
- package/dist/keyframes.d.ts.map +1 -0
- package/dist/keyframes.js +67 -0
- package/dist/keyframes.js.map +1 -0
- package/dist/transform.d.ts +18 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +184 -0
- package/dist/transform.js.map +1 -0
- package/docs/01-syntax.md +80 -0
- package/docs/02-how-it-works.md +34 -0
- package/docs/index.md +33 -0
- package/package.json +54 -0
- package/reset.css +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yuta Ura
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"class-name.d.ts","sourceRoot":"","sources":["../src/class-name.ts"],"names":[],"mappings":"AAaA,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOhD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGzD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* css`` の中身(生 CSS テキスト)からスコープ用クラス名を生成する。
|
|
3
|
+
*
|
|
4
|
+
* 入力を CSS 内容のみとし、ファイル名を混ぜていない理由:
|
|
5
|
+
* 同一内容の css`` がファイルを跨いで存在するとき同一クラス名に収束させ、
|
|
6
|
+
* Phase 2 の重複排除(同一ルールの共有)の基盤にするため。
|
|
7
|
+
*/
|
|
8
|
+
// FNV-1a を使う理由: 依存ゼロ・数行で書け、ビルド毎に決定的。
|
|
9
|
+
// 暗号学的強度は不要(衝突時はビルド時に検出して対処できる)で、
|
|
10
|
+
// crypto.createHash より高速なため。
|
|
11
|
+
const FNV_OFFSET_BASIS = 0x811c9dc5;
|
|
12
|
+
const FNV_PRIME = 0x01000193;
|
|
13
|
+
/** 内容から決定的な短いハッシュ文字列を作る(クラス名・keyframes 名で共用) */
|
|
14
|
+
export function contentHash(text) {
|
|
15
|
+
let hash = FNV_OFFSET_BASIS;
|
|
16
|
+
for (let i = 0; i < text.length; i++) {
|
|
17
|
+
hash ^= text.charCodeAt(i);
|
|
18
|
+
hash = Math.imul(hash, FNV_PRIME);
|
|
19
|
+
}
|
|
20
|
+
return (hash >>> 0).toString(36);
|
|
21
|
+
}
|
|
22
|
+
export function generateClassName(cssText) {
|
|
23
|
+
// CSS クラス名は数字始まりが許されないため "bc" プレフィックスで保証する
|
|
24
|
+
return `bc${contentHash(cssText)}`;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=class-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"class-name.js","sourceRoot":"","sources":["../src/class-name.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qCAAqC;AACrC,kCAAkC;AAClC,6BAA6B;AAC7B,MAAM,gBAAgB,GAAG,UAAU,CAAC;AACpC,MAAM,SAAS,GAAG,UAAU,CAAC;AAE7B,gDAAgD;AAChD,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,IAAI,GAAG,gBAAgB,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,2CAA2C;IAC3C,OAAO,KAAK,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ビルド時のクラス名短縮。
|
|
3
|
+
*
|
|
4
|
+
* 内容ハッシュ(bc 接頭辞)は「全クラスが揃う前に衝突なく名前を決める」ための
|
|
5
|
+
* 長さを持つが、ビルド最終段階では全クラスの一覧が確定しているため、
|
|
6
|
+
* 全単射の短い名前(a, b, ..., z, aa, ...)に振り直せる。
|
|
7
|
+
* 名前の長さ × 出現回数が出力サイズを決めるので、頻度の高い順に短い名前を
|
|
8
|
+
* 割り当てる(Huffman 符号と同じ発想)。
|
|
9
|
+
*/
|
|
10
|
+
/** 使用頻度の高い順に短い名前を割り当てたリネーム表を作る */
|
|
11
|
+
export declare function createRenameMap(frequencies: Map<string, number>): Map<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* テキスト(JS チャンク / CSS アセット)内のクラス名をリネーム表に従って置換する。
|
|
14
|
+
* bc 接頭辞の生成名だけを対象にするため、ユーザーコードの他の文字列を
|
|
15
|
+
* 誤って書き換えることはない
|
|
16
|
+
*/
|
|
17
|
+
export declare function applyRename(text: string, renameMap: Map<string, string>): string;
|
|
18
|
+
//# sourceMappingURL=class-rename.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"class-rename.d.ts","sourceRoot":"","sources":["../src/class-rename.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAiBH,kCAAkC;AAClC,wBAAgB,eAAe,CAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAOrB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,MAAM,CAKR"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ビルド時のクラス名短縮。
|
|
3
|
+
*
|
|
4
|
+
* 内容ハッシュ(bc 接頭辞)は「全クラスが揃う前に衝突なく名前を決める」ための
|
|
5
|
+
* 長さを持つが、ビルド最終段階では全クラスの一覧が確定しているため、
|
|
6
|
+
* 全単射の短い名前(a, b, ..., z, aa, ...)に振り直せる。
|
|
7
|
+
* 名前の長さ × 出現回数が出力サイズを決めるので、頻度の高い順に短い名前を
|
|
8
|
+
* 割り当てる(Huffman 符号と同じ発想)。
|
|
9
|
+
*/
|
|
10
|
+
const FIRST_CHARS = "abcdefghijklmnopqrstuvwxyz";
|
|
11
|
+
const REST_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
12
|
+
/** index 番目の短縮名(bijective 進法。CSS クラス名は英字始まりが必要) */
|
|
13
|
+
function shortName(index) {
|
|
14
|
+
let name = FIRST_CHARS[index % FIRST_CHARS.length];
|
|
15
|
+
let rest = Math.floor(index / FIRST_CHARS.length);
|
|
16
|
+
while (rest > 0) {
|
|
17
|
+
rest -= 1;
|
|
18
|
+
name += REST_CHARS[rest % REST_CHARS.length];
|
|
19
|
+
rest = Math.floor(rest / REST_CHARS.length);
|
|
20
|
+
}
|
|
21
|
+
return name;
|
|
22
|
+
}
|
|
23
|
+
/** 使用頻度の高い順に短い名前を割り当てたリネーム表を作る */
|
|
24
|
+
export function createRenameMap(frequencies) {
|
|
25
|
+
// 同頻度は元の名前順で割り当て、入力順に依存しない決定的なビルドにする
|
|
26
|
+
const sorted = [...frequencies.entries()].sort(([nameA, countA], [nameB, countB]) => countB - countA || nameA.localeCompare(nameB));
|
|
27
|
+
return new Map(sorted.map(([name], index) => [name, shortName(index)]));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* テキスト(JS チャンク / CSS アセット)内のクラス名をリネーム表に従って置換する。
|
|
31
|
+
* bc 接頭辞の生成名だけを対象にするため、ユーザーコードの他の文字列を
|
|
32
|
+
* 誤って書き換えることはない
|
|
33
|
+
*/
|
|
34
|
+
export function applyRename(text, renameMap) {
|
|
35
|
+
return text.replace(/\bbc[a-z0-9]+\b/g, (matched) => renameMap.get(matched) ?? matched);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=class-rename.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"class-rename.js","sourceRoot":"","sources":["../src/class-rename.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,GAAG,4BAA4B,CAAC;AACjD,MAAM,UAAU,GAAG,sCAAsC,CAAC;AAE1D,mDAAmD;AACnD,SAAS,SAAS,CAAC,KAAa;IAC9B,IAAI,IAAI,GAAG,WAAW,CAAC,KAAK,GAAG,WAAW,CAAC,MAAM,CAAW,CAAC;IAC7D,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,IAAI,GAAG,CAAC,EAAE,CAAC;QAChB,IAAI,IAAI,CAAC,CAAC;QACV,IAAI,IAAI,UAAU,CAAC,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,eAAe,CAC7B,WAAgC;IAEhC,qCAAqC;IACrC,MAAM,MAAM,GAAG,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAC5C,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,CACnC,MAAM,GAAG,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAChD,CAAC;IACF,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,SAA8B;IAE9B,OAAO,IAAI,CAAC,OAAO,CACjB,kBAAkB,EAClB,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,CAC/C,CAAC;AACJ,CAAC"}
|
package/dist/css.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* css`` タグ。ビルド時に変換で消えるため、ランタイムでは決して実行されない。
|
|
3
|
+
*
|
|
4
|
+
* values を never[] にしている理由: `${}` 補間を型レベルでも拒否し、
|
|
5
|
+
* ビルドエラーより手前(エディタ上)で気付けるようにするため。
|
|
6
|
+
*/
|
|
7
|
+
export declare function css(_strings: TemplateStringsArray, ..._values: never[]): string;
|
|
8
|
+
//# sourceMappingURL=css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../src/css.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,CAK/E"}
|
package/dist/css.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* css`` タグ。ビルド時に変換で消えるため、ランタイムでは決して実行されない。
|
|
3
|
+
*
|
|
4
|
+
* values を never[] にしている理由: `${}` 補間を型レベルでも拒否し、
|
|
5
|
+
* ビルドエラーより手前(エディタ上)で気付けるようにするため。
|
|
6
|
+
*/
|
|
7
|
+
export function css(_strings, ..._values) {
|
|
8
|
+
throw new Error("bestcss: css`` が実行時に呼ばれました。" +
|
|
9
|
+
"ビルド時に変換されるはずなので、@bestcss/vite-plugin が設定されているか確認してください。");
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=css.js.map
|
package/dist/css.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css.js","sourceRoot":"","sources":["../src/css.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAAC,QAA8B,EAAE,GAAG,OAAgB;IACrE,MAAM,IAAI,KAAK,CACb,6BAA6B;QAC3B,yDAAyD,CAC5D,CAAC;AACJ,CAAC"}
|
package/dist/dedupe.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS のトップレベル文(ルール / @media ブロック / @import など)を単位に
|
|
3
|
+
* 完全一致の重複を取り除く。
|
|
4
|
+
*
|
|
5
|
+
* ルール単位の AST 比較ではなくトップレベル文のテキスト比較にしている理由:
|
|
6
|
+
* 重複は同一の変換パイプラインが生成した同一 css`` ブロック由来であり
|
|
7
|
+
* バイト単位で一致することが保証される。また @media を丸ごと 1 単位と
|
|
8
|
+
* みなすことで「異なるメディアクエリ内の同一ルール」を誤って統合する
|
|
9
|
+
* 事故が構造的に起きない。
|
|
10
|
+
*/
|
|
11
|
+
export declare function dedupeCss(css: string): string;
|
|
12
|
+
//# sourceMappingURL=dedupe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe.d.ts","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgB7C"}
|
package/dist/dedupe.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS のトップレベル文(ルール / @media ブロック / @import など)を単位に
|
|
3
|
+
* 完全一致の重複を取り除く。
|
|
4
|
+
*
|
|
5
|
+
* ルール単位の AST 比較ではなくトップレベル文のテキスト比較にしている理由:
|
|
6
|
+
* 重複は同一の変換パイプラインが生成した同一 css`` ブロック由来であり
|
|
7
|
+
* バイト単位で一致することが保証される。また @media を丸ごと 1 単位と
|
|
8
|
+
* みなすことで「異なるメディアクエリ内の同一ルール」を誤って統合する
|
|
9
|
+
* 事故が構造的に起きない。
|
|
10
|
+
*/
|
|
11
|
+
export function dedupeCss(css) {
|
|
12
|
+
const statements = splitTopLevelStatements(css);
|
|
13
|
+
const lastIndexByKey = new Map();
|
|
14
|
+
statements.forEach((statement, index) => {
|
|
15
|
+
lastIndexByKey.set(statement.trim(), index);
|
|
16
|
+
});
|
|
17
|
+
// 最後の出現を残す理由: 複数クラスを併用した要素では同一詳細度の
|
|
18
|
+
// ルール間で後方が勝つため、前方を残すと間に挟まったルールとの
|
|
19
|
+
// 勝敗が元の CSS と変わってしまう
|
|
20
|
+
const deduped = statements.filter((statement, index) => lastIndexByKey.get(statement.trim()) === index);
|
|
21
|
+
return deduped.join("\n");
|
|
22
|
+
}
|
|
23
|
+
/** 波括弧の深さ 0 で CSS をトップレベル文に分割する */
|
|
24
|
+
function splitTopLevelStatements(css) {
|
|
25
|
+
const statements = [];
|
|
26
|
+
let depth = 0;
|
|
27
|
+
let start = 0;
|
|
28
|
+
let inString = null;
|
|
29
|
+
let inComment = false;
|
|
30
|
+
for (let i = 0; i < css.length; i++) {
|
|
31
|
+
const char = css[i];
|
|
32
|
+
if (inComment) {
|
|
33
|
+
if (char === "*" && css[i + 1] === "/") {
|
|
34
|
+
inComment = false;
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (inString !== null) {
|
|
40
|
+
if (char === "\\") {
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
else if (char === inString) {
|
|
44
|
+
inString = null;
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === "/" && css[i + 1] === "*") {
|
|
49
|
+
inComment = true;
|
|
50
|
+
i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (char === '"' || char === "'") {
|
|
54
|
+
inString = char;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (char === "{") {
|
|
58
|
+
depth++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (char === "}") {
|
|
62
|
+
depth--;
|
|
63
|
+
if (depth === 0) {
|
|
64
|
+
statements.push(css.slice(start, i + 1));
|
|
65
|
+
start = i + 1;
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// @import / @charset のようなブロックを持たない文はセミコロンで終わる
|
|
70
|
+
if (char === ";" && depth === 0) {
|
|
71
|
+
statements.push(css.slice(start, i + 1));
|
|
72
|
+
start = i + 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const rest = css.slice(start).trim();
|
|
76
|
+
if (rest !== "") {
|
|
77
|
+
statements.push(rest);
|
|
78
|
+
}
|
|
79
|
+
return statements.filter((s) => s.trim() !== "");
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=dedupe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ソースコードから import / export-from / 動的 import の指定子を列挙する。
|
|
3
|
+
*
|
|
4
|
+
* ルート単位の CSS 収集(routeStyles)で import グラフを辿るための部品。
|
|
5
|
+
* 依存の解決(相対パス → 実ファイル)はバンドラー側の resolver に任せ、
|
|
6
|
+
* ここは指定子の抽出だけを担う
|
|
7
|
+
*/
|
|
8
|
+
export declare function collectImportSources(code: string, filename: string): string[];
|
|
9
|
+
//# sourceMappingURL=imports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../src/imports.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAkC7E"}
|
package/dist/imports.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { parseSync } from "oxc-parser";
|
|
2
|
+
/**
|
|
3
|
+
* ソースコードから import / export-from / 動的 import の指定子を列挙する。
|
|
4
|
+
*
|
|
5
|
+
* ルート単位の CSS 収集(routeStyles)で import グラフを辿るための部品。
|
|
6
|
+
* 依存の解決(相対パス → 実ファイル)はバンドラー側の resolver に任せ、
|
|
7
|
+
* ここは指定子の抽出だけを担う
|
|
8
|
+
*/
|
|
9
|
+
export function collectImportSources(code, filename) {
|
|
10
|
+
const parsed = parseSync(filename, code);
|
|
11
|
+
const sources = [];
|
|
12
|
+
const visit = (node) => {
|
|
13
|
+
if (Array.isArray(node)) {
|
|
14
|
+
for (const child of node) {
|
|
15
|
+
visit(child);
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (node === null || typeof node !== "object") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const candidate = node;
|
|
23
|
+
if ((candidate.type === "ImportDeclaration" ||
|
|
24
|
+
candidate.type === "ExportNamedDeclaration" ||
|
|
25
|
+
candidate.type === "ExportAllDeclaration" ||
|
|
26
|
+
candidate.type === "ImportExpression") &&
|
|
27
|
+
typeof candidate.source?.value === "string") {
|
|
28
|
+
sources.push(candidate.source.value);
|
|
29
|
+
}
|
|
30
|
+
for (const value of Object.values(node)) {
|
|
31
|
+
visit(value);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
visit(parsed.program.body);
|
|
35
|
+
return sources;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=imports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.js","sourceRoot":"","sources":["../src/imports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,QAAgB;IACjE,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACzC,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,KAAK,GAAG,CAAC,IAAa,EAAQ,EAAE;QACpC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;gBACzB,KAAK,CAAC,KAAK,CAAC,CAAC;YACf,CAAC;YACD,OAAO;QACT,CAAC;QACD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9C,OAAO;QACT,CAAC;QACD,MAAM,SAAS,GAAG,IAGjB,CAAC;QACF,IACE,CAAC,SAAS,CAAC,IAAI,KAAK,mBAAmB;YACrC,SAAS,CAAC,IAAI,KAAK,wBAAwB;YAC3C,SAAS,CAAC,IAAI,KAAK,sBAAsB;YACzC,SAAS,CAAC,IAAI,KAAK,kBAAkB,CAAC;YACxC,OAAO,SAAS,CAAC,MAAM,EAAE,KAAK,KAAK,QAAQ,EAC3C,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,KAAK,CAAC,KAAK,CAAC,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,CAAE,MAAM,CAAC,OAA0C,CAAC,IAAI,CAAC,CAAC;IAC/D,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { generateClassName } from "./class-name.js";
|
|
2
|
+
export { applyRename, createRenameMap } from "./class-rename.js";
|
|
3
|
+
export { dedupeCss } from "./dedupe.js";
|
|
4
|
+
export { collectImportSources } from "./imports.js";
|
|
5
|
+
export { css } from "./css.js";
|
|
6
|
+
export { transform, type TransformOptions, type TransformResult, } from "./transform.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EACL,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { generateClassName } from "./class-name.js";
|
|
2
|
+
export { applyRename, createRenameMap } from "./class-rename.js";
|
|
3
|
+
export { dedupeCss } from "./dedupe.js";
|
|
4
|
+
export { collectImportSources } from "./imports.js";
|
|
5
|
+
export { css } from "./css.js";
|
|
6
|
+
export { transform, } from "./transform.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EACL,SAAS,GAGV,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* css`` ブロック内の @keyframes のスコープ化。
|
|
3
|
+
*
|
|
4
|
+
* keyframes 名は CSS 上グローバルなので、クラス名と同様に内容ハッシュで
|
|
5
|
+
* 命名し直す("bk" プレフィックス)。内容ハッシュにすることで、同一の
|
|
6
|
+
* keyframes がファイルを跨いで同一名に収束し、重複排除にそのまま乗る。
|
|
7
|
+
* "bc"(クラス)と接頭辞を分けているのは、ビルド時クラス名短縮
|
|
8
|
+
* (ADR-0004)の置換対象と名前空間を衝突させないため。
|
|
9
|
+
*/
|
|
10
|
+
export interface ExtractedKeyframes {
|
|
11
|
+
/** ユーザーが書いた元の名前 */
|
|
12
|
+
name: string;
|
|
13
|
+
/** 内容ハッシュによるスコープ名 */
|
|
14
|
+
scopedName: string;
|
|
15
|
+
/** ブロック本体(波括弧の中身) */
|
|
16
|
+
body: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* css`` の生テキストから、ブロック直下(ネスト深さ 0)の @keyframes を
|
|
20
|
+
* 取り出し、残りの CSS と分離する
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractKeyframes(rawCss: string): {
|
|
23
|
+
css: string;
|
|
24
|
+
keyframes: ExtractedKeyframes[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* animation / animation-name 宣言の値に現れる keyframes 名をスコープ名へ
|
|
28
|
+
* 書き換える。宣言値に限定するのは、"block" のような CSS キーワードと
|
|
29
|
+
* 同名の keyframes が無関係な宣言(display: block 等)を壊さないため
|
|
30
|
+
*/
|
|
31
|
+
export declare function rewriteAnimationNames(css: string, renames: Map<string, string>): string;
|
|
32
|
+
//# sourceMappingURL=keyframes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyframes.d.ts","sourceRoot":"","sources":["../src/keyframes.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,WAAW,kBAAkB;IACjC,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,kBAAkB,EAAE,CAAC;CACjC,CAkDA;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,MAAM,CASR"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { contentHash } from "./class-name.js";
|
|
2
|
+
/**
|
|
3
|
+
* css`` の生テキストから、ブロック直下(ネスト深さ 0)の @keyframes を
|
|
4
|
+
* 取り出し、残りの CSS と分離する
|
|
5
|
+
*/
|
|
6
|
+
export function extractKeyframes(rawCss) {
|
|
7
|
+
const keyframes = [];
|
|
8
|
+
let css = "";
|
|
9
|
+
let segmentStart = 0;
|
|
10
|
+
let depth = 0;
|
|
11
|
+
let i = 0;
|
|
12
|
+
while (i < rawCss.length) {
|
|
13
|
+
const char = rawCss[i];
|
|
14
|
+
if (char === "{") {
|
|
15
|
+
depth++;
|
|
16
|
+
i++;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (char === "}") {
|
|
20
|
+
depth--;
|
|
21
|
+
i++;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (depth === 0 && rawCss.startsWith("@keyframes", i)) {
|
|
25
|
+
const head = /^@keyframes\s+([-\w]+)\s*\{/.exec(rawCss.slice(i));
|
|
26
|
+
if (head?.[1] !== undefined) {
|
|
27
|
+
css += rawCss.slice(segmentStart, i);
|
|
28
|
+
// 対応する閉じ括弧まで読み進めて本体を切り出す
|
|
29
|
+
let j = i + head[0].length;
|
|
30
|
+
let bodyDepth = 1;
|
|
31
|
+
while (j < rawCss.length && bodyDepth > 0) {
|
|
32
|
+
if (rawCss[j] === "{") {
|
|
33
|
+
bodyDepth++;
|
|
34
|
+
}
|
|
35
|
+
else if (rawCss[j] === "}") {
|
|
36
|
+
bodyDepth--;
|
|
37
|
+
}
|
|
38
|
+
j++;
|
|
39
|
+
}
|
|
40
|
+
const body = rawCss.slice(i + head[0].length, j - 1);
|
|
41
|
+
keyframes.push({
|
|
42
|
+
name: head[1],
|
|
43
|
+
scopedName: `bk${contentHash(body)}`,
|
|
44
|
+
body,
|
|
45
|
+
});
|
|
46
|
+
i = j;
|
|
47
|
+
segmentStart = j;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
css += rawCss.slice(segmentStart);
|
|
54
|
+
return { css, keyframes };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* animation / animation-name 宣言の値に現れる keyframes 名をスコープ名へ
|
|
58
|
+
* 書き換える。宣言値に限定するのは、"block" のような CSS キーワードと
|
|
59
|
+
* 同名の keyframes が無関係な宣言(display: block 等)を壊さないため
|
|
60
|
+
*/
|
|
61
|
+
export function rewriteAnimationNames(css, renames) {
|
|
62
|
+
if (renames.size === 0) {
|
|
63
|
+
return css;
|
|
64
|
+
}
|
|
65
|
+
return css.replace(/(animation(?:-name)?\s*:)([^;}]*)/g, (_match, property, value) => property + value.replace(/[-\w]+/g, (word) => renames.get(word) ?? word));
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=keyframes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyframes.js","sourceRoot":"","sources":["../src/keyframes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type SourceMap } from "magic-string";
|
|
2
|
+
export interface TransformOptions {
|
|
3
|
+
filename: string;
|
|
4
|
+
}
|
|
5
|
+
export interface TransformResult {
|
|
6
|
+
/** css`` をクラス名リテラルに置換し、import を除去したコード */
|
|
7
|
+
code: string;
|
|
8
|
+
/** 抽出・スコープ化された CSS */
|
|
9
|
+
css: string;
|
|
10
|
+
/** このファイルから生成したクラス名(ビルド時の短縮リネームの対象特定に使う) */
|
|
11
|
+
classNames: string[];
|
|
12
|
+
/** 変換後コードから元ソースへのソースマップ */
|
|
13
|
+
map: SourceMap;
|
|
14
|
+
/** 出力 CSS から元ソース(css`` の位置)へのソースマップ(JSON 文字列) */
|
|
15
|
+
cssMap: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function transform(code: string, options: TransformOptions): TransformResult | null;
|
|
18
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAEA,OAAoB,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAQ3D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,2BAA2B;IAC3B,GAAG,EAAE,SAAS,CAAC;IACf,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;CAChB;AAgFD,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,gBAAgB,GACxB,eAAe,GAAG,IAAI,CAyJxB"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { encode } from "@jridgewell/sourcemap-codec";
|
|
2
|
+
import { transform as transformCss } from "lightningcss";
|
|
3
|
+
import MagicString, {} from "magic-string";
|
|
4
|
+
import { parseSync } from "oxc-parser";
|
|
5
|
+
import { generateClassName } from "./class-name.js";
|
|
6
|
+
import { extractKeyframes, rewriteAnimationNames } from "./keyframes.js";
|
|
7
|
+
/** ユーザーが css をここから import したときだけ変換対象とする */
|
|
8
|
+
const CSS_TAG_MODULE = "@bestcss/core";
|
|
9
|
+
/** code 中のオフセットを 0-based の行番号に変換する */
|
|
10
|
+
function lineNumberAt(code, offset) {
|
|
11
|
+
let line = 0;
|
|
12
|
+
for (let i = 0; i < offset && i < code.length; i++) {
|
|
13
|
+
if (code[i] === "\n") {
|
|
14
|
+
line++;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return line;
|
|
18
|
+
}
|
|
19
|
+
function walk(node, visit) {
|
|
20
|
+
if (Array.isArray(node)) {
|
|
21
|
+
for (const child of node) {
|
|
22
|
+
walk(child, visit);
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (node !== null && typeof node === "object") {
|
|
27
|
+
const candidate = node;
|
|
28
|
+
if (typeof candidate.type === "string") {
|
|
29
|
+
visit(node);
|
|
30
|
+
}
|
|
31
|
+
for (const value of Object.values(node)) {
|
|
32
|
+
walk(value, visit);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** import { css } from "@bestcss/core" のローカル名と import 文を探す */
|
|
37
|
+
function findCssImport(body) {
|
|
38
|
+
for (const stmt of body) {
|
|
39
|
+
if (stmt.type !== "ImportDeclaration") {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const source = stmt["source"];
|
|
43
|
+
if (source.value !== CSS_TAG_MODULE) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const specifiers = stmt["specifiers"];
|
|
47
|
+
for (const spec of specifiers) {
|
|
48
|
+
if (spec.type === "ImportSpecifier" &&
|
|
49
|
+
spec.imported?.name === "css" &&
|
|
50
|
+
spec.local?.name !== undefined) {
|
|
51
|
+
return { localName: spec.local.name, declaration: stmt };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
export function transform(code, options) {
|
|
58
|
+
// パース前の文字列検索で早期リターンする理由:
|
|
59
|
+
// 変換対象は通常ごく一部のファイルであり、全ファイルの AST 構築を
|
|
60
|
+
// 避けることがビルド全体の速度に効くため。
|
|
61
|
+
if (!code.includes(CSS_TAG_MODULE)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const parsed = parseSync(options.filename, code);
|
|
65
|
+
if (parsed.errors.length > 0) {
|
|
66
|
+
const detail = parsed.errors.map((e) => e.message).join("\n");
|
|
67
|
+
throw new Error(`bestcss: ${options.filename} のパースに失敗しました:\n${detail}`);
|
|
68
|
+
}
|
|
69
|
+
const program = parsed.program;
|
|
70
|
+
const cssImport = findCssImport(program.body);
|
|
71
|
+
if (cssImport === null) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const tags = [];
|
|
75
|
+
walk(program.body, (node) => {
|
|
76
|
+
if (node.type !== "TaggedTemplateExpression") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const tagged = node;
|
|
80
|
+
if (tagged.tag.type === "Identifier" &&
|
|
81
|
+
tagged.tag.name === cssImport.localName) {
|
|
82
|
+
tags.push(tagged);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if (tags.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const ms = new MagicString(code);
|
|
89
|
+
const classNames = [];
|
|
90
|
+
// 1 パス目: 全ブロックから @keyframes を抽出する。
|
|
91
|
+
// 参照の解決をファイル単位にする(CSS Modules と同じメンタルモデル)ため、
|
|
92
|
+
// クラス化より先に全ブロック分のリネーム表を確定させる必要がある
|
|
93
|
+
const blocks = [];
|
|
94
|
+
const keyframesRenames = new Map();
|
|
95
|
+
const keyframesStatements = new Map();
|
|
96
|
+
for (const tag of tags) {
|
|
97
|
+
if (tag.quasi.expressions.length > 0) {
|
|
98
|
+
throw new Error(`bestcss: ${options.filename} — css\`\` 内の \${} 補間は未サポートです。` +
|
|
99
|
+
`動的な値は CSS カスタムプロパティ(var(--x) + style 属性)を使ってください。`);
|
|
100
|
+
}
|
|
101
|
+
const rawCss = tag.quasi.quasis[0]?.value.raw ?? "";
|
|
102
|
+
const { css: blockCss, keyframes } = extractKeyframes(rawCss);
|
|
103
|
+
for (const kf of keyframes) {
|
|
104
|
+
keyframesRenames.set(kf.name, kf.scopedName);
|
|
105
|
+
// scopedName は内容ハッシュなので、同一内容はここで自然に 1 つに収束する
|
|
106
|
+
if (!keyframesStatements.has(kf.scopedName)) {
|
|
107
|
+
keyframesStatements.set(kf.scopedName, {
|
|
108
|
+
statement: `@keyframes ${kf.scopedName} {${kf.body}}`,
|
|
109
|
+
originLine: lineNumberAt(code, tag.start),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
blocks.push({ tag, css: blockCss });
|
|
114
|
+
}
|
|
115
|
+
// 2 パス目: animation 参照を書き換えてからクラス化する。
|
|
116
|
+
// クラス名は書き換え後の CSS から生成し、意味的に同じブロックが
|
|
117
|
+
// ファイルを跨いで同一クラス名に収束するようにする。
|
|
118
|
+
// あわせて、合成 CSS の各行が元ソースのどの行由来かを記録し、
|
|
119
|
+
// 出力 CSS → tsx のソースマップの入力にする
|
|
120
|
+
const cssChunks = [];
|
|
121
|
+
const lineOrigins = [];
|
|
122
|
+
const pushChunk = (text, originLine, trackPerLine) => {
|
|
123
|
+
const lineCount = text.split("\n").length;
|
|
124
|
+
for (let i = 0; i < lineCount; i++) {
|
|
125
|
+
// trackPerLine 時はブロック内の行ずれを追う(ブロック先頭行 + i)。
|
|
126
|
+
// keyframes は抽出で行構造が変わるため定義ブロックの先頭行に丸める
|
|
127
|
+
lineOrigins.push(trackPerLine ? originLine + i : originLine);
|
|
128
|
+
}
|
|
129
|
+
cssChunks.push(text);
|
|
130
|
+
};
|
|
131
|
+
for (const kf of keyframesStatements.values()) {
|
|
132
|
+
pushChunk(kf.statement, kf.originLine, false);
|
|
133
|
+
}
|
|
134
|
+
for (const block of blocks) {
|
|
135
|
+
const rewritten = rewriteAnimationNames(block.css, keyframesRenames);
|
|
136
|
+
const className = generateClassName(rewritten);
|
|
137
|
+
classNames.push(className);
|
|
138
|
+
pushChunk(`.${className} {${rewritten}}`, lineNumberAt(code, block.tag.start), true);
|
|
139
|
+
ms.overwrite(block.tag.start, block.tag.end, JSON.stringify(className));
|
|
140
|
+
}
|
|
141
|
+
// 合成 CSS → 元 tsx の行マッピング。Lightning CSS に inputSourceMap として
|
|
142
|
+
// 渡すことで、出力 CSS のソースマップが元 tsx まで連鎖する
|
|
143
|
+
const inputSourceMap = JSON.stringify({
|
|
144
|
+
version: 3,
|
|
145
|
+
file: options.filename,
|
|
146
|
+
sources: [options.filename],
|
|
147
|
+
sourcesContent: [code],
|
|
148
|
+
names: [],
|
|
149
|
+
mappings: encode(lineOrigins.map((line) => [[0, 0, line, 0]])),
|
|
150
|
+
});
|
|
151
|
+
// 変換後は css の参照が残らないため import ごと除去する(ゼロランタイム)。
|
|
152
|
+
// 現状 core の公開 API は css のみなので、この import 文に他の specifier が
|
|
153
|
+
// 混在するケースは考慮しない
|
|
154
|
+
ms.remove(cssImport.declaration.start, cssImport.declaration.end);
|
|
155
|
+
let cssOutput;
|
|
156
|
+
let cssMap;
|
|
157
|
+
try {
|
|
158
|
+
const result = transformCss({
|
|
159
|
+
filename: options.filename,
|
|
160
|
+
code: Buffer.from(cssChunks.join("\n")),
|
|
161
|
+
minify: false,
|
|
162
|
+
sourceMap: true,
|
|
163
|
+
inputSourceMap,
|
|
164
|
+
});
|
|
165
|
+
cssOutput = result.code.toString();
|
|
166
|
+
// map が返らない場合は入力マップで代用する(行単位の近似としては有効)
|
|
167
|
+
cssMap = result.map?.toString() ?? inputSourceMap;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
171
|
+
throw new Error(`bestcss: ${options.filename} の CSS の解析に失敗しました: ${message}`);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
code: ms.toString(),
|
|
175
|
+
css: cssOutput,
|
|
176
|
+
cssMap,
|
|
177
|
+
classNames,
|
|
178
|
+
// hires: "boundary" は行内のトークン境界単位でマッピングを出す。
|
|
179
|
+
// 置換箇所(css`` → 文字列リテラル)以外の行内位置も正確に保ち、
|
|
180
|
+
// ブレークポイントやスタックトレースのずれを防ぐため
|
|
181
|
+
map: ms.generateMap({ source: options.filename, hires: "boundary" }),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.js","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# css`` の文法
|
|
2
|
+
|
|
3
|
+
## 書けるもの
|
|
4
|
+
|
|
5
|
+
生の CSS 宣言に加え、ネストと条件付き at-rules がそのまま書ける:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const card = css`
|
|
9
|
+
padding: 16px;
|
|
10
|
+
|
|
11
|
+
&:hover {
|
|
12
|
+
box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (min-width: 600px) {
|
|
16
|
+
padding: 24px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@supports (display: grid) {
|
|
20
|
+
display: grid;
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## @keyframes はスコープ付きで書ける
|
|
26
|
+
|
|
27
|
+
ブロック直下に書いた `@keyframes` は内容ハッシュで命名し直され、名前が衝突しない。参照(`animation` / `animation-name`)は**同一ファイル内**のブロック間で解決される:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const title = css`
|
|
31
|
+
animation: pulse 2s infinite;
|
|
32
|
+
|
|
33
|
+
@keyframes pulse {
|
|
34
|
+
50% { opacity: 0.5; }
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 書けないもの
|
|
40
|
+
|
|
41
|
+
### `${}` 補間 — ビルドエラー
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// ❌ 型エラー + ビルドエラーになる
|
|
45
|
+
const bad = css`color: ${color};`;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
ランタイム動的スタイルは設計上サポートしない。動的な値は CSS カスタムプロパティで表現する:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
const box = css`background: var(--box-color);`;
|
|
52
|
+
|
|
53
|
+
<div className={box} style={{ "--box-color": color } as React.CSSProperties} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### グローバルな定義 — 通常の CSS ファイルへ
|
|
57
|
+
|
|
58
|
+
`:root` のデザイントークンや要素デフォルトはクラスにスコープできないため、通常の `.css` ファイルに書いて import する:
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
/* global.css */
|
|
62
|
+
:root {
|
|
63
|
+
--brand: #2563eb;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const title = css`color: var(--brand);`;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## クラス合成の注意(CSS の一般則)
|
|
72
|
+
|
|
73
|
+
`` `${base} ${variant}` `` のような className 上の合成は可能だが、同一プロパティが衝突したときの勝敗は **className に並べた順ではなく、スタイルシート内でのルールの順** で決まる。ベースとバリアントで同じプロパティを両方に書かないこと。
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const base = css`padding: 8px 16px; border-radius: 6px;`;
|
|
77
|
+
const primary = css`background: #2563eb; color: #fff;`;
|
|
78
|
+
|
|
79
|
+
<button className={`${base} ${primary}`} />
|
|
80
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 内部のしくみ
|
|
2
|
+
|
|
3
|
+
デバッグや挙動の推論に必要な内部構造の要点。
|
|
4
|
+
|
|
5
|
+
## 変換の流れ(ビルド時)
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
.tsx ソース
|
|
9
|
+
→ css タグの検出(oxc-parser で AST 解析。@bestcss/core からの import のみ対象)
|
|
10
|
+
→ 生 CSS のパース・正規化(Lightning CSS)・クラス名生成
|
|
11
|
+
→ JS 側: css`` がクラス名文字列リテラルに置換され、import は除去される
|
|
12
|
+
→ CSS 側: 「元ファイル名 + .bestcss.css」の仮想モジュールとしてバンドラーの CSS パイプラインへ
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
ランタイムの `css` 関数はビルドで消える前提のスタブで、実行されると「プラグイン未設定」を示すエラーを投げる。
|
|
16
|
+
|
|
17
|
+
## クラス名
|
|
18
|
+
|
|
19
|
+
- **内容ハッシュ**: クラス名は CSS 内容の FNV-1a ハッシュ(`bc` + base36)。同一内容はファイル・ビルド・バンドラーを跨いで**同一クラス名に収束**する。これが重複排除と SSR での HTML/CSS 一致の土台
|
|
20
|
+
- **頻度順短縮**(本番ビルドのみ): 全クラス確定後、使用頻度順に `a`, `b`, ... へ全単射リネームされる。HTML の class 属性が大幅に縮む(ベンチで -48%)
|
|
21
|
+
- `@keyframes` の名前も内容ハッシュ(`bk` + base36)でスコープされる
|
|
22
|
+
|
|
23
|
+
## CSS の重複排除
|
|
24
|
+
|
|
25
|
+
同一内容の css`` が複数ファイルにあるとクラス名は収束するが、CSS 本文は各仮想モジュールから重複して出力される。最終アセット段階でトップレベル文単位の完全一致を検出して 1 つに畳む(カスケードを保つため最後の出現を残す)。
|
|
26
|
+
|
|
27
|
+
## 意図的にやらないこと
|
|
28
|
+
|
|
29
|
+
- ランタイム動的スタイル(`${}` 補間) — CSS カスタムプロパティで代替
|
|
30
|
+
- コンポーネント生成 API(styled.div`` 風) — 責務は「CSS → クラス名」に限定
|
|
31
|
+
- 独自プリプロセッサ構文 — 生 CSS(+ 標準のネスト仕様)に準拠し続ける
|
|
32
|
+
- デザイントークン機構 — CSS カスタムプロパティに委ねる
|
|
33
|
+
|
|
34
|
+
これらは設計上の決定であり、「対応してほしい」という要望に対しては上記の代替手段を案内すること。
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# bestcss ドキュメント(@bestcss/core)
|
|
2
|
+
|
|
3
|
+
このディレクトリは `@bestcss/core` パッケージに同梱されており、**インストールされているバージョンと常に一致する**。AI コーディングエージェントは、学習データではなくここにあるドキュメントを参照すること。
|
|
4
|
+
|
|
5
|
+
bestcss は「ゼロランタイム × コロケーション × 生 CSS 文法 × サイズ最適化」の CSS ライブラリである。JSX 内に書いた `` css`...` `` をビルド時に抽出し、クラス名リテラルへ変換する。**ランタイムに CSS 生成コードは一切含まれない**。
|
|
6
|
+
|
|
7
|
+
## 目次(core: バンドラー非依存の内容)
|
|
8
|
+
|
|
9
|
+
1. [css`` の文法 — 書けるもの・書けないもの](./01-syntax.md)
|
|
10
|
+
2. [内部のしくみ(クラス名・最適化・デバッグ)](./02-how-it-works.md)
|
|
11
|
+
|
|
12
|
+
## バンドラー統合のドキュメント
|
|
13
|
+
|
|
14
|
+
セットアップとビルド設定は各統合パッケージに同梱されている:
|
|
15
|
+
|
|
16
|
+
- **Vite**: `node_modules/@bestcss/vite-plugin/docs/`(セットアップ・オプション・SSR / MPA 統合)
|
|
17
|
+
- **webpack / Next.js(Turbopack)**: `node_modules/@bestcss/webpack-loader/docs/`
|
|
18
|
+
|
|
19
|
+
## Reset CSS(opt-in)
|
|
20
|
+
|
|
21
|
+
必要な場合のみ、エントリファイルで import する(中身は modern-normalize への委譲):
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import "@bestcss/core/reset.css";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
自動注入ではないのは、reset がコンポーネントスタイルより前に読み込まれる必要があり、import 順 = カスケード順をユーザーが制御できるべきだからである。
|
|
28
|
+
|
|
29
|
+
## エージェント向けの要点
|
|
30
|
+
|
|
31
|
+
- `css` は `@bestcss/core` から import する。**`${}` 補間は使えない**(型レベル + ビルドエラーで拒否される)。動的な値は CSS カスタムプロパティ + style 属性で表現する
|
|
32
|
+
- ゼロランタイムを崩す提案(実行時のスタイル生成など)はこのライブラリの設計上あり得ない
|
|
33
|
+
- グローバルな定義(`:root` のトークン、要素デフォルト)は css`` ではなく通常の `.css` ファイルに書く
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bestcss/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ゼロランタイム CSS-in-JS のバンドラー非依存な変換コア。css`` タグをビルド時にクラス名と CSS へ変換する",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"css",
|
|
7
|
+
"css-in-js",
|
|
8
|
+
"zero-runtime",
|
|
9
|
+
"bestcss"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Yuta Ura",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/YutaUra/bestcss.git",
|
|
16
|
+
"directory": "packages/core"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"sideEffects": [
|
|
20
|
+
"*.css"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./reset.css": "./reset.css"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"docs",
|
|
38
|
+
"reset.css"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@jridgewell/sourcemap-codec": "^1.5.5",
|
|
42
|
+
"lightningcss": "^1.32.0",
|
|
43
|
+
"magic-string": "^0.30.21",
|
|
44
|
+
"modern-normalize": "^3.0.1",
|
|
45
|
+
"oxc-parser": "^0.138.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^26.1.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.build.json",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/reset.css
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* bestcss opt-in reset。
|
|
3
|
+
* 使い方: エントリファイルで import "@bestcss/core/reset.css";
|
|
4
|
+
*
|
|
5
|
+
* 独自ルールを持たず modern-normalize に委譲する。リセットの中身は
|
|
6
|
+
* ブラウザ挙動への継続的な追従が必要で、bestcss の責務
|
|
7
|
+
* (CSS → クラス名の変換)の外にあるため、実績あるものに任せる。
|
|
8
|
+
* 別のリセットを使いたい場合は、これを import せず好きなものを
|
|
9
|
+
* 直接 import すればよい。
|
|
10
|
+
*/
|
|
11
|
+
@import "modern-normalize";
|