From d20d6042bca9daf4bb5b7bbcba701b3dd5ae3c0d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Mar 2026 13:41:36 -0400 Subject: [PATCH] customElement --- .../svelte/src/compiler/phases/2-analyze/index.js | 8 ++++++-- .../phases/3-transform/client/transform-client.js | 2 +- .../phases/3-transform/server/transform-server.js | 2 +- packages/svelte/src/compiler/types/index.d.ts | 6 +++++- packages/svelte/src/compiler/validate-options.js | 12 +++++++++++- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index a5df104814..40992865e0 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -465,7 +465,8 @@ export function analyze_component(root, source, options) { } } - const is_custom_element = !!options.customElementOptions || options.customElement; + const is_custom_element = + !!options.customElementOptions || options.customElement({ filename: options.filename }); const name = module.scope.generate(options.name ?? component_name); @@ -682,7 +683,10 @@ export function analyze_component(root, source, options) { w.options_deprecated_accessors(attribute); } - if (attribute.name === 'customElement' && !options.customElement) { + if ( + attribute.name === 'customElement' && + !options.customElement({ filename: options.filename }) + ) { w.options_missing_custom_element(attribute); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index b50a73b8b6..f64733e675 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -595,7 +595,7 @@ export function client_component(analysis, options) { ); } - const ce = options.customElementOptions ?? options.customElement; + const ce = options.customElementOptions ?? options.customElement({ filename: options.filename }); if (ce) { const ce_props = typeof ce === 'boolean' ? {} : ce.props || {}; diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 375df4a90a..49c1d8d59f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -303,7 +303,7 @@ export function server_component(analysis, options) { if ( analysis.css.ast !== null && options.css({ filename: options.filename }) === 'injected' && - !options.customElement + !options.customElement({ filename: options.filename }) ) { const hash = b.literal(analysis.css.hash); const code = b.literal(render_stylesheet(analysis.source, analysis, options).code); diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index 9e2e5523db..c04466a24d 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -73,9 +73,11 @@ export interface CompileOptions extends ModuleCompileOptions { /** * If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. * + * You can also pass a function that receives `{ filename }` and returns a boolean. + * * @default false */ - customElement?: boolean; + customElement?: boolean | ((options: { filename: string }) => boolean); /** * If `true`, getters and setters will be created for the component's props. If `false`, they will only be created for readonly exported values (i.e. those declared with `const`, `class` and `function`). If compiling with `customElement: true` this option defaults to `true`. * @@ -250,6 +252,7 @@ export type ValidatedCompileOptions = ValidatedModuleCompileOptions & Required, | keyof ModuleCompileOptions | 'name' + | 'customElement' | 'compatibility' | 'outputFilename' | 'cssOutputFilename' @@ -258,6 +261,7 @@ export type ValidatedCompileOptions = ValidatedModuleCompileOptions & | 'runes' > & { name: CompileOptions['name']; + customElement: (options: { filename: string }) => boolean; outputFilename: CompileOptions['outputFilename']; cssOutputFilename: CompileOptions['cssOutputFilename']; sourcemap: CompileOptions['sourcemap']; diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js index 99a84693a0..e1f1525fc7 100644 --- a/packages/svelte/src/compiler/validate-options.js +++ b/packages/svelte/src/compiler/validate-options.js @@ -77,7 +77,17 @@ const component_options = { // TODO this is a sourcemap option, would be good to put under a sourcemap namespace cssOutputFilename: string(undefined), - customElement: boolean(false), + /** @type {Validator boolean), (options: { filename: string }) => boolean>} */ + customElement: parametric( + /** @type {(options: { filename: string }) => boolean} */ (() => false), + (input, keypath) => { + if (typeof input !== 'boolean') { + throw_error(`${keypath} should be true or false`); + } + + return /** @type {boolean} */ (input); + } + ), discloseVersion: boolean(true),