🍰

SvelteってReactやVueと比べてどうなの? フロントエンド実装してみた

2023/12/30に公開
4

筆者は4年目のWebコーダー/フロントエンドエンジニアである。

ある日Reactでフロントエンドをつくっていたが、コンポーネントを拡張するうちに微妙なストレスがチリツモでジワジワと溜まってきて、ついに爆発した。

「なんかも~めちゃくちゃ書きやすくて開発&保守の両方がしやすいフレームワーク無いのかな?!!!」

と調べたり、ChatGPTと相談し合ったりするうちに、Svelteというフロントエンドフレームワークにたどり着いた。

大げさかもしれないが、ちょっと触ってみてすぐに魂が激震するような感動を覚えた。

ちなみに筆者が触れたことがあるのは
(フレームワーク&ライブラリ入り混じる)

  • JavaScript...React.js、Vue.js、jQuery
  • PHP...Laravel、CakePHP、Zend
  • CMS...WordPress、MovableType、WebRelease

などで、振り返ると今となっては書くのもためらわれるような黒歴史なレガシー技術もあったりする……。

そんな中でもこれまでの感動した技術の遍歴を辿ってみると、

  • マークアップにおいてはScss(CSS拡張言語)
  • フロントエンドにおいてはVue
  • フロント+バックエンドにおいてはLaravel

などで、その圧倒的な記述量の少なさ、コードの可読性
それゆえに産まれる保守性・拡張性の高さに安心感と魅力を感じた。

あくまでもそんな好みと経歴に偏りのある筆者が書いた記事であるということを念頭においてほしい……(予防線)

しかし、それにしてもSvelteの感動はこれまでを遥かに上回っていた。
感動と情熱だけを頼りに、この記事を書いてみることにする。

Svelteって一体何なの?

2016年にRich Harrisによって開発されたJavaScriptのフロントエンドライブラリ/フレームワーク/コンパイラであり、近年世界中で注目を集め始めている。

公式サイトによると、stackoverflowの2023年の調査上で
最も賞賛されているJS Webフレームワークにもなったようだ。
https://survey.stackoverflow.co/2023/#section-admired-and-desired-web-frameworks-and-technologies

SvelteKitって?

Svelteをビルドする公式の方法として、SvelteKit(前身:Sapper)がある。
SvelteKitはブラウザに送信されるコードの量をめっちゃ削減してくれる。

基本的にはSvelte+SvelteKitはセットで使うものになる。

まだ日本での知名度は低く、日本語のブログやコミュニティは充実していないが、公式のドキュメントは既にわかりやすい日本語に翻訳されており、チュートリアルやサンプル情報も充実している。

ちょっと素敵なものを加えただけの HTML

そして、とにかくコードがシンプルで記述量が少なくて済み、HTML・CSS・JSの理解さえあれば概ね理解できるほどに可読性が高い。

公式チュートリアルでも
「Svelteは、実際にはちょっと素敵なものを加えただけのHTML」
と表現しているように(全然ちょっとではないが、この表現がとても良い)、

SvelteファイルにおいてはHTMLファイル・HTMLタグ内で可能な記述は概ねそのまま再現することができる。

実際どんな感じなの?

公式チュートリアルから引用すると、あるページ/ないしはコンポーネントを定義するApp.svelteファイルの中身はこんな感じになる。

<script>
	let m = { x: 0, y: 0 };

	function handleMove(event) {
		m.x = event.clientX;
		m.y = event.clientY;
	}
</script>

<div on:pointermove={handleMove}>
	The pointer is at {m.x} x {m.y}
</div>

<style>
	div {
		position: fixed;
		left: 0;
		top: 0;
		width: 100%;
		height: 100%;
		padding: 1rem;
	}
</style>

公式チュートリアル(Part1 Events/DOM events)より

各ファイルにはスクリプト、HTML、スタイル要素が順番に定義されている。

スクリプト、スタイルは省略可能。
HTMLにラッパーとなる親要素は必要ない。(好き)

ちなみに、Vueとほぼ同様であるが、
初期セットアップが実施されていれば

<script lang="ts"></script>

このように書けばTypeScriptでの記述が可能。

<style lang="scss"></style>

CSSについてもSassやScssを書くことができる。

Vueならかなり似た形にできるが、記述量に若干の差異が出てくる。
チリツモで開発体験の差につながるように感じた。

▼Vueの場合(雰囲気)

<template>
  <div @mousemove="handleMove">
    The pointer is at {{ m.x }} x {{ m.y }}
  </div>
</template>

<script>
import { reactive } from 'vue';

export default {
  setup() {
    const m = reactive({ x: 0, y: 0 });

    function handleMove(event) {
      m.x = event.clientX;
      m.y = event.clientY;
    }

    return { m, handleMove };
  }
};
</script>

<style>
div {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  padding: 1rem;
}
</style>

Reactと比べた時のほうが違いがわかりやすいような気がする。

▼Reactの場合(雰囲気)

import React, { useState } from 'react';
import styled from "styled-components";

const Container = styled.section`
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  padding: 1rem;
`;

function PointerComponent() {
  const [m, setM] = useState({ x: 0, y: 0 });

  function handleMove(event) {
    setM({ x: event.clientX, y: event.clientY });
  }

  return (
    <Container onMouseMove={handleMove}>
      The pointer is at {m.x} x {m.y}
    </Container>
  );
}

export default PointerComponent;

フロントエンド実装してみた

実際にSvelteを使って、筆者の昔の絵を詰め込んだWebサイトのフロントエンドをリニューアルしてみた。

🧠Eru's Gallery

筆者の絵が怖いのはどうか許してほしい。

たとえばaboutページのprofileコンポーネントの記述例。

▼Profile.svelte

<script lang="ts">
	import H2 from '$lib/components/heading/H2.svelte';
	import enText from '$lib/data/about/profile/_en.json';
	import jaText from '$lib/data/about/profile/_ja.json';

	import { locale } from '$lib/stores';
	let body = enText;
	$: body = $locale === 'en' ? enText : jaText;
</script>

<H2 id="profile" text={body.name} />
<section class="profile-container">
	<figure class="imgBox">
		<img src={body.image} alt="profile" width={350} height={350} />
	</figure>
	<div class="TextBox clearfix">
		<p class="text">{body.text}</p>
	</div>
</section>

<style lang="scss">
	@use '$lib/styles/foundation/mixin';

	.imgBox {
		float: left;
		margin-right: 5rem;
		shape-outside: circle();
		border-radius: 100%;
		width: 35%;
		height: auto;
		img {
			border-radius: 100%;
			width: 100%;
			height: auto;
		}
	}
</style>

特にこの部分が特徴的かなと思う。
$をつけることで、リアクティブな宣言を行うことができる。

	$: body = $locale === 'en' ? enText : jaText;

ここの$マークはパスのエイリアスだ。

	import H2 from '$lib/components/heading/H2.svelte';

React, Vueでも設定すればパスエイリアスは使うことができるが、デフォルトなのが地味に嬉しいところ。

今回は日・英の2言語で実装したかったので、
storeという状態管理の機能を使用して、ユーザーの言語設定に応じた言語データを返すようにした。
(store機能ではRedux等のようにアプリケーション全体の状態を管理できる)

	import { locale } from '$lib/stores';

実装してみての個人的な感想

普段なら3日かかるようなフロントの実装が1日で終わり、筆者としては生産性が爆上がりで心の底から感動した。

中でもとにかくJSを書くのが楽。JSの記述量が圧倒的に減る。

今まで作りたいけど作るの面倒くさいなと思って放置していた個人開発もSvelteのおかげで俄然やる気が出て、いろいろ再開し始めた。

ただし、前述のイラストサイトに関しては、Svelteのせいではなくて
個人的な知識不足で以下のような課題を抱えているので一応共有しておく。

  • 初期読み込み

初期読み込みが終わってからのサクサク感は嬉しい限りなのだが、現状では初期読み込みが完了する前にページを描画しているので、完了するまでギャラリー等のJSが関わる操作ができず閲覧者を戸惑わせてしまう可能性がある。

→ 追々ロード画面を用意する、初期読み込みを高速化できるよう工夫する。イラスト画像もWebpファイル化して軽くしたが、もっと軽くできないか…

  • ブログ個別ページにまつわるバグ

ブログデータを納めたマークダウンファイルからブログページを自動生成するためのバックエンドスクリプトを書いてみたが、Vercel上でのみうまく動作せずENOENTとなる。
(なのでブログページのリンクは一旦消している)

→ Vercelのサーバレス関数に対する理解不足かもしれない。Vercel上でしか再現しないのでサーバを複製して引き続き調査する。

開発者のRich Harrisってどんな人なんだろ?

これほどまでに素晴らしい開発体験を与えてくれるSvelteの開発者、Rich Harrisって一体何者なの…??とふと気になった。

Rich Harrisはジャーナリスト、ウェブプロデューサーとしての略歴も持っていて、Svelte開発に専念するため、2021年からNext.jsでお馴染みのVercel社に加わっている。
https://rich.ip.new/

なんと大手新聞社ガーディアン紙にいた略歴もあり、ロンドンのユニバーシティ・カレッジで哲学を学んでいたそう。
(世界ランキングにも入る一流大学の、しかも哲学科卒…!面白い方だ😯)

Svelteの前身となるRactive.jsの開発者でもあるらしい。
※読み間違えそうになるが、リアクティブではなくラクティブ。海外では密かにファンの多い軽量フレームワークのようだ

インタビューをいくつか拝読してみて感じたこととしては、Rich Harrisは凡人には考えられないほど広い視野を持っている方で、開発者だけでなく様々なポジションの開発従事者の気持ちがわかっている、聡明で心やさしい方だ。

他のフレームワークの良い部分を取り入れつつ、「もっとこうあってほしい」という開発者たちの願いを叶え、かゆい部分に手が届くようなフレームワークを作り上げたRich Harrisにほんっっっとうに心から感謝と敬礼を捧げたい🙏

Svelteまとめ

  • とにかく見やすい!書きやすい!軽量!
  • 世界的に人気が出ており、これから主要フレームワークとなっていく可能性がある
  • 好き!!!!!

現時点での筆者からお伝えできるのはこんなところだろうか。

個人開発はもちろんのこと、デザイナーさんやコーダーさんにマークアップを別途依頼したい場合や、チーム開発時にもかなりアドバンテージがあるのではないかと思う。

今回ReactやVueと比べてはみたが、それぞれの価値や魅力は今後ももちろん変わらないと思っている。
そんな中でも、少しでもSvelteの特異な魅力が伝わっていたらそれほど幸せなことはない。

以上。最後までお読みいただきありがとうございました!🙇✨

Discussion

Honey32Honey32

失礼します。重箱の隅かもしれませんが、

body の文字列をロケールによって切り替えるのは、以下のようにするほうが短くて明瞭だと思います。(「手続き的」な記述を「宣言的」な記述に置き換えているので)

    import { locale } from '$lib/stores';
-   let body = enText;
-   $: {
-     body = $locale === 'en' ? enText : jaText;
-   }

+   $: body = $locale === 'en' ? enText : jaText;

そしてこれは React においても同様です。useState + useEffect は不要で、ただのレンダリング途中で毎回計算するほうが良いです。 (Vue だと watch ではなく computed を使うことになると思います)

    const locale = useLocale();
-   const [body, setBody] = useState(enText);
-   useEffect(() => {
-     setBody(locale === "en" ? enText : jaText);
-   }, [locale]);

+   const body = locale === "en" ? enText : jaText;   

https://qiita.com/honey32/items/58e56e407d4d87e294a4

つじの えるつじの える

Honey32様、ご指摘誠にありがとうございます…!!

🐻Svelteのリアクティブ宣言の記述について

「手続き的」な記述を「宣言的」な記述に置き換えている

➡ハッ、確かに…!
 他にも手続きをしていたのを削除した名残りな気がします。
 修正いたします!

🐻React&Vueの記述について

そしてこれは React においても同様です。useState + useEffect は不要で、ただのレンダリング途中で毎回計算するほうが良いです。 (Vue だと watch ではなく computed を使うことになると思います)

➡そ、そっか確かに…!!
 雰囲気だけすぎて反省しています。。修正してみます!

記事を拝読してしっかり基礎固めさせていただきます✨
ご丁寧に教えていただきありがとうざいました!

ShoutaShouta

イラスト画像もWebpファイル化して軽くしたが、もっと軽くできないか…

今回の場合ですと、画像の容量による遅延というよりはCSS内のbackgroud-imageで読み込んでいるのが問題だと思うので、imgタグから読み込むようにするのが良いと思います。なのでそれ以上軽量化を頑張る必要はないと思います。
どうしても軽量化したいのであれば.avifに変換すればもう少し小さくなるとは思いますが、高速化が見込めるかと言われると、うーん...という感じです。

つじの えるつじの える

Shouta様、ご意見ありがとうございます…!!

なのでそれ以上軽量化を頑張る必要はないと思います。

➡ありがとうございます😭✨
今回background-imageはプロフィールの1か所だけにとどめてて
基本imgタグなのですが、その割にはLighthouseやSpeed Insightの
Performance数値がちょっと悪くて凹みました、、が、励まされました。

avifはチャレンジしたことなかったです、良いですね…!

確かに仰る通り、これ以上軽量化するというよりは
遅延処理を行うなど別のアプローチを取るべきなのかもしれません🤔
いろいろ角度を変えて試してみようと思います!✨