@nx/vite 23.2.1 → 23.3.0-beta.1

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.
@@ -15,7 +15,7 @@ export interface nxViteTsPathsOptions {
15
15
  mainFields?: (string | string[])[];
16
16
  /**
17
17
  * extensions to check when resolving files when package.json resolution fails
18
- * @default ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs', '.cjs']
18
+ * @default ['.ts', '.tsx', '.js', '.jsx', '.json', '.mts', '.mjs', '.cts', '.cjs', '.css', '.scss', '.less']
19
19
  **/
20
20
  extensions?: string[];
21
21
  /**
@@ -8,7 +8,7 @@ const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = require("node:path");
9
9
  const tsconfig_paths_1 = require("tsconfig-paths");
10
10
  const deprecation_1 = require("../src/utils/deprecation");
11
- const nx_tsconfig_paths_find_file_1 = require("../src/utils/nx-tsconfig-paths-find-file");
11
+ const nx_tsconfig_paths_load_file_1 = require("../src/utils/nx-tsconfig-paths-load-file");
12
12
  const options_utils_1 = require("../src/utils/options-utils");
13
13
  const nx_vite_build_coordination_plugin_1 = require("./nx-vite-build-coordination.plugin");
14
14
  /**
@@ -22,7 +22,9 @@ function nxViteTsPaths(options = {}) {
22
22
  (0, deprecation_1.warnNxViteTsPathsDeprecation)();
23
23
  let foundTsConfigPath;
24
24
  let matchTsPathEsm;
25
+ let matchTsPathEsmExact;
25
26
  let matchTsPathFallback;
27
+ let matchTsPathFallbackExact;
26
28
  let tsConfigPathsEsm;
27
29
  let tsConfigPathsFallback;
28
30
  options.extensions ??= [
@@ -94,14 +96,28 @@ function nxViteTsPaths(options = {}) {
94
96
  if (parsed.resultType === 'failed') {
95
97
  throw new Error(`Failed loading tsconfig at ${foundTsConfigPath}`);
96
98
  }
97
- tsConfigPathsEsm = parsed;
98
- matchTsPathEsm = (0, tsconfig_paths_1.createMatchPath)((0, js_1.resolvePathsBaseUrl)(foundTsConfigPath), parsed.paths, options.mainFields);
99
+ // `loadConfig` derives `absoluteBaseUrl` from the leaf tsconfig, but
100
+ // `paths` resolve against the config that declared them.
101
+ const pathsBaseUrl = (0, js_1.resolvePathsBaseUrl)(foundTsConfigPath);
102
+ tsConfigPathsEsm = { ...parsed, absoluteBaseUrl: pathsBaseUrl };
103
+ matchTsPathEsm = (0, tsconfig_paths_1.createMatchPath)(pathsBaseUrl, parsed.paths, options.mainFields);
104
+ matchTsPathEsmExact = createExactMatchPath(pathsBaseUrl, parsed.paths, options.mainFields);
99
105
  const rootLevelTsConfig = getTsConfig((0, node_path_1.join)(devkit_1.workspaceRoot, 'tsconfig.base.json'));
100
- const rootLevelParsed = (0, tsconfig_paths_1.loadConfig)(rootLevelTsConfig);
101
- logIt('fallback parsed tsconfig: ', rootLevelParsed);
102
- if (rootLevelParsed.resultType === 'success') {
103
- tsConfigPathsFallback = rootLevelParsed;
104
- matchTsPathFallback = (0, tsconfig_paths_1.createMatchPath)((0, js_1.resolvePathsBaseUrl)(rootLevelTsConfig), rootLevelParsed.paths, ['main', 'module']);
106
+ // A workspace may have no root-level tsconfig at all. Passing no path to
107
+ // `loadConfig` makes it search upwards from the cwd instead, which finds
108
+ // an unrelated tsconfig whose directory is not this workspace.
109
+ if (rootLevelTsConfig) {
110
+ const rootLevelParsed = (0, tsconfig_paths_1.loadConfig)(rootLevelTsConfig);
111
+ logIt('fallback parsed tsconfig: ', rootLevelParsed);
112
+ if (rootLevelParsed.resultType === 'success') {
113
+ const rootLevelPathsBaseUrl = (0, js_1.resolvePathsBaseUrl)(rootLevelTsConfig);
114
+ tsConfigPathsFallback = {
115
+ ...rootLevelParsed,
116
+ absoluteBaseUrl: rootLevelPathsBaseUrl,
117
+ };
118
+ matchTsPathFallback = (0, tsconfig_paths_1.createMatchPath)(rootLevelPathsBaseUrl, rootLevelParsed.paths, ['main', 'module']);
119
+ matchTsPathFallbackExact = createExactMatchPath(rootLevelPathsBaseUrl, rootLevelParsed.paths, ['main', 'module']);
120
+ }
105
121
  }
106
122
  },
107
123
  resolveId(importPath) {
@@ -116,18 +132,27 @@ function nxViteTsPaths(options = {}) {
116
132
  return null;
117
133
  let resolvedFile;
118
134
  try {
119
- resolvedFile = matchTsPathEsm(importPath);
135
+ resolvedFile =
136
+ matchTsPathEsmExact(importPath) ??
137
+ loadFileFromExactAlias(tsConfigPathsEsm, importPath) ??
138
+ matchTsPathEsm(importPath);
120
139
  }
121
140
  catch (e) {
122
141
  logIt('Using fallback path matching.');
123
- resolvedFile = matchTsPathFallback?.(importPath);
142
+ resolvedFile =
143
+ matchTsPathFallbackExact?.(importPath) ??
144
+ loadFileFromExactAlias(tsConfigPathsFallback, importPath) ??
145
+ matchTsPathFallback?.(importPath);
124
146
  }
125
147
  if (!resolvedFile || !(0, node_fs_1.existsSync)(resolvedFile)) {
126
148
  if (tsConfigPathsEsm || tsConfigPathsFallback) {
127
149
  logIt(`Unable to resolve ${importPath} with tsconfig paths. Using fallback file matching.`);
150
+ // The tsconfig the project builds with need not extend the
151
+ // root-level one, so the second pass covers aliases only the
152
+ // root-level config declares.
128
153
  resolvedFile =
129
- loadFileFromPaths(tsConfigPathsEsm, importPath) ||
130
- loadFileFromPaths(tsConfigPathsFallback, importPath);
154
+ loadFileFromPathsWithLogging(tsConfigPathsEsm, importPath) ||
155
+ loadFileFromPathsWithLogging(tsConfigPathsFallback, importPath);
131
156
  }
132
157
  else {
133
158
  logIt(`Unable to resolve ${importPath} with tsconfig paths`);
@@ -178,31 +203,42 @@ function nxViteTsPaths(options = {}) {
178
203
  console.debug('\n[Nx Vite TsPaths]', ...msg);
179
204
  }
180
205
  }
181
- function loadFileFromPaths(tsconfig, importPath) {
182
- logIt(`Trying to resolve file from config in ${tsconfig.configFileAbsolutePath}`);
183
- let resolvedFile;
184
- for (const alias in tsconfig.paths) {
185
- const paths = tsconfig.paths[alias];
186
- const normalizedImport = alias.replace(/\/\*$/, '');
187
- if (importPath === normalizedImport ||
188
- importPath.startsWith(normalizedImport + '/')) {
189
- for (const path of paths) {
190
- const joinedPath = (0, devkit_1.joinPathFragments)(tsconfig.absoluteBaseUrl, path.replace(/\/\*$/, ''));
191
- resolvedFile = (0, nx_tsconfig_paths_find_file_1.findFile)(importPath.replace(normalizedImport, joinedPath), options.extensions);
192
- if (resolvedFile === undefined &&
193
- options.extensions.some((ext) => importPath.endsWith(ext))) {
194
- const foundExtension = options.extensions.find((ext) => importPath.endsWith(ext));
195
- const pathWithoutExtension = importPath
196
- .replace(normalizedImport, joinedPath)
197
- .slice(0, -foundExtension.length);
198
- resolvedFile = (0, nx_tsconfig_paths_find_file_1.findFile)(pathWithoutExtension, options.extensions);
199
- }
200
- if (resolvedFile !== undefined) {
201
- return resolvedFile;
202
- }
203
- }
204
- }
206
+ /**
207
+ * Resolves an import that names an alias exactly, through that alias alone.
208
+ *
209
+ * The matcher above already covers a mapped path that names a file or a
210
+ * package entry, so this is what finds an `index` or supplies the extension.
211
+ * Restricting it to the one alias keeps it from answering with a wildcard,
212
+ * which is the last resort's job.
213
+ */
214
+ function loadFileFromExactAlias(tsconfig, importPath) {
215
+ // An own-property check, because an import named after an `Object`
216
+ // prototype member (`constructor`, `toString`) would otherwise read the
217
+ // inherited value and hand a non-array on to the resolver.
218
+ if (!tsconfig || !Object.hasOwn(tsconfig.paths, importPath)) {
219
+ return undefined;
205
220
  }
206
- return resolvedFile;
221
+ return (0, nx_tsconfig_paths_load_file_1.loadFileFromPaths)({ ...tsconfig, paths: { [importPath]: tsconfig.paths[importPath] } }, importPath, options.extensions);
207
222
  }
223
+ function loadFileFromPathsWithLogging(tsconfig, importPath) {
224
+ // The root-level tsconfig is optional: a workspace without one leaves
225
+ // `tsConfigPathsFallback` unset, and the import has to defer to Vite.
226
+ if (!tsconfig)
227
+ return undefined;
228
+ logIt(`Trying to resolve file from config in ${tsconfig.configFileAbsolutePath}`);
229
+ return (0, nx_tsconfig_paths_load_file_1.loadFileFromPaths)(tsconfig, importPath, options.extensions);
230
+ }
231
+ }
232
+ /**
233
+ * Matcher over the non-wildcard aliases alone, to run before the full one.
234
+ *
235
+ * `tsconfig-paths` ranks an alias by the length of the text before its `*`,
236
+ * counting one without a `*` as zero, so an exact alias sorts no higher than
237
+ * any wildcard. TypeScript matches an exact alias first.
238
+ */
239
+ function createExactMatchPath(absoluteBaseUrl, paths, mainFields) {
240
+ const exactPaths = Object.fromEntries(Object.entries(paths).filter(([alias]) => !alias.includes('*')));
241
+ // The match-all `*` entry would resolve any import against `baseUrl` before
242
+ // the wildcard aliases of the full matcher get their turn.
243
+ return (0, tsconfig_paths_1.createMatchPath)(absoluteBaseUrl, exactPaths, mainFields, false);
208
244
  }
@@ -127,7 +127,7 @@ async function buildViteTargets(configFilePath, projectRoot, options, tsConfigFi
127
127
  targets[options.buildTargetName] = await buildTarget(options.buildTargetName, namedInputs, buildOutputs, projectRoot, isUsingTsSolutionSetup, pmc);
128
128
  // If running in library mode, then there is nothing to serve.
129
129
  if (!viteBuildConfig.build?.lib || hasServeConfig) {
130
- const devTarget = serveTarget(projectRoot, isUsingTsSolutionSetup, pmc);
130
+ const devTarget = serveTarget(projectRoot, isUsingTsSolutionSetup, pmc, namedInputs);
131
131
  targets[options.serveTargetName] = {
132
132
  ...devTarget,
133
133
  metadata: {
@@ -136,8 +136,8 @@ async function buildViteTargets(configFilePath, projectRoot, options, tsConfigFi
136
136
  },
137
137
  };
138
138
  targets[options.devTargetName] = devTarget;
139
- targets[options.previewTargetName] = previewTarget(projectRoot, options.buildTargetName, pmc);
140
- targets[options.serveStaticTargetName] = serveStaticTarget(options, isUsingTsSolutionSetup);
139
+ targets[options.previewTargetName] = previewTarget(projectRoot, options.buildTargetName, pmc, namedInputs);
140
+ targets[options.serveStaticTargetName] = serveStaticTarget(options, isUsingTsSolutionSetup, namedInputs);
141
141
  }
142
142
  }
143
143
  if (tsConfigFiles.length) {
@@ -200,14 +200,7 @@ async function buildTarget(buildTargetName, namedInputs, outputs, projectRoot, i
200
200
  options: { cwd: (0, devkit_1.joinPathFragments)(projectRoot) },
201
201
  cache: true,
202
202
  dependsOn: [`^${buildTargetName}`],
203
- inputs: [
204
- ...('production' in namedInputs
205
- ? ['production', '^production']
206
- : ['default', '^default']),
207
- {
208
- externalDependencies: ['vite'],
209
- },
210
- ],
203
+ inputs: buildInputs(namedInputs),
211
204
  outputs,
212
205
  metadata: {
213
206
  technologies: ['vite'],
@@ -228,9 +221,20 @@ async function buildTarget(buildTargetName, namedInputs, outputs, projectRoot, i
228
221
  }
229
222
  return buildTarget;
230
223
  }
231
- function serveTarget(projectRoot, isUsingTsSolutionSetup, pmc) {
224
+ function buildInputs(namedInputs) {
225
+ return [
226
+ ...('production' in namedInputs
227
+ ? ['production', '^production']
228
+ : ['default', '^default']),
229
+ {
230
+ externalDependencies: ['vite'],
231
+ },
232
+ ];
233
+ }
234
+ function serveTarget(projectRoot, isUsingTsSolutionSetup, pmc, namedInputs) {
232
235
  const targetConfig = {
233
236
  continuous: true,
237
+ inputs: buildInputs(namedInputs),
234
238
  command: `vite`,
235
239
  options: {
236
240
  cwd: (0, devkit_1.joinPathFragments)(projectRoot),
@@ -253,9 +257,10 @@ function serveTarget(projectRoot, isUsingTsSolutionSetup, pmc) {
253
257
  }
254
258
  return targetConfig;
255
259
  }
256
- function previewTarget(projectRoot, buildTargetName, pmc) {
260
+ function previewTarget(projectRoot, buildTargetName, pmc, namedInputs) {
257
261
  const targetConfig = {
258
262
  continuous: true,
263
+ inputs: buildInputs(namedInputs),
259
264
  command: `vite preview`,
260
265
  dependsOn: [buildTargetName],
261
266
  options: {
@@ -276,9 +281,10 @@ function previewTarget(projectRoot, buildTargetName, pmc) {
276
281
  };
277
282
  return targetConfig;
278
283
  }
279
- function serveStaticTarget(options, isUsingTsSolutionSetup) {
284
+ function serveStaticTarget(options, isUsingTsSolutionSetup, namedInputs) {
280
285
  const targetConfig = {
281
286
  continuous: true,
287
+ inputs: buildInputs(namedInputs),
282
288
  executor: '@nx/web:file-server',
283
289
  options: {
284
290
  buildTarget: `${options.buildTargetName}`,
@@ -0,0 +1,12 @@
1
+ import { existsSync } from 'node:fs';
2
+ import type { ConfigLoaderSuccessResult } from 'tsconfig-paths';
3
+ /**
4
+ * Fallback resolver used when `tsconfig-paths` produces nothing that exists on
5
+ * disk, whether because no alias matched or because the match pointed at a
6
+ * missing file.
7
+ *
8
+ * A wildcard alias captures the suffix of the import and substitutes it into
9
+ * the `*` of each mapped path. The `*` is not always trailing, so the suffix
10
+ * cannot simply be appended.
11
+ */
12
+ export declare function loadFileFromPaths(tsconfig: ConfigLoaderSuccessResult, importPath: string, extensions: string[], existsSyncImpl?: typeof existsSync): string;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadFileFromPaths = loadFileFromPaths;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const nx_tsconfig_paths_find_file_1 = require("./nx-tsconfig-paths-find-file");
7
+ /**
8
+ * Fallback resolver used when `tsconfig-paths` produces nothing that exists on
9
+ * disk, whether because no alias matched or because the match pointed at a
10
+ * missing file.
11
+ *
12
+ * A wildcard alias captures the suffix of the import and substitutes it into
13
+ * the `*` of each mapped path. The `*` is not always trailing, so the suffix
14
+ * cannot simply be appended.
15
+ */
16
+ function loadFileFromPaths(tsconfig, importPath, extensions, existsSyncImpl = node_fs_1.existsSync) {
17
+ let resolvedFile;
18
+ for (const alias of sortAliasesBySpecificity(Object.keys(tsconfig.paths), importPath)) {
19
+ const paths = tsconfig.paths[alias];
20
+ const isWildcard = alias.endsWith('/*');
21
+ const normalizedImport = alias.replace(/\/\*$/, '');
22
+ if (importPath === normalizedImport ||
23
+ importPath.startsWith(normalizedImport + '/')) {
24
+ const suffix = importPath.slice(normalizedImport.length + 1);
25
+ for (const path of paths) {
26
+ // The replacements go through a function because a string replacement
27
+ // would expand `$&` and friends as substitution patterns.
28
+ const joinedPath = (0, node_path_1.join)(tsconfig.absoluteBaseUrl, isWildcard
29
+ ? path.replace('*', () => suffix)
30
+ : path.replace(/\/\*$/, ''));
31
+ const candidate = isWildcard
32
+ ? joinedPath
33
+ : importPath.replace(normalizedImport, () => joinedPath);
34
+ resolvedFile = (0, nx_tsconfig_paths_find_file_1.findFile)(candidate, extensions, existsSyncImpl);
35
+ // The candidate ends with the import's own tail only when the wildcard
36
+ // is last. Anything the mapped path appends after the `*`
37
+ // (`packages/foo/*.ts`) is the tail instead, so dropping an extension
38
+ // from it would resolve a sibling the mapping never pointed at.
39
+ const endsWithImportTail = !isWildcard || path.endsWith('*');
40
+ if (resolvedFile === undefined && endsWithImportTail) {
41
+ const foundExtension = extensions.find((ext) => importPath.endsWith(ext));
42
+ if (foundExtension) {
43
+ resolvedFile = (0, nx_tsconfig_paths_find_file_1.findFile)(candidate.slice(0, -foundExtension.length), extensions, existsSyncImpl);
44
+ }
45
+ }
46
+ if (resolvedFile !== undefined) {
47
+ return resolvedFile;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ return resolvedFile;
53
+ }
54
+ /**
55
+ * TypeScript picks between competing aliases by specificity, never by
56
+ * declaration order: an exact hit on a non-wildcard alias first, then the
57
+ * longest wildcard prefix.
58
+ */
59
+ function sortAliasesBySpecificity(aliases, importPath) {
60
+ const isWildcard = (alias) => alias.endsWith('/*');
61
+ const rank = (alias) => isWildcard(alias) ? 1 : importPath === alias ? 2 : 0;
62
+ const prefixLength = (alias) => alias.replace(/\/\*$/, '').length;
63
+ return [...aliases].sort((a, b) => {
64
+ const byRank = rank(b) - rank(a);
65
+ if (byRank !== 0)
66
+ return byRank;
67
+ // Prefix length only separates wildcards. Reordering two aliases
68
+ // TypeScript matches neither way would change resolution for no gain.
69
+ return isWildcard(a) ? prefixLength(b) - prefixLength(a) : 0;
70
+ });
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/vite",
3
- "version": "23.2.1",
3
+ "version": "23.3.0-beta.1",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -100,13 +100,13 @@
100
100
  "semver": "^7.6.3",
101
101
  "tslib": "^2.3.0",
102
102
  "ajv": "^8.0.0",
103
- "@nx/devkit": "23.2.1",
104
- "@nx/js": "23.2.1",
105
- "@nx/vitest": "23.2.1"
103
+ "@nx/devkit": "23.3.0-beta.1",
104
+ "@nx/js": "23.3.0-beta.1",
105
+ "@nx/vitest": "23.3.0-beta.1"
106
106
  },
107
107
  "devDependencies": {
108
- "@nx/eslint": "23.2.1",
109
- "nx": "23.2.1"
108
+ "@nx/eslint": "23.3.0-beta.1",
109
+ "nx": "23.3.0-beta.1"
110
110
  },
111
111
  "peerDependencies": {
112
112
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"