@kbach/react 1.0.0-beta.7 → 1.0.0-beta.9
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/README.md +52 -39
- package/dist/{chunk-P4CQSYIL.mjs → chunk-7JBSTM5D.mjs} +56 -11
- package/dist/postcss-plugin.js +28 -7
- package/dist/postcss-plugin.mjs +3 -3
- package/dist/vite-plugin.d.mts +64 -24
- package/dist/vite-plugin.d.ts +64 -24
- package/dist/vite-plugin.js +56 -10
- package/dist/vite-plugin.mjs +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,11 +24,10 @@ export default defineConfig({
|
|
|
24
24
|
});
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
27
|
+
That's it — the plugin finds or creates your `kbach.css` on its own,
|
|
28
|
+
wherever your source actually lives (`src/`, `app/`, ...; see "Where does
|
|
29
|
+
`kbach.css` go?" below if you want to know exactly where or control it).
|
|
30
|
+
Import it once from your entry point and render normally:
|
|
32
31
|
|
|
33
32
|
```tsx
|
|
34
33
|
// main.tsx
|
|
@@ -45,12 +44,25 @@ createRoot(document.getElementById('root')!).render(<App />);
|
|
|
45
44
|
</div>
|
|
46
45
|
```
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
No `initKbach()`/async step before your first render: `className` here is
|
|
48
|
+
a plain string prop, resolved entirely by the CSS `@kbach/react/vite`
|
|
49
|
+
already generated at build time, same as any other static stylesheet —
|
|
50
|
+
nothing here calls into the WASM engine at render time, so there's nothing
|
|
51
|
+
to await.
|
|
52
|
+
|
|
53
|
+
### Where does `kbach.css` go?
|
|
54
|
+
|
|
55
|
+
You don't need to think about this for the common case — skip to
|
|
56
|
+
[React Router](#react-router)/[Next.js](#nextjs) if that's what brought you
|
|
57
|
+
here. For everyone else: the plugin looks for a file already containing
|
|
58
|
+
`/* kbach:start */`/`/* kbach:end */` markers under each of your `include`
|
|
59
|
+
directories (`src`, `app`, `pages`, `components` by default) and the
|
|
60
|
+
project root, in that order, and keeps using whichever one it finds. If
|
|
61
|
+
none exists yet, it creates `<first-existing-include-dir>/kbach.css` for
|
|
62
|
+
you, markers included — you never have to hand-author that file. Pass
|
|
63
|
+
`cssFile` to `kbach()` only if you want to pin the location explicitly
|
|
64
|
+
(a path outside every `include` dir, or more than one candidate present at
|
|
65
|
+
once).
|
|
54
66
|
|
|
55
67
|
`initKbach()` (still exported from `@kbach/react`) is only needed for two
|
|
56
68
|
opt-in features that genuinely do call into the Rust/WASM engine at
|
|
@@ -88,34 +100,35 @@ createRoot(document.getElementById('root')!).render(<RouterProvider router={rout
|
|
|
88
100
|
```
|
|
89
101
|
|
|
90
102
|
**Framework mode** (`npx create-react-router@latest` — file-based routing
|
|
91
|
-
under `app/`, its own Vite plugin, SSR by default):
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
under `app/`, its own Vite plugin, SSR by default):
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// vite.config.ts
|
|
107
|
+
import { reactRouter } from '@react-router/dev/vite';
|
|
108
|
+
import { kbach } from '@kbach/react/vite';
|
|
109
|
+
import { defineConfig } from 'vite';
|
|
110
|
+
|
|
111
|
+
export default defineConfig({
|
|
112
|
+
plugins: [kbach(), reactRouter()],
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Same `kbach()` call as everywhere else — its `app/`-vs-`src/` convention
|
|
117
|
+
is exactly what auto-detection (above) exists for, so there's nothing
|
|
118
|
+
React-Router-specific to configure. The one real gotcha is unrelated to
|
|
119
|
+
Kbach at all: **don't call `createRoot` yourself in `app/root.tsx`** — the
|
|
120
|
+
framework's own generated client/server entry points already do this;
|
|
121
|
+
`root.tsx` only ever *exports* a `Layout`/default component. Adding your
|
|
122
|
+
own `createRoot(...).render(...)` at that file's top level runs during SSR
|
|
123
|
+
too, where `document` doesn't exist, and crashes the build immediately.
|
|
124
|
+
`app/root.tsx` needs only:
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import './kbach.css';
|
|
128
|
+
export default function App() {
|
|
129
|
+
return <Outlet />;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
119
132
|
|
|
120
133
|
Either mode: every route's own components use `className` exactly like any
|
|
121
134
|
other component — routing has no effect on how Kbach resolves classes,
|
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
} from "./chunk-N5HCQIBB.mjs";
|
|
8
8
|
|
|
9
9
|
// src/vite-plugin/index.ts
|
|
10
|
-
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
10
|
+
import { readFileSync as readFileSync3, writeFileSync, mkdirSync, existsSync, statSync as statSync2 } from "fs";
|
|
11
11
|
import { join as join4, dirname } from "path";
|
|
12
|
+
import { pathToFileURL } from "url";
|
|
12
13
|
import { createRequire } from "module";
|
|
13
14
|
|
|
14
15
|
// src/vite-plugin/cssVars.ts
|
|
@@ -1235,22 +1236,63 @@ function readSourceFile(path) {
|
|
|
1235
1236
|
}
|
|
1236
1237
|
|
|
1237
1238
|
// src/vite-plugin/index.ts
|
|
1239
|
+
function findMainCSSFile(root, includeDirs, explicitCssFile) {
|
|
1240
|
+
if (explicitCssFile) return join4(root, explicitCssFile);
|
|
1241
|
+
const searchDirs = [...includeDirs, "."];
|
|
1242
|
+
for (const dir of searchDirs) {
|
|
1243
|
+
const candidate = join4(root, dir, "kbach.css");
|
|
1244
|
+
if (!existsSync(candidate)) continue;
|
|
1245
|
+
try {
|
|
1246
|
+
const content = readFileSync3(candidate, "utf-8");
|
|
1247
|
+
if (content.includes(KBACH_START) && content.includes(KBACH_END)) return candidate;
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
for (const dir of includeDirs) {
|
|
1252
|
+
const full = join4(root, dir);
|
|
1253
|
+
try {
|
|
1254
|
+
if (statSync2(full).isDirectory()) return join4(full, "kbach.css");
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return join4(root, includeDirs[0] ?? "src", "kbach.css");
|
|
1259
|
+
}
|
|
1238
1260
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
|
|
1239
1261
|
var CSS_SYNC_DEBOUNCE_MS = 50;
|
|
1240
1262
|
function writeFileEnsuringDir(path, content) {
|
|
1241
1263
|
mkdirSync(dirname(path), { recursive: true });
|
|
1242
1264
|
writeFileSync(path, content, "utf-8");
|
|
1243
1265
|
}
|
|
1244
|
-
function
|
|
1245
|
-
|
|
1246
|
-
|
|
1266
|
+
function isEsmRequireFailure(err) {
|
|
1267
|
+
if (!(err instanceof Error)) return false;
|
|
1268
|
+
return err.code === "ERR_REQUIRE_ESM" || /is not defined in ES module scope/.test(err.message);
|
|
1269
|
+
}
|
|
1270
|
+
async function importConfigModule(path) {
|
|
1271
|
+
const mod = await import(pathToFileURL(path).href);
|
|
1272
|
+
return mod.default ?? mod;
|
|
1273
|
+
}
|
|
1274
|
+
function requireConfigModule(root, path) {
|
|
1247
1275
|
const mod = createRequire(root)(path);
|
|
1248
1276
|
return mod.default ?? mod;
|
|
1249
1277
|
}
|
|
1250
|
-
function
|
|
1278
|
+
async function loadConfigFile(root) {
|
|
1279
|
+
const cjsPath = join4(root, "kbach.config.cjs");
|
|
1280
|
+
if (existsSync(cjsPath)) return requireConfigModule(root, cjsPath);
|
|
1281
|
+
const mjsPath = join4(root, "kbach.config.mjs");
|
|
1282
|
+
if (existsSync(mjsPath)) return importConfigModule(mjsPath);
|
|
1283
|
+
const jsPath = join4(root, "kbach.config.js");
|
|
1284
|
+
if (!existsSync(jsPath)) return void 0;
|
|
1285
|
+
try {
|
|
1286
|
+
return requireConfigModule(root, jsPath);
|
|
1287
|
+
} catch (err) {
|
|
1288
|
+
if (!isEsmRequireFailure(err)) throw err;
|
|
1289
|
+
return importConfigModule(jsPath);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
async function resolveEffectiveTheme(root, options) {
|
|
1251
1293
|
if (options.theme) return options.theme;
|
|
1252
1294
|
if (options.config) return resolveKbachConfig(options.config);
|
|
1253
|
-
const discovered = loadConfigFile(root);
|
|
1295
|
+
const discovered = await loadConfigFile(root);
|
|
1254
1296
|
return discovered ? resolveKbachConfig(discovered) : defaultTheme;
|
|
1255
1297
|
}
|
|
1256
1298
|
function kbach(options = {}) {
|
|
@@ -1259,8 +1301,9 @@ function kbach(options = {}) {
|
|
|
1259
1301
|
let root = process.cwd();
|
|
1260
1302
|
let theme = defaultTheme;
|
|
1261
1303
|
let engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
|
|
1304
|
+
let mainCSSFilePath = join4(root, "src", "kbach.css");
|
|
1262
1305
|
function mainCSSFile() {
|
|
1263
|
-
return
|
|
1306
|
+
return mainCSSFilePath;
|
|
1264
1307
|
}
|
|
1265
1308
|
function classNameHintsFile() {
|
|
1266
1309
|
return join4(root, ".kbach", "kbach-classnames.d.ts");
|
|
@@ -1270,7 +1313,7 @@ function kbach(options = {}) {
|
|
|
1270
1313
|
writeFileEnsuringDir(classNameHintsFile(), dts);
|
|
1271
1314
|
}
|
|
1272
1315
|
function syncMainCSSFile(server) {
|
|
1273
|
-
const changed = writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile,
|
|
1316
|
+
const changed = writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, writeFileEnsuringDir);
|
|
1274
1317
|
if (changed && server) server.watcher.emit("change", mainCSSFile());
|
|
1275
1318
|
}
|
|
1276
1319
|
let syncTimer = null;
|
|
@@ -1284,16 +1327,17 @@ function kbach(options = {}) {
|
|
|
1284
1327
|
return {
|
|
1285
1328
|
name: "kbach",
|
|
1286
1329
|
enforce: "pre",
|
|
1287
|
-
configResolved(resolved) {
|
|
1330
|
+
async configResolved(resolved) {
|
|
1288
1331
|
root = resolved.root;
|
|
1289
|
-
theme = resolveEffectiveTheme(root, options);
|
|
1332
|
+
theme = await resolveEffectiveTheme(root, options);
|
|
1290
1333
|
engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
|
|
1334
|
+
mainCSSFilePath = findMainCSSFile(root, includeDirs, options.cssFile);
|
|
1291
1335
|
},
|
|
1292
1336
|
buildStart() {
|
|
1293
1337
|
engine.scanProjectCssSelectorsOnce();
|
|
1294
1338
|
engine.initialScan();
|
|
1295
1339
|
engine.processSafelist();
|
|
1296
|
-
writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile,
|
|
1340
|
+
writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, writeFileEnsuringDir);
|
|
1297
1341
|
writeClassNameHints();
|
|
1298
1342
|
},
|
|
1299
1343
|
// handleHotUpdate covers edits; add/unlink (create/delete) land on the
|
|
@@ -1332,6 +1376,7 @@ function kbach(options = {}) {
|
|
|
1332
1376
|
|
|
1333
1377
|
export {
|
|
1334
1378
|
createKbachStaticCssEngine,
|
|
1379
|
+
findMainCSSFile,
|
|
1335
1380
|
DEFAULT_SCAN_DIRS,
|
|
1336
1381
|
CSS_SYNC_DEBOUNCE_MS,
|
|
1337
1382
|
loadConfigFile,
|
package/dist/postcss-plugin.js
CHANGED
|
@@ -618,6 +618,7 @@ function createKbachStaticCssEngine(options) {
|
|
|
618
618
|
// src/vite-plugin/index.ts
|
|
619
619
|
var import_fs2 = require("fs");
|
|
620
620
|
var import_path4 = require("path");
|
|
621
|
+
var import_url = require("url");
|
|
621
622
|
var import_module = require("module");
|
|
622
623
|
|
|
623
624
|
// src/generatedPalette.ts
|
|
@@ -1038,16 +1039,36 @@ function resolveKbachConfig(config) {
|
|
|
1038
1039
|
|
|
1039
1040
|
// src/vite-plugin/index.ts
|
|
1040
1041
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
|
|
1041
|
-
function
|
|
1042
|
-
|
|
1043
|
-
|
|
1042
|
+
function isEsmRequireFailure(err) {
|
|
1043
|
+
if (!(err instanceof Error)) return false;
|
|
1044
|
+
return err.code === "ERR_REQUIRE_ESM" || /is not defined in ES module scope/.test(err.message);
|
|
1045
|
+
}
|
|
1046
|
+
async function importConfigModule(path) {
|
|
1047
|
+
const mod = await import((0, import_url.pathToFileURL)(path).href);
|
|
1048
|
+
return mod.default ?? mod;
|
|
1049
|
+
}
|
|
1050
|
+
function requireConfigModule(root, path) {
|
|
1044
1051
|
const mod = (0, import_module.createRequire)(root)(path);
|
|
1045
1052
|
return mod.default ?? mod;
|
|
1046
1053
|
}
|
|
1047
|
-
function
|
|
1054
|
+
async function loadConfigFile(root) {
|
|
1055
|
+
const cjsPath = (0, import_path4.join)(root, "kbach.config.cjs");
|
|
1056
|
+
if ((0, import_fs2.existsSync)(cjsPath)) return requireConfigModule(root, cjsPath);
|
|
1057
|
+
const mjsPath = (0, import_path4.join)(root, "kbach.config.mjs");
|
|
1058
|
+
if ((0, import_fs2.existsSync)(mjsPath)) return importConfigModule(mjsPath);
|
|
1059
|
+
const jsPath = (0, import_path4.join)(root, "kbach.config.js");
|
|
1060
|
+
if (!(0, import_fs2.existsSync)(jsPath)) return void 0;
|
|
1061
|
+
try {
|
|
1062
|
+
return requireConfigModule(root, jsPath);
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
if (!isEsmRequireFailure(err)) throw err;
|
|
1065
|
+
return importConfigModule(jsPath);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
async function resolveEffectiveTheme(root, options) {
|
|
1048
1069
|
if (options.theme) return options.theme;
|
|
1049
1070
|
if (options.config) return resolveKbachConfig(options.config);
|
|
1050
|
-
const discovered = loadConfigFile(root);
|
|
1071
|
+
const discovered = await loadConfigFile(root);
|
|
1051
1072
|
return discovered ? resolveKbachConfig(discovered) : defaultTheme;
|
|
1052
1073
|
}
|
|
1053
1074
|
|
|
@@ -1089,9 +1110,9 @@ function spliceGeneratedCss(root, generatedCss) {
|
|
|
1089
1110
|
function kbachPostcss(options = {}) {
|
|
1090
1111
|
return {
|
|
1091
1112
|
postcssPlugin: "kbach",
|
|
1092
|
-
Once(root, { result }) {
|
|
1113
|
+
async Once(root, { result }) {
|
|
1093
1114
|
const projectRoot = options.root ?? process.cwd();
|
|
1094
|
-
const theme = resolveEffectiveTheme(projectRoot, options);
|
|
1115
|
+
const theme = await resolveEffectiveTheme(projectRoot, options);
|
|
1095
1116
|
const includeDirs = options.include ?? DEFAULT_SCAN_DIRS;
|
|
1096
1117
|
const safelist = options.safelist ?? [];
|
|
1097
1118
|
const engine = createKbachStaticCssEngine({
|
package/dist/postcss-plugin.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
DEFAULT_SCAN_DIRS,
|
|
3
3
|
createKbachStaticCssEngine,
|
|
4
4
|
resolveEffectiveTheme
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-7JBSTM5D.mjs";
|
|
6
6
|
import "./chunk-NKR3PSWO.mjs";
|
|
7
7
|
import "./chunk-N5HCQIBB.mjs";
|
|
8
8
|
|
|
@@ -46,9 +46,9 @@ function spliceGeneratedCss(root, generatedCss) {
|
|
|
46
46
|
function kbachPostcss(options = {}) {
|
|
47
47
|
return {
|
|
48
48
|
postcssPlugin: "kbach",
|
|
49
|
-
Once(root, { result }) {
|
|
49
|
+
async Once(root, { result }) {
|
|
50
50
|
const projectRoot = options.root ?? process.cwd();
|
|
51
|
-
const theme = resolveEffectiveTheme(projectRoot, options);
|
|
51
|
+
const theme = await resolveEffectiveTheme(projectRoot, options);
|
|
52
52
|
const includeDirs = options.include ?? DEFAULT_SCAN_DIRS;
|
|
53
53
|
const safelist = options.safelist ?? [];
|
|
54
54
|
const engine = createKbachStaticCssEngine({
|
package/dist/vite-plugin.d.mts
CHANGED
|
@@ -29,37 +29,76 @@ interface KbachPluginOptions {
|
|
|
29
29
|
safelist?: string[];
|
|
30
30
|
/**
|
|
31
31
|
* Path to the `kbach:start`/`kbach:end` marker file, relative to Vite's
|
|
32
|
-
* resolved root.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
32
|
+
* resolved root. Not needed for the common case — `kbach()` auto-detects
|
|
33
|
+
* it (see `findMainCSSFile`'s own doc comment) by finding wherever you
|
|
34
|
+
* actually put the two marker comments, regardless of whether your
|
|
35
|
+
* project's source root is `src/`, `app/` (React Router v7's framework
|
|
36
|
+
* mode), or anything else in `include`. Set this explicitly only to
|
|
37
|
+
* disambiguate a genuinely unusual layout (a marker file outside every
|
|
38
|
+
* `include` directory, or more than one candidate present at once).
|
|
39
39
|
*/
|
|
40
40
|
cssFile?: string;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Finds where to read/write the `kbach:start`/`kbach:end` marker file,
|
|
44
|
+
* with no configuration required for the common case:
|
|
45
|
+
*
|
|
46
|
+
* 1. An explicit `cssFile` always wins (the one supported escape hatch).
|
|
47
|
+
* 2. Otherwise, look for a `kbach.css` already containing BOTH markers in
|
|
48
|
+
* each `include` directory (in order) and the project root itself —
|
|
49
|
+
* wherever you actually created it is where this plugin keeps using it,
|
|
50
|
+
* regardless of which framework convention (`src/`, `app/`, ...) that
|
|
51
|
+
* is. This is what makes React Router v7's framework mode (`app/
|
|
52
|
+
* kbach.css`) and a plain Vite app (`src/kbach.css`) both "just work"
|
|
53
|
+
* with the exact same `kbach()` call — no per-framework option needed.
|
|
54
|
+
* 3. No marked file exists yet (first run in a fresh project) — pick the
|
|
55
|
+
* first `include` directory that already exists as a real directory on
|
|
56
|
+
* disk, so the file this plugin is about to CREATE lands somewhere that
|
|
57
|
+
* won't need its own parent directory made up out of nothing.
|
|
58
|
+
* 4. Nothing in `include` exists yet either (a genuinely empty project,
|
|
59
|
+
* scaffolded before its first source file) — fall back to the first
|
|
60
|
+
* configured `include` entry regardless; `writeMainCSSFile` (below)
|
|
61
|
+
* creates its parent directory unconditionally, so this never crashes
|
|
62
|
+
* even here.
|
|
63
|
+
*/
|
|
64
|
+
declare function findMainCSSFile(root: string, includeDirs: string[], explicitCssFile?: string): string;
|
|
42
65
|
declare const DEFAULT_SCAN_DIRS: string[];
|
|
43
66
|
declare const CSS_SYNC_DEBOUNCE_MS = 50;
|
|
44
67
|
/**
|
|
45
|
-
* Auto-discovers `kbach.config
|
|
46
|
-
*
|
|
68
|
+
* Auto-discovers a `kbach.config.*` file at the project root — `undefined`
|
|
69
|
+
* if none exists (the common case: most projects have no customization at
|
|
47
70
|
* all, so this is a silent, expected no-op, not a warning).
|
|
48
71
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
72
|
+
* Checks three filenames, in order, mirroring Node's own module-format
|
|
73
|
+
* conventions rather than assuming one: `kbach.config.cjs` (always
|
|
74
|
+
* CommonJS, `module.exports = {...}` — the one unambiguous choice
|
|
75
|
+
* regardless of the project's package.json) and `kbach.config.mjs`
|
|
76
|
+
* (always real ESM, `export default {...}`) are tried first, each loaded
|
|
77
|
+
* the one way that's guaranteed correct for that extension. Only the bare
|
|
78
|
+
* `kbach.config.js` — whose actual meaning depends on the nearest
|
|
79
|
+
* package.json's `"type"` field, exactly like every other `.js` file in
|
|
80
|
+
* the project — needs the fallback below: try `require()` first (correct
|
|
81
|
+
* for `"type": "commonjs"`/no `"type"` field, the more common case), and
|
|
82
|
+
* only on a failure that specifically indicates an ESM/CJS mismatch (see
|
|
83
|
+
* `isEsmRequireFailure`), retry via a real dynamic `import()` instead. A
|
|
84
|
+
* genuine syntax error in an otherwise-correctly-formatted config file is
|
|
85
|
+
* NOT caught here — it propagates as a real, actionable build failure,
|
|
86
|
+
* rather than being silently swallowed into a confusing second attempt.
|
|
87
|
+
*
|
|
88
|
+
* `require()` is built via `createRequire(root)` rather than
|
|
52
89
|
* `createRequire(import.meta.url)` — this file is written as ESM source,
|
|
53
90
|
* but tsup's CJS build output has no real `import.meta.url` (esbuild
|
|
54
91
|
* leaves it `undefined` there), which would make `createRequire` throw for
|
|
55
92
|
* anyone consuming this package's `require('@kbach/react/vite')` entry.
|
|
56
93
|
* `root` works as `createRequire`'s base in either build: it's always an
|
|
57
|
-
* absolute, already-`existsSync`-validated
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
94
|
+
* absolute, already-`existsSync`-validated directory, and `require()`
|
|
95
|
+
* given an already-absolute `path` doesn't consult the base for
|
|
96
|
+
* resolution anyway — the base only matters for a relative specifier,
|
|
97
|
+
* which this never passes it. `import()` needs a real `file://` URL, not
|
|
98
|
+
* a bare Windows path (`C:\...` isn't a valid ESM specifier) — hence
|
|
99
|
+
* `pathToFileURL`.
|
|
61
100
|
*/
|
|
62
|
-
declare function loadConfigFile(root: string): KbachConfig | undefined
|
|
101
|
+
declare function loadConfigFile(root: string): Promise<KbachConfig | undefined>;
|
|
63
102
|
/**
|
|
64
103
|
* Resolves the theme to use, applying the same "explicit theme/config
|
|
65
104
|
* always wins over kbach.config.js auto-discovery" precedence both
|
|
@@ -68,16 +107,17 @@ declare function loadConfigFile(root: string): KbachConfig | undefined;
|
|
|
68
107
|
declare function resolveEffectiveTheme(root: string, options: {
|
|
69
108
|
theme?: ThemeConfig;
|
|
70
109
|
config?: KbachConfig;
|
|
71
|
-
}): ThemeConfig
|
|
110
|
+
}): Promise<ThemeConfig>;
|
|
72
111
|
/**
|
|
73
112
|
* Build-time static CSS generation for Vite — scans source files for
|
|
74
113
|
* Kbach class strings, resolves them through the same Rust engine the
|
|
75
114
|
* runtime uses (via the Node-target WASM build, see wasmNode.ts), and
|
|
76
|
-
* writes a real kbach.css.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
115
|
+
* writes a real kbach.css. Auto-detects WHERE that file is (or should be
|
|
116
|
+
* created) via `findMainCSSFile` — no per-framework config needed for
|
|
117
|
+
* `src/`, `app/` (React Router v7's framework mode), or any other
|
|
118
|
+
* `include` convention; create a file with `/* kbach:start *\/` / `/*
|
|
119
|
+
* kbach:end *\/` markers wherever your project's source actually lives
|
|
120
|
+
* and this plugin finds it there.
|
|
81
121
|
*
|
|
82
122
|
* The actual scanning/resolution/generation logic lives in
|
|
83
123
|
* `staticCss/engine.ts`, shared with `postcss-plugin/index.ts` (Next.js/
|
|
@@ -87,4 +127,4 @@ declare function resolveEffectiveTheme(root: string, options: {
|
|
|
87
127
|
*/
|
|
88
128
|
declare function kbach(options?: KbachPluginOptions): Plugin;
|
|
89
129
|
|
|
90
|
-
export { CSS_SYNC_DEBOUNCE_MS, DEFAULT_SCAN_DIRS, type KbachPluginOptions, kbach, loadConfigFile, resolveEffectiveTheme };
|
|
130
|
+
export { CSS_SYNC_DEBOUNCE_MS, DEFAULT_SCAN_DIRS, type KbachPluginOptions, findMainCSSFile, kbach, loadConfigFile, resolveEffectiveTheme };
|
package/dist/vite-plugin.d.ts
CHANGED
|
@@ -29,37 +29,76 @@ interface KbachPluginOptions {
|
|
|
29
29
|
safelist?: string[];
|
|
30
30
|
/**
|
|
31
31
|
* Path to the `kbach:start`/`kbach:end` marker file, relative to Vite's
|
|
32
|
-
* resolved root.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
32
|
+
* resolved root. Not needed for the common case — `kbach()` auto-detects
|
|
33
|
+
* it (see `findMainCSSFile`'s own doc comment) by finding wherever you
|
|
34
|
+
* actually put the two marker comments, regardless of whether your
|
|
35
|
+
* project's source root is `src/`, `app/` (React Router v7's framework
|
|
36
|
+
* mode), or anything else in `include`. Set this explicitly only to
|
|
37
|
+
* disambiguate a genuinely unusual layout (a marker file outside every
|
|
38
|
+
* `include` directory, or more than one candidate present at once).
|
|
39
39
|
*/
|
|
40
40
|
cssFile?: string;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Finds where to read/write the `kbach:start`/`kbach:end` marker file,
|
|
44
|
+
* with no configuration required for the common case:
|
|
45
|
+
*
|
|
46
|
+
* 1. An explicit `cssFile` always wins (the one supported escape hatch).
|
|
47
|
+
* 2. Otherwise, look for a `kbach.css` already containing BOTH markers in
|
|
48
|
+
* each `include` directory (in order) and the project root itself —
|
|
49
|
+
* wherever you actually created it is where this plugin keeps using it,
|
|
50
|
+
* regardless of which framework convention (`src/`, `app/`, ...) that
|
|
51
|
+
* is. This is what makes React Router v7's framework mode (`app/
|
|
52
|
+
* kbach.css`) and a plain Vite app (`src/kbach.css`) both "just work"
|
|
53
|
+
* with the exact same `kbach()` call — no per-framework option needed.
|
|
54
|
+
* 3. No marked file exists yet (first run in a fresh project) — pick the
|
|
55
|
+
* first `include` directory that already exists as a real directory on
|
|
56
|
+
* disk, so the file this plugin is about to CREATE lands somewhere that
|
|
57
|
+
* won't need its own parent directory made up out of nothing.
|
|
58
|
+
* 4. Nothing in `include` exists yet either (a genuinely empty project,
|
|
59
|
+
* scaffolded before its first source file) — fall back to the first
|
|
60
|
+
* configured `include` entry regardless; `writeMainCSSFile` (below)
|
|
61
|
+
* creates its parent directory unconditionally, so this never crashes
|
|
62
|
+
* even here.
|
|
63
|
+
*/
|
|
64
|
+
declare function findMainCSSFile(root: string, includeDirs: string[], explicitCssFile?: string): string;
|
|
42
65
|
declare const DEFAULT_SCAN_DIRS: string[];
|
|
43
66
|
declare const CSS_SYNC_DEBOUNCE_MS = 50;
|
|
44
67
|
/**
|
|
45
|
-
* Auto-discovers `kbach.config
|
|
46
|
-
*
|
|
68
|
+
* Auto-discovers a `kbach.config.*` file at the project root — `undefined`
|
|
69
|
+
* if none exists (the common case: most projects have no customization at
|
|
47
70
|
* all, so this is a silent, expected no-op, not a warning).
|
|
48
71
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
72
|
+
* Checks three filenames, in order, mirroring Node's own module-format
|
|
73
|
+
* conventions rather than assuming one: `kbach.config.cjs` (always
|
|
74
|
+
* CommonJS, `module.exports = {...}` — the one unambiguous choice
|
|
75
|
+
* regardless of the project's package.json) and `kbach.config.mjs`
|
|
76
|
+
* (always real ESM, `export default {...}`) are tried first, each loaded
|
|
77
|
+
* the one way that's guaranteed correct for that extension. Only the bare
|
|
78
|
+
* `kbach.config.js` — whose actual meaning depends on the nearest
|
|
79
|
+
* package.json's `"type"` field, exactly like every other `.js` file in
|
|
80
|
+
* the project — needs the fallback below: try `require()` first (correct
|
|
81
|
+
* for `"type": "commonjs"`/no `"type"` field, the more common case), and
|
|
82
|
+
* only on a failure that specifically indicates an ESM/CJS mismatch (see
|
|
83
|
+
* `isEsmRequireFailure`), retry via a real dynamic `import()` instead. A
|
|
84
|
+
* genuine syntax error in an otherwise-correctly-formatted config file is
|
|
85
|
+
* NOT caught here — it propagates as a real, actionable build failure,
|
|
86
|
+
* rather than being silently swallowed into a confusing second attempt.
|
|
87
|
+
*
|
|
88
|
+
* `require()` is built via `createRequire(root)` rather than
|
|
52
89
|
* `createRequire(import.meta.url)` — this file is written as ESM source,
|
|
53
90
|
* but tsup's CJS build output has no real `import.meta.url` (esbuild
|
|
54
91
|
* leaves it `undefined` there), which would make `createRequire` throw for
|
|
55
92
|
* anyone consuming this package's `require('@kbach/react/vite')` entry.
|
|
56
93
|
* `root` works as `createRequire`'s base in either build: it's always an
|
|
57
|
-
* absolute, already-`existsSync`-validated
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
94
|
+
* absolute, already-`existsSync`-validated directory, and `require()`
|
|
95
|
+
* given an already-absolute `path` doesn't consult the base for
|
|
96
|
+
* resolution anyway — the base only matters for a relative specifier,
|
|
97
|
+
* which this never passes it. `import()` needs a real `file://` URL, not
|
|
98
|
+
* a bare Windows path (`C:\...` isn't a valid ESM specifier) — hence
|
|
99
|
+
* `pathToFileURL`.
|
|
61
100
|
*/
|
|
62
|
-
declare function loadConfigFile(root: string): KbachConfig | undefined
|
|
101
|
+
declare function loadConfigFile(root: string): Promise<KbachConfig | undefined>;
|
|
63
102
|
/**
|
|
64
103
|
* Resolves the theme to use, applying the same "explicit theme/config
|
|
65
104
|
* always wins over kbach.config.js auto-discovery" precedence both
|
|
@@ -68,16 +107,17 @@ declare function loadConfigFile(root: string): KbachConfig | undefined;
|
|
|
68
107
|
declare function resolveEffectiveTheme(root: string, options: {
|
|
69
108
|
theme?: ThemeConfig;
|
|
70
109
|
config?: KbachConfig;
|
|
71
|
-
}): ThemeConfig
|
|
110
|
+
}): Promise<ThemeConfig>;
|
|
72
111
|
/**
|
|
73
112
|
* Build-time static CSS generation for Vite — scans source files for
|
|
74
113
|
* Kbach class strings, resolves them through the same Rust engine the
|
|
75
114
|
* runtime uses (via the Node-target WASM build, see wasmNode.ts), and
|
|
76
|
-
* writes a real kbach.css.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
115
|
+
* writes a real kbach.css. Auto-detects WHERE that file is (or should be
|
|
116
|
+
* created) via `findMainCSSFile` — no per-framework config needed for
|
|
117
|
+
* `src/`, `app/` (React Router v7's framework mode), or any other
|
|
118
|
+
* `include` convention; create a file with `/* kbach:start *\/` / `/*
|
|
119
|
+
* kbach:end *\/` markers wherever your project's source actually lives
|
|
120
|
+
* and this plugin finds it there.
|
|
81
121
|
*
|
|
82
122
|
* The actual scanning/resolution/generation logic lives in
|
|
83
123
|
* `staticCss/engine.ts`, shared with `postcss-plugin/index.ts` (Next.js/
|
|
@@ -87,4 +127,4 @@ declare function resolveEffectiveTheme(root: string, options: {
|
|
|
87
127
|
*/
|
|
88
128
|
declare function kbach(options?: KbachPluginOptions): Plugin;
|
|
89
129
|
|
|
90
|
-
export { CSS_SYNC_DEBOUNCE_MS, DEFAULT_SCAN_DIRS, type KbachPluginOptions, kbach, loadConfigFile, resolveEffectiveTheme };
|
|
130
|
+
export { CSS_SYNC_DEBOUNCE_MS, DEFAULT_SCAN_DIRS, type KbachPluginOptions, findMainCSSFile, kbach, loadConfigFile, resolveEffectiveTheme };
|
package/dist/vite-plugin.js
CHANGED
|
@@ -22,6 +22,7 @@ var vite_plugin_exports = {};
|
|
|
22
22
|
__export(vite_plugin_exports, {
|
|
23
23
|
CSS_SYNC_DEBOUNCE_MS: () => CSS_SYNC_DEBOUNCE_MS,
|
|
24
24
|
DEFAULT_SCAN_DIRS: () => DEFAULT_SCAN_DIRS,
|
|
25
|
+
findMainCSSFile: () => findMainCSSFile,
|
|
25
26
|
kbach: () => kbach,
|
|
26
27
|
loadConfigFile: () => loadConfigFile,
|
|
27
28
|
resolveEffectiveTheme: () => resolveEffectiveTheme
|
|
@@ -29,6 +30,7 @@ __export(vite_plugin_exports, {
|
|
|
29
30
|
module.exports = __toCommonJS(vite_plugin_exports);
|
|
30
31
|
var import_fs3 = require("fs");
|
|
31
32
|
var import_path4 = require("path");
|
|
33
|
+
var import_url = require("url");
|
|
32
34
|
var import_module = require("module");
|
|
33
35
|
|
|
34
36
|
// src/vite-plugin/cssVars.ts
|
|
@@ -1754,22 +1756,63 @@ function readSourceFile(path) {
|
|
|
1754
1756
|
}
|
|
1755
1757
|
|
|
1756
1758
|
// src/vite-plugin/index.ts
|
|
1759
|
+
function findMainCSSFile(root, includeDirs, explicitCssFile) {
|
|
1760
|
+
if (explicitCssFile) return (0, import_path4.join)(root, explicitCssFile);
|
|
1761
|
+
const searchDirs = [...includeDirs, "."];
|
|
1762
|
+
for (const dir of searchDirs) {
|
|
1763
|
+
const candidate = (0, import_path4.join)(root, dir, "kbach.css");
|
|
1764
|
+
if (!(0, import_fs3.existsSync)(candidate)) continue;
|
|
1765
|
+
try {
|
|
1766
|
+
const content = (0, import_fs3.readFileSync)(candidate, "utf-8");
|
|
1767
|
+
if (content.includes(KBACH_START) && content.includes(KBACH_END)) return candidate;
|
|
1768
|
+
} catch {
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
for (const dir of includeDirs) {
|
|
1772
|
+
const full = (0, import_path4.join)(root, dir);
|
|
1773
|
+
try {
|
|
1774
|
+
if ((0, import_fs3.statSync)(full).isDirectory()) return (0, import_path4.join)(full, "kbach.css");
|
|
1775
|
+
} catch {
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return (0, import_path4.join)(root, includeDirs[0] ?? "src", "kbach.css");
|
|
1779
|
+
}
|
|
1757
1780
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
|
|
1758
1781
|
var CSS_SYNC_DEBOUNCE_MS = 50;
|
|
1759
1782
|
function writeFileEnsuringDir(path, content) {
|
|
1760
1783
|
(0, import_fs3.mkdirSync)((0, import_path4.dirname)(path), { recursive: true });
|
|
1761
1784
|
(0, import_fs3.writeFileSync)(path, content, "utf-8");
|
|
1762
1785
|
}
|
|
1763
|
-
function
|
|
1764
|
-
|
|
1765
|
-
|
|
1786
|
+
function isEsmRequireFailure(err) {
|
|
1787
|
+
if (!(err instanceof Error)) return false;
|
|
1788
|
+
return err.code === "ERR_REQUIRE_ESM" || /is not defined in ES module scope/.test(err.message);
|
|
1789
|
+
}
|
|
1790
|
+
async function importConfigModule(path) {
|
|
1791
|
+
const mod = await import((0, import_url.pathToFileURL)(path).href);
|
|
1792
|
+
return mod.default ?? mod;
|
|
1793
|
+
}
|
|
1794
|
+
function requireConfigModule(root, path) {
|
|
1766
1795
|
const mod = (0, import_module.createRequire)(root)(path);
|
|
1767
1796
|
return mod.default ?? mod;
|
|
1768
1797
|
}
|
|
1769
|
-
function
|
|
1798
|
+
async function loadConfigFile(root) {
|
|
1799
|
+
const cjsPath = (0, import_path4.join)(root, "kbach.config.cjs");
|
|
1800
|
+
if ((0, import_fs3.existsSync)(cjsPath)) return requireConfigModule(root, cjsPath);
|
|
1801
|
+
const mjsPath = (0, import_path4.join)(root, "kbach.config.mjs");
|
|
1802
|
+
if ((0, import_fs3.existsSync)(mjsPath)) return importConfigModule(mjsPath);
|
|
1803
|
+
const jsPath = (0, import_path4.join)(root, "kbach.config.js");
|
|
1804
|
+
if (!(0, import_fs3.existsSync)(jsPath)) return void 0;
|
|
1805
|
+
try {
|
|
1806
|
+
return requireConfigModule(root, jsPath);
|
|
1807
|
+
} catch (err) {
|
|
1808
|
+
if (!isEsmRequireFailure(err)) throw err;
|
|
1809
|
+
return importConfigModule(jsPath);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
async function resolveEffectiveTheme(root, options) {
|
|
1770
1813
|
if (options.theme) return options.theme;
|
|
1771
1814
|
if (options.config) return resolveKbachConfig(options.config);
|
|
1772
|
-
const discovered = loadConfigFile(root);
|
|
1815
|
+
const discovered = await loadConfigFile(root);
|
|
1773
1816
|
return discovered ? resolveKbachConfig(discovered) : defaultTheme;
|
|
1774
1817
|
}
|
|
1775
1818
|
function kbach(options = {}) {
|
|
@@ -1778,8 +1821,9 @@ function kbach(options = {}) {
|
|
|
1778
1821
|
let root = process.cwd();
|
|
1779
1822
|
let theme = defaultTheme;
|
|
1780
1823
|
let engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
|
|
1824
|
+
let mainCSSFilePath = (0, import_path4.join)(root, "src", "kbach.css");
|
|
1781
1825
|
function mainCSSFile() {
|
|
1782
|
-
return
|
|
1826
|
+
return mainCSSFilePath;
|
|
1783
1827
|
}
|
|
1784
1828
|
function classNameHintsFile() {
|
|
1785
1829
|
return (0, import_path4.join)(root, ".kbach", "kbach-classnames.d.ts");
|
|
@@ -1789,7 +1833,7 @@ function kbach(options = {}) {
|
|
|
1789
1833
|
writeFileEnsuringDir(classNameHintsFile(), dts);
|
|
1790
1834
|
}
|
|
1791
1835
|
function syncMainCSSFile(server) {
|
|
1792
|
-
const changed = writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile,
|
|
1836
|
+
const changed = writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, writeFileEnsuringDir);
|
|
1793
1837
|
if (changed && server) server.watcher.emit("change", mainCSSFile());
|
|
1794
1838
|
}
|
|
1795
1839
|
let syncTimer = null;
|
|
@@ -1803,16 +1847,17 @@ function kbach(options = {}) {
|
|
|
1803
1847
|
return {
|
|
1804
1848
|
name: "kbach",
|
|
1805
1849
|
enforce: "pre",
|
|
1806
|
-
configResolved(resolved) {
|
|
1850
|
+
async configResolved(resolved) {
|
|
1807
1851
|
root = resolved.root;
|
|
1808
|
-
theme = resolveEffectiveTheme(root, options);
|
|
1852
|
+
theme = await resolveEffectiveTheme(root, options);
|
|
1809
1853
|
engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
|
|
1854
|
+
mainCSSFilePath = findMainCSSFile(root, includeDirs, options.cssFile);
|
|
1810
1855
|
},
|
|
1811
1856
|
buildStart() {
|
|
1812
1857
|
engine.scanProjectCssSelectorsOnce();
|
|
1813
1858
|
engine.initialScan();
|
|
1814
1859
|
engine.processSafelist();
|
|
1815
|
-
writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile,
|
|
1860
|
+
writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, writeFileEnsuringDir);
|
|
1816
1861
|
writeClassNameHints();
|
|
1817
1862
|
},
|
|
1818
1863
|
// handleHotUpdate covers edits; add/unlink (create/delete) land on the
|
|
@@ -1852,6 +1897,7 @@ function kbach(options = {}) {
|
|
|
1852
1897
|
0 && (module.exports = {
|
|
1853
1898
|
CSS_SYNC_DEBOUNCE_MS,
|
|
1854
1899
|
DEFAULT_SCAN_DIRS,
|
|
1900
|
+
findMainCSSFile,
|
|
1855
1901
|
kbach,
|
|
1856
1902
|
loadConfigFile,
|
|
1857
1903
|
resolveEffectiveTheme
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CSS_SYNC_DEBOUNCE_MS,
|
|
3
3
|
DEFAULT_SCAN_DIRS,
|
|
4
|
+
findMainCSSFile,
|
|
4
5
|
kbach,
|
|
5
6
|
loadConfigFile,
|
|
6
7
|
resolveEffectiveTheme
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7JBSTM5D.mjs";
|
|
8
9
|
import "./chunk-NKR3PSWO.mjs";
|
|
9
10
|
import "./chunk-N5HCQIBB.mjs";
|
|
10
11
|
export {
|
|
11
12
|
CSS_SYNC_DEBOUNCE_MS,
|
|
12
13
|
DEFAULT_SCAN_DIRS,
|
|
14
|
+
findMainCSSFile,
|
|
13
15
|
kbach,
|
|
14
16
|
loadConfigFile,
|
|
15
17
|
resolveEffectiveTheme
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kbach/react",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.9",
|
|
4
4
|
"description": "A utility-first styling engine for React, powered by a real Rust/WASM core — Tailwind CSS v4 feature parity with Kbach's own color palette.",
|
|
5
5
|
"keywords": ["css", "react", "tailwind", "utility-first", "wasm", "styling"],
|
|
6
6
|
"license": "MIT",
|