Skip to content

Commit 50db140

Browse files
bluwydominikg
andauthored
refactor: use vitefu to handle svelte libraries (#478)
Co-authored-by: dominikg <[email protected]>
1 parent ac82e00 commit 50db140

File tree

9 files changed

+142
-383
lines changed

9 files changed

+142
-383
lines changed

.changeset/loud-cameras-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/vite-plugin-svelte': patch
3+
---
4+
5+
Refactor Svelte libraries config handling

packages/vite-plugin-svelte/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"deepmerge": "^4.2.2",
5252
"kleur": "^4.1.5",
5353
"magic-string": "^0.26.7",
54-
"svelte-hmr": "^0.15.0"
54+
"svelte-hmr": "^0.15.0",
55+
"vitefu": "^0.2.0"
5556
},
5657
"peerDependencies": {
5758
"diff-match-patch": "^1.0.5",

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import fs from 'fs';
22
import { HmrContext, ModuleNode, Plugin, ResolvedConfig, UserConfig } from 'vite';
3+
// eslint-disable-next-line node/no-missing-import
4+
import { isDepExcluded } from 'vitefu';
35
import { handleHotUpdate } from './handle-hot-update';
46
import { log, logCompilerWarnings } from './utils/log';
57
import { CompileData, createCompileSvelte } from './utils/compile';
@@ -19,7 +21,7 @@ import { ensureWatchedFile, setupWatchers } from './utils/watch';
1921
import { resolveViaPackageJsonSvelte } from './utils/resolve';
2022
import { PartialResolvedId } from 'rollup';
2123
import { toRollupError } from './utils/error';
22-
import { isOptimizeExcluded, saveSvelteMetadata } from './utils/optimizer';
24+
import { saveSvelteMetadata } from './utils/optimizer';
2325
import { svelteInspector } from './ui/inspector/plugin';
2426

2527
interface PluginAPI {
@@ -68,7 +70,7 @@ export function svelte(inlineOptions?: Partial<Options>): Plugin[] {
6870
// @ts-expect-error temporarily lend the options variable until fixed in configResolved
6971
options = await preResolveOptions(inlineOptions, config, configEnv);
7072
// extra vite config
71-
const extraViteConfig = buildExtraViteConfig(options, config);
73+
const extraViteConfig = await buildExtraViteConfig(options, config);
7274
log.debug('additional vite config', extraViteConfig);
7375
return extraViteConfig;
7476
},
@@ -160,12 +162,12 @@ export function svelte(inlineOptions?: Partial<Options>): Plugin[] {
160162
options.prebundleSvelteLibraries &&
161163
viteConfig.optimizeDeps?.disabled !== true &&
162164
viteConfig.optimizeDeps?.disabled !== (options.isBuild ? 'build' : 'dev') &&
163-
!isOptimizeExcluded(importee, viteConfig.optimizeDeps?.exclude);
165+
!isDepExcluded(importee, viteConfig.optimizeDeps?.exclude ?? []);
164166
// for prebundled libraries we let vite resolve the prebundling result
165167
// for ssr, during scanning and non-prebundled, we do it
166168
if (ssr || scan || !isPrebundled) {
167169
try {
168-
const resolved = resolveViaPackageJsonSvelte(importee, importer, cache);
170+
const resolved = await resolveViaPackageJsonSvelte(importee, importer, cache);
169171
if (resolved) {
170172
log.debug(
171173
`resolveId resolved ${resolved} via package.json svelte field of ${importee}`

packages/vite-plugin-svelte/src/utils/__tests__/dependencies.spec.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 17 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,29 @@
1-
import { log } from './log';
21
import path from 'path';
3-
import fs from 'fs';
4-
import { createRequire } from 'module';
2+
import fs from 'fs/promises';
3+
// eslint-disable-next-line node/no-missing-import
4+
import { findDepPkgJsonPath } from 'vitefu';
55

6-
export function findRootSvelteDependencies(root: string, cwdFallback = true): SvelteDependency[] {
7-
log.debug(`findSvelteDependencies: searching svelte dependencies in ${root}`);
8-
const pkgFile = path.join(root, 'package.json');
9-
if (!fs.existsSync(pkgFile)) {
10-
if (cwdFallback) {
11-
const cwd = process.cwd();
12-
if (root !== cwd) {
13-
log.debug(`no package.json found in vite root ${root}`);
14-
return findRootSvelteDependencies(cwd, false);
15-
}
16-
}
17-
log.warn(`no package.json found, findRootSvelteDependencies failed`);
18-
return [];
19-
}
20-
21-
const pkg = parsePkg(root);
22-
if (!pkg) {
23-
return [];
24-
}
25-
26-
const deps = [
27-
...Object.keys(pkg.dependencies || {}),
28-
...Object.keys(pkg.devDependencies || {})
29-
].filter((dep) => !is_common_without_svelte_field(dep));
30-
31-
return getSvelteDependencies(deps, root);
32-
}
33-
34-
function getSvelteDependencies(
35-
deps: string[],
36-
pkgDir: string,
37-
path: string[] = []
38-
): SvelteDependency[] {
39-
const result = [];
40-
const localRequire = createRequire(`${pkgDir}/package.json`);
41-
const resolvedDeps = deps
42-
.map((dep) => resolveDependencyData(dep, localRequire))
43-
.filter(Boolean) as DependencyData[];
44-
for (const { pkg, dir } of resolvedDeps) {
45-
const type = getSvelteDependencyType(pkg);
46-
if (!type) continue;
47-
result.push({ name: pkg.name, type, pkg, dir, path });
48-
// continue crawling for component libraries so we can optimize them, js libraries are fine
49-
if (type === 'component-library' && pkg.dependencies) {
50-
let dependencyNames = Object.keys(pkg.dependencies);
51-
const circular = dependencyNames.filter((name) => path.includes(name));
52-
if (circular.length > 0) {
53-
log.warn.enabled &&
54-
log.warn(
55-
`skipping circular svelte dependencies in automated vite optimizeDeps handling`,
56-
circular.map((x) => path.concat(x).join('>'))
57-
);
58-
dependencyNames = dependencyNames.filter((name) => !path.includes(name));
59-
}
60-
if (path.length === 3) {
61-
log.debug.once(`encountered deep svelte dependency tree: ${path.join('>')}`);
62-
}
63-
result.push(...getSvelteDependencies(dependencyNames, dir, path.concat(pkg.name)));
64-
}
65-
}
66-
return result;
6+
interface DependencyData {
7+
dir: string;
8+
pkg: Record<string, any>;
679
}
6810

69-
export function resolveDependencyData(
11+
export async function resolveDependencyData(
7012
dep: string,
71-
localRequire: NodeRequire
72-
): DependencyData | void {
73-
try {
74-
const pkgJson = `${dep}/package.json`;
75-
const pkg = localRequire(pkgJson);
76-
const dir = path.dirname(localRequire.resolve(pkgJson));
77-
return { dir, pkg };
78-
} catch (e) {
79-
log.debug.once(`dependency ${dep} does not export package.json`, e);
80-
// walk up from default export until we find package.json with name=dep
81-
try {
82-
let dir = path.dirname(localRequire.resolve(dep));
83-
while (dir) {
84-
const pkg = parsePkg(dir, true);
85-
if (pkg && pkg.name === dep) {
86-
return { dir, pkg };
87-
}
88-
const parent = path.dirname(dir);
89-
if (parent === dir) {
90-
break;
91-
}
92-
dir = parent;
93-
}
94-
} catch (e) {
95-
log.debug.once(`error while trying to find package.json of ${dep}`, e);
96-
}
97-
}
98-
log.debug.once(`failed to resolve ${dep}`);
99-
}
100-
101-
function parsePkg(dir: string, silent = false): Pkg | void {
102-
const pkgFile = path.join(dir, 'package.json');
13+
parent: string
14+
): Promise<DependencyData | undefined> {
15+
const depDataPath = await findDepPkgJsonPath(dep, parent);
16+
if (!depDataPath) return undefined;
10317
try {
104-
return JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
105-
} catch (e) {
106-
!silent && log.warn.enabled && log.warn(`failed to parse ${pkgFile}`, e);
107-
}
108-
}
109-
110-
function getSvelteDependencyType(pkg: Pkg): SvelteDependencyType | undefined {
111-
if (isSvelteComponentLib(pkg)) {
112-
return 'component-library';
113-
} else if (isSvelteLib(pkg)) {
114-
return 'js-library';
115-
} else {
18+
return {
19+
dir: path.dirname(depDataPath),
20+
pkg: JSON.parse(await fs.readFile(depDataPath, 'utf-8'))
21+
};
22+
} catch {
11623
return undefined;
11724
}
11825
}
11926

120-
function isSvelteComponentLib(pkg: Pkg) {
121-
return !!pkg.svelte;
122-
}
123-
124-
function isSvelteLib(pkg: Pkg) {
125-
return !!pkg.dependencies?.svelte || !!pkg.peerDependencies?.svelte;
126-
}
127-
12827
const COMMON_DEPENDENCIES_WITHOUT_SVELTE_FIELD = [
12928
'@lukeed/uuid',
13029
'@playwright/test',
@@ -173,7 +72,7 @@ const COMMON_PREFIXES_WITHOUT_SVELTE_FIELD = [
17372
* @param dependency {string}
17473
* @returns {boolean} true if it is a dependency without a svelte field
17574
*/
176-
export function is_common_without_svelte_field(dependency: string): boolean {
75+
export function isCommonDepWithoutSvelteField(dependency: string): boolean {
17776
return (
17877
COMMON_DEPENDENCIES_WITHOUT_SVELTE_FIELD.includes(dependency) ||
17978
COMMON_PREFIXES_WITHOUT_SVELTE_FIELD.some(
@@ -184,58 +83,3 @@ export function is_common_without_svelte_field(dependency: string): boolean {
18483
)
18584
);
18685
}
187-
188-
export function needsOptimization(dep: string, localRequire: NodeRequire): boolean {
189-
const depData = resolveDependencyData(dep, localRequire);
190-
if (!depData) return false;
191-
const pkg = depData.pkg;
192-
// only optimize if is cjs, using the below as heuristic
193-
// see https://github.com/sveltejs/vite-plugin-svelte/issues/162
194-
const hasEsmFields = pkg.module || pkg.exports;
195-
if (hasEsmFields) return false;
196-
if (pkg.main) {
197-
// ensure entry is js so vite can prebundle it
198-
// see https://github.com/sveltejs/vite-plugin-svelte/issues/233
199-
const entryExt = path.extname(pkg.main);
200-
return !entryExt || entryExt === '.js' || entryExt === '.cjs';
201-
} else {
202-
// check if has implicit index.js entrypoint
203-
// https://github.com/sveltejs/vite-plugin-svelte/issues/281
204-
try {
205-
localRequire.resolve(`${dep}/index.js`);
206-
return true;
207-
} catch {
208-
return false;
209-
}
210-
}
211-
}
212-
213-
interface DependencyData {
214-
dir: string;
215-
pkg: Pkg;
216-
}
217-
218-
export interface SvelteDependency {
219-
name: string;
220-
type: SvelteDependencyType;
221-
dir: string;
222-
pkg: Pkg;
223-
path: string[];
224-
}
225-
226-
// component-library => exports svelte components
227-
// js-library => only uses svelte api, no components
228-
export type SvelteDependencyType = 'component-library' | 'js-library';
229-
230-
export interface Pkg {
231-
name: string;
232-
svelte?: string;
233-
dependencies?: DependencyList;
234-
devDependencies?: DependencyList;
235-
peerDependencies?: DependencyList;
236-
[key: string]: any;
237-
}
238-
239-
export interface DependencyList {
240-
[key: string]: string;
241-
}

packages/vite-plugin-svelte/src/utils/optimizer.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,3 @@ function generateSvelteMetadata(options: ResolvedOptions) {
4343
}
4444
return metadata;
4545
}
46-
47-
// vite optimizeDeps.exclude works for subpackages too
48-
// see https://github.com/vitejs/vite/blob/c87763c1418d1ba876eae13d139eba83ac6f28b2/packages/vite/src/node/optimizer/scan.ts#L293
49-
export function isOptimizeExcluded(dep: string, exclude?: string[]): boolean {
50-
return !!exclude?.some((e) => dep === e || dep.startsWith(`${e}/`));
51-
}
52-
53-
// include can contain `a > b` entries, so we have to test the last segment too
54-
export function isOptimizeIncluded(dep: string, include?: string[]): boolean {
55-
return !!include?.some((e) => {
56-
if (e === dep) {
57-
return true;
58-
}
59-
const lastArrow = e.lastIndexOf('>');
60-
return lastArrow > -1 && e.slice(lastArrow + 1).trim() === dep;
61-
});
62-
}

0 commit comments

Comments
 (0)