@nx/vite 23.3.0-pr.37068.f95e11e → 23.3.0-rc.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/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  [![Semantic Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](<>)
13
13
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
14
14
  [![Join us on the Official Nx Discord Server](https://img.shields.io/discord/1143497901675401286?label=discord)](https://go.nx.dev/community)
15
- [![Nx Sandboxing](https://staging.nx.app/workspaces/62d013ea0852fe0a2df74438/sandbox-badge.svg)](https://nx.dev/docs/features/ci-features/sandboxing)
15
+ [![Hours saved](https://img.shields.io/endpoint?url=https%3A%2F%2Fstaging.nx.app%2Fnx-cloud%2Fbadge%2F62d013ea0852fe0a2df74438%2Fhours-saved.json)](https://nx.dev/nx-cloud)
16
16
 
17
17
  </div>
18
18
 
@@ -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
  /**
@@ -21,8 +21,11 @@ const nx_vite_build_coordination_plugin_1 = require("./nx-vite-build-coordinatio
21
21
  function nxViteTsPaths(options = {}) {
22
22
  (0, deprecation_1.warnNxViteTsPathsDeprecation)();
23
23
  let foundTsConfigPath;
24
+ let tsConfigPathsLoaded = false;
24
25
  let matchTsPathEsm;
26
+ let matchTsPathEsmExact;
25
27
  let matchTsPathFallback;
28
+ let matchTsPathFallbackExact;
26
29
  let tsConfigPathsEsm;
27
30
  let tsConfigPathsFallback;
28
31
  options.extensions ??= [
@@ -53,6 +56,7 @@ function nxViteTsPaths(options = {}) {
53
56
  async configResolved(config) {
54
57
  projectRoot = config.root;
55
58
  projectRootFromWorkspaceRoot = (0, node_path_1.relative)(devkit_1.workspaceRoot, projectRoot);
59
+ tsConfigPathsLoaded = false;
56
60
  foundTsConfigPath = getTsConfig(process.env.NX_TSCONFIG_PATH ??
57
61
  (0, node_path_1.join)(devkit_1.workspaceRoot, 'tmp', projectRootFromWorkspaceRoot, process.env.NX_TASK_TARGET_TARGET ?? 'build', 'tsconfig.generated.json'));
58
62
  if (!foundTsConfigPath)
@@ -89,20 +93,10 @@ function nxViteTsPaths(options = {}) {
89
93
  }
90
94
  }
91
95
  }
92
- const parsed = (0, tsconfig_paths_1.loadConfig)(foundTsConfigPath);
93
- logIt('first parsed tsconfig: ', parsed);
94
- if (parsed.resultType === 'failed') {
95
- throw new Error(`Failed loading tsconfig at ${foundTsConfigPath}`);
96
- }
97
- tsConfigPathsEsm = parsed;
98
- matchTsPathEsm = (0, tsconfig_paths_1.createMatchPath)((0, js_1.resolvePathsBaseUrl)(foundTsConfigPath), parsed.paths, options.mainFields);
99
- 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']);
105
- }
96
+ // Graph construction resolves every config and never a module, so the
97
+ // parsing waits for the first `resolveId` there.
98
+ if (!global.NX_GRAPH_CREATION)
99
+ loadTsConfigPaths();
106
100
  },
107
101
  resolveId(importPath) {
108
102
  // Let other resolvers handle this path.
@@ -114,20 +108,31 @@ function nxViteTsPaths(options = {}) {
114
108
  // by tsconfig path mapping (which would incorrectly use baseUrl).
115
109
  if (importPath.startsWith('/'))
116
110
  return null;
111
+ if (!tsConfigPathsLoaded)
112
+ loadTsConfigPaths();
117
113
  let resolvedFile;
118
114
  try {
119
- resolvedFile = matchTsPathEsm(importPath);
115
+ resolvedFile =
116
+ matchTsPathEsmExact(importPath) ??
117
+ loadFileFromExactAlias(tsConfigPathsEsm, importPath) ??
118
+ matchTsPathEsm(importPath);
120
119
  }
121
120
  catch (e) {
122
121
  logIt('Using fallback path matching.');
123
- resolvedFile = matchTsPathFallback?.(importPath);
122
+ resolvedFile =
123
+ matchTsPathFallbackExact?.(importPath) ??
124
+ loadFileFromExactAlias(tsConfigPathsFallback, importPath) ??
125
+ matchTsPathFallback?.(importPath);
124
126
  }
125
127
  if (!resolvedFile || !(0, node_fs_1.existsSync)(resolvedFile)) {
126
128
  if (tsConfigPathsEsm || tsConfigPathsFallback) {
127
129
  logIt(`Unable to resolve ${importPath} with tsconfig paths. Using fallback file matching.`);
130
+ // The tsconfig the project builds with need not extend the
131
+ // root-level one, so the second pass covers aliases only the
132
+ // root-level config declares.
128
133
  resolvedFile =
129
- loadFileFromPaths(tsConfigPathsEsm, importPath) ||
130
- loadFileFromPaths(tsConfigPathsFallback, importPath);
134
+ loadFileFromPathsWithLogging(tsConfigPathsEsm, importPath) ||
135
+ loadFileFromPathsWithLogging(tsConfigPathsFallback, importPath);
131
136
  }
132
137
  else {
133
138
  logIt(`Unable to resolve ${importPath} with tsconfig paths`);
@@ -154,6 +159,37 @@ function nxViteTsPaths(options = {}) {
154
159
  }
155
160
  },
156
161
  };
162
+ function loadTsConfigPaths() {
163
+ const parsed = (0, tsconfig_paths_1.loadConfig)(foundTsConfigPath);
164
+ logIt('first parsed tsconfig: ', parsed);
165
+ if (parsed.resultType === 'failed') {
166
+ throw new Error(`Failed loading tsconfig at ${foundTsConfigPath}`);
167
+ }
168
+ // `loadConfig` derives `absoluteBaseUrl` from the leaf tsconfig, but
169
+ // `paths` resolve against the config that declared them.
170
+ const pathsBaseUrl = (0, js_1.resolvePathsBaseUrl)(foundTsConfigPath);
171
+ tsConfigPathsEsm = { ...parsed, absoluteBaseUrl: pathsBaseUrl };
172
+ matchTsPathEsm = (0, tsconfig_paths_1.createMatchPath)(pathsBaseUrl, parsed.paths, options.mainFields);
173
+ matchTsPathEsmExact = createExactMatchPath(pathsBaseUrl, parsed.paths, options.mainFields);
174
+ const rootLevelTsConfig = getTsConfig((0, node_path_1.join)(devkit_1.workspaceRoot, 'tsconfig.base.json'));
175
+ // A workspace may have no root-level tsconfig at all. Passing no path to
176
+ // `loadConfig` makes it search upwards from the cwd instead, which finds
177
+ // an unrelated tsconfig whose directory is not this workspace.
178
+ if (rootLevelTsConfig) {
179
+ const rootLevelParsed = (0, tsconfig_paths_1.loadConfig)(rootLevelTsConfig);
180
+ logIt('fallback parsed tsconfig: ', rootLevelParsed);
181
+ if (rootLevelParsed.resultType === 'success') {
182
+ const rootLevelPathsBaseUrl = (0, js_1.resolvePathsBaseUrl)(rootLevelTsConfig);
183
+ tsConfigPathsFallback = {
184
+ ...rootLevelParsed,
185
+ absoluteBaseUrl: rootLevelPathsBaseUrl,
186
+ };
187
+ matchTsPathFallback = (0, tsconfig_paths_1.createMatchPath)(rootLevelPathsBaseUrl, rootLevelParsed.paths, ['main', 'module']);
188
+ matchTsPathFallbackExact = createExactMatchPath(rootLevelPathsBaseUrl, rootLevelParsed.paths, ['main', 'module']);
189
+ }
190
+ }
191
+ tsConfigPathsLoaded = true;
192
+ }
157
193
  function getTsConfig(preferredTsConfigPath) {
158
194
  const projectTsConfigPath = (0, options_utils_1.getProjectTsConfigPath)(projectRootFromWorkspaceRoot);
159
195
  return [
@@ -178,31 +214,42 @@ function nxViteTsPaths(options = {}) {
178
214
  console.debug('\n[Nx Vite TsPaths]', ...msg);
179
215
  }
180
216
  }
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
- }
217
+ /**
218
+ * Resolves an import that names an alias exactly, through that alias alone.
219
+ *
220
+ * The matcher above already covers a mapped path that names a file or a
221
+ * package entry, so this is what finds an `index` or supplies the extension.
222
+ * Restricting it to the one alias keeps it from answering with a wildcard,
223
+ * which is the last resort's job.
224
+ */
225
+ function loadFileFromExactAlias(tsconfig, importPath) {
226
+ // An own-property check, because an import named after an `Object`
227
+ // prototype member (`constructor`, `toString`) would otherwise read the
228
+ // inherited value and hand a non-array on to the resolver.
229
+ if (!tsconfig || !Object.hasOwn(tsconfig.paths, importPath)) {
230
+ return undefined;
205
231
  }
206
- return resolvedFile;
232
+ return (0, nx_tsconfig_paths_load_file_1.loadFileFromPaths)({ ...tsconfig, paths: { [importPath]: tsconfig.paths[importPath] } }, importPath, options.extensions);
207
233
  }
234
+ function loadFileFromPathsWithLogging(tsconfig, importPath) {
235
+ // The root-level tsconfig is optional: a workspace without one leaves
236
+ // `tsConfigPathsFallback` unset, and the import has to defer to Vite.
237
+ if (!tsconfig)
238
+ return undefined;
239
+ logIt(`Trying to resolve file from config in ${tsconfig.configFileAbsolutePath}`);
240
+ return (0, nx_tsconfig_paths_load_file_1.loadFileFromPaths)(tsconfig, importPath, options.extensions);
241
+ }
242
+ }
243
+ /**
244
+ * Matcher over the non-wildcard aliases alone, to run before the full one.
245
+ *
246
+ * `tsconfig-paths` ranks an alias by the length of the text before its `*`,
247
+ * counting one without a `*` as zero, so an exact alias sorts no higher than
248
+ * any wildcard. TypeScript matches an exact alias first.
249
+ */
250
+ function createExactMatchPath(absoluteBaseUrl, paths, mainFields) {
251
+ const exactPaths = Object.fromEntries(Object.entries(paths).filter(([alias]) => !alias.includes('*')));
252
+ // The match-all `*` entry would resolve any import against `baseUrl` before
253
+ // the wildcard aliases of the full matcher get their turn.
254
+ return (0, tsconfig_paths_1.createMatchPath)(absoluteBaseUrl, exactPaths, mainFields, false);
208
255
  }
@@ -95,24 +95,12 @@ async function buildViteTargets(configFilePath, projectRoot, options, tsConfigFi
95
95
  catch {
96
96
  // do nothing
97
97
  }
98
- // Workaround for race condition with ESM-only Vite plugins (e.g. @vitejs/plugin-vue@6+)
99
- // If vite.config.ts is compiled as CJS, then when both require('@vitejs/plugin-vue') and import('@vitejs/plugin-vue')
100
- // are pending in the same process, Node will throw an error:
101
- // Error [ERR_INTERNAL_ASSERTION]: Cannot require() ES Module @vitejs/plugin-vue/dist/index.js because it is not yet fully loaded.
102
- // This may be caused by a race condition if the module is simultaneously dynamically import()-ed via Promise.all().
103
- try {
104
- const importVuePlugin = () => new Function('return import("@vitejs/plugin-vue")')();
105
- await importVuePlugin();
106
- }
107
- catch {
108
- // Plugin not installed or not needed, ignore
109
- }
110
98
  const { resolveConfig } = await (0, executor_utils_1.loadViteDynamicImport)();
111
- const viteBuildConfig = await resolveConfig({
99
+ const viteBuildConfig = await (0, internal_1.retryOnRequireEsmRace)(() => resolveConfig({
112
100
  configFile: absoluteConfigFilePath,
113
101
  mode: 'development',
114
102
  root: projectRoot,
115
- }, 'build');
103
+ }, 'build'));
116
104
  const metadata = {};
117
105
  const { buildOutputs, isBuildable, hasServeConfig } = getOutputs(viteBuildConfig, projectRoot, context.workspaceRoot);
118
106
  const namedInputs = (0, internal_1.getNamedInputs)(projectRoot, context);
@@ -326,7 +314,7 @@ function normalizeOutputPath(outputPath, projectRoot, workspaceRoot, path) {
326
314
  }
327
315
  else {
328
316
  if ((0, node_path_1.isAbsolute)(outputPath)) {
329
- return `{workspaceRoot}/${(0, node_path_1.relative)(workspaceRoot, outputPath)}`;
317
+ return `{workspaceRoot}/${(0, devkit_1.normalizePath)((0, node_path_1.relative)(workspaceRoot, outputPath))}`;
330
318
  }
331
319
  else {
332
320
  if (outputPath.startsWith('..')) {
@@ -2,12 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureDependencies = ensureDependencies;
4
4
  const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/js/internal");
5
6
  const semver_1 = require("semver");
6
7
  const versions_1 = require("./versions");
7
8
  function ensureDependencies(host, schema) {
8
9
  const devDependencies = {};
9
10
  if (schema.uiFramework === 'react') {
10
11
  if (schema.compiler === 'swc') {
12
+ // @vitejs/plugin-react-swc depends on @swc/core.
13
+ (0, internal_1.acknowledgeSwcBuildScripts)(host);
11
14
  devDependencies['@vitejs/plugin-react-swc'] = versions_1.vitePluginReactSwcVersion;
12
15
  }
13
16
  else {
@@ -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.3.0-pr.37068.f95e11e",
3
+ "version": "23.3.0-rc.0",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -94,19 +94,19 @@
94
94
  "supportsOptionalMigrations": true
95
95
  },
96
96
  "dependencies": {
97
+ "@nx/devkit": "23.3.0-rc.0",
97
98
  "@phenomnomnominal/tsquery": "~6.2.0",
99
+ "@nx/js": "23.3.0-rc.0",
100
+ "@nx/vitest": "23.3.0-rc.0",
98
101
  "picomatch": "4.0.4",
99
102
  "tsconfig-paths": "^4.1.2",
100
103
  "semver": "^7.6.3",
101
104
  "tslib": "^2.3.0",
102
- "ajv": "^8.0.0",
103
- "@nx/devkit": "23.3.0-pr.37068.f95e11e",
104
- "@nx/js": "23.3.0-pr.37068.f95e11e",
105
- "@nx/vitest": "23.3.0-pr.37068.f95e11e"
105
+ "ajv": "^8.0.0"
106
106
  },
107
107
  "devDependencies": {
108
- "@nx/eslint": "23.3.0-pr.37068.f95e11e",
109
- "nx": "23.3.0-pr.37068.f95e11e"
108
+ "@nx/eslint": "23.3.0-rc.0",
109
+ "nx": "23.3.0-rc.0"
110
110
  },
111
111
  "peerDependencies": {
112
112
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -0,0 +1,18 @@
1
+ <p style="text-align: center;">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-dark.svg">
4
+ <img alt="Nx - Smart Monorepos · Fast Builds" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-light.svg" width="100%">
5
+ </picture>
6
+ </p>
7
+
8
+ {{links}}
9
+
10
+ <hr>
11
+
12
+ # Nx: Smart Monorepos · Fast Builds
13
+
14
+ Get to green PRs in half the time. Nx optimizes your builds, scales your CI, and fixes failed PRs. Built for developers and AI agents.
15
+
16
+ This package is a [Vite plugin for Nx](https://nx.dev/nx-api/vite).
17
+
18
+ {{content}}