独学エンジニアのメモ帳

得た知識のアウトプットとか日常のメモとか。ゆるくやる。

React hooksで非同期通信のあるグローバルストア管理

ちょっとしっくりくるタイトルじゃない気がする。語彙力。

やりたいこと

例えば、ログインのよくある流れを考えてみる。
ストアの動きとして
1. ローディング状態をtrueにしてローダーを表示(dispatch)
2. ログイン通信
3. 取得したユーザデータをストアに保存(dispatch)
という動きが考えられる。

Reduxではredux-thunkなどを使ってaction内からdispatchすることで実現できたが、
Reduxを使わずにReact Hooksで実装する場合どうしたら良いか分からなかったので調べながらやってみた。

ストアの作成が趣旨なので、APIでのログイン処理の中身には触れません。

結論

ContextAPI + カスタムフック で対応できる

作成例

今回は一つですが、情報に応じてファイル分割も簡単にできそう。
ディレクトリは一旦全て同じ階層にあるという想定で。

ストア(reducer)の作成

まずは普通にreducerとactionを作る。

[actions.js]

export const AUTH_START = 'AUTH_START';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAIL = 'AUTH_FAIL';

export const authStart = () => {
  return {
    type: AUTH_START,
  }
}

// 引数はログインの仕様に合わせて
export const authSuccess = (id, email) => {
  return {
    type: AUTH_SUCCESS,
    id,
    email,
  }
}

export const authFail = (error) => {
  return {
    type: AUTH_FAIL,
    error
  }
}

[reducer.js]

import { AUTH_START, AUTH_SUCCESS, AUTH_FAIL } from './actions';

export const initialState = {
  id: '',
  email: '',
  error: null,
  loading: false,
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
  case AUTH_START:
    return { ...state, loading: true };
  case AUTH_SUCCESS:
      return {
        ...state,
        id: action.id,
        email: action.email,
        error: null,
        loading: false
      };
  case AUTH_FAIL:
    return { ...state, error: action.error, loading: false };

  default:
    return state
  }
}

export default reducer;

普通のreducer。

Contextの作成

[context.js]

import React, {useReducer} from "react";
import reducer, { initialState } from "./reducer";

export const Context = React.createContext({});

export const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return <Context.Provider value={{state, dispatch}}>{children}</Context.Provider>;
};

[App.js]

import { Provider } from "./store/auth/context";

const App = () => {
  return (
    <Provider>
      <ChildComponents />
    </Provider>
  );
};

ChildComponents以下でuseContext(Context)することで、
Providerのvalueに渡した値がchildrenの中で使えるようになる。

グローバルストアは一応ここまでで完成。

カスタムフックを作成

今回やりたいことは、非同期通信の結果を待ってdispatchすることので、このままだとできない。
そのため、カスタムフックを作成する。
use~で分かりやすい名前を付ける。

[useAuth.js]

impor { useCallback } from "react";
import { authStart, authSuccess, authFail } from "./actions";
import axios from "axios";

export const useAuth = () => {
  const {state, dispatch} = useContext(Context);

  const login = useCallback((email, password) => {
    // ローディングを走らせる
    dispatch(authStart());

    // ログイン通信
    axios.post('/api/login', {
      email,
      password,
    }).then(res => {
      console.log(res);
      // 成功したらログイン情報をストアに保存
      dispatch(authSuccess(res.data.user.id, res.data.user.email));
    }).catch(err => {
      // 失敗したらエラーをストアに保存
      console.log(err.response);
      dispatch(authFail(err));
    })
  }, [dispatch, authStart, authSuccess, authFail]);

  // ここでreturnしたものを各コンポーネントで使う
  return {state, login}
};

カスタムフック内でContextを呼び出し、
loginメソッド内で複数回dispatchしています。
コンポーネントではこのフックを介してストアにアクセスします。

使ってみる

[Login.js]

import React, { FormEvent, useState } from "react";

import { useAuth } from "./useAuth";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const { state, login } = useAuth();

  const loginHandler = (e) => {
    e.preventDefault();
    login(email, password);
  };

  let loader = null;
  if (state.loading) loader = <div>loading...</div>;

  return (
    <React.Fragment>
      {loader}
      <div>
        <h2>ユーザー情報</h2>
        <p>id: {state.id}</p>
        <p>email: {state.email}</p>
      </div>

      <form onSubmit={loginHandler}>
        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={e => setEmail(e.target.value)}
        />
        <input
          type="password"
          placeholder="Password"
          value={password}
          onChange={e => setPassword(e.target.value)}
        />
        <button type="submit">Login</button>
      </form>
    </React.Fragment>
  );
};

export default Login;

ログイン処理中に「loading...」が表示され、
ログイン後にユーザー情報が表示されれば成功。

まとめ

カスタムフックのおかげで記述少なめでグローバルストアが定義できた。
Reduxの導入はいろいろ手間がかかるので、小さい規模なら十分というか、むしろこっちが良い感じ。

ただ公式にも書いてあったけど、言ってしまえばただの関数なので、どうにでも書けてしまう感はある。
調べてもこれという正解がいまいち分からないので、複雑なものをReact Hooksでやろうとしたらまだいろいろ躓きそう。
今後どうなるかは分からないけど、Reduxの方が今のところ参考になる記事も多い気がするので、変なやり方を覚えたりする心配は少なそう。
規模が大きかったり複雑な場合は大人しくreduxに身を委ねるのが良いのかもと思ったり。

Laravel8 + Sanctum + ReactでSPA認証


前提

Larave8 + ReactでSPA環境ができていること

この記事ではブログ内で作った続きでやります。 nochio12.hatenablog.com


基本的に公式通り進めます。
laravel.com

Sanctumではトークンでの認証とCookieを使った認証のどちらかを選ぶことができますが、
今回はCookieを使った認証を実装します。

インストール

$ composer require laravel/sanctum
$ php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
$ php artisan migrate

2つめのコマンドで設定ファイルとmigrationが作成されます。

ミドルウェアの追加

[Kernel.php]

+ use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
+    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

モデルの修正

[Models/User.php]

+ use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
-   use HasApiTokens, Notifiable;
+   use HasFactory, HasApiTokens, Notifiable;
}

CORS対策

[config/cors.php]

- 'paths' => ['api/*],
+ 'paths' => ['api/*', 'sanctum/csrf-cookie'],

- 'supports_credentials' => false,
+ 'supports_credentials' => true,



実際の環境のドメインをenvに追記
[.env]

+ SANCTUM_STATEFUL_DOMAINS=127.0.0.1:8080



[config/sanctum.php]

    'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1,127.0.0.1:8000,::1')),

ローカルでの開発で、ここにあるドメインであれば追記しなくて平気ですが
ポートを変えていたり、本番環境ではどっちみち必要。 調べると出てきますが、公式で言及がないため忘れそう。

認証処理

Api用の認証コントローラを作成

$ php artisan make:controller Api/Auth/LoginController

[Api/Auth/LoginController.php]

<?php

namespace App\Http\Controllers\Api\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    use AuthenticatesUsers;

    /**
     * ログイン
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function login(Request $request)
    {
        // バリデーション
        $this->validateLogin($request);
        $result = false;
        $status = 401;
        $message = 'ユーザが見つかりません';
        $user = null;
        $credentials = $request->only('email', 'password');
        if (Auth::attempt($credentials)) {
            // Success
            $result = true;
            $status = 200;
            $message = 'OK';
            $user = Auth::user();
            // ※古いトークン削除&新しいトークン生成
            $user->tokens()->where('name', 'token-name')->delete();
            $token = $user->createToken('token-name')->plainTextToken;
        }
        return response()->json(['result' => $result, 'status' => $status, 'user' => $user, 'message' => $message]);
    }


    /**
     * ログアウト
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     */
    public function logout(Request $request)
    {
        Auth::logout();
        $result = true;
        $status = 200;
        $message = 'ログアウトしました';
        return response()->json(['result' => $result, 'status' => $status, 'message' => $message]);
    }
}

フロントの実装

[js/bootstrap.js]

window.axios = require('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+ window.axios.defaults.withCredentials = true;


ルーティングの追加 [app.js]

require('./bootstrap');

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";

import NavBar from "./NavBar";
import Home from "./Home";
import About from "./About";
import Auth from "./Auth"; // 追加

const App = () => {
    return (
        <BrowserRouter>
            <NavBar />
            <Route path="/about" component={About} />
            <Route path="/auth" component={Auth} /> // 追加
            <Route exact path="/" component={Home} />
        </BrowserRouter>
    );
};

if (document.getElementById("app")) {
    ReactDOM.render(<App />, document.getElementById("app"));
}


ログイン画面
[Auth.js]

import React, { useState, useEffect } from "react";

const Auth = () => {
  const [user, setUser] = useState(null);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  // ブラウザリロード時にログイン済みか判定
  useEffect(() => {
    getUser();
  }, [getUser]);

  // 認証ユーザを取得
  const getUser = () => {
      axios.get("/api/user").then(res => {
        console.log('[getUser]ログイン済み');
        console.log(res.data);
        setUser(res.data);
      }).catch(err => {
        console.log('[getUser]ログインしてません');
      })
  };

  // ログイン
  const login = async e => {
    e.preventDefault();
    // ログイン時にCSRFトークンを初期化
    axios.get("/sanctum/csrf-cookie").then(response => {
      axios
      .post("/api/login", {
        email,
        password
      })
      .then(res => {
        console.log(res.data);
        if (res.data.result) {
          console.log('[login]ログイン成功');
          setUser(res.data.user);
        } else {
          console.log(res.data.message);
          console.log('[login]ログイン失敗');
        }
      })
      .catch(err => {
        console.log(err.response);
        console.log('[login]ログイン失敗');
      });
    })

  };

  // ログアウト
  const logout = () => {
    axios
      .get("/api/logout")
      .then(res => {
        setUser(null);
      })
      .catch(err => {
        console.log(err);
      });
  };

  // ログインフォーム
  let form = (
    <form onSubmit={login}>
      <label>email</label>
      <input
        type="email"
        value={email}
        onChange={e => setEmail(e.target.value)}
      />
      <label>password</label>
      <input
        type="password"
        value={password}
        onChange={e => setPassword(e.target.value)}
      />
      <button type="submit">Login</button>
    </form>
  );

  // ユーザ情報
  let userInfo = null;

  // 認証済みの場合、ログアウトボタンとユーザ情報を表示
  if (user) {
    form = <button onClick={logout}>Logout</button>;
    userInfo = (
      <div>
        <h2>User</h2>
        <div>name: {user.name}</div>
        <div>email: {user.email}</div>

      </div>
    );
  }

  return (
    <div>
      {form}
      {userInfo}
      <button onClick={getUser}>getUser</button>
    </div>
  );
};

export default Auth;

ビルドして確認。

ログインを試す

ログインのためのユーザーを作成します。 1つあればいいので、tinkerでサクッと作ります。

$ php artisan tinker
>>> App\Models\User::factory()->create(['name' => 'hoge', 'email' => 'hoge@hoge.com', 'password' => Hash::make('password')]);

先ほど作成したフォームに
email : 'hoge@hoge.com
password : password
を打ち込み、ログインを試します。

ログインが成功し、画面にユーザー情報が表示されれば成功。 ログアウトせずにリロードをかけると勝手にログイン状態になるはず。

f:id:nochio12:20201003215237p:plain

まとめ

パッケージがよしなにやってくれてラクチンですが
内部的に何が起きているのかはちゃんと理解して使いたいところ。

Laradock+Apache2のDocumentRootの設定でハマった


先日、LaradockでLaravelの開発環境を構築しました。

サーバはapache2にしたのですが、若干エラーにハマったのでメモ書き。

とりあえず普通に構築

最初の設定はこんな感じ

APP_CODE_PATH_HOST=../laravelproject
APP_CODE_PATH_CONTAINER=/var/www/

<中略>

APACHE_HOST_HTTP_PORT=800
APACHE_DOCUMENT_ROOT=/var/www/public
FROM webdevops/apache:centos-7
<VirtualHost *:80>
  ServerName servername.test
  DocumentRoot /var/www
  Options Indexes FollowSymLinks

  <Directory "/var/www/">
    AllowOverride All
    <IfVersion < 2.4>
      Allow from all
    </IfVersion>
    <IfVersion >= 2.4>
      Require all granted
    </IfVersion>
  </Directory>

</VirtualHost>

この設定で/var/www/直下にlaravelprojectが展開され、DocumentRootがpublicになると思いました。

エラー

「このサイトにアクセスできません」画面で接続できない。

ログを確認

$ docker logs -f laradock_apache2_1

無限にエラー出てる

2020-07-30 13:08:44,418 INFO spawned: 'apached' with pid 23
-> Executing /opt/docker/bin/service.d/httpd.d//10-init.sh
2020-07-30 13:08:44,507 INFO success: apached entered RUNNING state, process has stayed up for > than 0 seconds (startsecs)  
AH00526: Syntax error on line 119 of /etc/httpd/conf/httpd.conf:
DocumentRoot '/var/www/html' is not a directory, or is not readable
2020-07-30 13:08:44,659 INFO exited: apached (exit status 1; not expected)

DocumentRootの/var/www/htmlがない? 設定が反映されていないのだろうか。

処置

とりあえず動く状態を目指す

APP_CODE_PATH_HOST=../laravelproject
APP_CODE_PATH_CONTAINER=/var/www/project

<中略>

APACHE_HOST_HTTP_PORT=800
APACHE_DOCUMENT_ROOT=/var/www/project/public
<VirtualHost *:80>
  ServerName servername.test
  DocumentRoot /var/www/project/public
  Options Indexes FollowSymLinks

  <Directory "/var/www/project/public/">
    AllowOverride All
    <IfVersion < 2.4>
      Allow from all
    </IfVersion>
    <IfVersion >= 2.4>
      Require all granted
    </IfVersion>
  </Directory>

</VirtualHost>
$ docker-compose down 
$ docker-compose build apache2
$ docker-compose up -d apache2

http://localhost:800/にアクセスしたら無事いつものLaravel画面が。

まとめ

いろいろ調べたけど、原因がいまいち掴めず。 もっといい方法があれば知りたい。

10/02追記 全然理解足りてなかった。 まあ今も理解できてないんですが、
サーバーにアクセスがあった時に見に行くRootはconfファイルの記述になるので、そっちの修正が必要だったと。

laradockの.envで設定する
APACHE_DOCUMENT_ROOT
は何をしているんだろうか。
Dockerfileに引数として渡されて環境変数として設定されている(?)けど、
その先にどう使われているが全く分からない。

Laravel8 + ReactのSPA環境構築

前提条件

・Laravel8のプロジェクトを作成し、初期画面表示まで完了している

環境構築した時の記事 nochio12.hatenablog.com

導入

Laravel8から公式にサポートされたJetstream+inertiaも気になりましたが、
どうもJetstreamはVueしか対応していないようなので、今回はスルーします。
Introduction | Laravel Jetstream

というわけで、Reactの導入手順はLaravel6辺りからと同じ手順です。

Reactのインストール

uiパッケージのインストール

$ composer require laravel/ui

Reactの導入

$ php artisan ui react --auth

...

Please run "npm install && npm run dev" to compile your fresh scaffolding.

ビルド

$ npm install
$ npm run dev

Reactの準備は一旦OK

Laravelのルーティングの設定

前の手順でbladeがいくつかできますが
SPAでは不要になるので消していきます。

また、ルーティングもReact側でやるので修正します。

削除前
resources
┗ views
┣ auth (削除)
┣ layouts - app.blade.php (views直下に移動)
┣ home.blade.php(削除)
┗ welcome.blade.php(削除)

削除後
resources
┗ views
┗ app.blade.php

app.blade.phpを修正

<body>
    <!-- divの中身を削除 -->
    <div id="app">
    </div>
</body>

resources/js/components/Example.js

- if (document.getElementById('example')) {
-    ReactDOM.render(<Example />, document.getElementById('example'));
- }

+ if (document.getElementById('app')) {
+     ReactDOM.render(<Example />, document.getElementById('app'));
+ }

ここまでできたらビルド

$ npm run dev

web.phpを修正して、
どんなURLでもapp.blade.phpを返すようにします。

Route::get('{any}', function () {
    return view('app');
})->where('any','.*');

ページを更新し、
ExampeComponentが表示されればOK

f:id:nochio12:20201002231835p:plain

Reactの初期設定

app.jsに初期設定を記述
app.js

require('./bootstrap');

import React from 'react';
import ReactDOM from 'react-dom';
import Example from './components/Example';


if (document.getElementById('app')) {
    ReactDOM.render(<Example />, document.getElementById('app'));
}

Exampleから描画関数を削除 Example.js

- if (document.getElementById('app')) {
-    ReactDOM.render(<Example />, document.getElementById('app'));
- }

Reactのルーティング設定

ルーティングライブラリをインストール

$ npm install react-router-dom

jsディレクトリに
NavBar.js
Home.js
About.js
の3つを作成

NavBar.js

import React from 'react';
import { Link } from 'react-router-dom';

const NavBar = () => {
    return (
        <div>
            <Link to="/">Home</Link>
            <Link to="/about">About</Link>
        </div>
    );
}

export default NavBar;

Home.js

import React from 'react';

const Home = () => {
    return (
        <div>
            Home
        </div>
    );
}

export default Home;

About.js

import React from 'react';

const About = () => {
    return (
        <div>
            About
        </div>
    );
}

export default About;

app.jsを変更

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";

import NavBar from "./NavBar";
import Home from "./Home";
import About from "./About";

const App = () => {
    return (
        <BrowserRouter>
            <NavBar />
            <Route path="/about" component={About} />
            <Route exact path="/" component={Home} />
        </BrowserRouter>
    );
};

if (document.getElementById("app")) {
    ReactDOM.render(<App />, document.getElementById("app"));
}

ルーターを使用する範囲をBrowserRouterで囲む必要があります。 Routeで、NavBar内のLinkのパスに対応するコンポーネントをセットしています。

$ npm run dev

して確認

f:id:nochio12:20201003001005g:plain

画面とURLが切り替わるのが確認できればOK 後はゴリゴリ開発していくだけです。

【Vue】「This can lead to unexpected behavior when compiling on a filesystem with other case-semantic」エラー対処

エラー内容

普段あまり見ないけど、タイポした時に出るエラー。

google翻訳の結果

これは、他のケースセマンティックを使用してファイルシステムコンパイルするときに予期しない動作を引き起こす可能性があります

原因と対策

例えば
/components/SampleComponent.vue
のようなファイルがあったとして

File1.vue

import SampleComponent from '@/components/SampleComponent';

File2.vue

import SampleComponent from '@/components/sampleComponent'; // sが小文字

のように複数箇所で別の記法でパスを書くと出てくるので、
File2.vueの方も

SampleComponent

に合わせる。

DockerでLaravel8(PHP7.4, Nginx, MySQL5.7)の最低限の環境構築

こちらを参考にさせていただきました。

qiita.com

Docker環境

ディレクトリ構成

project
┣ docker
┃ ┣ db
┃ ┣ nginx
┃ ┗ php
┣ server
┗docker-compose.yml

serverにlaravelプロジェクトが入る想定です。

各種ファイル作成

docker-compose.yml

version: '3'

services:
  php:
    container_name: php
    build: ./docker/php
    volumes:
    - ./server:/var/www/html

  nginx:
    image: nginx
    container_name: nginx
    ports:
    - 50080:80
    volumes:
    - ./server:/var/www/html
    - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
    - php

  db:
    image: mysql:5.7
    container_name: db
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: database
      MYSQL_USER: docker
      MYSQL_PASSWORD: docker
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
    - ./docker/db/data:/var/lib/mysql
    - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
    - 53306:3306

ポートが絶対に被らないよう
nginx -> 50080
mysql -> 53306
に変更しています。

/nginx/default.conf

server {
  listen 80;
    index index.php index.html;
    root /var/www/public;

  location / {
    try_files $uri $uri/ /index.php?$query_string;
  }

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass php:9000;
    fastcgi_index index.php;
    include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $document_root/index.php;
      fastcgi_param PATH_INFO $fastcgi_path_info;
  }
 }

php/Dockerfile

FROM php:7.4-fpm
COPY php.ini /usr/local/etc/php/


#Composer install
RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer

RUN apt-get update \
&& apt-get install -y \
git \
zip \
unzip \
vim
RUN apt-get update \
    && docker-php-ext-install pdo_mysql

ENV COMPOSER_ALLOW_SUPERUSER 1

ENV COMPOSER_HOME /composer

ENV PATH $PATH:/composer/vendor/bin


WORKDIR /var/www

RUN composer global require "laravel/installer"

php/php.ini

[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

コンテナ起動

$ docker-compose up -d

mysqlが起動失敗したので、エラーを確認

$ docker-compose up

バックグラウンド起動を辞めると、途中のエラーが分かります。

エラー内容

Operating system error number 22 in a file operation.

ぐぐったところ
docker-compose.ymlに

  db:
    image: mysql:5.7
    ...中略
    command: --innodb-use-native-aio=0 #この1行追加

でOKそうです。

一度dbディレクトリのデータを削除し、再度コンテナ起動

$ docker-compose up -d

Starting php     ... done
Starting db-host ... done
Starting nginx   ... done

無事起動完了

Laravelプロジェクト作成

作業コンテナに入る

$ docker-compose exec php bash

/var/www# composer create-project --prefer-dist laravel/laravel .

しばらく待ちます。

Package manifest generated successfully.
73 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan key:generate --ansi
Application key set successfully.

成功文が出たら
http://localhost:50080にアクセス

f:id:nochio12:20201002012635p:plain

Laravelのトップ画面が表示されればOK

DB作成

一度コンテナから出て、DBコンテナに接続します。

$ docker-compose exec db bash

コンテナ

/# mysql -u root -p
// docker-compose.ymlのMYSQL_ROOT_PASSWORD
Enter password: root

// データベース作成
mysql> create database laravel;

mysql> quit;
/# exit;

次にLaravel側の設定をします。

.env

DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel # さっき作ったDB名
DB_USERNAME=root
DB_PASSWORD=root

設定出来たら再びPHPコンテナへ

$ docker-compose exec php bash
/var/www# cd html
/var/www/html# php artisan migrate

無事マイグレーションが完了すればOK

まとめ

Laradock使ってて、重いし謎のエラー出るしで嫌になったので
今後はこの最小構成をベースにLaravel触っていきたい。

vue-cliでvue create後、npm run serveに失敗した時の対処法


環境

PC - Windows 10 Home node.js - 12.16.3 npm - 6.14.4

vueプロジェクト作成

まずはグローバルにvue-cliをインストール

> npm install -g @vue/cli

インストールを確認

> vue --version
@vue/cli 4.4.6

適当に作ったディレクトリでプロジェクト作成 今回はvueprojectという名前で進めます。

> vue create .

いろいろ聞かれますが、とりあえずデフォルトで進めます。

  Successfully created project vueproject.
  Get started with the following commands:

 $ npm run serve

と出れば成功。 言われた通り、サーバーを起動します。

> npm run serve

エラー発生

npm ERR! code ELIFECYCLE
npm ERR! vueproject@0.1.0 serve: `vue-cli-service serve`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the vueproject@0.1.0 serve script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\user\AppData\Roaming\npm-cache\_logs\2020-07-03T03_35_30_681Z-debug.log

vue-cli-serviceがおかしいらしいけど、よく分からないのでエラー文でググります。

解決

ググって発見。 https://teratail.com/questions/219324

プロジェクトディレクトリから下記を削除する - node_modules - package-lock.json

その後、モジュールを再インストール

> npm install

完了したら、サーバー起動

> npm run serve

  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.2.107:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

起動確認。

http://localhost:8080/ にアクセスし、下記の画面が表示されたらOK。 FireShot Capture 002 - portfolio - localhost.png

まとめ

別件でも同手順で対処したことあったし、モジュール入れ直しは割とあるあるなのかも。 エラー時にもっといろいろ出る場合は別の原因がありそう。