じまろぐ

めめ

Vueでsvgファイルをいい感じに扱う

svgtemplateにコピペする暗黒時代の終わりを告げる

vue-svg-loader を使う

github.com

vue-svg-loaderを使うとsvgファイルをvueコンポーネントとして扱えるようになる

<template>
    <a href="https://github.com/vuejs/vue">
      <VueLogo />
      Vue
    </a>
</template>
<script>
import VueLogo from './public/vue.svg';

export default {
  name: 'Example',
  components: {
    VueLogo,
  },
};
</script>

Nuxtで使うときは nuxt.config.jsbuild.extend でloaderを設定

ドキュメント: https://github.com/visualfanatic/vue-svg-loader/#basic-configuration

export default {
  build: {
    extend(config) {
      const svgRule = config.module.rules.find(rule => rule.test.test('.svg'));
      svgRule.test = /\.(png|jpe?g|gif|webp)$/i

      config.module.rules.push({
        test: /\.svg$/,
        loader: 'vue-svg-loader',
      })
    },
  },
}

これでsvgファイルをコンポーネントとして好きに使える

動的なパス指定でsvgを読み込むコンポーネントを作る

svgの数だけ import して components に登録するのもめんどうなのでsvg読み込むvueコンポーネントを作る

<template>
  <component :is="svg"/>
</template>

<script>
export default {
  name: "SVGElement",
  props: {
    name: {
      type: String,
    },
  },
  computed: {
    svg() {
      return () => import(`~/assets/img/${this.name}.svg`)
    }
  }
}
</script>

この例だと src/assets/img/ の中から name で指定したファイル名のsvgを読み込める。 ~/assets/img/${this.name}.svg の部分は自分の環境と使い方に合わせてお好きなように

使う側はこんな感じ

<template>
  <div>
    <SVGElement name="bars"/>
  </div>
<template>

<script>
import SVGElement from "~/elements/SVGElement"
export default {
  name: "ProjectsPage",
  components: { SVGElement },
}
</script>

これがVueでsvgを扱う体験の一番いいやつ、と思う

GridsomeでGraphCMSのデータを使う

gridsome.server.js

const axios = require('axios')

module.exports = function (api) {
  api.loadSource(async ({ addContentType }) => {
    const res = await axios.get(process.env.GRIDSOME_GRAPHCMS_ENDPOINT, {
      params: {
        query: `query {
          posts {
            id
            title
            slug
            date
            cover {
              thumbnail: url(transformation: {
                image: {
                  resize: {
                    width: 200
                    height: 200
                    fit: crop
                  }
                }
              })
              url
            }
            tags {
              name
            }
            category {
              name
            }
            body {
              html
            }
          }
        }`
      },
      headers:
        {
          Authorization: process.env.GRIDSOME_GRAPHCMS_TOKEN
        }
    })
    const {posts} = res.data.data
    if (posts) {
      const contentType = addContentType({
        typeName: 'Posts',
        route: '/posts/:slug'
      })
      posts.forEach(post => {
        contentType.addNode(post)
      })
    }
  })
}

addNode に追加したやつから自動でschemaを作ってくれて page-query とかから取得できるようになる。

axiosでGraphCMSのデータ取得

API Explorerでこんなクエリで取得するやつ

{
  posts {
    title
  }
}

jsから

const endPoint = "https://api-apeast.graphcms.com/v1/xxxxxxxxxxxxxx/master"
const authToken = "xxxxxxxxxxxx"
const res = await axios.get(endPoint, {
  params: {
    query: `query {
      posts {
        title
      }
    }`
  },
  headers:
    {
      Authorization: authToken
    }
})
console.log(res.data)

※ フロントから叩く場合はfunctionsとかかませてtokenは環境変数で。

Nuxtプロジェクト内で使われてないコンポーネントを探す

  • プロジェクトが進むと役目を終えた使わないコンポーネントが出てくる
  • 放置していくとどれが使っててどれが使ってないかわからなくなってくる
  • 使わなくなったコンポーネントの代わりに作られたコンポーネントの名前が似てると最悪みがある
    • 「このコンポーネントだろう」と思って変更してみたら「残念!そっちじゃありません!」みたいなやつ
  • 使わなくなったタイミングで削除するのがベストだけどそうもいかないのが現実

という感じで、使われなくなったコンポーネントを削除したい。

使われなくなったコンポーネントを見つける

webpackのプラグインでビルド時に使われていないコンポーネントが探せる。

unused-files-webpack-pluginを使って一覧を出す

github.com

nuxt.config.jsbuild.plugins に追記する。

const { UnusedFilesWebpackPlugin } = require('unused-files-webpack-plugin')

export default {
  // 省略
  build: {
    plugins: [
      new UnusedFilesWebpackPlugin({
        patterns: ['src/**/*.vue', 'src/**/*.js']
      })
    ],
  }
}

patterns はビルド時に使われてないファイルをフィルターするオプション。

['src/**/*.vue', 'src/**/*.js'] を指定すると、 src/ ディレクトリ内でビルド時に使われてない .vue.js ファイルが一覧で出力されるようになる。

build の実行

nuxt build を実行すると、次のような出力が得られる。

...

Entrypoint app [big] = aaa5230fc2fc8d2d7264.js a4af7f6bc00ce5633bbf.js b2deabd0e786c068c1e3.js 9d7368b9e3e8d6ddd2a4.js

WARNING in 
UnusedFilesWebpackPlugin found some unused files:
src/components/lp/AppFooter.vue
src/components/lp/ShareButtons.vue
src/components/lp/SocialLinks.vue
src/components/organisms/AppFooter.vue
src/components/organisms/AppPage.vue
src/modules/generate_route.js

...

ここに出てきたファイルを ビルド時に使われてない = 不要 とみなして削除。

実際は使ってないけど import されてるファイルは出てこない

使わなくなったけど、使われているコンポーネントから import されてる場合、使われてないファイルとしてはでてこない。

たとえば次のような場合

<script>
import AppFooter from '~/components/lp/AppFooter'
export default {
  layout: 'lp'
}
</script>

AppFooter は使われてないけど import しているコンポーネントが使われているのでunusedではなくなる。ESLintなりでチェックして潰していきましょう

Nuxtプロジェクトじゃなくても

unused-files-webpack-pluginは名前の通りただのwebpackのプラグインなので、webpackでビルドしてるプロジェクトなら使えます

開業届を出しました

会社はやめてません。

副業はじめてました

半年前くらいから継続してやってく副業をやりはじめて、本業と並行して続けていけそうだったので開業届を出しました。

屋号は技術書典のときに使ったサークル名から「PONYHEAD」にしました。

現状2案件を受けてるので直近では受けられないですが、タイミングとご縁があったらお仕事のご相談まっております。

やってる(やってた)こと

本業と同じくフロントエンドをやってます。JavaScriptが書きたいのです。

ウェルプレイドではWELLPLAYED JOURNALというesportsに関するメディアのお手伝いをしてます。esportsに関わる仕事やりたいな〜と思ってたので楽しくやっております。

※ 2019年半ばくらいで契約終了しました。

wellplayed.media

ROXXではback checkというリファレンスチェックを行う新規サービス開発のお手伝いをしてます。HRテックに興味もあり、VueでごりごりSPAが書けて楽しいです。

backcheck.jp

エンジニア募集してるらしいです。

社会性を取り戻す戦い

フルリモートな会社に勉めて6年目になるんですが、どんどん外に出る回数も少なくなりこりゃいかんなと思い始めてたので、人との接点が増えて今のところよい感じです。

本業+副業2つという感じなのでわりとパツパツで時間の管理が大変ですが、その分だけ収入が増えたので頑張りたい所存です。

収めるぞ税を。

宇田川町あたりで作業してる日があるので渋谷近辺の人はランチのお誘いお待ちしております。

Vue.jsのrender関数(JSX)に思いを馳せた結果

この記事はVue.js #2 Advent Calendar 2018の18日目の記事です。

Vue.jsを使った開発では、特別な理由がない限り.vueファイルで記述するのが主流かと思います。.vueの場合、テンプレートの定義は<template>で行うことになるでしょう。

自分も何の疑問が湧くことなく<template>を使っていましたが、ある日ふとrender関数に思いを馳せ、いくつかの個人プロジェクトで試してみました。

本記事では、その試行錯誤から得たrender関数についてのあれこれを記しています。

<template>コンパイルされるとどうなるか

render関数の前に、まずは<template>コンパイルされるとどうなるかについて目を向けてみます。

.vue<template>コンパイルした結果どのように変換されるのか、あまり知らずにVue.jsを使っている方もいるかと思います。意識せずとも使えますし、別に知らなくても特に問題となるようなものでもありません。

<template>createElementを使った関数になる

たとえばApp.vue<template>を次のように定義します。

<template>
  <div id="app">
    <HelloWorld/>
  </div>
</template>

これがコンパイルされると、次のような関数に変換されます(見やすいように整形してます)。

var Appvue_type_template_id_54d52fb2_render = function() {
  var _vm = this;
  var _h = _vm.$createElement;
  var _c = _vm._self._c || _h;
  return _c("div",
    {
      attrs: {
        id: "app"
      }
    }, [_c("HelloWorld")], 1);
};

このように<template>createElementでVDOMを作成する関数へと変換されます。

変数名のAppvue_type_template_id_54d52fb2_renderから、これがApp.vue<template>renderにしたもの、と読み取れます。

renderといえば、render関数ですね。

render関数の基本

render関数は<template>と同じく、Vueコンポーネントにテンプレートを定義するためのものです。

簡単なコードは次のような感じになります。

export default {
  data() {
    return { msg: "hello render function"}
  },
  render(h) {
    return h("div", {}, this.msg)
  }
}

render関数は引数にcreateElementを受けるので、それをhという名前にして使うのが一般的な作法です。

render関数がコンパイルされるとどうなるか

render関数で定義したものをコンパイルするとどうなるでしょう。

次のrenderは前述した<template>の例をcreateElementを使ったものに置き換えたやつです。

export default {
  render(h) {
    return h("div", {
      attrs: {
        id: "app"
      }
    }, [h(HelloWorld)]);
  }
}

これをコンパイルすると次のようになります。

var Appvue_type_script_lang_js_ = ({
  render: function render(h) {
    return h("div", {
      attrs: {
        id: "app"
      }
    }, [h(HelloWorld)]);
  }
});

コンパイルされる前と一緒になりました。render関数でcreateElementを使って定義されたテンプレートは、変換の必要がない(そのまま実行できる)ということになります。

【ネタ】ビルド速度を突き詰めるとcreateElement!?【真似しないで】

これは計測もしてない完全な推測ですが、いろいろなものを犠牲にしてもいいなら、ビルドの速度を速くするだけのため<template>を捨ててcreateElementにする、みたいなのも考えられるかもしれません。

考えられないし絶対やらないですけど。

宣言的vs命令的

.vue<template>は宣言的にテンプレートを記述できるのが特徴であり、一つの利点です。render関数はこれと逆を行くもので、関数によってDOMを組み立てるように命令を記述します。

では命令的な記述が必ずしも宣言的なテンプレートに劣っているか?と言われると全くそうではないと思います。それぞれにメリデメがありますし、何を選択するかは使う人に委ねられているでしょう。

ただしcreateElementをそのまま使ってテンプレートを定義するのは実用に耐えるものではない(めんどくさいし読みづらい)と思います。

createElementは現実的じゃないけどJSXは結構いける

render関数ではJSXが使えます。JSXでの記述は、createElement以上<template>未満レベルで宣言的であると思います。

ちなみに、Vue CLI V3で作成したプロジェクトはBabelのプリセットに@vue/appが指定されていて、この中にJSXを使うのに必要なものが入っているので、パッケージの追加インストールなしにJSXが使えます。

export default {
  render(h) {
    return <div id="app">
      <HelloWorld/>
    </div>
  }
}

JSXを使ったrendercreateElementに変換される

render関数でcreateElementを使った場合は、コンパイルされても変化はありませんでしたが、JSXを使っている場合はcreateElementを使った関数に変換されます。

次のコードは上記JSXの変換結果です。

var Appvue_type_script_lang_js_ = ({
  render: function render(h) {
    return h("div", {
      attrs: {
        id: "app"
      }
    }, [h(HelloWorld)]);
  }
});

前述したcreateElementを使ったrender関数と同じものに変換されました。

render with JSXの可能性を探る

「VueでJSX使うくらいならReact使えばよくない?」みたいなことを何度も見たり聞いたりしますが、そんな単純な話ではないと思います。

Vueの書き味やエコシステムといった部分に乗っかりつつ、要所要所でテンプレートをJSX(render関数)で書くのは何もおかしいところはないと思います。

JSX(render関数)の可能性を探るために、<template>を使わずにJSXだけを使ってアプリケーションを作成してみました。そこから得た知見?と感想をまとめておきます。

JSXとcreateElementは共存できる

属性やイベントを細かく指定したい場合、JSXで書くよりcreateElementのほうが記述しやすい場合があります。JSXとcreateElementは共存できるので、次のような書き方もできます。

render(h) {
    return <div id="app">
      <HelloWorld/>
      {h("div", {}, "hoge")}
    </div>
  }

JSXで書いているとピンポイントでcreateElementを使いたい場面もでてくるかと思いますので、覚えておいて損はないです。

renderでしかできないこと

renderではできて、<template>ではできないことがいくつかありました。

componentsに登録せずコンポーネントを使える

importしたコンポーネント<template>で使う場合、componentsまたはVue.componentで登録しないと使えません。

render関数はJavaScriptのスコープにいるので、importしたコンポーネントは登録せずに使うことができます。前述しているrender関数の例ではHelloWorldコンポーネントを使ってますが、componentsに登録せずに使っています。

import HelloWorld from './components/HelloWorld.vue'
export default {
  render(h) {
    return <div id="app">
      <HelloWorld/>
    </div>
  }
}

使うコンポーネントが増える度に、わざわざcomponentsに登録する手間がrenderではありません。

別ファイルの定数をそのまま使える

前述したコンポーネントと同じ話ですが、別の.jsファイルに定義した定数をコンポーネント内で使う場合、computeddataに割り当ててからでないと<template>からは参照できません。

render関数ではimportした値はそのまま使えます。

import constants from './constants'
export default {
  render(h) {
    return <div id="app">
      {constants.message}
    </div>
  }
}

イベントなどをすべて定数にする

<template>@customEventな記法をするときに、このcustomEventの値を変数を参照する形で定数にしたい、と思っていろいろ試したができませんでした。

render関数では次のように記述できます。

import Channel from "../components/Channel.vue"
import types from "../store/types"
import events from "../variables/events"
export default {
  name: "Channels",
  render(h) {
    return h(Channel, {
      class: "Channel",
      on: {
        [types.REACTION_TO_MESSAGE]: this.reactionToMessage,
        [events.CLICK_REACTION]: this.reactionToMessage,
      },
    })
  },
  // ...省略
}

$emitdispatchなどに使うイベント名を定数にして、テンプレートで同じ値を参照して使うようにできます。

<template>ではcomputedでオブジェクトを返してv-onに指定することでできなくもないが、これ以外の方法があれば教えて欲しい。)

render propで<div>を挟まない

HOCに代わるパターンとしてrender propが市民権を得つつあります(もう得た?)。

Vueでrender propをする場合、スコープ付きスロット(scoped slot)を使うことになりますが、<template>はルート要素として<slot>を許容しないので、<div>で挟む必要があります。

render関数であれば、次のように記述することで綺麗にrender propできます。

render(h) {
  return this.$scopedSlots.default({
    message: this.message,
  })
}

すべてはJavaScript

<template>を使わずに書いていると、<style>も削れないかな?と思ってしまうのが人間。

そんな願いを叶えてくれるのがvue-styled-components

github.com

JSX+vue-styled-componentsを使うと、もはや.vueにする必要もなく.jsですべてを記述できます。

import styled from 'vue-styled-components'
import HelloWorld from './components/HelloWorld.vue'

const Wrapper = styled.div`
  padding: 1em;
  font-size: 1.5em;
  text-align: center;
`

export default {
  render(h) {
    return <Wrapper>
      <HelloWorld/>
    </Wrapper>
  }
}

ただし、ここまで来るとさすがに「これVueでやる必要あるか〜???」という声が自分の中から聞こえてきます。

(この組み合わせ、別に推奨してるわけじゃないです。)

Vue 3.0が来たらワンチャン?

Vue 3.0ではTypeScript対応が強化されるとのことなので、.tsxで書くのがわりと普通な状況も来そうだなと予想しています。そうなるとJSX+vue-styled-componentsも候補にあがってもおかしくないのかなと思います。

おわりに

いろいろと書きましたが、Vueのrender関数(JSX)に対する印象が少しでも変わったなら幸いでございます。

Nuxt.jsが気になるならNuxt.jsビギナーズガイドを読めばいいと思う

f:id:nakajmg:20181110032919j:plain

Nuxt.jsビギナーズガイドを著者の@potato4dさんよりいただきましたので、読んでみての客観的な感想を記しておきたいと思います。

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

ちなみますと、自分のNuxt.js経験は0.10系からで、個人/業務を問わず採用した経験があります。

超実践的ビギナーズガイド

まずこの本を一言で表すとしたら超実践的ビギナーズガイドです。

本書ではフレームワークの解説にありがちな「それドキュメント読めばよくない?」的な、機能を羅列して紹介するようなことをしていません。一貫してアプリケーションを作りながら機能を覚えていくというスタイルを取っており、手を動かせば動かしたぶんだけ強くなれるような内容になっています。

作っておしまいではなく、アプリケーションをデプロイする方法についても数パターン紹介されています。

また、Nuxt.jsだけではなく、FirebaseやJestといった、実際に使う機会が多そうなライブラリも一緒に学べます。

Nuxt.jsのポテンシャルを知れる

Nuxt.js公式のAPIのドキュメントを見るとわかるように、Nuxt.js独自の機能はそこそこ数があります。しかしながら、これら機能の中にはドキュメントを読んだだけでは便利さに気づけないような機能もあります。

自身の経験として、Nuxt.js使い始めのころは「middlewareってこれいつどこで必要になるの?誰向け?」な状態で、Nuxt.jsの持つポテンシャルを十分に理解していませんでした。middlewareの強さは、実案件で必要になった機能を実装するのにあれやこれやと調べたうえで使ってみてやっとのことで理解しました。

本書ではパブリックなAPIからデータをfetchして表示したり、認証の機構を追加したり、プラグインを使ってみたりと、実際に作りがちなアプリケーションを例にNuxt.jsの機能を解説しています。その過程で「こんな機能を実装したい場合にはNuxt.jsのこれを使えばよくて実装方法はこうですよ」というのを簡潔に、段階的に理解できます。

本書を読みきればNuxt.jsの全容をほぼ知っている状態になれると思います。

テストも書けるようになる

本書ではVueコンポーネントやVuexストアについてのテストについても書かれています。これらテストについては、本質的にいうとNuxt.jsではなくVue.jsのテストですが、どういった指針で何をどうテストするかについて、コンパクトにまとまった内容で学べます。

Nuxt.jsを使ったプロジェクトをはじめるとき、もしVueコンポーネントのテストを書いたことがない人がいても、本書を読んでもらえばきっと大丈夫です。

個人的お気に入りチャプター

テストについて書いてあるのもビギナーズガイドらしからぬ(?)ポイントでとてもよいのですが、自分の個人的なお気に入りは最終チャプターの「最新情報キャッチアップのススメ」です。このチャプターは次のような内容になっています。

  • 関連情報のキャッチアップ方法
  • ドキュメント翻訳への貢献方法
  • 関連するSlackやDiscordチャンネルの紹介

ライブラリやフレームワークは1回覚えておしまい、とはいかないのが現実です。更新に合わせて新しい機能を調べたり、困ったときに検索したりと、都度都度アクションが必要になります。しかし情報にアクセスする方法と、その必要性に気づけないままだと、そういったアクションも起こせません。

最終チャプターでこの道標を提示することで自走できるように後押しをする、そんな著者の気づかいが表れているかのようなチャプターです。本書の底本でもある「Nuxt tech book」でも同様の内容について触れられていますが、これを商業本の単独チャプターとして乗せる心意気にあっぱれでございます。

ちょっとでもNuxt.jsに興味があるならオススメ

はじめに触れたとおり、本書はNuxt.jsの超実践的なビギナーズガイドです。もしNuxt.jsを使ってみようかなと思ったら、まずは本書を読みながら一通りの機能を試してみてください。

本書を読んだあとにNuxt.jsを実戦投入すれば、まるで強くてニューゲームのような状態で開発に臨めるでしょう。そんな本でした。

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発