Getting started with Nuxt 3
2022/07/30 現時点では RC 版だけど試しにさわってみる。
nuxt のバージョンは 3.0.0-rc.6
リポジトリは以下
プロジェクトを作成する。
pnpm dlx nuxi init nuxt-app
公式では依存パッケージの install に --shamefully-hoist
オプションを使っているけど、pnpm はこのオプションを非推奨としているので使わない。
pnpm i
実行してみる。
pnpm run dev
以下の様なエラーが出る。これは、pnpm の --shamefully-hoist
オプションを使っていないからで、nuxt が依存しているライブラリが install されていないからっぽい。
ERROR Failed to resolve import "ufo" ...
ERROR Failed to resolve import "vue" ...
なので必要なライブラリを install する。
pnpm add -D ufo vue
再度実行してみると動作した。
pnpm run dev
一応 build も試してみて正しく動作することが確認できた。
pnpm run build
ソースはディレクトリを切ってまとめる方が好みなので、src
ディレクトリを作成して config に追記した。
import { defineNuxtConfig } from "nuxt"
export default defineNuxtConfig({
srcDir: "src/"
})
Tailwind CSS のセットアップをする。
pnpm add -D tailwindcss @nuxtjs/tailwindcss
modules
を追記する。
export default defineNuxtConfig({
modules: ["@nuxtjs/tailwindcss"],
...
})
設定ファイルを新規で作成する。
import { Config } from "tailwindcss"
export default <Config>{
theme: {
extend: {}
},
}
ESLint をセットアップする。
必要そうなライブラリを install する。
pnpm add -D eslint eslint-plugin-vue @vue/eslint-config-typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
とりあえずの ESLint の設定ファイルを作成する。
env:
browser: true
extends:
- plugin:vue/vue3-recommended
- eslint:recommended
- "@vue/typescript/recommended"
parser: vue-eslint-parser
parserOptions:
parser: "@typescript-eslint/parser"
sourceType: module
plugins:
- vue
- "@typescript-eslint"
rules: {}
私は Visual Studio Code を利用しているので、以下の様に保存時に自動整形をする様に設定した。
{
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
},
}
Prettier をセットアップする。
pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier
追記する。
...
extends:
- plugin:vue/vue3-recommended
- eslint:recommended
- "@vue/typescript/recommended"
+ - plugin:prettier/recommended
...
これでファイルを保存すると自動で Prettier の整形も行ってくれる。
.
├── pages
│ ├── index.vue
│ └── about.vue
└── etc..
上記の構成で pnpm run generate
を試してみた。ReferenceError: Response is not defined
とコンソールに表示されて正しく html ファイルが生成されない。
ℹ Prerendering 2 initial routes with crawler
├─ /about (undefinedms) (ReferenceError: Response is not defined)
├─ / (undefinedms) (ReferenceError: Response is not defined
node_modules
を削除して、npm i
して、npm run generate
すると意図した通りの html が生成された。pnpm
だと、SSG するために必要なライブラリが入っていないのかもしれない。詳細なエラーが出てくれないので何のライブラリが不足しているのか分からない。
ℹ Prerendering 2 initial routes with crawler
├─ /about (78ms)
├─ / (12ms)
すぐに解決できそうにないので、pnpm から npm に移行する。合わせて ufo
や vue
など npm では不要なライブラリは package.json
から削除する。
GitHub Pages へ継続的デプロイをする。
つい最近以下の様な機能がリリースされたみたい。
以下の様に設定した。
name: Deploy to GitHub Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: "16"
- name: Setup Pages
id: pages
uses: actions/configure-pages@v1
- name: Install dependencies
run: npm i
- name: Static HTML export with Nuxt
run: NUXT_APP_CDN_URL=https://kazuhe.github.io/ npm run generate
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: ./dist
# Deployment job
deploy:
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
GitHub Pages のサイト URL を環境変数 NUXT_APP_CDN_URL
にセットしている(必須ではないかもしれない)。
main ブランチにマージすると意図した通りデプロイされた。
Nuxt3 に対応した @nuxt/content
を利用してコンテンツを管理する。
ドキュメントに従って環境を作成していく。
npm i -D @nuxt/content
nuxt.config の modules
に追記する。
export default defineNuxtConfig({
modules: [..."@nuxt/content"],
...
});
/content
に新しくマークダウンを作成する。
---
title: 'index page'
description: 'トップページです'
---
# Top page
- list
- list2
---
title: 'about page'
description: 'foo'
---
# タイトル
テキストテキストテキストテキストテキスト
<template>
<div>
<p>{{ $route.params.slug }}</p>
<ContentDoc />
</div>
</template>
npm run dev
すると、コンテンツが見つからないと表示された。
Document not found, overwrite this content with #not-found slot in <ContentDoc>.
nuxt.config で srcDir: "src/"
を指定してるから /content
が見つからないらしい。
なので以下のよう修正した。
export default defineNuxtConfig({
modules: ["@nuxt/content", "@nuxtjs/tailwindcss"],
srcDir: "src/",
/**
* @nuxt/content
*
* https://content.nuxtjs.org/api/configuration
*/
content: {
sources: [path.join(__dirname, "content")],
},
});
再度 npm run dev
するも、index.md が表示されない。
http://localhost:3000
も http://localhost:3000/about
も about.md が表示されている。
以下の様な issue を見つけた。
意訳すると、バグ修正はしてるけどリリース予定は決まっていないから、取り急ぎ @nuxt/content-edge
を使ってくれとのこと。
index.md 以外は正しく機能しているっぽいので今はこのまま利用することにした。
Zenn の記事を取得してブログに表示させる。
rss-parser
というライブラリを利用して、Zenn の RSS を利用することにした。
npm i -D rss-parser
RSS から取得した記事らと、先に設定した nuxt/content の記事をそれぞれ取得して、コンポーネントが期待する型にコンバートしている。その後に最新順に並び替えて表示している。
<script setup lang="ts">
import Parser from "rss-parser";
import { Blog } from "@/domain/blog";
import BlogCard from "@/components/BlogCard.vue";
/**
* zenn の RSS からコンテンツを取得する
*/
const fetchZennContent = (): Promise<Blog[]> =>
Promise.resolve(
new Parser()
.parseURL("https://zenn.dev/kazuhe/feed?all=1&include_scraps=1")
.then((res) =>
res.items.map((d) => ({
title: d.title,
description: d.content,
path: d.link,
createdAt: d.pubDate,
icon: "z",
type: "zenn",
}))
)
);
/**
* `/content` からコンテンツを取得する
*/
const fetchOwnContent = (): Promise<Blog[]> =>
queryContent("blog")
.find()
.then((res) =>
res.map((d) => ({
title: d.title,
description: d.description,
path: d._path,
createdAt: d.created_at,
icon: d.icon,
type: "own",
}))
);
type Sort<T> = ([a, b]: [T[], T[]]) => T[];
/**
* Blog を最新順に並び替える
*/
const sortBlogs: Sort<Blog> = ([a, b]) =>
a.concat(b).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
const { data: blogs } = await useAsyncData("blogs", () =>
Promise.all([fetchZennContent(), fetchOwnContent()]).then(sortBlogs)
);
</script>
<template>
<div>
<ul>
<li v-for="blog in blogs" :key="blog.path" class="mt-3 hover:opacity-60">
<blog-card
:title="blog.title"
:description="blog.description"
:path="blog.path"
:created-at="blog.createdAt"
:icon="blog.icon"
:type="blog.type"
/>
</li>
</ul>
</div>
</template>
便利だ。