Closed10

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

ウドウド

ガイドを読んでもいまいち挙動がわからないので自分で調べる

あとスクラップを使ってみたかった

ウドウド

とりあえず npm create vite@latest で svelte, typescript.
最小構成にしてみる。

Greet.svelte
<svelte:options customElement="my-greet" />

<script lang="ts">
  let name = "John";
</script>

<h1>Hello, {name}!</h1>
svelte.config.js
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
  }
}
vite.config.ts
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のほうの末尾を抜き出すとこんな感じ。

es.js
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が適用されている。副作用があるのでそれはそう。

index.html
<!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>
main.ts
import "@dist/ce-test.js"
vite.config.ts
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が生えていて、これを登録すれば使える。

https://svelte.dev/docs/svelte/custom-elements

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>
main.ts
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);
ウドウド

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

このスクラップは3ヶ月前にクローズされました