Nuxt3がやっとパブリックベータ版になった!!!
いや〜待ってたよずっと待ってた。で早速使ってみた。
必要環境
- Node.js
- VisualStudio Code + Volar Extension
これを書いている時点だと、Node.jsのバージョンは(^14.16.0 || ^16.11.0 || ^17.0.0)だけどしょっちゅう変わるので都度確認が必要。
VSCodeの圧が強い。が、まあ仕方ないねこれは。
Atomあんまり使わなくなってしまった。
Nuxt2からの移行
できるかできないかでいうと、一応できる。NuxtBridgeを使用する。
NuxtBridgeを使うと2と3の間みたいな状態になるので、Nuxt2、Nuxt2+Bridge、Nuxt3と3パターンバージョンがある感じだ。
去年作ったNuxt2のやつにBridge入れてみようかなあ。
今から作るプロジェクトはどれ使うのがええんや?
これについてはdicussionsの質問についてたコメントを引用しておく。
Hi @dijaca. I would recommend using Nuxt 3 with Vue 3 compatible libraries if you are starting a new project. During the beta period, we try to break as little as possible and bug fixes are immediately released.
https://github.com/nuxt/framework/discussions/1572#discussioncomment-1558284
V2使うくらいならReact使う方がいいと思っていたのは前にも書いた。が、V2は色々な意味で安定しているので、将来的に陳腐化するのを考慮して使うならありだと思う。
V3は更新が激しいので使うなら半年くらいはバグとバージョンアップに振り回される覚悟が必要だと思う。
Nuxt3の主な新機能
- Nitroエンジン(軽いし速い)
- Vue3サポート
- TypeScriptサポート(素で使える)
- Nuxt内部でのAPI作成
- useState
- Webpack5 & Vite
- Nuxt CLI(nuxi)
- Nuxt Devtools(登場予定)
起動からサーバーが立ち上がるまではちょいかかるが、それ以降が爆速。
ビルドは規模に比例して長引くからガチなプロジェクトでどうなのかはわからないが、後述のお試しアプリではDoneまで12.49sだった。
Devtoolsはまだなくて、Vue.js Devtoolsも未対応。だからちょっと不便。
Vue3がIE11のサポートを切ったことにより、IE11のサポートをキメたい場合はNuxt2(Vue2)を利用しなければならない。
Vue2は2.7が最終バージョンで、2.7リリース後18ヶ月がサポート期間となり、以降はセキュリティメンテナンスが行われる予定。
TypeScriptサポート
プラグインや設定が必要ないので気楽に使える。
V2で使おうとして酷い目にあったのが嘘のようだ。
Vueファイルならscriptタグにlang属性でtsを指定するだけでそこはTypeScriptの領域になる。
(lang属性による言語指定は必須になった)
<script lang="ts">
// TypeScript
</script>
同じコンポーネントファイルの中でTSとJSを混在させると怒られる。
ルートディレクトリ内がTypeScript環境になってるので、拡張子TSのファイルを作ればいい。
後述する server/api
とかの戻り値は内部で型定義してあれば自動で型生成してくれるが、外部APIからデータとってきててanyになる場合なんかは指定が要る。
const { data } = await useFetch<string, User[]>(
'https://example.com/api/users',
);
まだ型定義不足してるらしくてめっちゃエラー出る。
Auto Import
コンポーネントから関連APIまで自動でimportする。
V2(2.13〜)ではconfigで components: true
が必要だったが、この設定は不要になった。
対象になっているAPIとかはこの定義ファイル見るのが早い。
プラグインも自動でインポートされるので、nuxt.config.jsの設定は不要である。
この自動インポート環境楽すぎてやばい🙄
useState
V2ではアプリ内で共有される状態を保存しておくためにVuexを使っていたが、V3ではそれに変わるものとしてuseStateが使えるようになった。なんというかすごくReactっぽいな?
V2の例
// plugins/sample.js
export default ({ store }, inject) => {
store.commit('foo', 'bar');
}
// pages/index.vue
<template>
<div>
{{ foo }}
</div>
</template>
<script>
export default {
asyncData({ store }) {
return {
foo: store.state.foo
}
}
}
</script>
V3の例
// plugins/sample.ts
import { defineNuxtPlugin, useState } from '#app'
export default defineNuxtPlugin((nuxt) => {
const foo = useState(
'foo',
() => 'bar'
)
})
// app.vue
<template>
<div>
{{ foo }}
</div>
</template>
<script setup>
const foo = useState('foo')
</script>
あまりにもお手軽に使えてしまうが故に、アプリ全体で共通する状態を保存するものであるというのを忘れると闇鍋になるからV2以上に注意して使わないと怖いなあと思った。
Nuxt内部でのAPI作成
server
ディレクトリにバックエンドが作れるようになった。
V2の serverMiddleware
は server/middleware
に変更。これはディレクトリが変わっただけで機能は同じ。
新しく server/api
が増えてエンドポイントがお手軽に作れるようになった。pages
のようにファイル追加するだけでそのファイル名と同じエンドポイントが生成される。
// server/api/hello.ts
export default () => 'Hello World'
// app.vue
<template>
<p>{{data}}</p>
</template>
<script setup>
const { data } = await useFetch('/api/hello');
</script>
app.vue
Nuxt3でメインコンポーネントとなるファイル。
V2では app.html
をルートに置くことでhtml, head, bodyタグの操作ができたけれども、それが app.vue
に置き換わった感じ。
それらメタタグは新たに用意されたメタコンポーネントを使うもよし、useMeta
使うもよしって感じで変わらず操作できる。
Nuxt3は pages
がオプションなので、
ページ分けの必要がないいわゆるペライチサイトの場合、このapp.vueファイルだけあればOKである。
レイアウト
layoutsディレクトリに配置したファイルがページレイアウトとして利用されるのはV2と同じ。
layouts/default.vue
は app.vue
で代用できるようになったので、2つ以上レイアウトがあれば使うかなって感じ。
composables ディレクトリ
ここに作成したファイルも自動インポート対象。composables/useFoo.ts
は useFoo()
てな感じで利用できる。
Vue Composable の説明はこれがわかりやすいと思う。
同じことの繰り返しだなあこれ…ってロジックはここに切り出しておけばいいのだ。
$fetch
ohmyfetchを利用した内臓ヘルパー。
ブラウザで実行した場合はサーバーへのAPI呼び出し、
サーバーで実行した場合は関連する関数を呼び出す。という挙動をする。
const todo = await $fetch('/api/todos/add', {
params: {
body: this.newTodo
}
})
外部のAPIも叩けるからaxiosやhttpモジュールを追加する必要は無くなったと思う。
useAsyncDataとuseFetch
setupで使えるNuxt3のデータ取得用関数。
2つもあるとどっち使うのか迷うけど、ソースみた感じuseFetchはuseAsyncDataのラッパーでキーの設定とかを勝手にやってくれるので、大抵の場合はuseFetch使っておけばよさそう🤔
基本的にsetupで1回だけ呼び出すものだが、更新が必要な場合はrefreshを利用する。
<script setup lang="ts">
const id = ref(1)
const {
data,
refresh
} = await useAsyncData('characters', () => {
return $fetch('https://rickandmortyapi.com/api/character/' + id.value)
})
const getNextChar = () => {
id.value += 1
refresh()
}
</script>
<template>
<div>{{ data }}</div>
<button @click="getNextChar">Get next character</button>
<button @click="refresh">Refetch</button>
</template>
<script setup>
const useFetchWithHeaders = (req, opts) => useFetch(req, { ...opts, headers: { test: '123' } });
const { data, refresh } = await useFetchWithHeaders('/api/test');
</script>
プラグイン
pluginsディレクトリにファイル作ったらオートインポートされる。
そしてプラグインの作り方が変わった。
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.provide('hello', msg => `Hello ${msg}!`);
})
declare module '#app' {
interface NuxtApp {
$hello (msg: string): string
}
}
サーバーあるいはクライアントどちらに対応したプラグインかの指定は、ファイル名の拡張子前にclient
もしくはserver
を追加する。(例:sample.client.js
)
Tailwind(PostCSS)を利用する例
assets/css/tailwind.css
を用意する
@tailwind base;
@tailwind components;
@tailwind utilities;
nuxt.config.js に設定を追加する。
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
css: ['~/assets/css/tailwind.css'],
build: {
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
},
},
})
postcss.config.js
をrequireしても良い。
Todoアプリのサンプル
いつものcodesandboxで簡単なTodoアプリを作ってみた。
前に作ったページネーションのコンポーネントを再利用してる。
お試しにはログインしてforkが必要。
CompositionAPIちょっとわかってきた気がするぞ。
リポジトリ
以下はtailwindcssも使ってみたやつ。
codesandboxやstackblitzではエラーでて動かなかったから、Todoだけ切り出した。
https://github.com/Tenderfeel/nuxt3-app
難点
現状SSRしかないのでSSGができない。データもSSRからアクセスできるものでないとレンダリング時に反映されないので、LocalStorageを保存領域にしたい場合はちょっと面倒なことになる。