· 4 min read

Svelte Notes (2): The Compiler Is Way Smarter Than You

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Today I browsed through Svelte’s source code to get familiar with its architecture, and looked around for any simple issues I could pick up for practice. Then I came across this one — emits warning.

Although based on the issue description, an a tag without an href should be invalid (or at least violate a11y standards), I did some digging and found discussions on StackOverflow mentioning that current specifications have some inconsistencies.

Essentially, href can be empty, but in such cases, it’s better to avoid using a and consider replacing it with tags like button, which is much friendlier to screen readers.

Later, I checked eslint-plugin-jsx-a11y and found that this ESLint plugin checks whether href is a valid value. The invalid values listed include:

<a onClick={foo} /> // 可以用 button 取代
<a href="#" /> // 只有 # 沒有 id
<a href={"#"} /> // 字面值
<a href={`#`} /> // 字面值
<a href="javascript:void(0)" /> // 不應該用 javascript:void(0)
<a href={"javascript:void(0)"} /> // 字面值
<a href={`javascript:void(0)`} /> // 字面值

As well as cases where href is empty:

<a />
<a href={undefined} />
<a href={null} />

Svelte helps you check basic a11y issues, such as missing href or invalid href values. Because Svelte runs through a compiler step first, these checks are performed at compile time:

// compiler/compile/nodes/Element.ts
// L425
		if (this.name === 'a') {
			const attribute = attribute_map.get('href') || attribute_map.get('xlink:href');

			if (attribute) {
				const value = attribute.get_static_value();

				if (value === '' || value === '#') {
					component.warn(attribute, {
						code: `a11y-invalid-attribute`,
						message: `A11y: '${value}' is not a valid ${attribute.name} attribute`
					});
				}
			} else {
				component.warn(this, {
					code: `a11y-missing-attribute`,
					message: `A11y: <a> element should have an href attribute`
				});
			}
		}

At this stage, Svelte converts the syntax into an Abstract Syntax Tree (AST), making it very convenient to perform checks.

Looking closely, though—wait, it looks like the javascript:void(0) case isn’t handled! Currently, it only handles cases where the value is empty or #. So I went ahead and added a check: if (value === '' || value === '#' || /^\W*javascript:/.test(value)) and submitted a Pull Request.

Also, worth noting as a trickier issue: Svelte currently only checks values where is_static is true, so cases like:

<a href={undefined} />
<a href={null} />
<a href={`#`} />
<a href={"javascript:void(0)"} />
<a href={`javascript:void(0)`} />

Because everything inside {} is an Expression, Svelte can’t detect them (it directly marks is_static as false). If we want to check these, we’ll need to handle these Expressions separately. When I have time (Golden Week is coming up soon), I might try fixing them all at once.

It doesn’t feel like a hugely critical feature, but since it runs at compile time, it doesn’t impact bundle size anyway. I’m increasingly feeling that the compiler-first approach is really viable: you can write code however you prefer, and as long as it compiles down to JavaScript, you’re good.

While this isn’t a new idea—languages like LiveScript or Elm share similar concepts to varying degrees—I think the reason Svelte caught everyone’s attention (and is easier to adopt) is that it retains most standard JavaScript syntax. The learning curve for templates is practically zero (if you already know React or Vue); writing it just feels like Vue with HTML, CSS, and JavaScript bundled together.

Another strength is Svelte’s reactivity system and animation controls. These are things frontend developers constantly need to consider when building pages, and something other compilers don’t offer out of the box. Everyone clamors for Functional Programming, but if it doesn’t solve developers’ real-world problems, it’s ultimately just an obsession for a small group of enthusiasts.

Related Posts

Explore Other Topics