@ng-native/nx 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/files/AGENTS.md CHANGED
@@ -29,7 +29,7 @@ change; both are fast.
29
29
  - **Element names are lowercase:** `<view>`, `<text>`, `<pressable>`, `<scroll-view>`,
30
30
  `<text-input>`, `<image>`, `<switch>`, `<safe-area-view>`, `<virtual-list>`, `<modal>`. Each is
31
31
  imported from `@ng-native/components` into the component's `imports`, like any Angular
32
- component: `imports: [View, Text]`. `<View>` compiles to an empty template, and an element used
32
+ component: `imports: [View, Text]`. `<View>` matches no component and fails the build, and an element used
33
33
  without its import renders as a plain view.
34
34
  - **There is no DOM.** No `document`, no `window`, no `<button>`, `<a>`, `<input>` or `<img>`, no
35
35
  `@angular/platform-browser`, and no `@angular/animations`. Use the native elements above. HTML's
package/migrations.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "generators": {
3
3
  "sync-app-versions": {
4
- "version": "0.12.0",
4
+ "version": "0.14.0",
5
5
  "description": "Move the @ng-native packages each project's own package.json lists to the new version.",
6
6
  "implementation": "./migrations/run.cjs#sync-app-versions"
7
7
  },
@@ -19,6 +19,11 @@
19
19
  "version": "0.3.0",
20
20
  "description": "Note each import of reactNative or ReactNative from @ng-native/device, which no longer exports them.",
21
21
  "implementation": "./migrations/run.cjs#device-react-native-import"
22
+ },
23
+ "engine-node-tag": {
24
+ "version": "0.14.0",
25
+ "description": "Read an engine node's element tag as tag rather than name.",
26
+ "implementation": "./migrations/run.cjs#engine-node-tag"
22
27
  }
23
28
  }
24
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-native/nx",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Nx generators for Angular Native: nx add, an Expo app project whose targets run it, and native libraries and components.",
5
5
  "keywords": [
6
6
  "angular",
@@ -38,7 +38,7 @@
38
38
  ]
39
39
  },
40
40
  "dependencies": {
41
- "@ng-native/migrate": "0.12.0",
41
+ "@ng-native/migrate": "0.14.0",
42
42
  "@nx/devkit": "^23.0.0",
43
43
  "minimatch": "^10.0.0",
44
44
  "semver": "^7.7.0"
@@ -55,6 +55,7 @@ const POSTCSS_CONFIGS = [
55
55
  ...['', '.json', '.yaml', '.yml', '.js', '.cjs', '.mjs'].map((ext) => `.postcssrc${ext}`),
56
56
  ];
57
57
  const V3_DIRECTIVES = ['@tailwind base;', '@tailwind components;', '@tailwind utilities;'];
58
+ const COMMONJS_NAMES = ['require', 'module', 'exports', '__dirname', '__filename'];
58
59
  const PRESET_ALLOWED = String.raw`'^.*/tailwind\\.preset\\.cjs$'`;
59
60
 
60
61
  /**
@@ -245,9 +246,11 @@ function writeV4Stylesheet(tree, file, base, themes) {
245
246
  const lines =
246
247
  existing === null ? ["/* The app's Tailwind entry, without preflight. */", ...base] : [];
247
248
  const text = existing ?? '';
249
+ // Compared in one quote: a formatter may have rewritten the file since.
250
+ const written = text.replace(/"/g, "'");
248
251
  const imports = themes
249
252
  .map((theme) => `@import '${path.relative(path.dirname(file), theme)}';`)
250
- .filter((line) => !text.includes(line));
253
+ .filter((line) => !written.includes(line));
251
254
  if (existing !== null && imports.length === 0) return;
252
255
  const last = [...text.matchAll(/^@import .+;$/gm)].at(-1);
253
256
  const at = last ? last.index + last[0].length + 1 : text.length;
@@ -262,13 +265,13 @@ function writeV4Stylesheet(tree, file, base, themes) {
262
265
  function writeWebV4Stylesheet(tree, file, themes) {
263
266
  const existing = tree.read(file, 'utf-8');
264
267
  if (existing !== null && !existing.includes('@ng-native/tailwind/web.css')) {
265
- if (existing.includes('tailwindcss')) {
268
+ if (importsTailwind(existing)) {
266
269
  return byHand(
267
270
  file,
268
271
  `import '@ng-native/tailwind/web.css' after Tailwind's theme and utilities.`,
269
272
  );
270
273
  }
271
- tree.write(file, `${V4_WEB_IMPORTS.join('\n')}\n\n${existing}`);
274
+ tree.write(file, prependedTo(existing, V4_WEB_IMPORTS));
272
275
  }
273
276
  writeV4Stylesheet(tree, file, V4_WEB_IMPORTS, themes);
274
277
  }
@@ -323,9 +326,43 @@ function linkStylesheet(tree, root, stylesheet) {
323
326
  tree.write(file, `${html.slice(0, head.index)}${indent}${link}\n${html.slice(head.index)}`);
324
327
  }
325
328
 
329
+ function withoutComments(css) {
330
+ return css.replace(/\/\*[\s\S]*?\*\//g, '');
331
+ }
332
+
333
+ /** Whether `css` imports Tailwind 4, in a quote or `url()`; a comment that names it does not. */
334
+ function importsTailwind(css) {
335
+ return /@import\s+(?:url\(\s*)?["']tailwindcss(?:\/[^"']*)?["']/.test(withoutComments(css));
336
+ }
337
+
338
+ /** `css` with `lines` ahead of its rules, after a leading `@charset`, which must stay first. */
339
+ function prependedTo(css, lines) {
340
+ const charset = /^@charset\s+(["'])[^"'\n]*\1\s*;[ \t]*\r?\n?/.exec(css)?.[0] ?? '';
341
+ const head = charset && !charset.endsWith('\n') ? `${charset}\n` : charset;
342
+ return `${head}${lines.join('\n')}\n\n${css.slice(charset.length)}`;
343
+ }
344
+
326
345
  function writeV3Stylesheet(tree, root) {
327
346
  const file = joinPathFragments(root, 'src/styles.css');
328
- if (!tree.exists(file)) tree.write(file, `${V3_DIRECTIVES.join('\n')}\n`);
347
+ const existing = tree.read(file, 'utf-8');
348
+ if (existing === null) tree.write(file, `${V3_DIRECTIVES.join('\n')}\n`);
349
+ // One the app has already, without Tailwind, gets the directives ahead of its rules.
350
+ else if (!/@tailwind\s/.test(withoutComments(existing))) {
351
+ tree.write(file, prependedTo(existing, V3_DIRECTIVES));
352
+ }
353
+ }
354
+
355
+ /**
356
+ * The device build's Tailwind 4 stylesheet. One the app has already, without Tailwind, gets the
357
+ * native preset's imports ahead of its rules, where CSS requires an `@import`: Metro is given
358
+ * this file as Tailwind's input, and with no Tailwind in it every class would draw unstyled.
359
+ */
360
+ function writeDeviceV4Stylesheet(tree, file, themes) {
361
+ const existing = tree.read(file, 'utf-8');
362
+ if (existing !== null && !importsTailwind(existing)) {
363
+ tree.write(file, prependedTo(existing, V4_IMPORTS));
364
+ }
365
+ writeV4Stylesheet(tree, file, V4_IMPORTS, themes);
329
366
  }
330
367
 
331
368
  /**
@@ -345,8 +382,9 @@ function writeV3Config(tree, file, presets, options) {
345
382
  }
346
383
  return;
347
384
  }
348
- const had = [...existing.matchAll(/require\('([^']+\/tailwind\.preset\.cjs)'\)/g)].map(
349
- (match) => match[1],
385
+ // In either quote: a formatter may have rewritten the file since.
386
+ const had = [...existing.matchAll(/require\((['"])([^'"]+\/tailwind\.preset\.cjs)\1\)/g)].map(
387
+ (match) => match[2],
350
388
  );
351
389
  wanted.unshift(...had.filter((preset) => !wanted.includes(preset)));
352
390
  }
@@ -464,10 +502,21 @@ function setUpWeb(tree, root, vite, version, libraries, besideDevice) {
464
502
  linkStylesheet(tree, root, file);
465
503
  }
466
504
 
467
- /** A JavaScript name for a library's preset: `ui`, `sharedUi`. */
505
+ /**
506
+ * A JavaScript name for a library's preset: `ui`, `sharedUi`. A directory named for a word the
507
+ * language keeps, `switch` or `default`, gets `switchPreset`.
508
+ */
468
509
  function identifier(name) {
469
510
  const camel = name.replace(/[^a-zA-Z0-9]+(.)/g, (_, next) => next.toUpperCase());
470
- return /^[a-zA-Z_$]/.test(camel) ? camel : `_${camel}`;
511
+ const named = /^[a-zA-Z_$]/.test(camel) ? camel : `_${camel}`;
512
+ // The config runs in CommonJS's wrapper, which binds these names as parameters.
513
+ if (COMMONJS_NAMES.includes(named)) return `${named}Preset`;
514
+ try {
515
+ new Function(`'use strict'; const ${named} = 0;`);
516
+ return named;
517
+ } catch {
518
+ return `${named}Preset`;
519
+ }
471
520
  }
472
521
 
473
522
  /** Adds the preset's file name to `@nx/enforce-module-boundaries`' `allow` list, where there is one. */
@@ -550,10 +599,9 @@ async function tailwind(tree, options) {
550
599
  libraries.map((library) => libraryPreset(tree, library)),
551
600
  );
552
601
  } else {
553
- writeV4Stylesheet(
602
+ writeDeviceV4Stylesheet(
554
603
  tree,
555
604
  joinPathFragments(app.root, 'src/styles.css'),
556
- V4_IMPORTS,
557
605
  libraries.map((library) => libraryTheme(tree, library)),
558
606
  );
559
607
  }