Skip to content

Commit 05701c6

Browse files
authored
refactor(resolve): prefer exports condition over svelte field (#747)
* refactor(resolve): prefer exports condition over svelte field * chore: add changeset * refactor: use vite mainFields resolve instead of our custom implementation, log packages while scanning deps instead
1 parent 10ec2a4 commit 05701c6

File tree

10 files changed

+47
-186
lines changed

10 files changed

+47
-186
lines changed

.changeset/ten-tips-sip.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/vite-plugin-svelte': major
3+
---
4+
5+
breaking: prefer svelte exports condition over package.json svelte field

docs/faq.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,12 @@ module.exports = {
209209

210210
<!-- the following header generates an anchor that is used in logging, do not modify!-->
211211

212-
### conflicts in svelte resolve
212+
### missing exports condition
213213

214214
| If you see a warning logged for this when using a Svelte library, please tell the library maintainers.
215215

216-
In the past, Svelte recommended using the custom `svelte` field in `package.json` to allow libraries to point at `.svelte` source files.
217-
This field requires a custom implementation to resolve, so you have to use a bundler plugin and this plugin needs to implement resolving.
218-
Since then, Node has added support for [conditional exports](https://nodejs.org/api/packages.html#conditional-exports), which have more generic support in bundlers and Node itself. So to increase the compatibility with the wider ecosystem and reduce the implementation needs for current and future bundler plugins, it is recommended that packages use the `svelte` exports condition.
216+
Using the `svelte` field in `package.json` to point at `.svelte` source files is **deprecated** and you must use a `svelte` [export condition](https://nodejs.org/api/packages.html#conditional-exports).
217+
vite-plugin-svelte 3 still resolves it as a fallback, but in a future major release this is going to be removed and without exports condition resolving the library is going to fail.
219218

220219
Example:
221220

packages/e2e-tests/_test_dependencies/svelte-hybrid/package.json

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
"index.js",
1111
"cli.js"
1212
],
13+
"exports": {
14+
".": {
15+
"import": {
16+
"svelte": "./index.js"
17+
}
18+
}
19+
},
1320
"dependencies": {
1421
"@types/node": "^18.17.15",
1522
"e2e-test-dep-cjs-only": "file:../cjs-only"

packages/e2e-tests/_test_dependencies/svelte-nested/package.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
"src"
1010
],
1111
"exports": {
12-
"./package.json": "./package.json"
12+
"./package.json": "./package.json",
13+
".": {
14+
"import": {
15+
"svelte": "./src/index.js"
16+
}
17+
}
1318
},
1419
"dependencies": {
1520
"e2e-test-dep-svelte-simple": "file:../svelte-simple",

packages/e2e-tests/_test_dependencies/svelte-simple/package.json

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
"src",
99
"index.js"
1010
],
11+
"exports": {
12+
".": {
13+
"import": {
14+
"svelte": "./index.js"
15+
}
16+
}
17+
},
1118
"dependencies": {
1219
"e2e-test-dep-cjs-only": "file:../cjs-only"
1320
},

packages/vite-plugin-svelte/src/index.js

-70
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import fs from 'node:fs';
22

33
import { svelteInspector } from '@sveltejs/vite-plugin-svelte-inspector';
44

5-
import { isDepExcluded } from 'vitefu';
65
import { handleHotUpdate } from './handle-hot-update.js';
76
import { log, logCompilerWarnings } from './utils/log.js';
87
import { createCompileSvelte } from './utils/compile.js';
@@ -16,13 +15,10 @@ import {
1615
} from './utils/options.js';
1716

1817
import { ensureWatchedFile, setupWatchers } from './utils/watch.js';
19-
import { resolveViaPackageJsonSvelte } from './utils/resolve.js';
20-
2118
import { toRollupError } from './utils/error.js';
2219
import { saveSvelteMetadata } from './utils/optimizer.js';
2320
import { VitePluginSvelteCache } from './utils/vite-plugin-svelte-cache.js';
2421
import { loadRaw } from './utils/load-raw.js';
25-
import { FAQ_LINK_CONFLICTS_IN_SVELTE_RESOLVE } from './utils/constants.js';
2622

2723
/** @type {import('./index.d.ts').svelte} */
2824
export function svelte(inlineOptions) {
@@ -38,13 +34,9 @@ export function svelte(inlineOptions) {
3834
let options;
3935
/** @type {import('vite').ResolvedConfig} */
4036
let viteConfig;
41-
4237
/** @type {import('./types/compile.d.ts').CompileSvelte} */
4338
let compileSvelte;
4439
/* eslint-enable no-unused-vars */
45-
46-
/** @type {Set<string>} */
47-
let packagesWithResolveWarnings;
4840
/** @type {import('./types/plugin-api.d.ts').PluginAPI} */
4941
const api = {};
5042
/** @type {import('vite').Plugin[]} */
@@ -81,7 +73,6 @@ export function svelte(inlineOptions) {
8173
},
8274

8375
async buildStart() {
84-
packagesWithResolveWarnings = new Set();
8576
if (!options.prebundleSvelteLibraries) return;
8677
const isSvelteMetadataChanged = await saveSvelteMetadata(viteConfig.cacheDir, options);
8778
if (isSvelteMetadataChanged) {
@@ -138,57 +129,6 @@ export function svelte(inlineOptions) {
138129
return svelteRequest.cssId;
139130
}
140131
}
141-
142-
//@ts-expect-error scan
143-
const scan = !!opts?.scan; // scanner phase of optimizeDeps
144-
const isPrebundled =
145-
options.prebundleSvelteLibraries &&
146-
viteConfig.optimizeDeps?.disabled !== true &&
147-
viteConfig.optimizeDeps?.disabled !== (options.isBuild ? 'build' : 'dev') &&
148-
!isDepExcluded(importee, viteConfig.optimizeDeps?.exclude ?? []);
149-
// for prebundled libraries we let vite resolve the prebundling result
150-
// for ssr, during scanning and non-prebundled, we do it
151-
if (ssr || scan || !isPrebundled) {
152-
try {
153-
const isFirstResolve = !cache.hasResolvedSvelteField(importee, importer);
154-
const resolved = await resolveViaPackageJsonSvelte(importee, importer, cache);
155-
if (isFirstResolve && resolved) {
156-
const packageInfo = await cache.getPackageInfo(resolved);
157-
const packageVersion = `${packageInfo.name}@${packageInfo.version}`;
158-
log.debug.once(
159-
`resolveId resolved ${importee} to ${resolved} via package.json svelte field of ${packageVersion}`
160-
);
161-
162-
try {
163-
const viteResolved = (
164-
await this.resolve(importee, importer, { ...opts, skipSelf: true })
165-
)?.id;
166-
if (resolved !== viteResolved) {
167-
packagesWithResolveWarnings.add(packageVersion);
168-
log.debug.enabled &&
169-
log.debug.once(
170-
`resolve difference for ${packageVersion} ${importee} - svelte: "${resolved}", vite: "${viteResolved}"`
171-
);
172-
}
173-
} catch (e) {
174-
packagesWithResolveWarnings.add(packageVersion);
175-
log.debug.enabled &&
176-
log.debug.once(
177-
`resolve error for ${packageVersion} ${importee} - svelte: "${resolved}", vite: ERROR`,
178-
e
179-
);
180-
}
181-
}
182-
return resolved;
183-
} catch (e) {
184-
log.debug.once(
185-
`error trying to resolve ${importee} from ${importer} via package.json svelte field `,
186-
e
187-
);
188-
// this error most likely happens due to non-svelte related importee/importers so swallow it here
189-
// in case it really way a svelte library, users will notice anyway. (lib not working due to failed resolve)
190-
}
191-
}
192132
},
193133

194134
async transform(code, id, opts) {
@@ -239,16 +179,6 @@ export function svelte(inlineOptions) {
239179
},
240180
async buildEnd() {
241181
await options.stats?.finishAll();
242-
if (
243-
!options.experimental?.disableSvelteResolveWarnings &&
244-
packagesWithResolveWarnings?.size > 0
245-
) {
246-
log.warn(
247-
`WARNING: The following packages use a svelte resolve configuration in package.json that has conflicting results and is going to cause problems future.\n\n${[
248-
...packagesWithResolveWarnings
249-
].join('\n')}\n\nPlease see ${FAQ_LINK_CONFLICTS_IN_SVELTE_RESOLVE} for details.`
250-
);
251-
}
252182
}
253183
},
254184
svelteInspector()

packages/vite-plugin-svelte/src/utils/constants.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ export const SVELTE_HMR_IMPORTS = [
2222

2323
export const SVELTE_EXPORT_CONDITIONS = ['svelte'];
2424

25-
export const FAQ_LINK_CONFLICTS_IN_SVELTE_RESOLVE =
26-
'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#conflicts-in-svelte-resolve';
25+
export const FAQ_LINK_MISSING_EXPORTS_CONDITION =
26+
'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#missing-exports-condition';

packages/vite-plugin-svelte/src/utils/options.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { normalizePath } from 'vite';
33
import { isDebugNamespaceEnabled, log } from './log.js';
44
import { loadSvelteConfig } from './load-svelte-config.js';
55
import {
6+
FAQ_LINK_MISSING_EXPORTS_CONDITION,
67
SVELTE_EXPORT_CONDITIONS,
78
SVELTE_HMR_IMPORTS,
89
SVELTE_IMPORTS,
@@ -481,6 +482,7 @@ function validateViteConfig(extraViteConfig, config, options) {
481482
*/
482483
async function buildExtraConfigForDependencies(options, config) {
483484
// extra handling for svelte dependencies in the project
485+
const packagesWithoutSvelteExportsCondition = new Set();
484486
const depsConfig = await crawlFrameworkPkgs({
485487
root: options.root,
486488
isBuild: options.isBuild,
@@ -496,7 +498,11 @@ async function buildExtraConfigForDependencies(options, config) {
496498
return value;
497499
});
498500
}
499-
return hasSvelteCondition || !!pkgJson.svelte;
501+
const hasSvelteField = !!pkgJson.svelte;
502+
if (hasSvelteField && !hasSvelteCondition) {
503+
packagesWithoutSvelteExportsCondition.add(`${pkgJson.name}@${pkgJson.version}`);
504+
}
505+
return hasSvelteCondition || hasSvelteField;
500506
},
501507
isSemiFrameworkPkgByJson(pkgJson) {
502508
return !!pkgJson.dependencies?.svelte || !!pkgJson.peerDependencies?.svelte;
@@ -510,7 +516,16 @@ async function buildExtraConfigForDependencies(options, config) {
510516
}
511517
}
512518
});
513-
519+
if (
520+
!options.experimental?.disableSvelteResolveWarnings &&
521+
packagesWithoutSvelteExportsCondition?.size > 0
522+
) {
523+
log.warn(
524+
`WARNING: The following packages have a svelte field in their package.json but no exports condition for svelte.\n\n${[
525+
...packagesWithoutSvelteExportsCondition
526+
].join('\n')}\n\nPlease see ${FAQ_LINK_MISSING_EXPORTS_CONDITION} for details.`
527+
);
528+
}
514529
log.debug('extra config for dependencies generated by vitefu', depsConfig);
515530

516531
if (options.prebundleSvelteLibraries) {

packages/vite-plugin-svelte/src/utils/resolve.js

-66
This file was deleted.

packages/vite-plugin-svelte/src/utils/vite-plugin-svelte-cache.js

-41
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ export class VitePluginSvelteCache {
2424
#dependencies = new Map();
2525
/** @type {Map<string, Set<string>>} */
2626
#dependants = new Map();
27-
/** @type {Map<string, string>} */
28-
#resolvedSvelteFields = new Map();
2927
/** @type {Map<string, any>} */
3028
#errors = new Map();
3129
/** @type {PackageInfo[]} */
@@ -168,45 +166,6 @@ export class VitePluginSvelteCache {
168166
return dependants ? [...dependants] : [];
169167
}
170168

171-
/**
172-
* @param {string} name
173-
* @param {string} [importer]
174-
* @returns {string|void}
175-
*/
176-
getResolvedSvelteField(name, importer) {
177-
return this.#resolvedSvelteFields.get(this.#getResolvedSvelteFieldKey(name, importer));
178-
}
179-
180-
/**
181-
* @param {string} name
182-
* @param {string} [importer]
183-
* @returns {boolean}
184-
*/
185-
hasResolvedSvelteField(name, importer) {
186-
return this.#resolvedSvelteFields.has(this.#getResolvedSvelteFieldKey(name, importer));
187-
}
188-
/**
189-
*
190-
* @param {string} importee
191-
* @param {string | undefined} importer
192-
* @param {string} resolvedSvelte
193-
*/
194-
setResolvedSvelteField(importee, importer, resolvedSvelte) {
195-
this.#resolvedSvelteFields.set(
196-
this.#getResolvedSvelteFieldKey(importee, importer),
197-
resolvedSvelte
198-
);
199-
}
200-
201-
/**
202-
* @param {string} importee
203-
* @param {string | undefined} importer
204-
* @returns {string}
205-
*/
206-
#getResolvedSvelteFieldKey(importee, importer) {
207-
return importer ? `${importer} > ${importee}` : importee;
208-
}
209-
210169
/**
211170
* @param {string} file
212171
* @returns {Promise<PackageInfo>}

0 commit comments

Comments
 (0)