【Nuxt3】DockerでNuxt+αの環境構築をしたい
目的
Dockerで環境構築すればいろいろ使いまわせて楽そう
入れたいもの
- Nuxt3
- TailwindCSS
- ESLint + Prettier
- daisyui
- Vitest
- Storybook
Docker
ディレクトリ構成
├── docker
│ └── nuxt
│ └── Dockerfile
├── docker-compose.yml
└── front
Dockerfile
FROM node:16-slim
ENV TZ Asia/Tokyo
WORKDIR /app
RUN apt-get update \
&& apt-get install -y \
git \
vim
docker-compose.yml
version: "3.9"
services:
nuxt:
container_name: nuxt
build: ./docker/nuxt/
volumes:
- ./front:/app:cached
ports:
- "80:3000"
- "24678:24678"
tty: true
environment:
- HOST=0.0.0.0
- port=80
- CHOKIDAR_USEPOLLING=true
# command: sh -c "yarn && yarn dev -o"
参考
Docker起動
docker-compose up -d
docker-compose exec nuxt bash
Nuxtのセットアップ
※以下からはコンテナ内で実行
npx nuxi init .
yarn install
@tsconfig/strictest
のインストール
yarn add -D @tsconfig/strictest
scrDir
の設定とtsconfig
の設定
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
nitro: {
preset: 'node',
},
devServer: {
host: '0.0.0.0',
},
srcDir: 'src',
typescript: {
tsConfig: {
extends: '@tsconfig/strictest/tsconfig.json',
compilerOptions: {
noImplicitReturns: false, // For middleware
},
},
},
});
app.vue
をsrc
に移す
./front
├── .nuxt
├── README.md
├── node_modules
├── .gitignore
├── nuxt.config.ts
├── package.json
├── src
│ └── app.vue
├── tsconfig.json
└── yarn.lock
TailwindCSS
インストール
yarn add -D @nuxtjs/tailwindcss
nuxt.config.ts
の編集
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
nitro: {
preset: 'node',
},
devServer: {
host: '0.0.0.0',
},
srcDir: 'src',
typescript: {
tsConfig: {
extends: '@tsconfig/strictest/tsconfig.json',
compilerOptions: {
noImplicitReturns: false, // For middleware
},
},
},
modules: ['@nuxtjs/tailwindcss'],
tailwindcss: {
exposeConfig: true,
configPath: 'tailwind.config',
},
});
tailwind.config.ts
の作成
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.html', './src/**/*.vue', './src/**/*.jsx'],
theme: {
extend: {
colors: {
'light-black': '#333333',
},
},
},
};
./front
├── .nuxt
├── README.md
├── node_modules
├── .gitignore
├── nuxt.config.ts
├── package.json
├── src
│ └── app.vue
├── tailwind.config.cjs
├── tsconfig.json
└── yarn.lock
動作確認
yarn run dev
Nuxi 3.0.0
Nuxt 3.0.0 with Nitro 1.0.0
> Local: http://localhost:3000/
> Network: http://172.22.0.2:3000/
ℹ Using default Tailwind CSS file from runtime/tailwind.css
ℹ Tailwind Viewer: http://0.0.0.0:3000/_tailwind/
ERROR [postcss] ENOENT: no such file or directory, open '/app/src/app.vue'
ℹ Vite client warmed up in 3795ms
✔ Nitro built in 866 ms
ここでエラー発生。/app/src/app.vue
が開けない模様
エラー内容
500
[vite-node] [plugin:vite:css] [@tailwind base; @tailwind components; @tailwind utilities; ] /@fs./node_modules/@nuxtjs/tailwindcss/dist/runtime/tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;
at /@fs./node_modules/@nuxtjs/tailwindcss/dist/runtime/tailwind.css
完全に手詰まったので@nuxtjs/tailwindcss
ではなくTailwind公式で示されている方法に転換してみる
だめでした
そもそもtailwindcss自体読み込まれませんでした
そもそもtailwind関係なしにsrc/app.vue
が読み込めないっぽい
んー、
どうやらsrc/app.vue
だけが読み込めなくてapp/app.vue
とかsrc/src/app.vue
だと読み込めました(tailwind有りではまだ試してない)
とりあえずnuxt.config.ts
のsrcDir
にはapp
を指定しておくことにします
無事tailwind動作しました……
@nuxtjs/tailwindcss
の方で動作確認できました。やはりsrcDir
にsrc
が指定されているときのみsrc/app.vue
が読み込めないみたいです。どういうこと?
dockerのworkdirが/app
なのが原因とかってありますかね
./front
├── .gitignore
├── .nuxt
├── README.md
├── app
│ └── app.vue
├── node_modules
├── nuxt.config.ts
├── package.json
├── tailwind.config.cjs
├── tsconfig.json
└── yarn.lock
ESLint + Prettier
インストール
yarn add -D typescript eslint eslint-config-prettier eslint-plugin-import eslint-plugin-tailwindcss eslint-plugin-vue prettier prettier-plugin-tailwindcss @nuxtjs/eslint-config-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser
.eslintrc.cjs
の作成
module.exports = {
env: {
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:tailwindcss/recommended',
'@nuxtjs/eslint-config-typescript',
'prettier',
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['vue', '@typescript-eslint', 'tailwindcss'],
rules: {
/* typescript */
'dot-notation': 'off',
'no-restricted-imports': [
'error',
{
patterns: [
'../*',
'~/*',
'~~/*',
'./assets/*',
'./components/*',
'./pages/*',
'./plugins/*',
'./router/*',
'./composables/*',
'./server/*',
'./store/*',
'./types/*',
'./utils/*',
'./libs/*',
'./*.vue',
],
},
],
'import/order': [
'error',
{
'groups': [
'builtin',
'external',
'parent',
'sibling',
'index',
'object',
'type',
],
'pathGroups': [
{
pattern: '{vue,vue-router,vite,@vitejs/plugin-vue}',
group: 'builtin',
position: 'before',
},
{
pattern: '@src/**',
group: 'parent',
position: 'before',
},
],
'pathGroupsExcludedImportTypes': ['builtin'],
'alphabetize': {
order: 'asc',
},
'newlines-between': 'always',
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports' },
],
/* nuxt */
'vue/multi-word-component-names': 'off',
'vue/require-v-for-key': 'off',
/* tailwindcss */
'tailwindcss/no-custom-classname': [
'warn',
{
config: './front/tailwind.config.cjs',
},
],
'tailwindcss/classnames-order': 'off',
},
};
.prettierrc
の作成
{
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"quoteProps": "consistent",
"trailingComma": "es5",
"vueIndentScriptAndStyle": true
}
daisyui
インストール
yarn add daisyui
global.d.ts
の作成
declare module 'daisyui';
tailwind.config.cjs
の編集
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./app/**/*.html', './app/**/*.vue', './app/**/*.jsx'],
theme: {
extend: {
colors: {
'light-black': '#333333',
},
},
},
plugins: [require('daisyui')],
};
Vitest
ここからは初挑戦
どうやら@nuxt/test-utils
もあるらしいがまだ情報が少ないので@vue/test-utils
を採用する
参考
インストール
yarn add -D vitest @vue/test-utils
vitest.config.ts
の作成
/// <reference types="vitest" />
import Vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [Vue()],
test: {
globals: true,
environment: 'jsdom',
},
});
package.json
にtest
を追加
{
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
+ "test": "vitest"
},
"devDependencies": {
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@nuxtjs/tailwindcss": "^6.2.0",
"@tsconfig/strictest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"@vue/test-utils": "^2.2.7",
"eslint": "^8.31.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.4",
"eslint-plugin-tailwindcss": "^3.8.0",
"eslint-plugin-vue": "^9.8.0",
"jsdom": "^21.0.0",
"nuxt": "3.0.0",
"prettier": "^2.8.2",
"prettier-plugin-tailwindcss": "^0.2.1",
"typescript": "^4.9.4",
"vitest": "^0.27.1"
},
"dependencies": {
"daisyui": "^2.46.1"
}
}
テスト実行
yarn run test
なんかインストールしろと言われたのでyesを入力する
yarn run v1.22.19
$ vitest
MISSING DEP Can not find dependency 'jsdom'
✔ Do you want to install jsdom? … yes
[1/4] Resolving packages...
info There appears to be trouble with your network connection. Retrying...
[2/4] Fetching packages...
warning vscode-languageclient@7.0.0: The engine "vscode" appears to be invalid.
[3/4] Linking dependencies...
warning " > daisyui@2.46.1" has unmet peer dependency "autoprefixer@^10.0.2".
warning " > daisyui@2.46.1" has unmet peer dependency "postcss@^8.1.6".
warning "daisyui > postcss-js@4.0.0" has unmet peer dependency "postcss@^8.3.3".
warning "daisyui > tailwindcss@3.2.4" has unmet peer dependency "postcss@^8.0.9".
warning "@nuxtjs/tailwindcss > @nuxt/postcss8 > css-loader@5.2.7" has unmet peer dependency "webpack@^4.27.0 || ^5.0.0".
warning "@nuxtjs/tailwindcss > @nuxt/postcss8 > postcss-loader@4.3.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning " > @vue/test-utils@2.2.7" has unmet peer dependency "vue@^3.0.1".
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 30 new dependencies.
info Direct dependencies
└─ jsdom@21.0.0
info All dependencies
├─ @tootallnate/once@2.0.0
├─ acorn-globals@7.0.1
├─ asynckit@0.4.0
├─ combined-stream@1.0.8
├─ cssom@0.5.0
├─ cssstyle@2.3.0
├─ data-urls@3.0.2
├─ decimal.js@10.4.3
├─ delayed-stream@1.0.0
├─ domexception@4.0.0
├─ entities@4.4.0
├─ escodegen@2.0.0
├─ esprima@4.0.1
├─ form-data@4.0.0
├─ html-encoding-sniffer@3.0.0
├─ http-proxy-agent@5.0.0
├─ iconv-lite@0.6.3
├─ is-potential-custom-element-name@1.0.1
├─ jsdom@21.0.0
├─ nwsapi@2.2.2
├─ parse5@7.1.2
├─ psl@1.9.0
├─ querystringify@2.2.0
├─ saxes@6.0.0
├─ symbol-tree@3.2.4
├─ tough-cookie@4.1.2
├─ tr46@3.0.0
├─ url-parse@1.5.10
├─ w3c-xmlserializer@4.0.0
└─ xmlchars@2.2.0
$ nuxt prepare
[log] Nuxi 3.0.0
[info] [nuxt:tailwindcss] Using default Tailwind CSS file from runtime/tailwind.css
[success] Types generated in .nuxt
Package jsdom installed, re-run the command to start.
error Command failed with exit code 43.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
とここでエラー発生。vitest.config.ts
でエラーが出ていた。
型 '{ plugins: Plugin_2[]; test: { globals: boolean; environment: string; }; }' の引数を型 'UserConfigExport' のパラメーターに割り当てることはできません。
オブジェクト リテラルは既知のプロパティのみ指定できます。'test' は型 'UserConfigExport' に存在しません。
defineConfig
は引数にUserConfigExport
型を受け取るようだが、UserConfigExport
にもともとtest
はないらしくエラっている模様
/// <reference types="vitest" />
を加えると直るらしいが、解決せず
どうやら自分で型を定義することで解決できるっぽい
/// <reference types="vitest" />
import Vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import type { UserConfig } from 'vite';
import type { InlineConfig } from 'vitest';
interface VitestConfigExport extends UserConfig {
test: InlineConfig;
}
export default defineConfig({
plugins: [Vue()],
test: {
global: true,
environment: 'jsdom',
},
} as VitestConfigExport);
テスト実行
試しに適当なコンポーネントを作成しテストを行なってみる
<script setup lang="ts">
interface Props {
content: string;
}
const props = defineProps<Props>();
const { content } = toRefs(props);
</script>
<template>
<p class="font-bold">{{ content }}</p>
</template>
import { mount } from '@vue/test-utils';
import { describe, test, expect } from 'vitest';
// eslint-disable-next-line no-restricted-imports
import BaseAtomsText from '../../../../components/base/atoms/Text.vue';
describe('BaseAtomsText', () => {
test('メッセージが表示される', () => {
const wrapper = mount(BaseAtomsText, {
props: {
content: 'Hello World',
},
});
expect(wrapper.text()).toBe('Hello World');
});
});
ちなみにpathはaliasでやるとダメみたいです。
……と思いきや設定できるみたいです。
/// <reference types="vitest" />
import Vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import type { UserConfig } from 'vite';
import type { InlineConfig } from 'vitest';
interface VitestConfigExport extends UserConfig {
test: InlineConfig;
}
export default defineConfig({
plugins: [Vue()],
test: {
global: true,
environment: 'jsdom',
},
resolve: {
alias: {
'@': '/app',
},
},
} as VitestConfigExport);
それでは、いざテスト実行!
ReferenceError
が発生しました。
どうやら、Vue独自の要素はimportされていないみたいです。
それを解決します。
unplugin-auto-import
のインストール
yarn add -D unplugin-auto-import
vitest.config.ts
の編集
/// <reference types="vitest" />
import Vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import type { UserConfig } from 'vite';
import AutoImport from 'unplugin-auto-import/vite';
import type { InlineConfig } from 'vitest';
interface VitestConfigExport extends UserConfig {
test: InlineConfig;
}
export default defineConfig({
plugins: [Vue(), AutoImport({ imports: ['vue'] })],
test: {
global: true,
environment: 'jsdom',
},
resolve: {
alias: {
'@': '/app',
},
},
} as VitestConfigExport);
これでテストを実行するとauto-imports.d.ts
が生成される
しかし、これでもNuxtの独自要素はimportされない。@nuxt/test-utils
を頼る必要があるみたいだが、今回は見送る
そして、テストを実行すると
yarn run v1.22.19
$ vitest
DEV v0.27.1 /app
✓ app/tests/components/base/atoms/Text.spec.ts (1)
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 20:49:16
Duration 9.83s (transform 2.49s, setup 2ms, collect 1.10s, tests 78ms)
PASS Waiting for file changes...
press h to show help, press q to quit
無事通過!
Storybook
インストール
npx sb init --type vue3 --builder @storybook/builder-vite
参考
eslintのplugin入れるか? と聞かれるので同意する
✔ Do you want to run the 'eslintPlugin' migration on your project? … yes
すると、.storybook
とstories
が生成される。stories
は要らないので消してOK
.storybook/main.js
の編集
module.exports = {
stories: [
'../app/components/**/*.stories.mdx',
'../app/components/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: '@storybook/vue3',
core: {
builder: '@storybook/builder-vite',
},
features: {
storyStoreV7: true,
},
};
Storybookの起動
いざ起動!
yarn storybook
デフォルトで6006ポートで起動するみたい。localhost:6006を開く……
開きません。
docker-compose.yml
の編集
version: "3.9"
services:
nuxt:
container_name: nuxt
build: ./docker/nuxt/
volumes:
- ./front:/app:cached
ports:
- "80:3000"
- "24678:24678"
+ - "6006:6006"
tty: true
environment:
- HOST=0.0.0.0
- port=80
- CHOKIDAR_USEPOLLING=true
command: sh -c "yarn && yarn dev -o"
docker系を編集したら一度buildし直さないといけない
exit
docker-compose stop
docker-compose up -d --build
これで開くようになります。
.eslintrc.cjs
の編集
module.exports = {
env: {
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:tailwindcss/recommended',
'@nuxtjs/eslint-config-typescript',
+ 'plugin:storybook/recommended',
'prettier',
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['vue', '@typescript-eslint', 'tailwindcss'],
rules: {
/* typescript */
'dot-notation': 'off',
'no-restricted-imports': [
'error',
{
patterns: [
'../*',
'~/*',
'~~/*',
'./assets/*',
'./components/*',
'./pages/*',
'./plugins/*',
'./router/*',
'./composables/*',
'./server/*',
'./store/*',
'./types/*',
'./utils/*',
'./libs/*',
'./*.vue',
],
},
],
'import/order': [
'error',
{
'groups': [
'builtin',
'external',
'parent',
'sibling',
'index',
'object',
'type',
],
'pathGroups': [
{
pattern: '{vue,vue-router,vite,@vitejs/plugin-vue}',
group: 'builtin',
position: 'before',
},
{
pattern: '@src/**',
group: 'parent',
position: 'before',
},
],
'pathGroupsExcludedImportTypes': ['builtin'],
'alphabetize': {
order: 'asc',
},
'newlines-between': 'always',
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports' },
],
/* nuxt */
'vue/multi-word-component-names': 'off',
'vue/require-v-for-key': 'off',
/* tailwindcss */
'tailwindcss/no-custom-classname': [
'warn',
{
config: './front/tailwind.config.cjs',
},
],
'tailwindcss/classnames-order': 'off',
},
};
先ほど同意したせいで勝手に崩されているので修正する
動作確認
storyの作成
// eslint-disable-next-line no-restricted-imports
import Text from './Text.vue';
import type { Meta, StoryFn } from '@storybook/vue3';
export default {
title: 'Base/Atoms/Text',
component: Text,
args: {
content: 'Hello World',
},
} as Meta<typeof Text>;
const Template: StoryFn<typeof Text> = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { Text },
setup() {
return { args };
},
template: `
<Text v-bind="args"/>
`,
});
export const Default = Template.bind({});
Default.args = {
content: 'Hello World',
};
ここでもaliasが効いていないので設定しようとしたが沼ったので保留
ここを参考にしたが解決せず
.storybook/main.js
の編集
またもやReference Error
が出たので直す。こっちは直った
const AutoImport = require('unplugin-auto-import/vite');
module.exports = {
stories: [
'../app/components/**/*.stories.mdx',
'../app/components/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: '@storybook/vue3',
core: {
builder: '@storybook/builder-vite',
},
features: {
storyStoreV7: true,
},
async viteFinal(config) {
config.plugins.push(
AutoImport({ imports: ['vue'], dts: '../auto-imports.d.ts' })
);
return config;
},
};
ここまでのディレクトリ構成
./front
├── .eslintrc.cjs
├── .gitignore
├── .prettierrc
├── .storybook
│ ├── main.js
│ ├── preview-head.html
│ └── preview.js
├── README.md
├── app
│ ├── app.vue
│ ├── components
│ │ └── base
│ │ └── atoms
│ │ ├── Text.stories.ts
│ │ └── Text.vue
│ ├── tests
│ │ └── components
│ │ └── base
│ │ └── atoms
│ │ └── Text.spec.ts
│ └── types
│ └── global.d.ts
├── auto-imports.d.ts
├── nuxt.config.ts
├── package.json
├── tailwind.config.cjs
├── tsconfig.json
├── vitest.config.ts
└── yarn.lock
※node_modules
と.nuxt
は省略