アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Svelte 入門

morishitaです。

今回は JavaScript の UI フレームワーク Svelte を紹介します。
アクトインディでは今の所、フロントエンドフレームワークとしては Vue.js を使っていますが、これもなかなか良さそうだと思ったのでちょっと触ってみました。

プロジェクトの初期化から、次を使えるようにするまでを書きたいと思います。

  • typescript
  • pug
  • sass
  • jest
  • eslint

Svelte とは

Web フロントエンド開発のための UI フレームワークです。
2019 年にリリースされた Svelte3 で大幅にアップデートされたようです。Vue の SFC によく似たマークアップ、スクリプト、スタイルを1ファイルに記述する Svelte コンポーネントとしてアプリケーションを実装します。

ライブラリではなくコンパイラとして働き、ビルドするとブラウザで実行できる JavaScirpt と CSS を出力します。
なので、ランタイムライブラリを明示的に組み込む必要はありません。コードの中で import しなくてもビルド時に必要なコードが組み込まれます。

コンパイラとしてフレームワークの機能を注入してくれるおかげで、記述するコードは他のフレームワークより少ないと思います。そこが売りになっています。
しかも必要な機能のみ組み込まれるので、ビルド後のフットプリントも小さいです。

また、ガベージコレクターに負荷をかける Virtual DOM の差分更新は使わず、インタラクティブに DOM 更新します。

ドキュメントも必要十分に整っていると思います。
特にチュートリアルがよくできており、インタラクティブに Svelte の様々な機能を一通り学ぶことができます。
興味を持たれた方はぜひ一度やってみることを強くお勧めします。

プロジェクトの初期化

Svelte 公式のテンプレートリポジトリ sveltejs/template から degit1 を使ってプロジェクトの雛形を作ります。

次のコマンドを実行します。

$ npx degit sveltejs/template <プロジェクト名>
$ cd <プロジェクト名>
$ yarn install

次のファイル群が生成されます。

ファイルツリーを見る

❯ tree -I node_modules
.
├── README.md
├── package.json
├── public
│   ├── favicon.png
│   ├── global.css
│   └── index.html
├── rollup.config.js
├── scripts
│   └── setupTypeScript.js
├── src
│   ├── App.svelte
│   └── main.js
└── yarn.lock

Svelte コンポーネント

テンプレートに含まれる Svelte コンポーネント src/App.svelte を見てみましょう。

<script>
  export let name;
</script>

<main>
  <h1>Hello {name}!</h1>
  <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 240px;
    margin: 0 auto;
  }
  h1 {
    color: #ff3e00;
    text-transform: uppercase;
    font-size: 4em;
    font-weight: 100;
  }
  @media (min-width: 640px) {
    main {
      max-width: none;
    }
  }
</style>

基本構造は HTML になっています。
Vue の SFC の様にマークアップ部分を <template> タグで囲う必要もないです。
<script>export let name; で定義されている変数はコンポーネントの引数に入力引数となります。

で、このコンポーネントを初期化するコードが src/main.js で中身は次のとおりです。name の値がプロパティして与えられているのがわかりますね。

import App from './App.svelte';

const app = new App({
  target: document.body,
  props: {
    name: 'world'
  }
});

export default app;

動作確認

まずはこの状態で動作確認しておきましょう。
次のコマンドで開発用のサーバ起動します。

$ yarn dev

起動後、http://localhost:5000 にアクセスします。次のページが表示されれば OK です。

f:id:HeRo:20210318000216p:plain

public/build ディレクトリ内にコンパイル結果の次のファイル群ができています。

  • bundle.css
  • bundle.js
  • bundle.js.map

bundle.js にはマークアップとスクリプトの中身がコンパイルされています。コンポーネントのコンパイル結果と思われる箇所を見てみると、マークアップも JavaScript コードに変換されているのがわかります。よく見ると svelte-<hash値> という CSS クラスがつけられています。
bundle.css を見ると各定義に先程の CSS クラスが対応するように付与されています。このことからコンポーネント内の <style> の影響範囲がコンポーネント内に限定される仕組みになっていることがわかります2


これで Svelte で一応、開発はできる状態ではあるのですが JavaScript より TypeScript で書きたいし、 HTML と CSS より Pug と Sass の方が楽です。
そして、本格的に開発するなら Linter やテストフレームワークも欲しいところです。

ということで以降は、それらを順に導入していきます。

Typescript を有効にする

デフォルトは JavaScript での実装となっていますが、TypeScript で実装できるようにセットアップするスクリプトが用意されています。

実行すると、src ディレクトリ内の JavaScript が TypeScript に書き換えられます。

$ node scripts/setupTypeScript.js
$ yarn install

実行後のファイル一覧を見る

rollup.config.js 以外の .js ファイルは .ts に変換されますし、App.svelte 内も TypeScript に書き換えられています。

❯ tree -I node_modules
.
├── README.md
├── package.json
├── public
│   ├── build
│   │   ├── bundle.css
│   │   ├── bundle.js
│   │   └── bundle.js.map
│   ├── favicon.png
│   ├── global.css
│   └── index.html
├── rollup.config.js
├── src
│   ├── App.svelte
│   └── main.ts
├── tsconfig.json
└── yarn.lock


yarn dev で開発用サーバを起動して http://localhost:5000 にアクセスして表示できれば OK です。表示されるものは TypeScript を有効にする前と特に変わりません。

Pug と Sass を導入する

TypeScript を有効にすると svelte-preprocessdevDependencies に追加されています。
これが、Pug や Sass で書かれたコードの変換をサポートしているのでほとんど設定なく導入できます。

Pug

Pug は Ruby の Slim に似たシンタックスで HTML を記述できるテンプレートエンジンです。

次のコマンドで Pug をインストールします。

yarn add -D pug

src/App.svelte を開くと次のような HTML 部分があります。

<main>
  <h1>Hello {name}!</h1>
  <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

これを Pug で次の様に書き換えます。
Pug を利用す場合には、マークアップは <template> タグで囲う必要があります。

<template lang='pug'>
  main
    h1 Hello {name}!
    p
      | Visit the
      a(href="https://svelte.dev/tutorial") Svelte tutorial
      | to learn how to build Svelte apps.
</template>

これで閉じタグ無しのらくらくマークアップ記述ができるようになります。 開発サーバを起動して表示できれば OK です。

Sass

Sass も Pug と同様、モジュールを追加するだけで使えるようになります。

$ yarn add -D sass

src/App.svelte を開いて、<style> の部分を次の様に書き換えます。

<style lang='sass'>
  main
    text-align: center
    padding: 1em
    max-width: 240px
    margin: 0 auto

  h1
    color: #ff3e00
    text-transform: uppercase
    font-size: 4em
    font-weight: 100

  @media (min-width: 640px)
    main
      max-width: none
</style>

開発サーバを起動して表示できれば OK です。

Jest の導入

プロダクトを実装する上でテストコードがあるとないとでは、変更やライブラリのアップデート時の安心感が雲泥の差です。
ということでテスティングフレームワーク Jest を導入していきましょう。

まずは必要なモジュールをインストールします。

$ yarn add -D jest ts-jest ts-node @types/jest @testing-library/svelte svelte-jester @testing-library/jest-dom

Jest の設定ファイル jest.config.ts を作成します。最低限だとこんな感じです。

export default {
  clearMocks: true,
  coverageDirectory: 'coverage',
  moduleFileExtensions: ['js', 'ts', 'svelte'],
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.svelte$': ['svelte-jester', { preprocess: true }],
    '^.+\\.ts$': 'ts-jest',
    '^.+\\.js$': 'babel-jest',
  },
};

次に svelte.config.js を作成します。
rollup.config.js に同様の設定がありますが、Jest の実行には rollup は使わないので作る必要があります。

const sveltePreprocess = require("svelte-preprocess");

module.exports = {
  preprocess: sveltePreprocess({ sourceMap: true }),
};

テストコードで使う関数などの型定義を読み込ませるために tsconfig.json に次の様に設定を追加します。

{
  "extends": "@tsconfig/svelte/tsconfig.json",

  "include": ["src/**/*"],
  "exclude": ["node_modules/*", "__sapper__/*", "public/*"],
  "compilerOptions": {                      //  <= 追加
    "types": ["svelte", "jest", "node"],    //  <= 追加
  }                                         //  <= 追加
}

そして、App.svelte をテストするコード test/App.test.ts はこんな感じになります。

import { render } from '@testing-library/svelte';
import App from '../src/App.svelte';

test('should render', () => {
  const results = render(App, { props: { name: 'world' } });

  expect(() => results.getByText('Hello world!')).not.toThrow();
});

実行しやすいように package.jsonscriptstest も追加しておきましょう。

{
// 〜 略 〜
"scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "start": "sirv public",
    "validate": "svelte-check",
    "test": "jest"  // <== 追加
  },
// 〜 略 〜
}

最後に yarn test でエラーなく実行できれば OK です。

ESlint の導入

Svelte3 で ESLint を使うには eslint-plugin-svelte3 を使います3

次のコマンドでインストールします。

$ yarn add -D eslint eslint-plugin-svelte3 eslint-plugin-jest
$ ./node_modules/.bin/eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser, node
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-airbnb-base@latest
The config that you've selected requires the following dependencies:

@typescript-eslint/eslint-plugin@latest eslint-config-airbnb-base@latest eslint@^5.16.0 || ^6.8.0 || ^7.2.0 eslint-plugin-import@^2.22.1 @typescript-eslint/parser@latest
✔ Would you like to install them now with npm? · No / Yes
# 〜 略 〜
Successfully created .eslintrc.yml file in /Users/hero/Develop/javascript/svelte3-ts-pug-sass-template

$ yarn install
$ rm package-lock.json

上記を実行すると .eslintrc.js ができていますが、Svelte3 に必要な設定が含まれていないので追加します。

追加したものは次の通りです。svelte とコメントした部分が Svelte3 のための設定となります。 jest のコメントは Jest のための設定です。

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
    'jest/globals': true,             // <== svelte
  },
  extends: [
    'airbnb-base',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: [
    'svelte3',                        //<== svelte
    '@typescript-eslint',
    'jest',                           // <== jest
  ],
  overrides: [                        // <== svelte
    {                                 // <== svelte
      files: ['*.svelte'],            // <== svelte
      processor: 'svelte3/svelte3',   // <== svelte
    },                                // <== svelte
  ],                                  // <== svelte
  ignorePatterns: [
    'public/build/*',
  ],
  settings: {                         // <== svelte
    'svelte3/typescript': require('typescript'), // eslint-disable-line global-require
    'svelte3/ignore-styles': (attributes) => attributes.lang && attributes.lang === 'sass',
  },
  rules: { // これらは最小限必要と思う。その他ルールの追加はお好みで。
    'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
    'import/no-mutable-exports': 'off',
    'import/prefer-default-export': 'off',
  },
};

私は YAML 形式の .eslintrc.yml を好みますが、 settings で関数を設定する必要があり、JavaScript 形式が必須となります。

さらに次の様に packasge.json の script を追加しておくと便利です。

  "scripts": {
    "eslint": "eslint --ext svelte,js,ts ./"
  },

で、次のコマンドで、自動で修正できるところを修正しておきましょう。

$ yarn elint --fix

自動修正できない箇所は手で直しましょう。

開発をサポートしてくれるツール

開発をサポートする周辺ツールも紹介します。

REPL

Web ブラウザで気軽に試すことができます。ちょっと機能を試すのに便利そうです。 - Hello world • REPL • Svelte

エディタ拡張

VSCode のエクステンションが公式に提供されています。 - Svelte for VS Code - Visual Studio Marketplace

ブラウザ拡張

今のところ、公式のものはなさそうですが、いくつか存在します。

Svelte Devtools

Vue のVue.js devtoolsに相当する拡張。

Svelte Sight

Svelte Devtools と類似の用途の拡張だと思われますが、コンポーネント階層がグラフィカルに表示できます。 - Chrome


これで一通りの開発できる環境が整いました。

参考

おまけ

この手順を実施したあとの状態を次のリポジトリに置いておきます。

HeRoMo/svelte3-ts-pug-sass-template: Svelte3 App template with Typescript, Pug, Sass, Jest and ESLint

まとめ

Svelte はコード量を少なく高機能なコンポーネントが記述できる UI フレームワークです。
チュートリアルは本当によくできており、試しながら様々な機能を学べます。ざっと見ると、データストアやイベントハンドリングや非同期関数の扱い、<input> などの入力要素とのデータバインディング、トランジションなど他のフレームワークにある機能は一通り揃っていると思います。

Vue.js の Nuxt.js に相当する Sapperというフレームワークもあるようです。

Rails way を踏み外さない Rails のプロダクトの場合、JS のビルドには Webpacker 、つまり Webpack を使うことになるかと思います。
Rollup の代わりに Webpack を使うテンプレート sveltejs/template-webpack も用意されています。試してはないですが、それを参考にすればできるかなと思います4

さて、結構長くなりましたが、今回のエントリは前フリです。
Svelte コンポーネントは簡単に Web Components としてビルドもできます。
次回は Web Component を作ってみようと思います。

最後に

アクトインディではエンジニアを募集しています。 actindi.net


  1. Git リポジトリをローカルにコピーするコマンドラインツール。 https://github.com/Rich-Harris/degit

  2. スタイルのスコープはコンポーネントだとチュートリアルにも記述されています。コンポーネント内で別のコンポーネントを使ってもネストされた側に影響しません。また、グローバルスコープで定義する方法も用意されています。

  3. v3.1.0 で TypeScript の Svelte モジュールにも対応しました。

  4. いっそ Rails のプロジェクトとは別のリポジトリで Web Components としてコンポーネントを実装すると依存のしがらみ少なく UI を実装できるのではと思ったり…。