LoginSignup
1
1

More than 3 years have passed since last update.

sapper-templateを使ってSvelteの開発環境を構築する

Last updated at Posted at 2021-01-12

Svelteとても楽しいです。
こうしたらもっと楽だよとか、設定間違ってるよとかあればご指摘頂けると嬉しいです。

SvelteKitが安定したらそちらに移行するのが良いと思います。

この記事でやること

  • sapper-template導入
  • Linter(ESLint)とFormatter(Prettier)導入
  • Unit/Components Testing(Jest)導入
  • E2E Testing(Cypress)導入
  • git hook(husky)導入

コード

yoshida-san/sapper-ext: Svelte development environment

参考記事

先人の知恵をお借りして進めていきます。いつものことながら先人には感謝しかないです。

Sapperについて

こちらを参照ください。Svelteについてはこちらを参照ください。
Sapperって名前、良いですよね。

環境構築

IDEはVSCodeを使っていきます。

~$ npx degit "sveltejs/sapper-template#rollup" sapper-sample
~$ cd sapper-sample
~$ npm i
~$ npm run dev

localhost:3000で確認して'GREATE SUCCESS!'が見えればOK。

TypeScript support

※一応記載しておきますが、今回はTSを導入せずに進めていきます。
TSサポートは嬉しいですがLinterやFormatterも公式でサポートされる日が待ち遠しいですね。

~$ node scripts/setupTypeScript.js
~$ npm i
~$ npm run build

これで導入は完了ですが、そのままTypeScriptコードを書くとbuildでコケます。scriptタグにlang属性を追加する必要があります。src/routes/about.svelteで例を記します。

<script lang='ts'>
    const title: string = 'About!!!'
</script>

<svelte:head>
    <title>{title}</title>
</svelte>

<h1>About this site</h1>

<p>This is the 'about' page. There's not much here.</p>

VSCodeプラグインのSvelte for VS Codeをインストールしておきましょう。

eslint

~$ npm i -D eslint eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard eslint-plugin-svelte3 eslint-config-standard

.eslintrc.js

module.exports = {
  parserOptions: {
    ecmaVersion: 2019,
    sourceType: 'module'
  },
  env: {
    es6: true,
    browser: true,
    node: true
  },
  extends: [
    'standard'
  ],
  plugins: [
    'svelte3'
  ],
  ignorePatterns: [
    '/node_modules/',
    '/__sapper__/',
    '/src/node_modules/@sapper/'
  ],
  overrides: [
    {
      files: ['**/*.svelte'],
      processor: 'svelte3/svelte3'
    }
  ],
  rules: {},
  settings: {}
}

以下のコマンドで動作確認(sapper-templateがそのままならそれなりの量がエラーになるはず)。

npx eslint --ext svelte,js src/

standardそのままで利用するとscriptタグのラインでno-multiple-empty-linesのエラーが発生してしまいます。.eslintrc.jsのrulesで対応していきます。

'no-multiple-empty-lines': [
    'error',
    {
        max: 2,
        maxBOF: 2,
        maxEOF: 0
    }
]

npm scriptに追加しておきましょう。--fixで整形もできますが整形はPrettierに任せます(ESLintに任せられる範囲で任せる場合は--fixで良いと思います)。

"lint": "eslint --ext svelte,js src/"

prettier

~$ npm i -D prettier-plugin-svelte prettier

.prettierrc.js
(あえてフォーマットをぐちゃぐちゃにしてます)

module.exports = {
    svelteSortOrder : "options-scripts-markup-styles",
    svelteStrictMode: false,
  svelteBracketNewLine: true,
          svelteAllowShorthand: true,
  singleQuote: true,
  trailingComma: "none",
    tabWidth: 2,
  semi: false
}

以下のコマンドで動作確認。

npx prettier .prettierrc.js

整形後のコードが表示されていればOKです。--writeオプションを付けて実行すればコードが自動で整形されます。

続いて以下のコマンドで整形します。

npx prettier --write 'src/**/*.{js,svelte}'

eslintのspace-before-function-parenでエラーが発生します。しかしながらeslintのspace-before-function-parenに該当する設定がprettierには無く、ここが問題となってしまいます。ここではrulesを追加することで対応していきます。

'space-before-function-paren': ['error', 'never']

JavaScript Standard Styleからズレていくのはあまり良いとは言えませんがやむ無し...。rulesの細かい調整等は開発チームのコーディング規約に準じて修正してください。

最後にnpm scriptに追加しておきましょう。

"format": "prettier --write 'src/**/*.{js,svelte}'"

VSCode Extension

ファイル保存時にフォーマットをかける場合は、.vscode/setting.jsonに以下を追加します(またはPreferences->Settingsから設定します)。

"editor.fomatOnSave": true

Sample Code

テスト用にコードを用意します。
src/routes配下にcounterディレクトリを作成します。counterディレクトリ内にSvelte: Examples - Custom Storesをベースにしたコードを記述したファイルを作成します。

src/routes/counter/index.svelte

<script>
  import { count } from './store.js'
</script>

<h1>The count is <span data-test="result">{$count}</span></h1>

<button data-test="increment" on:click={count.increment}>+</button>
<button data-test="decrement" on:click={count.decrement}>-</button>
<button data-test="reset" on:click={count.reset}>reset</button>

src/routes/counter/store.js

import { writable } from 'svelte/store'

function createCount() {
  const { subscribe, set, update } = writable(0)

  return {
    subscribe,
    increment: () => update((n) => n + 1),
    decrement: () => update((n) => n - 1),
    reset: () => set(0),
    set: (n) => set(n)
  }
}

export const count = createCount()

ファイルの作成が完了したら、src/components/Nav.svelteにリンクを追加。

<li>
  <a aria-current={segment === "counter" ? "page" : undefined} href="counter">
    counter
  </a>
</li>

Unit testing

Ready

~$ npm i -D jest babel-jest @babel/core @babel/preset-env

babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          chrome: '87',
          firefox: '82',
          safari: '14',
          node: 'current'
        }
      }
    ]
  ]
}

jest.config.js

module.exports = {
  verbose: true,
  transform: {
    "^.+\\.js$": "babel-jest"
  }
}

.eslintrc.jsのenvに以下を追加。

env: {
  :
  'jest/globals': true
}

Testing

test/unit/counter/store.test.js

import { get } from 'svelte/store'
import { count } from '../../../src/routes/counter/store.js'

describe('Testing counter/store', () => {
  it('Increment(Positive number)', () => {
    count.set(0)
    expect(get(count)).toBe(0)
    count.increment()
    expect(get(count)).toBe(1)
  })

  it('Increment(Negative number)', () => {
    count.set(-100)
    expect(get(count)).toBe(-100)
    count.increment()
    expect(get(count)).toBe(-99)
  })

  it('Decrement(Positive number)', () => {
    count.set(100)
    expect(get(count)).toBe(100)
    count.decrement()
    expect(get(count)).toBe(99)
  })

  it('Decrement(Negative number)', () => {
    count.set(-99)
    expect(get(count)).toBe(-99)
    count.decrement()
    expect(get(count)).toBe(-100)
  })
})

以下のコマンドでUnit unit testを実行します。

~$ npx jest test/unit/
PASS  test/unit/counter/store.test.js
 Testing counter/store
   ✓ Increment(Positive number) (2 ms)
   ✓ Increment(Negative number) (1 ms)
   ✓ Decrement(Positive number)
   ✓ Decrement(Negative number)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        2.938 s

npm scriptに追加しておきます。

"test:unit": "jest test/unit"

Components testing

Ready

~$ npm i -D @testing-library/svelte jest-transform-svelte

jest.config.js

module.exports = {
  verbose: true,
  transform: {
    '^.+\\.js$': 'babel-jest',
    '^.+\\.svelte$': 'jest-transform-svelte'
  },
  moduleFileExtensions: ['js', 'svelte']
}

Testing

test/components/counter/counter.test.js

import { render, fireEvent } from '@testing-library/svelte'
import Counter from '../../../src/routes/counter/index.svelte'

it('Testting counter component', async () => {
  const { container } = render(Counter)
  const incrementButton = container.querySelector(
    'button[data-test="increment"]'
  )
  const resultText = container.querySelector('span[data-test="result"]')

  await fireEvent.click(incrementButton)
  expect(resultText.textContent).toBe('1')
})

以下のコマンドでComponent testを実行します。

~$ npx jest test/components/
PASS  test/components/counter/counter.test.js
 ✓ Testting counter component (28 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.764 s

npm scriptに追加しておきます。

"test:components": "jest test/components"

E2E testing

Ready

~$ npm i -D cypress

cypress.json

{
  "video": false,
  "baseUrl": "http://localhost:3000",
  "fixturesFolder": "test/e2e/fixtures",
  "integrationFolder": "test/e2e/integration",
  "screenshotsFolder": "test/e2e/screenshots",
  "videosFolder": "test/e2e/videos",
  "pluginsFile": false,
  "supportFile": false
}

Testing

test/e2e/integration/counter.test.js

it('Counter increment', () => {
  cy.visit('/counter')
  cy.get('span').should('have.text', '0')
  cy.get('button[data-test="increment"]').should('have.text', '+')
  cy.get('button[data-test="increment"]').click()
  cy.get('span[data-test="result"]').should('have.text', '1')
})

it('Counter decrement', () => {
  cy.visit('/counter')
  cy.get('span').should('have.text', '0')
  cy.get('button[data-test="decrement"]').should('have.text', '-')
  cy.get('button[data-test="decrement"]').click()
  cy.get('span[data-test="result"]').should('have.text', '-1')
})

テスト前にビルド&ローカルサーバ起動を行います。

~$ npm run build
~$ npm run start

cypressを走らせます(またはopenで起動してアプリケーション内でテストを実行します)。

~$ npx cypress run
or
~$ npx cypress open

Git hooks

Ready

~$ npm i -D husky

lint-stagedは使っていませんが、必要に応じてlint-stagedを併用するのが良いと思います。

Hooks settings

commitのタイミングでlint && formatを走らせて、pushのタイミングでtest:unit && test:componentsを走らせます。

package.json

"husky": {
  "hooks": {
    "pre-commit": "npm run lint && npm run format",
    "pre-push": "npm run test:unit && npm run test:components"
  }
}

おわり

環境周りはまだまだ変化していくと思うので、半年後には役に立たない記事になっていそうな気がします。LintやFormatter、Testing等もSapperに含まれたらいいですね。

Cypressを初めて使いましたが学習コスト低くて良いですね。導入も簡単です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1