はじめに
これは Nuxt.js で TypeScript を使って一通り試そうとしたら、
色々なところでつまずいてしまったのでメモに残すお話です。
また、この記事は Nuxt.js Advent Calendar 2019 21日目の記事です。
今までに Vue.js の経験はありますが Nuxt.js は初めて触ります。
nuxt-community/starter-template
と create-nuxt-app
の違いもよくわからない状態でした。
まぁ、、、公式を見ると create-nuxt-app
を使う手順になっており、nuxt-community/starter-template は、deprecated になっていたので、今は create-nuxt-app
を使うんだろう。と思っている程度です。
作業環境の情報
$ npm -v
6.13.2
$ node -v
v12.13.1
$ yarn -v
1.21.0
$ create-nuxt-app -v
create-nuxt-app/2.12.0 darwin-x64 node-v12.13.1
Nuxt(SPA) プロジェクト作成
この辺はささーっと。
$ create-nuxt-app tutorial
create-nuxt-app v2.12.0
✨ Generating Nuxt.js project in tutorial
? Project name tutorial
? Project description My super-duper Nuxt.js project
? Author name chibi929
? Choose the package manager Yarn
? Choose UI framework Buefy
? Choose custom server framework Express
? Choose Nuxt.js modules Axios
? Choose linting tools ESLint, Prettier
? Choose test framework Jest
? Choose rendering mode Single Page App
? Choose development tools jsconfig.json (Recommended for VS Code)
yarn run v1.21.0
$ eslint --ext .js,.vue --ignore-path .gitignore . --fix
✨ Done in 3.89s.
🎉 Successfully created project tutorial
To get started:
cd tutorial
yarn dev
To build & start for production:
cd tutorial
yarn build
yarn start
To test:
cd tutorial
yarn test
- UI framework: なんとなく Buefy 選択
- Server framework: SPA だけどなんとなく Express を選択
- linting tool: Lint、Formatter が欲しいので ESLint と Prettier 両方を選択
起動してみる
$ cd tutorial
$ yarn dev
yarn run v1.21.0
$ cross-env NODE_ENV=development nodemon server/index.js --watch server
[nodemon] 1.19.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): server/**/*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server/index.js`
ℹ Preparing project for development 17:33:31
ℹ Initial build may take a while 17:33:31
✔ Builder initialized 17:33:31
✔ Nuxt files generated 17:33:31
✔ Client
Compiled successfully in 7.14s
ℹ Waiting for file changes 17:33:39
READY Server listening on http://localhost:3000
ここまでは大丈夫。
Vue CLI 3 以降と違って、CLI で TypeScript 用のプロジェクトが作れないのが辛いだけ。
TypeScript 対応してみよう
https://typescript.nuxtjs.org/ に記載されている通りに進めるだけ。
しかし、所々で自分はハマってしまったので合わせてメモ。
Introduction
Introduction ページ を見てみると以下のパッケージが必要そう。
- @nuxt/types
- これは下2つに含まれているっぽいので敢えてインストールする必要はなさそうだ。
- @nuxt/typescript-build
- @nuxt/typescript-runtime
Setup
Setup ページ では @nuxt/typescript-build
のインストール・設定を行う。
やっぱり Introduction に記載されていた @nuxt/types
は気にしなくて良さそう。
手順通りに以下を行う。
- Installation
- Configuration
- nuxt.config.js
- tsconfig.json
- vue-shim.d.ts
メモ: Configuration - nuxt.config.js
// nuxt.config.js
export default {
buildModules: ['@nuxt/typescript-build']
}
と、記載されていたので buildModules を上記のように変更するのかと思ったが、
上書きではなくて追加するように修正したので、一応メモ。
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module',
+ '@nuxt/typescript-build'
],
動作確認のためもう一度 yarn dev
してみる。
特に問題は出ないはず...
Runtime (optional)
TypeScript runtime is needed for files not compiled by Webpack, such as nuxt.config file, local modules and serverMiddlewares.
ということなので nuxt.config.js
を TS 化するときに必要そう。
Introduction に記載されていた @nuxt/typescript-runtime
は Optional だったということか...
このページは、Optional になっていたので一旦無視する。 (後でハマることになるとは...)
Lint
Lint ページ では、
Introduction では未登場のモジュール( @nuxtjs/eslint-config-typescript
)のインストールと設定を行う。
手順は Setup と大体同じで以下を行う。
- Installation
- Configuration
- .eslintrc.js
- package.json
メモ: Configuration - .eslintrc.js
WARNING
As it will make ESlint use a TypeScript parser (
@typescript-eslint/parser
), please ensureparserOptions.parser
option is not overriden either by you or by another configuration you're extending.If you were using
babel-eslint
as parser, just remove it from your.eslintrc.js
and your dependencies.
という WARNING が記載されていた。
parserOptions.parser
を見てみると babel-eslint
が記載されていた。
消さないとダメらしい。一応以下のように修正する。
- parserOptions: {
- parser: 'babel-eslint'
- },
extends: [
'@nuxtjs',
'prettier',
'prettier/vue',
'plugin:prettier/recommended',
'plugin:nuxt/recommended',
+ '@nuxtjs/eslint-config-typescript'
],
メモ: Configuration - package.json
"lint": "eslint --ext .ts,.js,.vue ."
と、記載されていたが、
.ts
だけを追加記載する形にした。
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
"build": "nuxt build",
"start": "cross-env NODE_ENV=production node server/index.js",
"generate": "nuxt generate",
- "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+ "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore .",
"test": "jest"
},
とりあえず Lint を実行しておこう。
その結果がこれである。
$ yarn lint
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .
/tmp/tutorial/layouts/default.vue
10:11 warning Disallow self-closing on HTML void elements (<img/>) vue/html-self-closing
44:7 error Missing space before function parentheses space-before-function-paren
/tmp/tutorial/nuxt.config.js
60:11 error Missing space before function parentheses space-before-function-paren
60:12 error 'config' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
60:20 error 'ctx' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
/tmp/tutorial/server/index.js
10:21 error Missing space before function parentheses space-before-function-paren
✖ 6 problems (5 errors, 1 warning)
3 errors and 1 warning potentially fixable with the `--fix` option.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
5 errors, 1 warning...
激闘の ESLint と Prettier
これからがほんとうの地獄だ...
--fix
をとりあえず付けてやってみれば少しは改善しそう...的なことが書いてあるからやってみる。
--fix 1回目
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix
/tmp/tutorial/layouts/default.vue
10:63 warning Expected no space before '>', but found vue/html-closing-bracket-spacing
10:64 error Insert `/` prettier/prettier
44:7 error Missing space before function parentheses space-before-function-paren
/tmp/tutorial/nuxt.config.js
60:11 error Missing space before function parentheses space-before-function-paren
60:12 error 'config' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
60:20 error 'ctx' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
/tmp/tutorial/server/index.js
10:21 error Missing space before function parentheses space-before-function-paren
✖ 7 problems (6 errors, 1 warning)
4 errors and 1 warning potentially fixable with the `--fix` option.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
6 errors, 1 warning...
増えたやん...
--fix 2回目
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix
/tmp/tutorial/layouts/default.vue
10:63 error Insert `·/` prettier/prettier
44:7 error Missing space before function parentheses space-before-function-paren
/tmp/tutorial/nuxt.config.js
60:11 error Missing space before function parentheses space-before-function-paren
60:12 error 'config' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
60:20 error 'ctx' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
/tmp/tutorial/server/index.js
10:21 error Missing space before function parentheses space-before-function-paren
✖ 6 problems (6 errors, 0 warnings)
4 errors and 0 warnings potentially fixable with the `--fix` option.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
6 errors, 0 warnings...
改善する気配がない...
--fix 3回目
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix
/tmp/tutorial/layouts/default.vue
10:11 warning Disallow self-closing on HTML void elements (<img/>) vue/html-self-closing
44:7 error Missing space before function parentheses space-before-function-paren
/tmp/tutorial/nuxt.config.js
60:11 error Missing space before function parentheses space-before-function-paren
60:12 error 'config' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
60:20 error 'ctx' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
/tmp/tutorial/server/index.js
10:21 error Missing space before function parentheses space-before-function-paren
✖ 6 problems (5 errors, 1 warning)
3 errors and 1 warning potentially fixable with the `--fix` option.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Lint Error の内容まで振り出しに戻る...
何も考えずに --fix しただけなのでちゃんと見てみる
初めは ESLint と Prettier がバッティングしていることに気づかず苦しみ、
--fix した時の diff などを見ながら何度も実行して苦しみ...
一旦冷静になってエラーの1つだけに着目してみると...
# 初回
# ESLint さんは self-closing を許していないっぽい
10:11 warning Disallow self-closing on HTML void elements (<img/>) vue/html-self-closing
# 1回目の --fix 後
## --fix 効果で self-closing の / が消滅したけどスペースが残ったっぽくて怒ってる ESLint さん
10:63 warning Expected no space before '>', but found vue/html-closing-bracket-spacing
## 一方、Prettier さんは self-closing の / を入れろよ!と怒っていらっしゃる
10:64 error Insert `/` prettier/prettier
# 2回目の --fix 後
## --fix 効果で残ってたスペースが消えたっぽくて ESLint さんの怒りが静まる
## 一方、'スペース' と / を入れろよ!と激怒する Prettier さん
10:63 error Insert `·/` prettier/prettier
# 3回目の --fix 後
## Prettier さんの言い分が通ったのかスペースと / が復活し、振り出しに戻る
10:11 warning Disallow self-closing on HTML void elements (<img/>) vue/html-self-closing
とりあえず、自分のスタイルにルールを設定すれば良いんじゃね?
と、考え直す。
vue/html-self-closing
Enforce self-closing style
(self-closing style を強制する)
https://eslint.vuejs.org/rules/html-self-closing.html
self-closing は許容したいので、ESLint さんの設定を変えよう。
html.void ("never" by default) ... The style of well-known HTML void elements.
この設定が良くなさそうだな。
// add your custom rules here
rules: {
- 'nuxt/no-cjs-in-config': 'off'
+ 'nuxt/no-cjs-in-config': 'off',
+ 'vue/html-self-closing': ['error', {
+ 'html': {
+ 'void': 'always'
+ }
+ }]
}
折りたたみ (Lint 実行ログ)
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .
/tmp/tutorial/layouts/default.vue
44:7 error Missing space before function parentheses space-before-function-paren
/tmp/tutorial/nuxt.config.js
60:11 error Missing space before function parentheses space-before-function-paren
60:12 error 'config' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
60:20 error 'ctx' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
/tmp/tutorial/server/index.js
10:21 error Missing space before function parentheses space-before-function-paren
✖ 5 problems (5 errors, 0 warnings)
3 errors and 0 warnings potentially fixable with the `--fix` option.
error Command failed with exit code 1.
5 errors, 0 warnings
1個改善!
@typescript-eslint/no-unused-vars
Disallow unused variables
(未使用の変数を許可しない)
https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md
変数が未使用なだけで怒るのは、業務でもない限りちょっと怒りすぎだろう。
Warning くらいに変えておこう。
rules: {
'nuxt/no-cjs-in-config': 'off',
'vue/html-self-closing': ['error', {
'html': {
'void': 'always'
}
- }]
+ }],
+ 'no-unused-vars': 'off',
+ '@typescript-eslint/no-unused-vars': 'warn'
}
折りたたみ (Lint 実行ログ)
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .
/tmp/tutorial/layouts/default.vue
44:7 error Missing space before function parentheses space-before-function-paren
/tmp/tutorial/nuxt.config.js
60:11 error Missing space before function parentheses space-before-function-paren
60:12 warning 'config' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
60:20 warning 'ctx' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
/tmp/tutorial/server/index.js
10:21 error Missing space before function parentheses space-before-function-paren
✖ 5 problems (3 errors, 2 warnings)
3 errors and 0 warnings potentially fixable with the `--fix` option.
error Command failed with exit code 1.
3 errors, 2 warnings
よしよし。2つ warning 化。
space-before-function-paren
Require or disallow a space before function parenthesis
(関数の括弧の前にスペースを必要とするか、許可しない)
anonymous
is for anonymous function expressions (e.g.function () {}
).named
is for named function expressions (e.g.function foo () {}
).asyncArrow
is for async arrow function expressions (e.g.async () => {}
).
自分のスタイルでは以下のようなルールにすれば良いかな。。。
// note you must disable the base rule as it can report incorrect errors
'no-unused-vars': 'off',
- '@typescript-eslint/no-unused-vars': 'warn'
+ '@typescript-eslint/no-unused-vars': 'warn',
+ 'space-before-function-paren': ['error', {
+ 'anonymous': 'never',
+ 'named': 'never',
+ 'asyncArrow': 'always'
+ }]
}
折りたたみ (Lint 実行ログ)
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .
/tmp/tutorial/nuxt.config.js
60:12 warning 'config' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
60:20 warning 'ctx' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
✖ 2 problems (0 errors, 2 warnings)
Done in 3.74s.
自分で設定した warning (no-unused-vars) 以外、消えた!
TypeScript で記述してみよう
いよいよ TypeScript 編突入である。
型がないと怖くて生きていけない!!
https://typescript.nuxtjs.org/cookbook/components/ を参考に...
元々 Class API スタイルを使おうとしてたが、全部やってみた!
Options API スタイル
<template>
<div>
Name: {{ fullName }}
Message: {{ message }}
</div>
</template>
<script lang="ts">
import Vue, { PropOptions } from 'vue'
interface User {
firstName: string
lastName: number
}
export default Vue.extend({
name: 'OptionsAPIComponent',
props: {
user: {
type: Object,
required: true
} as PropOptions<User>
},
data() {
return {
message: 'This is a message'
}
},
computed: {
fullName(): string {
return `${this.user.firstName} ${this.user.lastName}`
}
}
})
</script>
<template>
~略~
+ <options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
</section>
</template>
<script>
import Card from '~/components/Card'
+ import Options from '~/components/Options'
export default {
name: 'HomePage',
components: {
Card,
+ Options
}
}
</script>
(こちらはあえてそのまま JavaScript スタイルで...)
メモ: ESLint/Prettier 連合との再戦
yarn dev
を実行したところエラーが...
Prettier め...
/tmp/tutorial/components/Options.vue
2:8 error Replace `⏎····Name:·{{·fullName·}}⏎····Message:·{{·message·}}⏎··` with `Name:·{{·fullName·}}·Message:·{{·message·}}` prettier/prettier
✖ 1 problem (1 error, 0 warnings)
1 error and 0 warnings potentially fixable with the `--fix` option.
面倒なのでまたもやとりあえず --fix
<template>
- <div>
- Name: {{ fullName }}
- Message: {{ message }}
- </div>
+ <div>Name: {{ fullName }} Message: {{ message }}</div>
</template>
これでエラーなくなるならとりあえずはいいか。
そのうちフォーマッターのスタイルは直したいけど。
反映された!!
Class API スタイル
Using vue-class-component through vue-property-decorator
Composition API は見覚えがないが、
Class API 見覚えがあるので、先にこっちから...
Vue CLI3 で作った TypeScript でも Class-Component Style で触ったデコレーター!
$ yarn add -D vue-property-decorator
<template>
<div>Name: {{ fullName }} Message: {{ message }}</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
interface User {
firstName: string
lastName: number
}
@Component
export default class ClassAPIComponent extends Vue {
@Prop({ type: Object, required: true }) readonly user!: User
message: string = 'This is a message'
get fullName(): string {
return `${this.user.firstName} ${this.user.lastName}`
}
}
</script>
<template>
~略~
<options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
+ <ClassAPIComponent :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
</section>
</template>
<script>
import Card from '~/components/Card'
import Options from '~/components/Options'
+ import ClassAPIComponent from '~/components/Class'
export default {
name: 'HomePage',
components: {
Card,
Options,
+ ClassAPIComponent
}
}
</script>
2行目の文字列がそれだ。上手くいった!
メモ: experimentalDecorators
但し、以下のエラーが出ているので tsconfig.json に追加しておこう
ERROR ERROR in /tmp/tutorial/components/Class.vue(14,22): nuxt:typescript 06:59:04
14:22 Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.
{
"compilerOptions": {
~略~
+ "experimentalDecorators": true,
~略~
}
Composition API スタイル
Using @vue/composition-api plugin
これは、知らない記法だった。
普段 Vue では前述の Class API スタイルを使っているし使う機会はなさそう...
yarn add @vue/composition-api
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
/*
** Plugins to load before mounting the App
*/
- plugins: [],
+ plugins: ['@/plugins/composition-api'],
<template>
<div>Name: {{ fullName }} Message: {{ message }}</div>
</template>
<script lang="ts">
import { createComponent, computed, ref } from '@vue/composition-api'
interface User {
firstName: string
lastName: number
}
export default createComponent({
props: {
user: {
type: Object as () => User,
required: true
}
},
setup({ user }) {
const fullName = computed(() => `${user.firstName} ${user.lastName}`)
const message = ref('This is a message')
return {
fullName,
message
}
}
})
</script>
<options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
<ClassAPIComponent :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
+ <Composition :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
</section>
</template>
<script>
import Card from '~/components/Card'
import Options from '~/components/Options'
import ClassAPIComponent from '~/components/Class'
+ import Composition from '~/components/Composition'
export default {
name: 'HomePage',
components: {
Card,
Options,
ClassAPIComponent,
+ Composition
}
}
</script>
SPA だけど Express を使ってみよう
const express = require('express')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const app = express()
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'
async function start() {
+ app.get('/test', (req, res, next) => {
+ const param = { test: 'Hello World!' }
+ res.send(param)
+ })
// Init Nuxt.js
const nuxt = new Nuxt(config)
~略~
}
start()
yarn dev
後に動作確認
$ curl http://localhost:3000/test
{"test":"Hello World!"}
メモ: 記述位置は大事らしい
以下の位置に GET API を記述したところ上手くいかなかった
~略~
async function start() {
~略~
// Give nuxt middleware to express
app.use(nuxt.render)
+ app.get('/test', (req, res, next) => {
+ const param = { test: 'Hello World!' }
+ res.send(param)
+ })
~略~
}
start()
# 失敗バージョン
##############################
$ curl http://localhost:3000/test
<!doctype html>
<html >
~略~
メモ: GET /
もダメらしい
GET /
を登録してしまうと、ページへのアクセスができなくなる。
そりゃそうか。
メモ: this.$axios が使えない
~略~
+ mounted() {
+ this.$axios
+ .get('/test')
+ .then((res) => {
+ console.log(res)
+ })
+ .catch((err) => {
+ console.log(err)
+ })
+ }
}
</script>
ERROR ERROR in /tmp/tutorial/components/Class.vue(25,10): nuxt:typescript 20:58:26
25:10 Property '$axios' does not exist on type 'ClassAPIComponent'.
23 | mounted() {
24 | console.log('mounted')
> 25 | this.$axios
| ^
26 | .get('/test')
27 | .then((res) => {
28 | console.log(res)
tsconfig.json に追記が必要
{
"compilerOptions": {
~略~
"types": [
"@types/node",
"@nuxt/types",
+ "@nuxtjs/axios"
]
},
Store を TypeScript で書いてみよう
とりあえず Vanilla JS の方で良いや...と...
ちなみにここも自分の中では激ハマりポイントである。
上記のページを参考に store/index.ts
を準備できたら、
任意の Component の mounted()
でログを出してみる。
mounted() {
console.log(this.$store.state.counter)
}
client.js?06a0:76 TypeError: Cannot read property 'state' of undefined
at VueComponent.mounted (Class.vue?e831:34)
しかし、どうも this.$store
が undefined みたい...
次に、Nuxt.js のページを参考に...
普通の Vuex... というわけではないが Nuxt Typescript
よりは普通に近い形で、型に怒られたら any でにげる。
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state: any) {
state.counter++
}
}
client.js?06a0:76 TypeError: Cannot read property 'state' of undefined
at VueComponent.mounted (Class.vue?e831:34)
しかし、やっぱり 'state' of undefined
。
どうにも this.$store
が未定義状態だなー...
試しに JS ファイルにしてみると...
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state) {
state.counter++
}
}
動くやん...?
Runtime 編突入
でもやっぱり TypeScript で書きたい!
TypeScript のセットアップの中で、ひとつだけ js 系を ts 系に置き換えるためのものなのに
やっていないものを思い出す。そう、オプショナルだったRuntime...
TypeScript runtime is needed for files not compiled by Webpack, such as nuxt.config file, local modules and serverMiddlewares.
と書いてあったものなので、
Store 系にも影響するかはわからない。けどやってみよう。そう思った。
そして、もう一度Runtimeページでセットアップ方法を確認する。
内容は Setup とほとんど同で以下の通りである。
- Installation
- Usage
- package.json
モジュールのインストールは特に問題ない。ひとつだけ注意するとしたら
TIP
Note that this package is installed as
dependency
and notdevDependency
like@nuxt/typescript-build
, cause@nuxt/typescript-runtime
is needed for production
devDependencies
には入れちゃいけない。
yarn add @nuxt/typescript-runtime
package.json
の書き換えは結構困った。
ドキュメントによると nuxt-ts コマンドに書きかえろよ。と...
{
~略~
"scripts": {
"dev": "nuxt-ts",
"build": "nuxt-ts build",
"generate": "nuxt-ts generate",
"start": "nuxt-ts start"
},
~略~
}
しかし、自分の package.json は build
と generate
は良いが...
dev
と start
には cross-env なるものが使われている...
果たして以下のように全部書き換えて良いものなのか?
{
~略~
"scripts": {
- "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
+ "dev": "nuxt-ts",
- "build": "nuxt build",
+ "build": "nuxt-ts build",
- "start": "cross-env NODE_ENV=production node server/index.js",
+ "start": "nuxt-ts start"
- "generate": "nuxt generate"
+ "generate": "nuxt-ts generate"
},
~略~
}
とりあえず、なんか怖いと思ったので dev
と start
は変えないことにした。
cross-env を使ったまま server/index.js
を起動する形のまま置いておく。
{
~略~
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
- "build": "nuxt build",
+ "build": "nuxt-ts build",
"start": "cross-env NODE_ENV=production node server/index.js",
- "generate": "nuxt generate"
+ "generate": "nuxt-ts generate"
},
~略~
}
とりあえず yarn dev
を実行しても問題なさそう。
いよいよ store/index.js
を TypeScript に置き換えてみる
と、言っても any の型を付けるだけである
export const state = () => ({
counter: 0
})
export const mutations = {
- increment(state) {
+ increment(state: any) {
state.counter++
}
}
yarn dev
を実行してみると、どうやら this.$store
はまだ読み込めていない。
しかたなく nuxt-ts
を実行してみると...
this.$store
が読み込め、 counter
の値が表示できた。
さて、ここで問題なのは
nuxt-ts
で動かした時に Express
で作成した API はどうなるのか?
server/index.js
と nuxt
コマンドは何をやっているのかを追ってみることにした。
server/index.js が何をしているのか?
server/index.js
は短いコードなのですぐ読める。
Nuxt インスタンスを生成して Builder.build
を実行する。
だったり Nuxt.ready
を実行する。
ついでに、Express の API を使う感じだ。
nuxt コマンドが何をしているのか?
@nuxt/cli を追ってみると色々汎用的にガチャガチャやってるが、
結局は cli-dev で Builder.build
したり Nuxt.ready
を実行しているだけである。
つまり答えは、 server/index.js
を動かしてないから、
単純に Express が立っていない状態になるのである。たぶん!!
$ curl http://localhost:3000/test
<!doctype html>
<html >
安心して nuxt-ts に書き換えよう
{
~略~
"scripts": {
- "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
+ "dev": "nuxt-ts",
"build": "nuxt-ts build",
- "start": "cross-env NODE_ENV=production node server/index.js",
+ "start": "nuxt-ts start"
"generate": "nuxt-ts generate"
},
~略~
}
こうして、Express を犠牲にして、
Store を TypeScript で書くことができた。
ServerMiddleware を使ってみよう
さて、そしたら次に気になってしまうのは
Nuxt.js で TypeScript を使ったとき、Express はどうやって使うのか。
Nuxt.js v2.9にTypeScriptとExpress.jsを対応してみた
API: serverMiddleware プロパティ
を参考にした。
nuxt.config.js
に serverMiddleware
の項目を追加すれば良いらしい。
まずは server/index.js
を書き換えよう。
import express from 'express'
import bodyParser from 'body-parser'
const app = express()
app.use(bodyParser.json())
app.get('/test', (req, res, next) => {
const param = { test: 'Hello World!' }
res.send(param)
})
export default app
Nuxt 系の処理が全部不要(nuxt-ts コマンドに任せた)なので、
ごっそり消して Express の処理だけ書きます。
Express のインスタンスは export しておきます。
次に nuxt.config.js
に serverMiddleware
の項目を追加します。
{
~略~
+ serverMiddleware: [
+ { path: '/api', handler: '@/server/index.ts' }
+ ]
}
ts ファイルが直接設定できるんだなー。
きっと Runtime 編を突破したおかげですね!
$ curl http://localhost:3000/api/test
{"test":"Hello World!"}
実行してみると普通に叩けました。
Middleware を使ってみよう
API: serverMiddleware プロパティ を参考してたら Middleware
も目に入ってしまった。
API: middleware プロパティ
ルーティング - ミドルウェア
どうやら、ルーティング時に呼び出すことができるものらしい。
sereverMiddleware
同様、 nuxt.config.js
と ソースコード
をちょちょっと触るだけみたい。
ルートが変更された時に呼ばれる Middleware
export default async function() {
console.log('Middleware: hoge')
}
serverMiddleware: [
{ path: '/api', handler: '@/server/index.ts' }
],
+ router: {
+ middleware: 'hoge'
+ }
}
これだけで、ルーティング変更時に毎度 console.log('Middleware: hoge')
が実行されるのである。
特定のルートに変更された時に呼ばれる Middleware
前述の nuxt.config.js
に書く Middleware は、
全てのルートにおいて、呼び出し時に動く Middleware である。
特定のページに行った時のみ実行したい場合はどうするか。
以下の通りである。
とりあえず hoge.ts
を使うとわかりづらくなってしまうので foo.ts
を用意する。
export default async function() {
console.log('Middleware: foo')
}
次にルーティング対象の pages
以下に手を入れる
~略~
</template>
+ <script lang="ts">
+ import { Vue, Component, Prop } from 'vue-property-decorator'
+
+ @Component({
+ middleware: 'foo'
+ })
+ export default class Inspire extends Vue {}
+ </script>
これで、inspire.vue ルーティングを変更したときに foo が呼び出される。
これで stats ミドルウェアはすべてのルート変更時に呼び出されるようになります。
同様に、特定のレイアウトもしくはページ内にもミドルウェアを追加することができます:
注意点としてはルーティング - ミドルウェアに記載されている通り、
layouts
もしくは pages
のものしか使えなそう。
ここで試している Class API スタイルの Component に middleware
を追加してみたけど、
foo は実行されなかった。
Store にデコレーターを使ってみよう
さて Store は最初 Vanilla JS を使ってしまいましたが、
vuex-module-decorator を使った記述が推されているっぽい。
まずはモジュールをインストールしておく
yarn add -D vuex-module-decorators
そして Store | Nuxt TypeScript と Module re-use, use with NuxtJS を参考に Store を書き換えてみることにする
import { Module, VuexModule, Mutation } from 'vuex-module-decorators'
@Module({ stateFactory: true })
class MyModule extends VuexModule {
counter = 0
@Mutation
increment() {
this.counter++
}
}
import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import example from '~/store/example'
let exampleStore: example
function initialiseStores(store: Store<any>): void {
exampleStore = getModule(example, store)
}
export {
initialiseStores,
exampleStore,
}
import { Store } from 'vuex'
import { initialiseStores } from '~/utils/store-accessor'
const initializer = (store: Store<any>) => initialiseStores(store)
export const plugins = [initializer]
export * from '~/utils/store-accessor'
メモ: example が見つからない
ERROR in /tmp/tutorial/utils/store-accessor.ts(3,8): nuxt:typescript 23:40:46
3:8 Module '"/tmp/tutorial/store/example"' has no default export.
1 | import { Store } from 'vuex'
2 | import { getModule } from 'vuex-module-decorators'
> 3 | import example from '~/store/example'
ドキュメント通りにやるとエラーになる。
store/example.ts
は export してないから当然か。
@Module({ stateFactory: true })
- class MyModule extends VuexModule {
+ export default class MyModule extends VuexModule {
counter = 0
メモ: module name が設定されていない
意気揚々と yarn dev
!!
http://localhost:3000 をブラウザで開いてみると
なんと真っ白!
Developer Tool を開いて確認すると
Error: ERR_GET_MODULE_NAME : Could not get module accessor.
Make sure your module has name, we can't make accessors for unnamed modules
i.e. @Module({ name: 'something' })
at getModuleName (index.js?6fc5:24)
at getModule (index.js?6fc5:41)
at initialiseStores (store-accessor.ts?8dac:8)
at initializer (index.ts?598a:3)
at eval (vuex.esm.js?2f62:353)
at Array.forEach (<anonymous>)
at new Store (vuex.esm.js?2f62:353)
at createStore (store.js?6c6b:42)
at createApp$ (index.js?f26e:51)
at tryCatch (runtime.js?96cf:45)
@Module({ name: 'something' })
のように設定してくれ。とのこと。
vuex-module-decorator
使うと名前空間付きモジュールしか設定できないのかなー?
- @Module({ stateFactory: true })
+ @Module({
+ stateFactory: true,
+ namespaced: true,
+ name: 'example'
+ })
export default class MyModule extends VuexModule {
counter = 0
のように書き換えて再度実行!
任意の Component では以下のようなログ出力を行なっています。
import { exampleStore } from '~/store'
mounted() {
console.log(this.$store.state.counter)
console.log(this.$store.state.example.counter)
console.log(exampleStore.counter)
}
// => undefined
// => 0
// => 0
やっぱり名前空間付きモジュールになっている。
ちなみに store/example.ts
の namespaced: true
を namespaced: false
に書き換えてもダメでした。
vuex-module-decorator
を使うとルートモジュールは作れないのかな?
ひとまず store/index.ts
を以下のように書き換えたら
ルートモジュールの store/index.ts
及び、
名前空間付きモジュールの store/example.ts
の両方が使えるようになりました。
import { Store } from 'vuex'
import { initialiseStores } from '~/utils/store-accessor'
const initializer = (store: Store<any>) => initialiseStores(store)
export const plugins = [initializer]
export * from '~/utils/store-accessor'
+
+ export const state = () => ({
+ counter: 0
+ })
+
+ export const mutations = {
+ increment(state: any) {
+ state.counter++
+ }
+ }
まとめ
これだけ一通りやって、たくさん躓き、
後輩の雑用を軽減するアプリを作成しました。
ちなみに、この Nuxt プロジェクトは、最初に作成している通り、
SPA なので、最終的には nuxt-ts generate
を実行して静的ホスティングをしております。
つまり、無駄に使った serverMiddleware は動いていません。
まぁ、動いても本記事に書いたレベルの {"test":"Hello World!"}
程度の戻り値が出るだけなんですけどね。
しかし、実験コードが入れっぱなしなので、
Developer Tool を開くと以下のエラーが出ているオマケ付きです。
まぁ、後輩が使うだけだし、悪影響ないし、ヨシ
http://example.com/api/test net::ERR_CONNECTION_REFUSED
ということで、
- 一番ハマるのは Linter + Prettier
- Runtime は Optional だけど TypeScript を使うなら入れておけ
-
$axios
もそうだが、時々型定義でハマる (本記事には書いてないけど$buefy
も型定義エラーになった) - 何かやろうとすると多少なりとも動かない!
[やってないこと]
- sass