SvelteのcustomElementについてしらべるぞ

ガイドを読んでもいまいち挙動がわからないので自分で調べる
あとスクラップを使ってみたかった

とりあえず npm create vite@latest
で svelte, typescript.
最小構成にしてみる。
<svelte:options customElement="my-greet" />
<script lang="ts">
let name = "John";
</script>
<h1>Hello, {name}!</h1>
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
compilerOptions: {
customElement: true
}
}
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vite.dev/config/
export default defineConfig({
build: {
lib: {
entry: "./src/lib/Greet.svelte",
name: "Greet"
},
},
plugins: [svelte()],
})
これでビルドが通って、esとumdでビルドができる。中を見てみる。

esのほうの末尾を抜き出すとこんな感じ。
var In = /* @__PURE__ */ On("<h1></h1>");
function Ln(t) {
var e = In();
e.textContent = "Hello, John!", ge(t, e);
}
customElements.define("my-greet", Fn(Ln, {}, [], [], !0));
export {
Ln as default
};
customElements.define("my-greet", Fn(Ln, {}, [], [], !0));
という文があるので副作用があり、何らかをデフォルトエクスポートしている。
umdのほうはよくわからないけど、多分vite.config.tsで定義した名前(Greet)でesと同じものが使えるはず。

viteのdevモードで動作を見てみると、単にimportするだけでcustomElementが適用されている。副作用があるのでそれはそう。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS</title>
</head>
<body>
<my-greet></my-greet>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
import "@dist/ce-test.js"
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vite.dev/config/
export default defineConfig({
build: {
lib: {
entry: "./src/lib/Greet.svelte",
name: "Greet"
},
},
+ resolve: {
+ alias: {
+ "@dist": "../dist"
+ }
+ },
plugins: [svelte({})],
})

エクスポートするものの正体はドキュメントに書いてあった。element
という名前のstatic propertyが生えていて、これを登録すれば使える。
import MyElement from './MyElement.svelte';
customElements.define('my-element', MyElement.element);
とすれば好きな名前で使える。同じ名前で登録するとエラーになる。
型補完は効かない。なぜ。

コンポーネントの名前を決めてしまいたくない場合、<svelte:option>
セクションを単に削除すればよさそう。こうすることでビルド生成物からcustomElements.define()
がなくなる。
<script lang="ts">
let name = "John";
</script>
<h1>Hello, {name}!</h1>
import MyGreet from './lib/greet.svelte';
customElements.define('my-greet, MyGreet.element);
これだけでいい。

この element
というstatic propertyはsvelte.config.jsで compilerOptions: {customElement: true }
を指定した場合にだけ生成される様子。svelteファイルの <svelte:options customElement="hoge" />
の有無は関係がない。

型定義ファイルの中に潜った結果、次のように書けば型エラーが消せることがわかった。
import MyGreet from "./lib/Greet.svelte"
import type { Component } from "svelte";
customElements.define("my-greet", (MyGreet as Component).element!);
svelteのindex.d.tsの中に次のようにある。
declare module '*.svelte' {
// use prettier-ignore for a while because of https://github.com/sveltejs/language-tools/commit/026111228b5814a9109cc4d779d37fb02955fb8b
// prettier-ignore
import { SvelteComponent } from 'svelte'
import { LegacyComponentType } from 'svelte/legacy';
const Comp: LegacyComponentType;
type Comp = SvelteComponent;
export default Comp;
}
このSvelteComponent
についてJSDocに次のようにある。
This was the base class for Svelte components in Svelte 4.
この型定義がsvelte5.xにも同梱されている。なぜ。

この element
はただのクラスなので、次のように拡張もできる。
import MyGreet from "./lib/Greet.svelte"
import type { Component } from "svelte";
const MyComponent = (MyGreet as Component).element!
class MyExtendGreet extends MyComponent {
constructor() {
super()
//@ts-ignore
this.name = "Alice"
}
}
customElements.define("my-greet", MyExtendGreet);

わかってきた。記事にまとめる