🌏

SvelteでWeb Componentsを作成し、CDNから配布して使う

2022/01/18に公開

モチベーション

  • SvelteでWeb Componentsを作りたい
  • 作ったWeb ComponentsをCDNで配布して、ブラウザから読み込んで使えるようにしたい

サードパーティーのスクリプトを作るのにSvelteが適している理由は以下の記事にわかりやすくまとめられています。
https://zenn.dev/mizchi/articles/8a017097d3994ddc0a85

SvelteでWeb Componentsをつくる

CounterコンポーネントとClockコンポーネントの計2つのコンポーネントを作ります。

まずはViteでアプリケーションの雛形をつくります。

yarn create vite svelte-web-components-example --template svelte-ts

lib/Counter.svelteのファイル先頭に<svelte:options tag="my-counter" />を追加してコンポーネントをカスタム要素としてコンパイルするようにします。
tagで指定した文字列が、Web Componentsの名前となります。

src/lib/Counter.svelte
<svelte:options tag="my-counter" />

<script lang="ts">
  let count: number = 0
  const increment = () => {
    count += 1
  }
</script>

<button on:click={increment}>
  Clicks: {count}
</button>

<style>
  button {
    font-family: inherit;
    font-size: inherit;
    padding: 1em 2em;
    color: #ff3e00;
    background-color: rgba(255, 62, 0, 0.1);
    border-radius: 2em;
    border: 2px solid rgba(255, 62, 0, 0);
    outline: none;
    width: 200px;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
  }

  button:focus {
    border: 2px solid #ff3e00;
  }

  button:active {
    background-color: rgba(255, 62, 0, 0.2);
  }
</style>

Clockコンポーネントを作ります。同様にファイル先頭に<svelte:options tag="my-clock" />を含めます。

src/lib/Clock.svelte
<svelte:options tag="my-clock" />

<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  let date = new Date().toLocaleTimeString();
  let interval;

  onMount(() => {
    interval = setInterval(() => {
      date = new Date().toLocaleTimeString()
    }, 1000);
  });

  onDestroy(() => {
    clearInterval(interval);
  });
</script>

<span>{date|| '' }</span>

<style>
  span {
    font-family: inherit;
    font-size: inherit;
    padding: 1em 2em;
    color: #ff3e00;
    background-color: rgba(255, 62, 0, 0.1);
    border-radius: 2em;
    border: 2px solid rgba(255, 62, 0, 0);
    outline: none;
    width: 200px;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
  }

  span:active {
    background-color: rgba(255, 62, 0, 0.2);
  }
</style>

main.tsは2つのコンポーネントをexportするようにしておきます。
今回はApp.svelteは使用しないので削除してしまって大丈夫です。

src/main.ts
export * from './lib/Clock.svelte';
export * from './lib/Counter.svelte';

index.htmlで先程作成したWeb Componentsを読み込んで表示するようにします。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Svelte + TS + Vite App</title>
  </head>
  <body>
    <my-counter></my-counter>
    <my-clock></my-clock>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

最後にvite.config.jsでコンパイルオプションを設定します。

vite.config.js
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { defineConfig } from 'vite';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    svelte({
      compilerOptions: {
        customElement: true,
      },
    }),
  ],
});

ここまでできたらyarn devで起動すると以下のように、SvelteコンポーネントとしてではなくHTML上でWeb Componentsを読み込んで表示できているはずです。

作ったWeb Componentsをnpmに公開してCDNから配布する

今回は2つのコンポーネントを作成しました。
それを1つのファイルにバンドルするようビルドのオプションをvite.config.jsに指定します。

vite.config.js
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { defineConfig } from 'vite';

// https://vitejs.dev/config/
export default defineConfig({
  build: {
    lib: {
      entry: './src/main.ts',
      name: 'MyLibrary',
      fileName: (format) => `svelte-web-components-example.${format}.js`,
    },
  },
  plugins: [
    svelte({
      compilerOptions: {
        customElement: true,
      },
    }),
  ],
});

次にnpmにライブラリとして公開するにあたって、package.jsonに以下を追加します。

package.json
  "main": "dist/svelte-web-components-example.umd.js",
  "module": "dist/svelte-web-components-example.es.js",
  "unpkg": "dist/svelte-web-components-example.umd.js",
  "files": [
    "dist"
  ],

最後に、npmに公開します。
※パッケージの名前が既に存在している名前だと公開できないので注意してください。

npm login
npm publish

ブラウザ上からCDNでWeb Componentsを読み込んで使う

作ってnpmに公開したWeb Componentsをブラウザから読み込んで使います。
npmに公開したライブラリはunpkgというCDNで配布されるので、それを利用します。
https://unpkg.com/パッケージ名@バージョン/ファイル名で読み込むことができます。
.es.umdの形式の2種類で配布するようにしましたが、ブラウザからscriptタグで読み込む場合は.umdの方を使えば良いです。

以下のHTMLだけでWeb Componentsを読み込んで動かすことができます。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Svelte Web Components Example</title>
  </head>
  <body>
    <my-counter></my-counter>
    <my-clock></my-clock>
    <script src="https://unpkg.com/svelte-web-components-example@0.0.0/dist/svelte-web-components-example.umd.js"></script>
  </body>
</html>

まとめ

Svelteで2つのWeb Componentsを作って単一のファイルにバンドルし、npmに公開してそれをCDNからscriptタグで読み込んで素のHTML上でWeb Componentsを動かすことができました。
SvelteはSPAを作るだけでなく、Web Componentを作る際にも利用することができます。
似た選択肢として、LitやStencilなどがあると思いますが、これに並ぶ選択肢なのではないかと個人的には思っています。

・Lit
https://lit.dev/

・Stencil
https://stenciljs.com/docs/introduction

今回作成したリポジトリ

https://github.com/EringiV3/svelte-web-components-example

参考

https://www.thisdot.co/blog/web-components-with-svelte

Discussion