@gusnips/native 0.1.0 → 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 +23 -5
- 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 +36 -0
- package/dist/node/css.d.ts.map +1 -1
- package/dist/node/css.js +124 -8
- 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 +61 -0
- package/src/node/css.ts +135 -8
package/README.md
CHANGED
|
@@ -121,13 +121,16 @@ Five things worth knowing:
|
|
|
121
121
|
of 48. `--rem` changes it if you really want another value.
|
|
122
122
|
- **Tailwind finds your classes by itself** in the app's folder. Tailwind v4 has no `--content`
|
|
123
123
|
flag. For a file outside the folder, add an `@source` line to `global.css`.
|
|
124
|
-
- **Pin lightningcss to 1.30.1
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
- **Pin lightningcss to exactly 1.30.1.** react-native-css 3.0.7's compiler breaks on 1.30.2, and
|
|
125
|
+
an app measured 1.32.0 and 1.33.0 breaking it too, so no later release is known to work.
|
|
126
|
+
react-native-css's own check is swallowed by an empty `catch`, so the build refuses every
|
|
127
|
+
release after 1.30.1 instead. Pin it with `"overrides": { "lightningcss": "1.30.1" }` in the root
|
|
127
128
|
`package.json`: a devDependency is not enough, because react-native-css loads the copy under
|
|
128
129
|
`@expo/metro-config` first.
|
|
129
130
|
- **A `className` change hot-reloads.** Only a change to `global.css` or your tokens needs the
|
|
130
|
-
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.
|
|
131
134
|
- **There is no global font.** react-native-css drops element and `*` selectors, so a text without
|
|
132
135
|
a `font-*` class renders in the system font.
|
|
133
136
|
|
|
@@ -148,7 +151,8 @@ each class compiles to. The colours come from the stylesheet itself, so a change
|
|
|
148
151
|
icons with the text. Tailwind writes only classes it finds in your files, so keep each palette
|
|
149
152
|
class in your source; the build stops and names any it cannot find.
|
|
150
153
|
|
|
151
|
-
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 })`.
|
|
152
156
|
|
|
153
157
|
## Streams
|
|
154
158
|
|
|
@@ -293,6 +297,20 @@ and under the default flow that URL carries the refresh token.
|
|
|
293
297
|
resolver protects the bundle from bun's store layout; the catalog stops the versions drifting in
|
|
294
298
|
the first place.
|
|
295
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
|
+
|
|
296
314
|
## Why the dependencies are peers
|
|
297
315
|
|
|
298
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
|
@@ -41,4 +41,40 @@ export interface NativeCssResult {
|
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
export declare function buildNativeCss(options?: NativeCssOptions): Promise<NativeCssResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Whether this lightningcss breaks react-native-css 3.0.7's compiler. 1.30.2 is the release
|
|
46
|
+
* react-native-css itself names, and an adopter's app then measured 1.32.0 and 1.33.0 failing the
|
|
47
|
+
* same way, so every release after 1.30.1 counts as broken until one is measured working.
|
|
48
|
+
*/
|
|
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
|
+
}>;
|
|
44
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
|
|
@@ -115,10 +116,23 @@ function loadCompiler(fromApp) {
|
|
|
115
116
|
return compiler;
|
|
116
117
|
}
|
|
117
118
|
/**
|
|
118
|
-
* lightningcss
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
|
|
119
|
+
* Whether this lightningcss breaks react-native-css 3.0.7's compiler. 1.30.2 is the release
|
|
120
|
+
* react-native-css itself names, and an adopter's app then measured 1.32.0 and 1.33.0 failing the
|
|
121
|
+
* same way, so every release after 1.30.1 counts as broken until one is measured working.
|
|
122
|
+
*/
|
|
123
|
+
export function isBrokenLightningcss(version) {
|
|
124
|
+
const [major = 0, minor = 0, patch = 0] = version.split(/[.+-]/, 3).map(Number);
|
|
125
|
+
if (major !== 1)
|
|
126
|
+
return major > 1;
|
|
127
|
+
if (minor !== 30)
|
|
128
|
+
return minor > 30;
|
|
129
|
+
return patch > 1;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* react-native-css checks for a broken lightningcss and means to throw, but the check sits inside a
|
|
133
|
+
* `try` whose `catch` is empty, so the throw is swallowed and the build goes on. This asks the same
|
|
134
|
+
* copy its loader picks: the one under `@expo/metro-config` first (a peer of react-native-css, so
|
|
135
|
+
* bun installs it), then its own.
|
|
122
136
|
*
|
|
123
137
|
* That order is also why a devDependency on lightningcss does not fix it. Pin it with `overrides`
|
|
124
138
|
* in the root `package.json`, which reaches every copy.
|
|
@@ -142,9 +156,10 @@ function refuseBrokenLightningcss(fromApp) {
|
|
|
142
156
|
catch {
|
|
143
157
|
continue;
|
|
144
158
|
}
|
|
145
|
-
if (version
|
|
146
|
-
throw new Error(
|
|
147
|
-
'"overrides": { "lightningcss": "1.30.1" } in the root
|
|
159
|
+
if (isBrokenLightningcss(version))
|
|
160
|
+
throw new Error(`lightningcss ${version} breaks react-native-css's compiler (every release after 1.30.1 ` +
|
|
161
|
+
'measured so far does). Pin it with "overrides": { "lightningcss": "1.30.1" } in the root ' +
|
|
162
|
+
"package.json, then run bun install.");
|
|
148
163
|
return;
|
|
149
164
|
}
|
|
150
165
|
}
|
|
@@ -178,4 +193,105 @@ function missing(name) {
|
|
|
178
193
|
function isNotFound(error) {
|
|
179
194
|
return error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND";
|
|
180
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
|
+
}
|
|
181
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"));
|
|
@@ -0,0 +1,61 @@
|
|
|
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";
|
|
6
|
+
|
|
7
|
+
describe("isBrokenLightningcss", () => {
|
|
8
|
+
it("lets 1.30.1 and earlier through", () => {
|
|
9
|
+
for (const version of ["1.30.1", "1.30.0", "1.29.3", "1.0.0", "0.9.9"])
|
|
10
|
+
expect(isBrokenLightningcss(version), version).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("refuses every release after 1.30.1, the measured ones included", () => {
|
|
14
|
+
for (const version of ["1.30.2", "1.30.10", "1.31.0", "1.32.0", "1.33.0", "2.0.0"])
|
|
15
|
+
expect(isBrokenLightningcss(version), version).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("reads a prerelease or build suffix by its numbers", () => {
|
|
19
|
+
expect(isBrokenLightningcss("1.30.1-beta.2")).toBe(false);
|
|
20
|
+
expect(isBrokenLightningcss("1.30.2+build.7")).toBe(true);
|
|
21
|
+
});
|
|
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");
|
|
@@ -172,10 +173,22 @@ function loadCompiler(fromApp: NodeRequire): Compiler {
|
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
/**
|
|
175
|
-
* lightningcss
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
|
|
176
|
+
* Whether this lightningcss breaks react-native-css 3.0.7's compiler. 1.30.2 is the release
|
|
177
|
+
* react-native-css itself names, and an adopter's app then measured 1.32.0 and 1.33.0 failing the
|
|
178
|
+
* same way, so every release after 1.30.1 counts as broken until one is measured working.
|
|
179
|
+
*/
|
|
180
|
+
export function isBrokenLightningcss(version: string): boolean {
|
|
181
|
+
const [major = 0, minor = 0, patch = 0] = version.split(/[.+-]/, 3).map(Number);
|
|
182
|
+
if (major !== 1) return major > 1;
|
|
183
|
+
if (minor !== 30) return minor > 30;
|
|
184
|
+
return patch > 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* react-native-css checks for a broken lightningcss and means to throw, but the check sits inside a
|
|
189
|
+
* `try` whose `catch` is empty, so the throw is swallowed and the build goes on. This asks the same
|
|
190
|
+
* copy its loader picks: the one under `@expo/metro-config` first (a peer of react-native-css, so
|
|
191
|
+
* bun installs it), then its own.
|
|
179
192
|
*
|
|
180
193
|
* That order is also why a devDependency on lightningcss does not fix it. Pin it with `overrides`
|
|
181
194
|
* in the root `package.json`, which reaches every copy.
|
|
@@ -197,10 +210,11 @@ function refuseBrokenLightningcss(fromApp: NodeRequire): void {
|
|
|
197
210
|
} catch {
|
|
198
211
|
continue;
|
|
199
212
|
}
|
|
200
|
-
if (version
|
|
213
|
+
if (isBrokenLightningcss(version))
|
|
201
214
|
throw new Error(
|
|
202
|
-
|
|
203
|
-
'"overrides": { "lightningcss": "1.30.1" } in the root
|
|
215
|
+
`lightningcss ${version} breaks react-native-css's compiler (every release after 1.30.1 ` +
|
|
216
|
+
'measured so far does). Pin it with "overrides": { "lightningcss": "1.30.1" } in the root ' +
|
|
217
|
+
"package.json, then run bun install.",
|
|
204
218
|
);
|
|
205
219
|
return;
|
|
206
220
|
}
|
|
@@ -244,3 +258,116 @@ function missing(name: string): string {
|
|
|
244
258
|
function isNotFound(error: unknown): boolean {
|
|
245
259
|
return error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND";
|
|
246
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
|
+
}
|