svelteの場合はhtmlに対して以下のcssを追加すれば解決できます。
それがないとjsのpull to refreshが上手く作動しないケースがあるので追加したほうがいいと思います。html { overscroll-behavior: none; }
1現在メジャーでは二つの書き方がある
- 表示するよと宣言する
Icon
と具体的なアイコン内容PencilSquare
InformationCircle
をそれぞれimportして、ペアで使うパターン
<script> import { Icon, PencilSquare, InformationCircle } from 'svelte-hero-icons'; </script> <Icon src={PencilSquare} /> <Icon src={InformationCircle} />
- 表示したいアイコンだけをimportして使うパターン
<script> import { PencilSquare, InformationCircle } from 'svelte-hero-icons-example'; </script> <PencilSquare /> <InformationCircle />
1のほうはいちいち
Icon
を導入しないといけないのがめんどくさいように見えるけど、個人的には圧倒的に1の書き方のほうがいいと考えている。なぜなら、メンテナンスしやすいからだ。例えば表示するアイコンを変えたいときにVSCodeで<Icon src=
を検索すれば、コードのどこにどんなアイコンが使われてるかはすぐわかる。逆にアイコン名だけでやる場合はどんなアイコンを導入したかを確認してから検索しないといけない、圧倒的に面倒だ。- 表示するよと宣言する
https://github.com/ghostdevv/svelte-turnstile
svelte-turnstileを使ったほうが手取り早いです。ここでは一番よく使うパターンを紹介します。
- フロントエンドでトークンを獲得して、turnstileTokenに保存
<script> import { Turnstile } from 'svelte-turnstile'; let turnstileToken = ''; </script> <form method="POST" action="/login"> <Turnstile on:turnstile-callback={(event) => (turnstileToken = event.detail.token)} siteKey="SITE_KEY" /> </form>
- フロントエンドで獲得したトークンをサーバーに問い合わせ、成功してなかったら不正なアクセスだと返し、成功してたら通常な処理を行う
interface TokenValidateResponse { 'error-codes': string[]; success: boolean; action: string; cdata: string; } async function validateToken(token: string, secret: string) { const response = await fetch( 'https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ response: token, secret: secret, }), }, ); const data: TokenValidateResponse = await response.json(); return { success: data.success, error: data['error-codes']?.length ? data['error-codes'][0] : null, }; } const turnstileSecretKey = import.meta.env.VITE_TURNSTILE_SECRET_KEY; //envで保存したセキュリティキー /** @type {import('./$types').RequestHandler} */ export async function POST({ request, cookies }) { const data = await request.json(); const turnstileToken = data.turnstileToken; const { success, error } = await validateToken(turnstileToken, turnstileSecretKey); if (!success) { return new Response(JSON.stringify({ 'error': '不正なアクセス' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); } //こっから通常処理 }
ネットにあるものを優先してアクセスする設定例を載せました。
import { precacheAndRoute } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'; import { ExpirationPlugin } from 'workbox-expiration'; declare let self: ServiceWorkerGlobalScope precacheAndRoute(self.__WB_MANIFEST) //キャッシュ化することでロードスピードを向上 // ネットに接続することを優先 registerRoute( ({ request }) => request.mode === 'navigate', new NetworkFirst() ); // CSSとJSに対して既存のものを使いつつ、アップデートがあれば更新する registerRoute( ({ request }) => request.destination === 'style' || request.destination === 'script', new StaleWhileRevalidate() ); // 画像をキャッシュ化する、保存期間は7日間 registerRoute( ({ request }) => request.destination === 'image', new StaleWhileRevalidate({ cacheName: 'image-cache', plugins: [ new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 7 * 24 * 60 * 60, }), ], }) );
tsconfig.jsonに以下のtypeを追加
"compilerOptions": { "types": [ "vite-plugin-pwa/client", "vite-plugin-pwa/info", ]
現時点でSvelteKitPWA 0.4でvite.config.tsでworkboxに関するルールを設定する場合はすべて無視されてしまいます。またホームページをキャッシュ化するので、リアルに更新する系のアプリは使用するのをやめたほうがいいです
VitePWAで設定してもそんなに時間かからないので、VitePWAを使ったほうがよいかと思います
以下はSSRの場合の解決方法の例です。
dataは+page.server.tsから共有されるものでタイプはpageData、どうやらフロントエンドで追加したデータは自動的には保存されないので(もう一回ロード処理してるだけかもし、中身はどう動いてるかは正直わからない)、dataを保存しないと最初にロードした最後のところまでにしか戻れない。
await tick();はDOMの更新を待つための非同期処理、それが終わる前に保存したyに戻ろうとするとずれてしまいます。export const snapshot: Snapshot = { capture: () => { return { data, scrollPosition: window.scrollY }; }, restore: async (value) => { data = value.data; await tick(); window.scroll(0, value.scrollPosition); } };
また、公式の例は以下通り
https://github.com/Rich-Harris/sveltesnaps/blob/main/src/lib/components/Scroller.svelte#L6C1-L27C3
https://github.com/Rich-Harris/sveltesnaps/blob/main/src/routes/%2Bpage.svelte#L15C1-L30C4sveltekitでは現在にいる同じページのリンクをクリックする際に、データは自動的に更新されません。要は+page.server.tsのロード関数が実行されないです。
例をいうと今はページ/hoge1にいて、そのページに/hoge1へのリンクがあるとしよう、その時に/hoge1をクリックするとページの一番上には飛びますが、データは更新されないです。
解決方法としては下記の通り
data-sveltekit-reload
タグをつけることで、挙動はrel="external"をつけるのと同じ効果のように見えます。<a data-sveltekit-reload href="/hoge1">Path</a>
ただこの方法だとページ全体がリロードされます。
もう一つの方法invalidateを使うことです。
https://kit.svelte.dev/docs/modules#$app-navigation-invalidate (このリンクの内容を単体で読むとわけわからなくなるので下のと合わせて読むことをお勧めします。
https://kit.svelte.jp/docs/load#rerunning-load-functions-manual-invalidation- invalidate単体だと指定するするload関数だけが実行されます。
- invalidateAllだとそのページとかかわるすべてのload関数が実行されます。
<a on:click={() => invalidateAll()} href="/hoge1">Path</a>
ただその方法だと挙動がちょっと怪しくて、複数の/hoge1のタブを開く場合はすべての/hoge1がリロードされてしまいます。(少なくともchrome+sveltekit2.5.5のDEV環境ではそうなる)また、/hoge2などには影響しません。
メジャーな言語の解析しか要らないので以下のように絞りました。
それでほとんどのケースでは問題ないはず。import MarkdownIt from 'markdown-it' import hljs from 'highlight.js/lib/core'; import 'highlight.js/styles/github-dark-dimmed.css'; import javascript from 'highlight.js/lib/languages/javascript'; import python from 'highlight.js/lib/languages/python'; hljs.registerLanguage('javascript', javascript); hljs.registerLanguage('python', python); export const md: any = new MarkdownIt({ highlight: function (str, lang) { // Syntax highlighting configuration if (lang && hljs.getLanguage(lang)) { try { return '<pre class="hljs"><code>' + hljs.highlight(str, { language: lang, ignoreIllegals: true }).value + '</code></pre>'; } catch (__) { } } return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'; }, })
v2.0.0でimport { env } from '$env/dynamic/public'する場合は例のバグが発生してしまいます。sveltekitの問題ではなくて、Safariのバグだそうです。なおv2.3.2で対応済みでupgradeすると治ります。
ref: https://github.com/sveltejs/kit/issues/11364
余談だけどそのバグにめっちゃくちゃ悩まされました。デスクトップでのテストは全く問題なかったのですが、safariではバグりまくりでした。
https://github.com/spences10/sveltekit-embed
<script> import { Tweet } from 'sveltekit-embed' </script> <Tweet tweetLink="adamwathan/status/959078631434731521" />
<script> import { YouTube } from 'sveltekit-embed' </script> <YouTube youTubeId="L7_z8rcbFPg" />
のような書き方ができます。
Markdown-itなど直接Componentとして解析が難しい場合はtweet.svelteなどに参照するといいです、例のライブライでは一つのファイルで必要な機能を全部完結しているので。余談ですが、そのライブライが対応しているサービスに知らないものがいっぱいあってちょっとびっくり、世界って広いですね。日本の場合はTweet、Youtube、Spotify、Slidesだけ対応しておけば大丈夫なイメージがありますが。
https://github.com/wobsoriano/svelte-sonner
https://github.com/zerodevx/svelte-toast
https://github.com/kbrgl/svelte-french-toast上記の三つは1年内以内に更新されているもので、実際に使ったことがあるのはsvelte-toastとsvelte-sonner。
svelte-sonnerのほうが書きやすくてモダンだと思う。
svelte-toastはtoast.push()
のような書き方で、succes、errorで分けて書きたいときはちょっとめんどくさい
svelte-sonnerは
toast.sucess()
toast.error()ような書き方ができ、場合分けのtoastを作りたい場合は便利
svelte-french-toastはDEMOを見た感じではよさそうで書き方はsvelte-sonnerと近くて、使いやすい気がします
Svelteのアプリケーションで全ページに渡ってデータを共有する際、
svelte/store
モジュールのwritable
ストアを使用するのが一般的です。これにより、状態管理が簡単になります。しかし、ブラウザがリフレッシュされると、これらのストアのデータはリセットされてしまいます。この問題を解決するために、ブラウザのLocalStorageやSessionStorageにデータを保存する方法がありますが、これを手動で行うのは煩わしい作業です。
そこで、
svelte-persisted-store
ライブラリを使用することをおすすめします。このライブラリを使用すると、Svelteのwritable
ストアのデータを自動的にLocalStorageまたはSessionStorageに保存し、アプリケーションのリフレッシュ後も状態を保持することができます。詳しい使い方については、svelte-persisted-storeのGitHubリポジトリのREADMEを参照するといいでしょう。
onMount
<script>の中のコードに関しては、サーバー側で動くものか、クライアント側で動くものかを意識する必要があります。クライアント側で動くロジックは必ずonMountの中に書かないといけない。また、console.logでログを取る際に、クライアント側実行だったらブラウザ、サーバー側実行ならコマンドラインで確認する必要があります。最初はそれがわからなくてなんで出力されないんだって戸惑っていました。UIライブラリの選定
PCだけでなく必ずスマホでもコンポーネントの挙動を確認したほうがいいです。dropdown/drawのような新しいウィンドウを開く系のものはPC上では動く自動で閉じてもスマホのsafariで上手く動かなかったりする。svelteはエコシステムはreactなどにまだ勝てないため、そもそも選べるものが少なく、現時点でshadcn-svelteが一番いいかなとデプロイ
プロダクション環境に切り替える際は、アダプターを@sveltejs/adapter-nodeに切り替える必要があります。linuxサーバー上で動かす際はpm2を使うといいでしょう、csrfをオンにする場合はパラメータにORIGIN=https://hogehoge.com
を入れるといいでしょう。またアップロードなどが存在する場合、手動でBODY_SIZE_LIMITを設定する必要があります。LinkOption
https://kit.svelte.jp/docs/link-options
デフォルトではdata-sveltekit-preload-codeになっているため、マウスがリンクの上に移動したらそのページのデータをダウンロードし始めてしまいます。通信料でかかるクラウドを使って料金を気にする場合は変更するといいでしょう。メンテナンスされている・そこそこきれいなものを選んでいます
flowbite-svelte
https://github.com/themesberg/flowbite-svelte
コンポーネントはデスクトップではちゃんと動きますが、dropdown/drawなどはiOSのsafariでうまく閉じない不具合が存在しています、手動で調整する必要がありそこまでめんどくさいわけではありません。shadcn-svelte
https://github.com/huntabyte/shadcn-svelte
flowbiteはまだealry devなので、こっちに乗り換えようかなと検討中。iOSのsafariでテストしたのですが、flowbiteのような不具合はありませんでした。skeleton UI
https://github.com/skeletonlabs/skeleton
部品は色々揃っているのですが、使い方に結構癖があります。カスタム性が高いように見えますが、意外と弄りにくい部分もあるので、私は2回試そうと思って2回とも諦めました。melt-ui
https://github.com/melt-ui/melt-ui
結構コードを書かないといけないので、スピード重視の開発には向いてないでしょう。shadcn-svelteはこちらのwrappperなのでスピード重視ならそちらを使った方がいいかもsvelte-headlessui
https://github.com/rgossiaux/svelte-headlessui
ちょっと部品が少ないcarbon-components-svelte
https://github.com/carbon-design-system/carbon-components-svelte
2Bのためのサイトならいいかもしれないが、2Cはちょっと厳しいかなsveltestrap
https://github.com/bestguy/sveltestrap
若干まだ未完成品のように見えます。svelte-material-ui
https://github.com/hperrin/svelte-material-ui
私の知っているMUIではありません...