20191203のJavaScriptに関する記事は30件です。

1年間サーバーレスで運用したNuxt.js製サイトをTypescript移行した話とそこで学んだこと

概要・前置き

どうも、都内でフロントエンドエンジニアをしてます、かめぽんです。以前、Nuxt.js + Netlifyで爆速構築するサーバーレス開発入門という記事を書きまして、その記事と同じNuxt.js + Netlifyのシンプルな構成で作った、以下のサイトを1年間と3ヶ月ほど運用してきました。撮影からデザイン、インタラクションの実装からデプロイまで一気通貫でやってみました。

https://www.brightanddizain.com/スクリーンショット 2019-12-03 22.45.40.png

Lighthouseの評価もよくサイトがきっかけでお仕事もちょくちょくいただいたりと、おかげさまでここまでくることができました。(PWA対応もしてます)

スクリーンショット 2019-12-01 1.52.16.png

しかしながら、フロントエンド技術の流れが非常に早く陳腐化してきたことやサステナビリティを高めたいとは思いつつも課題が多かったのでこの機にnuxtのtypescript移行とそれに伴う環境基盤の構築をしました。そこで学んだことや大事なことなどをご紹介し、少しでも技術の移行やフロントエンド基盤構築や設計のお役に立てればと思います。
少々長いですが、最後まで読んでいただけると嬉しいです!

サイトの概要

以下、サイトの技術スタックです。動的な部分は少なく頻繁にメンテもするわけではないので、静的なコンテンツで管理しています。しかし、DXの向上を目指しつつメンテしやすいことが条件ではあり、かつコンポーネント指向開発は必須なためnuxt.jsを使用しています。

  • Nuxt.js(nuxt generate)
  • Netlify
  • Atomic Design
  • slack api

移行の目的

現状、SFCのVueコンポーネントでサイトを構成していますが、コンポーネントの管理があまりできておらず将来的にさらに陳腐化が進むことが懸念されます。
また、これからくるであろう技術を出来るだけ実践的に試せるプレイグラウンド的な立ち位置にしたく整理をしていきます。さらに、Typescriptがこれからデファクトスタンダートのような立ち位置になるのではないかと予想をふみjavascriptから脱却し、型安全による品質とサステナビリティ向上を目指します。

  • コンポーネント資産の管理と品質の向上
  • プレイグラウンド的立ち位置
  • 陳腐化による技術スタックの刷新

実際にやったこと・構成のポイント

ここからは実施したものやリファクタをした内容などを説明していきますが、実際に導入した施策はかなり多かったので、厳選して nuxt typescript移行をする際の重要な部分を記載します。また、アーキテクチャを考えるときに重要視しているStoreとService層について少し説明します。

  • typescript導入
  • Vuex Storeの型定義
  • StoreとSeriviceについて

typescript導入

まず、nuxtのtypescript移行についてですがこちらは 公式でも出ているtypescript.nuxtjs.orgに従って移行します。ここでやることとしては以下の通りです。

スクリーンショット 2019-12-03 22.42.24.png

  • 必要なモジュールの導入
  • config系の編集

必要なモジュールの導入

まずはじめにnuxt tsに移行するために、 @nuxt/types, @nuxt/typescript-build @nuxt/typescript-runtime を導入します。 加えて、ts-loader もインストールします。@nuxt/typescript-runtimeは任意ではありますがnuxt.configなどでtsを使う場合には必要なのでインストールします。なお@nuxt/typesは@nuxt/typescript-buildに含まれているので個別にインストールする必要はありません。

npm i -D @nuxt/typescript-build ts-loader

npm i @nuxt/typescript-runtimeはプロダクション環境でも必要になるためdependenciesでインストールします。

npm i @nuxt/typescript-runtime

config系の編集

最初にtsconfig.jsonの準備をしましょう。設定はお好みで良いですが以下に例を載せておきます。typesの欄に@nuxt/typesを追加しておきましょう。

package.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
      "esnext",
      "esnext.asynciterable",
      "dom"
    ],
    "typeRoots" : ["./type"],
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": false,
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "rootDir": "./src/",
    "baseUrl": "./src/",
    "paths": {
      "@*": [
        "./*"
      ],
      "*": [
        "*"
      ]
    },
    "types": [
      "@types/node",
      "@nuxt/types"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.vue",
    "src/**/*.spec.ts",
    "src/**/*.spec.tsx",
    "src/**/*.test.ts",
    "src/**/*.test.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

次に nuxt.configの設定ですが、先ほど @nuxt/typescript-runtimeを導入したので早速 nuxt.config.jsの拡張子を .tsに変えます。また、nuxtConfigに型をつけるので@nuxt/typesでtypeをつけます。

import { Configuration as NuxtConfiguration } from '@nuxt/types'

...

const nuxtConfig: NuxtConfiguration = {
  mode: 'universal',
  ...
  build: {
    extend(config: any, { isDev, isClient }) {
      const tsLoader = {
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
          context: __dirname,
          configFile: 'tsconfig.json'
        }
      }

      for (let rule of config.module.rules) {
        if (rule.loader === 'vue-loader') {
          rule.options.loaders = {
            ...rule.options.loaders,
            ts: tsLoader
          }
        }
      }
    }
  },
  buildModules: [
    [
      '@nuxt/typescript-build', // buildMudulesに@nuxt/typescript-buildを追加します。
      {
        typeCheck: true,
        ignoreNotFoundWarnings: true
      }
    ]
  ]
}
export default nuxtConfig

次に、npmコマンドからtsを動かせるようにするためにpackage.jsonのscriptsを編集します。今までは nuxt buildなどで動きましたがts環境では nuxt-tsを使用するのでnpm scripts内のコマンドを書き換えましょう。

package.json
{
    ...
  "scripts": {
    "dev": "nuxt-ts --spa",
    "build": "nuxt-ts build",
    "start": "nuxt-ts start",
    "generate": "nuxt-ts generate"
        ...
    }
    ...
}

ビルド周りの設定は以上なので、npm run devのコマンドを実行し http://localhost:3000/にアクセスしてサイトが表示されたら成功です。

Vuex Storeの型定義

次にStoreのtypescript対応になりますが、Storeの型定義に関しては@takepepeさんの ts-nuxtjs-expressを参考にさせていただいてます。圧倒的に感謝です!

NuxtのVuex Storeでやることとしては以下の通りになります。

  • typesの準備
  • Vuexの型の拡張
  • storeの型付け

最初にVuexにおけるtypesの準備をします。Storeはモジュールモードで以下のようなディレクトリ構成にします。

├─store/
  ├─ contact/
    ├─ index.ts  // contactのstoreモジュール本体
    └─ type.ts  // Storeの型定義ファイル

ここで、nuxtが型定義のファイルまでstore配下のファイルを自動的にstoreに認識してしまうため .nuxtignoreファイルを作り自動的にstoreに認識されないようにします。

.nuxtignore
store/**/type.ts

次にcontact storeの型定義ファイルを作ります。(記事に収めるるため実際のパラメータよりも少なくしています)
interfaceの名前が略称になってますが、それぞれ S(State)、 G(Getters)、 RG(RootGetters)、 M(Mutations)、 RM(RootMutations)、 A(Actions)、 RA(RootActions)の意味になります。

store/contact/type.ts
export interface S {
  name: string
  tel: string
  message: string
}

export interface G {
  name: string
  tel: string
  message: string
  isErrName: boolean
  isErrTel: boolean
  isErrMessage: boolean
}

export interface RG {
  'contact/name': G['name']
  'contact/tel': G['tel']
  'contact/message': G['message']
  'contact/isErrName': G['isErrName']
  'contact/isEreTel': G['isErrTel']
  'contact/isErrMeassage': G['isErrMessage']
}

export interface M {
  SET_NAME: string
  SET_TEL: string
  SET_MESSAGE: string
}

export interface RM {
  'contact/SET_NAME': M['SET_NAME']
  'contact/SET_TEL': M['SET_TEL']
  'contact/SET_MESSAGE': M['SET_MESSAGE']
}

export interface A {
  setName: string
  setTel: string
  setMessage: string
  resetContacts: void
  sendContacts: void
}

export interface RA {
  'contact/setName': A['setName']
  'contact/setTel': A['setTel']
  'contact/setMessage': A['setMessage']
  'contact/resetContacts': A['resetContacts']
  'contact/sendContacts': A['sendContacts']
}

次にVuexの型の拡張に入ります。以下のようにプロジェクトのルートに typesディレクトリを作成しVuexの型を拡張していきます。

├─types/
  ├─ vuex/
    ├─ index.d.ts  // プロジェクト全体に影響する実際に呼び出されるVuexの型定義
    ├─ root.ts  // プロジェクト固有のstoreで定義したtypesをVuexにつなぎこむ場所
    └─ type.ts  // Storeの型定義ファイル

index.d.tsでは主に共通的に使えるtype.tsとプロジェクト固有のルールを含んだroot.tsをインポートします。このプロジェクトでVuexを使う場合このファイルが読み込まれます。

types/vuex/index.d.ts
import './root'
import './type'

こちらはプロジェクト固有のルールを含んだVuexの型拡張になります。

types/vuex/root.ts
import 'vuex'
import * as Contact from '../../store/contact/type'
import * as View from '../../store/view/type'

declare module 'vuex' {
  type RootState = {
    contact: Contact.S
    viwe: View.S
  }
  type RootGetters = Contact.RG
  type RootMutations = Contact.RM
  type RootActions = Contact.RA
}

以下はVuexで共通的に使われる型拡張です。

types/vuex/type.ts
import 'vuex'

declare module 'vuex' {
  type Getters<S, G> = {
    [K in keyof G]: (
      state: S,
      getters: G,
      rootState: RootState,
      rootGetters: RootGetters
    ) => G[K]
  }

  type Mutations<S, M> = { [K in keyof M]: (state: S, payload: M[K]) => void }
  type ExCommit<M> = <T extends keyof M>(type: T, payload?: M[T]) => void
  type ExDispatch<A> = <T extends keyof A>(type: T, payload?: A[T]) => any
  type ExActionContext<S, A, G, M> = {
    commit: ExCommit<M>
    dispatch: ExDispatch<A>
    state: S
    getters: G
    rootState: RootState
    rootGetters: RootGetters
  }
  type Actions<S, A, G = {}, M = {}> = {
    [K in keyof A]: (ctx: ExActionContext<S, A, G, M>, payload: A[K]) => any
  }

  interface ExStore extends Store<RootState> {
    getters: RootGetters
    commit: ExCommit<RootMutations>
    dispatch: ExDispatch<RootActions>
  }
  type StoreContext = ExActionContext<
    RootState,
    RootActions,
    RootGetters,
    RootMutations
  >
}

最後にtsconfig.jsonのfilesにtypes/vuex/index.d.tsをfilesに記述します。基本的に独自で拡張した型ファイルがあれば、随時tsconfigに追加すようにしましょう。

tsconfig.json
"files": [
  "src/types/vuex/index.d.ts"
],

ここまで、Storeの型定義をしてきましたが、ここからは実際のStoreに型を付けていきます。先ほど配置した store/contact/index.tsに、定義した型を当てていきます。1行目のvuexのimportで、先ほどtypes/vuex/index.d.tsで拡張したGtters、Mutations、Actionsの型を取り込みます。2行目ではstore/contact/type.tsのinterface宣言した型を取り込んでいます。

GettersやActions内にあるserviceディレクトリから取り込んでいるのは、ビジネス要件を含んだ純関数になっています。

store/contact/index.ts
import { Getters, Mutations, Actions } from 'vuex'
import { S, G, M, A } from './type'

import {
  validName,
  validTel,
  validMessage
} from '../../service/validation'
import submitContact from '../../service/Contact'

export const state = (): S => ({
  name: '',
  tel: '',
  message: ''
})

export const getters: Getters<S, G> = {
  name: state => state.name,
  tel: state => state.tel,
  message: state => state.message,
  isErrName({ name }) {
    return validName(name)
  },
  isErrTel({ tel }) {
    return validTel(tel)
  },
  isErrMessage({ message }) {
    return validMessage(message)
  }
}

export const mutations: Mutations<S, M> = {
  SET_NAME(state, payload) {
    state.name = payload
  },
  SET_TEL(state, payload) {
    state.tel = payload
  },
  SET_MESSAGE(state, payload) {
    state.message = payload
  }
}


export const actions: Actions<S, A, G, M> = {
  setName({ commit }, payload) {
    commit('SET_NAME', payload)
  },
  setTel({ commit }, payload) {
    commit('SET_TEL', payload)
  },
  setMessage({ commit }, payload) {
    commit('SET_MESSAGE', payload)
  },
  resetContacts({ commit }) {
    commit('SET_NAME', '')
    commit('SET_TEL', '')
    commit('SET_EMAIL', '')
    commit('SET_COMPANY', '')
    commit('SET_MESSAGE', '')
  },
  sendContacts({ state }) {
    const { company, name, email, tel, message } = state

    return submitContact({
      name,
      tel,
      message
    })
  }
}

Storeの型定義と型付けに関しては以上で、あとはいつも通りVueコンポーネントでmapGettersやmapActionsでStoreの内容を取り込むだけで型安全なStoreを扱うことができます。モジュールモードになっているので、別のStoreを導入し型安全にしていきたい場合は同様の手順を踏んでいくと良いと思います。
ここで書いてあるtypescriptのコードはあくまで一例なのでプロジェクトにあった型定義を模索していければと思います。

StoreとService層ついて

NuxtでもVueのプロジェクトでもビジネスルールを含んだロジックの記述場所は悩みのタネの一つだと思います。コンポーネント側に寄せるのか、それともStoreに集約されるのか、両方使うならどれくらStoreに寄せるべきかなどなどあると思います。よく、 Storeの肥大化なんて言われるかと思います。とはいえ、コンポーネントにロジックが並んでいるのもViewとロジックが同居してしまい混乱の原因になり得ます。では、Storeの肥大化を防ぎながらどうやってビジネスルールを含んだロジックをVuexで管理するか、というところになります。

僕の意見としては、「ロジックの網羅性」「ビジネス固有の複雑さ」を役割分担することが大事だとおもってます。
ここでいうと、Storeがロジック(ユースケース)の網羅性を担保し、Service層でビジネス固有の複雑性を含むことです。

例えば先ほどのStoreの型をつけているところでいうと、Gettersを宣言しているisErrNameやisErrTelなどのreturn部分で、validNameやvalidTelなどがあります。これがServiece層の関数になっています。ここでは、Storeはこのロジックの中身を知りません。しかしながら、Storeは画面側で必要なプロパティやActionsをもつべきなので、このようにしています。

export const getters: Getters<S, G> = {
  name: state => state.name,
  tel: state => state.tel,
  message: state => state.message,
  isErrName({ name }) {
    return validName(name)  // ValidNameがService層
  },
  isErrTel({ tel }) {
    return validTel(tel)
  },
  isErrMessage({ message }) {
    return validMessage(message)
  }
}

こちらがそのService層の中身です。ここではあくまでとてもシンプルな例を出してますが、アプリケーション固有のロジックを以下のように値を受け取って出力するだけの純関数にすることで、簡単に使い回せますしVuexのコードを汚すことなく済みます。

type ValidType = (value: string) => boolean

export const validName: ValidType = value => {
  const isErr: boolean = value.length < 4 // 名前が4文字以内だと弾かれるというルール
  return isErr
}

StoreとService層とで分割することで、Vuexのことを気にせずこの関数の中身自体をスケールさせることも分割することも用意です。
また、純関数なので非常にテストがしやすく、Vuex以外の技術に移行しやすくその時のFWなどに依存しずらいというメリットもあります。(そもそもビジネスがFWの流行り廃りに左右されるのは本望ではないかと思います)

Service層が割とutils層みたいな部分と区別がつかないみたいな話もありますが、僕個人の意見としてはutils層は アプリケーション固有の情報を含まず共通して使えるものと認識しております。
例えば、axiosをラップした外部リソース取得用のモジュールなどです。それ自体は、ビジネスルールを含む訳ではないのでutils層におきます。ただそこから、特定のAPIを叩くための関数だったりプロジェクト固有の独自キーを用いたストレージへのアクセスとなるとそれを取り込んだ上でService層で定義します。

学んだこと

サーバーレス&generateサイトを1年間運用してみて

サーバーレス&静的サイトジェネレートということでNuxt + Netlifyで1年間サイトを運用してみて、非常に楽だったなという思いです。動的コンテンツが少なくちょっとしたお問い合わせフォームだけで複雑な実装の画面などはなかったので静的コンテンツの管理だけで済みました。
また、料金面でもドメイン取得料だけだったので総額の出費も3000円程度で済みました。Netlifyに限った話ではなく、Amazon S3にアップするだけでも良いですし、若干レガシー環境だとレンタルサーバーに書き出したファイルを設定するだけでサイト作成がほぼ完了したりします。またNuxtなので、静的ファイルも書き出せつつコンポーネント志向開発で出来るという強みは非常に大きかったです。

見えてきた課題とアーキテクチャを考える上で大事なこと

という主語がデカめのタイトルになってしまって申し訳ないですが、「配置すること」って結構重要だと思っていてディレクトリ構成やなんの技術をどう使うか、という話は後の運用に大きく影響し導入時に大部分が決まる、みたいなとこはあると思います。

実際に、typescript移行は影響範囲が全体でコンポーネントやStore全てに影響が出たので、最初からTSにすればよかったんや!!って思うことがありました。また、それによって必要なモジュールも増えるので決めるべき時にしっかり決めるべきだなと感じました。
その上で設計や技術選定で気をつけたいなと思ったことは「壊しやすいか」と「組織あるいはチームの構造はどんなか」です。

壊しやすさについて

壊しやすさは「依存度を下げる」みたいな意味合いも入っていて、ある特定の技術がなければ作れないあるいは作り直したほうが早いみたいな状況です。

いかに既存の資産を活かせる形をとれるかが重要になってくるんですが、先ほどのStoreとService層の話はまさにそれです。
Service層はビジネス固有のルールを含んでいますが、Vuexに対して依存しないようにしています。そうすることによって、最悪VueやVuexが使えない状態になったとしても、そのServiceの純関数群はそのまま他のFWでも扱うことが出来ます。(あくまで「壊しやすい形」のほんの一例です)

おそらく、フレームワークのライフサイクルよりも事業のライフサイクルの方がおそらく長い(事業による)かと思います。また、プロダクトやサービスに与える影響の大きさは、フレームワークではなくマーケットの動きだったりユーザーフィードバックの方が大きいはずなので、それに合わせていかに変化(壊しやすく)出来るかが重要、という感じです。

組織あるいはチームの構造はどんなか

こちらは、コンウェイの法則でもあるんですが

The structure of any system designed by an organization is isomorphic to the structure of the organization.

とあるので、出来上がるシステムの構造は設計する組織の構造に依存する、と解釈できます。
設計する時の組織構造は何か、どんなメンバーがいるのか、どんなチーム構成なのかという部分がシステムやアプリケーションに影響します。加えて、そのサービスやプロダクトが将来的にどういった方向に行きたいのか、どういった形態になりうるのか、未来の組織の様子を踏まえて設計することが必要です。つまり、未来のチームの姿と現在との差分を定義した上でアーキテクチャを考えると良さそうだなと、思ってます。

まとめ

  • Nuxt typescrptおすすめ!
  • 静的サイトジェネレート+ホスティングで運用コストを減らそう
  • 設計は「壊しやすいか」と「組織あるいはチームの構造はどんなか」を考慮する

ここまで読んでいただき誠に感謝です。
前章で組織構造大事やぞ、と言いつつ今回のNuxtのサイトは僕一人で作ったので、今回に関しては組織構造とかないですが技術に依存し過ぎずでもそれの旨味は味わいつつ、という風にやっていけそうな気はするのでこれからも運用していきます。以下、本記事のまとめになります。

今回実際に刷新したサイトのリポジトリをのせていますのでご参考にしていただければと思います。
https://github.com/isihigameKoudai/bright-and-dizain

参考

以下、参考リポジトリや資料になります。ありがとうございます!
- https://typescript.nuxtjs.org/
- https://github.com/takefumi-yoshii/ts-nuxtjs-express
- https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%AB%E3%83%B4%E3%82%A3%E3%83%B3%E3%83%BB%E3%82%B3%E3%83%B3%E3%82%A6%E3%82%A7%E3%82%A4

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

正規表現を理解してみる

はじめに

これは、静岡 Advent Calendar 2019の3日目の記事となります。

今年は勉強会には2019/1/26(土)に「DevLOVE静岡」しか参加していないので勉強会のことは書きにくい。
一応、Qiitaは技術系ブログなので静岡にまつわる(名産品や地名など)なら何でもいいかなと、ネタとして2012年に静岡Developers勉強会で「JavaScriptの正規表現」のセッションを引っ張り出して、これをQiita用に書き直してみました。

正規表現

正規表現ってなに?
正規表現とは、簡単に言えば 検索や置換をより便利にするものです。
例えば文章中に 「りんご」 と 「みかん」 を検索したいとき、別々に検索するのは手間だし面倒です。
正規表現で検索に使うキーワードを以下のように書くと

 りんご|みかん

「りんご」と「みかん」を同時に検索する事が出来ます。

javascript 正規表現 チートシート
http://visibone.com/products/bbk14-15_850.jpg

オンライン上の正規表現の確認サイト

正規表現で使用する特殊文字

「|」を使うと複数キーワードで検索できるように、下記の特殊文字には役割がある。

 ¥ / [ ] - ( ) ? + * | . ^ $

 
正規表現では「メタ文字」と呼んでいる。
メタ文字自身を検索対象とする場合、¥を付ける

例 ¥¥ ,¥[ , ¥+

任意の1文字と位置指定

「.」…とにかくなんでもいい1文字(改行以外)

正規表現 み.
対象文字 みん みん み

「^」…先頭にマッチ 呼び名:ハット

正規表現 ^みかん
対象文字 みかん みかん みかん

「$」…末尾にマッチ

正規表現 みかん$
対象文字 みかん みかん みかん

同じ文字の繰り返し

「*」…直前の文字を 0 回以上にマッチ

正規表現 おー*い  ※数量詞表現: おー{0,}い
対象文字 おい おーい おーーい

「+」…直前の文字を 1 回以上にマッチ

正規表現 おー+い ※数量詞表現: おー{1,}い
対象文字 おい おーい おーーい

「?」…直前の文字を 0 回又は 1 回にマッチ

正規表現 おー?い ※数量詞表現: おー{0,1}い
対象文字 おい おーい おーーい

「.*」 …なんでもいい文字の連続(最長)

正規表現 JR.*
対象文字 JR静岡駅から浜松駅まで
※可能な限り合致するまでヒットする

「.*?」…なんでもいい文字の連続(最短)

正規表現 JR.*?
対象文字 JR静岡駅から浜松駅まで
※可能な限り少ない回数でヒットする

パターン論理和とパターングループ

「|」…いずれかの文字列(論理和)

正規表現 みかん|りんご
対象文字 ばなな みかん ぶどう りんご

「()」…グループ化(先優先)

()を付けた場合の違い
正規表現 私はみかん|りんごが好きです
対象文字 私はみかんが好きです
対象文字 私はりんごが好きです

正規表現 私は(みかん|りんご)が好きです
対象文字 私はみかんが好きです
対象文字 私はりんごが好きです

数詞とパターングループ

「{}」…数量詞

正規表現 w{3} 直前の文字を n 回にマッチ
対象文字 www.ora.com/goodparts/ww

正規表現 w{2,} 直前の文字を n 回以上にマッチ
対象文字 www.ora.com/goodparts/ww

正規表現 w{0,3} 直前の文字を n 回以上、m 回以下にマッチ
対象文字 www.ora.com/goodparts/ww

「()」…グループ化

正規表現 (じゃ)+ーん
正規表現 (じゃ){1,}ーん ※数量詞に置き換えた場合
対象文字 じゃーん じゃじゃーん じゃじゃじゃーん

数量詞の補足

繰り返し回数
正規表現 (みかん){2,4}
対象文字 みかんみかんみかんみかんみかん
※指定した回数より多く繰り返している場合には 繰り返し回数の上限までマッチする。

正規表現 (みかん){2,4}
対象文字 みかんみかんみかん
※可能な限り多い回数でマッチする。

正規表現 (みかん){2,4}
対象文字 みかんみかんみかんみかんみかんみかん
※5 個以上は、パターンの繰り返しでマッチする。

文字クラス

「[]」…指定内の任意表現 呼び名:ブラケット

正規表現 今日の天気は[晴曇雨]です
対象文字 今日の天気はです
対象文字 今日の天気はです
対象文字 今日の天気はです
対象文字 今日の天気は雪です

「[^]」…指定内の任意以外表現

正規表現 今日の天気は[^晴曇雨]です (^が先頭)
対象文字 今日の天気はです

※[]の中で ^ が使用された場合は、行の先頭を表す ^(ハット)とは意味が異なります。

[] と (|) の使い方を間違えた場合

例 [りんご|みかん]
「り」「ん」「ご」「|」「み」「か」「ん」のどれか一文字という意味になる。
regex101_2.png

文字クラス内の記述

  • 「[A-Z]」…英大文字 A から Z までのどれか
  • 「[a-z]」…英小文字 a から z までのどれか
  • 「[0-9]」…数字 0 から 9 までのどれか
  • 「[A-Za-z0-9]」…上記を連続して書ける
  • 「[あ-お]」…あお のどれか
  • 「[か-こ]」…かこ のどれか
  • 「[.*]」…. か * のいずれかの文字 ※[]の中ではメタ文字は普通の文字として認識される文字もメタ文字ではない。 (但し、先頭の^や ] や範囲指定の‐は例外)

主な特殊文字

  • 「¥w」…英字、数字、アンダースコア。 [a-zA-Z0-9_] と同じ
  • 「¥W」…英字、数字、アンダースコア以外の文字。 [^a-zA-Z0-9_]と同じ
  • 「¥d」…数字。[0-9] と同じ
  • 「¥D」…数字以外の文字。[^0-9]と同じ。
  • 「¥n」…改行
  • 「¥s」…スペース(全角スペースも対象)
  • 「¥S」…スペース(全角スペースも対象)以外の文字

主な正規表現の使用例

日付を検索する

正規表現 \d{4}\/\d{1,2}\/\d{1,2}
対象文字 2019/12/20 2019/12/2

郵便番号を検索する(厳密ではない)

正規表現 \d{3}-\d{4}
対象文字 012-1234

メールアドレスを検索する(厳密ではない)

正規表現 ^[\w_-]+@[\w¥.-]+.\w{2,}$
対象文字 microsoft@e-mail.microsoft.com

正規表現の修飾子

「/g」…Global (複数回マッチする)

正規表現 /みかん/g
対象文字 みかん りんご みかん

「/i」…Insensitive (大文字小文字を区別しない)

正規表現 /ABC/i
対象文字 abcdef

「/m」… Multiline (^ と $ が行末記号にマッチ)

正規表現 /456/m
対象文字 123¥n456¥n789
※¥n:改行

正規表現グループ

多くの正規表現エンジンでは「( )」を使ったキャプチャ(マッチした文字列の捕捉)をサポートしています。「$(番号)」でマッチした文字列を順番に取得できます。

キャプチャ

xx = "12:34:56".match(/(\d+):(\d+):(\d+)/);
document.write(RegExp.$1 + "<br>");    // → 12
document.write(RegExp.$2 + "<br>");    // → 34
document.write(RegExp.$3 + "<br>");    // → 56

非キャプチャ

「(?:)」を使うことでキャプチャを外します。

xx = "12:34:56".match(/(?:\d+):(\d+):(\d+)/);
document.write(RegExp.$1 + "<br>");    // → 34
document.write(RegExp.$2 + "<br>");    // → 56

肯定先読み

リンカーンの「人民の人民による人民のための政治」というフレーズ
正規表現 人民
対象文字 人民人民による人民のための政治
人民が3つ抽出されました。

肯定先読みを使用します。
正規表現 人民(?=による)
対象文字 人民の人民による人民のための政治

人民が3つありますが、肯定先読み(?=による)をつけることで「による」が含まれる「人民」だけがマッチしたということです。ここでポイントなのは、マッチした「による」が結果に含まれていないことです。
肯定先読みは後に特定の言葉を含んでいる条件を満たす文字列のみを抽出したいという時に使えます。

参照:肯定先読み~否定後読み

否定先読み

肯定先読みの逆になります。「による」が含まれる「人民」以外が抽出されます。

正規表現 人民(?!による)
対象文字 人民の人民による人民のための政治

文字列の否定

[^ABC]*? … 文字1つ1つを除外

正規表現 JR[^静岡]*?駅
対象文字 JR静岡駅 JR浜松駅 JR三島駅 JR山駅
これだとJR岡山駅があると対象外となる

((?!ABC).)? … 文字列を除外 (?!ABC).?に変更

正規表現 JR((?!静岡).)*?駅
対象文字 JR静岡駅 JR浜松駅 JR三島駅 JR岡山駅
これだとJR東静岡駅を含めた場合、JR東静岡駅も対象外となってしまう

最後に

当初は静岡のIT勉強会について何か書こうと思ったのですが、あまり過去を振り返っても仕方ないし未来志向にしようと。

ここ数年は低活動状態なので勉強会を検索するとかもしてなかったので、いつの間にか静岡でも結構勉強会やってたんだなというのを記事を書く上で検索して今頃知るくらいです。行こうかなと思ったら出勤日でした残念。
"11月か12月を目処に「人工知能ハンズオン リベンジ」として勉強会を開催しよう"と思ってたのですが、まだ知識が足りていないことと、仕事でサーバーリプレース作業をしていまして、これが来年6月までに月2,3の土曜出勤(日曜日は予備日)となり実施日も2週間前くらいでないと確定しないので、延び延びになっています。
自分的は絶対やりたいので来年のいつぞやまでお待ちください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Figmaプラグイン開発のデバッグ方法

はじめに

こちらはFigmaプラグインAdvent Calendar 2019の3日目の記事です。

Figmaプラグイン開発を始める上で、使えると便利なデバッグの方法をご紹介します。

基本的には以下に載っている方法の紹介です。
https://www.figma.com/plugin-docs/debugging/

デバッグ方法

早速デバッグ方法を紹介していきます。

1. コンソール出力

実際の開発でとりあえずコンソールに値を出力してみる、ということがあると思います。Figmaプラグインでも同様のことを行うことができます。

今回は1日目のfigmaプラグイン開発の準備の内容を使ってコンソール出力を利用してみましょう。

step1. console.logをコードに書き込む

出力させたい箇所でconsole.logを書きましょう。
console.log(nodes);

step2. figma.closePlugin()をコメントアウトする

これはプラグインの処理が終了してしまうと、オブジェクトに(...)という部分があるのですが、そこがErrorで読み取ってくれません。プラグインを終了させないように、figma.closePlugin()をコメントアウトしましょう。
※figma.closePlugin()をコメントアウトしていないので、Errorになってしまっている図
スクリーンショット 2019-12-03 22.37.12.png

↓step1,2を書いたコード

code.ts
// This plugin will open a modal to prompt the user to enter a number, and
// it will then create that many rectangles on the screen.

// This file holds the main code for the plugins. It has access to the *document*.
// You can access browser APIs in the <script> tag inside "ui.html" which has a
// full browser enviroment (see documentation).

// This shows the HTML page in "ui.html".
figma.showUI(__html__);

// Calls to "parent.postMessage" from within the HTML page will trigger this
// callback. The callback will be passed the "pluginMessage" property of the
// posted message.
figma.ui.onmessage = msg => {
  // One way of distinguishing between different types of messages sent from
  // your HTML page is to use an object with a "type" property like this.
  if (msg.type === 'create-rectangles') {
    const nodes: SceneNode[] = [];
    for (let i = 0; i < msg.count; i++) {
      const rect = figma.createRectangle();
      rect.x = i * 150;
      rect.fills = [{type: 'SOLID', color: {r: 1, g: 0.5, b: 0}}];
      figma.currentPage.appendChild(rect);
      nodes.push(rect);
    }
    console.log(nodes) //nodesを出力してみる
    figma.currentPage.selection = nodes;
    figma.viewport.scrollAndZoomIntoView(nodes);
  }

  // Make sure to close the plugin when you're done. Otherwise the plugin will
  // keep running, which shows the cancel button at the bottom of the screen.
  // figma.closePlugin(); // コメントアウトする
};

step3. OpenConsoleを開く

次にFigmaのOpenConsoleを開きましょう。
Figmaのメニュー → Plugins → Development → Open Consoleをクリック
スクリーンショット 2019-12-03 22.53.28.png

開くとこんな感じです。
スクリーンショット 2019-12-03 22.40.17.png

step4. 実行

では実行してみましょう。
Figmaのメニュー → Plugins → Development → プラグイン名
実行するとConsoleタブに出力結果が表示されます。

こんな感じ。
スクリーンショット 2019-12-03 22.38.17.png

step5. プラグインの終了

figma.closePlugin()をコメントアウトしているので、処理が終了されません。コンソール出力を確認し終えたら、figma.closePlugin()をConsoleで実行しましょう。
処理が終了します。
スクリーンショット 2019-12-03 22.43.10.png

2. DeveloperVM

こちらを使うとGoogleChromeのディベロッパーツールと同じようにステップ実行などができます。複雑な開発をする上では欠かせない方法ですね!

step1. Debuggerの埋め込み

ソースコードの止めたい箇所にdebuggerを埋め込みましょう。

code.ts
// This plugin will open a modal to prompt the user to enter a number, and
// it will then create that many rectangles on the screen.

// This file holds the main code for the plugins. It has access to the *document*.
// You can access browser APIs in the <script> tag inside "ui.html" which has a
// full browser enviroment (see documentation).

// This shows the HTML page in "ui.html".
figma.showUI(__html__);

// Calls to "parent.postMessage" from within the HTML page will trigger this
// callback. The callback will be passed the "pluginMessage" property of the
// posted message.
figma.ui.onmessage = msg => {
  // One way of distinguishing between different types of messages sent from
  // your HTML page is to use an object with a "type" property like this.
  if (msg.type === 'create-rectangles') {
    const nodes: SceneNode[] = [];
    debugger; // 止めたいところでdebuggerを書く
    for (let i = 0; i < msg.count; i++) {
      const rect = figma.createRectangle();
      rect.x = i * 150;
      rect.fills = [{type: 'SOLID', color: {r: 1, g: 0.5, b: 0}}];
      figma.currentPage.appendChild(rect);
      nodes.push(rect);
    }

    figma.currentPage.selection = nodes;
    figma.viewport.scrollAndZoomIntoView(nodes);
  }

  // Make sure to close the plugin when you're done. Otherwise the plugin will
  // keep running, which shows the cancel button at the bottom of the screen.
  figma.closePlugin();
};

step2. DeveloperVMをオンにする

DeveloperVMはデフォルトではオフになっています。
Figmaのメニュー → Plugins → Development → Use Developer VMをクリックしてオンにします。

※「Use Developer VM」にチェックがつけばOK
スクリーンショット 2019-12-03 22.45.07.png

step3. 実行

では実行してみましょう。
Figmaのメニュー → Plugins → Development → プラグイン名

こんな感じで止めることができました。
スクリーンショット 2019-12-03 22.46.26.png

あとはディベロッパーツールと同じようにいろいろ確認してみましょう!

最後に

以上2つのデバッグ方法を紹介しました。
デバッグを駆使して、Figmaプラグイン開発をしていきましょう!

どなたかの助けになれば嬉しいです。ありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【自分用メモ】supertestとpassport-stubをmochaテストに組み合わせる

supertestとは

supertestはmochaと組み合わせて使うのですが、ExpressのRouterモジュールのテストを行うことができます。
例えば以下の例では、/にアクセスしたらindexRouterが処理されるかテストしてくれます。
もちろん、/login/logoutもテストしてくれます。

app.js
app.use('/', indexRouter);
app.use('/login', loginRouter);
app.use('/logout', logoutRouter);

passport-stubとは

passport-stubは、passportモジュールを利用した認証システムを、テストする際に役に立ちます。
例えば、
「facebook認証などのテストをしたいけど、facebookアカウントを持っていない!」
といった時に役に立ちます。

テストの例

test.js
//supertestの読み込み
const request = require('supertest');
//supertestで使う、app.jsの読み込み
const app = require('app');
//passport-stubの読み込み
const passportStub = require('passport-stub');

//ログイン(/login)のテストであることを明示
describe('/login', () => {
  //before、afterはmochaの機能
  before(() => {
    //テストの前にpassportstubモジュールでログイン
    passportStub.install(app);
    //'testuser'としてログイン
    passportStub.login({ username: 'testuser' });
  });
  after(() => {
    //テストの後にpassportstubモジュールでログアウト
    passportStub.logout();
    passportStub.uninstall(app);
  });

//以下の記法は、supertestの記法
 //テストの内容を指定
  it('ログインのためのリンクが含まれる', (done) => {
    //request(app).get('/login') で、 /login への GETリクエストを作成
    request(app)
      .get('/login')
      //文字列を2つ引数として渡すとヘッダのテスト
      .expect('Content-Type', 'text/html; charset=utf-8')
      //正規表現を1つ渡すとHTMLのテスト
      .expect(/<a href="\/auth\/facebook"/)
      //期待されるステータスコードの整数と、テスト自体の引数に渡されるdone 関数を渡すと、レスポンスヘッダのテスト
      .expect(200, done);
  });

  it('ログイン時はユーザー名が表示される', (done) => {
    request(app)
      .get('/login')
      .expect(/testuser/)
      .expect(200, done);
  });
});

//ログアウト(/logout)のテストであることを明示
describe('/logout', () => {
 //テスト内容を明示
  it('ログアウト後に / にリダイレクトされる', (done) => {
    ////request(app).get('/logout')で、/logoutへのGETリクエストを作成
    request(app)
      .get('/logout')
       // `/`へリダイレクトされるかのテスト
      .expect('Location', '/')
    // ステータスコードがリクエストであるかのテスト
      .expect(302, done);
  });
});

describeitbeforeafterはmochaの書き方。
request(app).get.expectはsupertestの書き方
passportStub.install(app)passportStub.loginはpassport-stubの書き方

テスト結果

スクリーンショット 2019-12-03 22.47.50.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの基本箇所についての復習:寄り道(変数var)

はじめに

JSの変数宣言には3つの方法があります。
var,let,constです。
これから3回にわたって、この3つの違いについて解説したいと思います。
最初はvarです。

実行

varは昔からある書き方のようで、宣言した関数の中でしか使えない変数です。
次の関数を実行すると、意味がわかると思います。

var name = "suzuki";

function Say() {
    var name = "satou";
    console.log(name);
}

Say();
console.log(name);

まずnamesuzukiを代入し、その次にSay関数を定義します。
Say関数では、最初にnamesatouを代入し、console.logで出力する、という処理を行います。
関数を定義した後は、Sayを実行し、最後にnameの中身をconsole.logで出力します。

この一連の処理を行うと、どういう風に出力されるでしょうか?
まず一番最初に出力を行うのは、Say();の部分です。関数Sayでは、前述の通りnameにsatouを代入して、それを出力するという処理を行うので、ここではsatouと出力されそうです。
その後のconsole.log(name);との間で、nameに新しい値を代入してないので、ここでもsatouと出力されそうな気がします。つまり、

satou
satou

という出力結果になりそうです。
しかし実際は予想と違って、

satou
suzuki

と出力されます。
Say();を実行した時に、一度var name = "satou"とし、実際にその通りに出力されているのに、Say()*の処理が終わった瞬間、nameが最初に代入されたsuzukiに戻っています。

これこそが、varで宣言した変数は、関数の中でしか使えない変数の具体例になります。
var name = "satou"と宣言したのは関数Sayの中なので、nameにsatouが代入されている事は、関数Sayの処理を行っている時だけであり、関数Sayの処理が終わればnameは元に戻るという事になります。
今回の関数でいえばSay関数の処理が終われば、nameは最初に代入したsuzukiに戻るという事になります。

基本的な部分ですが、この事を知らないと「ちゃんと変数を上書きしたのに!?」と慌てることになるので、しっかり覚えておきましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモ

ストロングパラメータ

指定したキーのみを受け取るので、データをDBに入れるかどうかを判別させるために使う。
ex params.permit(:キー名,キーの値)

java script

list.push(***); 配列に要素を追加する。

list.pop(); 配列の最後の要素削除
⇅ 逆の関係
list.shift(); 配列の最初の要素削除

オブジェクトの作成
let object = {};

関数を使うとき
returnを記述する。
 returnは関数内で処理をした結果戻り値として返す。

関数とは
入力された 値に対して 出力して 返すこと

引数 → 関数 → 戻り値

function calc(num1,num2){
return num1*num2;
}

let num1 = 5;
let num2 = 7;
console.log(calc(num1,num2));

DOM (document object model)

htmlを階層上に表現したデータでjava script

body ---------header
|
|
------content
|
|
-------footer

body header content footer をノードと言う

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Three.jsで遊んでみよう

「GLSLの記事書いておいて、今さらWebGLのライブラリかよ!」というツッコミは置いておいて
実はオライリーのThree.jsの参考書を読んでいる最中なのです。
(第2版の内容はr78ですが、ここではr107で進めていきます。)

これからThree.jsを触ってみようという方向けに
流動食のように咀嚼しやすく飲み込みやすく書いていこうと思います。

Three.jsとは?

WebGLをイチから書くのは、とんでもなく大変なのです………。
「じゃあ、WebGLを簡単に書けるようにしようじゃなイカ!」
そうして生まれたJavaScriptのライブラリです。

WebGLのライブラリは他にもありますが、代表的なものだと
2DはPixiJS、3DはThree.js
といったところでしょうか?

そもそもWebGLとは?

すっごく簡単に言うと

インターネットブラウザをプレイステーションにしてしまう魔法の技術
(引用元:http://wise9.jp/archives/6060)

もうちょっと詳しくいうと…
ブラウザ上で3DCGプログラミングを実現できる、JavaScriptから利用できるAPI。
ブラウザを通してデバイスのGPUにアクセスすることができます。
(GPU:PCの中で画像処理を専門とした頭脳)

(上記ですでに書いてはいますが)
WebGLと聞くと3Dがすぐ思い浮かぶかと思いますが、実はWebGLは3Dのためだけの機能ではなく、2Dでの表現も可能です。

Three.jsで3D空間をつくってみる

まず、3D空間をつくる流れを説明します。

とりあえず何が必要でしょう…?

シーン
いわゆるステージ。テレビの撮影スタジオみたいなもの。すべてを保持し監視するコンテナ。

カメラ
そのままカメラ。シーンに何を描画するかを決定する。

レンダラ
すべてを踏まえ、つくったシーンがどう見えるか計算してくれる。

イラストにするとこんな感じでしょうか??

img_01.jpg

コードはこんな感じ↓↓↓

<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
</head>

<body>
<script>
var scene;
var camera;
var renderer;


function init() {
  // まず、シーンをつくる。
  scene = new THREE.Scene();


  // そして、カメラをつくる。PerspectiveCamera は遠近感があり自然な見た目になります。
  // PerspectiveCamera の引数は (視野角, アスペクト比, 近くはどこまで映すか, 遠くはどこまで映すか)
  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
  // カメラの位置を指定する。
  camera.position.set(-20, -15, -30);
  // カメラの視点を指定する。この場合はシーンの位置。
  camera.lookAt(scene.position);


  // 最後に、レンダラをつくる。antialias を true にしてあげると輪郭のギザギザが滑らかになりますが、重たくなるのでそこは様子をみて。
  renderer = new THREE.WebGLRenderer({ antialias: true });
  // デバイスの解像度を指定する。
  renderer.setPixelRatio(window.devicePixelRatio);
  // レンダラのサイズを指定する。
  renderer.setSize(window.innerWidth, window.innerHeight);


  // ボディにレンダラ(canvas)を追加する。
  document.body.appendChild(renderer.domElement);

  // レンダー用関数呼び出し
  render();
}


// レンダー用関数
function render() {
  // レンダラをシーンに追加する。
  renderer.render(scene, camera);
  // 1フレームごとにループする。
  requestAnimationFrame(render);
}


// リサイズイベント
function onWindowResize() {
  // カメラのアスペクト比を更新する。
  camera.aspect = window.innerWidth / window.innerHeight;
  // カメラのパラメータを変更したら必ず呼び出す。(カメラ投影行列を更新)
  camera.updateProjectionMatrix();
  // レンダラのサイズを更新する。
  renderer.setSize(window.innerWidth, window.innerHeight);
}


window.addEventListener('DOMContentLoaded', init);
window.addEventListener('resize', onWindowResize);
</script>
</body>

さて、見てましょう………!とはいかず、見てもエラーにはなりませんが
あくまで空間をつくっただけですので何も映りません。

ものを映すために必要なのは

ここからは撮影風景を思い浮かべるとわかりやすいかもです。

ライティング
撮影にはライティングが大事。

オブジェクトの配置
頑張って3D空間をつくっても、撮影小物や役者がいないと何も映らないですよね。

ここでもイメージイラストを載せておきます…笑

img_02.jpg

さてさて、以下はオブジェクトをつくるのに必要なもの…それは3つあります。

ジオメトリ
オブジェクトの形状を表す情報。ポリゴン。(頂点や線分、面など)

マテリアル
オブジェクトの質感の情報。オブジェクトの皮のようなもの。(色や、金属っぽいのか木っぽいのか…など)

メッシュ
ジオメトリとマテリアルの集合体。ここでいうと、オブジェクト=メッシュになります。

流れとしては、
① ジオメトリで形状をつくり、マテリアルで質感をつくる。
② 最後にメッシュにこの2つをいれてあげることでオブジェクトが完成します。

先ほどのサンプルに、オブジェクトを配置します。
(今回はライトの影響を受けないマテリアルを使用するので、ライトは省きます。)

<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
</head>

<body>
<script>
var scene;
var camera;
var renderer;

function init() {
  scene = new THREE.Scene();

  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(-20, -15, -30);
  camera.lookAt(scene.position);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);


  // 円球のジオメトリ(ポリゴン)をつくる。SphereBufferGeometry の引数は (半径, 横方向を何面にするか, 縦方向を何面にするか)
  var sphereGeometry = new THREE.SphereBufferGeometry(6, 36, 36);
  // 円球のマテリアル(質感)をつくる。
  var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  // 円球のジオメトリとマテリアルをメッシュにまとめる。
  var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  // シーンに円球を追加する。
  scene.add(sphere);


  document.body.appendChild(renderer.domElement);

  render();
}

function render() {
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

window.addEventListener('DOMContentLoaded', init);
window.addEventListener('resize', onWindowResize);
</script>
</body>

img_03.jpg

確かに円球らしきものは見えましたが、これだけだと面白くはないですよね〜…
…ということで、もう少しゴニョゴニョしたいと思います。

<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
</head>

<body>
<script>
var scene;
var camera;
var renderer;
// キューブマップ
var cubeMap = {
  path: './assets/cubemap/',
  urls: [ 'posx.jpg', 'negx.jpg','posy.jpg', 'negy.jpg','posz.jpg', 'negz.jpg' ]
}

function init() {
  scene = new THREE.Scene();
  // シーンの背景にキューブマップを設定する。
  scene.background = new THREE.CubeTextureLoader().setPath(cubeMap.path).load(cubeMap.urls);

  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(-20, -15, -30);
  camera.lookAt(scene.position);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  var sphereGeometry = new THREE.SphereBufferGeometry(6, 36, 36);
  // マテリアルに環境マッピングを設定する。(シーンの背景)
  var sphereMaterial = new THREE.MeshBasicMaterial({ envMap: scene.background });
  var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  scene.add(sphere);

  document.body.appendChild(renderer.domElement);

  render();
}


function render() {
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}


function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}


window.addEventListener('DOMContentLoaded', init);
window.addEventListener('resize', onWindowResize);
</script>
</body>

img_04.jpg

これはいわゆる環境マッピングというやつです。
(スカイボックスを作らないでシーンに背景をそのまま貼り付けられるようになったんだ…とか、こっそり感動していました。)

ここでは紹介していませんが、OrbitControlsdat.guiを使うと
マウスでぐりぐり動かせたり、メーターで色や形を変更できるようになります。

最後に

Three.jsはどうしても3DCGの知識が伴うので、あまり知らない人にとっては敷居が高いかもしれません。
…が、ちょろっと遊ぶくらいなら簡単にできるので、ぜひとも遊んでいただきたいです。

また、誤字や間違いなどがあれば、ご指摘いただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Expoで作成したアプリをGoogle Playに申請するまで(2019/11時点)

App Store版の記事と同様に、Expoで作成した最低限のアプリをGoogle Playに申請する流れに関してまとめます。

今回私が作成し申請したアプリはこちらのFirebaseとカメラを使用したアプリです。

Google Play Developper登録

Google Playにアプリを公開するには、GoogleアカウントとGoogle Play Developerとしての登録が必要です。

Google Play Developer
https://developer.android.com/distribute/console

こちらの「ログイン」ボタンから新規登録して支払い情報を入力してください。
App Storeと違う点は年額ではなく最初に$25かかるだけであるというのと、デベロッパー登録のみに関しては審査がないというところです。

Google Play Consoleで新規アプリを作成

デベロッパー登録が完了したら、Google Play Consoleを開き、「アプリの作成」ボタン→言語・アプリ名を入力してアプリ情報を作成します。

googleplay.png

app.jsonを編集

まずはドキュメントを参考にしながら、app.jsonに必要な情報を追加します。

iconsplashはiOSと共通

app.json(部分)
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#00c2ad"
    },

Android向けにアイコンなどを追加します。

app.json(部分)
    "android": {
      "package": "[パッケージ名]",
      "adaptiveIcon": {
        "backgroundColor": "#00c2ad",
        "foregroundImage": "./assets/adaptiveicon.png"
      },
      "permissions": [
        "CAMERA",
        "READ_EXTERNAL_STORAGE",
        "WRITE_EXTERNAL_STORAGE"
      ]
    },
    "notification": {
      "icon": "./assets/notificationicon.png",
      "color": "#00c2ad"
    },

パッケージ名はiOSのbundleIdentifierと同じように、ドメインを使って世界的に固有のものを使用します。

アダプティブアイコンはAndroid独自のアイコン規格で、ランチャー側でマスク形状を変えたりアニメーションなどをつけることができるものです。
アダプティブアイコンがサポートされている端末においてはこれを設定していないアプリはアイコンの周りに白い余白が出てしまいます(参考)。
前面の画像(foregroundImage)と後景の画像(backgroundColor(デフォルトは白) あるいは backgroundImage)で構成され、
Expoの設定ではforegroundImageに1024x1024pxの透明pngファイル、背景色(backgroundColor)あるいはbackgroundImageに1024x1024pxのpngファイルを指定すればOKです。私が作成したforegroundImageは以下の画像ですが、このように中央に要素を配置するようなデザインの場合、様々な形状に合わせるためにある程度余白を取っておく必要があります。

adaptiveicon.png

Expoではデフォルトでは全ての権限への許可が設定されていて、そのままだとインストールする際に必要以上の権限をユーザーに求めてしまうことになりますので、permissionsに必要な分だけ記載しましょう。
今回私が申請したアプリはカメラとFirebase(内部的にAsyncStorageを使用)を使用するので、CAMERAREAD_EXTERNAL_STORAGEおよびWRITE_EXTERNAL_STORAGEを許可しています。

通知アイコンについてはこちらの記事を参考にしてください。

ビルドする

app.jsonに必要な設定を追加したら、Android Studioをインストールした上で、Android向けにビルドします。

$ expo build:android

最初にビルドする際は、このように証明書(keystore)を新しく生成するか既存のものを使うかどうかの選択肢が出てきます。

[exp] No currently active or previous builds for this project.

Would you like to upload a keystore or have us generate one for you?
If you don't know what this means, let us handle it! :)

  1) Let Expo handle the process!
  2) I want to upload my own keystore!

通常は1)でそのままExpoに任せてしまって大丈夫です。ただし、後々アップデートする際には同じkeystoreが必要なので、(これも同じ環境で開発する場合は通常意識しなくても大丈夫ですが)keystoreを安全な場所にバックアップして置くことが推奨されています。

$ expo fetch:android:keystore

上記のコマンドを使うと、keystoreの情報が表示され、ディレクトリに.jksファイルが生成されます。
これをpublicなリポジトリにアップしたりしないよう注意して保存しましょう。

ビルドが完了し、apkファイルへのリンクが表示されたらダウンロードし完了です。

アプリ情報を入力する

Google Play Consoleを開き、アプリ情報を入力します。

googleplay.png

右側メニューのチェックマークが付いている部分を順に入力していきます。

まずは「ストアの掲載情報」で基本的な情報を埋めていきましょう。
タイトルや説明文などのほか、最低限以下のものが必要です。

  • アイコン→ 512x512pxのPNG画像(Google Playで表示されます)
  • スクリーンショット→ 1辺の長さ320px~3840pxのJPGまたはPNG画像 少なくとも2種類
  • フィーチャーグラフィック(ストアで大きめに表示されるバナー)→ 横1024 x 縦500のJPGまたはPNG画像(↓スクショを使って適当に作っておきました) featuregraphic.png
  • プライバシーポリシー→ 今回はURLを送信しないというチェックがありますが、Expoでは必須(広告IDを送信しているため

プライバシーポリシーは気になりますが、App Store版の記事と同じくそれほど厳しい感じではない気がします(もちろんそれなりのサービスを運用するのであれば別ですが)。

「価格と配布」で価格、配布する国、同意事項などを入力します。ここで広告を含むかどうかを選択したら、「アプリのコンテンツ」でターゲット年齢層を入力することができます。

残るは「コンテンツのレーティング」と「アプリのリリース」ですが、「コンテンツのレーティング」でアンケートに回答するにはapkファイルをアップロードする必要があります。
「アプリのリリース」で任意のトラック(そのまま公開する場合は製品版)から「管理」→「リリースを作成」し、ドロップエリアにapkファイルをドラッグ&ドロップしてアップロードします。

googleplay.png

アップロードが完了し、「コンテンツのレーティング」で全ての質問に答えたら、全ての必要な情報が入力できたことになります。
その後先ほど作成したリリース画面から確認→公開へ進めばアプリの申請が完了します。

審査

申請が完了すると、ダッシュボードには公開中と表示されますが、しばらくすると以下のように審査中と表示されるようになります。

googleplay.png

今までにExpoで作成したアプリをアップデートを含め3回申請しましたが、問題ない場合でも審査の完了には数時間から数日と幅があるような気がします。
そして無事に審査が完了したら、このようにIARCのレーティングを知らせるメールでもって伝えられます。
googleplay.png

公開されたアプリはGoogle Play Consoleのダッシュボード→「GOOGLE PLAYで表示」で確認できます。

以上、Google PlayでのExpoアプリ申請の大まかな流れでした。
App Storeでの申請のようにアップロード作業などが縛られていないのと、最低限用意するスクリーンショットが少ないため比較的簡単に感じるのではないでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Expoで作成したアプリをGoogle Playに申請するまで(2019/11~12時点)

App Store版の記事と同様に、Expoで作成した最低限のアプリをGoogle Playに申請する流れに関してまとめます。

今回私が作成し申請したアプリはこちらのFirebaseとカメラを使用したアプリです。

Google Play Developper登録

Google Playにアプリを公開するには、GoogleアカウントとGoogle Play Developerとしての登録が必要です。

Google Play Developer
https://developer.android.com/distribute/console

こちらの「ログイン」ボタンから新規登録して支払い情報を入力してください。
App Storeと違う点は年額ではなく最初に$25かかるだけであるというのと、デベロッパー登録のみに関しては審査がないというところです。

Google Play Consoleで新規アプリを作成

デベロッパー登録が完了したら、Google Play Consoleを開き、「アプリの作成」ボタン→言語・アプリ名を入力してアプリ情報を作成します。

googleplay.png

app.jsonを編集

まずはドキュメントを参考にしながら、app.jsonに必要な情報を追加します。

iconsplashはiOSと共通

app.json(部分)
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#00c2ad"
    },

Android向けにアイコンなどを追加します。

app.json(部分)
    "android": {
      "package": "[パッケージ名]",
      "adaptiveIcon": {
        "backgroundColor": "#00c2ad",
        "foregroundImage": "./assets/adaptiveicon.png"
      },
      "permissions": [
        "CAMERA",
        "READ_EXTERNAL_STORAGE",
        "WRITE_EXTERNAL_STORAGE"
      ]
    },
    "notification": {
      "icon": "./assets/notificationicon.png",
      "color": "#00c2ad"
    },

パッケージ名はiOSのbundleIdentifierと同じように、ドメインを使って世界的に固有のものを使用します。

アダプティブアイコンはAndroid独自のアイコン規格で、ランチャー側でマスク形状を変えたりアニメーションなどをつけることができるものです。
アダプティブアイコンがサポートされている端末においてはこれを設定していないアプリはアイコンの周りに白い余白が出てしまいます(参考)。
前面の画像(foregroundImage)と後景の画像(backgroundColor(デフォルトは白) あるいは backgroundImage)で構成され、
Expoの設定ではforegroundImageに1024x1024pxの透明pngファイル、背景色(backgroundColor)あるいはbackgroundImageに1024x1024pxのpngファイルを指定すればOKです。私が作成したforegroundImageは以下の画像ですが、このように中央に要素を配置するようなデザインの場合、様々な形状に合わせるためにある程度余白を取っておく必要があります。

adaptiveicon.png

Expoではデフォルトでは全ての権限への許可が設定されていて、そのままだとインストールする際に必要以上の権限をユーザーに求めてしまうことになりますので、permissionsに必要な分だけ記載しましょう。
今回私が申請したアプリはカメラとFirebase(内部的にAsyncStorageを使用)を使用するので、CAMERAREAD_EXTERNAL_STORAGEおよびWRITE_EXTERNAL_STORAGEを許可しています。

通知アイコンについてはこちらの記事を参考にしてください。

ビルドする

app.jsonに必要な設定を追加したら、Android Studioをインストールした上で、Android向けにビルドします。

$ expo build:android

最初にビルドする際は、このように証明書(keystore)を新しく生成するか既存のものを使うかどうかの選択肢が出てきます。

[exp] No currently active or previous builds for this project.

Would you like to upload a keystore or have us generate one for you?
If you don't know what this means, let us handle it! :)

  1) Let Expo handle the process!
  2) I want to upload my own keystore!

通常は1)でそのままExpoに任せてしまって大丈夫です。ただし、後々アップデートする際には同じkeystoreが必要なので、(これも同じ環境で開発する場合は通常意識しなくても大丈夫ですが)keystoreを安全な場所にバックアップして置くことが推奨されています。

$ expo fetch:android:keystore

上記のコマンドを使うと、keystoreの情報が表示され、ディレクトリに.jksファイルが生成されます。
これをpublicなリポジトリにアップしたりしないよう注意して保存しましょう。

ビルドが完了し、apkファイルへのリンクが表示されたらダウンロードし完了です。

アプリ情報を入力する

Google Play Consoleを開き、アプリ情報を入力します。

googleplay.png

右側メニューのチェックマークが付いている部分を順に入力していきます。

まずは「ストアの掲載情報」で基本的な情報を埋めていきましょう。
タイトルや説明文などのほか、最低限以下のものが必要です。

  • アイコン→ 512x512pxのPNG画像(Google Playで表示されます)
  • スクリーンショット→ 1辺の長さ320px~3840pxのJPGまたはPNG画像 少なくとも2種類
  • フィーチャーグラフィック(ストアで大きめに表示されるバナー)→ 横1024 x 縦500のJPGまたはPNG画像(↓スクショを使って適当に作っておきました) featuregraphic.png
  • プライバシーポリシー→ 今回はURLを送信しないというチェックがありますが、Expoでは必須(広告IDを送信しているため

プライバシーポリシーは気になりますが、App Store版の記事と同じくそれほど厳しい感じではない気がします(もちろんそれなりのサービスを運用するのであれば別ですが)。

「価格と配布」で価格、配布する国、同意事項などを入力します。ここで広告を含むかどうかを選択したら、「アプリのコンテンツ」でターゲット年齢層を入力することができます。

残るは「コンテンツのレーティング」と「アプリのリリース」ですが、「コンテンツのレーティング」でアンケートに回答するにはapkファイルをアップロードする必要があります。
「アプリのリリース」で任意のトラック(そのまま公開する場合は製品版)から「管理」→「リリースを作成」し、ドロップエリアにapkファイルをドラッグ&ドロップしてアップロードします。

googleplay.png

アップロードが完了し、「コンテンツのレーティング」で全ての質問に答えたら、全ての必要な情報が入力できたことになります。
その後先ほど作成したリリース画面から確認→公開へ進めばアプリの申請が完了します。

審査

申請が完了すると、ダッシュボードには公開中と表示されますが、しばらくすると以下のように審査中と表示されるようになります。

googleplay.png

今までにExpoで作成したアプリをアップデートを含め3回申請しましたが、問題ない場合でも審査の完了には数時間から数日と幅があるような気がします。
そして無事に審査が完了したら、このようにIARCのレーティングを知らせるメールでもって伝えられます。
googleplay.png

公開されたアプリはGoogle Play Consoleのダッシュボード→「GOOGLE PLAYで表示」で確認できます。

以上、Google PlayでのExpoアプリ申請の大まかな流れでした。
App Storeでの申請のようにアップロード作業などが縛られていないのと、最低限用意するスクリーンショットが少ないため比較的簡単に感じるのではないでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでカレンダーを作ってみた

JavaScriptの基本を勉強したのでなにか作ってみようと思い、
カレンダーの制作を行いました。

スクリーンショット 2019-12-03 21.21.16.png

スクリーンショット 2019-12-03 21.25.52.png

どこで何をしているかをコメントで書いておかないとわからなくなるので
コメントを書くことを意識しようと思いました。

参考にしたサイト
http://cly7796.net/wp/javascript/create-a-calendar-with-javascript/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インターンが勝手にTypeScript導入して学んだこと

初めに

こんにちは、株式会社LITALICOにて19卒エンジニアの@M108Kineticsです。

同社でインターンだった頃に、ほぼ独断と偏見でプロダクションコードをTypeScript化した話をご紹介します。

細かい導入お作法ではなく、チームへのインパクトなど比較的エモめな内容になっております。

TL;DR

TypeScriptは不可欠ではないが楽しいしためになる。

対象

世の中には「全人類TypeScript化するべき」みたいなマウント風潮がなくもない気がしますが、過激派はそっとしておきましょう。

実際のところメリデメってどんな感じだったのか、弊チームでの体験をお伝えします。

TypeScriptが最高であることにノーダウトな方にはあまり参考にならないかもしれませんが、
- ちょっと気になってるけど触れられてない
- 既存のプロジェクトに導入してみたいが実際どれぐらいコストがかかるのかよくわからない
- やたらと最近聞くけど本当に必要?
等々感じている皆様に参考になれば幸いです。

チーム

この記事での「チーム」は、リードとなる1~2名の先輩エンジニアの他、4~5名の18卒・19卒の新卒エンジニアで構成されています。

若い衆はいわゆる「文系エンジニア」で、経験も浅いですが日々頑張ってプロダクト開発しています。

動機

導入背景はいたってシンプル、純粋に興味でした。

学生はやたらと新しい技術に触りたがって「〇〇やったことあります」と言いたがってるぐらいがいいんです。(のはず)

フロントエンドに長けた先輩ともちょこちょこ「楽しそうだよね」と話していて、それをゴーサインと勝手に認識してとりあえずイシューだけ立てたのがファーストステップでした。
Screen Shot 2019-11-12 at 14.37.30.png

ふざけたイシューですが、個人的に新しい技術・ライブラリの導入意欲発信のハードルはできるだけ低くていいと考えています。

移行作業

もともとWebpackとBabelを使ってES6を書いていました。

それをJS・TSどちらもをトランスパイルできるようにしたので、移行作業は時間を見つけながらゆっくり行いました。

プルリク数約30、ファイル数でいうと200以上(たぶん)。

今考えるとよく一人でやるなとも思いますが、インターンだからこそできたのかもしれません。

実際終わったのは正社員になってから、一番はじめのプルリクを出してから約半年後でした。

おこがましいことに説明らしい説明も勉強会らしい勉強会もせず勝手に導入し、今となっては6名いる開発チーム全体がTypeScriptを日常的に書いています。

やってよかったと思える理由

仕様・内装を知ろうとする癖がつく

僕はTypeScriptを使うメリットはコードの安全性より何より、「今まで理解しづらかったコードへの窓口を用意してくれる」ことにあると思っています。

完成されているコードって難しい

あるライブラリを利用する場合、ドキュメントをさらっと読んで言われた通りのものを渡せばそれっぽい返り値が返ってきます。

ある別の開発メンバーが用意したモジュールなどを使うときは、直接聞けばなんとなくやるべきことはわかります。

しかしそれだけの理解だとバグ発生の際やモンキーパッチを当てたい時に頭を抱えることになります。

一方で、中身を覗きに行こうとして「どこから始めればいいんだ...」と途方に暮れたことのある方も多いのではないかと思います。

糸口としての「型」

全てのコードを「インプットを受け取って何かしらの処理をし、その結果をアウトプットとして返す」関数として捉えるとします。

インプットである引数とアウトプットである返り値が「どういった形式でどんな情報であるべきなのか」がハッキリとコードの中で定義されているだけで、理解へのとっつきやすさはものすごく変わります。

渡しているどのデータがどのように処理・利用され、最終的なアウトプットとどう関係するのか。これを追うスタートに立てるので、気がついたら大まかな流れを理解できていたりします。

実際の規模感はわからない、巨大に思えるひとつのブラックボックスが、より小さな、案外大したことのないブラックボックスに細分化されていく感じでしょうか。

潜在バグを発見することができた・しやすくなった

JSからTSへの移行は基本的に寝ながらでもできるような作業が大半でした。

が、中には「ほんとにこれで今まで動いてたの!?」と思わせるような型エラーもありました。

そのままだったらまず気付いていなかったでしょうし、何か変更の副作用でバグとして顕在化したとしても原因特定までかなりの時間を要したと思います。

また、導入後のチェック・レビューもしやすくなりました。コードレビューも初心者にとっては基準がよくわからないことが多いかと思いますが、先ほどと同じように「とりあえず引数と返り値の型がちゃんとしているか見よう」と思って始めると、案外指摘点が見つかったりするものです。

デメリット

入れたことによって起きた・起きているダウンサイドはデメリットは、正直特にありません。

なんらかの理由でTypeScriptがオワコンになってJavaScriptに戻すときのコストぐらいでしょうか。

(チームメンバーに文句を言われたら追記します。)

反省

ちゃんとした導入背景・技術説明をチームにしなかった

「なぜ何の為に導入するのか」「導入するなら手順でやるのか」「TypeScriptってなんなのか」といったような説明を、チームに一切せずに進めてしまいました。

TypeScriptは必要になるものではなく、あくまであったらいいものでしかありません。

導入することによっての移行コストや学習コストは払わなければなりません。
そしてさらには、「どこまで徹底するのか」も自由に決めることができるので、チームでのポリシーを決める必要があります。(だからこその過激派)

結果的にペアプロやレビューを通してチーム全体の方針が固まっていきましたが、
きちっと方針を決めてアウトプットするべきでした。

課題

弊チームでのTypeScript運用は@uhyoさんがおっしゃっている「敗北者のTypeScript」です。
anyは移行時にバラまいたものがまだまだありますし、そのほかの「ループホール」を自主規制するポリシーを決め切れてもいません。

ただそれでも導入するだけの価値はあったと個人的には思っています。
これらの課題はこれから、時間をかけて練っていこうと思います。

まとめ

TypeScript導入にはプログラムの理解への手順の取り方を教わりました。
これからはそもそもそれの存在意義、コードの安全性・保守性を意識しながら開発できるように精進したいと思います。
そして新米ながらにも新技術導入を経験させてくれたチームに感謝します。

成果報告のようになってしまいましたが、ここまで読んでいただきありがとうございました。

次回は@YudaiTsukamotoさんの「parcelの時代が中々はじまらないので使いみちを見出してみた」です!お楽しみに!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Web3.jsでイベント購読 前編

Web3.jsでイベントハンドリング 前編

お久しぶりです、アメリカ8泊10日の後にぶっ続けで3泊4日北海道に行ったり気温0度の広島の県北で野宿して死にかけたりしてる旅行ガイジです。

(建前上は)ブロックチェーンエンジニアとしてプログラムを書く日々も早1年半、せっかくのアドベントカレンダーということでまだqiitaに無さそうだったWeb3.js@1.xのイベント購読についてまとめようかなと思った次第です。

全部書くと間に合いそうにない割とボリュームが大きくなりそうだったので前後編に分けます。
前編は基本的なEthereumのスマートコントラクト(以下、コントラクト)のイベントハンドリングになります。

前提

EthereumとかSolidityの初歩的なとこは分かっている前提です。
とりあえずコンソールでもなんでもいいからコントラクトをデプロイしてみたことがあれば多分分かります。

各種モジュールのバージョン
node@12.13.1
web3@1.2.1

今回は裏でganache-cliというモジュールを使ってEthereumチェーン(をエミュレートしたもの)を用意しています。
gagache-cliを使えばコマンド一つでHTTPとWebソケットがご用意されます。便利。
そして、その環境に後述のコントラクトがデプロイされているものとします。

コントラクト

今回使用するコントラクトは以下になります。よくチュートリアルとして使われるSimpleStorageコントラクトのデータ型をmappingにしてイベント処理を突っ込んだだけです。

MappingStorage.sol
pragma solidity ^0.5.11;

contract MappingStorage {
    mapping(uint => uint) private storedValue;

    // mappingにセットしたkey, valueを通知するイベント
    event SetValue(uint key, uint value); 

    constructor() public {
        storedValue[0] = 1;
        emit SetValue(0, 1); // イベントの発火
    }

    function set(uint key, uint value) public {
        storedValue[key] = value;
        emit SetValue(key, value); // イベントの発火
    }

    function get(uint key) public view returns(uint retVal) {
        return storedValue[key];
    }
}

イベントとはなんぞや

コントラクトにおいて変数などのデータはブロックチェーンのストレージ領域に保存されますが、イベントはその中でもトランザクションレシートのログ領域に書き込まれるものになります。
コントラクトというものは外部から隔離された環境で動作するため、コントラクトから何か外部の機能を発火させるということはできません。
しかし、例えば特定の関数が呼ばれたのをトリガーに処理を行う、といった際にはそれでは不都合な訳です。ひたすら繰り返し値を取得し続けて差分を記録するなんて前時代的なやり方はこのご時世にはちょっといただけません。そこで登場するのがイベントです。
イベントを利用することで外部からコントラクトの動作を動的に知ることができるようになります。

前述のコントラクトではコンストラクタとset関数が呼ばれた際にイベントが発火します。

イベントの購読

ここからが本題になります。コントラクトのイベント購読を実例で見ていきましょう。
まず、イベントを購読するスクリプトが以下になります。

eventHandle.js
const Web3 = require('web3');
// HttpProviderでなくWebsocketProviderなので注意
const web3 = new Web3(new Web3.providers.WebsocketProvider("http://localhost:8545"));

(async () => {
    const contractAddress = "コントラクトアドレス";
    const abi = ["コントラクトのABI"];

    const MappingStorage = await new web3.eth.Contract(abi, contractAddress);

    // {}内に色々入れることで購読するイベントをフィルタリングできます、詳しくは後述のドキュメント参照
    MappingStorage.events.SetValue({}, (err, event) => {
        console.log(`event called: ${event.event}`);
        console.log(JSON.stringify(event, null, "    "));
    });
})();

MappingStorage.events.SetValue(...)でイベントの購読を行います。購読するイベントのフィルタリングなんかもできますがここでは省きます。コントラクト内でイベントが発火される(set関数が呼ばれる)と、第2引数にセットしたコールバック関数が呼ばれるようになります。
なお、MappingStorage.events.allEvents(...)とすることでコントラクト内の全イベントが購読対象となります。(今回はSetValueイベントしかありませんが)

上記スクリプトを実行するとイベントの購読待ちとなります。この状態でset関数が呼ばれると、以下のような出力がされます。

出力
event called: SetValue
{
    "logIndex": 0,
    "transactionIndex": 0,
    "transactionHash": "0xa79f032fb7faf9384da2249cc24e0c4fad6ee582fd5715a39db8f665446866f2",
    "blockHash": "0xdabdbac45b7493db64315cde20a1e2635eac54aaa61426d75ec0d0a8fc5d9eee",
    "blockNumber": 8,
    "address": "0xFcac91F1b345bC6E955fDc317fF827950C014142",
    "returnValues": {
        "0": "10",
        "1": "1",
        "key": "10",
        "value": "1"
    },
    "event": "SetValue",
    "signature": "0xf2247e57590b0cc543ff61d1604a3fc00440b206ff3ee22e9a2ff5e5bbdf83da",
    "raw": {
        "data": "0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001",
        "topics": [
            "0xf2247e57590b0cc543ff61d1604a3fc00440b206ff3ee22e9a2ff5e5bbdf83da"
        ]
    }
}

イベント購読のドキュメント

logIndex:ブロック内のイベントインデックス
transactionIndex:ブロック内のトランザクションインデックス
transactionHash:トランザクションハッシュ
blockHash:ブロックハッシュ
blockNumber:ブロック番号
address:イベントが発火されたコントラクトアドレス
returnValues:イベントにセットされたパラメータ、数字キーが自動追加され、event.returnValues[0]のようにアクセスできます
event:イベント名
signature:イベント定義のkeccak256ハッシュです。今回の場合はkeccak256("SetValue(uint,uint)")です
raw.data:イベントのパラメータが格納されたHEX文字列
raw.topics:最大4×32ByteのHEX文字列配列。インデックス0にはシグネチャと同内容が入り、1-3にはインデックス化されたイベントパラメータの内容が格納されます(詳細は省略)

おわりに

色々複雑なブロックチェーンですが、最近は補助モジュールやIDEも登場してずいぶん扱いやすいものになっています。
Web3.jsを使えばEthereumの細かな動きが分からずとも動かせてしまうので、JSの環境構築の容易さも相まってなかなかにお手軽だと思います。

後編では、イベントを利用した効率的なデータ管理について書こうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactくんさぁHooksとか言ってるけど、で?どうやってAPIから値持ってくんの?

はじめに

いなたつアドカレの三日目の記事です。

今回はReactでRestAPIなどから値を取ってくるのに、関数型コンポーネントでどーやってAPIから値取ってくんの?

componentDidMountなんてないよ?

componentDidUpdateなんてないよ?

ってなりますよね、僕はなります。

結論から言ってクレメンス

useEffectを使うンゴ

React Hooks にある、useEffectを使用します。

useEffectってなんぞや

関数コンポーネントで副作用を実行するためのフックで、classコンポーネント時のcomponentDidMount,componentDidUpdate,componentWillUpdateの3つにあたります。

じっそー

useEffect(() => {
// ここでAPIをたたく
},[])

useEffectの第一引数が、実行する関数で、第二引数が何が変化したときにuseEffectを実行するかを決めるものとなっています。
ここでは空配列を指定しています、空配列を指定することでコンポーネントが配置される前(componentDidMount)でのみuseEffectを実行するといったことができます。

useEffect(() => {
// ここでAPIをたたく
},[hoge])

これでhogeが変更された時に反応してuseEffectが実行され、APIを再度叩くことができます。

このようにしてきっちりバックエンドとフロントエンドを分離した際に再描画のタイミングを指定してAPIを再び指定することができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React x Firebase を用いた Web アプリケーション開発時のハマりどころと解決策

概要

これから React x Firebase の構成で Web アプリケーションを開発する方が同じところでハマらないようにハマりどころと解決策をまとめました。

この資料は Firebase Meetup #15 Cloud Functions Day の LT 資料です。

スライドは以下です。
React x Firebase を用いた Web アプリケーション開発時のハマりどころと解決策 - Speaker Deck

1. Hosting と同じドメインで Cloud Functions の関数を呼び出す

Cloud Functions のデフォルトで生成される URL は以下ですが、

https://[region]-[project id].cloudfunctions.net/[function name]

以下の URL で呼び出したいことがあります。

  • https://[project id].web.app/[function name]
  • https://[project id].firebaseapp.com/[function name]
  • https://[独自ドメイン]/[function name]

Hosting の設定ファイルである firebase.json の rewrites の設定をすれば OK です。以下の設定で /notifications にアクセスされた時に Cloud Functions の notifications 関数を実行し、その他のパスにアクセスされた場合は index.html が参照されます。

firebase.json
  "hosting": {
    "rewrites": [
      {
        "source": "/notifications",
        "function": "notifications"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]

Functions は以下のように、通常通り実装すれば良いです。

functions/index.js
exports.notifications = functions
  .https
  .onRequest((req, res) => {
    // ...
  };

2. ローカル環境の React から Emulator の Functions を呼び出す

やりたかったことは以下です。

  • Functions をいちいちデプロイして検証したくない
  • React のホットリロードはそのまま使いたい

そのため、 React は yarn start して Functions は emulator を使って接続します。 Functions をローカルで動作させるために Emulator Suite を使います。

Command
$ firebase emulators:start --only functions
src/index.js
const app = firebase.initializeApp(config)

if (process.env.NODE_ENV === 'development') {
  app.functions().useFunctionsEmulator('http://localhost:5001')
}

3. React と Functions で Firestore の参照先を合わせる

2 の設定を行った場合、 Firestore を利用していると以下のような状態になります。

  • React: Cloud の Firestore を参照
  • Functions: Emulator の Firestore 参照

そのため、Cloud か Emulator か参照先を統一する必要があります。

3-1. Cloud の Firestore を参照する

以下のような構成です。

  • React: yarn start
  • Functions: Emulator
  • Firestore: Cloud

この場合のメリデメは以下のようになります。

  • メリット: 開発時に Firebase console からデータを確認できる
  • デメリット: Cloud のデータが更新されるため、複数人開発には不向き。リリース後は Firebase を複数プロジェクト構成 にするなどの工夫が必要

設定は以下のように行います。Functions のディレクトリで @google-cloud/firestore パッケージをインストールして、 functions/index.js からそのパッケージを利用します。 引数には Firebase の project id を指定します。

Command
$ npm install --save @google-cloud/firestore
functions/index.js
const Firestore = require('@google-cloud/firestore')
const admin = require('firebase-admin')

const db = (() => {
  if (process.env.FUNCTIONS_EMULATOR) {
    return new Firestore({ projectId: 'your project id' })
  } else {
    return admin.firestore()
  }
})()

公式ドキュメントの 認証の開始 ページからサービスアカウントキーを作成してローカルに配置し、 .bashrc や .zshrc などに追記しておきます。

Command
$ export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"

3-2. ローカルの Firestore を参照する

以下のような構成です。

  • React: yarn start
  • Functions: Emulator
  • Firestore: Emulator

この場合のメリデメは以下のようになります。

  • メリット: ローカル環境に閉じてデータを変更できるので複数人開発やリリース後の運用にも向いている
  • デメリット: データの確認が難しい

デメリットとしてデータ確認が難しいとしましたが、これは Firestore CLI などがない(?)ためで、もし良いアクセス方法ご存じの方いたらコメントください。

設定は以下のように行います。公式ドキュメント でもこの方法が紹介されています。

Command
$ firebase emulators:start --only functions,firestore
src/index.js
const app = firebase.initializeApp(config)

if (process.env.NODE_ENV === 'development') {
  app.functions().useFunctionsEmulator('http://localhost:5001')
  db.settings({
    host: "localhost:8081",
    ssl: false
  });
}

4. Hosting から asia-northeast1 region の Functions を呼び出せない

以下のように region 指定をすると Hosting から呼び出せなくなる

functions/index.js
exports.getEvents = functions
  .region(asia-northeast1)
  .https()
  .onCall((data) => {
    // ...
  });

スクリーンショット 2019-12-02 14.49.50.png

公式ドキュメント を見ると

Firebase Hosting は、us-central1 でのみ Cloud Functions をサポートします。

との記述が。。というわけで、Hosting とつなぐ Function は、現状 region 指定しないで運用するしかなさそうです。

以上!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nodejs v12(LTS)におけるasync, awaitを用いたstream処理

nodejs v12(LTS)におけるasync, awaitを用いたstream処理

QualiArts Advent Calendar 2019、3日目の記事になります。

はじめに

2019年10月21日にnodejs v12のLTS版が公開されました。

nodeは奇数バージョンが開発版、偶数バージョンが安定版となるため、v11以降の今まで実プロジェクトだと利用しにくかった機能がこれによりいくつか使えるようになりました。

そのなかでもasync-generatorsやfor-await-of loops構文が大手を振って使えるようになったことにより、stream関連の処理が大きく変わると感じたため、すでにいくつか紹介されている記事も有りますが、この機会に改めて紹介したいと思います。
また、最後に簡単なサンプル処理も記述しているので、ご参考いただければ幸いです。

for-await-of loops

今までstreamはeventEmitterを利用し、発火したeventをトリガーに処理を記述していました。
for-await-of loopsを用いると下記のようにわかりやすくかけるようになります。
for-await-of自体は単純にfor-ofのasyncも利用可能になったものとなります。

for-await-of_loops1.js
const fs = require('fs');

const reader = async (rs) => {
  for await (const chunk of rs) {
    console.log(chunk);
  }
};
(async function () {
  const rs = fs.createReadStream('./input.txt', { encoding: 'utf8' });
  await reader(rs);
})();
input.txt
abcde
fghij
klmno
pqrxy
z

実行結果

terminal
abcde
fghij
klmno
pqrxy
z

従来だと、下記のように終了イベントをpromise化するなどで、全体のpromise化などは簡単にできますが、イテレータ内部のchunk単位でpromise化を行う場合非常に可読性が悪くなってしまっていました。
(v10以降であれば下記のようにstream.finishedをpromisifyすることで全体のpromise化は簡略可能です)

これが上記のように簡単に記述できるようになったのは非常にやりやすくなったと感じます。

for-await-of_loops2.js
const fs = require('fs');
const stream = require('stream');
const util = require('util');

const finished = util.promisify(stream.finished);
const msleep = util.promisify(setTimeout);

// streamを用いた場合の処理(全体終了部分のみのpromise化)
const reader1 = async (rs) => {
  rs.on('data', (chunk) => {
    console.log(chunk);
  });

  await finished(rs);
  // stream.finishedを使わない場合下記のようなpromiseを生成する
  // await new Promise((resolve, reject) => {
  //   rs.once('finished', (chunk) => {
  //     return resolve(data);
  //   });
  //   rs.once('error', (err) => {
  //     return reject(err);
  //   });
  // });
};

// streamを用いた場合の処理(chunk単位でのpromise化)
const reader2 = async (rs, iterator) => {
  let buffer = '';
  rs.on('data', (chunk) => {
    buffer = chunk;
    rs.pause();
  });

  let isEnd = false;
  rs.once('end', () => {
    isEnd = true;
  });
  let error;
  rs.once('error', (err) => {
    error = err;
  });

  while (true) {
    if (error) {
      throw error;
    }
    if (buffer) {
      await iterator(buffer);
      buffer = '';
    } else if (isEnd) {
      return;
    } else if (rs.isPaused()) {
      rs.resume();
    }
    // 非同期メソッドがないと無限ループしてしまうため
    await msleep(0);
  }
}

(async function () {
  const rs1 = fs.createReadStream('./input.txt', { encoding: 'utf8' });
  await reader1(rs1);
  const rs2 = fs.createReadStream('./input.txt', { encoding: 'utf8' });
  await reader2(rs2, async (chunk) => {
    console.log(chunk);
  });
})();

実行結果

terminal
abcde
fghij
klmno
pqrxy
z
abcde
fghij
klmno
pqrxy
z

async-generators

今までは同期メソッドでしか使えなかったyieldがasyncにも対応しました。
async function*でasyncIteratorのジェネレータメソッドを生成でき、await対応したnextメソッドを呼び出すことができます。
(nextで呼び出した場合返り値はObjectになります)

async-generators1.js
const util = require('util');
const msleep = util.promisify(setTimeout);

async function* generate() {
  for (let i = 1; i <= 3; i++) {
    await msleep(1000);
    yield i;
  }
}

const asyncIterator = generate();
(async () => {
  console.log(await asyncIterator.next());
  console.log(await asyncIterator.next());
  console.log(await asyncIterator.next());
  console.log(await asyncIterator.next());
})();

実行結果

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

こちらはfor-await-of loopsも利用可能です。
こちらを利用すると簡単にラグのあるstreamデータの生成が可能になります。

async-generators2.js
const util = require('util');
const msleep = util.promisify(setTimeout);

async function* generate() {
  for (let i = 1; i <= 3; i++) {
    await msleep(1000);
    yield i;
  }
}

const asyncIterator = generate();
(async () => {
  for await (const v of asyncIterator) {
    console.log(v);
  }
})();

実行結果

1
2
3

行ごとにawait処理を行うサンプル

上記の機能が実装されたことで、行ごとのように一定windowずつstreamで非同期メソッドを実行する処理が非常に簡単にかけるようになりました。
下記は得られたstreamを、行ごとに非同期メソッドを実行する場合のサンプルになります。
可読性重視&行単位でeventループが回るため、パフォーマンスがシビアな場合は別途実装することをおすすめします。

readlineモジュールを利用した場合

line-reader1.js
const fs = require('fs');
const readline = require('readline');
const util = require('util');

const msleep = util.promisify(setTimeout);

const asyncLineReader = async (iterater) => {
  const rl = readline.createInterface({
    input: fs.createReadStream('input.txt', { encoding: 'utf8' }),
    crlfDelay: Infinity
  });

  for await (const line of rl) {
    await iterater(line);
  }
}

(async () => {
  await asyncLineReader(async (line) => {
    await msleep(100);
    console.log(line);
  });
})();

実行結果

terminal
abcde
fghij
klmno
pqrxy
z

解説

こちらはreadlineモジュールを利用したものになります。
以前も行ごとに処理を行えたのですが、非同期メソッドの実行はできませんでした。
v1.12からasyncIteratorに対応したことで、上記のように簡単に非同期メソッドが実行できるようになりました。

stream(for-await-of利用)

line-reader2.js
const fs = require('fs');
const util = require('util');

const msleep = util.promisify(setTimeout);

// streamを用いた場合の処理(for-await-of使用)
const asyncLineReader = async (iterater) => {
  const rs = fs.createReadStream('./input.txt', { encoding: 'utf8' });

  let buffer = '';
  for await (const chunk of rs) {
    buffer += chunk;
    const list = buffer.split('\n');
    // 最後の要素は改行が含まれているわけではないため、bufferに戻す
    buffer = list.pop();
    for (let i = 0; i < list.length; i++) {
      await iterater(list[i]);
    }
  }
  if (buffer) {
    // 終了時にbufferに残っている文字列もiteratorにわたす
    await iterater(buffer);
  }
}

(async () => {
  await asyncLineReader(async (line) => {
    await msleep(100);
    console.log(line);
  });
})();

実行結果

terminal
abcde
fghij
klmno
pqrxy
z

解説

こちらはstreamのfor-await-ofを利用したものになります。
イベントループの回数が他と比べて半分以下なので、このなかでは一番パフォーマンスが良いです。
実装を合わせるために1行ずつ処理していますが、こちらで複数行制御してlistをiteratorに渡すような実装が実利用だと良いかもしれません。
比較的簡単に可読性良く記述できるようになっているかと思います。

stream(for-await-of未使用)

line-reader2.js
const fs = require('fs');
const util = require('util');

const msleep = util.promisify(setTimeout);

// streamを用いた場合の処理(for-await-of未使用)
const asyncLineReader = async (iterator) => {
  const rs = fs.createReadStream('./input.txt', { encoding: 'utf8' });

  let buffer = '';
  let rows = [];
  rs.on('data', (chunk) => {
    buffer += chunk;
    const list = buffer.split('\n');
    buffer = list.pop();
    if (list.length) {
      rows.push(...list);
      rs.pause();
    }
  });

  let isEnd = false;
  rs.once('end', () => {
    isEnd = true;
  });
  let error;
  rs.once('error', (err) => {
    error = err;
  });

  while (true) {
    if (error) {
      // errorがあれば終了
      throw error;
    }

    if (rows.length) {
      for (let i = 0; i < rows.length; i++) {
        await iterator(rows[i]);
      }
      rows = [];
    } else if (isEnd) {
      if (buffer) {
        // 終了時にbufferに残っている文字列もiteratorにわたす
        await iterator(buffer);
      }
      return;
    } else if (rs.isPaused()) {
      rs.resume();
    }
    // 非同期メソッドがないと無限ループしてしまうため、setImmediate代わりに実行
    await msleep(0);
  }
}

(async () => {
  await asyncLineReader(async (line) => {
    await msleep(100);
    console.log(line);
  });
})();

実行結果

terminal
abcde
fghij
klmno
pqrxy
z

解説

こちらはstreamのfor-await-ofの未使用版になります。
かなり複雑になり可読性も落ちている事がわかります。
ただ、async, awaitが対応しているv8以降であれば利用可能なため、nodeのバージョン次第では利用できるかもしれません。

まとめ

これらの機能の追加により、async, awaitを用いたstream処理が非常に簡潔に記述できるようになりました。
v1.12LTSに上げることで非常にstream周りが記述しやすくなっているため、この機会にぜひ試してみてはどうでしょうか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reduceを使った集計データへの変換

概要

配列の形で持っているデータをグループごとに集計したデータの形に変換したときの話。

こんなデータを

グループ 目標 実績
A 100 200
A 100 50
B 50 100

こんな形に集計する

グループ 目標 実績
A 200 250
B 50 100

詳細

まずはreduceで呼び出す、集計処理の関数
hasOwnProperty でマップに対象キーのデータが登録済みかどうかで判断している。

const convertDataMap = function(dataMap, d) {
  const key = d.group1;
  //const key = `${d.group1}-${d.group2}`; // keyの作り方次第で複合キーでの集計も可能
  if (dataMap.hasOwnProperty(key)) {
    const tmp = Object.assign({}, dataMap[key]);
    dataMap[key] = {
      target: tmp.target + d.target,
      result: tmp.result + d.result
    }
  } else {
    dataMap[key] = {
      target: d.target,
      result: d.result
    }
  }
  return dataMap;
}

上記の関数を呼び出す部分
reduce 内でさっきの関数を呼びだす。

const data = [
  { "group1":"A","group2":"C", "target":100, "result":50 },
  { "group1":"A","group2":"D", "target":50, "result":50 },
  { "group1":"A","group2":"D", "target":10, "result":100 },
  { "group1":"B","group2":"D", "target":100, "result":50 },
  { "group1":"B","group2":"C", "target":50, "result":50 },
  { "group1":"B","group2":"C", "target":10, "result":100 },
]

const convertMap = data.reduce((dataMap, d) => {
  return convertDataMap(dataMap, d);
}, {});

作成後のデータの形はこんな感じ

変換後データ
{
  "A": {
    "target": 160,
    "result": 200
  },
  "B": {
    "target": 160,
    "result": 200
  }
}

データを配列の形にしたい場合は、以下のような処理をしてあげると

const _data = [];
_data.push(
 ...Object.entries(convertMap).map(([key, value]) =>({key, value}))
);

配列になる(value の部分もフラットにしたい場合は、上記の関数のmap内の処理を書き換えてください)

変換後データ
[
  {
    "key": "A",
    "value": {
      "target": 160,
      "result": 200
    }
  },
  {
    "key": "B",
    "value": {
      "target": 160,
      "result": 200
    }
  }
]

まとめ

サーバー側で集計まで処理して返すことは結構あったが、JavaScriptでやるのは何気に初めてだった。

画面側で集計処理できれば、集計キーが複数あった場合の階層を変更してもリクエスト投げずに処理できてしまうので、今後も使いどころはあるかも。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

git add -pで分割コミットしようとするとlint-stagedでエラーになる

症状

git add -pなどして「partially staged」した状態からgit commit経由でlint-stagedをすると、エラーになった。lint-stagedのバージョンは9.2.3で確認

$ git commit
husky > pre-commit (node v11.13.0)
Stashing changes... [started]
Stashing changes... [completed]
Running tasks... [started]
Running tasks for *.js [started]
eslint --fix [started]
eslint --fix [completed]
git add [started]
git add [completed]
Running tasks for *.js [completed]
Running tasks... [completed]
Updating stash... [started]
Updating stash... [completed]
Restoring local changes... [started]
Restoring local changes... [failed]
→ Command failed with exit code 128 (Unknown system error -128): git checkout-index -a
Command failed with exit code 128 (Unknown system error -128): git checkout-index -af
husky > pre-commit hook failed (add --no-verify to bypass)

これが起こると、lintが異常終了していて以下の状態になる。

  • add -pして分け分けしたdiffを持つファイルが全部add状態になる
  • 一部のファイルがworkdirからなくなる (たまたま作業中のファイルに当たると悲惨?)

原因と解決策

別端末で自動ファイル更新検知と再ビルドが走っていると、それと競合することが原因の模様。npm run serve経由で開発用httpサーバーを動かしていた。

解決策はシンプルに開発用httpサーバーを都度止めてgit操作をする。ちょっと面倒だがとりあえずデータロストの心配はなくなる。

参考 & 解決策ではなかったもの

  • そもそもpartially stagedなdiffをlint-stagedが扱えるようになったのはver.8以降その基本アリゴリズムはこちらの通り
  • partially stagedなdiffとlint-stagedの組み合わせに関するバグがlint-staged@9.5.0修正されているそうなので、それにアップデートしてみたが、本投稿の件は解決しなかった。よく見るとエラーメッセージが違う。ちなみにこのバグは本投稿の事象のエラーメッセージ中のgit checkout-index -afで検索をかけて発見した。
  • エラーメッセージ中には--no-verifyしろと書いてあるが、それをやると当然lintが通らないので回避は可能だが解決策にはならない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

p5.js - SVG文字列を読み込んで表示する

動機

p5.jsライブラリを使い、SVG文字列を配置できるようにしたい

実行環境

実行結果

sample.png

サンプルコード

let svgString = `<svg> .. 虎のSVG文字列..</svg>`
let img;

function convertBase64EncodedSvg(svgString) {
 return "data:image/svg+xml;base64," + btoa(svgString)
}

function preload() {
  img = loadImage(convertBase64EncodedSvg(svgString))
}

function setup() {
  createCanvas(512, 512)
}

function draw() {
  background(220)
  image(img, 0, 0, 512, 512)
}

コード解説

1.SVG文字列を用意する

let svgString = `<svg> .. 虎のSVG文字列..</svg>`

ネット上によくある虎の絵をサンプルとして準備。(参考: ファイル:Ghostscript Tiger.svg)

そのまま使用すると画質が荒くなった。
svgタグにwidth,height属性を追記した結果綺麗になった。
(<svg .. width="900" height="900"...>)

2.SVGのBase64変換処理を定義

function convertBase64EncodedSvg(svgString) {
 return "data:image/svg+xml;base64," + btoa(svgString)
}

サンプルのため、SVGのみで定義。
PNGやJPGなどの分岐をもたせて汎用性を高くしておくといいと思う。

Base64画像変換も簡略化。
encodeURIComponentなどで各種文字に対応するなどの検討余地はあり。

3. p5.jsで読み込んで実行

let img;

画像の変数を定義。

function preload() {
  img = loadImage(convertBase64EncodedSvg(svgString))
}

事前読み込み処理でBase64変換したSVG文字列をp5.jsのloadImage関数で読み込む。
こうすることでSVG画像をimageとして扱うことが可能になる。

function setup() {
  createCanvas(512, 512)
}

function draw() {
  background(220)
  image(img, 0, 0, 512, 512)
}

あとはp5.jsのサンプルコードによくある処理。
imageを配置。(便宜的に512pxに設定)

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【CakePHP2】formタグのスコープ外からformの内容をcontrollerに送信

やりたいこと

formタグの外にあるボタンやリンクでもcontrollerに値を渡したい

やったこと

test.ctp
                            <?= $this->Form->create('HogeList', ['type' => 'GET']); ?>
                            <div class="searchInputArea" style="margin-bottom: 0px;margin-top: 0px;">
                                <table class="em9">
                                    <tr>
                                        <th>
                                            ID
                                        </th>
                                        <td>
                                            <?php
                                                echo $this->Form->input('id', [
                                                    'type'     => 'text',
                                                    'id'       => 'item_id',
                                                    'size'     => 30,
                                                    'label'    => false,
                                                    'div'      => false,
                                                    'empty'    => true,
                                                    'required' => false,
                                                ]);
                                            ?>
                                        </td>
                                    </tr>
                                    <tr>
                                        <th>
                                            名前
                                        </th>
                                        <td>
                                            <?php
                                                echo $this->Form->input('name', [
                                                    'id'       => 'name',
                                                    'type'     => 'text',
                                                    'size'     => 30,
                                                    'label'    => false,
                                                    'div'      => false,
                                                    'empty'    => true,
                                                    'required' => false,
                                                ]);
                                            ?>
                                        </td>
                                    </tr>
                                    <tr>
                                        <th>
                                            ステータス有効のみ
                                        </th>
                                        <td>
                                            <?php
                                                echo $this->Form->input('status', [
                                                    'type'  => 'checkbox',
                                                    'div'   => false,
                                                    'value' => TestAppModel::STATUS_ACTIVE,
                                                    'label' => '',
                                                ]);
                                            ?>
                                        </td>
                                    </tr>
                                </table>
                            </div>
                            <div class="submitArea hasSubAction">
                                <ul class="submitButtons">
                                    <li>
                                        <label class="submitBase primary" for="submit">
                                            <?php echo $this->Form->hidden('mode', ['value'=> '']); ?>
                                            <?php
                                                echo $this->Form->submit('検索', [
                                                    'type'  => 'submit',
                                                    'id'    => 'submit',
                                                    'div'   => false,
                                                    'label' => false
                                                ]);
                                            ?>検索
                                        </label>
                                    </li>
                                </ul>
                            </div>
                            <?php echo $this->Form->end(); ?>
                        </div>
test.ctp
        <!--formの外に追加-->
        <?php echo $this->Html->link('formの値を送信した使いたいリンク', 'javascript:clickOutOfFormLink();void(0)', ['class' => 'actionBtnBase hogeListOutput']); ?>

<!--jQuery部分-->
<script>
    // スコープ外から検索ボタンを押下
    function clickOutOfFormLink() {
        // 検索機能をform外の処理に切り替えるためhiddenを設定
        $('#HogeListMode').val('hogehoge');

        document.getElementById('submit').click();

        // 検索機能に戻すためhiddenをクリア
        $('#HogeListMode').val('');
    }
</script>

結果

TestController.php
    /**
     * 一覧
     *
     * @return void
     */
    public function index(): void {
        if (Hash::get($this->request->query, 'mode') === 'hoge') {
            // modeがhogeである場合はform外のリンクが押下されている
            echo 'hoge';

            // 入力値も使用可能?
            var_dump($this->request->query);
        }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsのCLIツールをカスタマイズ可能にする

Node.jsで何らかのCLIツールを作っているとします。

おそらくどのツールでも、オプショナルな機能を使うかどうかは、エンドユーザーに決めてもらいたいでしょう。その場合、-iとか-aのようにコマンドの引数をいくつか提供するのもいいですが、複雑な設定が必要になりそうだったら、設定ファイルでのカスタマイズを可能にした方がいいです。

設定ファイル

ファイル名

設定ファイルの名前は、ツールの開発者が決めないといけないのですが、ほとんどのプロジェクトでは、{tool}.config.jsという風に決まっています。たとえば、webpackだったら、webpack.config.jsというファイルで設定できます。

設定ファイルの検出

最近の多くのプロジェクトでは、設定を全てオプショナルにするトレンドがあります。もし設定ファイルが存在しなければ、デフォルトの値で動きます。設定ファイルが存在するかどうか、Node.jsのfsモジュールにあるexistsSyncという関数を使えばわかります。

existsSyncはダイレクトリーかファイルのパスを取るが、CLIツールのソースコードと、ユーザーのプロジェクトのルートはそれぞれ違う場所にあるので、ユーザーがCLIを呼んだダイレクトリーを検出しないといけないです。それはprocess.cwd()でできます。

const path = require('path');
const fs = require('fs');

const cwd = process.cwd();
const configPath = path.join(cwd, 'tool.config.json');

if (fs.existsSync(configPath)) {
  // 設定を読み込む
}

読み込み

これは設定ファイルの形式によりますが、多くのプロジェクトではJSONを使っています。JSONを読み込むには、fsにあるreadFileSyncでファイルを読み込み、その中身をJSON.parseします。

if (fs.existsSync(configPath)) {
  const rawConfig = fs.readFileSync(configPath);
  const config = JSON.parse(rawConfig);
}

fsの拡張であるfs-extraというライブラリには、fs.readJSONSyncという関数があるので、それを使うと一気にJSONのファイルが読み込めます。

もちろん、requireだけで読み込むこともできますし、jsonにもjsにも使えます。

if (fs.existsSync(configPath)) {
  const config = require(configPath);
}

ただし、一回requireされたものは、Node.jsプログラムのライフタイムに渡ってキャッシュされるので、CLIにはwatchのような機能を入れたい場合は、注意が必要です。設定ファイルが変更されても、たとえば2回目のビルドでキャッシュされた古いものがrequireされてしまいます。そういう問題を避けるためにfs-extraなどを使った方がいいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HandsonTableのドキュメントを見るときの注意点

HandsonTableとは

HandsonTableは、JavaScriptのEXCELのようなグリッドコンポーネントです。
https://handsontable.com/

ドキュメントを見るときの注意点

Handsontableを使う際に、公式のドキュメントを見ると思います。
React、Angular、Vue.jsのサンプルも書いてあります。
こちらを見る際に、知っておかないと困ることがあります。

コメント 2019-12-03 155207.png

サンプルページまでは、簡単に行けます。

コメント 2019-12-03 155853.png

このサンプルページを見てソースコードを読んでいると、
「あれ、vueコンポーネントを呼び出しているところはどこだ?」となると思います。

コメント 2019-12-03 155853.png

上記のEditボタンを押してください!!!

キャプチャ.PNG

そうするとすべてのソースコードが見えます!!!
当たり前のようですが、気づくのにしばらくかかり困りました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gmailの添付ファイルを自動でGoogle Driveに保存 をGAS(Google Apps Script)で実装

初投稿です。

やりたいこと

Gmailで条件検索したメールの添付ファイルを、Google Driveに自動で保存

他担当者が、取引先からの請求書を都度印刷→経理ソフトで入力していましたが、印刷を省いてPDFファイルを表示しながら入力する環境を目指すため、Gmailのメールを添付ファイル(請求書はPDFですが今回は添付ファイルななんでも保存)をGoogle Driveに保存するスクリプトを実装しました。
新規性に乏しい無い内容ですが、初投稿ということと、多少つまづいた点を含め、記載しておきます。

実装の流れ

  1. コーディング:Gmailを条件検索(今回は from,日付,添付あり を条件)し、検索結果を取得
  2. コーディング:検索結果から添付をGoogle Driveへ順次保存
  3. 実行トリガー設定:時間をトリガーとして1日に1回実行する

コード以下を参照させていただきました。ほぼ丸々コピー。
テレビ局で働く独学プログラマー Gmailの添付ファイルをgoogle driveに保存する

1. Gmailを条件で検索(今回は from,日付,添付あり を条件)し、検索結果を取得

SaveGmailAttachToDrive.gs
  //メール検索用の日付計算
  var now = new Date();
  var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); //こうすることで0時0分0秒となる
  var yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);  
  var strYesterday = Utilities.formatDate(yesterday, "JST", "yyyy/MM/dd");

  //メール検索キーワードの作成
  var condition = '';
//  condition  += ' is:unread'  //未読
//  condition += ' subject:添付やで';  //件名
  condition += ' has:attachment';  //添付あり
  condition += ' after:' + strYesterday;  //今日(昨日より後)
  condition += ' from:' + 'メールアドレス'; //差出人

  //Gmailで検索
  var searchMail = GmailApp.search(condition);
  var messeges = GmailApp.getMessagesForThreads(searchMail); //検索結果を配列で取得

2. Gmailを条件で検索(今回は from,日付,添付あり を条件)し、検索結果を取得

メールの検索結果の配列は2次元の為、2重ループで取り出します

SaveGmailAttachToDrive.gs
  var hozon_folder = DriveApp.getFolderById('GoogleDriveのFolderID');
  for(var i = 0; i < messeges.length; i++) { //検索結果を一つずつ取り出す
    for(var j = 0; j < messeges[i].length; j++) { //スレッドの場合ここを複数回実行
      var attach = messeges[i][j].getAttachments();
      var day = messeges[i][j].getDate(); //取り出したメールの日付を取得
      var strDay = Utilities.formatDate(day , "JST", "yyyy_MM_dd");
      if (day > today){ //★★メールが今日以降か再度チェック
        for(var k = 0; k < attach.length; k++){
          var filename = strDay + '_' + attach[k].getName();
          hozon_folder.createFile(attach[k]);
          var file = hozon_folder.getFilesByName(attach[k].getName())
          file.next().setName(filename);
        }
      }
    }
    messeges[i][0].markRead();
  }

はまったポイント

メールの検索結果はメール単位でなく、スレッド単位でした。
つまり、1.の処理で今日のメールのみ取得していると思いきや、以下画像ように、Gmailの検索結果で数字が表示される場合は、スレッド内に複数のメールが含まれており、今日より以前のメールも含まれている可能性が高い。
Gmail検索結果スレッド

ということで、スレッドからメッセージを取り出した後も日付を判定する処理を加えてます。

3. 実行トリガー設定:時間をトリガーとして1日に1回実行する

実行日の添付ファイルをチェックするので、23~24時で毎日実行でセットしました。
トリガーの設定は以下の参照ください
GAS ビギナーが GAS を使いこなすために知るべきこと 10 選

おわり

Google Driveに保存したファイルは、Google Drive↔Synology NAS Cloud Sync↔DropBoxでDropbBox同期させ他ユーザも閲覧可能にしています。
Gmail→DropBox直接保存も考えましたが、以下消去法により今回の実装をしました。

  • IFTTT Gmailをトリガーとした連携が終了していた
  • zaiper 有料範囲があるから面倒、手を出したくない
  • MS Flow:実装可能が、サービスの継続が不安
  • DropBox APIの利用:DropBoxでtokenを取得する際、DropBox内の既存のフォルダを対象としたAPIを利用する場合、無駄にAPI使われないかDropBox側でチェック入るっぽい(参照)。 今回は既存のフォルダを使うつもりだったので無し。

初投稿でした。他にもGASで色々実装しているので、その周り寄稿できればと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SweetAlert2(swal2)のcss適応方法〜ゴリ押しでできてしまった?〜

はじめに

こんにちは。
記事を書くのは何ヶ月ぶりだろうか…。
最近、SweetAlert2使ってるんだけど、毎回調べてる気がするから備忘録兼記事として。

やりたいこと

・Swalのコンテナにボーダーをつけたい!
・タイトルの文字色などを変えたい!
etc...

1.そもそもSweetAlertってなんやねんって話

SweetAlert2.png
SweetAlert2

SweetAlertというのは、とある方(多分Limon Monte氏)によって開発されたJSのライブラリである。
主な機能としては、JSで出すアラートを綺麗にカッコよく可愛く装飾、表示してくれるライブラリである。
売りはそれだけじゃなくて、綺麗以外にもレスポンシブに対応していたり、使いやすかったり、カスタマイズが簡単だったり。

See the Pen mdydaBJ by 至極色のひかる (@sigoku14) on CodePen.

とこんな感じで、簡単にアラートをデコレーションすることができます!

2.どうやってcss弄るの?(基本編)

これはライブラリ側でほとんどやってくれているようなもの。
ドキュメントを読めば大体のことは書いてくれている。

その上、下みたいにクラスを追加してアニメーションや微細なところまでいじれるようにしてくれているのだ!

customClass: 'customizable'

とか、

customClass: {
  container: 'container-class',
  popup: 'popup-class',
  header: 'header-class',
  title: 'title-class',
  closeButton: 'close-button-class',
  icon: 'icon-class',
  image: 'image-class',
  content: 'content-class',
  input: 'input-class',
  actions: 'actions-class',
  confirmButton: 'confirm-button-class',
  cancelButton: 'cancel-button-class',
  footer: 'footer-class'
}

みたいな感じで、アラート自体にクラスを付与することができ、cssを記入してあげれば動かすことができます。

そもそもこのcustomClassというのが強いのですが、なぜか二つ目の書き方をすると、classが効かないという。
多分、僕の問題なんだろうけど、調べて解決するつもりが、何となくでやったらできてしまったので、それを綴ります。

3.どうやってcss弄るの?(class分解編)

まず、Swal2のclassがどうなっているか調べましょう。
version 9.4.1時点での構成を見てみましょう。

swal2.png

まぁ、これ見れば大体のことはわかるんだけど、正直読むのめんどくさいわ!!
っていう人が大半でしょう。

  • swal2-container
    • swal2-title
      • swal2-header
        • swal2-icon とか swal2-progress-steps
      • swal2-content
        • swal2-html-container とか swal2-range とか swal2-check
      • swal2-actions
      • swal2-footer
      • swal2-timer-progress-bar

と、分解したらこんな感じ(かなり端折ってはいますが)。
なら、これに書き込めば良いじゃん!
初心者の私はそう思いましたが、もちろん動くはずもなく...。
では、どうしたか。

4.どうやってcssを弄るの?(ゴリ押し編)

多分、様々なところに応用できるとは思うので、今回は自分が解決したかったことを中心に書いていく。

1.Swalのコンテナにボーダーをつけたい!

→解決法
まず、

customClass: 'customizable'

のような感じで、アラート自体にclassを付与します。
で、次はcssに

div.customizable{
    border: 3px solid black !important;
}

と記述します。
すると、class名がかかっている要素にcssをかけることができます。ただ、それだけでは動かずcssに

!important

を追加してあげる必要があります。
これにより、優先度を上げることができる!

2.タイトルの文字色などを変えたい!

→解決法

先ほどと、解決法にさほど変わりはないが、
付与したclassの階層下の要素がどうなっているかをよく見ましょう。

すると、 h2 とか、label とかが存在しています。
それらにcssを直に付与すれば良いわけです。

.customizable h2{
    color: red !important;
}

と今回も同様に

!important

は必要です。

5.〆

わかったことは、cssは割とゴリ押しでもどうにかなる。
そして、このやり方はだいぶキモイ。

正規のやり方があるなら、正規のやり方でやりたい。
でも、英語しかなくてわからない。

つまり、英語を翻訳した方が良さそうということがわかった。
最近、英語の翻訳系ばっかやってる気がするw

長々としょうもない備忘録にお付き合いいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

俺の正規表現は長い

この記事は株式会社ACCESS Advent Calendar 2019の7日目の記事です。が、内容は個人的なものです。

まえがき

皆さん正規表現は好きですか?僕は嫌いです。

そんなある日、「7の倍数」を表す正規表現 という記事を見つけました。
ちょっと分かりづらかったですが、「7の倍数」という言語を受理するDFAを作り、そこから機械的に正規表現を作っていったわけですね。

DFA -> 正規表現 の変換は StackExchange のこの質問ページの図で分かりやすく表現されています。

しかし、なんとこの正規表現、期待通りに動いてくれません。Chrome DevTool で (問題の正規表現).test('7') を評価すると false になってしまいました。

正規表現自体は正しいはずなのに。

僕がいつも使ってる Elixir くんならうまくやってくれるはず…

$ iex
Erlang/OTP 20 [erts-9.3.3.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> ~R/\A((...中略...))+\z/ \
...(1)> |> Regex.match?("7")
** (Regex.CompileError) regular expression is too large at position 19647
    (elixir) lib/regex.ex:209: Regex.compile!/2
    (elixir) expanding macro: Kernel.sigil_R/2
    iex:1: (file)
    (elixir) expanding macro: Kernel.|>/2
    iex:1: (file)

regular expression is too large と悲鳴を上げて死んでしまいました。

JavaScriptでダメだったのも実は正規表現が長すぎたためかもしれません。

こうなると悪戯心に火が付きます。

正規表現の長さの限界は?

僕の検索力が足りず、明確にいくつみたいなのが書いてあるドキュメントが見つかりませんでした。
(Elixir -> Erlang の re -> PCRE という依存までは見えましたがPCREでの制限がどうなってるのかよく分からず…)

なのでテキトーに二分探索してみます。結果は環境に依存する可能性があります。

JavaScript

const testN = (n) => {
  const source = Array(n).fill('a').join``;
  try {
    return new RegExp(source).test(source);
  } catch(_) {
    return false;
  }
};

testN(50000);
// false

const tryAndContinue = (previousN, currentN) => {
  const diff = Math.abs(currentN - previousN);
  if (diff <= 1) {
    return [previousN, currentN];
  }
  const nextN = currentN + (testN(currentN) ? 1 : -1) * (diff >> 1);
  return () => tryAndContinue(currentN, nextN);
}

let continuation = tryAndContinue(50000, 25000);

while(typeof continuation === 'function') {
  continuation = continuation();
}

continuation
// (2) [32765, 32766]

testN(32766);
// true
testN(32767);
// true
testN(32768);
// false

a を 32768個並べるだけでアウト、ということが分かりました。

Elixir

test_n = fn n ->
  source = String.duplicate("a", n)
  case Regex.compile(source) do
    {:ok, regex} -> Regex.match?(regex, source)
    _            -> false
  end
end

Stream.unfold({50000, 25000}, fn {previous_n, current_n} ->
  diff = abs(current_n - previous_n)
  cond do
    diff <= 1 ->
      nil
    test_n.(current_n) ->
      next_n = current_n + div(diff, 2)
      {next_n, {current_n, next_n}}
    :else ->
      next_n = current_n - div(diff, 2)
      {next_n, {current_n, next_n}}
  end
end) \
|> Enum.take(-3)
# => [32768, 32765, 32764]

test_n.(32764)
# => true

こちらは a を 32765個並べるとアウト。JavaScriptに近いですがちょっとだけ違いがありますね。

もっと短く壊す

((((((((((a)))))))))) みたいな感じで () を多用したりして複雑にすると上限がもっと短くなります。
最初の正規表現は () だけでなく |[*+ も使っているのでダメだった感じですね。

みなさんもぜひお気に入りの言語で試してみてください。


QAの人に前後を挟まれたためか、奇しくも「コーナーケースのテスト」みたいな話になりました。明日の記事は @illypong さんが数多のバグをワンパンしてくれそうな雰囲気です。お楽しみに!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】すでに同じフォルダ内に同一ファイル名が存在した時、連番をふって命名する

Driveは最近、ファイル自体をIDで管理するようになったので、ファイル名自体の重複を気にしなくなった。ひとえに不便なので機能を @acro5piano と協力して作成した。

function fileExists(files) {
  return files.hasNext()
}

function getNextFileNameFromOutputFolder(outputFolder, fileName, index) {
  index = index || 0
  var maybeFileName = index === 0
    ? fileName 
    : fileName + "" + index + ""
  if (fileExists(outputFolder.getFilesByName(maybeFileName))) {
    return getNextFileNameFromOutputFolder(outputFolder, fileName, index + 1)
  }
  return maybeFileName
}

function incrementFileNumber(OutputFileName, folderId) {
  var OutputFolder = DriveApp.getFolderById(folderId);
  return getNextFileNameFromOutputFolder(OutputFolder, OutputFileName)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SPAってページ遷移するの?

概要

  • SPAにおけるルーティングについて簡単に紹介します
  • SPAを作ったことがない人が対象読者です
  • 知ってる人からすると、そんな当たり前なことを・・と思うかもしれません

SPAとは?

  • Single Page Applicationの略です
  • 対義語はMultiple Page Applicationです
  • つまり単一ページで構成されるのがSPAで、複数ページで構成されるのがMPAです
  • SPAは最初に画面(HTML)を取得した後は、JavaScriptによって画面の書き換えを行うことで単一ページでありながらアプリケーションを構築することができます
  • これは言い換えるとSPAはページ遷移しないということ?本当にそうでしょうか?

SPAはページ遷移しない?

SPA

spa.gif

MPA

mpa.gif

  • どちらのページもリンクをクリックするとURLが切り替わってページ遷移しています
    • どういうことでしょう???

SPAにおけるページ遷移

URLの書き換え

  • SPAはJavaScriptで画面を操作することになりますが、実はアドレスバーのURLはJavaScriptで操作できるんです
    • ブラウザにはHistoryAPIというJavaScriptのAPIが実装されており、それらを使うことでブラウザの履歴を操作できます
    • アドレスバーのURLはhistory.pushState()で書き換えられます
  • 試しに開発者ツールで実行してみた例

pushstate.gif

  • 実行するとURLが書き換わるのが分かると思います
    • ブラウザのHistoryを操作しているためブラウザバックにも対応します
    • この関数はあくまでURLを書き換えるだけなので画面に表示されている内容に変化はありません

画面の書き換え

  • SPAにおける画面の書き換えは、「ボタンをクリックしたこと」や「通信が完了したこと」などのイベントを検知して処理を実行します
  • 「URLが書き換わったこと」もイベントの1つに過ぎず、それを検知して画面全体を書き換えているというわけです
  • 実際の開発ではReact RouterVue Routerといったライブラリを用いることが多いです
    • どのURLに変更されたらどのコンポーネントを表示するかマッピングしておくことで、MPAの画面遷移のように扱うことができます

まとめ

  • SPAってページ遷移するの?
    • ユーザ視点で見た場合、MPAと同じようにページ遷移することができます
  • SPAってどうやってページ遷移してるの?
    • ブラウザのHistoryをJavaScriptで操作することでURLを書き換えることができます
    • URLの更新を検知して画面を書き換えることでページ遷移を表現しています

余談

  • 途中紹介したSPAとMPAのサンプルは全く同じ挙動をするわけではありません
  • SPAは/homeなどでリロードすると404(Not Found)となってしまいます

404.gif

  • 仕組みを考えるとあたりまえで、サーバ上には初回アクセス時に取得する/index.htmlしかなく/home.htmlなどは存在しないためです
  • 404を出さないためには、SPAを配信するホスティング側の設定で404が出た場合に/index.htmlにforwardしてあげればOKです
  • 今回サンプルで使っているNetlifyでは_redirectsというファイルを作成し以下のように設定すればOKです
_redirects
/*    /index.html   200
  • この辺はホスティングサービスごとに異なるのでそれぞれ調べてみてください
  • URLダイレクトアクセスにも対応したサンプル
  • ここまでできるとユーザ視点ではSPAもMPAもほとんど変わらないてすね!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ざっくりwebpack ~その壱~

概要

開発メンバーの偉い人が環境作ってくれた上にREADMEに事細かくビルド方法とか環境構築のやり方が書いてあってnpmyarn叩けばすべてオッケーみたいな状態になってる事ない?
そんで他の開発環境作ることになっても「偉い人が書いたコードだし」みたいな感覚でそのままコピペして使って...

そしてぬるま湯に浸かったまま自分が環境作っていかなきゃならなくなった時にきっとこう思う
「webpackってどうやって使うんだよ...」

このまま行けば間違いなく訪れるであろう悲劇を回避するためにも、少しずつでもざっくりでも知っておくことが必要だと感じたので書き記す

こんな感じからスタート

まずはバンドルツールとして基本的な流れを追ってみるのだ
scripts配下のjsどもをwebpackを使ってバンドる

public/
  ├ index.html
  └ app.bundle.js
src/
├ scripts/
│   ├ hoge1.js
│   ├ hoge2.js
│   └ hoge3.js
│ └ app.js
└ webpack.config.js
package.json

まずはじめに

使う前に必要なパッケージはいれないとな!

ターミナル
$ yarn add -D webpack webpack-cli babel-loader @babel/core @babel/preset-env
  • webpack -> webpackの事やるんだからそりゃ入る
  • webpack-cli -> 4以前は一緒になってたけど、分割されたらしいのでこれもいれる
  • babel-loader -> ES6で書くから必要
  • @babel/core -> babelの本体
  • @babel/preset-env -> 変換設定するためのやつ
Q.まずローダーってなんだろ?  
A.webpack自体はjavascriptしか分かんないから、他の言語で書かれた(cssとか)ファイルをモジュールに変換してくれる機能...らしい  

Q.でもES6ってjavascriptじゃん  
A.ES6対応してないブラウザ多いからbable-loader通してbabelで古い書き方に対応してくれる感じ?

Q.@babel/preset-envってなんすか?
A.上でES6対応してないブラウザ多いって言ったけど、ブラウザもバージョン上がったら順次対応はしていくわけで。 
ブラウザのバージョンとか指定するとその環境に合わせてよしなにやってくださるようです。

modeの設定

ではwebpack.config.jsを書いていく
まずはじめにmodeの設定

weback.config.js
// outputのパスを絶対パスで書く必要があるのでパスモジュール読込
const path = require('path')

module.exports = {
  // webpack4からmodeを設定しないと警告が出るようになったので設定
  mode: 'development'
}

コメントに書いたけどmodeの設定をしないと文句言われる

ターミナル
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.

mode:

production development none の3つある
productionモードでビルドすると圧縮される。
あとnoneていつ使うの?

development
development.png
production どうです?圧縮されてるでしょう?
production.png

enrtyの設定

modeの設定ができたので、enrtyを書いていく

webpack.config.js
module.exports = {
  // webpack4からmodeを設定しないと警告が出るようになったので設定
  mode: 'development',
  // エントリーポイントの設定
  entry: './src/app.js'
}

entry:

ここで指定された.jsを親分として、子分どもをまとめてくれる
複数の親分(エントリーポイント)を指定する事も可能

outputの設定

続いてoutputを書く
その名の通り出力ファイルや出力先の設定

module.exports = {
  // webpack4からmodeを設定しないと警告が出るようになったので設定
  mode: 'development',
  // エントリーポイントの設定
  entry: './src/app.js',
  // 出力先を設定
  output: {
    // 出力する時の名前
    filename: 'app.bundle.js',
    // 出力先のファイルパス(絶対パスじゃないとダメ)
    path: path.join(__dirname, 'public')
  }
}

filename:

その名の通り出力する時のファイル名
[name]とか[hash]とかあるけど、また後日書いてみようと思う

path:

出力先のディレクトリパスを指定するが、絶対パスで指定する必要がある

ターミナル
Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.output.path: The provided value "./public" is not an absolute path!
   -> The output directory as **absolute path** (required).

ここもコメントにあるけど、'./public'みたいに相対パスで書くと文句言われる

んじゃビルド

ファイルの入出力に関する設定が終わったので、ビルドしてみるぞ
package.jsonにこれを追加する

package.json
"scripts": {
  "build": "webpack"
 },

そしたらコマンド叩く

ターミナル
$ yarn build

or

ターミナル
$ npm run build

public/が生成されて、app.bundle.jsが出力されたな?されたよな??
されたら続きは其の弐で!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

年末まで毎日webサイトを作り続ける大学生 〜46日目 Cookieを使う、その他〜

はじめに

こんにちは!@70days_jsです。

Cookieを使ってみました。
その他色々JavaScriptの機能を使ってみました。

今日は46日目。(2019/12/3)
よろしくお願いします。

サイトURL

https://sin2cos21.github.io/day46.html

やったこと

  1. documentオブジェクトを利用する
  2. onselectionchangeイベントと、getSelectionを利用する
  3. cookieを利用する

では1からいきます。

1. documentオブジェクトを利用する

ざっとこんな感じです。↓

スクリーンショット 2019-12-03 13.39.49.png

<div id="test"></div>
// 最終更新日 lastModified
test.innerHTML += '最終更新日: ' + document.lastModified + '<br>';

// ドメイン名 domain
test.innerHTML += 'ドメイン名: ' + document.domain + '<br>';

// タイトル title
test.innerHTML += 'タイトル名: ' + document.title + '<br>';

// プラグイン数 plugins
for (var i = 0; document.plugins.length > i; i++) {
    plugins += document.plugins[i] + ',';
}

test.innerHTML += 'プラグイン数: ' + plugins + '<br>';

documentオブジェクトは色々な情報が取得できて便利ですね。
当たり前ですが、どれもdocumentの後にプロパティ名をつけるだけで呼び出せます。

2. onselectionchangeイベントと、getSelectionを利用する

名前だけじゃ分かりづらいですね。
onselectionchangeイベントは選択範囲の変更時に起きるイベントです。
getSelectionは選択範囲の文字列を調べるメソッドです。
gif↓
test2.gif

あらかじめ用意しておいた文字列を選択すると、その文字列をinnnerHTMLに加えていくように書いています。

    <div id="field2">
        <div id="charSelect">選択した文字列: </div>
        何だろう
        。
        どうしてか
        、
        僕は
        今日
    </div>
// 選択範囲に変化があったときに起こるイベント onselectionchange
document.onselectionchange = function () {
    // 選択された文字列を調べる getSelection()
    let select = document.getSelection();
    charSelect.innerHTML += select;
}

onselectionchangeイベントもdocumentから取っています。

3. cookieを利用する

では最後にcookieです。↓
test3.gif

太郎と名前を入力すれば、それをnameに入れてcookieで記録するようにしています。
もし、nameに値があれば、h2タグに値をいれて名前を表示します。

<h2 id="myName"></h2>
...

<div id="field3">
    <button id="test2">cookieを作るボタン</button>
    <div id="cookieField"></div>
</div>
test2.addEventListener('click', test3);

function test3() {
    let name = prompt('名前を入力してください。');
    document.cookie = 'name=' + name;
    document.cookie = 'your_type = shy';
    cookieField.innerHTML += 'クッキー: ' + document.cookie + '<br>';
    takeCookie();
    myName2();
}

function takeCookie() {
    if (document.cookie) {
        let cookie = document.cookie.split(';');
        cookie.forEach(function (value) {
            let content = value.split('=');
            cookieField.innerHTML += 'クッキーの値だけ: ' + content[1] + '<br>';
            if (content[0] == 'name') {
                cookieName = content[1];
            }
        })
    }
};

let myName = document.getElementById('myName');

function myName2() {
    if (cookieName) {
        myName.innerHTML = 'Hey, ' + cookieName + 'さん!';
    }
};

どうでもいいことを色々書いているので長くなっていますが、要はdocument.cookieで値を代入していて、cookie.forEachで値を取り出しているだけです。
cookieは一連の文字列なので、;で区切ってやります。
その後に=で区切って名前だけを取り出します。

感想

最近眠れなくて脳がまともに働きません。プログラムミスかな?
冗談です。

最後までお読みいただきありがとうございます。明日も投稿しますのでよろしくお願い致します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript初心者の 非同期処理 Promise async await

はじめに

私はJavascript初心者です。
今までバックエンドエンジニアをやってきたので、Javascriptの概念に追いつくのに時間がかかりました。

その中で私を苦しめたのは、並列処理と非同期処理でした。
また、それを解決してくれたPromise async awaitについてのお話をしようと思います。
並列処理に関しては、またの機会に・・・。

そもそも非同期処理ってなに?

非同期処理とは・・・色々な尊敬すべき先輩たちが語っているので、私から説明するのもおこがましいのですが、簡単に説明します。
一言で言うと、I/O待ちをしないってことです。
別な言い方をすると、実行できる時に実行するってことですね。

具体的な例を出してみましょう。

この記事を読んでいるJavascript初心者の皆さん。ボタンのクリックイベントって違和感ありませんか?
私は同期処理ばかり書いてきたので、違和感がありました。
だってプログラムって上から順番に実行されるものでしょ?それが鉄則。そう思っていた時期が私にもありました。

Javascriptのボタンクリックイベントは、対象のボタンイベントを覚えていて、ボタンが押された時に実行される。
これは違和感ありありでした。

ざっくりですが、これが非同期処理で言う、実行できる時に実行をする。と言う考え方です。

ざっくりした非同期のイメージ
アドベントカレンダー1 IO待ち.png

理解しにくい”時間がかかる処理”

ボタンクリックイベントなんかは、なんとなく理解できました。
JS読み込み時にクリックイベントが存在することを認識させておけば、クリックされた時点で、クリックイベントを発火できる!

なんだよ!楽勝じゃん!
とは問屋がおろしませんでした。

本質的には同じことなのですが、少し理解しにくい"時間がかかる処理"について。

例えば、今回仕事で利用したFirebaseです。
これ、Node.jsで作成して、サーバ側もJSでかけまっせ!ってやつです。
Node.jsは某有名な騎空団なゲームも利用しています。
「C10K問題」(クライアント1万台問題)を解消してくれるすごいやつです。
「C10K問題」や「Firebase」についてはまた今度機会があったら記事にしたいです。

Firebaseには、FirestoreっていうDBがあるのですが、クライアント側からFirestoreにアクセスすると時間がかかる。
PHPなんかで言うと、DBConnectionですね。

先ほどもお話したように、時間がかかる処理はI/O待ちしませんので、例えば以下のようなコードを書くと問題が起こります。

const snapshot = firebase.firestore().collection("aaa").get();

var snapDatas = snapshot.data();

for(const data of snapDatas){
  ........処理......
}

さて、どんな問題が起こるでしょうか?

Javascriptさんの気持ちを代弁しましょう。

Javascriptさんは基本的にせっかちです。

const snapshot = firebase.firestore().collection("aaa").get();

Javascriptさん「Firestoreに接続しに行ってるよー」
Javascriptさん「.............まだー?」
Javascriptさん「応答ないから先行くねー!」

と結果が返ってくる前に先に進んでしまいます。
その結果

var snapDatas = snapshot.data();

Javascriptさん「"undefined"って言われたー」

と言ってエラーを吐きます。
そしてプログラムが動き終わって暫くたった頃、

Javascriptさん「snapshotさん帰ってきたー」

と反応があります。
おせぇぇぇぇぇぇぇぇぇぇぇぇ!
Javascriptさん的には、今処理が終わったようです。
実行できる時に実行する。。。。。なるほどそう言うことね。。。。
ともあれ、ほしい時にデータが返ってこない!

これ・・・どうすんの?
これを解決してくれたのが、Promise async awaitでした。

Promiseってなに?

一言で言うと、非同期のJSを同期っぽく動作させてくれる関数です。

callbackでも同様の処理は一応作れますが、めちゃくちゃ複雑化するので、使わない方針で。
その辺はググってもらえればエンジニアの悲喜交々が聞こえてきますよきっと。
(IEの昔のバージョンはPromiseに対応していないため、悲鳴をあげているのはIEが要件に入ってるエンジニアなのではないかなー?)

Promiseの基本的な使い方は以下の通りです。

//Promise関数を利用して、本日の日付を取得する。
var result = new Promise(function(resolve){
  resolve(new Date);
});
//resultを実行した後に、年を取得する
var res = result.then(function(data){
  console.log(data.getFullYear());
});

ポイントはresolve()と、then()です。

resolve(new Date);
とやると、戻り値に「new Date」が設定されます。
thenは、Promise関数実行後に、実行してほしい処理を記載します。

then(function(data))
のdataにnew Dateが入っているわけですね。

読みにくい問題

Promiseで記載してあげると、順番を指定できることがわかりました。
しかし、そうすると結構読みにくくなる印象です。
なぜなら、

var res = result.then(function(dara){
  var testData = "";
  //ここに後続処理
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
});

thenの中に記載する内容が長くなれば長くなるほど、下に読んでいって、上に戻らないといけないわけです。
これは読みにくい。

async await

そこで登場するのがasync awaitです。
firebaseのソースを元に考えてみましょう。
まずはthenを利用した書き方。
ちなみに、firebase.firestore().collectiom("aa").get()が既にPromise関数で返ってくるため、
新規でPromise関数として宣言する必要はありません。

var getFirebaseData = function(){

  firebase.firestore.collection("aa").get().then(function(snapshot){
    var snapDatas = snapshot.data();

    for(const data of snapDatas){
      ........処理......
    }
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
  });
}

となります。

続いて、async awaitを利用した記載方法

var getFirebaseData = async function(){
  const snapshot = await firebase.firestore.collection("aa").get();

  var snapDatas = snapshot.data();

  for(const data of snapDatas){
    ........処理......
  }
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
}

こっちのが読みやすくないですか?

thenの中で書くと、スコープの管理もしなくちゃいけないですし、面倒が増えます。
何より、ごちゃつく!

そのため、Promiseの同期処理をしたい場合は、async awaitを利用するのがおすすめです!

まとめ

・時間がかかる処理を実行したいときはPromiseを思い出してみて。
・Promiseを使ったらasync awaitがとっても便利!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AngularDart開発の最低限の基本

AngularDart、まだ多少しかやってないけど、最低限これだけあれば開発できるってのをまとめてみた。細かいとこは端折ってるので、他サイト見てね。

Dart

Flutter/Dartの入門サイトが多数あるので、初期設定する。
IDEはVSCodeがおすすめ。
Inteli Jがいいってサイトも多数あり。
なので、若干キモいけどAndroidStudioでも開発できる。

とりあえず雛形生成して、IDEで開く。

stagehand web-angular
pub get

webdev serveするとローカルで動く。auto restartはセッションとかがダメで、そのうちできなくなるのであてにしない。

コンポーネントはhtml用コードで一対一が基本

xx_component.html/xx_component.dartで1画面を構成する。雛形を参考に。
複数画面あるときはコンポーネントを複数作る。

複数にしたら、appコンポーネントに追加する。

app_component.dart
  directives: [
    routerDirectives,
    ProductsComponent,
    ListComponent,
  ],

html側 {{インスタンス変数}}/ngIf/ngFor/(click)

<img src="{{header}}">
<p>{{item['price']}}</p>

xx_component.dartのインスタンス変数がhtml側から参照できるので、htmlに{{インスタンス変数名}}とかで埋め込む。
itemがMapのときは「item['price']」とか書ける。つーかDartコードを埋め込める。

<p class="price {{(downloaded[item['key']]!=null)?'downloaded':''}}">{{item['price']}}</p>

dartそのまんま。

<img *ngIf="!headerLink" class="thumbnail" src="{{header}}">

タグ内にifを書ける。falseのときはimgタグが無くなる。使うにはコンポーネントのdirectivesに追加する。NgForも同様。NgSwitchとかもある。

    directives: [NgIf, NgFor],

当然Listでループできる。

<li *ngFor="let item of citem['items']; let idx=index">
</li>

コンポーネントのonClickMore(int idx,var citem)でクリックを受け取れる。

<a class="categoryTitle" (click)="onClickMore(idx,citem)">

固定値クラスとかもhtmlから参照できる。コンポーネントのexportsに追加する。

  exports: [MyString,MyStrId,Constant],

てやると

<a *ngIf="downloaded[product['key']]==Constant.ITEM_DOWNLOADED" class="getButton downloaded">{{MyString.get(MyStrId.downloaded)}}</a>

とかね。素敵。

パイプ

<div innerHtml="{{product|lang:'description'|mynl2br}}"></div>

パイプで整形できる。nl2brがなぜか動かなかったので、そのまんまコピーで自作で作ってみた。langも自作でMapから設定言語にしたがったアイテム持ってくるやつ。

@Pipe('mynl2br')
class MyNl2brPipe implements PipeTransform {
  String transform(String value, [bool isXhtml = false]) {
    // ignore: avoid_positional_boolean_parameters
    if (value == null) return "";
    return value.replaceAll(RegExp(r'\r?\n'), isXhtml ? '<br/>' : '<br>');
  }
}

コンポーネント側。commonPipesに色々入ってる。

  pipes: [commonPipes, MyNl2brPipe],

画面遷移(Router)

複数画面切り替える時は、Routerを導入する。呪文がたくさん必要。

angular_routerを追加。alphaだよ。

pubspec.yaml
dependencies:
  angular: ^5.3.0
  angular_components: ^0.13.0
  angular_router: ^2.0.0-alpha+22
main.dart
final InjectorFactory injector = self.injector$Injector;

void main() {
  //アプリ起動
  runApp(ng.AppComponentNgFactory, createInjector: injector);
}

routes.dart追加して、URLを定義

routes.dart
class RoutePaths {
  ///URL セット
  static final setp = RoutePath(path: 'set');
  ///URL カード表
  static final cfp = RoutePath(path: 'cf');
  ///その他全部
  static final allp = RoutePath(path: '.+');
}

class Routes {
  static final all = <RouteDefinition>[
    RouteDefinition(
        routePath: RoutePaths.setp,
        component: product_temp.ProductsComponentNgFactory,
        additionalData: {
          "type": RoutePaths.setp.path,
          "index": 0,
          "css": "setList"
        }),
    RouteDefinition(
        routePath: RoutePaths.cfp,
        component: product_temp.ProductsComponentNgFactory,
        additionalData: {
          "type": RoutePaths.cfp.path,
          "index": 1,
          "css": "cFrontList"
        }),
  ];
}

appコンポーネントのhtmlはこうする。

app_component.html
<router-outlet [routes]="Routes.all"></router-outlet>

index.htmlは全画面共通な感じになる。cssとかも。

index.html
<body>
    <my-app>Loading...</my-app>
</body>

コンポーネントでこんなんで遷移できる。

  Future<NavigationResult> onClickMore(int idx, Map item) {
    return router.navigate(
        RoutePaths.setp.path,
        NavigationParams(queryParameters: {
          "type": item["type"],
          "index": "$idx",
          "class": cssClass
        }));
  }

routerは注入してくれる。

  XxComponent(this.productsService, this.router);

こういう風にも書ける。

<a [routerLink]="item['url']" 
[routerLinkActive]="'active-route'">{{item|lang:"name"}}</a>

http通信

データはサービスで持つらしいぞ。

class ProductsService {
  //シングルトンのはずがなぜかだめなので、とりあえずstatic
  static List<Map<String, String>> _types;
  final Client _http;

コンストラクタに引数追加すると、入れてくれる。

  ProductsService(this._http) {
  }

なんか呪文が必要だったけど、後ほど・・・:relaxed:

    final response = await _http.get(ここにURL);

リリースビルド

webdev build

すると、build以下にビルドされる。それをサーバーに持ってくだけ。
packagesはリリース時は要らないので、削除する。

ビルドエラー

最近のバージョンはwebdevが頻繁にビルドエラー出すバグがある。
.dart_tool/build以下を消すと、概ね、直る。

最後に

調べるの、ちょっと時間かかったけど、概ねこれだけ。Dartできれば数日で結構慣れる。あまり深く考えないで、雛形に従う。
慣れるとめっちゃサクサク開発できる。幸せが訪れる:kissing_closed_eyes:。たまにハマると非常に困る:sob:

呪文はちょっと端折ってるので、そのうち追加予定。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む