Sapper で Smelte を利用する

Sapper で CSS フレームワークの Smelte を導入してみたのですが、結構ヒント少なめだったので残します。



## [Smelte](https://smeltejs.com/?ref=madewithsvelte.com) is 何？

Vue.js を使う人ならよく知っている [Vuetify](https://vuetifyjs.com/ja/) の Svelte 版です。

Smelte はレスポンシブなマテリアルデザインコンポーネントやユーティリティを備えた UI ライブラリで、 Svelte で作成されています。
Vuetify よりもコンポーネントの数が少ないですが、その分軽量で、 Svelte の速度も相まって Web サイトが高速に動作します。



## テンプレートを利用して Sapper プロジェクトを作成する

Smelte の開発者により Sapper 用のテンプレートが用意されていますので、それを使えばすぐに Smelte を使う事ができます。

```shell
$ npx degit matyunya/smelte-sapper-template my-app
$ cd my-app
$ npm run dev
```

![template.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317946/1962f565-6472-6722-a162-374ef45c266e.png)


いい感じにヘッダーがついてます！
（おじさんのころのテンプレートですね）



### ⚠ 注意

[smelte-sapper-template](https://github.com/matyunya/smelte-sapper-template) の README のインストール方法では `npx degit matyunya/smelte-template my-svelte-project` となっており、これを実行すると Sapper ではなく Svelte のプロジェクトができてしまいます。

僕はこれに気づかずに結構な時間を無駄にしてしまいました。
ご注意ください。
（ README を変更するプルリクが投げられていますので、そのうち直るとは思います）



## 既存の Sapper プロジェクトで Smelte を使う

Smelte の公式ページや、 GitHub の [リポジトリ](https://github.com/matyunya/smelte) にインストール方法が記載されていますが、これは Svelte 用です。

Sapper の場合は次の手順で利用可能です。
（基本的には上で紹介したテンプレートと同じ構成になるので、これからプロジェクトを作る場合はテンプレートを利用するほうが断然ラクです）



### 前提

次のバージョンで実装しています。

```json
"devDependencies": {
  "sapper": "^0.27.0",
  "svelte": "^3.0.0",
}
```



### インストール

Smelte のインストールと、設定で利用する `includePaths` をインストールします。

```shell
$ npm install smelte
$ npm install -D rollup-plugin-includepaths
```



### 設定

#### rollup.config.js

`rollup.config.js` を次のように変更します。

```javascript:rollup.config.js
// 🚀 追加
import includePaths from "rollup-plugin-includepaths";
const smelte = require("smelte/rollup-plugin-smelte");

export default {
  client: {
        // ...
    plugins: [
            // ...
      svelte({
        dev,
        hydratable: true,
        emitCss: true
      }),
            
      // 🚀 追加
      !dev && smelte,
            
      resolve({
        browser: true,
        dedupe: ['svelte']
      }),
      commonjs(),
            
      // 🚀 追加
      includePaths({ paths: ["./src", "./", "./node_modules/smelte/src/"] }),
            
      // ...
    ],
    // ...
  },

  server: {
    // ...
    plugins: [
      // ...
      svelte({
        generate: 'ssr',
        dev
      }),
            
      // 🚀 追加
      smelte(),
            
      resolve({
        dedupe: ['svelte']
      }),
            
      // 🚀 追加
      includePaths({ paths: ["./src", "./", "./node_modules/smelte/src/"] }),
            
      commonjs()
    ],
    // ...
  },

  serviceworker: {
    // ...
  }
};
```

必要なパッケージのインポートと `client` と `server` それぞれに記述を追加しています。



#### tailwind.css

`src` に `tailwind.css` ファイルを作成して、 `tailwind` をインポートするコードを書きます。

```css:src/tailwind.css
@import 'smelte/src/tailwind';
```



#### server.js

`server.js` に先程作成した `tailwind.css` をインポートして、アプリケーションで読み込めるようにします。

```javascript:src/server.js
import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';

// 🚀 追加
import "./tailwind.css";

const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';

polka() // You can also use Express
  .use(
    compression({ threshold: 0 }),
    sirv('static', { dev }),
    sapper.middleware()
  )
  .listen(PORT, err => {
    if (err) console.log('error', err);
  });
```

#### template.html
アイコンを利用する場合は `template.html` へ次のリンクを追加します。

```html:src/template.html
<!doctype html>
  <html lang='en'>
  <head>
    <!-- ... -->
    %sapper.base%

    <!-- 🚀 追加 -->
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:300,400,500|Material+Icons&display=swap"
      rel="stylesheet"
    />
    
    <link rel='stylesheet' href='global.css'>
    <!-- ... -->  
  </head>
  <body>
    <!-- ... -->
  </body>
</html>
```

### 使い方

例えば、 `index.svelte` を次のように変更すると……。

```html:`src/routes/index.svelte`
<script>
  import Image from "components/Image";
  import Button from "components/Button";
  import Checkbox from "components/Checkbox";
  import Slider from "components/Slider";
</script>

<h1>Great success!</h1>

<div class="container max-w-xl items-center flex flex-col">
  <Image alt="Success Kid" src="successkid.jpg" />
  <h4 class="my-8">Have fun with Sapper!</h4>
  <div class="my-4">
    Try editing this file (src/routes/index.svelte) to test live reloading.
  </div>

  <Button>I'm a button</Button>
  <Checkbox label="I'm a checkbox" />
  <Slider label="Me be slider" />
</div>
```

![add-smelte1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317946/dad88351-4a0c-f9a3-f7e4-916e54217c89.png)


Smelte のボタンやスライダーなどが表示されました！



#### 黒いナビバーに変更する

テンプレートのように黒色のナビバーにしたい場合は、 `_layout.svelte` を次のように変更します。
（テンプレートからそのままってきました……）

```html:src/routes/_layout.svelte
<script>
  import { stores } from "@sapper/app";
  import { onMount } from "svelte";
  import { fade } from "svelte/transition";

  import AppBar from "smelte/src/components/AppBar";
  import Tabs from "smelte/src/components/Tabs";
  import Button from "smelte/src/components/Button";
  import { Spacer } from "smelte/src/components/Util";
  import List from "smelte/src/components/List";
  import ListItem from "smelte/src/components/List/ListItem.svelte";
  import NavigationDrawer from "smelte/src/components/NavigationDrawer";
  import ProgressLinear from "smelte/src/components/ProgressLinear";
  import { writable } from "svelte/store";

  const right = writable(false);
  const persistent = writable(true);
  const elevation = writable(false);
  const showNav = writable(true);

  const { preloading, page } = stores();

  let selected = "";

  $: path = $page.path;

  const menu = [{ to: "/about", text: "About" }, { to: "/blog", text: "Blog" }];

  const topMenu = [
    { to: "/about", text: "About" },
    { to: "/blog", text: "Blog" }
  ];
</script>

{#each menu as link}
  <a href={link.to} class="hidden">{link.text}</a>
{/each}

<svelte:head>
  <title>Smelte: Material design using Tailwind CSS for Svelte</title>
</svelte:head>

{#if $preloading}
  <ProgressLinear app />
{/if}

<AppBar class={i => i.replace('primary-300', 'dark-600')}>
  <a href="." class="px-2 md:px-8 flex items-center">
    <img src="/logo.png" alt="Smelte logo" width="44" />
    <h6 class="pl-3 text-white tracking-widest font-thin text-lg">SMELTE</h6>
  </a>
  <Spacer />
  <Tabs navigation items={topMenu} bind:selected={path} />
  <div class="md:hidden">
    <Button
      icon="menu"
      small
      flat
      add="text-white"
      remove="p-1 h-4 w-4"
      iconClasses={i => i.replace('p-4', 'p-3').replace('m-4', 'm-3')}
      text
      on:click={() => showNav.set(!$showNav)} />
  </div>
</AppBar>

<main
  class="container relative p-8 lg:max-w-3xl lg:ml-64 mx-auto mb-10 mt-24
  md:ml-56 md:max-w-md md:px-3"
  transition:fade={{ duration: 300 }}>
  <NavigationDrawer
    bind:show={$showNav}
    right={$right}
    persistent={$persistent}
    elevation={$elevation}>
    <h6 class="p-6 ml-1 pb-2 text-xs text-gray-900">Menu</h6>
    <List items={menu}>
      <span slot="item" let:item class="cursor-pointer">
        <a href={item.to}>
          <ListItem
            selected={path.includes(item.to)}
            {...item}
            dense
            navigation />
        </a>
      </span>
    </List>
    <hr />
  </NavigationDrawer>

  <slot />
</main>
```

![add-smelte2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317946/42190a55-5f1e-09d6-527e-874c4effe2f4.png)

**Great success!**

## まとめ

Sapper 自体が発展途上のこともあり、 CSS フレームワークの導入も少し煩雑な感じがしました。
Svelte がもう少し流行って、このあたりの煩雑さも解消したらいいなぁと思います。

ともあれ、無事に Sapper へ Smelte を導入することができました！
この記事がどなたかの役に立つと嬉しいです :yum: