世界中の"むずかしい"を簡単にする株式会社diffeasyで日本一魅力的なプログラマー集団を目指すCTO西です。
この記事はGeeks Who Drink in Fukuoka -Front End Edition 2-でのLTの内容を記事にしたものです。
当日発表スライドはこちら。
Nuxt.jsで
TypeScriptとJSXとJest
ソースはGitHubにあげています。
Nuxt.jsとTypeScript
Nuxt.jsとTypeScriptについては、こちらでは説明を省略します。
フロントエンドはTypeScriptで書きたい!という人が最近増えてきたように思います。
Nuxt.jsはTypeScriptをサポートしていなかったのですが、Ver.2.4.0からTypeScriptがサポートされるようになりました。
Nuxt.jsとJSX
JSX(「JavaScript eXtension」の略)はReactで利用されるJavaScriptの記法です。
JSX=Reactというイメージですが、Vue.js(Nuxt.js)でも利用することができます。
ちなみに、JSXでGoogle検索しようとすると、候補キーワードとして「気持ち悪い」が出てきます笑
私自身も初めてJSXに出会った時の正直な感想は「キモっ!!」でした。
当時は「ロジックとデザインの分離をいかに進めるか?」ということを考えてシステム設計されることが多かったと思います。
弊社もエンジニアのスキル、体制を考慮して、ロジックとデザインは基本的に分離する設計を進めていました。
しかし、フロントエンド開発において構造・見た目・振る舞いをセットにして考える「コンポーネント志向」が増えてきました。その流れの中で、最近では私も「JSXありなんじゃないか?」と思うようになってきました。
Nuxt.jsとTypeScriptとJSXでアプリケーション開発
create-nuxt-appでNuxt.jsアプリケーション構築
Nuxt.jsのアプリケーションはcreate-nuxt-appを利用すると、コマンド1つで簡単に作成できます。まずはこれでNuxt.jsアプリケーションの雛形を作ります。
ただし、2019年3月時点で、TypeScriptには対応していないので、後からTypeScript対応していきます。
以下のコマンドを実行します。<my-project>
にはプロジェクト名を入れてください。
$ yarn create nuxt-app <my-project>
実行すると、server framework、UI framework、test frameworkなど利用するフレームワークを聞かれますので、それぞれ選択してください。
今回はJestでテストするので、test frameworkはJestを選択します。
その他はお好みで。
nuxt-tsでTypeScript対応
nuxtを削除して、nuxt-tsを利用することで、TypeScript対応していきますので、一旦、yarn.lockとnode_modulesを削除します。
$ rm ./yarn.lock
$ rm -rf ./node_modules
nuxt-tsとvue-property-decoratorをインストールします。
$ yarn add nuxt-ts
$ yarn add vue-property-decorator
package.jsonのScriptをnuxtからnuxt-tsに書き換えます。
"scripts": {
"dev": "nuxt-ts",
"build": "nuxt-ts build",
"start": "nuxt-ts start",
"generate": "nuxt-ts generate",
"test": "jest"
},
tsconfig.jsonにTypeScriptを利用するための設定を追加します。
(この後の手順で再度tsconfig.json編集します。)
{
"extends": "@nuxt/typescript",
"compilerOptions": {
"baseUrl": ".",
"experimentalDecorators": true,
"noImplicitAny": false,
"allowJs": true,
"types": [
"@types/node",
"@nuxt/vue-app"
]
}
}
Nuxt.jsでJSXを利用する
tsconfig.jsonにJSXを利用するための設定を追加します。
{
"extends": "@nuxt/typescript",
"compilerOptions": {
"baseUrl": ".",
"experimentalDecorators": true,
"noImplicitAny": false,
"allowJs": true,
"module": "commonjs",
"jsxFactory": "h",
"types": [
"@types/node",
"@nuxt/vue-app"
]
}
}
コンポーネントを作成します。
/components配下に、*.tsxの拡張子でファイルを作成します。
ここでは、HelloWorld.tsxとします。
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class HelloWorld extends Vue {
@Prop({ default: 'TypeScript!' }) readonly name!: string
message: string = 'Hello, '
render(h: Vue.CreateElement): Vue.VNode {
return (
<div>
<p>{this.message} {this.name}</p>
</div>
)
}
}
通常のVueでは、*.vueファイル内にtemplateとscriptタグでテンプレートとロジックを書きますが、JSXを利用する場合は、render関数の中でJSXをレンダリングします。
pagesからHelloWorld.tsxを読み込みます。
<template>
・・・
<HelloWorld name="Takeshi"/>
・・・
</template>
<script>
import HelloWorld from '~/components/HelloWorld.tsx'
export default {
components: {
・・・,
HelloWorld
}
}
</script>
devサーバーを起動して、ブラウザで http://localhost:3000 を開きます。
$ yarn run dev
![NuxtJSX.png](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F3003%2F42ed3781-89e6-1286-1bc4-3c2dc19e834b.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=ba15a97caa389e645d6b1674c4cb603f)
起動して、コンポーネントが表示されました。
Jestでテスト
続けてコンポーネントのテストを書きます。
TypeScriptのテストを書くために、ts-jestをインストールします。
$ yarn add -D ts-jest
テストコードはとりあえず簡単に以下の通り。
import { mount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.tsx'
describe('HelloWorld', () => {
test('is a Vue instance', () => {
const wrapper = mount(HelloWorld)
expect(wrapper.isVueInstance()).toBeTruthy()
})
})
JestでJSXのテストを実行すると、以下のエラーが発生します。
Jest encountered an unexpected token
・・・
Details:
SyntaxError: /Users/takeshi/projects/nuxt24/nuxt_ts/components/HelloWorld.tsx: Unexpected token (16:16)
14 | }
15 | render(h) {
> 16 | return (<div>
| ^
17 | <p>{this.message} {this.name}</p>
18 | </div>);
19 | }
JestでJSXのテストを実行するためには、tsconfig.jsonに"jsx": "react"
の設定が必要なのですが、これを追加すると、JSXで書いたコンポーネントが画面に表示されなくなってしまいました。
そこで、テスト用のtsconfig.jsonを新たに作成して回避します。
{
"extends": "@nuxt/typescript",
"compilerOptions": {
"baseUrl": ".",
"experimentalDecorators": true,
"noImplicitAny": false,
"allowJs": true,
"jsx": "react",
"module": "commonjs",
"jsxFactory": "h",
"types": [
"@types/node",
"@nuxt/vue-app"
]
}
}
jest.config.jsからこちらのテスト用のtsconfigを読み込みます。
globals -> ts-jest -> tsConfigFileの設定箇所です。
module.exports = {
globals: {
'ts-jest': {
useBabelrc: true,
tsConfigFile: 'tsconfig.test.json'
}
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^~/(.*)$': '<rootDir>/$1',
'^vue$': 'vue/dist/vue.common.js'
},
moduleFileExtensions: ['js', 'vue', 'json', 'tsx'],
transform: {
'^.+\\.js$': 'babel-jest',
'.*\\.(vue)$': 'vue-jest',
"^.+\\.tsx?$": "ts-jest"
},
"collectCoverage": true,
"collectCoverageFrom": [
"<rootDir>/components/**/*.vue",
"<rootDir>/pages/**/*.vue"
]
}
テストを実行します。
$ yarn test
感想
Nuxt.js Ver.2.4.0からTypeScirptサポートされるようになったとはいえ、色々なライブラリと連携させようとするとなかなかスムーズにはいきませんでした。
あと個人的な感想ですが、JSXだとrenderのなかにあまりダラダラとHTMLを書きたくないので、自然とコンポーネントの単位が小さくなりそうな気がします。