@gusnips/native 0.1.1 → 0.1.2
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 +19 -2
- package/dist/node/bin/gusnips-native-css.js +20 -5
- package/dist/node/bin/gusnips-native-css.js.map +1 -1
- package/dist/node/css.d.ts +30 -0
- package/dist/node/css.d.ts.map +1 -1
- package/dist/node/css.js +103 -1
- package/dist/node/css.js.map +1 -1
- package/package.json +1 -1
- package/src/node/bin/gusnips-native-css.ts +21 -5
- package/src/node/css.test.ts +44 -2
- package/src/node/css.ts +115 -1
package/README.md
CHANGED
|
@@ -128,7 +128,9 @@ Five things worth knowing:
|
|
|
128
128
|
`package.json`: a devDependency is not enough, because react-native-css loads the copy under
|
|
129
129
|
`@expo/metro-config` first.
|
|
130
130
|
- **A `className` change hot-reloads.** Only a change to `global.css` or your tokens needs the
|
|
131
|
-
build to run again.
|
|
131
|
+
build to run again. While you work on those, keep `gusnips-native-css --watch` running beside
|
|
132
|
+
Metro: it builds again when `global.css` or a file it imports changes (your token files, followed
|
|
133
|
+
through their own imports), and not when you edit a component.
|
|
132
134
|
- **There is no global font.** react-native-css drops element and `*` selectors, so a text without
|
|
133
135
|
a `font-*` class renders in the system font.
|
|
134
136
|
|
|
@@ -149,7 +151,8 @@ each class compiles to. The colours come from the stylesheet itself, so a change
|
|
|
149
151
|
icons with the text. Tailwind writes only classes it finds in your files, so keep each palette
|
|
150
152
|
class in your source; the build stops and names any it cannot find.
|
|
151
153
|
|
|
152
|
-
From a script, the same build is `buildNativeCss({ palette })` from `@gusnips/native/css
|
|
154
|
+
From a script, the same build is `buildNativeCss({ palette })` from `@gusnips/native/css`, and the
|
|
155
|
+
watch is `watchNativeCss(options, { onBuild, onError })`.
|
|
153
156
|
|
|
154
157
|
## Streams
|
|
155
158
|
|
|
@@ -294,6 +297,20 @@ and under the default flow that URL carries the refresh token.
|
|
|
294
297
|
resolver protects the bundle from bun's store layout; the catalog stops the versions drifting in
|
|
295
298
|
the first place.
|
|
296
299
|
|
|
300
|
+
## Testing an app that uses this
|
|
301
|
+
|
|
302
|
+
Mock the kit's import, not `react-native` under it:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
vi.mock("@gusnips/native/network", () => ({ useIsOffline: () => false }));
|
|
306
|
+
vi.mock("@gusnips/native/query", () => ({ installQueryManagers: () => {} }));
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Vitest runs a package from `node_modules` as it is, outside its own module graph, so a
|
|
310
|
+
`vi.mock("react-native")` or `vi.mock("@react-native-community/netinfo")` in your test never
|
|
311
|
+
reaches the imports inside `@gusnips/native`. Those load the real packages, which do not run under
|
|
312
|
+
Node. Mocking the subpath your code imports replaces the whole thing, and nothing under it loads.
|
|
313
|
+
|
|
297
314
|
## Why the dependencies are peers
|
|
298
315
|
|
|
299
316
|
There are no `dependencies`. Each package here is one your app already has, or one that carries
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { readFile } from "node:fs/promises";
|
|
9
9
|
import { resolve } from "node:path";
|
|
10
10
|
import { parseArgs } from "node:util";
|
|
11
|
-
import { buildNativeCss } from "../css.js";
|
|
11
|
+
import { buildNativeCss, watchNativeCss } from "../css.js";
|
|
12
12
|
const HELP = `gusnips-native-css: compile global.css for React Native, before Metro starts.
|
|
13
13
|
|
|
14
14
|
Usage: gusnips-native-css [options]
|
|
@@ -20,9 +20,11 @@ Usage: gusnips-native-css [options]
|
|
|
20
20
|
{ "text": { "light": "text-ink", "dark": "dark:text-ink-dark" } }
|
|
21
21
|
writes colors.ts beside stylesheet.ts
|
|
22
22
|
--rem <px> pixels per rem (default: 16, as on the web)
|
|
23
|
+
--watch build again when global.css or a file it imports changes
|
|
23
24
|
-h, --help show this
|
|
24
25
|
|
|
25
26
|
Run it before Metro, in the same script: "start": "gusnips-native-css && react-native start"
|
|
27
|
+
While you edit the CSS or the tokens, keep "gusnips-native-css --watch" open beside Metro.
|
|
26
28
|
`;
|
|
27
29
|
async function main() {
|
|
28
30
|
const { values } = parseArgs({
|
|
@@ -32,6 +34,7 @@ async function main() {
|
|
|
32
34
|
out: { type: "string" },
|
|
33
35
|
palette: { type: "string" },
|
|
34
36
|
rem: { type: "string" },
|
|
37
|
+
watch: { type: "boolean" },
|
|
35
38
|
help: { type: "boolean", short: "h" },
|
|
36
39
|
},
|
|
37
40
|
});
|
|
@@ -39,17 +42,29 @@ async function main() {
|
|
|
39
42
|
process.stdout.write(HELP);
|
|
40
43
|
return 0;
|
|
41
44
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
root,
|
|
45
|
+
const options = {
|
|
46
|
+
root: resolve(values.root ?? "."),
|
|
45
47
|
input: values.input,
|
|
46
48
|
outDir: values.out,
|
|
47
49
|
rem: values.rem === undefined ? undefined : Number(values.rem),
|
|
48
50
|
palette: values.palette === undefined ? undefined : await readPalette(resolve(values.palette)),
|
|
51
|
+
};
|
|
52
|
+
if (!values.watch) {
|
|
53
|
+
report(await buildNativeCss(options));
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
// The folder watchers keep the process running until Ctrl-C. A failed build is printed and
|
|
57
|
+
// the watch goes on.
|
|
58
|
+
await watchNativeCss(options, {
|
|
59
|
+
onBuild: report,
|
|
60
|
+
onError: (error) => console.error(`native css: ${error.message}`),
|
|
49
61
|
});
|
|
50
|
-
console.log(
|
|
62
|
+
console.log("native css: watching global.css and the files it imports. Ctrl-C stops.");
|
|
51
63
|
return 0;
|
|
52
64
|
}
|
|
65
|
+
function report(result) {
|
|
66
|
+
console.log(`native css: ${result.classes} classes → ${result.files.join(", ")}`);
|
|
67
|
+
}
|
|
53
68
|
/** The palette file is the app's own, but a typo in it should read as one, not as a crash. */
|
|
54
69
|
async function readPalette(file) {
|
|
55
70
|
const parsed = JSON.parse(await readFile(file, "utf-8"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gusnips-native-css.js","sourceRoot":"","sources":["../../../src/node/bin/gusnips-native-css.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"gusnips-native-css.js","sourceRoot":"","sources":["../../../src/node/bin/gusnips-native-css.ts"],"names":[],"mappings":"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"}
|
package/dist/node/css.d.ts
CHANGED
|
@@ -47,4 +47,34 @@ export declare function buildNativeCss(options?: NativeCssOptions): Promise<Nati
|
|
|
47
47
|
* same way, so every release after 1.30.1 counts as broken until one is measured working.
|
|
48
48
|
*/
|
|
49
49
|
export declare function isBrokenLightningcss(version: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The files whose change means the stylesheet must be built again: the entry, and every file of the
|
|
52
|
+
* app's it imports, followed through their own imports. That is `global.css` and the token files
|
|
53
|
+
* it pulls in. A package import (`tailwindcss/theme.css`, `nativewind/theme`) is left out, because it
|
|
54
|
+
* changes only on an install. Source files are left out on purpose too: a `className` edit
|
|
55
|
+
* hot-reloads without a rebuild, and a rebuild on every save would cost a compile per keystroke.
|
|
56
|
+
*
|
|
57
|
+
* An import is the app's own when it names a file that exists from the importing file's folder,
|
|
58
|
+
* which is how Tailwind tries it first.
|
|
59
|
+
*/
|
|
60
|
+
export declare function cssInputs(entry: string): Promise<string[]>;
|
|
61
|
+
export interface NativeCssWatchHandlers {
|
|
62
|
+
/** A build finished. Also called for the first build. */
|
|
63
|
+
onBuild(result: NativeCssResult): void;
|
|
64
|
+
/** A build failed. Watching goes on, so fixing the file builds again. */
|
|
65
|
+
onError(error: Error): void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build, then build again whenever `global.css` or a file it imports changes. Stop it with
|
|
69
|
+
* `close()`. A first build that fails is reported and watched like any other, so saving the fix is
|
|
70
|
+
* enough.
|
|
71
|
+
*
|
|
72
|
+
* It watches each FOLDER and filters by name, not each file: an editor that saves by writing a new
|
|
73
|
+
* file and renaming it over the old one leaves a per-file watcher on the deleted file, which then
|
|
74
|
+
* never fires again. The import list is read again after every build, so a new token file starts
|
|
75
|
+
* being watched as soon as `global.css` imports it.
|
|
76
|
+
*/
|
|
77
|
+
export declare function watchNativeCss(options: NativeCssOptions, { onBuild, onError }: NativeCssWatchHandlers): Promise<{
|
|
78
|
+
close(): void;
|
|
79
|
+
}>;
|
|
50
80
|
//# sourceMappingURL=css.d.ts.map
|
package/dist/node/css.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../../src/node/css.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../../src/node/css.ts"],"names":[],"mappings":"AA+BA,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CA2C7F;AAuDD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK7D;AA+ED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAehE;AAMD,MAAM,WAAW,sBAAsB;IACrC,yDAAyD;IACzD,OAAO,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IACvC,yEAAyE;IACzE,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,gBAAgB,EACzB,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,sBAAsB,GAC3C,OAAO,CAAC;IAAE,KAAK,IAAI,IAAI,CAAA;CAAE,CAAC,CA0D5B"}
|
package/dist/node/css.js
CHANGED
|
@@ -20,10 +20,11 @@
|
|
|
20
20
|
* from where the app installed it.
|
|
21
21
|
*/
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
|
+
import { statSync, watch } from "node:fs";
|
|
23
24
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
24
25
|
import { createRequire } from "node:module";
|
|
25
26
|
import { tmpdir } from "node:os";
|
|
26
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
27
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
27
28
|
/**
|
|
28
29
|
* ```ts
|
|
29
30
|
* await buildNativeCss(); // global.css → src/generated/stylesheet.ts
|
|
@@ -192,4 +193,105 @@ function missing(name) {
|
|
|
192
193
|
function isNotFound(error) {
|
|
193
194
|
return error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND";
|
|
194
195
|
}
|
|
196
|
+
// ── Watching ─────────────────────────────────────────────────────────────────
|
|
197
|
+
/**
|
|
198
|
+
* The files whose change means the stylesheet must be built again: the entry, and every file of the
|
|
199
|
+
* app's it imports, followed through their own imports. That is `global.css` and the token files
|
|
200
|
+
* it pulls in. A package import (`tailwindcss/theme.css`, `nativewind/theme`) is left out, because it
|
|
201
|
+
* changes only on an install. Source files are left out on purpose too: a `className` edit
|
|
202
|
+
* hot-reloads without a rebuild, and a rebuild on every save would cost a compile per keystroke.
|
|
203
|
+
*
|
|
204
|
+
* An import is the app's own when it names a file that exists from the importing file's folder,
|
|
205
|
+
* which is how Tailwind tries it first.
|
|
206
|
+
*/
|
|
207
|
+
export async function cssInputs(entry) {
|
|
208
|
+
const found = new Set();
|
|
209
|
+
async function visit(file) {
|
|
210
|
+
if (found.has(file))
|
|
211
|
+
return;
|
|
212
|
+
found.add(file);
|
|
213
|
+
const source = await readFile(file, "utf-8").catch(() => "");
|
|
214
|
+
const code = source.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
215
|
+
for (const [, specifier] of code.matchAll(/@import\s+(?:url\(\s*)?["']([^"']+)["']/g)) {
|
|
216
|
+
if (specifier === undefined)
|
|
217
|
+
continue;
|
|
218
|
+
const target = isAbsolute(specifier) ? specifier : resolve(dirname(file), specifier);
|
|
219
|
+
if (isFile(target))
|
|
220
|
+
await visit(target);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
await visit(resolve(entry));
|
|
224
|
+
return [...found];
|
|
225
|
+
}
|
|
226
|
+
function isFile(path) {
|
|
227
|
+
return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Build, then build again whenever `global.css` or a file it imports changes. Stop it with
|
|
231
|
+
* `close()`. A first build that fails is reported and watched like any other, so saving the fix is
|
|
232
|
+
* enough.
|
|
233
|
+
*
|
|
234
|
+
* It watches each FOLDER and filters by name, not each file: an editor that saves by writing a new
|
|
235
|
+
* file and renaming it over the old one leaves a per-file watcher on the deleted file, which then
|
|
236
|
+
* never fires again. The import list is read again after every build, so a new token file starts
|
|
237
|
+
* being watched as soon as `global.css` imports it.
|
|
238
|
+
*/
|
|
239
|
+
export async function watchNativeCss(options, { onBuild, onError }) {
|
|
240
|
+
const root = resolve(options.root ?? ".");
|
|
241
|
+
const entry = resolve(root, options.input ?? "global.css");
|
|
242
|
+
let watchers = [];
|
|
243
|
+
let watched = new Set();
|
|
244
|
+
let timer;
|
|
245
|
+
let running = null;
|
|
246
|
+
let again = false;
|
|
247
|
+
let closed = false;
|
|
248
|
+
async function rewatch() {
|
|
249
|
+
const inputs = await cssInputs(entry);
|
|
250
|
+
for (const watcher of watchers)
|
|
251
|
+
watcher.close();
|
|
252
|
+
watched = new Set(inputs);
|
|
253
|
+
watchers = [...new Set(inputs.map((file) => dirname(file)))].map((folder) => watch(folder, (_event, name) => {
|
|
254
|
+
if (name !== null && watched.has(join(folder, basename(name))))
|
|
255
|
+
schedule();
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
// One build at a time: a save during a build marks it to run once more after, so the last save
|
|
259
|
+
// always lands and two compiles never write the same files at once.
|
|
260
|
+
async function build() {
|
|
261
|
+
if (running) {
|
|
262
|
+
again = true;
|
|
263
|
+
return running;
|
|
264
|
+
}
|
|
265
|
+
running = (async () => {
|
|
266
|
+
do {
|
|
267
|
+
again = false;
|
|
268
|
+
try {
|
|
269
|
+
onBuild(await buildNativeCss(options));
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
onError(error instanceof Error ? error : new Error(String(error)));
|
|
273
|
+
}
|
|
274
|
+
if (!closed)
|
|
275
|
+
await rewatch();
|
|
276
|
+
} while (again && !closed);
|
|
277
|
+
})().finally(() => {
|
|
278
|
+
running = null;
|
|
279
|
+
});
|
|
280
|
+
return running;
|
|
281
|
+
}
|
|
282
|
+
// Editors fire several events for one save. One build per burst.
|
|
283
|
+
function schedule() {
|
|
284
|
+
clearTimeout(timer);
|
|
285
|
+
timer = setTimeout(() => void build(), 50);
|
|
286
|
+
}
|
|
287
|
+
await build();
|
|
288
|
+
return {
|
|
289
|
+
close() {
|
|
290
|
+
closed = true;
|
|
291
|
+
clearTimeout(timer);
|
|
292
|
+
for (const watcher of watchers)
|
|
293
|
+
watcher.close();
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
195
297
|
//# sourceMappingURL=css.js.map
|
package/dist/node/css.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/node/css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/node/css.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gusnips/native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "What a bare React Native app needs beside @gusnips/react: streams over XHR, React Query on app focus and network, theme and language that remember the choice, the Supabase session storage, the Metro config for a bun monorepo, and the Tailwind to react-native-css build.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { readFile } from "node:fs/promises";
|
|
9
9
|
import { resolve } from "node:path";
|
|
10
10
|
import { parseArgs } from "node:util";
|
|
11
|
-
import { buildNativeCss, type PaletteEntry } from "../css.ts";
|
|
11
|
+
import { buildNativeCss, watchNativeCss, type NativeCssResult, type PaletteEntry } from "../css.ts";
|
|
12
12
|
|
|
13
13
|
const HELP = `gusnips-native-css: compile global.css for React Native, before Metro starts.
|
|
14
14
|
|
|
@@ -21,9 +21,11 @@ Usage: gusnips-native-css [options]
|
|
|
21
21
|
{ "text": { "light": "text-ink", "dark": "dark:text-ink-dark" } }
|
|
22
22
|
writes colors.ts beside stylesheet.ts
|
|
23
23
|
--rem <px> pixels per rem (default: 16, as on the web)
|
|
24
|
+
--watch build again when global.css or a file it imports changes
|
|
24
25
|
-h, --help show this
|
|
25
26
|
|
|
26
27
|
Run it before Metro, in the same script: "start": "gusnips-native-css && react-native start"
|
|
28
|
+
While you edit the CSS or the tokens, keep "gusnips-native-css --watch" open beside Metro.
|
|
27
29
|
`;
|
|
28
30
|
|
|
29
31
|
async function main(): Promise<number> {
|
|
@@ -34,6 +36,7 @@ async function main(): Promise<number> {
|
|
|
34
36
|
out: { type: "string" },
|
|
35
37
|
palette: { type: "string" },
|
|
36
38
|
rem: { type: "string" },
|
|
39
|
+
watch: { type: "boolean" },
|
|
37
40
|
help: { type: "boolean", short: "h" },
|
|
38
41
|
},
|
|
39
42
|
});
|
|
@@ -42,18 +45,31 @@ async function main(): Promise<number> {
|
|
|
42
45
|
return 0;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
root,
|
|
48
|
+
const options = {
|
|
49
|
+
root: resolve(values.root ?? "."),
|
|
48
50
|
input: values.input,
|
|
49
51
|
outDir: values.out,
|
|
50
52
|
rem: values.rem === undefined ? undefined : Number(values.rem),
|
|
51
53
|
palette: values.palette === undefined ? undefined : await readPalette(resolve(values.palette)),
|
|
54
|
+
};
|
|
55
|
+
if (!values.watch) {
|
|
56
|
+
report(await buildNativeCss(options));
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
// The folder watchers keep the process running until Ctrl-C. A failed build is printed and
|
|
60
|
+
// the watch goes on.
|
|
61
|
+
await watchNativeCss(options, {
|
|
62
|
+
onBuild: report,
|
|
63
|
+
onError: (error) => console.error(`native css: ${error.message}`),
|
|
52
64
|
});
|
|
53
|
-
console.log(
|
|
65
|
+
console.log("native css: watching global.css and the files it imports. Ctrl-C stops.");
|
|
54
66
|
return 0;
|
|
55
67
|
}
|
|
56
68
|
|
|
69
|
+
function report(result: NativeCssResult): void {
|
|
70
|
+
console.log(`native css: ${result.classes} classes → ${result.files.join(", ")}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
57
73
|
/** The palette file is the app's own, but a typo in it should read as one, not as a crash. */
|
|
58
74
|
async function readPalette(file: string): Promise<Record<string, PaletteEntry>> {
|
|
59
75
|
const parsed: unknown = JSON.parse(await readFile(file, "utf-8"));
|
package/src/node/css.test.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { cssInputs, isBrokenLightningcss } from "./css.ts";
|
|
3
6
|
|
|
4
7
|
describe("isBrokenLightningcss", () => {
|
|
5
8
|
it("lets 1.30.1 and earlier through", () => {
|
|
@@ -17,3 +20,42 @@ describe("isBrokenLightningcss", () => {
|
|
|
17
20
|
expect(isBrokenLightningcss("1.30.2+build.7")).toBe(true);
|
|
18
21
|
});
|
|
19
22
|
});
|
|
23
|
+
|
|
24
|
+
const made: string[] = [];
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
for (const dir of made.splice(0)) await rm(dir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("cssInputs: what makes --watch build again", () => {
|
|
30
|
+
it("is the entry and every local file it imports, followed through their own imports", async () => {
|
|
31
|
+
const app = await mkdtemp(join(tmpdir(), "native-css-inputs-"));
|
|
32
|
+
made.push(app);
|
|
33
|
+
const shared = join(app, "packages", "shared");
|
|
34
|
+
await mkdir(shared, { recursive: true });
|
|
35
|
+
await mkdir(join(app, "mobile"), { recursive: true });
|
|
36
|
+
await writeFile(
|
|
37
|
+
join(app, "mobile", "global.css"),
|
|
38
|
+
[
|
|
39
|
+
'@import "tailwindcss/theme.css" layer(theme);', // a package: changes only on install
|
|
40
|
+
'@import "nativewind/theme";',
|
|
41
|
+
'@import "../packages/shared/tokens.css";',
|
|
42
|
+
'/* @import "./commented-out.css"; */',
|
|
43
|
+
'@import url("./fonts.css");',
|
|
44
|
+
'@import "./missing.css";', // a typo is Tailwind's error to report, not a file to watch
|
|
45
|
+
].join("\n"),
|
|
46
|
+
);
|
|
47
|
+
await writeFile(join(app, "mobile", "fonts.css"), ":root { --font-sans: Inter; }");
|
|
48
|
+
await writeFile(join(app, "mobile", "commented-out.css"), "");
|
|
49
|
+
await writeFile(join(shared, "tokens.css"), '@import "./colors.css";\n@theme {}');
|
|
50
|
+
await writeFile(join(shared, "colors.css"), '@import "./tokens.css";'); // a cycle ends
|
|
51
|
+
|
|
52
|
+
expect((await cssInputs(join(app, "mobile", "global.css"))).sort()).toEqual(
|
|
53
|
+
[
|
|
54
|
+
join(app, "mobile", "fonts.css"),
|
|
55
|
+
join(app, "mobile", "global.css"),
|
|
56
|
+
join(shared, "colors.css"),
|
|
57
|
+
join(shared, "tokens.css"),
|
|
58
|
+
].sort(),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
});
|
package/src/node/css.ts
CHANGED
|
@@ -20,10 +20,11 @@
|
|
|
20
20
|
* from where the app installed it.
|
|
21
21
|
*/
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
|
+
import { statSync, watch, type FSWatcher } from "node:fs";
|
|
23
24
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
24
25
|
import { createRequire } from "node:module";
|
|
25
26
|
import { tmpdir } from "node:os";
|
|
26
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
27
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
27
28
|
import type { ReactNativeCssStyleSheet } from "react-native-css/compiler";
|
|
28
29
|
|
|
29
30
|
type Compiler = typeof import("react-native-css/compiler");
|
|
@@ -257,3 +258,116 @@ function missing(name: string): string {
|
|
|
257
258
|
function isNotFound(error: unknown): boolean {
|
|
258
259
|
return error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND";
|
|
259
260
|
}
|
|
261
|
+
|
|
262
|
+
// ── Watching ─────────────────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The files whose change means the stylesheet must be built again: the entry, and every file of the
|
|
266
|
+
* app's it imports, followed through their own imports. That is `global.css` and the token files
|
|
267
|
+
* it pulls in. A package import (`tailwindcss/theme.css`, `nativewind/theme`) is left out, because it
|
|
268
|
+
* changes only on an install. Source files are left out on purpose too: a `className` edit
|
|
269
|
+
* hot-reloads without a rebuild, and a rebuild on every save would cost a compile per keystroke.
|
|
270
|
+
*
|
|
271
|
+
* An import is the app's own when it names a file that exists from the importing file's folder,
|
|
272
|
+
* which is how Tailwind tries it first.
|
|
273
|
+
*/
|
|
274
|
+
export async function cssInputs(entry: string): Promise<string[]> {
|
|
275
|
+
const found = new Set<string>();
|
|
276
|
+
async function visit(file: string): Promise<void> {
|
|
277
|
+
if (found.has(file)) return;
|
|
278
|
+
found.add(file);
|
|
279
|
+
const source = await readFile(file, "utf-8").catch(() => "");
|
|
280
|
+
const code = source.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
281
|
+
for (const [, specifier] of code.matchAll(/@import\s+(?:url\(\s*)?["']([^"']+)["']/g)) {
|
|
282
|
+
if (specifier === undefined) continue;
|
|
283
|
+
const target = isAbsolute(specifier) ? specifier : resolve(dirname(file), specifier);
|
|
284
|
+
if (isFile(target)) await visit(target);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
await visit(resolve(entry));
|
|
288
|
+
return [...found];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function isFile(path: string): boolean {
|
|
292
|
+
return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface NativeCssWatchHandlers {
|
|
296
|
+
/** A build finished. Also called for the first build. */
|
|
297
|
+
onBuild(result: NativeCssResult): void;
|
|
298
|
+
/** A build failed. Watching goes on, so fixing the file builds again. */
|
|
299
|
+
onError(error: Error): void;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Build, then build again whenever `global.css` or a file it imports changes. Stop it with
|
|
304
|
+
* `close()`. A first build that fails is reported and watched like any other, so saving the fix is
|
|
305
|
+
* enough.
|
|
306
|
+
*
|
|
307
|
+
* It watches each FOLDER and filters by name, not each file: an editor that saves by writing a new
|
|
308
|
+
* file and renaming it over the old one leaves a per-file watcher on the deleted file, which then
|
|
309
|
+
* never fires again. The import list is read again after every build, so a new token file starts
|
|
310
|
+
* being watched as soon as `global.css` imports it.
|
|
311
|
+
*/
|
|
312
|
+
export async function watchNativeCss(
|
|
313
|
+
options: NativeCssOptions,
|
|
314
|
+
{ onBuild, onError }: NativeCssWatchHandlers,
|
|
315
|
+
): Promise<{ close(): void }> {
|
|
316
|
+
const root = resolve(options.root ?? ".");
|
|
317
|
+
const entry = resolve(root, options.input ?? "global.css");
|
|
318
|
+
let watchers: FSWatcher[] = [];
|
|
319
|
+
let watched = new Set<string>();
|
|
320
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
321
|
+
let running: Promise<void> | null = null;
|
|
322
|
+
let again = false;
|
|
323
|
+
let closed = false;
|
|
324
|
+
|
|
325
|
+
async function rewatch(): Promise<void> {
|
|
326
|
+
const inputs = await cssInputs(entry);
|
|
327
|
+
for (const watcher of watchers) watcher.close();
|
|
328
|
+
watched = new Set(inputs);
|
|
329
|
+
watchers = [...new Set(inputs.map((file) => dirname(file)))].map((folder) =>
|
|
330
|
+
watch(folder, (_event, name) => {
|
|
331
|
+
if (name !== null && watched.has(join(folder, basename(name)))) schedule();
|
|
332
|
+
}),
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// One build at a time: a save during a build marks it to run once more after, so the last save
|
|
337
|
+
// always lands and two compiles never write the same files at once.
|
|
338
|
+
async function build(): Promise<void> {
|
|
339
|
+
if (running) {
|
|
340
|
+
again = true;
|
|
341
|
+
return running;
|
|
342
|
+
}
|
|
343
|
+
running = (async () => {
|
|
344
|
+
do {
|
|
345
|
+
again = false;
|
|
346
|
+
try {
|
|
347
|
+
onBuild(await buildNativeCss(options));
|
|
348
|
+
} catch (error) {
|
|
349
|
+
onError(error instanceof Error ? error : new Error(String(error)));
|
|
350
|
+
}
|
|
351
|
+
if (!closed) await rewatch();
|
|
352
|
+
} while (again && !closed);
|
|
353
|
+
})().finally(() => {
|
|
354
|
+
running = null;
|
|
355
|
+
});
|
|
356
|
+
return running;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Editors fire several events for one save. One build per burst.
|
|
360
|
+
function schedule(): void {
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
timer = setTimeout(() => void build(), 50);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await build();
|
|
366
|
+
return {
|
|
367
|
+
close() {
|
|
368
|
+
closed = true;
|
|
369
|
+
clearTimeout(timer);
|
|
370
|
+
for (const watcher of watchers) watcher.close();
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|