Closed10

Getting started with Nuxt 3

kazuhekazuhe

プロジェクトを作成する。

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
kazuhekazuhe

ソースはディレクトリを切ってまとめる方が好みなので、src ディレクトリを作成して config に追記した。

nuxt.config.ts
import { defineNuxtConfig } from "nuxt"

export default defineNuxtConfig({
  srcDir: "src/"
})
kazuhekazuhe

Tailwind CSS のセットアップをする。

pnpm add -D tailwindcss @nuxtjs/tailwindcss

modules を追記する。

nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@nuxtjs/tailwindcss"],
  ...
})

設定ファイルを新規で作成する。

tailwind.config.ts
import { Config } from "tailwindcss"

export default <Config>{
  theme: {
    extend: {}
  },
}
kazuhekazuhe

ESLint をセットアップする。

必要そうなライブラリを install する。

pnpm add -D eslint eslint-plugin-vue @vue/eslint-config-typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin

とりあえずの ESLint の設定ファイルを作成する。

.eslintrc.yml
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 を利用しているので、以下の様に保存時に自動整形をする様に設定した。

.vscode/settings.json
{
  "editor.tabSize": 2,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
  },
}
kazuhekazuhe

Prettier をセットアップする。

pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier

追記する。

.eslintrc.yml
...
extends:
  - plugin:vue/vue3-recommended
  - eslint:recommended
  - "@vue/typescript/recommended"
+  - plugin:prettier/recommended
...

これでファイルを保存すると自動で Prettier の整形も行ってくれる。

kazuhekazuhe
.
├── 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 に移行する。合わせて ufovue など npm では不要なライブラリは package.json から削除する。

kazuhekazuhe

GitHub Pages へ継続的デプロイをする。
つい最近以下の様な機能がリリースされたみたい。
https://github.blog/changelog/2022-07-27-github-pages-custom-github-actions-workflows-beta/

以下の様に設定した。

.github/workflows/deploy.yml
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 ブランチにマージすると意図した通りデプロイされた。
https://github.com/kazuhe/kazuhe.github.io/actions/runs/2769003556

kazuhekazuhe

Nuxt3 に対応した @nuxt/content を利用してコンテンツを管理する。

https://content.nuxtjs.org/

ドキュメントに従って環境を作成していく。

npm i -D @nuxt/content

nuxt.config の modules に追記する。

nuxt.config.ts
export default defineNuxtConfig({
  modules: [..."@nuxt/content"],
  ...
});

/content に新しくマークダウンを作成する。

content/index.md
---
title: 'index page'
description: 'トップページです'
---

# Top page

- list
- list2
content/about.md
---
title: 'about page'
description: 'foo'
---

# タイトル

テキストテキストテキストテキストテキスト
src/pages/[...slug].vue
<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 が見つからないらしい。
なので以下のよう修正した。

nuxt.config.ts
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:3000http://localhost:3000/about も about.md が表示されている。

以下の様な issue を見つけた。
https://github.com/nuxt/content/issues/1237

意訳すると、バグ修正はしてるけどリリース予定は決まっていないから、取り急ぎ @nuxt/content-edge を使ってくれとのこと。

index.md 以外は正しく機能しているっぽいので今はこのまま利用することにした。

kazuhekazuhe

Zenn の記事を取得してブログに表示させる。

rss-parser というライブラリを利用して、Zenn の RSS を利用することにした。

npm i -D rss-parser

RSS から取得した記事らと、先に設定した nuxt/content の記事をそれぞれ取得して、コンポーネントが期待する型にコンバートしている。その後に最新順に並び替えて表示している。

src/pages/index.vue
<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>

便利だ。

このスクラップは2022/08/21にクローズされました