sapper-templateを使ってSvelteの開発環境を構築する
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を初めて使いましたが学習コスト低くて良いですね。導入も簡単です。
Author And Source
この問題について(sapper-templateを使ってSvelteの開発環境を構築する), 我々は、より多くの情報をここで見つけました https://qiita.com/s-yoshida/items/621c8f8487170634e0bd著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .