Sapper で CSS フレームワークの Smelte を導入してみたのですが、結構ヒント少なめだったので残します。
Smelte is 何?
Vue.js を使う人ならよく知っている Vuetify の Svelte 版です。
Smelte はレスポンシブなマテリアルデザインコンポーネントやユーティリティを備えた UI ライブラリで、 Svelte で作成されています。
Vuetify よりもコンポーネントの数が少ないですが、その分軽量で、 Svelte の速度も相まって Web サイトが高速に動作します。
テンプレートを利用して Sapper プロジェクトを作成する
Smelte の開発者により Sapper 用のテンプレートが用意されていますので、それを使えばすぐに Smelte を使う事ができます。
$ npx degit matyunya/smelte-sapper-template my-app
$ cd my-app
$ npm run dev
いい感じにヘッダーがついてます!
(おじさんのころのテンプレートですね)
⚠ 注意
smelte-sapper-template の README のインストール方法では npx degit matyunya/smelte-template my-svelte-project
となっており、これを実行すると Sapper ではなく Svelte のプロジェクトができてしまいます。
僕はこれに気づかずに結構な時間を無駄にしてしまいました。
ご注意ください。
( README を変更するプルリクが投げられていますので、そのうち直るとは思います)
既存の Sapper プロジェクトで Smelte を使う
Smelte の公式ページや、 GitHub の リポジトリ にインストール方法が記載されていますが、これは Svelte 用です。
Sapper の場合は次の手順で利用可能です。
(基本的には上で紹介したテンプレートと同じ構成になるので、これからプロジェクトを作る場合はテンプレートを利用するほうが断然ラクです)
前提
次のバージョンで実装しています。
"devDependencies": {
"sapper": "^0.27.0",
"svelte": "^3.0.0",
}
インストール
Smelte のインストールと、設定で利用する includePaths
をインストールします。
$ npm install smelte
$ npm install -D rollup-plugin-includepaths
設定
rollup.config.js
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
をインポートするコードを書きます。
@import 'smelte/src/tailwind';
server.js
server.js
に先程作成した tailwind.css
をインポートして、アプリケーションで読み込めるようにします。
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
へ次のリンクを追加します。
<!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
Great success!
Have fun with Sapper!
I'm a button
Smelte のボタンやスライダーなどが表示されました!
黒いナビバーに変更する
テンプレートのように黒色のナビバーにしたい場合は、 _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>
Great success!
まとめ
Sapper 自体が発展途上のこともあり、 CSS フレームワークの導入も少し煩雑な感じがしました。
Svelte がもう少し流行って、このあたりの煩雑さも解消したらいいなぁと思います。
ともあれ、無事に Sapper へ Smelte を導入することができました!
この記事がどなたかの役に立つと嬉しいです