🔧

SvelteKit(SSR)+Custom elements環境でのページ読み込み時のガタつきを防止する

2024/05/26に公開

以下は、Svelte5 + SvelteKit(SSR) + Custom elements利用環境で発生した問題とその解決策を記しています。

概要

・Svelte で Custom elements を利用するために、svelte.config.js でコンパイラオプションに customElement: true を指定すると、ページ読み込み時に一瞬CSS未適用状態が発生しコンテンツがガタつく。

・オプションを指定しなくても Custom elements は利用可能だが、Custom elements化するコンポーネント毎に「オプションを忘れている」との警告が発生する。

svelte.config.jsvitePlugin オプションに dynamicCompileOptions を指定し、Custom elementsに対してのみ customElement: true が指定されるようにすると、ガタつきも警告も解消された。

問題

SvelteではコンポーネントをCustom elements(Web Components)にコンパイルできます。その場合、コンパイラオプションとしてcustomElement: trueを指定する必要があります。
https://svelte.jp/docs/custom-elements-api

しかし、手元の環境では、このオプションを指定するとページ読み込み時に一瞬CSS未適用状態が発生し、コンテンツのガタつきが生じます。

オプションを指定しなくても Custom elements を利用することは可能ですが、オプションの指定を忘れいてるとの趣旨の警告 The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option? がコンポーネント毎に表示されるようになります。

この方法は、おざなりな対策や保守性を低下させる小技である可能性があるため、より堅実な解決策を探す必要があります。

解決策

別件(Custom elements利用時に Vite の HMR が無効になる問題)の解決策を調べていたところ、この問題にも適用できる方法を見つけたため、それを紹介します。
https://github.com/sveltejs/vite-plugin-svelte/issues/270#issuecomment-1033190138

svelte.config.js 中の configvitePlugin オプションを指定し、その中で dynamicCompileOptions を利用し Custom elements のみに customElement: true を指定します。

svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';

+function isWebComponentSvelte(code) {
+   const svelteOptionsIdx = code.indexOf('<svelte:options ')
+   if(svelteOptionsIdx < 0) {
+       return false
+   }
+ 
+   // Svelte 4 以上の場合は 'tag:'、未満の場合は 'tag=' を指定
+   const tagOptionIdx = code.indexOf('tag:', svelteOptionsIdx)
+   const svelteOptionsEndIdx = code.indexOf('>',svelteOptionsIdx);
+   return tagOptionIdx > svelteOptionsIdx && tagOptionIdx < svelteOptionsEndIdx;
+ }

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),

+   vitePlugin: {
+     dynamicCompileOptions({code}) {
+       if (isWebComponentSvelte(code)) {
+         return {
+           customElement: true
+         }
+       }
+     }
+   },

  kit: {
    adapter: adapter()
  },
};

export default config;

この方法により、ページ読み込み時のガタつきもコンポーネント毎の警告も解消されました。問題が解決されました。

追記

Custom elements は、HMR時に以下のエラーを発生させます。

DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "<コンポーネント名>" has already been used with this registry

非SvelteKit環境において同じ問題に対処する方法は、既に以下の記事で紹介されています。
https://zenn.dev/temori/articles/b8bbb454dc0105#viteのhmrを有効にしたい

上掲の記事では、HMR時にフルリロードを行うことで、この問題を解決しています。この方法を参考に、SvelteKit環境で Custom elements のエラーを回避する設定を行います。以下の作業を行う必要があります。

・リロード設定の作成

vite-plugins/reloadSettings.ts
+ import { type PluginOption } from 'vite';
+ 
+ export default function reloadSettings(): PluginOption {
+   return {
+     name: 'sync-reload',
+     enforce: 'pre',
+     apply: 'serve',
+     handleHotUpdate({ server }) {
+       server.ws.send({
+         type: 'full-reload',
+       });
+       return [];
+     }
+   };
+ }

vite.config.js への追加

vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
import reloadSettings from './vite-plugins/reloadSettings';

export default defineConfig({
  plugins: [
    sveltekit(),
+   reloadSettings()
  ],
  test: {
    include: ['src/**/*.{test,spec}.{js,ts}']
  }
});

以上の変更により、SvelteKit環境でも Custom elements によるHMR時のエラーを回避することができます。

Discussion