9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScriptAdvent Calendar 2022

Day 1

型とテストに守られたナウなフロントエンド環境構築概要

Last updated at Posted at 2022-11-30

はじめに

この記事は :sparkles: TypeScript Advent Calendar 2022 :sparkles: の、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テストだし、逆にユーザーエンドに近いのでは」と思っている

解説

Nuxt3

某所ではVue.jsのフレームワーク、Nuxt.jsを使っています。
ここでNuxt.jsの利点や詳細は省略しますが、ディレクトリ構成に沿った自動ルーティングや、@/components@/composablesの自動importができる等、開発DXのかなり高いフロントエンドフレームワークになっています。

@/composables/useExample.ts
// ↓ 必要ない
// 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,
  }
}
index.vue
<script setup lang="ts">
// ↓ 必要ない
// import { useExample } from '@/composables/useExample'

const example = useExample()

onMounted(async () => {
  await example.fetch()
})
</script>

この前に安定版がとうとう出ましたね!

もちろんTypeScriptも使っています。
ここで必要な人間部品として、僕は本環境の「asanyなどのunsafeをコ□すマン」として活動しています。

TypeScriptはunsafeの温床で、レビューで叩き落としていかない限り、自然に型と値のふるまいが乖離していきます
絶対unsafeコ□していこうな。

本構成で特筆すべき部分は、ステート管理にライブラリを使っていないところです。
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操作を個人的に許可しています。
テストは必ず停止し、そして異常はすぐに発現するからです。

テストは必ず停止する。そうだよね?

SomeComponent.spec.ts
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の例

@/modules/array.ts
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を設定すると、テスト実行時に勝手にサーバーを上げ下げしてくれます。
たすかる~~ :pray:

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もとても書きやすいテスティングフレームワークでした。

これからも俺たちフロントエンドエンジニアの、ナウを追い求める戦いは終わらない!

     

9
9
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
9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?