Madogiwa Blog

主に技術系の学習メモに使っていきます。

クックパッドマートさんのエンジニア採用試験が公開されているみたいなのでやってみた🥞

以下のTweetを見かけて気になり、勉強がてら自分でも解いてみたので自分の回答がいい感じかはわからないですがメモしておきます📝

以下の環境でやってみました💻

$ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin18]

コードはGitHubに公開してみたのでこちらまで🐙

github.com

Q1 大きさは同じで重さが異なる商品が複数あるとします。この商品N個を、以下の条件にそって...

大きさは同じで重さが異なる商品が複数あるとします。この商品N個を、以下の条件にそって3つのトラックに分配するアルゴリズムを実装してください。

  1. すべての商品は同一の大きさ、重さの箱に入り、箱は個別のIDを持つものとする
  2. プログラム実行時は、コマンドライン引数で「箱ID」と「重さ」の情報を与え、プログラムの結果には各トラックに積載する「箱ID」を出力してください。たとえば 1:50 の文字列をコマンドライン引数で渡したときは、箱ID=1, 重さ=50kg の商品とする
  3. 商品は箱に入った状態で列となって連続で運び込まれ、重さは持ち上げるまでわからず、尚且つ同時に1つしか持ち上げられない
  4. それぞれのトラックには、なるべく重さが均等になるように分配する必要がある
  5. それぞれのトラックの積載可能重量に制限はない

実行例

$ ruby solve.rb 1:50 2:30 3:40 4:10
truck_1:1
truck_2:2,4
truck_3:3

自分の実装の実行結果

$ ruby exam_1/main.rb 1:50 2:30 3:40 4:10
truck_1:1
truck_2:2,4
truck_3:3

以下のような感じで実装してみました。

  • 実行時に渡されたid:重さ形式で渡された引数をsplit(':')でidとweightに分割し、idとweightをもとに荷物オブジェクトを生成
  • 3台のトラックのオブジェクトを生成
  • 荷物オブジェクトの配列をトラックに割り当てる
    • 重量は持ち上げるまでわからないということなので、とりあえず一番積載量の少ないトラックに乗せるようなロジックで実装してみた・・・!
  • 整形して結果を出力

荷物のトラックへの割当処理を行うクラス

class TruckAllocator
  def self.call(baggages:, trucks:)
    new(baggages: baggages, trucks: trucks).call
  end

  def initialize(baggages:, trucks:)
    @baggages = baggages
    @trucks = trucks
  end

  attr_reader :baggages, :trucks

  def call
    allocate
    result_text
  end

  private

  def result_text
    trucks.map(&:info_text)
  end

  def allocate
    baggages.each { |baggage| target_truck.baggages << baggage }
  end

  def target_truck
    # NOTE: 積載重量が一番少ないトラックをターゲットにする
    trucks.min { |a, b| a.weight <=> b.weight }
  end
end

Q2 任意の数のモンスターがいます。APIサーバーにそのうちの2匹を指定すると...

任意の数のモンスターがいます。APIサーバーにそのうちの2匹を指定すると、対戦をさせた結果を得ることができます。モンスターの強さは決まっていて、同じモンスター同士であれば、対戦の結果は常に変わりません。また、三すくみのような状態は考えないものとします。このAPIサーバーをつかって、モンスターを強い順に並べてください。

APIレスポンスの例

$ curl https://ob6la3c120.execute-api.ap-northeast-1.amazonaws.com/Prod/battle/dragon+griffin
{"winner":"dragon","loser":"griffin"}

実行例

ruby solve.rb griffin vampire dragon troll medusa

自分の実装の実行結果

$ ruby exam_2/main.rb griffin vampire dragon troll medusa
==== RESULT ====
No.1: troll
No.2: dragon
No.3: medusa
No.4: griffin
No.5: vampire

以下のような感じで実装してみました。

  • 実行時に渡されたモンスター(文字列)の配列を取得
  • 渡されたモンスターの配列をもとに順位付け
    • 最初のモンスターと次のモンスターを戦わせていき、最終的に勝ち残ったモンスターを1位で確定させて、2位以降も同様に行うような、勝ち抜け方式で順位付けするような形で実装してみましたが(バブルソートライクっぽい)、もうちょっといい方法がありそうな・・・!
  • 整形して結果を出力

以下が渡されたモンスターの配列をもとに順位付けを行うClassです。

require_relative './judge'

class Arena
  def initialize(monsters)
    @monsters = monsters
    @challengers = monsters.dup
    @results = []
  end

  attr_reader :monsters, :challengers, :results

  def call
    knockout_competition
    results_info
  end

  private

  # NOTE: 勝ち抜け方式でモンスターの順位付けを行うメソッド
  # ※ deleteを使っているので重複したモンスター(文字列)が渡された場合、名寄せされる
  # 例) dragon, dragon, troll => troll, dragon
  def knockout_competition
    until challengers.empty?
      champion = battle(challengers)
      results << champion
      challengers.delete(champion)
    end
    results
  end

  def battle(monsters)
    # NOTE: モンスターが1体の場合は、不戦勝で渡されたモンスターを勝ちとする
    return monsters[0] if monsters.length < 2
    judge = ->(winner, monster) { Judge.new(winner, monster).call['winner'] }
    champion, challengers = monsters[0], monsters[1..-1]
    challengers.inject(champion) { |winner, monster| judge.call(winner, monster) }
  end

  def results_info
    text = "==== RESULT ====\n"
    results.each_with_index do |result, index|
      text << "No.#{index + 1}: #{result}\n"
    end
    text
  end
end

以下がモンスター同士の勝敗判定を行うClassです。開発時にAPIにリクエストを投げるのは検証にも時間がかかるのと先方のサーバーに負荷を書けてしまうので、ローカルで検証出来る開発用ロジックも用意して切り替え可能にしました。

require_relative './judge/api_strategy'
require_relative './judge/develop_strategy'

class Judge
  def initialize(blue, red)
    @red = red
    @blue = blue
  end

  attr_reader :blue, :red

  def call
    DevelopStrategy.call(red, blue) # NOTE: 開発用のMockストラテジ
    # ApiStrategy.call(red, blue) # NOTE: APIを実際に呼び出すストラテジ
  end
end

Q3 APIサーバーのアプリケーションに性能改善を目的としてキャッシュを導入...

APIサーバーのアプリケーションに性能改善を目的としてキャッシュを導入するとします。アプリケーションのどの部分に、どのような手法でキャッシュを導入するのか記述したうえで、なぜその手法を導入するのか、メリットやデメリットを挙げて説明してください。

今回のQ2のケースで考えるとして、以下のようなキャッシュ等が考えられるかなと思いましたけど、どうなんだろう・・・🤔

  • モンスター同士の対戦結果を対戦するモンスター名のペアをキーにKVストア等にキャッシュ
    • メリット: DBへのアクセス + 勝敗判定処理の時間を削減
    • デメリット: モンスターのパラメータ変更等による同一モンスターの勝敗結果が変わる場合にキャッシュのリフレッシュが必要。KVストア等を導入する + 運用していくコストが掛かる。
  • モンスター単位の強さの計算結果をテーブルにキャッシュしそれを利用して判定を行う
    • メリット: 勝敗判定前にモンスターの強さを計算する処理を行っている場合には、その処理時間を削減
    • デメリット: モンスターのパラメータ変更等、強さに影響がある変更が入った場合には再計算が必要

おわりに

あまりアルゴリズムとかはちゃんと勉強したことがないので、あまり自信ないですが、こういう企業が採用に使っている問題を使って勉強出来ると一石二鳥感があってありがたいですね🙏

cookpad-mart-careers.studio.site

OmniAuthのDeveloper Strategyを使って開発環境でOmniAuthを使った認証を試すMEMO

Twitter等の外部アカウントの認証を開発環境で試すのは秘匿情報を本番と開発環境で分けたり等、色々考慮することが多くて面倒だと思うのですが、OmniAuthのDeveloper Strategyを使うと簡単にOmniAuthを使った認証処理を試せそうなので使い方とかをMEMOしておきます📝

試しているOmniAuthのバージョンはomniauth (1.9.1)Railsrails (6.1.0)です。

Developer Strategyとは?

OmniAuthのREADMEにも記載されている通り、OmniAuthに内蔵された開発用のStrategyです。Developer Strategyを使っておいて、のちのち他のTwitter等のStrategyに切り替えるといったことが簡単に出来るようなもののようです。

One strategy, called Developer, is included with OmniAuth and provides a completely insecure, non-production-usable strategy that directly prompts a user for authentication information and then passes it straight through. You can use it as a placeholder when you start development and easily swap in other strategies later. https://github.com/omniauth/omniauth#an-introduction

Developer Strategyを使う準備

Developer Strategyを使うには他と同様にinitializerで以下を実行します。fieldsuid_fieldの値は任意の値に変更してください。

Rails.application.config.middleware.use OmniAuth::Builder do
  if !Rails.env.production?
    provider :developer, fields: [:email, :nickname, :user_id], uid_field: :user_id
  else
    # 本番で利用するtwitter等のその他認証プロバイダ用の設定を記述
  end
end

そしてCallbackを受け取れるようにroutesにpost '/auth/:provider/callback', to: 'sessions#create'を追記しときます。

Rails.application.routes.draw do
  get '/', to: 'posts#index'
  post '/auth/:provider/callback', to: 'sessions#create'
end

Developer Strategyのログインフォーム

http://localhost:3000/auth/developerにアクセスすると以下のフォームが表示されます。

f:id:madogiwa0124:20210102165336p:plain

<form method="post" action="/auth/developer/callback" novalidate="noValidate">
  <label for="email">Email:</label>
  <input type="text" id="email" name="email">
  <label for="nickname">Nickname:</label>
  <input type="text" id="nickname" name="nickname">
  <label for="user_id">User id:</label>
  <input type="text" id="user_id" name="user_id">
  <button type="submit">Sign In</button> 
</form>

このフォームはinitializerで定義したfieldsを元に動的にOmniAuthがフォームのVIEWを生成してくれます😳

実際に入力してSign Inを押すと入力値をパラメータにPOST "/auth/developer/callback"のリクエストが発生します。

Started POST "/auth/developer/callback" for 172.18.0.1 at 2021-01-02 16:46:31 +0900
I, [2021-01-02T16:46:31.340986 #1]  INFO -- omniauth: (developer) Callback phase initiated.
Processing by SessionsController#create as HTML
  Parameters: {"email"=>"test@example.com", "nickname"=>"test", "user_id"=>"111", "provider"=>"developer"}

Developer Strategyのログイン処理サンプル

あらかじめroutesでコールバックを受け取るpost '/auth/:provider/callback', to: 'sessions#create'を定義しているので、SessionsController#createに認証情報を取得してユーザーを作成 or 取得、セッションに格納するようなログイン処理を入れてあげればOmniAuthを使った認証を開発環境で簡単に試すことが出来ます✨

class ApplicationController < ActionController::Base
  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end
end

class SessionsController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :create

  def create
    auth_hash = request.env['omniauth.auth'] # 入力された認証情報を取得できる
    # 取得情報を元にユーザーを取得 or 作成するようなメソッドを定義しといてUserIdをsessionに保存
    session[:user_id] = User.find_or_create_from_auth(auth_hash).id 
    redirect_to '/'
  end
end

request.env['omniauth.auth']には以下のような形で入力値が入ってます🙌

# request.env['omniauth.auth']の値
{"provider"=>"developer",
 "uid"=>"111",
 "info"=>{"email"=>"test@example.com", "nickname"=>"test", "user_id"=>"111"},
 "credentials"=>{},
 "extra"=>{}}

OmniAuthのDeveloper Strategy、あまり使ったことなかったのですが非常に便利ですね✨

参考

www.rubydoc.info

2020年振り返り

今年も一年が終わるということで今年も振り返ってみる。

今年の振り返り

アウトプット

BLOG

BLOGは、今年1年で59記事書いてました📝 1週間に1.2記事ぐらい書いてるので、そこそこ書いてた✍

また月間PV数は4000弱くらいでした、別にPVは気にしてないですが、ほぼトラフィックGoogle経由なので、毎月そこそこ誰かしらの検索結果に表示されていると思うと、感慨深いものがありますね🍵

f:id:madogiwa0124:20201231140320p:plain

Webサービスとかツール

今思い返すとgemとかwebサービスとか結構作って公開してた・・・!

github.com

github.com

github.com

github.com

github.com

github.com

OSS活動

OSS活動としてはドキュメント変更ばかりですがPRを出しました🙏 ※一部は出した後にrevertしてもらって申し訳ないものもある・・・。

github.com

github.com

github.com

github.com

github.com

github.com

イベント登壇

gemの依存関係まわりの話をした💎

madogiwa0124.hatenablog.com

書籍

インプット

今年は結構読んでて、42冊読んでました📚

個人的に印象に残っている本載せときます。

ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本

ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本

  • 作者:成瀬 允宣
  • 発売日: 2020/02/13
  • メディア: 単行本(ソフトカバー)

今年はRuby Kaigiとかオンライン配信で現地に行けなかったのは残念。。。

去年の目標と結果

目標 結果 メモ
ChangeLogに乗るかbugfix的なドキュメント以外のPRを出してマージされる しょぼしょぼ系だけど、ドキュメント以外のPRを出してマージされた
DBスペシャリストを取る コロナで試験が無くなった(T_T)
プロポーザルがあるようなイベントで登壇してみる これは出来なかった
なんかグロースハック系の本を読んで実践する 結果にはあまり結びつかなかったけど、AARRRとかグロースハック的な分析してみた。
フロントとバックエンドでリポジトリが別なサービスを作る 一応リポジトリ作ってopenapiとかcommitteeとか使ってみたけどサービスとまでは行かなかった。

今年の目標

目標 メモ
CS系の本を6冊読む コンピュータサイエンス系の知識をちゃんと身につけていきたい
身近な課題を解決するなにかを作る なんか身近な課題を解決するようなサービスとかを作りたい
仕事以外の時間をちゃんと確保する 仕事とその他のバランスを取れるようになる
Ruby, JavaScript以外の言語を学ぶ 新しい言語に触れてみる

おわりに

今年は、コロナがあったり、その他色々あって割と慌ただしい1年だった気もする。来年も色々ありそうだけど、なるべく落ち着いて過ごせるようにしていきたい。。。

今年もありがとうありがとうございました、来年もよろしくおねがいします🙇‍♂️

Vue.js: vue-type-checkerで単一ファイルコンポーネントの型チェックを行う

Vue.jsの単一ファイルコンポーネントの型チェックを行う場合、webpackts-loaderを用いて、 build時に型チェックを走らせるやり方があると思うのですが、その場合ts-loadertranspileOnlyを有効に出来ないので、build時間がネックになったりします。。。

CLIで型チェックだけ走らせる方法が無いかと調べたところvue-type-checkというnpm packageがあることを初めてしったのでメモしておきます📝

vue-type-checkとは?

vue-type-check is a type checker for typescript written Vue components.

単一ファイルコンポーネントのTypeScriptの型チェックを行ってくれるライブラリで、

github.com

コードを見るとVueのLangage ServerのValidationの機能を使って型チェックを実現しているようです👀

https://github.com/Yuyz0112/vue-type-check/blob/8951e88adc6950df3f48e9dcc1e6bfcadd3a7f89/src/index.ts#L121

vue-type-checkの使い方

以下のようにインストールして、

npm i --save-dev vue-type-check
# or 
yarn add -D vue-type-check

以下のような形で実行します。

vtc --workspace . --srcDir app/javascript/components/ --onlyTypeScript true

--onlyTypeScript trueはREADMEに乗っていないのですが、これを使うとlang='ts'コンポーネントのみを対象にしてくれます✨

結果は以下のような形で表示されます。

# 成功
✨  Done in 15.40s.

# 失敗
Error in path
40:6 Type 'number' is not assignable to type 'Entry[]'.
  38 |     resetEntryList: function (): void {
> 40 |       this.entries = 1;
     |       ^^^^^^^^^^^^
  41 |     },
  42 |     updateEntryList: function (entries: Entry[]): void {

私は以下のようなスクリプトを作ってtscvtcをあわせて実行するようにして、CI等で回すようにしてみました🤖

  "scripts": {
    "type:check": "tsc -p . --noEmit && vtc --workspace . --srcDir app/javascript/components/ --onlyTypeScript true",
  }

コマンドで静的解析で型チェックが出来るようになるとbuildをしなくても型チェックが出来るのでCIとかで回しやすくていいですね✨

参考

qiita.com

Bulmaのcolumns内にSwiperを配置するとレイアウトが崩れるので対応法をMEMO

Bulmaのcolumns内にSwiperの要素を配置すると以下のような感じで盛大にレイアウトが崩れる。。。

f:id:madogiwa0124:20201219134355p:plain

Bulmaのissueも上がっているが特にいい感じの回答も無くCloseされている😢

github.com

なので色々調べてみたところ、display: -webkit-box;がswiperのcssで上書きされてdisplay: flexになることによって起こっているように見えたので、

f:id:madogiwa0124:20201219133709p:plain

display: flexを外すといい感じになる

より優先度が上がるように以下のような形で.columnsに対してdisplay: -webkit-box;を当ててあげるといい感じになった。※モバイルの場合はdisplay: -webkit-box;を当てるとうまくレスポンシブにならないのでPCの場合だけmediaクエリを使って反映するようにした。

  .columns:not(.is-desktop) {
    @media screen and (min-width: 768px) {
      display: -webkit-box;
    }
    display: -ms-flexbox;
  }

この対応方針で良いのかはあれですが、PC/SP共にいい感じになったのでメモしておきます・・・!

f:id:madogiwa0124:20201219134429p:plain

f:id:madogiwa0124:20201219134450p:plain

試したversionは以下です。

"swiper": "^5.4.5",
"bulma": "^0.9.1",

Swiperでpagenationの中に前/次のnavigationを入れたい場合の実装メモ📝

以下のような感じでSwiper.jsを使って、pagenationの中にnavigationを入れて前後のスライドに移動できるようにするようなデザインを作りたいときにデモやネット上にもあまり情報が無く、結構悩んだのですが一定出来たのでやり方をメモしておきます📝

f:id:madogiwa0124:20201219014604g:plain

CSS等を使ったもっといいやり方があるかもしれないです。

swiperjs.com

前提事項

以下の環境での実装で検証したものになります。

"vue": "^2.6.11",
"swiper": "^5.4.5",
"vue-awesome-swiper": "^4.1.1",

実装memo

以下のような実装方針で今回は実装してみました。

  • pagenationのnodeを取得
  • swiperオブジェクトを引数に前/後のnavigationのインスタンスを生成
  • pagenationの一番最初に前、最後に後のnavigationを挿入
  • navigationを押下時にpropsで渡したswiperオブジェクトに前/後のスライドに移動するメソッドを呼び出す。

以下がswiperをwrapしたコンポーネントです。 Swiperの生成と管理する要素の設置、そしてmoutedでpagenation内に前/後のボタンを配置しています。

paginationclickableがtrueにしたときにpagenationクリック時の挙動がおかしくなります。。。おそらく要素のindexを使っており、pagenation内に他の要素が入るとおかしくなってそう?

<template>
  <div class="carousel">
    <swiper ref="mySwiper" :options="swiperOptions">
      <swiper-slide>Slide 1</swiper-slide>
      <swiper-slide>Slide 2</swiper-slide>
      <swiper-slide>Slide 3</swiper-slide>
      <swiper-slide>Slide 4</swiper-slide>
      <swiper-slide>Slide 5</swiper-slide>
      <div slot="pagination" class="swiper-pagination" />
    </swiper>
  </div>
</template>
<script>
import Vue from "vue";
import { Swiper, SwiperSlide } from "vue-awesome-swiper";
import CarouselPagenationNav from "./CarouselPagenationNav.vue";
import "swiper/css/swiper.min.css";

export default {
  name: "Carousel",
  components: {
    Swiper,
    SwiperSlide,
  },
  data() {
    return {
      swiperOptions: {
        loop: true,
        centeredSlides: true,
        slidesPerView: 1.5,
        watchOverflow: true,
        spaceBetween: 16,
        pagination: {
          el: ".swiper-pagination", 
          clickable: false // clickableはfalseにしないとうまく動かない
        },
      },
    };
  },
  computed: {
    swiper() {
      return this.$refs.mySwiper.$swiper;
    },
  },
  mounted() {
    const pagenation = this.$el.querySelector("div.swiper-pagination");
    const ComponentClass = Vue.extend(CarouselPagenationNav);
    // NOTE: 動的コンポーネントで送出したemitを親コンポーネントで補足する方法がわからなかったので、
    // 仕方無しにpropsで引き回して子コンポーネントでswiperのslide操作を行っている。
    const prevBtn = new ComponentClass({ propsData: { swiper: this.swiper, type: "prev" } });
    const nextBtn = new ComponentClass({ propsData: { swiper: this.swiper, type: "next" } });
    prevBtn.$mount();
    nextBtn.$mount();
    // pagenationの最初に戻るのボタンを配置
    pagenation.insertBefore(prevBtn.$el, pagenation.children[0]);
    // pagenationの最後に次へのボタンを配置
    pagenation.appendChild(nextBtn.$el);
  },
  methods: {},
};
</script>
<style lang="scss">
.carousel {
  .swiper-container {
    color: white;

    .swiper-slide {
      background-color: burlywood;
      height: 250px;
    }
  }
}
</style>

以下が前後のボタンのコンポーネントです。 前か後かによってクリック時に実行する処理をslidePrev() or slideNext()で切り替えています。

あとついでにpagenationとnavigationは<swiper>...</swiper>の外に配置し、スライドの外に表示されるようにしています。

<template>
  <button class="carousel-pagenation-nav" @click="handleOnClick">
    {{ typeText }}
  </button>
</template>
<script>
export default {
  name: "CarouselPagenationNav",
  props: {
    swiper: {
      type: Object,
      required: true,
    },
    type: {
      type: String,
      required: true,
    },
  },
  data() {
    return {};
  },
  computed: {
    isPrev() {
      return this.type == "prev";
    },
    typeText() {
      return this.isPrev ? "<" : ">";
    },
  },
  methods: {
    handleOnClick() {
      // propsで受け取ったswiperに対して戻る/進むのメソッドを実行
      this.isPrev ? this.swiper.slidePrev() : this.swiper.slideNext();
    },
  },
};
</script>
<style lang="scss"></style>

2021/01/17 追記

以下のような感じにすると特に動的にボタンを追加したりしなくても良さそうだったので追記

f:id:madogiwa0124:20210117160638g:plain

諸々修正していますがポイントは、以下です。

  • navigationに使う要素のclass名はswiper-button-prevswiper-button-next以外の値にしてデフォルトのスタイルを当てないようにして、スタイルのカスタマイズができるように
  • pagenationとnavigationの要素はspanにして横並びで表示させられるように
<template>
  <div class="carousel">
    <swiper ref="mySwiper" :options="swiperOptions">
      <swiper-slide>Slide 1</swiper-slide>
      <swiper-slide>Slide 2</swiper-slide>
      <swiper-slide>Slide 3</swiper-slide>
      <swiper-slide>Slide 4</swiper-slide>
      <swiper-slide>Slide 5</swiper-slide>
    </swiper>
    <div class="swiper-controll">
      <!-- NOTE: swiper-button-prev/nextにすると -->
      <!-- swiperのデフォルトのnavigationのスタイルがあたってしまうので別のclass名を設定 -->
      <span class="button-prev">&lt;</span>
      <span slot="pagenation" class="swiper-pagination" />
      <span class="button-next">&gt;</span>
    </div>
  </div>
</template>
<script>
import { Swiper, SwiperSlide } from "vue-awesome-swiper";
import "swiper/css/swiper.min.css";

export default {
  name: "Carousel",
  components: {
    Swiper,
    SwiperSlide
  },
  data() {
    return {
      swiperOptions: {
        slidesPerView: 1.5,
        slidesPerGroup: 1,
        slidesPerColumn: 1,
        slidesPerColumnFill: "column",
        centeredSlides: true,
        loop: true,
        watchOverflow: true,
        spaceBetween: 16,
        breakpoints: {
          480: {
            slidesPerView: 3,
            slidesPerGroup: 3,
            slidesPerColumn: 2,
            slidesPerColumnFill: "row",
            loop: false,
            centeredSlides: false,
          },
        },
        navigation: {
          nextEl: ".button-next",
          prevEl: ".button-prev",
        },
        pagination: {
          el: ".swiper-pagination",
          clickable: true // clickable: trueにしても動く
        },
      },
    };
  },
  computed: {
    swiper() {
      return this.$refs.mySwiper.$swiper;
    },
  },
  methods: {},
};
</script>
<style lang="scss">
// scopedにしたいけど.swiper-pagination-bulletのstyleが当たらないのでscopedにしていない。
// https://github.com/surmon-china/vue-awesome-swiper/issues/22
.carousel {
  .swiper-container {
    color: white;
    width: 100%;

    .swiper-slide {
      background-color: burlywood;
      height: 250px;
    }
  }

  .swiper-controll {
    margin-top: 5px;
    text-align: center;
    vertical-align: middle;

    .button-next,
    .button-prev {
      display: inline-block;
      border-radius: 50%;
      color: #ffffff;
      background-color: #007aff;
      height: 24px;
      width: 24px;
      padding: 0;

      @media screen and (max-width: 480px) {
        display: none;
      }
    }

    .swiper-pagination {
      position: static;
      display: inline;

      .swiper-pagination-bullet {
        margin: 0 2px 0 2px;
      }
    }
  }
}
</style>

参考資料

qiita.com

swiperjs.com

Ruby on Rails: Webpackerのビルド時にSourceMapを無効化してビルド時間を短縮するメモ

開発環境でWebpackerのbuildが遅くてちょっと開発効率が落ちるみたいなことって、あると思うのですが、その際にSourceMapの生成をやめると割と早くなりそうだったので、そのへんをメモしておきます📝

WebpackerでSourceMapの生成をやめる

Webpackerで開発環境でのSourMap生成をやめるにはconfig/webpack/development.jsに以下の記述を追加するだけです。 ※productionでやめる場合はconfig/webpack/production.jsを変更してください。

// sourcemapを無効化
environment.config.delete("devtool");

WebpackでSourceMapの生成をやめるにはdevtoolを未設定にすれば良いので、上記コードでdevtoolのプロパティを削除しています。

webpack.js.org

Webpackerの開発環境のデフォルト設定はcheap-module-source-mapになっていて、これは公式ドキュメントのbuildの項目がslowになっているので、そこそこコストが高いもののようです。※当たり前ですが、(none)fastestです。

webpacker/development.js at 7e5083fb06b926d925cfff3971758a1ad5314009 · rails/webpacker · GitHub

ちなみに少し本線とずれますが、WebpackerのSourceMapへの考え方はこのあたりのissueにDHHのコメントが乗っているので、参考になると思います🚃

github.com

どれくらいビルド時間が短縮出来るか

まず今回試した環境は90ファイル(計: 200.0KB程度)をWebpackでビルドしているような環境で計測しています。

$ ls app/javascript -1R | wc -l
90
$ du -h app/ | grep javascript
200.0K     app/javascript

計測に使用したcommandは以下です。以下を実行して最速/最遅を除いた3回をSourceMapのあり(before)/なし(after)でビルドにかかった時間(real)を比較しました。

for run in {1..5}; do time bin/webpack; done;

結果は以下の通りで、これぐらいの規模感でも2.5秒ぐらいは早くなりそうですね👀

# before after diff
1 0m17.504s 0m15.607s 1.897s
2 0m19.847s 0m17.129s 2.718s
3 0m20.963s 0m17.150s 3.813s

一応最速/最遅を比較したものも載せておきます🐰🐢

# before after diff
MIN 0m16.128s 0m14.866s 1.262s
MAX 0m21.899s 0m21.546s 0.353s

おわりに

buidのスクリプトに1行追加するだけの割には割と効果がありそうなので、Webpackerを使っていて、かつビルド時間に不満がある方は試してみても良いかもですね。

参考

kyamashiro.hateblo.jp