あいつの日誌β

働きながら旅しています。

Vue.js で Carousel な component を再開発して色々な学びを得たお話

f:id:okamuuu:20190504160726j:plain

あらすじ

Vue.js でシンプルな Carousel を作ります。すでにそういった component は一杯あるのですが、車輪の再開発を通じてお勉強しました、という趣旨の記事です。すでに vue-carousel という定番モジュールがあるのでこれを真似て作成しています。

Just Do it

以下のようにファイルを作成します。

mkdir vue-tiny-carousel && cd $_
mkdir src example
touch src/{index.js,Carousel.js,Slide.vue, Navigation.vue}
touch example/App.vue
yarn init -y
yarn add lodash.debounce
yarn add -D @vue/cli

touch したファイルを以下のように修正します。

src/index.js

import Carousel from "./Carousel.vue";
import Slide from "./Slide.vue";

const install = Vue => {
  Vue.component("carousel", Carousel);
  Vue.component("slide", Slide);
};

export default {
  install
};

export { Carousel, Slide };

src/Carousel.Vue

<template>
  <div class="carousel">
    <div ref="carousel-wrapper" class="carousel-wrapper">
      <div
        ref="carousel-inner"
        class="carousel-inner"
        :style="{
          'transform': `translate(${offset}px, 0)`,
          'transition': dragging ? 'none' : `0.4s ease transform`,
          'flex-basis': `${carouselWidth}px`,
        }"
      >
        <slot></slot>
      </div>
    </div>

    <navigation
      :clickTargetSize=8
      :nextLabel="`>`"
      :prevLabel="`<`"
      @navigationclick="handleNavigation"
    />

  </div>
</template>

<script>
/* eslint-disable no-console  */
import debounce from "lodash.debounce"
import Navigation from "./Navigation.vue";
export default {
  name: "carousel",
  components: {
    Navigation
  },
  mounted() {
    window.addEventListener(
      "resize",
      debounce(this.onResize, this.refreshRate)
    );
    this.setSlideCount()
    this.computeCarouselWidth();
  },
  beforeUpdate() {
    // this.computeCarouselWidth();
  },
  provide() {
    return {
      carousel: this
    };
  },
  data() {
    return {
      slideCount: 0,
      currentPage: 1,
      carouselWidth: 0,
      dragging: false,
      refreshRate: 16,
    };
  },
  computed: {
    offset() {
      return this.carouselWidth * (this.currentPage - 1) * -1
    },
    canAdvanceForward() {
      return this.slideCount - this.currentPage > 0
    },
    canAdvanceBackward() {
      return this.currentPage > 1;
    }
  },
  methods: {
    setSlideCount() {
      const slots = this.$slots
      const tagName = "slide"
      const isSlide = slot => slot.tag && slot.tag.match(`^vue-component-\\d+-${tagName}$`) !== null
      this.slideCount = (slots && slots.default && slots.default.filter(isSlide)).length || 0
    },
    computeCarouselWidth() {
      let carouselInnerElements = this.$el.getElementsByClassName("carousel-inner");
      this.carouselWidth = carouselInnerElements[0].clientWidth || 0;
    },
    handleNavigation(direction) {
      if (direction && direction === "backward") {
        this.goPreviousPage()
      } else if (direction && direction === "forward") {
        this.goNextPage()
      }
    },
    onResize() {
      this.computeCarouselWidth();
      this.dragging = true; // force a dragging to disable animation

      // clear dragging after refresh rate
      const refreshRate = 16
      setTimeout(() => {
        this.dragging = false;
      }, refreshRate);
    },
    goToPage(page) {
      this.currentPage = page
    },
    getNextPage() {
      return this.currentPage < this.slideCount ? this.currentPage + 1 : this.currentPage;
    },
    getPreviousPage() {
      return this.currentPage > 1 ? this.currentPage - 1 : this.currentPage
    },
    goNextPage() {
      this.goToPage(this.getNextPage())
    },
    goPreviousPage() {
      this.goToPage(this.getPreviousPage())
    }
  }
}
</script>

<style scoped>
.carousel {
  height: 100%;
  display: flex;
  flex-direction: column;
  position: relative;
}
.carousel-wrapper {
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
}
.carousel-inner {
  height: 100%;
  display: flex;
  flex-direction: row;
  backface-visibility: hidden;
}
</style>

src/Slide.Vue

<template>
    <div class="slide">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "slide",
}
</script>

<style scoped>
.slide {
  flex-basis: inherit;
  flex-grow: 0;
  flex-shrink: 0;
  user-select: none;
  backface-visibility: hidden;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  outline: none;
}
</style>

src/Navigation.Vue

<template>
  <div class="carousel-navigation">
    <button
      type="button"
      aria-label="Previous page"
      class="carousel-navigation-button carousel-navigation-prev"
      v-on:click.prevent="triggerPageAdvance('backward')"
      v-bind:class="{ 'carousel-navigation--disabled': !canAdvanceBackward }"
      v-bind:style="`padding: ${clickTargetSize}px; margin-right: -${clickTargetSize}px;`"
      v-html="prevLabel"></button>
    <button
      type="button"
      aria-label="Next page"
      class="carousel-navigation-button carousel-navigation-next"
      v-on:click.prevent="triggerPageAdvance('forward')"
      v-bind:class="{ 'carousel-navigation--disabled': !canAdvanceForward }"
      v-bind:style="`padding: ${clickTargetSize}px; margin-left: -${clickTargetSize}px;`"
      v-html="nextLabel"></button>
  </div>
</template>

<script>
/* eslint-disable no-console  */
export default {
  name: "navigation",
  inject: ["carousel"],
  props: {
    clickTargetSize: {
      type: Number,
      default: 8
    },
    nextLabel: {
      type: String,
      default: "&#9654"
    },
    prevLabel: {
      type: String,
      default: "&#9664"
    }
  },
  computed: {
    canAdvanceForward() {
      return this.carousel.canAdvanceForward || false;
    },
    canAdvanceBackward() {
      return this.carousel.canAdvanceBackward || false;
    }
  },
  methods: {
    triggerPageAdvance(direction) {
      this.$emit("navigationclick", direction);
    }
  }
};
</script>

<style scoped>
.carousel-navigation-button {
  position: absolute;
  top: 50%;
  box-sizing: border-box;
  color: #000;
  text-decoration: none;
  appearance: none;
  border: none;
  background-color: transparent;
  padding: 0;
  cursor: pointer;
  outline: none;
}
.carousel-navigation-button:focus {
  outline: 1px solid lightblue;
}
.carousel-navigation-next {
  right: 45px ;
  font-size: 20px;
  transform: translateY(-50%) translateX(100%);
  font-family: "system";
}
.carousel-navigation-prev {
  left: 45px;
  font-size: 20px;
  transform: translateY(-50%) translateX(-100%);
  font-family: "system";
}
.carousel-navigation--disabled {
  opacity: 0.2;
  cursor: default;
}
/* Define the "system" font family */
@font-face {
  font-family: system;
  font-style: normal;
  font-weight: 300;
  src: local(".SFNSText-Light"), local(".HelveticaNeueDeskInterface-Light"),
    local(".LucidaGrandeUI"), local("Ubuntu Light"), local("Segoe UI Symbol"),
    local("Roboto-Light"), local("DroidSans"), local("Tahoma");
}
</style>

example/App.vue

<template>
  <div style="background: #eee;">
    <Carousel style="width:100%; height: 80vh;">
      <Slide>
        <div class="sample bg-primary">
          <h1>slide 1</h1>
          <p>test test test</p>
        </div>
      </Slide>
      <Slide>
        <div class="sample bg-warning">
          <h1>slide 2</h1>
          <p>test test test</p>
        </div>
      </Slide>
      <Slide>
        <div class="sample bg-danger">
          <h1>slide 3</h1>
          <p>test test test</p>
        </div>
      </Slide>
    </Carousel>
  </div>
</template>

<script>
import { Carousel, Slide } from '../src/index';
export default {
  components: {
    Carousel,
    Slide
  }
}
</script>

<style>
.sample {
  box-sizing: border-box;
  margin: 0;
  padding: 30px 60px;
  height: 100%;
}
.bg-primary { background: hsl(171, 100%, 41%) }
.bg-warning { background: hsl( 48, 100%, 67%) }
.bg-danger  { background: hsl(348, 100%, 61%) }
h1 {
  margin: 0;
  padding: 0;
}
</style>

動作確認

yarn vue serve example/App.vue で動作確認をします。

解説

この Carousel では以下の事を行なっています。おそらく世の中に出回っている Carousel と同じ挙動だと思います。

  • 画面に Carousel を配備する
  • Carousel の内部に Slide を横一列に並べる
  • Slide ひとつの横幅は親要素である Carousel と同じサイズにする
  • 横一列に並んだ Slide は Carousel に対して Slide の数だけ大きくはみ出す。
  • はみ出した部分は非表示にする
  • 次のスライドに遷移させた場合はスライドを一つ分左にずらす

ただし、この vue-tiny-carousel はあくまで初学者向けの教材として利用することを目的としているので細かい調整を行えるように設計していません*1。あらかじめご了承ください。

setSlideCount()

Carousel の中に Slide を複数配備して、その Slide の数をカウントします。この数字を元に offset の数などを計算します。Slide の tagName が vue-component-2-slide という形式なので、それにマッチしたものを Slide だとカウントします。

setSlideCount() {
  const slots = this.$slots
  const tagName = "slide"
  const isSlide = slot => slot.tag && slot.tag.match(`^vue-component-\\d+-${tagName}$`) !== null
  this.slideCount = (slots && slots.default && slots.default.filter(isSlide)).length || 0
}

computeCarouselWidth

ルーセルの表示領域を計算します。window がリサイズされた時にも再計算が必要なので関数化しています。

computeCarouselWidth() {
  let carouselInnerElements = this.$el.getElementsByClassName("carousel-inner");
  this.carouselWidth = carouselInnerElements[0].clientWidth || 0;
}

lodash.debounce

リサイズのイベントはこまめに発生するので lodash.debounce を使って関数の実行を抑制します。以下の記事に詳しい解説が載っていたのでこちらも参考にしてみてください。

https://qiita.com/waterada/items/986660d31bc107dbd91c

provide と inject

parent component である Carousel を、child component である Navigation で使いたい、というケースで便利な機構です。こちらも以下の記事に詳しい解説が載っていたのでこちらも参考にしてみてください。

https://qiita.com/kaorun343/items/397b1fa6afe96fa2b30f

まとめ

というわけで世の中には Carousel のライブラリが多数あるにも関わらず、車輪の再開発を行ってみました。再開発といっても同等の機能を求めた訳でなく、あくまで学習目的なので、厳密には車輪の再開発ではないと思いますが。

私はすでに Vue.js や Nuxt.js でやりたい事ができるような状態ではあったのですが、それでもまだ知らなかった便利な機構が存在したので学べた事があってとても有意義な時間となりました。というわけで車輪の一部を再開発すると他の優れたエンジニアの叡智を学ぶ事ができてなかなか良い経験でした。

今回作成したソースコードは以下に置いてあります。実用的かどうかはさておき、学習する分には適度な量のソースコードだと思いますのでよかったらご覧になってください。

https://github.com/okamuuu/vue-tiny-carousel/

*1:アラビア語のような Right to left に未対応だったり細かいデザインの調整をする props を用意していません

Vue.js を書くために最小構成でセットアップする方法

f:id:okamuuu:20190429135426j:plain

あらすじ

vue cli を使う方法とスクラッチで書く方法を紹介します。基本的には vue cli を使う方が便利なのですが、初学者の方で vue の文法についてだれかに質問したい場合は後者の方法を一応覚えておくと良いと思います。

vue cli を使う方法

vue-cli を install します。以下の例ではプロジェクト単位で vue-cli を追加しています。

mkdir practice-vue-minimum && cd $_
touch  App.vue
yarn init -y
yarn add -D @vue/cli

この記事を書いている時点の vue-cli の version は 3.7.0 です。ちなみに vue.js の version は 2.6.10 です。

node_modules/.bin/vue --version
3.7.0

App.vue を以下のように編集します。

<template>
  <h1>Hello!</h1>
</template>

以下のコマンドで上記で作成した component をエントリーポイントにして起動する事ができます。

yarn vue serve App.vue

以下のようにメッセージが表示されれば OK

DONE  Compiled successfully in

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

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

npm を日常的に使っている方であればこの方法が一番簡単だと思います。vue-cliwebpack の設定などを代行してくれますので Vue.js の学習に集中する事ができます。

vue cli を使わない方法

誰かに質問をする時に、例えば codepen.io や jsfiddle.net で公開してから質問をしたい時があると思います。jsfiddle だと以下のような URL で雛形を利用する事ができます。

https://jsfiddle.net/boilerplate/vue

この形式で vue を始めたい人は以下のような手順を踏むと良いでしょう。

mkdir practice-vue-scratch && cd $_
touch touch index.html style.css
yarn init -y
yarn add -D http-server

index.html と sytle.css を以下のように修正します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Vue.js App</title>
  <link href="/style.css" rel="stylesheet">
</head>
<body>
<div id="app">
  <h2>Todos:</h2>
  <ol>
    <li v-for="todo in todos">
      <label>
        <input type="checkbox"
          v-on:change="toggle(todo)"
          v-bind:checked="todo.done">

        <del v-if="todo.done">
          {{ todo.text }}
        </del>
        <span v-else>
          {{ todo.text }}
        </span>
      </label>
    </li>
  </ol>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
new Vue({
  el: "#app",
  data: {
    todos: [
      { text: "Learn JavaScript", done: false },
      { text: "Learn Vue", done: false },
      { text: "Play around in JSFiddle", done: true },
      { text: "Build something awesome", done: true }
    ]
  },
  methods: {
    toggle: function(todo){
      todo.done = !todo.done
    }
  }
})
</script>
</body>
</html>

style.css

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

del {
  color: rgba(0, 0, 0, 0.3);
}

以下のコマンドで localhost で動作確認ができます。

yarn http-server .

まとめ

Vue.js の書き方について学習する際に学習する環境を簡単にセットアップする方法を紹介しました。もっと良い方法がある気もしますが、やりかたは一つだけではないのでこちらの記事が参考になる場面があれば思い出して頂ければ幸いです。

We Are JavaScripters! @31th【初心者歓迎・ショートセション大会】に行ってきました

f:id:okamuuu:20190426151415j:plain

wajs.connpass.com

というわけで最近 JS 界隈の勉強会によく出没しています。

TDD & 登壇のハードルを下げたい

WeJS の基本的なコンセプトはこんな感じです。

  • 初心者歓迎
  • マサカリ遠慮
  • 緊張するのでお酒飲んでやりましょう

今回はLT5分の枠だけではなく、15分の枠も登場しました。

スポンサー

ありがとうございます。

  • 会場: retty
  • ドリンク: forkwell, Sony

会場を探しているそうです

運営する人たち毎回開催場所の確保に困っているみたいです。

  • 飲食可能
  • 30-100人
  • マイク・プロジェクター

ショートセッション(15分)

Gatsby.js で導入事例をバシバシ読めるSPAなLPを作った話 @kikunantoka

https://speakerdeck.com/kikunantoka/gatsby-js-for-biz-lp-a5b9dafd-e9ea-453a-8989-6e3792ec7962

giftee というサービスで法人向けのランディングページが WordPress で重いんだけどほとんど静的サイトだったから Gatsby にしたというお話。Gatsby は Static Generator なんだけど生成したページがそれ自体で SPA のように振る舞うので高速に動作するので用途としてはバッチリだと思います。

【SS】CG in Web最前線 @kyasbal

https://speakerdeck.com/kyasbal1994/cg-in-web-zui-qian-xian-jin-sokodehe-gaqi-kiteirufalseka

CPU と GPU の違いがとてもわかりやすかったです。その後は難しくてよくわからなかったです。でも glTF は少し気になりました。これは以下のようにタグを書くだけでそこにアバだーの3Dモデルを表示できるとか、そんな事ができたりするのかな?

<body-model-vierwer src="xxx">
  <head-model-vierwer src="xxx" />
  <face-model-vierwer src="xxx" />
  <hand-model-vierwer src="xxx" />
  <foot-model-vierwer src="xxx" />
</body-model-viewre>

【SS】javascriptはどのように動くのか? @brn0227

https://speakerdeck.com/brn/how-javascript-works

あんまり内容わかってないのですが、たぶん javascript のように抽象化されている High-Lebel な言語が実際に具体的に命令しないといけない Low-legel な機械語に翻訳される過程のお話なのかな?

普段このような知識を要する場面に出会わないのですが、そういった情報に触れることができるのが勉強会のいいところだと思います。

【SS】Google I/Oに備える Polymer Project最新動向 @takanorip

わたしは lit-html も LitElement も初耳でした。世の中には色々なライブラリがあるんですね。Polymer の思想は良かったと思いますが、React.js や Vue.js が色々と流行しているので若干役目果たし終えた気がするんですが、まだまだこれからなのかも。というわけで動向に注目したいと思います。

【LT】スポンサーLT @Forkwell

今までビデオでしか見た事がなかった本人が登壇されていました。去年までインターンでしたが無事新入社員へなれたそうです。おめでとうございます。Forkwellエンジニアのプロフィールサイトを運営していて Sony もスポンサーになってくれたそうです。

LT

【LT】Web Components時代に備える Aureliaのすすめ @panchan9

私、実は Aurelia 知らないんですけど新しいフレームワークが次々出てくるなあ... Web Components の思想は便利だと思うんですが、理想がちょっと高いので使いやすいツールを作るほうがいいんだるなあ。と思っています。たぶんそれで lit-html, LitElement の流れなんだと思います。

でもいろんなフレームワークが登場して、みんながそれぞれいろんな選択する事ができるのはとても良い事だと思います。

【LT】やさしい仮想DOM @RyoNkmr_

https://speakerdeck.com/ryonkmr/yasasiijia-xiang-dom

そういえば仮想DOMっという言葉ができてから時間がたってしまい仮想DOMってなんですかっいまさら聞き辛いような気がします。そんなわけでとっても良いトークだったと思います。

【LT】TypeScriptを書いてみよう @yamatatsu

TypeScript のライブコーディング。今回のLTは時間配分について厳守するように*1言われていた事もある時間が迫る中 TypeScript の型について説明してくれました。

monaco-editor を使っているので資料の中で JS 使えて便利。今度真似しようと思いました。

【LT】redux と unstated @okamuuu

私のターン。本当は unsated について話そうと思ったのですが、資料を作成している最中に、すでに詳しい事かいてある記事を発見してしまいました。

https://qiita.com/kaba/items/b05f680f850dd46548f3

実は以前 Redux は敷居が高すぎて導入するのになかなか踏み切れないで悩んでいた時期があったのですが、今はもう大丈夫、unstated という選択肢がありますよ。というお話をしたつもりです。

ちなみに redux をやめて unstated にしましょう、という話ではなくて選択肢が増えた、という話です。念の為。

LT資料: https://okamuuu.com/talks/2019/2019-04-24.we-are-javascript@31.htmlokamuuu.com

【SS】バイナリの話?

写真データをごにょごにょしようとして EXIF 情報調べる時に文字でなくてバイナリが保存されているからデバッグするときに「おお、JSだとこうなるのか」といった内容を共有してくださいました。たぶん。

シフト演算子の話を聞いていたら昔 MySQL の整数型に保存してあるユーザーの持ち物を0以下にして 16777215 みたいなそんな数値にしてしまうバグを生み出した苦い記憶が蘇りました。

懇親会は

とても寝不足だったので参加せずにまっすぐ帰宅しました。

今回の勉強会で得た知見とまとめ

今回得た知見

  • glTF は要チェック
  • monaco-editor 使ってみたい

そして最近資料書いているうちに「あーこれ5分以内に話きるの難しいな」みたいなのが時間を計測しなくても感覚値で把握できるようになってきました。たぶん大勢の人たちの前で登壇している人たちはみんなそうやって経験を積んでいくのかな、と思うのでまだ登壇した事ない人も徐々に上達すると思うので思いってチャレンジしたらいいんじゃなかと思います。

こちらからは以上です。

*1:撤収する時間を破ると会場貸してくれた人たちに対して不義となる

台北駅から徒歩5分のスターホステルに一週間滞在して明るくオシャレな空間でノマドしてきた

f:id:okamuuu:20190414153040j:plain

あらすじ

というわけで台湾も花粉が飛ばないはずなのでノマドしてきました。スターホステルっていうお洒落なドミトリー式のホテルに宿泊しました。一泊だいたい¥5000(1400元)ぐらいから。ドミトリーだともっと安くて2300円(620元)から。

今回は Booking.com から6日間連続でシングルルームが空いていたのでそちらを予約。宿泊費は6日間で¥33,592でした。ただし、後述しますが実際に支払った金額は¥37215です。

ということで以下のことをこの記事では書いています。

  • 台湾旅行でクレジットカードを使うときの注意点
  • Star Hostel
  • 桃園国際空港
  • 誠品書店(エスリテ)
  • 中正紀念堂(ヅョンヅェンジーネンタン)
  • 迪化街(ディーホアジエ)
  • ごはんとビール
  • まとめ

ちなみに一週間滞在したのですがそのうち3日間は風邪をひいてしまったのであまり観光地に行けなかったので観光地の情報はあんまりないです。

台湾旅行でクレジットカードを使うときの注意点

さて、冒頭で宿泊料金が¥33,592ではなくて¥37215となってしまった件ですが、ホテルで支払いを JPY か TWD のどちらかを選択する際に何気なく JPY で支払いました。実は海外でクレジットカードの支払い方法を選択する時には注意が必要です。

f:id:okamuuu:20190421105731j:plain
1ニュー台湾ドル は3.63円とするならば9265TWDは33653.75JPYです

どうしてこうなったんでしょう?以下の記事が詳しいので読むと良いでしょう。また一つ賢くなりました。

https://note.mu/suzukyuin/n/n48c6602adf99

Star Hostel Taipei main station

www.starhostel.com.tw

古い建物をリノベーションしてある建物なのですがかなり清潔な状態を保たれているようです。というのもスタッフがかなり掃除に気を使っているらしく、こまめに掃除をしていました。受付スタッフは基本的に英語を話せるので*1やりとりは基本的に英語です。

私はそんなに英語得意ではないのですが鍵を部屋の中に閉じ込めてしまったとか、ランドリースペースはどこですか?程度の英語だったんで不自由はしませんでした。まあ英語できなくても一部のスタッフが日本語を話していたので困らないと思います。最後の手段として漢字を使う、という方法もあります。漢字は偉大。

洗濯機が2台、乾燥機が1台ありました。それぞれ40元です。長期滞在も可能です。朝ごはんは宿泊費に含まれていて、毎朝少しずつ違ったメニューです。オレンジジュースが美味しかったです。お昼ごはん、夜ごはんは後述しますが歩いてすぐの場所に京站時尚廣場(Qスクエア)というファッションビルがあって、そこの地下3階にフードコートがあります。フードコートでの食事は癖がほとんどなく学食程度の金額でおいしいごはんがたくさん食べられますので長期滞在するのに不便ありませんでした。

f:id:okamuuu:20190420093306j:plain
朝食は無料サービス

f:id:okamuuu:20190420085951j:plain
夜は照明が効いてお洒落な空間

f:id:okamuuu:20190420100845j:plain
ノマドに最適な空間

f:id:okamuuu:20190416092359j:plain
お気に入りの場所

f:id:okamuuu:20190416092426j:plain
奥の椅子でノマドしてました

f:id:okamuuu:20190416105750j:plain
ホテルの窓から。左側のビルがQスクエア。奥の建物が台北

あと週末にはコーヒー屋さんがいます。一杯100元なので少々お値段しますが*2。ちなみに私にコーヒーを淹れてくれた青年は平日はWEB デザイナーをしているそうです。日本語が上手でした。

ノマドの拠点として最高だったんですが、問題なのは室内の温度が寒すぎる事です。私は風邪を引いてしまいました。ヒートテックを持ってきた方が良いと思います。ちなみに台湾ではユニクロ製品ちょっと高いです。ヒートテックが790元でした。

桃園国際空港(terminal 1)

Terminal1 と Terminal2 があります。私は今回入国も出国も Terminal1 だったので Terminal2 に関しては書くことがありません。

Terminal1には地下1Fにコンビニとフードコートがあります。ただし、地下が2箇所あってそこを行き来するには1Fのロビーから移動する必要があります。地下に下がってからそこにコンビニがあった場合はフードコートに行くには1度1Fに戻る必要があります。逆もしかりです。

ちなみに3Fにも飲食店、お土産屋さん、お手洗いがあります。この3Fへの行き方が少しわかりづらいのですがチェックインカウンターの奥側にエスカレーターがあります。出国する際はそこに行かなければならないので急ぎでなければ入国の時点で立ち寄っておいた方が帰る時に楽です。ちなみに3Fはトイレなども比較的空いています。

あと Terminal 1 と Terminal 2 を行き来するスカイトレイン無人運転されていて5分感覚で運行しています。ただし、ちょっと場所が分かりづらいので初めて移動する人は時間配分にご注意ください*3

今度アジアを色々旅して回る予定なので桃園国際空港は今後頻繁に立ち寄る事になる予感がしています。

誠品書店(エスリテ)

台北のお洒落なお店です。誠品グループは台北の各地にあります。最近は日本橋にも出店されていたりします。台北地下街のK區にもありますし、中山駅にある誠品R79は台北駅から中山地下街を通って行けるので暑い日だったり、雨の日などで便利です。

中正紀念堂(ヅョンヅェンジーネンタン)

散歩がてらに立ち寄った中正紀念堂ですが思った以上に大きな建物でした。スターホステルから歩いて10分程度なので早朝の散歩がてらに行ってみると観光客も少なくていい写真がとれると思います。微動だにしない衛兵は1時間おきに交代するのですが、わたしは残念ながらそれを見ていませんが微動だにしない衛兵みるだけでも楽しかったです。

f:id:okamuuu:20190420095902j:plain
中正紀念堂は思ったより大きかった

f:id:okamuuu:20190420100008j:plain
微動だにしない衛兵

迪化街(ディーホアジエ)

台北で私が一番お気に入りの街です。問屋街なのですが小洒落たお店もあったりして私はこの通りの雰囲気がとても好きです。この街にきたら烏龍茶とカラスミを買うといいと思います。

f:id:okamuuu:20190421120340j:plain
おいしそうなフルーツも売っています。

ごはんについて

私は基本的に綺麗なデパートのフードコートを利用しました。1食100-130元程度なので大体500円程度でしょうか。路面で食事している現地向けのお店だと割と味付けに癖があって馴染みづらいのと写真がなくて注文しづらいのですが、かといって店内にテーブル席があるお店だと結構お値段がします。

なお、大戸屋やもーもーパラダイスといった日本に馴染みのあるお店もありましたがちょっと割高です。

ビールに関しては台湾製のビールは安いのですが日本製だと高いです。単純に輸入代金だと思いますが。私が珍しいなと思ったのは18days っていう賞味期限が18日の生ビールでした。台湾に来たらこういった缶ビールを買ってみるのも楽しいと思います。

f:id:okamuuu:20190420084555j:plain
桃園国際空港のフードコードにて。150元

f:id:okamuuu:20190420084723j:plain
台北地下街 K區のスタバにて。80元。

f:id:okamuuu:20190420085249j:plain
台北駅(台北車站)にて。450元。日本食は割と高い。

f:id:okamuuu:20190420094916j:plain
朝からモツとタコ。80元。

f:id:okamuuu:20190415133442j:plain
Qスクエアのフードコート。150元。

minder vegetarian はとても便利。不足しがちな野菜をここで補充

f:id:okamuuu:20190420100603j:plain
ほぼ毎日食べていた。115元

f:id:okamuuu:20190416112258j:plain
Qスクエアのフードコートにて。160元。

f:id:okamuuu:20190416114419j:plain
QスクエアのJASONS。お土産買ったりビール買ってホテルで飲んだり。使い勝手が非常に良い。

まとめ

というわけで一週間台北ノマドしてきました。といっても3日間体調不良のためほとんど観光できませんでしたが。今後地方、あるいは外国でノマドをしたいという野望があり、その視察に行ってきたのですが台北ノマドするのにとても良かったです。寒いっていう課題があるので解決策は考える必要がありそうですが。という訳で今回の旅で学んだ事は以下の通り。

  • スターホステルは立地、空間ともにノマドをするのに最高だった
  • ただし、冷房が強いので寒がりの人はヒートテックが必要
  • クレジットカードでの支払いは現地通貨を選択すべし
  • お土産は徒歩10分程度で迪化街があるのでそこで買うと良いでしょう。
  • スターホステルに滞在する場合は徒歩3分程度の場所にQスクエアがあり、食事も飲み物も事足りる
  • スタッフとの会話は英語を使うので英会話の練習にもなる

台湾は日本からも行きやすく、またアジアを行き来するのにちょうど良い中継地点なので今後も頻繁に行き来することになりそうです。こちらからは以上です。

f:id:okamuuu:20190417133042j:plain
台湾はマンゴーがとってもおいしいです

*1:清掃スタッフは基本的に英語を話さない。多少は話せると思うんですけど英語での対応は受付スタッフに任せている気がします

*2:スタバだと1杯80元

*3:3Fにスカイトレインがあるのですが、場所によっては一度1Fロビーに降りて移動した後に3Fへ再度登る必要があります

石垣島のお洒落なカプセルホテルブルーキャビン石垣島に6泊7日滞在しノマドワークしてきた

f:id:okamuuu:20190410083252j:plain

あらすじ

毎年4月になると花粉症が酷く、4月は仕事をしていないという事実データが存在するので南の島へ避粉地ツアーしようと思いました。最初は沖縄本島へ行こうかと思ったのですが、以下の理由で石垣島を選びました。

  • バニラエアで成田から石垣島への直通便がある
  • 石垣島に良さげなホステル(簡易宿泊所)がある
  • 手頃なアクティビティがたくさんある

というわけで今回のノマドワークで体験した事を記します

  • 花粉の症状について
  • 宿泊所であるブルーキャビン石垣島の事
  • ノマドした場所
  • アクティビティの事
  • オススメのごはんどころ
  • 石垣島のコーヒー事情

花粉の症状について

到着して2日ぐらいはまだ花粉の症状が残っていました。もしかしたら洋服に花粉がついているからかもしれません。石垣島にはすぎ、ひのきの花粉はありませんが、サトウキビにも花粉症になる人がいるらしいです。ちなみに今年は天候の影響でまだサトウキビを刈りきれていないとのことでした。あと地理的にPMや黄砂も少しあるらしいです。私はスギ、ヒノキの花粉がつらくて仕事のパフォーマンスが落ちるのが嫌だったのですが、ここに来ればまったくその心配が無く快適に過ごすことができました。これだけでも石垣島に来た甲斐がありました。

ブルーキャビン石垣島

f:id:okamuuu:20190407154119j:plain

今回宿泊したブルーキャビン石垣島は離島ターミナルから徒歩1分の場所という、とても便利な立地です。石垣島では基本的にレンタカーで移動するのが良いと思いますが、このホステルは離島ターミナルに近いので竹富島などへの離島めぐりができるのでレンタカー無しでも石垣島旅行を楽しむ事ができます。

宿泊期間とお値段

6泊して宿泊料金の合計は ¥19,500(消費税:込)でした。飛行機代と宿泊料金が安い時期を選んでいるのでハイシーズンはもう少し高いと思います。ちなみに貸切プランというのもあるので大人数でわいわいしても楽しいと思います。5名以上だったら貸切りしたほうがお得になる気がします。

日付 宿泊費用 思い出
2019年4月07日(日) ¥3,200 電動バイク、鰓呼吸
2019年4月08日(月) ¥3,200 ノマド、ペンギン食堂、石垣ブルー
2019年4月09日(火) ¥3,200 のりば食堂、竹富島、朝日食堂、星空鑑賞ツアー
2019年4月10日(水) ¥3,200 幻の島上陸、ノマド、ペンギン食堂、ぱいかじ
2019年4月11日(木) ¥3,200 自習、ドライブ、明石食堂、ぷかぷか、ぱいかじ、つる商店
2019年4月12日(金) ¥3,500 西表島、ツル商店

新石垣空港と石垣港離島ターミナルの移動手段

バスは二つあるので宿泊先が離島ターミナルの場合はどちらでも行けます。ANAインターコンチネンタルなどに宿泊する場合は各停に乗りましょう。

  • ¥500: 空港からターミナルまで直通(カリー観光)
  • ¥540: 各停(東運輸株式会社)

バスに関してはこちらに詳しく乗っています: https://okirito.net/bus/8440.html

ノマドした場所

石垣島ノマドするなら多分以下の3箇所がおすすめです。

  • ブルーカフェ
  • ホテルエメラルドアイル石垣島のカフェ
  • 図書館

ブルーキャビン石垣島のカフェ: ブルーカフェ

f:id:okamuuu:20190408093708j:plain

ブルーカフェは宿泊所に併設されているカフェで早朝から営業しております。電源コンセントが少ないのですが朝6:00から営業しているので出かける前に少しノマドする、といったことができて便利でした。私は冷房が苦手なのでテラス席で仕事することが多かったです。

ホテルエメラルドアイル石垣島のカフェ

こちらは電源コードがたくさんあって訪問した時間(14:00ごろ)は私しか利用者がいませんでした。15:00以降はチェックインする宿泊者がやってくるので少し賑わい始めましたが冷房はそこまで強くないのでとても便利なスペースでした。

図書館

私は利用する機会がなかったのですが「ノマドするなら図書館もおすすめです。冷房は強くないですよ」という現地の方のアドバイスがありましたのでご紹介します。

アクティビティについて

石垣島とその周辺の離島でこんなことを楽しんできました。どれも楽しいんですけど全ては天気次第だったりします。なのでご参考程度に見てやってください。

電動バイク

f:id:okamuuu:20190407171051j:plain

一応私は車の運転ができるのですが、石垣島に電動スクーターのレンタルサービスが開始されていたので、島内の散策はこれを利用しました。50cc と 125cc があり、50cc は普通免許があれば運転できます。もし石垣島に行くけど免許を持っていない、という方は原付免許*1だけ取得しておく、というのも有りだと思います。

でもまあ4時間以上借りると4千円となるのですが石垣島だと4時間あるとレンタカーが借りられるので普通自動車免許を持っている人はレンタカーの方が便利かもしれませんし、石垣島よりもその周辺の離島をめぐったりしたほうが面白いかもしれません。

私はとりあえず2時間だけレンタルしたのですが時間が足りずにあまり移動ができませんでしたがそれでもフサキビーチ、名蔵大橋、カンムリワシ展望台とドライブすることができました。バッテリーの交換にチャレンジしたかったのですがさすがに時間ないのでとりあえず帰還しました。でも久々にバイクに乗ってとっても楽しかったです。

f:id:okamuuu:20190413210400j:plain
カンムリワシ高台

竹富島

f:id:okamuuu:20190409113424j:plain

滞在3日目に石垣島に船で15分ぐらいにある小さな離島、竹富島へふらっと行ってきました。小さい島ですが自転車を借りた方が良いです。石垣島は日差しがとても強いので天気が良い日に歩いて移動するのはかなりつらいと思います。

私は特に予約せずにふらっと離島ターミナルに行って竹富島に行きたいと窓口に伝えて船へ乗船。離島ターミナルについたらレンタルサイクル屋のバンが何台か止まっているので「予約なしでもOK?」とい聞いたら余裕があったのでそのままバンに乗ってレンタルサイクル屋まで移動して自転車を借りました。

時間帯にもよると思うのですがもし早い時間帯でしたら最初に桟橋に向かってみるのも良いと思います。人が少ない時間帯だと絶好のフォトスポットだと思います。

f:id:okamuuu:20190413210953j:plain
遠浅で透き通った海

コンドイビーチは泳ぐことができるのですが遠浅の砂浜で、少し離れた場所に小島が見えます。腰ぐらいまで海水につかりますがその小島に上陸できるのでとても楽しそうです。私はただ眺めていましたが。ちなみに濡れたままの格好で自転車を返すと帰りにバンで港まで運んでもらえません。といっても歩ける距離なので歩いてもいいと思います。そして帰りの便はちょっと混み合うので船に乗るのに少し行列ができます。乗り切れない場合は次の便まで待ちましょう。あと小銭を多めにしてから出向くと良いと思います。

f:id:okamuuu:20190413211252j:plain
遠浅の砂浜の向こうに小さな島があります。コンドイビーチは水着必須です。

星空鑑賞ツアー(星空ツーリズム)

ベルトラ*2から星空ツーリズムさんが開催する星空鑑賞ツアーに申し込みしました。集合場所は離島ターミナルのバス停なのでブルーキャビン石垣島から歩いて行けます。市街地から屋根なしのバスに乗って街灯が見当たらない高台に向かいます。道中で車内灯を消したのですがもうすでに星空が綺麗でした。現地について専門家による星座の説明があって昔学校で習ったことを思い出すことができたりして、そんなのも楽しかったです。

なんですが残念なことに途中で厚い雲がやってきて、星を全く見ることができなくなりました。というわけで今回のツアーはキャンセルによる返金*3という事になりました。でも割と途中までいい感じだったので返金でなくてもたぶん参加者全員満足したんじゃないかな?というぐらい良かったです。気温がちょうど良かったのと、屋根なしのバスに乗って、気持ちいい夜風に吹かれながら星を見ることができたので非常に贅沢な時間を過ごすことができました。

なお写真撮影すると人間の目が暗闇に適用するまで数分要するらしく、撮影を自粛したので写真はありません。

幻の島上陸(shechan石垣島)

f:id:okamuuu:20190410081521j:plain
幻の島こと浜島に向かって出発

予報によると天気が良い日が限られている事に気づいたんで1日中ノマドをする予定の日だったのですが仕事前にアクティビティを予約しました。この日も天気が良かったのでとても楽しかったです。小型ボードに乗って片道30分かけて潮の満ち引きによって限られた時間だけ姿を見せる島へ向かいました。

参加したツアーは早朝7:30からの上陸だけのプランだったのですが、どのツアーよりも最初に訪れることができたので島を独り占めできて良かったです*4

ただ、小型ボードなので強風だとボードがすごく揺れると思います。あと島の滞在時間が1時間+往復1時間の間トイレに立ち寄ることができません。寒い日だと防寒しっかりしないとつらいかもしれません。私が参加した日は、波も比較的穏やかで寒さをあまり感じない天候だったので、これも素敵な思い出になりました。

f:id:okamuuu:20190410082611j:plain
一番乗りを果たして島の端っこから他の参加者を撮影した図。

レンタカーで島半周

f:id:okamuuu:20190411143055j:plain
平久保埼灯台に到着。絶景だったのですが良い写真なかったのでみなさんご自分の目で確かめてください。

天気があまり良くないことがわかっていたので午前中はホテルのカフェで自習*5してお昼から島の最北の地である平久保埼灯台に向かいました。最初レンタカー屋さんに「東京ではあまり運転しない」と伝えると「結構事故が多いので本当に気をつけてください」「練習目的での利用だったら危険ですのでお貸しするのは難しい」と説明を受けました。まあでももう何回も乗ってるので一応ペーパーではないという事で無事(?)車を借りて出発。なんですけど車もカーナビも古すぎて最初はやたらと道に迷いました*6

石垣島は制限速度がだいたい40kmぐらいで、市街地を出るとほとんど歩行者も車もないので割と快適に運転できました。平久保埼灯台近辺は道が狭かったので「対向車くるなよ、くるなよ、あーやっぱ来たかー」みたいな独り言をたくさん喋っていましたがそれ以外は特に困らなかったです。

道中で赤石(あかいし)食堂でソーキそばを頂きました。14時前だったのですが待っている人が数名ほどいらっしゃいました。お肉はボリューミーでかみごたえもほどほどに残っているけど口の中でうまいこと溶ける、そんな感じでした。また石垣島に行ったら食べたいなと思いました。

f:id:okamuuu:20190413212802j:plain
極上のソーキそば。定休日があるのでレンタカー借りる日は赤石食堂の営業日に合わせておくのをおすすめします。

最後にレンタカーを返却するまで時間があったので Natural Garden Cafe PUFFPUFF でコーヒーを頂きました。ここはテラス席からの眺めがよくてとても良かったです。帰り際に「島内の方ですか?今度BBQプランができたのでご検討ください」とお店の方から言われました。でも私の顔は全く沖縄感ないのでなんでだろうとその時は思ったのですが、石垣島はかなり移住者が多く、しかももう何年も住んでいる人が結構いるみたいです。*7

f:id:okamuuu:20190411155743j:plain
海辺のカフェってやっぱ最高すよねえ

そんなわけで無事レンタカーを返却しました。最初かなり迷子になってしまったので結構へとへとになりました。西側は電動バイクで走り回ったので一応石垣島はほぼ一周したことになります。たぶん。

f:id:okamuuu:20190411162955j:plain
南ぬ浜人工ビーチのテトラポット。釣り人がいたけど、魚から人間丸見えなんじゃないかな。

西表島由布島2島めぐり(石垣島トラベルセンター)

f:id:okamuuu:20190412103706j:plain
浦内川の絶景

出立前日、最後の思い出を作ろうと8:00-17:00のツアーを申し込みしました。これは以下のアクティビティがセットになっています。ちなみにこの日は波が高いので上原港ではなく大原港に到着しました。ちなみに船酔いしている人が何人かいて、バスの中でちょっとだけ苦しんでいました。船の大きさにもよるのですが、基本的に後部座席だと揺れが少ないので船に乗る時は並んでおくのがおすすめです。

  • 石垣島西表島の往復の船代
  • 西表島で移動するバス代
  • 浦内川ボート遊覧+マリュウドの滝までトレッキング
  • キッチンイナバ昼食代
  • 星砂の浜散策
  • 水牛に乗って由布島入園

この日は結構肌寒くて浦内川ボート遊覧と星砂の浜散策の時はとても寒かったです。ただとても絶景だったので天気が良かったら風も気持ちよく浴びられて最高だったんじゃないかと思います。でもマリュウドの滝までトレッキングした時は結構運動するのでちょうど良い温度だった気がします。

f:id:okamuuu:20190413214137j:plain
リュウドの滝

一応レンタカーを借りて同じアクティビティを楽しむことは可能なんですが、2人ぐらいならツアーに参加したほうがお値段的には良いのかな、と思います*8。あとトレッキングで結構体力使うのでみんなバスの中で眠ってたので運転する人がつらいかも。それとツアーだと約1日同じ人たちと一緒に行動するのでトレッキング中に「これ急いで帰らないと船にまにあわなくないですか?」「ほんとだ!」みたいにみんなで急いで帰ったりしたのでそれもなんだか楽しかったです。

f:id:okamuuu:20190413214415j:plain
スニーカーのほうが歩きやすいのですが靴の汚れが気になる方や歩きづらい靴の方には長靴の貸し出しもしているとのこと。写真は少し汚れた私のスニーカーです、ぬかるみ時々あったのですが全部避けれたらこれぐらいで済みます。

西表島はとにかく絶景でした。世界自然遺産への登録を目指しているのでもしかしたら今後制限がかかったりするかもしれないですし、登録されたら今度は人がいっぱいになると思うので石垣島に来たら一度行ってみるといいと思います。

オススメのごはんどころ

  • 辺銀食堂
  • 島の食べものや 南風(ぱいかじ)
  • ツル商店

辺銀食堂

f:id:okamuuu:20190413214848j:plain
県産豚のレモングラス丼 

ランチで利用しました。11:30に開店で、私は11:40頃訪問したのですがすでに満席でした。なんですが名前を伝えると席が空いたら電話で連絡してくれるので便利です。近くにユーグレナモールがあるので散歩ができます。後日13:00頃訪問したときは空いていたのですが、お店の人曰く「急に団体さんが来るので何時頃が空いている、というのはわからない」との事です。まあでも待ってれば空くので気長に待ちましょう。夜は予約制のようです。

島の食べものや 南風(ぱいかじ)

www.instagram.com

ここの料理はとても美味しかったです。他にも2件ほど飲み歩きしていて、そこも十分美味しかったのですが南風を訪れた時に「ここよりも良いお店を見つけるのは難しい」と判断して次の日も訪問しました。ちなみにこのお店はたまたま入店したのですが、地元の人でもおいしいと評判のお店らしいです。

ツル商店

www.instagram.com

夜散歩していたときにたまたまツル商店の前を通った時に私の呑み屋レーダーが反応したので飲む予定なかったのですが急遽潜入捜査しました。スタッフさんや常連さんがとても気さくで私に声をかけてくれました。色々と石垣島の事を教えたもらえて楽しかったです。

というわけでこの夜は2名のスタッフさんと3名の常連さん、合わせて5名の現地の人とお話ができました。なんですけどみなさん石垣島に移住してきた人ばかりでした。実は石垣島って移住してくる人が大変多いそうです。ちなみに住処も仕事も「一人でお酒を飲んでいたら勝手に見つかる」との事でした。住まいは不動産屋で探すよりも誰かの紹介の方が効率が良いらしいです。そして仕事は選ばなければいくらでもある、との事です。実際石垣島に来ればわかるんですけど団体客がすごく多いです。ホテルとかは年中てんやわんやしているそうです。

石垣島のコーヒー事情

実は石垣島に来て、ずっと気になっていることがありました。それは「コーヒー薄すぎるな...」ということです。私は結構酸味が強いコーヒーをブラックで飲むのが好きなのですが、ちょっと私の好みとは合わなかったです。強いて言えば A&W*9のコーヒーが一番美味しいと思ってしまいました。

という事を前述のツル商店のスタッフに嘆いたところ以下のお店をおすすめしてもらいました。

  • 和居津
  • coral tree cafe
  • MAHINA MELE / Lino coffee & espresso

というわけで MAHINA MELE / Lino coffee & espresso に訪問しました。とても美味しいコーヒーだったし、洋服屋さんとセットになっているお店でなかなか感度の高いお店だなと思いました。恵比寿とか代官山にお店がそのままやってきもて違和感がないと思います。

f:id:okamuuu:20190413090848j:plain
感度の高い洋服たち

f:id:okamuuu:20190413090856j:plain
感度の高いカフェスペース

お店を教えてもらったのが出立前夜だったので、飛行機の搭乗時間の都合上 MAHINA MELE にしか行けませんでしたが次回他のお店にもお伺いしたいと思います。

まとめ

というわけでスギ花粉とヒノキ花粉が存在しない石垣島で1週間ノマドしてきました。石垣島は昼の日差しはとても強烈でした案外朝と夜が肌寒かったです。でも花粉に関しては3日目ぐらいから症状がなくなりました。最高。

滞在してすぐにノマドする場所も見つかったし、おいしいお店も開拓できたし、気分転換したい場合はたくさんアクティビティがあるしでずっと快適な1週間でした。そして石垣島はなんだか楽しそうに働いている人ばかりでした。すっかり石垣島が気に入ったので花粉の時期に関わらずらまた行きたいと思います。

こちらからは以上です。

*1:最短で即日取得が可能

*2:現地オプショナルツアーの予約サイト

*3:バス代は返還されません

*4:みなさん足元が濡れないように服をまくしあげている間に水着を履いていた私が一番最初に上陸しました

*5:swiftの案件に関わる事になったので勉強しないといけない

*6:Google Map だけに頼ると位置情報はばっちりだけど方角があさって。最終的にカーナビで向きを確認しつつ、Google Map と併用する事にした

*7:後述しますがツル商店というお店で現地の人と少しお話して知りました

*8:無料のバスもあるみたいですが、見た感じそんなに本数が多くない

*9:沖縄のファーストフードのチェーン店

Meguro.es # 20 @ Drecom で LT してきました

最近フロント界隈の勉強会に参加するようになったのですが、とある勉強会に来ていた人に「他にどんな勉強会に行っているんですか?」と聞いたところ Megro.es を教えてもらいましたので参加してきました。

meguroes.connpass.com

スポンサー

今回の会場は株式会社ドリコム様が提供されていました。ありがとうございます。 そして個人スポンサーもいらっしゃったそうです。ありがとうございます!

会場の雰囲気

思った以上に参加者が多く、お酒も一応飲みながら〜という事でしたが割とみなさん真面目な雰囲気(?)でした。参加者がお酒飲みたい人が少なめだったからだと思うのですが、割と厳かな雰囲気になっており、私は少し緊張しながらLTしました。小心者の登壇者の為にも、参加者の皆さまにはもっと出来上がって頂けば幸いです。

話した内容: VuePress で Blog を作る

https://okamuuu.com/talks/2019/2019-04-04.megro-es20@Drecom.html

Blog を書くのにオレオレCMSから始まり、Gatsby, Nuxt.js, VuePress と色々なツールを使っていたのですが、現時点では VuePress が一番気に入っているのでそれを使っています。なんですがテクニカルドキュメントを書くように最適化されているツールなのでそれを Blog のように振る舞うようにカスタマイズした、という話しです。

VuePress

そんな中、今度は VuePress という新しいツールが発明され、CSS がとても綺麗だったんで気に入っています。今回 Blog 化をする方法を紹介していますが実はプラグインがすでに存在します。 @vuepress/plugin-blog@next なんですが、まだ alpha 版なのでドキュメントがほとんどなく、結局ソースコードを読んだ結果自分でカスタマイズした方が良さそうだと思いました。

そのとき得た Tips を共有しようと思い登壇したのですが、大事なのは以下2点

  • module.exports が返すのは plain JavaScript object である必要があるが、オブジェクトを返すならば関数をセットでき、その場合は引数に options, ctx を受け取ることができる
  • life cycle に ready という hook 関数が存在しており、options, ctx を引き渡すことができる
module.exports = (options, ctx) => {
  // 最終的に plain object を返せば OK
  return {
    ready() {
      const { pages, themeConfig } = options
      // pages や themeConfig の設定をここで頑張る
    }
  },
  ...
}

上記の原理を覚えておくと結構カスタマイズできると思います。

LT一覧

以下各LTの内容です。内容間違えていたらごめんなさい。

Gatsbyで導入事例をバシバシ読めるSPAなLPを作った話 @kikunantoka

board gameを趣味で作成されている方のLT.PHPで動的に動いていたLPが重くてたまに落ちるので Gatsby.js + Netlify にしたお話。

React.js のような View component はデザインが関数化できて社内の複数のサービスで共通のレイアウトを担保できるのでとても良い改善な気がします。Netlify は何も考えなくても(?) deploy できるし https にするの簡単だし私も気に入っています。

automatically build のお話は私知らなかったのでとてもタメになりました。デフォルトで Every pull request gets deployed in its own context with a unique URL. となっていて、勝手にネット上に PR 時のデータが公開され、削除する為には 一度全て削除する 必要があるそうです。

それから計測した結果、Gatsby にしたことによって SEO が悪くなったことはない、という事でした。

react-redux code reading @sadnessOjisan

ライブラリのコードを読むことによって、パフォーマンスチューニングの手法が理解できるようになったというお話。redux のコードを読むのは大変なのですが star の数が大きいライブラリを読むのはとても良い勉強になるので良いと思いました。

パフォチューって言葉、私も言いたいです。ちなみに unstaed と typeless が最近登場しています。

VuePress で Blog を作る @okamuuu

私のターン

.d.ts を解析して破壊的変更を検知する @L_e_k_o

話が難しくてよく分からなかったのですがソースコードから API Document 生成することができるのだったらその処理を利用してコードの差分だけで feat, chore, fix, test, docs みたいな commit prefix を自動的に判別できるかもしれない、というお話かな?

もしかしたら fix, test, doc みたいな分類はできるかもしれないし、それだけでも使い所あるのかも、デモが未完成らしいので続編を期待

自分の結婚披露宴のために Vue.js と firebase で画像投稿サービスを作った話 skmtko

Vue.js + Firebase で画像投稿サービスを作ったそうです。結婚式でプランナーからフォトシャワー(10万円)の提案を聞いて「自分で作れそう、というか作りたい」という非常にエンジニアらしい動機でサービスを作った時の体験談。

トラブルがあって現実はそんなにあまくなかったという話も面白かったのですが、「親族が割と楽しげに自分のサービスを使ってくれた」という話しを聞いて密かに胸熱でした。

そしてリサイズ大事すよねえ。

Storybook とスナップショットテストで API開発の完成を待たないフロント開発フロー @_sisisin

API開発の完成を待たないフロント開発フロー、というのはほとんどの人が同様の状況にはならない気がするのですが...

reg-suit という component の差分を 画像 として比較するツールのようです。私は知らなかったツールですがこれはなかなか良さそうです。canvas が微妙に同じ結果にならない、といった問題はあるそうなのですが、これによって リファクタやり放題 という現場を作り上げたとか。言葉にすると簡単だけどそこそこの統率力がないと出来ないと思うので強いチームだと思いました。

ただ 画像の差分を計算するのは重たい処理だと思うのですが Local PC でもストレスなくできたりするのかな?機会があったら試してみたいです。

JavaScript + Docker の知見@odan3240

開発環境を Docker にした場合ホストPCで編集した内容を container に反映させる Tips を紹介。全部 Docker でやろうとすると結構大変らしいです。VSCode で補完させるためには node_modles の扱いを気にしないといけないし node-gyp とか不安?

Docker は便利なんだけどどこまで頑張るのが便利なんでしょうね。私も一時期は仮想環境を用意しないエンジニアはダメな意味で怠惰だ、と思っていましたが iOS のデバックの時にネットワークの設定が面倒すぎて「やっぱ仮想環境でなくてもいいかな」という風に趣旨替えしました。

個人的にはサーバーサイドの API だったり、デザイナーに作業してもらうための storybook の提供だったりは Docker があったら便利だと思いますが、フロントで Docker にするとコストがかかるんだったら頑張らなくてもいいんじゃないかと思いました。

今日得た知見

  • netlify の automatically build に注意
  • reg-suit すごい

あとそれから

こちらに公式な記事があります。過去の記事も見れるので興味のある人は参加してみてはいかがでしょうか?こちらからは以上です。

https://meguro.es/posts/meguro-es-20