備忘録

何かあったとき用に

Nuxt2 + TypeScriptの環境構築

この記事はKaigen Discord Advent Calendar 2018 13日目の記事です。

前置き

Nuxt.jsでTypeScriptを導入した環境を構築するのにかなり苦労したので同じことをしようと思う人と自分に向けてまとめておく。
最終的なテンプレートはGitHubにあげていますのでご参考に。

環境

  • Node.js: 10.14.1
  • Nuxt: 2.3.4

構築過程

プロジェクト作成

まず

$ yarn create nuxt-app

でNuxtプロジェクトを作る。構成は以下の画像のようにした。

f:id:KashEight:20181206205904p:plain

今回は環境構築だけなのでサーバサイドフレームワークやUIフレームワークは使わず, また, APIをいじいじするわけでもないのでAxiosも入れない。Universalアプリケーション作るわけでもないのでSPA。lintはかけたいのでeslintやprettierは今回のプロジェクトで導入した。

プロジェクトを作成したらとりあえず

$ yarn run dev

で動かしてみる。
Nuxt v2.3.4ではこのような画面が出たら成功。

f:id:KashEight:20181206213350p:plain

TypeScriptの導入

TypeScriptとWebpack用のloaderとnodeの型宣言ファイルを導入する

$ yarn add -D typescript ts-loader @types/node

Nuxt(というよりかはVueファイル)で使用するパッケージを導入

$ yarn add vue-class-component vue-property-decorator vuex-class

vue-class-componentやvue-property-decoratorをベースとしたnuxt-class-componentnuxt-property-decoratorがnuxt-communityで公開されているがどちらともメンテナンスが滞ってる(nuxt-property-decoratorは最近メンテナンスが再開された?)し, どちらともベースとなるもので事足りるのとそっちの方が機能が多いので別に導入しなくていいと思う。

TypeScriptをビルドできるようにする

TypeScriptをビルドできるようにWebpackの設定をいじってみる。
Nuxtではnuxt.config.jsのbuildプロパティ*1でWebpackの設定をいじるほか, モジュール*2という形でもWebpackの設定をいじることができる。
前者の方法では軽く設定を変える程度で複数プロジェクトで同じようなことをしない場合, そうではない場合は後者の方法を用いるのがいいでしょう。
今回のような場合は後者の方法で実装したほうがよさそう。
幸いにもNuxt2ではないがTypeScriptのテンプレートがあったのでそれを参考にNuxt2用に実装してみる。
まず, modules/typescript.jsとmodulesフォルダとjsファイルを作り以下のコードを記入する。(一応, tsxにも対応できるようにした)

module.exports = function() {
    // Nuxtで.ts/.tsxファイルを解決させる
    this.nuxt.options.extensions.push('ts', 'tsx')
    // Webpackの設定
    this.extendBuild(config => {
        // .ts/.tsxをビルドできるようにts-loaderを追加する。
        config.module.rules.push({
            test: /\.tsx?$/,
            exclude: /(node_modules)/,
            loader: 'ts-loader',
            options: {
                appendTsSuffixTo: [/\.vue$/]
            }
        })
        // Webpackのextensionsに.tsが入っていない場合解決できるようにする
        if (!config.resolve.extensions.includes('.ts')) {
            config.resolve.extensions.push('.ts')
        }
        // Webpackのextensionsに.tsxが入っていない場合解決できるようにする
        if (!config.resolve.extensions.includes('.tsx')) {
            config.resolve.extensions.push('.tsx')
        }
    })
}

次にモジュールを読み込めるようにnuxt.config.jsに以下を追記する。

modules: ['~modules/typescript.js']

tsconfig.jsonは以下のようにした。

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom", "es2015", "es2016", "es2017"],
        "module": "es2015",
        "moduleResolution": "node",
        "alwaysStrict": true,
        "experimentalDecorators": true,
        "noImplicitAny": false,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "sourceMap": true,
        "removeComments": true,
        "suppressImplicitAnyIndexErrors": true,
        "allowSyntheticDefaultImports": true,
        "allowJs": true,
        "baseUrl": ".",
        "paths": {
            "~/*": ["./*"],
            "@/*": ["./*"]
        },
        "typeRoots": [
            "node_modules/@types",
            "types"
        ]
    },
    "exclude": [
        "node_modules"
    ]
}

vscodeだと以下のようなファイルエラーが出ますが無視して構いません。(理由はよくわからないです…分かる人は教えてくれれば幸いです。)

[ts] 入力ファイルを上書きすることになるため、ファイル <rootPath>/modules/typescript.js' を書き込めません。

2/20追記: コメントにて

tsconfing.jsonのcompilerOptionsにoutdirかoutFileを指定するとエラーがなくなるようです。
変換しない場合は、"outFile": "" とするかまたは存在しないデタラメなフォルダを指定すればいいようです。

という指摘をいただきました。
確認はまだしていないのですがこれでそのvscodeのエラーは回避できるようです。
7ccさんありがとうございます!

最後に, VueでTypeScriptが使用できるように型宣言ファイルをtypes/index.d.tsのように作成して以下を記入する。

declare module '*.vue' {
    import Vue from 'vue'
    export default Vue
}

TypeScriptを使用する

pages/index.vueでTypeScriptを使用してみる。ソースは以下のようにした。

<template>
  <section class="container">
    <div>
      <logo/>
      <h1 class="title">
        {{ title }}
      </h1>
      <h2 class="subtitle">
        {{ fuwa }}
      </h2>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          class="button--green">Documentation</a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey">GitHub</a>
      </div>
    </div>
  </section>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import Logo from '~/components/Logo.vue'

@Component({
    components: {
        Logo
    }
})
export default class extends Vue {
    title = 'hoge'
    desc = 'fuwa'
}
</script>

<style>
.container {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.title {
  font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  display: block;
  font-weight: 300;
  font-size: 100px;
  color: #35495e;
  letter-spacing: 1px;
}

.subtitle {
  font-weight: 300;
  font-size: 42px;
  color: #526488;
  word-spacing: 5px;
  padding-bottom: 15px;
}

.links {
  padding-top: 15px;
}
</style>

ビルドしてみる

一度, yarn run lint --fixyarn run devをしてビルドしてみよう。
localhost:3000に接続してみて以下の画像のようになったら成功。

f:id:KashEight:20181207011810p:plain

これでTypeScriptの導入は終了した。

TypeScriptにlintをかける

TypeScriptを導入したはいいけれども.ts/.tsxファイルや.vueファイル内のscript部分にはlintはかからない。
そこでtslintやtypescript-eslint-parserを導入してlintをかけようと思う。今回はeslintを導入しているのとtslintとvueの相性が悪いのでtypescript-eslint-parserを使ってlintをかける。
まず必要となるパッケージを追加する。

$ yarn add -D typescript-eslint-parser eslint-plugin-typescript

nuxt.config.js

config.module.rules.push({
    enforce: 'pre',
    test: /\.(js|vue)$/,
    loader: 'eslint-loader',
    exclude: /(node_modules)/
})

を以下のように変更

config.module.rules.push({
     enforce: 'pre',
     test: /\.(js|ts|vue)$/,
     loader: 'eslint-loader',
     exclude: /(node_modules)/
})

.eslintrc.jsに以下を追記

overrides: [
    {
        files: ["*.ts", "*.vue"],
        parserOptions: {
            parser: "typescript-eslint-parser"
        },
        plugins: ['vue', 'prettier', 'typescript']
    }
]

これでTypeScriptにlintがかかるようになった。

余談

この記事を作るきっかけとしてyarn create nuxt-appでeslintとprettierを導入して初期の状態でビルドするとエラーを吐いて導入できなかったのがあった。
原因は.vueファイル内のscriptの言語がTypeScriptだと初期のeslintのパーサであるbabel-eslintで解析してしまうためそれを回避するために記事中に書いたTypeScriptにlintをかけるような方法を取らなければいけなかった。
この記事を書いてる最中にNuxtのバージョンが上がったため新しいバージョンで試したところ, エラーを吐かずにすんなりビルドを通した。
今までの苦労はなんだったんだろう…。

脚注