Vue.jsでHPを作成してみる - GitHub Pagesにポートフォリオサイトを作る

##はじめに
転職活動用のポートフォリオを作るにあたり勉強を兼ねてインデックス的なHPを作成することにしました。
Vue.jsにてサイトを作り、GitHubPagesを利用して公開できることを目標にします。

[成果物](#成果物)

##スキルアセット
この時点で業務経験のあった主なスキルは以下の通りです。
ミドルウェアインストール以上のWEBサーバ構築全般一通り可能です。

- 言語：PHP、Java、HTML/CSS/JavaScript
- ファイル管理：SVN
- その他：Linux、Bash、SQL、Perl、Apache、Tomcat、AWS EC2、AWS S3、AWS AutoScallingなどの基本的な知識

##開発環境の構築
Vue.js、GitHubの経験自体がなかったため、各技術の学習を行いながら、開発環境構築と開発方針の決定を行いました。
詳細はメモ的に以下に残してあります。
前回：[Vue.jsでHPを作成する - 開発環境を整える](https://qiita.com/c3drive/items/6c1e5dd281dfd106d5c6)

####ファイル管理の構成とコミット方針

```
Repositorie
  ┣ masterブランチ    //公開用
  ┣ gh-pagesブランチ　//リリース用
  ┣ devブランチ       //作業用細かい修正はこちらで行っていく
```
- dev -> gh-pagesへプルリクエストする時は「Create a merge commit」
- gh-pages　-> masterへプルリクエストする時は「Squash and merge」

##HPの仕様
以下を含むものとします。

- 自己紹介
- スキル
- 成果物
- 連絡先

また、人様に見ていただくことが目的なので、閲覧者が途中で脱落しないものになるように心がけます。

- 極力ページ移動がなく、あってもストレスなく移動できる
- シンプル
- 環境に依存しない表示と挙動

##デザイン
画像：[pixabay](https://pixabay.com/ja/)
フォント（テキスト）：[Google Fonts](https://fonts.google.com/)
フォント（アイコン）：[Font Awesome](https://fontawesome.com/)

##利用した主な技術  

- vue-cli
- Git/GitHub/GitHubPages
- HTML5/CSS/Javascript

##構成
簡単ですが全体の構成について記述します。

```
PROJECT_ROOT
  ┣ src
    ┣ assets         #画像格納
    ┣ components     #.vueの拡張子がついたコンポーネントファイル。部品単位で作成。
    ┣ pagess         #.vueの拡張子がついたコンポーネントファイル。ページ（機能）単位で作成。
      ┣ HomePage.vue     #Topページとして、各ページファイルをコンポーネントとして読み込み。
      ┣ AboutPage.vue    #Aboutページ。単体でも表示可能だが、Topのコンポーネントの一つという扱い。
      ┣ WorkPage.vue     #Workページ同上の立ち位置。
      ┣ SkillPage.vue    #Skillページ。同上の立ち位置。
      ┣ ContactPage.vue  #Contactページ。同上の立ち位置。
    ┣ router         #ルーティング用の設定ファイル。ページ単位でルーティング設定。
    ┣ App.vue        #index.htmlに出力されるvue側のインデックスファイル
    ┗ main.js        #各種ファイルをインポートして管理するエントリーポイント用ファイル
  ┣ static
  ┣ test
  ┗ index.html       #ルートファイル
```

####index.html
初期からほとんど変更なし。webフォントの読み込み定義をここにしました。

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>Yuko Kanai's portfolio</title>
    <script src="https://kit.fontawesome.com/82c457574d.js"></script>
    <link href="https://fonts.googleapis.com/css?family=Vollkorn&display=swap" rel="stylesheet">
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
```

####router/index.js
http://{domain}/とすると、HomePageが表示され、http://{domain}/aboutとするとAboutPageが表示される設定です。
現在もこの設定のままのため成果物のディレクトリ指定を変更すると単体での画面表示となりますが、画面上のリンクからこの画面にたどり着くことはありません。
ページ遷移時に、画面読み込みが入らずスムーズな画面遷移が行えるため、SPAらしさがありましたが、
画面遷移よりも同一ページ内で全ての情報が閲覧できる事を優先しました。
※SPAにおいてページ内リンクは画面遷移とみなされるため邪道なのかもしれません。これについてはベストプラクティスが見つからず。
なお、mode:historyは使えない環境があったのでコメントアウトしています。

```js
import Vue from 'vue'
import Router from 'vue-router'
import smoothScroll from 'vue-smoothscroll'
import Home from '@/pages/HomePage'
import About from '@/pages/AboutPage'
import Work from '@/pages/WorkPage'
import Skill from '@/pages/SkillPage'
import Contact from '@/pages/ContactPage'

Vue.use(Router)
Vue.use(smoothScroll)

export default new Router({
  // mode: 'history', //GitHubPagesで画面が出ないためコメントアウトする
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      component: About
    },
    {
      path: '/work',
      name: 'Work',
      component: Work
    },
    {
      path: '/skill',
      name: 'Skill',
      component: Skill
    },
    {
      path: '/contact',
      name: 'Contact',
      component: Contact
    }
  ]
})
```

####App.vue
初期からほとんど変えていません。`<transition>`を使って画面遷移にアニメーションをつけました。（ほとんど画面遷移のないサイトになりましたが）
また、ここで各ページ共通のスタイル定義も行いました。

```js

<template>
  <div id="app">
    <header></header>
    <main>
      <transition mode="out-in">
        <router-view/>
      </transition>
    </main>
    <footer></footer>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  font-family: 'Roboto', 'Noto Sans JP', 'Vollkorn', 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
.v-enter-active, .v-leave-active {
  transition: opacity .5s;
}
.v-enter, .v-leave-to {
  opacity: 0;
}
.section-index::after {
  content: "";
  display: block;
  height: 2px;
  width: 50px;
  background-color: #ccc;
  margin: auto;
}
p {
  margin: 0;
}
・・・
</style>
```

####src/pages/HomePages.vue
各ページをコンポーネントとして記述して1ページとして見せて、各機能のボタン押下でそこまでスクロールする画面です。
アンカーリンクを使うにあたり、ルーティングの設定と「#id」の書き方が衝突するので、`vue-smoothscroll`を使っています。

```js

<template>
  <div>
    <div class="top-wrapper">
      <div class="container">
        <h1>{{ title }}</h1>
        <p>{{ msg }}</p>
      </div>
      <div class="container">
        <div class="items">
          <a
            v-for="section in sections"
            v-bind:key="section.id"
            href="#"
            @click="doSmoothScroll(section.link)"
            class="btn item"
          >
            <p class="caption">
              <span v-bind:class="section.icon"></span>
              {{ section.title }}
            </p>
          </a>
        </div>
      </div>
    </div>
    <about-page id='about' class='section'></about-page>
    <work-page id='work' class='section'></work-page>
    <skill-page id='skill' class='section'></skill-page>
    <contact-page id='contact' class='section'></contact-page>
  </div>
</template>

<script>
import Item from '@/components/Item'
import AboutPage from '@/pages/AboutPage'
import WorkPage from '@/pages/WorkPage'
import SkillPage from '@/pages/SkillPage'
import ContactPage from '@/pages/ContactPage'
export default {
  components: {
    'item-component': Item,
    'about-page': AboutPage,
    'work-page': WorkPage,
    'skill-page': SkillPage,
    'contact-page': ContactPage
  },
  methods: {
    doSmoothScroll (id) {
      this.$SmoothScroll(document.getElementById(id), 1000, null, null, 'y')
    }
  },
  name: 'Home',
  data () {
    return {
      title: 'Yuko Kanai\'s portfolio',
      msg: 'wellcome',
      sections: [
        {id: 1, title: 'About', link: 'about', icon: 'fas fa-user'},
        {id: 2, title: 'Work', link: 'work', icon: 'fas fa-images'},
        {id: 3, title: 'Skill', link: 'skill', icon: 'fas fa-list-ul'},
        {id: 4, title: 'Contact', link: 'contact', icon: 'fas fa-envelope'}
      ]
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1 {
  font-weight: normal;
  font-size: 60px;
  margin-bottom: 10px;
}
header {
  height: 58px;
  width: 100%;
  background-color: rgba(14, 21, 22, 0.9);
  z-index: 1;
}
.top-wrapper {
  padding: 100px 0 50px 0;
  background-image: url(~@/assets/keyboard-2308477_1920.jpg);
  background-size: cover;
  color: white;
  border-bottom: 2px solid #dee7ec;
}
.top-wrapper h1 {
  opacity: 0.8;
  font-size: 45px;
  letter-spacing: 3px;
}
.top-wrapper p {
  opacity:0.8;
}
.items {
  padding: 10px 0;
}
.item {
  width: 15%;
  margin: 10px;
  border: 1px solid #dee7ec;
  background-color:rgba(0, 0, 0, 0.3);
}
.item:hover {
  background-color:rgba(255, 255, 255, 0.3);
}
.section {
    width:70%;
    margin: 0 auto;
    padding: 10px;
    border: solid #b0c4de 0px;
}
.section::after {
    content: "";
    display: block;
    clear: both;
}
</style>
```

####src/pages/WorkPages.vue
HomePageから読み込まれるコンポーネントの１例としてWorkを載せます。
ここではさらにwork.vueをコンポーネントとして呼んでいます。

```js
<template>
  <div>
    <h1 class="section-index">Work</h1>
    <p class="msg">{{ msg }}</p>
    <div class="workarea">
      <work-component
          v-for="post in posts"
          v-bind:key="post.id"
          v-bind:title="post.title"
          v-bind:url="post.url"
          v-bind:giturl="post.giturl"
          v-bind:description="post.description"
          v-bind:technology="post.technology"
      ></work-component>
    </div>
  </div>
</template>

<script>
import Work from '@/components/work'
export default {
  components: {
    'work-component': Work
  },
  data () {
    return {
      msg: '今まで開発したものです。クリックで詳細が表示されます。',
      posts: [
        {id: 1, title: 'HomePage', url: 'http://selene.himegimi.jp/', giturl: '', description: '（略）', technology: 'Java, eclipse, html, javascript, css'},
        {id: 2, title: 'Portfolio', url: 'https://c3drive.github.io/homepage-project/dist/', giturl: 'https://github.com/c3drive/homepage-project', description: 'このサイトです。', technology: 'vue-cli, github, html5, javascript, css'}
      ]
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

```
##気づき
###リポジトリ構成について
「dev：細かい開発単位」→「gh-pages：ある程度のまとまった単位」→「master：公開」という構成にしていました。

- VueとGitHubPagesの組み合わせがよくない場合（Routerでmode: 'history'を定義すると画面が真っ白など）があり、ローカルで動いてもgitHubPagesで動かないなどあったため、細かい単位でgh-pagesへマージし動作確認をする必要があり、当初のコンセプトに合わせた開発が難しい状況でした。
- GitHubに慣れていなかったこともありますが、マージ先を間違うことが何度かありました。

あるべきよりも、場合により不要なリポジトリ構成にしないという判断も必要だと思い知りました。
今回は一人開発で並行開発が走ることもなかったため明らかに過剰な構成にした結果、余計な時間がとられてしまったと感じます。
この規模でしたら、今では「gh-pages：細かい開発単位」→「master：公開」の構成でよかったと思っています。

###動くものを作るという事
今回、初心者本などは読まず公式ドキュメントをベースに手を動かしつつ開発しました。
また先人の知恵により先回りして防げる事象も、自身がソースやドキュメントから読み取れなかったことはそのまま障害になることを承知で進めました。

- Git、GitHubの業務経験不足。様々なことができることもあり、実業務ではどう使われるかを意識して開発しようとしたときに、本やwebから得られる知識では不足を感じました。（開発人数や体制が異なるため）
- SPAのベストプラクティス、HTML5への知識不足を痛感。フロントだけ、というのは今までの経験上なかく魅力もあまり感じていなかったのですが、いざ取り組むとやれる範囲が多いのに対し、自身は最低限の知識でしか挑めなかったという悔しさだけが残りました。フロントの本を数冊買いました。
- npm、package.jsonへの理解不足。グローバルインストールとローカルインストールの区別もついていなかったのですが、バージョン管理においてありえないミスをしたことにより先人の知識も得られずに困ってしまいました。なお、その時のことはQiitaにまとめています。タイトルが適切ではないですがあえて自身の知識レベルで検索しようとするとこんな感じのワードで検索するため戒めとして残しました。  
参考：[vue+webpack環境にて、npm run devのエラーがどうしようもなくなった件](https://qiita.com/c3drive/items/e886625fcc41465b7a3c)  
- webpackのバージョン管理の大切さとツール選択時の比較検討の大切さ。バージョン管理をミスったことによりwebpackの強烈な依存関係やバージョン移行の難易度を認識しました。vueプロジェクトを作成するとき、よく選択されているのを見ていたので問題ない判断をしましたが、自身でも調査が必要だったと反省しています。ただそれ以上の恩恵は受けています。

分からないことがわからない状態において、これらの気づきは本を読んでいても気づけなかったと思います。
また、文系にありがちだと思われますが、動かしてみて「動かない」状態から復旧させるために調べるという動機付けからの行動が自身には合っていました。

##成果物
HP：[https://c3drive.github.io/homepage-project/dist/](https://c3drive.github.io/homepage-project/dist/)
GitHub：[https://github.com/c3drive/homepage-project](https://github.com/c3drive/homepage-project)

##おつかれさまでした
ありがとうございました。