Open4

Vite + Vue + UnoCSS 環境構築

ナイトウコウスケナイトウコウスケ

手順

まずはべた書き

node とかのバージョンを確認する

Vue プロジェクトを作成

pnpm create vue@latest

ウィザードへの回答

Project name: try-vite-vue-unocss
Add TypeScript? > Yes
Add JSX Support? > No 
Add Vue Router for Single Page Application development? > No
Add Pinia for state management? > No
Add Vitest for Unit Testing? > No
Add an End-to-End Testing Solution? > No
Add ESLint for code quality? > Yes
Add Prettier for code formatting? > Yes
Add Vue DevTools 7 extension for debugging? (experimental) > No

prettier の設定ファイル

テキトー。

.prettierc.json
{
  "$schema": "https://json.schemastore.org/prettierrc",
  "semi": true,
  "tabWidth": 2,
  "singleQuote": false,
  "printWidth": 80,
  "trailingComma": "es5"
}

Vue プロジェクトの作成成功を確認

cd try-vite-vue-unocss
pnpm install
pnpm format
pnpm dev

localhost:5173 を確認して、サーバを停止しておく。

UnoCSS を試す

VS Code 拡張機能

https://unocss.dev/integrations/vscode

  • UnoCSS

https://marketplace.visualstudio.com/items?itemName=antfu.unocss

  • Iconify

https://marketplace.visualstudio.com/items?itemName=antfu.iconify

UnoCSS のインストール

pnpm add -D unocss

設定ファイルの修正

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules: [
    ["m-100px", { margin: "100px" }],
    ["text-green", { color: "green" }],
  ],
});

vite.config.ts
import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import UnoCSS from "unocss/vite";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), UnoCSS()],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});

src/main.ts
import { createApp } from "vue";
import App from "./App.vue";

import "uno.css";

createApp(App).mount("#app");

不要なものを消す

  • src/assets/
  • src/components/
rm -rf src/assets/ && rm -rf src/components/

ユーティリティを使い始める

App.vue
<template>
  <div class="m-100px text-green">Hello World!</div>
</template>

サーバを起動して、ユーティリティが有効かどうかを確認する。

pnpm dev

Attributify をためす

設定ファイルの修正 & ユーティリティの追加

uno.config.ts
import { defineConfig, presetAttributify } from "unocss";

export default defineConfig({
  presets: [presetAttributify()],
  rules: [
    ["m-100px", { margin: "100px" }],
    ["text-green", { color: "green" }],
    ["text-bold", { "font-weight": "700" }],
  ],
});

src/App.vue
<template>
  <div m-1 text="green bold">Hello World!</div>
</template>

グリッドレイアウトを UnoCSS で実装

リセットCSS をインストール

https://unocss.dev/integrations/astro#style-reset

pnpm add @unocss/reset
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";

import "@unocss/reset/tailwind.css";
import "uno.css";

createApp(App).mount("#app");

presetTagify, presetIcons の追加

uno.config.ts
import {
  defineConfig,
  presetAttributify,
  presetTagify,
  presetIcons,
  presetUno,
} from "unocss";

export default defineConfig({
  presets: [
    presetAttributify(),
    presetTagify(),
    presetIcons({
      scale: 2.0,
      autoInstall: true,
    }),
    presetUno(),
  ],
});

index.html の修正

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app" h-100vh></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

App.vue を修正してグリッドレイアウトを実装

App.vue
<template>
  <div
    p-5
    grid
    gap="x-8 y-8"
    grid-cols="sm:2 md:3 lg:4"
    h-full
    border="[&>div]:~ [&>div]:solid"
    class="[&>div]:rounded-lg [&>div]:grid [&>div]:justify-center [&>div]:items-center [&>div>*]:text-4xl"
  >
    <div border-red><i-carbon-logo-github text-green /></div>
    <div border-orange><i-carbon-logo-vue text-green /></div>
    <div border-yellow><i-twemoji-frog /></div>
    <div border-green><i-twemoji-green-heart /></div>
    <div border-gray><i-logos-unocss /></div>
    <div border-blue><i-devicon-nuxtjs /></div>
    <div border="#000080"><i-devicon-homebrew /></div>
    <div border-purple><i-logos-vitejs /></div>
    <div border-pink><i-logos-visual-studio-code /></div>
  </div>
</template>

ナイトウコウスケナイトウコウスケ

手順

Vue らしくコンポーネントに切り出したい

GridLayout.vue

src/components/GridLayout.vue
<script setup lang="ts">
const props = defineProps<{
  gap: string;
  columnsLg: string;
  columnsMd: string;
  columnsSm: string;
}>();
</script>

<template>
  <div
    grid
    px-5
    pb-5
    :gap="props.gap"
    :class="[columnsLg, columnsMd, columnsSm]"
  >
    <slot />
  </div>
</template>
src/components/GridIcon.vue
<script setup lang="ts">
import { computed } from "vue";

const props = defineProps<{
  borderColor?: string;
  icon?: string;
  isDark: boolean;
}>();

const borderClass = computed(() => {
  return props.borderColor || "border-gray";
});
const iconClass = computed(() => {
  return props.icon || "i-logos-vue";
});
</script>

<template>
  <div
    grid
    items-center
    justify-center
    border
    rounded-lg
    border-solid
    :class="borderClass"
    hover="bg-gray"
  >
    <span
      text-4xl
      :class="[iconClass, { 'text-white': isDark }]"
    >
    </span>
  </div>
</template>

src/components/NavBar.vue
<script setup lang="ts">
import { computed } from "vue";

const props = defineProps<{
  isDark: boolean;
}>();

const emit = defineEmits<{
  (event: "toggleDarkMode"): void;
}>();

const emitToggleDarkMode = () => {
  emit("toggleDarkMode");
};

const iconClass = computed(() => {
  return props.isDark ? "i-mi-moon text-white" : "i-mi-sun";
});
</script>

<template>
  <nav
    h-10vh
    w-full
    px-10
    flex
    justify-end
    items-center
  >
    <button @click="emitToggleDarkMode">
      <span
        text-2xl
        flex
        content-center
        :class="iconClass"
      ></span>
    </button>
  </nav>
</template>

src/App.vue
<script setup lang="ts">
import { ref } from "vue";

import GridLayout from "./components/GridLayout.vue";
import GridIcon from "./components/GridIcon.vue";
import Navbar from "./components/Navbar.vue";

const isDark = ref(false);

const toggleDarkMode = () => {
  isDark.value = !isDark.value;
};

const items = [
  { borderColor: "border-red", icon: "i-carbon-logo-github" },
  { borderColor: "border-orange", icon: "i-carbon-logo-vue" },
  { borderColor: "border-yellow", icon: "i-twemoji-frog" },
  { borderColor: "border-green", icon: "i-twemoji-avocado" },
  { borderColor: "border-gray", icon: "i-logos-unocss" },
  { borderColor: "border-blue", icon: "i-devicon-nuxtjs" },
  { borderColor: "border-#000080", icon: "i-devicon-homebrew" },
  { borderColor: "border-purple", icon: "i-logos-vitejs" },
  { borderColor: "border-pink", icon: "i-logos-visual-studio-code" },
];
</script>

<template>
  <div :class="{ 'bg-black': isDark }">
    <Navbar
      :isDark
      @toggleDarkMode="toggleDarkMode"
    >
    </Navbar>
    <GridLayout
      :gap="'x-8 y-8'"
      :columnsLg="'lg:grid-cols-4'"
      :columnsMd="'md:grid-cols-3'"
      :columnsSm="'sm:grid-cols-2'"
      h-90vh
    >
      <GridIcon
        v-for="(item, index) in items"
        :key="`icon-${index}`"
        :borderColor="item.borderColor"
        :icon="item.icon"
        :isDark
      />
    </GridLayout>
  </div>
</template>