Skip to content

Commit fff3320

Browse files
authored
chore: custom elements validation (#10720)
- add "missing customElement option" warning - add backwards compat support for customElement={null}
1 parent 6a01f48 commit fff3320

File tree

15 files changed

+41
-37
lines changed

15 files changed

+41
-37
lines changed

.changeset/kind-spoons-return.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
chore: custom elements validation

packages/svelte/src/compiler/index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@ export function compile(source, options) {
2323
const validated = validate_component_options(options, '');
2424
let parsed = _parse(source);
2525

26-
const combined_options = /** @type {import('#compiler').ValidatedCompileOptions} */ ({
26+
const { customElement: customElementOptions, ...parsed_options } = parsed.options || {};
27+
28+
/** @type {import('#compiler').ValidatedCompileOptions} */
29+
const combined_options = {
2730
...validated,
28-
...parsed.options
29-
});
31+
...parsed_options,
32+
customElementOptions
33+
};
3034

3135
if (parsed.metadata.ts) {
3236
parsed = {

packages/svelte/src/compiler/phases/1-parse/read/options.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ export default function read_options(node) {
5454
component_options.customElement = ce;
5555
break;
5656
} else if (value[0].expression.type !== 'ObjectExpression') {
57+
// Before Svelte 4 it was necessary to explicitly set customElement to null or else you'd get a warning.
58+
// This is no longer necessary, but for backwards compat just skip in this case now.
59+
if (value[0].expression.type === 'Literal' && value[0].expression.value === null) {
60+
break;
61+
}
5762
error(attribute, 'invalid-svelte-option-customElement');
5863
}
5964

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,8 @@ export function analyze_component(root, options) {
374374
uses_rest_props: false,
375375
uses_slots: false,
376376
uses_component_bindings: false,
377-
custom_element: options.customElement,
378-
inject_styles: options.css === 'injected' || !!options.customElement,
377+
custom_element: options.customElementOptions ?? options.customElement,
378+
inject_styles: options.css === 'injected' || options.customElement,
379379
accessors: options.customElement
380380
? true
381381
: !!options.accessors ||
@@ -399,6 +399,10 @@ export function analyze_component(root, options) {
399399
}
400400
};
401401

402+
if (!options.customElement && root.options?.customElement) {
403+
warn(analysis.warnings, root.options, [], 'missing-custom-element-compile-option');
404+
}
405+
402406
if (analysis.runes) {
403407
const props_refs = module.scope.references.get('$$props');
404408
if (props_refs) {

packages/svelte/src/compiler/types/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { SourceMap } from 'magic-string';
1111
import type { Context } from 'zimmerframe';
1212
import type { Scope } from '../phases/scope.js';
1313
import * as Css from './css.js';
14-
import type { EachBlock, Namespace, SvelteNode } from './template.js';
14+
import type { EachBlock, Namespace, SvelteNode, SvelteOptions } from './template.js';
1515

1616
/** The return value of `compile` from `svelte/compiler` */
1717
export interface CompileResult {
@@ -224,6 +224,7 @@ export type ValidatedCompileOptions = ValidatedModuleCompileOptions &
224224
sourcemap: CompileOptions['sourcemap'];
225225
legacy: Required<Required<CompileOptions>['legacy']>;
226226
runes: CompileOptions['runes'];
227+
customElementOptions: SvelteOptions['customElement'];
227228
};
228229

229230
export type DeclarationKind =

packages/svelte/src/compiler/types/template.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export interface Root extends BaseNode {
6565
}
6666

6767
export interface SvelteOptions {
68-
// start/end info (needed for Prettier, when someone wants to keep the options where they are)
68+
// start/end info (needed for warnings and for our Prettier plugin)
6969
start: number;
7070
end: number;
7171
// options

packages/svelte/src/compiler/warnings.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,11 @@ const block = {
237237
'empty-block': () => 'Empty block'
238238
};
239239

240+
const options = {
241+
'missing-custom-element-compile-option': () =>
242+
"The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?"
243+
};
244+
240245
/** @satisfies {Warnings} */
241246
const warnings = {
242247
...css,
@@ -247,7 +252,8 @@ const warnings = {
247252
...state,
248253
...components,
249254
...legacy,
250-
...block
255+
...block,
256+
...options
251257
};
252258

253259
/** @typedef {typeof warnings} AllWarnings */

packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { test } from '../../assert';
2+
import { mount } from 'svelte';
23
const tick = () => Promise.resolve();
34

45
export default test({
5-
skip: true, // TODO: decide if we want to keep the customElement={null} behavior (warning about not having set the tag when in ce mode, and disabling that this way)
6-
7-
async test({ assert, target, component: Component }) {
8-
const component = new Component({ target, props: { name: 'slot' } });
6+
async test({ assert, target, componentCtor: Component }) {
7+
const component = mount(Component, { target, props: { name: 'slot' } });
98
await tick();
109
await tick();
1110

packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/main.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- before Svelte 4 it was necessary to explicitly set customElement to null or else you'd get a warning. Keep this around for backwards compat -->
12
<svelte:options customElement={null} />
23

34
<script>

packages/svelte/tests/validator/samples/missing-custom-element-compile-options/_config.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)