カスタム要素
Svelteコンポーネントは、`customElement: true`コンパイラオプションを使用して、カスタム要素(別名Webコンポーネント)にコンパイルすることもできます。`<svelte:options>` 要素を使用して、コンポーネントのタグ名を指定する必要があります。
<svelte:options customElement="my-element" />
<script>
let { name = 'world' } = $props();
</script>
<h1>Hello {name}!</h1>
<slot />
公開したくない内部コンポーネントのタグ名は省略し、通常のSvelteコンポーネントのように使用できます。コンポーネントの利用者は、必要に応じて、カスタム要素コンストラクタを含む静的な`element`プロパティを使用して、後で名前を付けることができます。このプロパティは、`customElement`コンパイラオプションが`true`の場合に利用可能です。
import type MyElement = SvelteComponent<Record<string, any>, any, any>
const MyElement: LegacyComponentType
MyElement from './MyElement.svelte';
var customElements: CustomElementRegistry
Defines a new custom element, mapping the given name to the given constructor as an autonomous custom element.
customElements.CustomElementRegistry.define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void
define('my-element', const MyElement: LegacyComponentType
MyElement.element);
カスタム要素が定義されると、通常のDOM要素として使用できます
var document: Document
document.Document.body: HTMLElement
Specifies the beginning and end of the document body.
body.InnerHTML.innerHTML: string
innerHTML = `
<my-element>
<p>This is some slotted content</p>
</my-element>
`;
propsは、DOM要素のプロパティとして公開されます(可能な場合は、属性としても読み取り/書き込み可能です)。
const const el: Element | null
el = var document: Document
document.ParentNode.querySelector<Element>(selectors: string): Element | null (+4 overloads)
Returns the first element that is a descendant of node that matches selectors.
querySelector('my-element');
// get the current value of the 'name' prop
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(const el: Element | null
el.name);
// set a new value, updating the shadow DOM
const el: Element | null
el.name = 'everybody';
<a href="#Component-options">コンポーネントオプション</a>で`props`を宣言せずに`let props = $props()`を実行すると、SvelteはDOM要素のプロパティとして公開するpropsを認識できないため、すべてのプロパティを明示的にリストする必要があることに注意してください。
コンポーネントのライフサイクル
カスタム要素は、ラッパーアプローチを使用してSvelteコンポーネントから作成されます。つまり、内部のSvelteコンポーネントは、自分がカスタム要素であることを認識していません。カスタム要素ラッパーは、ライフサイクルを適切に処理します。
カスタム要素が作成されたとき、それをラップするSvelteコンポーネントはすぐに作成され*ません*。 `connectedCallback`が呼び出された後の次のティックで作成されます。DOMに挿入される前にカスタム要素に割り当てられたプロパティは一時的に保存され、コンポーネントの作成時に設定されるため、値は失われません。ただし、カスタム要素でエクスポートされた関数を呼び出す場合は同じようには機能せず、要素がマウントされた後にのみ使用できます。コンポーネントの作成前に関数を呼び出す必要がある場合は、<a href="#Component-options">`extend`オプション</a>を使用することで回避できます。
Svelteで記述されたカスタム要素が作成または更新されると、シャドウDOMはすぐにではなく、次のティックで値を反映します。これにより、更新をバッチ処理でき、要素をDOMから一時的に(ただし同期的に)デタッチするDOMの移動によって、内部コンポーネントがアンマウントされることはありません。
内部のSvelteコンポーネントは、`disconnectedCallback`が呼び出された後の次のティックで破棄されます。
コンポーネントオプション
カスタム要素を構築する場合、Svelte 4以降では、`<svelte:options>`内で`customElement`をオブジェクトとして定義することで、いくつかの側面を調整できます。このオブジェクトには、次のプロパティを含めることができます。
- `tag: string`:カスタム要素の名前のオプションの`tag`プロパティ。設定されている場合、このタグ名を持つカスタム要素は、このコンポーネントのインポート時にドキュメントの`customElements`レジストリで定義されます。
- `shadow`:シャドウルートの作成を中止するために`"none"`に設定できるオプションのプロパティ。スタイルがカプセル化されなくなり、スロットを使用できなくなることに注意してください。
- `props`:コンポーネントのプロパティの特定の詳細と動作を変更するためのオプションのプロパティ。次の設定が可能です。
- `attribute: string`:カスタム要素のプロパティを更新するには、上記のようにカスタム要素の参照にプロパティを設定するか、HTML属性を使用するかの2つの方法があります。後者の場合、デフォルトの属性名は小文字のプロパティ名です。これを変更するには、`attribute: "<目的の名前>"`を割り当てます。
- `reflect: boolean`:デフォルトでは、更新されたプロパティ値はDOMに反映されません。この動作を有効にするには、`reflect: true`を設定します。
- `type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object'`:属性値をプロパティ値に変換して反映する場合、プロパティ値はデフォルトで`String`と見なされます。これは必ずしも正確とは限りません。たとえば、数値型の場合は、`type: "Number"`を使用して定義します。すべてのプロパティをリストする必要はありません。リストされていないプロパティはデフォルト設定を使用します。
- `extend`: 引数として関数を期待するオプションのプロパティです。 Svelte によって生成されたカスタム要素クラスが渡され、カスタム要素クラスを返すことが期待されます。 これは、カスタム要素のライフサイクルに非常に具体的な要件がある場合や、たとえば <a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals#examples">ElementInternals</a> を使用して HTML フォームの統合を改善するためにクラスを拡張する場合に役立ちます。
<svelte:options
customElement={{
tag: 'custom-element',
shadow: 'none',
props: {
name: { reflect: true, type: 'Number', attribute: 'element-index' }
},
extend: (customElementConstructor) => {
// Extend the class so we can let it participate in HTML forms
return class extends customElementConstructor {
static formAssociated = true;
constructor() {
super();
this.attachedInternals = this.attachInternals();
}
// Add the function here, not below in the component so that
// it's always available, not just when the inner Svelte component
// is mounted
randomIndex() {
this.elementIndex = Math.random();
}
};
}
}}
/>
<script>
let { elementIndex, attachedInternals } = $props();
// ...
function check() {
attachedInternals.checkValidity();
}
</script>
...
注意点と制限事項
カスタム要素は、バニラHTMLとJavaScript、およびほとんどのフレームワークで動作するため、Svelte以外のアプリで使用するコンポーネントをパッケージ化するのに役立ちます。ただし、注意すべき重要な違いがいくつかあります。
- スタイルは、単に*スコープ*されるのではなく、*カプセル化*されます(`shadow: "none"`を設定しない限り)。これは、`:global(...)`修飾子を持つスタイルを含め、`global.css`ファイルにある可能性のある非コンポーネントスタイルがカスタム要素に適用されないことを意味します。
- 別の.cssファイルとして抽出される代わりに、スタイルはJavaScript文字列としてコンポーネントにインライン化されます。
- カスタム要素は、JavaScriptがロードされるまでシャドウDOMが表示されないため、一般にサーバーサイドレンダリングには適していません。
- Svelteでは、スロット化されたコンテンツは*遅延*レンダリングされます。DOMでは、*先行*レンダリングされます。つまり、コンポーネントの`<slot>`要素が`{#if ...}`ブロック内にある場合でも、常に作成されます。同様に、`{#each ...}`ブロックに`<slot>`を含めても、スロット化されたコンテンツが複数回レンダリングされることはありません。
- 非推奨の`let:`ディレクティブは無効です。カスタム要素には、スロットを埋める親コンポーネントにデータを渡す方法がないためです。
- 古いブラウザをサポートするには、ポリフィルが必要です。
- カスタム要素内の通常のSvelteコンポーネント間でSvelteのコンテキスト機能を使用できますが、カスタム要素全体では使用できません。つまり、親カスタム要素で`setContext`を使用し、子カスタム要素で`getContext`を使用してそれを読み取ることはできません。