thumbnail
thumbnail

【Svelte】Typescriptで入門する

updated 2021-8-27

はじめに

ViteでVueのプロジェクトをビルドするたびにSvelteが気になっていたのと、Svelteのコード量の少なさに魅かれたので試していきます

Svelte

Svelte公式
Svelte公式 日本語訳 <-最近できた

Svelteの特徴は

コード量が少ない
仮想DOMじゃない
Reactive

Svelteで書いたコードはフレームワークではないシンプルなJavascript(VanillaJS)に出力される
ReactやVueと大きく違うところは仮想DOMではないってところだと思う
仮想DOMは早いぞ!って謳われているのを聞いてきたので差分取らないから遅いんじゃないの!って思った

どれくらい少ないか

一番シンプルなカウンターで比べてみる

React

import React, { useState } from 'react';

export default function Counter() {
  const [count, counter] = useState(0);

  return (
    <div>
      <button onClick={() => counter(count + 1)}>count:{count}</button>
    </div>
  );
}

Vue

<template>
  <button v-on:click="counter">count:{{count}}</button>
</template>

<script>
  export default {
    data: () => ({
      count: 0,
    }),
    methods: {
      counter() {
        this.count++;
      }
    }
  }
</script>

Svelte

<script>
  let count = 0;
  function counter() {
    count++;
  }
</script>
<button on:click={counter}></button>

確かにかなりコード量は少ない(フレームワークとして見たら...)

仮想DOMとは

小さいDOMツリーを作って差分ロードしますよって仕組み
Virtual DOMのことでShadow DOM(CSSをスコープ化する技術)ではない

svelteの開発者によると仮想DOMは早いが余計な処理が増えるとのこと
そして仮想DOMを使わなくても同じプログラミングモデルが実現できるよう
規模が大きくなればその分差分も増えるってことで、その方法以外にあるならそっちの方が良い

インストールと起動

環境

Node v15.14.0

インストール

npx degit sveltejs/template [プロジェクトの名前]

フォルダができたら

cd [プロジェクトの名前]
yarn

yarnのとこはnpm installでも良い

起動

yarn dev
or
npm run dev

Svelte Hello World

...はっや
まあ、起動だけなら変化もないし仮想DOMの比較にはならないけど

svelteでTypescriptを使う

公式で設定ファイルを書き換えてくれるスクリプトがあるので使います
公式を見に行く

node scripts/setupTypescript.js
scripts/setupTypescript.jsの中身
// @ts-check

/** This script modifies the project to support TS code in .svelte files like:
  <script lang="ts">
  	export let name: string;
  </script>
 
  As well as validating the code for CI.
  */

/**  To work on this script:
  rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
*/

const fs = require("fs")
const path = require("path")
const { argv } = require("process")

const projectRoot = argv[2] || path.join(__dirname, "..")

// Add deps to pkg.json
const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
  "svelte-check": "^2.0.0",
  "svelte-preprocess": "^4.0.0",
  "@rollup/plugin-typescript": "^8.0.0",
  "typescript": "^4.0.0",
  "tslib": "^2.0.0",
  "@tsconfig/svelte": "^2.0.0"
})

// Add script for checking
packageJSON.scripts = Object.assign(packageJSON.scripts, {
  "check": "svelte-check --tsconfig ./tsconfig.json"
})

// Write the package JSON
fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, "  "))

// mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
fs.renameSync(beforeMainJSPath, afterMainTSPath)

// Switch the app.svelte file to use TS
const appSveltePath = path.join(projectRoot, "src", "App.svelte")
let appFile = fs.readFileSync(appSveltePath, "utf8")
appFile = appFile.replace("<script>", '<script lang="ts">')
appFile = appFile.replace("export let name;", 'export let name: string;')
fs.writeFileSync(appSveltePath, appFile)

// Edit rollup config
const rollupConfigPath = path.join(projectRoot, "rollup.config.js")
let rollupConfig = fs.readFileSync(rollupConfigPath, "utf8")

// Edit imports
rollupConfig = rollupConfig.replace(`'rollup-plugin-terser';`, `'rollup-plugin-terser';
import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';`)

// Replace name of entry point
rollupConfig = rollupConfig.replace(`'src/main.js'`, `'src/main.ts'`)

// Add preprocessor
rollupConfig = rollupConfig.replace(
  'compilerOptions:',
  'preprocess: sveltePreprocess({ sourceMap: !production }),\n\t\t\tcompilerOptions:'
);

// Add TypeScript
rollupConfig = rollupConfig.replace(
  'commonjs(),',
  'commonjs(),\n\t\ttypescript({\n\t\t\tsourceMap: !production,\n\t\t\tinlineSources: !production\n\t\t}),'
);
fs.writeFileSync(rollupConfigPath, rollupConfig)

// Add TSConfig
const tsconfig = `{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "include": ["src/**/*"],
  "exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}`
const tsconfigPath =  path.join(projectRoot, "tsconfig.json")
fs.writeFileSync(tsconfigPath, tsconfig)

// Add global.d.ts
const dtsPath =  path.join(projectRoot, "src", "global.d.ts")
fs.writeFileSync(dtsPath, `/// <reference types="svelte" />`)

// Delete this script, but not during testing
if (!argv[2]) {
  // Remove the script
  fs.unlinkSync(path.join(__filename))

  // Check for Mac's DS_store file, and if it's the only one left remove it
  const remainingFiles = fs.readdirSync(path.join(__dirname))
  if (remainingFiles.length === 1 && remainingFiles[0] === '.DS_store') {
    fs.unlinkSync(path.join(__dirname, '.DS_store'))
  }

  // Check if the scripts folder is empty
  if (fs.readdirSync(path.join(__dirname)).length === 0) {
    // Remove the scripts folder
    fs.rmdirSync(path.join(__dirname))
  }
}

// Adds the extension recommendation
fs.mkdirSync(path.join(projectRoot, ".vscode"), { recursive: true })
fs.writeFileSync(path.join(projectRoot, ".vscode", "extensions.json"), `{
  "recommendations": ["svelte.svelte-vscode"]
}
`)

console.log("Converted to TypeScript.")

if (fs.existsSync(path.join(projectRoot, "node_modules"))) {
  console.log("\nYou will need to re-run your dependency manager to get started.")
}

tsconfigやrollup.configを作成してくれる

起動

src/App.svelteを下記のように書き換える

<script lang="ts">
	export let name: string;

	let countStore: number = 0;
	function count() {
		return ++countStore;
	}
</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>
	<button on:click="{count}">{ countStore }</button>
</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>

yarn checkをターミナルで打つと書き方が正しいかみてくれるので実行します。

yarn check

問題なさそう...実行!

hintsがでてめんどくさい時は、一旦消したい時にはpackage.json内のコマンドyarn check--threshold warningを追加します
warningだけ出るので、warningを修正したら戻しておくと良いです 新しくコマンドを追加してもOKです

チェックを飛ばしたいファイルがあれば--ignore ファイル名で飛ばせます

yarn dev

vueで最初に出てくるcounterみたいなのができた

Svelte Counter

SCSSを使う

typescriptでrollup.config.jsを書き換えてあるのでインストールするだけでOK

yarn add -D sass

src/App.svelteを編集する

<script lang="ts">
	export let name: string;

	let countStore: number = 0;
	function count() {
		return ++countStore;
	}
</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>
	<button on:click="{count}">{ countStore }</button>
</main>

<style lang="scss">
	main {
		text-align: center;
		padding: 1em;
		max-width: 240px;
		margin: 0 auto;
		@media (min-width: 640px) {
			max-width: none;
		}
	}

	h1 {
		color: #ff3e00;
		text-transform: uppercase;
		font-size: 4em;
		font-weight: 100;
	}
</style>

styleの部分にlang="scss"を追加して、
mainの中に@mediaを入れた
実行するとちゃんとレスポンシブになっているので反映されてるよう

Vite + Svelte

svelte + rollupとは別の話
viteで立ち上げたら設定何もいらないのでおまけで書いておきます

yarn create @vitejs/app vite-svelte

vite-svelteのところはアプリの名前を入れる
選択項目でsvelteを選んでsvelte-tsを選ぶ

yarn add -D sass
yarn 

src/App.svelteをscssに書き換えて

<script lang="ts">
  import logo from './assets/svelte.png'
  import Counter from './lib/Counter.svelte'
</script>

<main>
  <img src={logo} alt="Svelte Logo" />
  <h1>Hello Typescript!</h1>

  <Counter />

  <p>
    Visit <a href="https://svelte.dev">svelte.dev</a> to learn how to build Svelte
    apps.
  </p>

  <p>
    Check out <a href="https://github.com/sveltejs/kit#readme">SvelteKit</a> for
    the officially supported framework, also powered by Vite!
  </p>
</main>

<style lang="scss">
  :root {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
      Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  }

  main {
    text-align: center;
    padding: 1em;
    margin: 0 auto;
  }

  img {
    height: 16rem;
    width: 16rem;
  }

  h1 {
    color: #ff3e00;
    text-transform: uppercase;
    font-size: 4rem;
    font-weight: 100;
    line-height: 1.1;
    margin: 2rem auto;
    max-width: 14rem;
    @media (min-width: 480px) {
      max-width: none;
    }
  }

  p {
    max-width: 14rem;
    margin: 1rem auto;
    line-height: 1.35;
    @media (min-width: 480px) {
      max-width: none;
    }
  }
</style>

<style lang="scss">と@mediaの位置を書き換えてる

yarn dev

Vite Svelte Hello World

何一つエラーなくHello Worldできると好印象
~sveltekitに切り替えられるとあるけどまだベータ版だし見送り~ 今このサイトSveltekitを使って作ってます!

参考

Svelte公式
Svelte公式 日本語訳

Vue・React・Angularのパフォーマンス比較検証
React 18とはなにか、Fiberの観点から理解する

Reactの仮想DOMについて
Vueの仮想DOMについて

React Fiber Architecture

仮想DOMは本当に“速い”のか? DOM操作の新しい考え方を、フレームワークを実装して理解しよう