はじめに
この記事は TypeScript Advent Calendar 2022 の、1日目の記事です!
ここでは某所で作成している、ナウで堅牢な開発構成を紹介します!
結論
- Nuxt3
- TypeScript + unsafe絶対コ□すマン
vite
script setup
- ステート管理: composables
- Linter
- eslint, stylelint, prettier
- Tests
- Unit Tests: vitest
- E2E Tests: Playwright
- vitestとplaywrightのうまい繋ぎ方が見つからなかったため、vitestは使わずに開発サーバー(
nuxi dev
)のDOMを直接叩いている- 一周回って「E2Eテストだし、逆にユーザーエンドに近いのでは」と思っている
- vitestとplaywrightのうまい繋ぎ方が見つからなかったため、vitestは使わずに開発サーバー(
解説
Nuxt3
某所ではVue.jsのフレームワーク、Nuxt.jsを使っています。
ここでNuxt.jsの利点や詳細は省略しますが、ディレクトリ構成に沿った自動ルーティングや、@/components
・@/composables
の自動import
ができる等、開発DXのかなり高いフロントエンドフレームワークになっています。
// ↓ 必要ない
// import { useState } from '#imports'
import { Ref } from 'vue'
export type Example = {
state: Ref<number | null>
fetch: () => Promise<void>
}
export function useExample(): Example {
const state = useState<number | null>('example', () => null)
async function fetch(): Promise<void> {
state.value = await something()
}
return {
state: readonly(state),
fetch,
}
}
<script setup lang="ts">
// ↓ 必要ない
// import { useExample } from '@/composables/useExample'
const example = useExample()
onMounted(async () => {
await example.fetch()
})
</script>
この前に安定版がとうとう出ましたね!
【Nuxt 3、ついに登場】
— NuxtLabs Japan by ZEN Advisor (@zen_nuxtlabs_jp) November 12, 2022
\📣Nuxt 3の正式リリース日は11/16(水)です!/ https://t.co/DhFAtox0IZ
もちろんTypeScriptも使っています。
ここで必要な人間部品として、僕は本環境の「as
・any
などのunsafeをコ□すマン」として活動しています。
TypeScriptはunsafeの温床で、レビューで叩き落としていかない限り、自然に型と値のふるまいが乖離していきます。
絶対unsafeコ□していこうな。
- 全TypeScriptプログラマーが読むべき参考資料↓
本構成で特筆すべき部分は、ステート管理にライブラリを使っていないところです。
composablesで十分だと考えているからです。
useState()
いいぞ~!
<script setup>
はもしかしたら知らない人がいるかもしれません。
これはVue3のsetup()
をかなり簡潔に書ける素晴らしい機能です。
Svelteの<script>
に似ているかもしれません。
Svelteを知らない?
大丈夫、僕もそんなに知らない。
Linter
Linter三銃士をつれてきたよ :)
Liter三銃士!?
やはりコーディングルールは自動化されていなければいけません。
ということで某所でも、eslint, stylelint, prettier
で自動化をしています。
B
eslintの"extends"
はこのように、堅牢にしています。
"extends": [
"@nuxtjs/eslint-config-typescript",
"plugin:prettier/recommended",
"plugin:nuxt/recommended",
"eslint:recommended",
"google",
"prettier",
// ...
],
prettierではセミコロンレス・''
強制をしています。
この2つは、基本的人権なんだよなあ。
TypeScriptのおかげでできる設定です。たすかる。
{
"semi": false,
"singleQuote": true
}
Tests
Unit Tests
最後にテスト環境です。
Unitテストはvitest
を使っています。
余談ですが、vitest
ではIn-source testingが使えます。
今は某所環境では.spec.ts
に記述するようにしていますが、こちらに切り替えるのもありよりのありですね!
本当はdoctestがしたい。
ちなみにテストではunsafe操作を個人的に許可しています。
テストは必ず停止し、そして異常はすぐに発現するからです。
テストは必ず停止する。そうだよね?
import { test, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import SomeComponent from '@/components/SomeComponent.vue'
test('matches with snapshot', () => {
const wrapper = mount(SomeComponent, {})
expect(wrapper.getCurrentComponent()).toBeTruthy()
expect(wrapper.html()).toMatchSnapshot()
})
test('shows a text when a button clicked', async () => {
const wrapper = mount(SomeComponent)
await wrapper.get('.some-button').trigger('click')
expect(wrapper.get('some-text').text()).toBe('Clicked!')
})
In-source testingの例
export const range = (begin: number, end: number) => ([...Array(to - from)].map((_, i) => (from + i)))
// モジュール内にテストが直接書ける!
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest
it('includes end number', () => {
expect(range(0, 10)).toContain(10)
})
}
E2E Tests
vitestとplaywrightのうまい繋ぎ方が見つからなかったため、nuxi dev
で建てたサーバーに直接テストを叩いています。
ちなみにplaywrightは、.webServer
を設定すると、テスト実行時に勝手にサーバーを上げ下げしてくれます。
たすかる~~
import { devices, type PlaywrightTestConfig } from '@playwright/test'
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
// ...
/* Run your local dev server before starting the tests */
webServer: {
command: 'nuxi dev',
port: 3000,
},
// ...
}
テスト用モックAPIサーバーにはMSWを使っています。
@/mocks/server
にテスト用サーバーが設定されています。
import { test, expect } from '@playwright/test'
import { server } from '@/mocks/server'
test.describe('/index.html', () => {
test.beforeAll(() => {
server.listen()
})
test.afterAll(() => {
server.close()
})
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:8080')
})
test('shows a text when a button clicked', async ({ page }) => {
await page.locator('.some-container > .button').click()
expect(await page.locator('.some-container > p').textContent()).toBeDefined()
})
test('shows a todo list', async ({ page }) => {
const state = await getSomeState()
const lis = await page.locator('.todo-list > .list > .todo')
if (state.data.todos.length > 0) {
expect(lis).not.toBe(0)
} else {
expect(lis).toBe(0)
}
})
})
vitestとplaywrightはまだ連携できないけど、ほら、E2Eテストだし、一周回って逆にユーザーエンドに近いから、利点だよね!
終わり
以上が「型とテストに守られたナウなフロントエンド環境」でした!
Nuxt3は開発DXのかなり高いフレームワークで、unsafeの駆逐されたTypeScriptは最高の言語。
vitestとplaywrightもとても書きやすいテスティングフレームワークでした。
これからも俺たちフロントエンドエンジニアの、ナウを追い求める戦いは終わらない!
完