Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: log warning if svelte field causes difference in resolve #510

Merged
merged 17 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-horses-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/vite-plugin-svelte': patch
---

log deprecation warnings for packages that use the "svelte" field to resolve svelte files differently than standard vite resolve
dominikg marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,10 @@ export default {
- **Default:** `false`

disable svelte compile statistics.

### disableSvelteResolveWarnings

- **Type** `boolean`
- **Default:** `false`

disable svelte resolve warnings
dominikg marked this conversation as resolved.
Show resolved Hide resolved
30 changes: 30 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,33 @@ module.exports = {
+ }
}
```

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

### deprecated "svelte" field
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per @dummdidumm's point, I'm not sure we need to actually deprecate the svelte field. we just need to stop resolving it before exports and resolve it after instead. that would make the warning trigger less frequently which would be nicer for users

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, I just looked at the code and I think it works assuming the svelte field will be left in place and only changes the order. if we really did want to get rid of it entirely we'd probably have to change the code, but I'd just suggest changing the title here and note below saying it will be removed and instead say the resolve order will change

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we can recover from a resolve error unless we always call this.resolve in a try/catch not something i want to do long term.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also having 2 accepted ways to do this doesn't feel great. it makes writing new bundler plugins even more challanging. The only point i see in this whole excercise is that at the end of it the svelte field is no longer used at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already two ways to resolve JS files with main and exports, so it's just the analogous thing. As long as we prefer exports we can rip out all the code in vite-plugin-svelte and just defer to Vite

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bluwy iirc you changed the behavior of exports + mainfields lately. Is the above still true? If yes we should be able to remove the custom code for resolveViaPackageJsonSvelte even today.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we change the title from deprecated "svelte" field to something more neutral like prefer "svelte" export condition over "svelte" field?

kept the svelte field as neutral instead of remove. We should not encourage them to remove it for now as that could break older tooling that still relies on it

I agree with what you said in that comment and so the word "deprecated" seems too strong to me

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If exports is define, Vite should ignore mainFields (except browser) completely now. Vite also doesn't have a restriction on what the extensions for mainFields could be, so exporting a Svelte component is fine too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dominikg any thoughts about the heading title here? I think it might be the only outstanding item for us to resolve

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dominikg any thoughts about the heading title here? I think it might be the only outstanding item for us to resolve

i have changed the wording to use conflicting resolve results instead of deprecation.


In the past, Svelte recommended using the custom "svelte" field in package.json to allow libraries to point at .svelte source files.
dominikg marked this conversation as resolved.
Show resolved Hide resolved
This field requires a custom implementation to resolve, so you have to use a bundler plugin and this plugin needs to implement resolving.
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.
dominikg marked this conversation as resolved.
Show resolved Hide resolved

Example:

```diff
// package.json
"svelte": "src/index.js",
+ "exports": {
+ "./*": {
+ "svelte": "./src/lib/components/*",
+ },
+ ".": {
+ "svelte": "./index.js"
+ }
}
```

> **Support for the svelte field is going to be deprecated and later removed in a future versions of vite-plugin-svelte.**
>
dominikg marked this conversation as resolved.
Show resolved Hide resolved
> Library authors are highly encouraged to update their packages to add the new exports condition as outlined above. Check out
> [svelte-package](https://kit.svelte.dev/docs/packaging) which already supports it.
>
> For backwards compatibility, you can keep the svelte field in addition to the exports condition. But make sure that both always resolve to the same files.
dominikg marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Dependency } from './src/components/Dependency.svelte';
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"e2e-test-dep-cjs-only": "file:../cjs-only"
},
"type": "module",
"files": ["src","index.svelte.js","index.js"],
"svelte": "./index.svelte.js",
"exports": {
"./*": {
"svelte": "./src/components/*"
Expand Down
7 changes: 6 additions & 1 deletion packages/e2e-tests/resolve-exports-svelte/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ import { svelte } from '@sveltejs/vite-plugin-svelte';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte({ compilerOptions: { css: 'none' } })]
plugins: [
svelte({
compilerOptions: { css: 'none' },
experimental: { disableSvelteResolveWarnings: false }
dominikg marked this conversation as resolved.
Show resolved Hide resolved
})
]
});
46 changes: 41 additions & 5 deletions packages/vite-plugin-svelte/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { saveSvelteMetadata } from './utils/optimizer';
import { svelteInspector } from './ui/inspector/plugin';
import { VitePluginSvelteCache } from './utils/vite-plugin-svelte-cache';
import { loadRaw } from './utils/load-raw';
import { FAQ_LINK_DEPRECATED_SVELTE_FIELD } from './utils/constants';

interface PluginAPI {
/**
Expand All @@ -50,6 +51,7 @@ export function svelte(inlineOptions?: Partial<Options>): Plugin[] {
/* eslint-enable no-unused-vars */

let resolvedSvelteSSR: Promise<PartialResolvedId | null>;
let packagesWithResolveWarnings: Set<string>;
const api: PluginAPI = {};
const plugins: Plugin[] = [
{
Expand All @@ -73,7 +75,7 @@ export function svelte(inlineOptions?: Partial<Options>): Plugin[] {
},

async configResolved(config) {
options = resolveOptions(options, config);
options = resolveOptions(options, config, cache);
patchResolvedViteConfig(config, options);
requestParser = buildIdParser(options);
compileSvelte = createCompileSvelte(options);
Expand All @@ -84,6 +86,7 @@ export function svelte(inlineOptions?: Partial<Options>): Plugin[] {
},

async buildStart() {
packagesWithResolveWarnings = new Set<string>();
if (!options.prebundleSvelteLibraries) return;
const isSvelteMetadataChanged = await saveSvelteMetadata(viteConfig.cacheDir, options);
if (isSvelteMetadataChanged) {
Expand Down Expand Up @@ -164,13 +167,36 @@ export function svelte(inlineOptions?: Partial<Options>): Plugin[] {
// for ssr, during scanning and non-prebundled, we do it
if (ssr || scan || !isPrebundled) {
try {
const isFirstResolve = !cache.hasResolvedSvelteField(importee, importer);
const resolved = await resolveViaPackageJsonSvelte(importee, importer, cache);
if (resolved) {
log.debug(
`resolveId resolved ${resolved} via package.json svelte field of ${importee}`
if (isFirstResolve && resolved) {
const packageInfo = await cache.getPackageInfo(resolved);
const packageVersion = `${packageInfo.name}@${packageInfo.version}`;
log.debug.once(
`resolveId resolved ${importee} to ${resolved} via package.json svelte field of ${packageVersion}`
);
return resolved;

try {
const viteResolved = (
await this.resolve(importee, importer, { ...opts, skipSelf: true })
)?.id;
if (resolved !== viteResolved) {
packagesWithResolveWarnings.add(packageVersion);
log.debug.enabled &&
log.debug.once(
`resolve difference for ${packageVersion} ${importee} - svelte: "${resolved}", vite: "${viteResolved}"`
);
}
} catch (e) {
packagesWithResolveWarnings.add(packageVersion);
log.debug.enabled &&
log.debug.once(
`resolve error for ${packageVersion} ${importee} - svelte: "${resolved}", vite: ERROR`,
e
);
}
}
return resolved;
} catch (e) {
log.debug.once(
`error trying to resolve ${importee} from ${importer} via package.json svelte field `,
Expand Down Expand Up @@ -224,6 +250,16 @@ export function svelte(inlineOptions?: Partial<Options>): Plugin[] {
},
async buildEnd() {
await options.stats?.finishAll();
if (
!options.experimental?.disableSvelteResolveWarnings &&
packagesWithResolveWarnings?.size > 0
) {
log.warn(
`WARNING: The following packages use a svelte resolve configuration in package.json that is going to cause problems in the future.\n\n${[
...packagesWithResolveWarnings
].join('\n')}\n\nPlease see ${FAQ_LINK_DEPRECATED_SVELTE_FIELD} for details.`
);
}
}
}
];
Expand Down
3 changes: 3 additions & 0 deletions packages/vite-plugin-svelte/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ export const SVELTE_HMR_IMPORTS = [
];

export const SVELTE_EXPORT_CONDITIONS = ['svelte'];

export const FAQ_LINK_DEPRECATED_SVELTE_FIELD =
'https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#deprecated-svelte-field';
2 changes: 1 addition & 1 deletion packages/vite-plugin-svelte/src/utils/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function createLogger(level: string): LogFn {
const logFn: LogFn = _log.bind(null, logger) as LogFn;
const logged = new Set<String>();
const once = function (message: string, payload?: any) {
if (logged.has(message)) {
if (!logger.enabled || logged.has(message)) {
return;
}
logged.add(message);
Expand Down
13 changes: 11 additions & 2 deletions packages/vite-plugin-svelte/src/utils/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {

import { isCommonDepWithoutSvelteField } from './dependencies';
import { VitePluginSvelteStats } from './vite-plugin-svelte-stats';
import { VitePluginSvelteCache } from './vite-plugin-svelte-cache';

const allowedPluginOptions = new Set([
'include',
Expand Down Expand Up @@ -178,7 +179,8 @@ function mergeConfigs<T>(...configs: (Partial<T> | undefined)[]): T {
// also validates the final config.
export function resolveOptions(
preResolveOptions: PreResolvedOptions,
viteConfig: ResolvedConfig
viteConfig: ResolvedConfig,
cache: VitePluginSvelteCache
): ResolvedOptions {
const css = preResolveOptions.emitCss ? 'external' : 'injected';
const defaultOptions: Partial<Options> = {
Expand Down Expand Up @@ -211,7 +213,7 @@ export function resolveOptions(
const statsEnabled =
disableCompileStats !== true && disableCompileStats !== (merged.isBuild ? 'build' : 'dev');
if (statsEnabled && isLogLevelInfo) {
merged.stats = new VitePluginSvelteStats();
merged.stats = new VitePluginSvelteStats(cache);
}
return merged;
}
Expand Down Expand Up @@ -732,6 +734,13 @@ export interface ExperimentalOptions {
* @default false
*/
disableCompileStats?: 'dev' | 'build' | boolean;

/**
* disable svelte field resolve warnings
*
* @default false
*/
disableSvelteResolveWarnings?: boolean;
}

export interface InspectorOptions {
Expand Down
55 changes: 55 additions & 0 deletions packages/vite-plugin-svelte/src/utils/vite-plugin-svelte-cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { SvelteRequest } from './id';
import { Code, CompileData } from './compile';
import { readFileSync } from 'fs';
import { dirname } from 'path';
//eslint-disable-next-line node/no-missing-import
import { findClosestPkgJsonPath } from 'vitefu';
import { normalizePath } from 'vite';

interface PackageInfo {
name: string;
version: string;
svelte?: string;
path: string;
}

export class VitePluginSvelteCache {
private _css = new Map<string, Code>();
Expand All @@ -8,6 +20,7 @@ export class VitePluginSvelteCache {
private _dependants = new Map<string, Set<string>>();
private _resolvedSvelteFields = new Map<string, string>();
private _errors = new Map<string, any>();
private _packageInfos: PackageInfo[] = [];

public update(compileData: CompileData) {
this._errors.delete(compileData.normalizedFilename);
Expand Down Expand Up @@ -110,6 +123,9 @@ export class VitePluginSvelteCache {
return this._resolvedSvelteFields.get(this._getResolvedSvelteFieldKey(name, importer));
}

public hasResolvedSvelteField(name: string, importer?: string) {
return this._resolvedSvelteFields.has(this._getResolvedSvelteFieldKey(name, importer));
}
public setResolvedSvelteField(
importee: string,
importer: string | undefined = undefined,
Expand All @@ -124,4 +140,43 @@ export class VitePluginSvelteCache {
private _getResolvedSvelteFieldKey(importee: string, importer?: string): string {
return importer ? `${importer} > ${importee}` : importee;
}

public async getPackageInfo(file: string): Promise<PackageInfo> {
let info = this._packageInfos.find((pi) => file.startsWith(pi.path));
if (!info) {
info = await findPackageInfo(file);
this._packageInfos.push(info);
}
return info;
}
}

/**
* utility to get some info from the package.json a file belongs to (closest one with a "name" set)
dominikg marked this conversation as resolved.
Show resolved Hide resolved
*
* @param {string} file to find info for
* @returns {PackageInfo}
*/
async function findPackageInfo(file: string): Promise<PackageInfo> {
const info: PackageInfo = {
name: '$unknown',
version: '0.0.0-unknown',
path: '$unknown'
};
let path = await findClosestPkgJsonPath(file, (pkgPath) => {
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
if (pkg.name != null) {
info.name = pkg.name;
if (pkg.version != null) {
info.version = pkg.version;
}
info.svelte = pkg.svelte;
return true;
}
return false;
});
// return normalized path with appended '/' so .startsWith works for future file checks
path = normalizePath(dirname(path ?? file)) + '/';
info.path = path;
return info;
}
38 changes: 6 additions & 32 deletions packages/vite-plugin-svelte/src/utils/vite-plugin-svelte-stats.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { log } from './log';
//eslint-disable-next-line node/no-missing-import
import { findClosestPkgJsonPath } from 'vitefu';
import { readFileSync } from 'fs';
import { dirname } from 'path';
import { performance } from 'perf_hooks';
import { normalizePath } from 'vite';
import { VitePluginSvelteCache } from './vite-plugin-svelte-cache';

interface Stat {
file: string;
Expand Down Expand Up @@ -87,31 +84,13 @@ function formatPackageStats(pkgStats: PackageStats[]) {
return table;
}

/**
* utility to get the package name a file belongs to
*
* @param {string} file to find package for
* @returns {path:string,name:string} tuple of path,name where name is the parsed package name and path is the normalized path to it
*/
async function getClosestNamedPackage(file: string): Promise<{ name: string; path: string }> {
let name = '$unknown';
let path = await findClosestPkgJsonPath(file, (pkgPath) => {
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
if (pkg.name != null) {
name = pkg.name;
return true;
}
return false;
});
// return normalized path with appended '/' so .startsWith works for future file checks
path = normalizePath(dirname(path ?? file)) + '/';
return { name, path };
}

export class VitePluginSvelteStats {
// package directory -> package name
private _packages: { path: string; name: string }[] = [];
private _cache: VitePluginSvelteCache;
private _collections: StatCollection[] = [];
constructor(cache: VitePluginSvelteCache) {
this._cache = cache;
}
startCollection(name: string, opts?: Partial<CollectionOptions>) {
const options = {
...defaultCollectionOptions,
Expand Down Expand Up @@ -186,12 +165,7 @@ export class VitePluginSvelteStats {
private async _aggregateStatsResult(collection: StatCollection) {
const stats = collection.stats;
for (const stat of stats) {
let pkg = this._packages.find((p) => stat.file.startsWith(p.path));
if (!pkg) {
pkg = await getClosestNamedPackage(stat.file);
this._packages.push(pkg);
}
stat.pkg = pkg.name;
stat.pkg = (await this._cache.getPackageInfo(stat.file)).name;
}

// group stats
Expand Down