From 23ccb375e08bc36aeba6f132e0700a0e570d6302 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 7 Mar 2024 10:20:51 +0100 Subject: [PATCH 1/2] chore: custom elements validation - add "missing customElement option" warning - add backwards compat support for customElement={null} --- .changeset/kind-spoons-return.md | 5 +++++ packages/svelte/src/compiler/index.js | 10 +++++++--- .../src/compiler/phases/1-parse/read/options.js | 5 +++++ .../svelte/src/compiler/phases/2-analyze/index.js | 8 ++++++-- packages/svelte/src/compiler/types/index.d.ts | 3 ++- packages/svelte/src/compiler/types/template.d.ts | 2 +- packages/svelte/src/compiler/warnings.js | 8 +++++++- .../$$slot-dynamic-content/_config.js | 7 +++---- .../$$slot-dynamic-content/main.svelte | 1 + .../_config.js | 3 --- .../input.svelte | 1 - .../warnings.json | 14 -------------- .../tag-custom-element-options-missing/_config.js | 3 --- .../warnings.json | 6 +++--- 14 files changed, 40 insertions(+), 36 deletions(-) create mode 100644 .changeset/kind-spoons-return.md delete mode 100644 packages/svelte/tests/validator/samples/missing-custom-element-compile-options/_config.js delete mode 100644 packages/svelte/tests/validator/samples/missing-custom-element-compile-options/input.svelte delete mode 100644 packages/svelte/tests/validator/samples/missing-custom-element-compile-options/warnings.json delete mode 100644 packages/svelte/tests/validator/samples/tag-custom-element-options-missing/_config.js diff --git a/.changeset/kind-spoons-return.md b/.changeset/kind-spoons-return.md new file mode 100644 index 000000000000..d9c1aa16c860 --- /dev/null +++ b/.changeset/kind-spoons-return.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +chore: custom elements validation diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index 09ab56d788a6..509bc8714df9 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -23,10 +23,14 @@ export function compile(source, options) { const validated = validate_component_options(options, ''); let parsed = _parse(source); - const combined_options = /** @type {import('#compiler').ValidatedCompileOptions} */ ({ + const { customElement: customElementOptions, ...parsed_options } = parsed.options || {}; + + /** @type {import('#compiler').ValidatedCompileOptions} */ + const combined_options = { ...validated, - ...parsed.options - }); + ...parsed_options, + customElementOptions + }; if (parsed.metadata.ts) { parsed = { diff --git a/packages/svelte/src/compiler/phases/1-parse/read/options.js b/packages/svelte/src/compiler/phases/1-parse/read/options.js index 69ca025f589e..5d4f58868ff1 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/options.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/options.js @@ -54,6 +54,11 @@ export default function read_options(node) { component_options.customElement = ce; break; } else if (value[0].expression.type !== 'ObjectExpression') { + // Before Svelte 4 it was necessary to explicitly set customElement to null or else you'd get a warning. + // This is no longer necessary, but for backwards compat just skip in this case now. + if (value[0].expression.type === 'Literal' && value[0].expression.value === null) { + break; + } error(attribute, 'invalid-svelte-option-customElement'); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 545fc90ecbbe..3ddb68237c04 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -374,8 +374,8 @@ export function analyze_component(root, options) { uses_rest_props: false, uses_slots: false, uses_component_bindings: false, - custom_element: options.customElement, - inject_styles: options.css === 'injected' || !!options.customElement, + custom_element: options.customElementOptions ?? options.customElement, + inject_styles: options.css === 'injected' || options.customElement, accessors: options.customElement ? true : !!options.accessors || @@ -399,6 +399,10 @@ export function analyze_component(root, options) { } }; + if (!options.customElement && root.options?.customElement) { + warn(analysis.warnings, root.options, [], 'missing-custom-element-compile-option'); + } + if (analysis.runes) { const props_refs = module.scope.references.get('$$props'); if (props_refs) { diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index a04d1c39af45..6d09effe9321 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -11,7 +11,7 @@ import type { SourceMap } from 'magic-string'; import type { Context } from 'zimmerframe'; import type { Scope } from '../phases/scope.js'; import * as Css from './css.js'; -import type { EachBlock, Namespace, SvelteNode } from './template.js'; +import type { EachBlock, Namespace, SvelteNode, SvelteOptions } from './template.js'; /** The return value of `compile` from `svelte/compiler` */ export interface CompileResult { @@ -224,6 +224,7 @@ export type ValidatedCompileOptions = ValidatedModuleCompileOptions & sourcemap: CompileOptions['sourcemap']; legacy: Required['legacy']>; runes: CompileOptions['runes']; + customElementOptions: SvelteOptions['customElement']; }; export type DeclarationKind = diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index b714370b6daa..695720d48e70 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -65,7 +65,7 @@ export interface Root extends BaseNode { } export interface SvelteOptions { - // start/end info (needed for Prettier, when someone wants to keep the options where they are) + // start/end info (needed for warnings and for our Prettier plugin) start: number; end: number; // options diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index 1dd7e2aac913..7b863ef2b1ba 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -237,6 +237,11 @@ const block = { 'empty-block': () => 'Empty block' }; +const options = { + 'missing-custom-element-compile-option': () => + "The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?" +}; + /** @satisfies {Warnings} */ const warnings = { ...css, @@ -247,7 +252,8 @@ const warnings = { ...state, ...components, ...legacy, - ...block + ...block, + ...options }; /** @typedef {typeof warnings} AllWarnings */ diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js index 4685488c5f9d..8ade347a79f0 100644 --- a/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/_config.js @@ -1,11 +1,10 @@ import { test } from '../../assert'; +import { mount } from 'svelte'; const tick = () => Promise.resolve(); export default test({ - 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) - - async test({ assert, target, component: Component }) { - const component = new Component({ target, props: { name: 'slot' } }); + async test({ assert, target, componentCtor: Component }) { + const component = mount(Component, { target, props: { name: 'slot' } }); await tick(); await tick(); diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/main.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/main.svelte index ba1cf087b8e6..0ee2f70a60f1 100644 --- a/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/main.svelte +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/$$slot-dynamic-content/main.svelte @@ -1,3 +1,4 @@ +