diff --git a/.changeset/funny-waves-poke.md b/.changeset/funny-waves-poke.md new file mode 100644 index 000000000..dfa9451e7 --- /dev/null +++ b/.changeset/funny-waves-poke.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/vite-plugin-svelte': minor +--- + +feat(preprocess): add warnings in case preprocess dependencies contain anomalies diff --git a/packages/vite-plugin-svelte/src/utils/compile.js b/packages/vite-plugin-svelte/src/utils/compile.js index a7612e8bc..8d30b4268 100644 --- a/packages/vite-plugin-svelte/src/utils/compile.js +++ b/packages/vite-plugin-svelte/src/utils/compile.js @@ -4,7 +4,10 @@ import { createMakeHot } from 'svelte-hmr'; import { safeBase64Hash } from './hash.js'; import { log } from './log.js'; -import { createInjectScopeEverythingRulePreprocessorGroup } from './preprocess.js'; +import { + checkPreprocessDependencies, + createInjectScopeEverythingRulePreprocessorGroup +} from './preprocess.js'; import { mapToRelative } from './sourcemaps.js'; import { enhanceCompileError } from './error.js'; @@ -22,7 +25,10 @@ export const _createCompileSvelte = (makeHot) => { return async function compileSvelte(svelteRequest, code, options) { const { filename, normalizedFilename, cssId, ssr, raw } = svelteRequest; const { emitCss = true } = options; + /** @type {string[]} */ const dependencies = []; + /** @type {import('svelte/types/compiler/interfaces').Warning[]} */ + const warnings = []; if (options.stats) { if (options.isBuild) { @@ -87,7 +93,16 @@ export const _createCompileSvelte = (makeHot) => { throw e; } - if (preprocessed.dependencies) dependencies.push(...preprocessed.dependencies); + if (preprocessed.dependencies?.length) { + const checked = checkPreprocessDependencies(filename, preprocessed.dependencies); + if (checked.warnings.length) { + warnings.push(...checked.warnings); + } + if (checked.dependencies.length) { + dependencies.push(...checked.dependencies); + } + } + if (preprocessed.map) compileOptions.sourcemap = preprocessed.map; } if (typeof preprocessed?.map === 'object') { @@ -134,6 +149,12 @@ export const _createCompileSvelte = (makeHot) => { } mapToRelative(compiled.js?.map, filename); mapToRelative(compiled.css?.map, filename); + if (warnings.length) { + if (!compiled.warnings) { + compiled.warnings = []; + } + compiled.warnings.push(...warnings); + } if (!raw) { // wire css import and code for hmr const hasCss = compiled.css?.code?.trim().length > 0; diff --git a/packages/vite-plugin-svelte/src/utils/preprocess.js b/packages/vite-plugin-svelte/src/utils/preprocess.js index de8da3dc3..675607460 100644 --- a/packages/vite-plugin-svelte/src/utils/preprocess.js +++ b/packages/vite-plugin-svelte/src/utils/preprocess.js @@ -1,6 +1,7 @@ import MagicString from 'magic-string'; import { log } from './log.js'; import path from 'node:path'; +import { normalizePath } from 'vite'; /** * this appends a *{} rule to component styles to force the svelte compiler to add style classes to all nodes @@ -121,3 +122,52 @@ export function addExtraPreprocessors(options, config) { } } } + +/** + * + * @param filename {string} + * @param dependencies {string[]} + * @returns {({dependencies: string[], warnings:import('svelte/types/compiler/interfaces').Warning[] })} + */ +export function checkPreprocessDependencies(filename, dependencies) { + /** @type {import('svelte/types/compiler/interfaces').Warning[]} */ + const warnings = []; + + // to find self, we have to compare normalized filenames, but must keep the original values in `dependencies` + // because otherwise file watching on windows doesn't work + // so we track idx and filter by that in the end + /** @type {number[]} */ + const selfIdx = []; + const normalizedFullFilename = normalizePath(filename); + const normalizedDeps = dependencies.map(normalizePath); + for (let i = 0; i < normalizedDeps.length; i++) { + if (normalizedDeps[i] === normalizedFullFilename) { + selfIdx.push(i); + } + } + const hasSelfDependency = selfIdx.length > 0; + if (hasSelfDependency) { + warnings.push({ + code: 'vite-plugin-svelte-preprocess-depends-on-self', + message: + 'svelte.preprocess returned this file as a dependency of itself. This can be caused by an invalid configuration or importing generated code that depends on .svelte files (eg. tailwind base css)', + filename + }); + } + + if (dependencies.length > 10) { + warnings.push({ + code: 'vite-plugin-svelte-preprocess-many-dependencies', + message: `svelte.preprocess depends on more than 10 external files which can cause slow builds and poor DX, try to reduce them. Found: ${dependencies.join( + ', ' + )}`, + filename + }); + } + return { + dependencies: hasSelfDependency + ? dependencies.filter((_, i) => !selfIdx.includes(i)) // remove self dependency + : dependencies, + warnings + }; +}