Skip to content

Commit 5d8944b

Browse files
authored
feat: improve compile error (#757)
* feat: improve compile error * chore: improve error message
1 parent 5637512 commit 5d8944b

File tree

5 files changed

+92
-2
lines changed

5 files changed

+92
-2
lines changed

.changeset/grumpy-emus-destroy.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/vite-plugin-svelte': patch
3+
---
4+
5+
Improve compile error messages

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const lang_sep = '.vite-preprocess.';
1313
/** @type {import('./index.d.ts').vitePreprocess} */
1414
export function vitePreprocess(opts) {
1515
/** @type {import('svelte/types/compiler/preprocess').PreprocessorGroup} */
16-
const preprocessor = {};
16+
const preprocessor = { name: 'vite-preprocess' };
1717
if (opts?.script !== false) {
1818
preprocessor.script = viteScript().script;
1919
}

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { log } from './log.js';
66

77
import { createInjectScopeEverythingRulePreprocessorGroup } from './preprocess.js';
88
import { mapToRelative } from './sourcemaps.js';
9+
import { enhanceCompileError } from './error.js';
910

1011
const scriptLangRE = /<script [^>]*lang=["']?([^"' >]+)["']?[^>]*>/;
1112

@@ -119,7 +120,14 @@ export const _createCompileSvelte = (makeHot) => {
119120
: compileOptions;
120121

121122
const endStat = stats?.start(filename);
122-
const compiled = compile(finalCode, finalCompileOptions);
123+
/** @type {import('svelte/types/compiler/interfaces').CompileResult} */
124+
let compiled;
125+
try {
126+
compiled = compile(finalCode, finalCompileOptions);
127+
} catch (e) {
128+
enhanceCompileError(e, code, preprocessors);
129+
throw e;
130+
}
123131

124132
if (endStat) {
125133
endStat();

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

+76
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,79 @@ function formatFrameForVite(frame) {
100100
.map((line) => (line.match(/^\s+\^/) ? ' ' + line : ' ' + line.replace(':', ' | ')))
101101
.join('\n');
102102
}
103+
104+
/**
105+
* @param {import('svelte/types/compiler/interfaces').Warning & Error} err a svelte compiler error, which is a mix of Warning and an error
106+
* @param {string} originalCode
107+
* @param {import('../index.js').Arrayable<import('svelte/types/compiler/preprocess').PreprocessorGroup>} [preprocessors]
108+
*/
109+
export function enhanceCompileError(err, originalCode, preprocessors) {
110+
preprocessors = arraify(preprocessors ?? []);
111+
112+
/** @type {string[]} */
113+
const additionalMessages = [];
114+
115+
// Handle incorrect TypeScript usage
116+
if (err.code === 'parse-error') {
117+
// Reference from Svelte: https://github.com/sveltejs/svelte/blob/800f6c076be5dd87dd4d2e9d66c59b973d54d84b/packages/svelte/src/compiler/preprocess/index.js#L262
118+
const scriptRe = /<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
119+
const errIndex = err.pos ?? -1;
120+
121+
let m;
122+
while ((m = scriptRe.exec(originalCode))) {
123+
const matchStart = m.index;
124+
const matchEnd = matchStart + m[0].length;
125+
const isErrorInScript = matchStart <= errIndex && errIndex <= matchEnd;
126+
if (isErrorInScript) {
127+
// Warn missing lang="ts"
128+
const hasLangTs = m[1]?.includes('lang="ts"');
129+
if (!hasLangTs) {
130+
additionalMessages.push('Did you forget to add lang="ts" to your script tag?');
131+
}
132+
// Warn missing script preprocessor
133+
if (preprocessors.every((p) => p.script == null)) {
134+
const preprocessorType = hasLangTs ? 'TypeScript' : 'script';
135+
additionalMessages.push(
136+
`Did you forget to add a ${preprocessorType} preprocessor? See https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md for more information.`
137+
);
138+
}
139+
}
140+
}
141+
}
142+
143+
// Handle incorrect CSS preprocessor usage
144+
if (err.code === 'css-syntax-error') {
145+
const styleRe = /<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi;
146+
147+
let m;
148+
while ((m = styleRe.exec(originalCode))) {
149+
// Warn missing lang attribute
150+
if (!m[1]?.includes('lang=')) {
151+
additionalMessages.push('Did you forget to add a lang attribute to your style tag?');
152+
}
153+
// Warn missing style preprocessor
154+
if (
155+
preprocessors.every((p) => p.style == null || p.name === 'inject-scope-everything-rule')
156+
) {
157+
const preprocessorType = m[1]?.match(/lang="(.+?)"/)?.[1] ?? 'style';
158+
additionalMessages.push(
159+
`Did you forget to add a ${preprocessorType} preprocessor? See https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md for more information.`
160+
);
161+
}
162+
}
163+
}
164+
165+
if (additionalMessages.length) {
166+
err.message += '\n\n- ' + additionalMessages.join('\n- ');
167+
}
168+
169+
return err;
170+
}
171+
172+
/**
173+
* @param {T | T[]} value
174+
* @template T
175+
*/
176+
function arraify(value) {
177+
return Array.isArray(value) ? value : [value];
178+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import path from 'node:path';
1212
*/
1313
export function createInjectScopeEverythingRulePreprocessorGroup() {
1414
return {
15+
name: 'inject-scope-everything-rule',
1516
style({ content, filename }) {
1617
const s = new MagicString(content);
1718
s.append(' *{}');

0 commit comments

Comments
 (0)