@ng-native/nx 0.3.0 → 0.4.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/application/index.cjs +15 -3
- package/files/AGENTS.md +8 -3
- package/library/index.cjs +101 -4
- package/migrations.json +1 -1
- package/native-app.cjs +28 -0
- package/package.json +5 -2
package/application/index.cjs
CHANGED
|
@@ -59,6 +59,15 @@ function hasPathAliases(tree, workspaces) {
|
|
|
59
59
|
* The compiler options `file` ends up with, those it inherits through `extends` included, as
|
|
60
60
|
* TypeScript merges them: each option from the last config that sets it.
|
|
61
61
|
*/
|
|
62
|
+
/**
|
|
63
|
+
* Whether the app reaches its libraries through the package manager's links alone, where a
|
|
64
|
+
* library can have a copy of its own of a package the app has too. With path aliases the
|
|
65
|
+
* workspace has one `node_modules`, and a `paths` list of the app's would replace the aliases.
|
|
66
|
+
*/
|
|
67
|
+
function linksLibraries(tree, workspaces) {
|
|
68
|
+
return workspaces && !hasPathAliases(tree, workspaces);
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
function baseCompilerOptions(tree, file = 'tsconfig.base.json', ancestors = []) {
|
|
63
72
|
if (ancestors.includes(file) || !tree.exists(file)) return {};
|
|
64
73
|
const config = readJson(tree, file);
|
|
@@ -116,7 +125,7 @@ function metroPort(tree) {
|
|
|
116
125
|
return port;
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
function targets(directory, port) {
|
|
128
|
+
function targets(directory, port, oneCopy) {
|
|
120
129
|
const run = (command) => ({ executor: 'nx:run-commands', options: { cwd: directory, command } });
|
|
121
130
|
const own = port === DEFAULT_PORT ? '' : ` --port ${port}`;
|
|
122
131
|
// `expo run:ios` and `expo run:android` build the port into the app, so a later app's has to be
|
|
@@ -127,7 +136,7 @@ function targets(directory, port) {
|
|
|
127
136
|
};
|
|
128
137
|
return {
|
|
129
138
|
typecheck: {
|
|
130
|
-
...run(native.TYPECHECK),
|
|
139
|
+
...run(oneCopy ? native.TYPECHECK_ONE_COPY : native.TYPECHECK),
|
|
131
140
|
cache: true,
|
|
132
141
|
inputs: ['default', '^production'],
|
|
133
142
|
// The Tailwind sheet the command builds, which a cache hit restores rather than rebuilds.
|
|
@@ -190,6 +199,9 @@ async function writeFiles(tree, { directory, projectName, workspaces, bundleIden
|
|
|
190
199
|
const workspaceBase = base ? `${offsetFromRoot(directory)}tsconfig.base.json` : undefined;
|
|
191
200
|
const conditions = baseCompilerOptions(tree).customConditions ?? [];
|
|
192
201
|
file('tsconfig.json', JSON.stringify(native.tsconfig(workspaceBase, conditions), null, 2) + '\n');
|
|
202
|
+
if (linksLibraries(tree, workspaces)) {
|
|
203
|
+
file('tsconfig.typecheck.json', JSON.stringify(native.typecheckTsconfig(), null, 2) + '\n');
|
|
204
|
+
}
|
|
193
205
|
file('vitest.config.mts', native.vitestConfig(Boolean(base)));
|
|
194
206
|
|
|
195
207
|
const manifest = { name: projectName, version: '0.0.1', private: true, main: 'src/main.ts' };
|
|
@@ -258,7 +270,7 @@ async function application(tree, options) {
|
|
|
258
270
|
.split(',')
|
|
259
271
|
.map((tag) => tag.trim())
|
|
260
272
|
.filter(Boolean),
|
|
261
|
-
targets: targets(directory, port),
|
|
273
|
+
targets: targets(directory, port, linksLibraries(tree, workspaces)),
|
|
262
274
|
});
|
|
263
275
|
|
|
264
276
|
ignoreExpo(tree);
|
package/files/AGENTS.md
CHANGED
|
@@ -31,12 +31,17 @@ change; both are fast.
|
|
|
31
31
|
imported from `@ng-native/components` into the component's `imports`, like any Angular
|
|
32
32
|
component: `imports: [View, Text]`. `<View>` compiles to an empty template, and an element used
|
|
33
33
|
without its import renders as a plain view.
|
|
34
|
-
- **There is no DOM.** No `document`, no `window`, no `<
|
|
35
|
-
|
|
34
|
+
- **There is no DOM.** No `document`, no `window`, no `<button>`, `<a>`, `<input>` or `<img>`, no
|
|
35
|
+
`@angular/platform-browser`, and no `@angular/animations`. Use the native elements above. HTML's
|
|
36
|
+
text elements (`p`, `span`, `h1`, `strong`) draw as text and its layout elements (`div`,
|
|
37
|
+
`section`, `ul`) as plain views, with none of a browser's default styles.
|
|
36
38
|
- **A component's host is a flex item.** `<app-case-list />` commits as a view of its own, with no
|
|
37
39
|
`flex`, so a `flex: 1` scroll view inside it gets a height of zero and shows nothing. A component
|
|
38
40
|
that fills the space it is given sets `host: { style: 'flex: 1' }` (or `:host { flex: 1; }`).
|
|
39
|
-
- **Text
|
|
41
|
+
- **Text is drawn in a text element.** `<text>` is the one with inputs and outputs (`numberOfLines`,
|
|
42
|
+
`(press)`). `p`, `span` and the other HTML text elements are text while they hold only text and
|
|
43
|
+
other text elements; one that holds a view is a view. Text written straight into a view is given
|
|
44
|
+
a paragraph of its own, one per run, and never flows inline with the elements beside it.
|
|
40
45
|
- **Events are native:** `(press)` on `<pressable>`, not `(click)`; `[(value)]` or `(changeText)` on
|
|
41
46
|
`<text-input>`; `(scroll)`, `(layout)`. A pressable text is `<text pressable (press)="...">`.
|
|
42
47
|
- **Signals, zoneless, AOT.** State is signals and `computed()`; there is no zone.js, so nothing
|
package/library/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
names,
|
|
24
24
|
readNxJson,
|
|
25
25
|
readJson,
|
|
26
|
+
writeJson,
|
|
26
27
|
updateJson,
|
|
27
28
|
updateProjectConfiguration,
|
|
28
29
|
} = require('@nx/devkit');
|
|
@@ -107,6 +108,11 @@ const imports = {
|
|
|
107
108
|
},
|
|
108
109
|
};
|
|
109
110
|
|
|
111
|
+
/** What `nx typecheck` runs for a library in the TypeScript preset: `ngc`, which reads templates. */
|
|
112
|
+
const typecheckDependencies = {
|
|
113
|
+
'@angular/compiler-cli': native.devDependencies['@angular/compiler-cli'],
|
|
114
|
+
};
|
|
115
|
+
|
|
110
116
|
function projectAt(tree, root) {
|
|
111
117
|
for (const [name, project] of getProjects(tree))
|
|
112
118
|
if (project.root === root) return { name, project };
|
|
@@ -164,12 +170,100 @@ function writeSpecConfig(tree, root, angular) {
|
|
|
164
170
|
});
|
|
165
171
|
}
|
|
166
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Lets the library's source import as its test, the template and the documentation do, with
|
|
175
|
+
* `.ts`: `allowImportingTsExtensions` in `tsconfig.lib.json`, and `src/index.ts` written that way
|
|
176
|
+
* in place of the `.js` `@nx/js` gives it.
|
|
177
|
+
*
|
|
178
|
+
* TypeScript takes the option only where no JavaScript is emitted, which is the TypeScript
|
|
179
|
+
* preset's own setting (`emitDeclarationOnly`, which `@nx/js` writes into `tsconfig.lib.json`). A
|
|
180
|
+
* workspace that has turned that off keeps `.js`.
|
|
181
|
+
*/
|
|
182
|
+
function importWithTsExtensions(tree, root) {
|
|
183
|
+
const config = joinPathFragments(root, 'tsconfig.lib.json');
|
|
184
|
+
if (!emitsDeclarationsOnly(tree, config)) return;
|
|
185
|
+
updateJson(tree, config, (lib) => ({
|
|
186
|
+
...lib,
|
|
187
|
+
compilerOptions: { ...lib.compilerOptions, allowImportingTsExtensions: true },
|
|
188
|
+
}));
|
|
189
|
+
const index = joinPathFragments(root, 'src', 'index.ts');
|
|
190
|
+
if (!tree.exists(index)) return;
|
|
191
|
+
const source = tree.read(index, 'utf-8');
|
|
192
|
+
tree.write(index, source.replace(/(from\s+['"]\.{1,2}\/[^'"]*)\.js(['"])/g, '$1.ts$2'));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** The config `nx typecheck` gives `ngc` for a library, beside the one it is built with. */
|
|
196
|
+
const TYPECHECK_CONFIG = 'tsconfig.typecheck.json';
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* A config `ngc` can check the library's templates with, as an app's `typecheck` does. `@nx/js`
|
|
200
|
+
* infers a `typecheck` that runs `tsc`, which reads no template: a misspelled input passes.
|
|
201
|
+
*
|
|
202
|
+
* `tsconfig.lib.json` itself will not do: `ngc` refuses `emitDeclarationOnly`, and a project that
|
|
203
|
+
* is `composite` has to emit. The Angular options are the generated app's: a native view has no
|
|
204
|
+
* DOM to check a host binding or an event against.
|
|
205
|
+
*/
|
|
206
|
+
function writeTypecheckConfig(tree, root) {
|
|
207
|
+
writeJson(tree, joinPathFragments(root, TYPECHECK_CONFIG), {
|
|
208
|
+
extends: './tsconfig.lib.json',
|
|
209
|
+
compilerOptions: { noEmit: true, composite: false, emitDeclarationOnly: false },
|
|
210
|
+
angularCompilerOptions: {
|
|
211
|
+
strictTemplates: true,
|
|
212
|
+
typeCheckHostBindings: false,
|
|
213
|
+
strictDomEventTypes: false,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** `nx typecheck <library>`: `ngc`, in place of the `tsc` `@nx/js` infers. */
|
|
219
|
+
function typecheckTarget(root) {
|
|
220
|
+
return {
|
|
221
|
+
executor: 'nx:run-commands',
|
|
222
|
+
options: { cwd: root, command: `ngc -p ${TYPECHECK_CONFIG}` },
|
|
223
|
+
cache: true,
|
|
224
|
+
inputs: ['default', '^production'],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Whether a tsconfig emits declarations and no JavaScript, once what it extends is merged in. */
|
|
229
|
+
function emitsDeclarationsOnly(tree, file) {
|
|
230
|
+
const { noEmit, emitDeclarationOnly } = emitOptions(tree, file, new Set());
|
|
231
|
+
return noEmit === true || emitDeclarationOnly === true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* `noEmit` and `emitDeclarationOnly` as the compiler reads them: each from the nearest config that
|
|
236
|
+
* sets it, and a later entry of an `extends` list over an earlier one.
|
|
237
|
+
*/
|
|
238
|
+
function emitOptions(tree, file, seen) {
|
|
239
|
+
if (seen.has(file) || !tree.exists(file)) return {};
|
|
240
|
+
// The configs on the way here, to stop at a cycle. Not every config read: two entries of a list
|
|
241
|
+
// may share an ancestor, and each is read whole.
|
|
242
|
+
const above = new Set(seen).add(file);
|
|
243
|
+
const { compilerOptions = {}, extends: base = [] } = readJson(tree, file);
|
|
244
|
+
// ponytail: a config that comes from a package is not followed, so a library under one keeps
|
|
245
|
+
// `.js`. Resolve the specifier from the config's directory if a workspace needs it.
|
|
246
|
+
const inherited = [base]
|
|
247
|
+
.flat()
|
|
248
|
+
.filter((from) => typeof from === 'string' && from.startsWith('.'))
|
|
249
|
+
.map((from) => joinPathFragments(path.dirname(file), from))
|
|
250
|
+
.map((from) => emitOptions(tree, from.endsWith('.json') ? from : `${from}.json`, above));
|
|
251
|
+
const { noEmit, emitDeclarationOnly } = compilerOptions;
|
|
252
|
+
return Object.assign(
|
|
253
|
+
{},
|
|
254
|
+
...inherited,
|
|
255
|
+
noEmit === undefined ? {} : { noEmit },
|
|
256
|
+
emitDeclarationOnly === undefined ? {} : { emitDeclarationOnly },
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
167
260
|
/**
|
|
168
261
|
* The packages the component and its test import: in the library's own `package.json` when it is
|
|
169
262
|
* a workspace package, which pnpm links from, and at the root otherwise. A version already there
|
|
170
263
|
* is left alone.
|
|
171
264
|
*/
|
|
172
|
-
async function addImports(tree, root) {
|
|
265
|
+
async function addImports(tree, root, alsoDev = {}) {
|
|
266
|
+
const devDependencies = { ...imports.devDependencies, ...alsoDev };
|
|
173
267
|
const manifest = joinPathFragments(root, 'package.json');
|
|
174
268
|
const rootManifest = readJson(tree, 'package.json');
|
|
175
269
|
if (tree.exists(manifest)) {
|
|
@@ -177,7 +271,7 @@ async function addImports(tree, root) {
|
|
|
177
271
|
return addDependenciesToPackageJson(
|
|
178
272
|
tree,
|
|
179
273
|
await own(imports.dependencies),
|
|
180
|
-
await own(
|
|
274
|
+
await own(devDependencies),
|
|
181
275
|
manifest,
|
|
182
276
|
true,
|
|
183
277
|
);
|
|
@@ -186,7 +280,7 @@ async function addImports(tree, root) {
|
|
|
186
280
|
return addDependenciesToPackageJson(
|
|
187
281
|
tree,
|
|
188
282
|
await asSaved(tree, imports.dependencies, existing),
|
|
189
|
-
await asSaved(tree,
|
|
283
|
+
await asSaved(tree, devDependencies, existing),
|
|
190
284
|
'package.json',
|
|
191
285
|
true,
|
|
192
286
|
);
|
|
@@ -211,10 +305,13 @@ async function library(tree, options) {
|
|
|
211
305
|
const aliases = hasPathAliases(tree, usesWorkspaces(tree));
|
|
212
306
|
tree.write(joinPathFragments(directory, 'vitest.config.mts'), native.vitestConfig(aliases));
|
|
213
307
|
writeSpecConfig(tree, directory, angular);
|
|
308
|
+
if (!angular) importWithTsExtensions(tree, directory);
|
|
309
|
+
if (!angular) writeTypecheckConfig(tree, directory);
|
|
214
310
|
updateProjectConfiguration(tree, name, {
|
|
215
311
|
...project,
|
|
216
312
|
targets: {
|
|
217
313
|
...project.targets,
|
|
314
|
+
...(angular ? {} : { typecheck: typecheckTarget(directory) }),
|
|
218
315
|
test: {
|
|
219
316
|
executor: 'nx:run-commands',
|
|
220
317
|
options: { cwd: directory, command: 'vitest run' },
|
|
@@ -224,7 +321,7 @@ async function library(tree, options) {
|
|
|
224
321
|
},
|
|
225
322
|
});
|
|
226
323
|
|
|
227
|
-
await addImports(tree, directory);
|
|
324
|
+
await addImports(tree, directory, angular ? {} : typecheckDependencies);
|
|
228
325
|
// `nxViteTsPaths()`, which the Vitest config uses to reach the workspace's other libraries.
|
|
229
326
|
if (aliases) {
|
|
230
327
|
const vite = { '@nx/vite': workspaceNxVersion(tree) };
|
package/migrations.json
CHANGED
package/native-app.cjs
CHANGED
|
@@ -121,6 +121,31 @@ function tsconfig(workspaceBase, conditions = []) {
|
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/** The `@ng-native` packages with entry points beside their main one, each published in `dist`. */
|
|
125
|
+
const WITH_ENTRY_POINTS = ['components', 'expo', 'platform', 'web'];
|
|
126
|
+
|
|
127
|
+
/** `nx typecheck` in a workspace whose libraries are the package manager's links. */
|
|
128
|
+
const TYPECHECK_ONE_COPY = TYPECHECK.replace('tsconfig.json', 'tsconfig.typecheck.json');
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The tsconfig `nx typecheck` reads where libraries are the package manager's links: the app's,
|
|
132
|
+
* with every `@ng-native` package resolved to the app's own copy.
|
|
133
|
+
*
|
|
134
|
+
* pnpm gives a library a copy of its own of a package whose peers it does not share with the app,
|
|
135
|
+
* the gesture and animation packages an app adds among them. `ngc` then finds a component a
|
|
136
|
+
* library imports in one copy and the app's import of it in the other, and stops at `NG3004`
|
|
137
|
+
* before it checks a template. The Metro preset and the Vitest plugin make the same choice where
|
|
138
|
+
* they resolve. It is a file of its own because Metro reads the `paths` of `tsconfig.json`, and
|
|
139
|
+
* these skip the `exports` of each package, its `browser` condition included.
|
|
140
|
+
*/
|
|
141
|
+
function typecheckTsconfig() {
|
|
142
|
+
const paths = { '@ng-native/*': ['./node_modules/@ng-native/*'] };
|
|
143
|
+
for (const name of WITH_ENTRY_POINTS) {
|
|
144
|
+
paths[`@ng-native/${name}/*`] = [`./node_modules/@ng-native/${name}/dist/*`];
|
|
145
|
+
}
|
|
146
|
+
return { extends: './tsconfig.json', compilerOptions: { paths } };
|
|
147
|
+
}
|
|
148
|
+
|
|
124
149
|
/** @param {boolean} withPaths whether to resolve the workspace's tsconfig path aliases */
|
|
125
150
|
function vitestConfig(withPaths) {
|
|
126
151
|
const template = readFileSync(path.join(__dirname, 'files', 'vitest.config.mts'), 'utf8');
|
|
@@ -165,7 +190,10 @@ module.exports = {
|
|
|
165
190
|
agentsFile,
|
|
166
191
|
METRO_CONFIG,
|
|
167
192
|
TYPECHECK,
|
|
193
|
+
TYPECHECK_ONE_COPY,
|
|
194
|
+
WITH_ENTRY_POINTS,
|
|
168
195
|
tsconfig,
|
|
196
|
+
typecheckTsconfig,
|
|
169
197
|
vitestConfig,
|
|
170
198
|
reuseRootRanges,
|
|
171
199
|
expoCompanions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-native/nx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@ng-native/migrate": "0.
|
|
40
|
+
"@ng-native/migrate": "0.4.0",
|
|
41
41
|
"@nx/devkit": "^23.0.0",
|
|
42
42
|
"minimatch": "^10.0.0",
|
|
43
43
|
"semver": "^7.7.0"
|
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
"tags": [
|
|
55
55
|
"scope:nx",
|
|
56
56
|
"layer:build"
|
|
57
|
+
],
|
|
58
|
+
"implicitDependencies": [
|
|
59
|
+
"@ng-native/template"
|
|
57
60
|
]
|
|
58
61
|
},
|
|
59
62
|
"license": "MIT",
|