20190327のJavaScriptに関する記事は19件です。

XX is not a functionエラーがどうしても解決しないときの可能性

こんにちは。俳句や川柳から動くGIF画像を生成できるWebサービス「五七五メーカー」をリリースしたアカネヤ(@ToshioAkaneya)です。

JavaScriptで、XX is not a functionというエラーがどうしても解決しないことがあったので、参考になれば幸いです。

XX is not a functionエラーがどうしても解決しないときの可能性

セミコロンが絡んでいる可能性があります。
そのエラーは、次の様な状況で起きていました。
Screen Shot 2019-03-27 at 23.35.19.png
console.logが関数じゃないだって...!?
これは、一行目にセミコロンが無いからです。
Screen Shot 2019-03-27 at 23.35.43.png
これなら大丈夫です。
ちなみに、Numberリテラルにメソッド呼び出しを行うときは、32..toString()の様にドットを付ける方法もあります。
上の様なセミコロンのエラーが起きないし、簡潔なのでこちらを使う様にしましょう。Screen Shot 2019-03-27 at 23.39.10.png

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

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

ブラウザで動作するJavaScriptのdeepEquals(不完全)を書いたけど、fast-deep-equalを使います

(不完全)と前置きする感じ、弱気である。

雑にdeepEqualsしたい、という要件があったので。
npmにはもちろん上がっているけど、ブラウザでちょっと試しに使いたい感じだったので、ググってすぐ見つからなかったので書いた。

https://jsfiddle.net/upcme7ob/

対応しているのは、null,undefined,number,string,object,array。
(boolean同士がない事に気づく。「こんなん書かなくてもあってるでしょ」とタカをくくって書いてないですね・・・よくない・・・)

2019-03-26時点のモダンなEdge,Chrome,Firefoxでエラーが出てないことは確認。Safari、モバイルは知らない。
雰囲気で適当なテストを書きながら実装してたので、テストしてないパターンありそう。

Functionからインスタンスを作る感じのやつも多分大丈夫だけど、Functionを作ってるようなものは無理かもしれない。
NaNとかSymbolとかは見てない。循環参照も知らない。こちらからすれば、そんな汚いオブジェクトを作る方が悪い。

他を見てみましょう

こういう書いた系は、いつもある程度満足いくところまで実装してから他の実装見ています。なぜ逆にしないのか。

Node 実装

最初はnodejs実装を参考にしようとしたけど、なんかめんどくさそうだったので諦めた。

NodeJSの実装は以下。setを持ちまわる感じ、循環参照を考慮してる感はある。Symbolも見てるっぽい。よくわからない。

https://github.com/nodejs/node/blob/5f032a7a269b66d48505869b0ae4fb1db403b118/lib/internal/util/comparisons.js#L548

fast-deep-equal

https://www.npmjs.com/package/fast-deep-equal

code: https://github.com/epoberezkin/fast-deep-equal/blob/master/index.js

こちらは1300万DL/weekな実装。テストか何かの依存なんだろうか
何度もオブジェクトを辿らないようにしたり、varを1回で済ませてたり、何度もvarしないように頑張っている感がある。コンパクトだし読みやすい。
export等を削れば自分が用意したテストケースでブラウザ上でも動いた。本番で使うならこっち使います。

node-deep-equal

https://www.npmjs.com/package/deep-equal

code: https://github.com/substack/node-deep-equal/blob/master/index.js

こちら600万DL/weekな実装。名前は一番良い。でもDate型とかも見てるし、なんか読みにくく感じてあまり見てない。

他にもあるみたいだけどどんぐりの背比べのような気がしたので見てない。

真のdeepEqualsを見せてくれる方を募集しております。

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

新元号はこれに決定

思わせぶりなタイトルですみません。

新元号はリークしたら変更されるから予想出来ないとか、みなさん色々言われていますが、新しい元号はもう昨年にはとっくに決まっていたのです。

よろしいですか?新しい元号は「㋿」です。

・・・これが豆腐にしか見えないみなさんも、いずれ私の言うことが正しかったと分かるはずです。

それでは、せっかくのQiitaですので、新しい元号を出力してクリップボードに入れるプログラムを紹介します。

WindowsでPowershellを開く、もしくはコマンドプロンプトを開いて powershell を実行し、下記のスクリプトを実行しましょう。

"新元号は" + [char]::ConvertFromUtf32([int]"0x32ff") + "。" | clip

せっかくなので、JavaScript版も紹介します。

console.log("新元号は「\u32ff」。")

というわけで、unicode.orgにもあるように、 32FFcode point reserved for pending Japanese era name で新元号に決まっていますので、みなさんお忘れなきよう。

各種申請書類にも、"\u32FF元年 5 月 1 日" などと書き込むようにしましょう。

・・・フォントがいつアップデートされるかは、みなさんの環境次第ですが。

最後に

よろしければ、コメントで各言語での新元号表示方法を教えてください。

それから@BotUnicode というBotを運用しています。よろしくお願いします。

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

状態管理ツールdobについて

dobjs/dob についての解説です。

dobとは

Light and fast ? state management tool using proxy と書かれているように、軽くて速い状態管理ツールです。
特定のフレームワークに依存することなく使えます。
現状フロントエンドの状態管理ツールは、特定のフレームワークに依存しているものが殆どですが、このツールをうまく利用すれば、Viewフレームワークだけを取り替えることが出来ると考えています。もちろんサーバー側でも使えます。

dobのバックグラウンド

まずは、dobで利用されているJavaScriptの標準機能Proxyについてざっくり説明します。

(ここは表面的な部分からは触れないので、読み飛ばしても大丈夫です)

Proxy オブジェクトとは、プロパティの検索・代入・削除等の操作をしたときに、独自の動作を定義することが出来るものです。

例として、猫の体重をチェックするコードは以下のようになります。

{
  const handler = {
    set (target, prop, value) {
      if (value < 10) {
        console.log(`現在${value}kgです。`)
        target[prop] = value
        return true
      }

      throw new Error(`食事内容を変更してください。`)
    }
  }

  const cat = new Proxy({ weight: 2 }, handler)

  cat.weight += 1 // 現在3kgです。
  cat.weight += 2 // 現在5kgです。
  cat.weight += 10 // Error: 食事内容を変更してください。
}

この機能を利用し、値の変更を検知をすることが出来るツールです。

dobのざっくり簡単な使い方

observable でobjectを監視対象とし、 observe でpropertyの変更を検知し処理を実行します。

{
  const { observable, observe, Action } = dob

  const dog = observable({
    name: 'ポチ',
    gender: 'male',
    age: 2,
    condition: {
      happy: true,
      hunger: false      
    }
  })

  // observe定義時に、渡した関数が即時実行されます。
  observe(() => {
    console.log(dog.name, dog.gender)

    if (dog.condition.hungry) {
      console.log('ご飯を食べさせましょう。')
    }
  })
  // ポチ male


  // observe内でアクセスされたpropertyを変更すると検知対象となります。
  dog.name = 'ポチ太郎'
  // ポチ太郎 male

  // observe内で参照されていないpropertyを変更しても何も起きません。
  dog.age += 1

  // Actionを使うことで、値を複数回変更しても、コールバックが一度のみ実行されるようになります。
  Action(() => {
    dog.name = 'ポチ美'
    dog.gender = 'female'
  })
  // ポチ美 female


  // object を持つ property についても同様に扱えます。
  dog.condition.hungry = true
  // ポチ美 female
  // ご飯を食べさせましょう。
}

上記のように、property値の変更をすることで、コールバックが呼び出されていることが確認できるかと思います。

気を付ける点

自分がはまったことのある点をいくつか紹介します。

配列操作

以下のように、配列自体を監視すると、配列に値を追加してもコールバックが呼ばれません。

{
  const { observable, observe, Action } = dob

  const shop = observable({
    fruits: ['?', '?', '?']
  })

  observe(() => {
    console.log(shop.fruits, 'フルーツが変更されました')
  })
  // Proxy {}, フルーツが変更されました。

  // 以下を実行しても、配列自体は同じものと見なされるため、コールバックが実行されない。
  shop.fruits.push('?')
}

上記のケースの場合は、shop.fruits.length を参照する等、配列操作によって変更される部分を監視する必要があります。

外部関数

observe の中で別の関数を呼び出しても、その関数で使われたpropertyは検知対象となります。

{
  const { observable, observe, Action } = dob

  const tree = observable({
    root: 1,
    leaf: 100
  })

  function treeNode () {
    return tree.root + tree.leaf
  }

  observe(() => {
    console.log(tree.root, treeNode())
  })
  // 1 101

  // treeNodeで参照しているため、以下の処理は検知対象となる。
  ++tree.leaf
  // 1 102
}

仕組みを理解すると当然と言えば当然なんですが、外側の関数は割と見落としがちなので、注意が必要です。
どうしても検知対象から外したい場合は、 setTimeout(fn, 0) 等を呼び出す必要があります。

自己検知による無限ループ

observe時に、自分自身が参照している値を変更すると無限ループになります。

{
  const { observable, observe, Action } = dob

  const game = observable({
    count: 1
  })

  observe(() => {
    ++game.count // InternalError: too much recursion
  })
}

observe時に値を変更したい場合は、以下のように Action でくくる必要があります。(参考issue

{
  const { observable, observe, Action } = dob

  const game = observable({
    count: 1
  })

  observe(() => {
    Action(() => {
      ++game.count // no loop
    })
  })
}

検知対象とならないproperty

検知対象となるルールとして、observeに渡したコールバック関数が、最後に実行されたときに読み取られたpropertyが検知対象となります。

以下では、分岐により読み取られなかったpropertyを変更しても実行されない例です。

{
  const { observable, observe, Action } = dob

  const game = observable({
    isPlay: false,
    title: 'マリオ'
  })

  observe(() => {
    console.log(game.isPlay ? 'ゲームプレイ中' : 'ゲームしてません')

    if (game.isPlay) {
      console.log(`「${game.title}」をプレイしています。`)
    }
  })
  // ゲームしてません

  // 以下のproperty変更は、最後に呼び出されたコールバックで読み取られていないため、呼び出されません。
  game.title = '?の大冒険'

  game.isPlay = true
  // ゲームプレイ中
  // 「?の大冒険」をプレイしています。

  // 以下のproperty変更は、最後に呼び出されたコールバックで読み取られたため、呼び出されます。
  game.title = '?クエスト'
  // ゲームプレイ中
  // 「?クエスト」をプレイしています。
}

これらを考慮するのは大変なため、分岐で使ったpropertyは変更の有無に関わらず問題が無いような作りを考えたほうが楽です。
自分はコールバックの頭に void (game.title, ...) のように、監視したいpropertyを並べて、分岐による処理がされないように施したりしています。

試してみる

上記のコードは https://observablehq.com/@ampcpmgp/dob-example に置いてあります。devtoolsのconsole画面を開いて、画面内コードセル上にある右上の実行ボタンを押すことで、実際の動作を確認できます。編集して実行しても大丈夫です。(Observableもとても良いツールなので使ってみてください?)

所感

objectの変更検知が、他ツールに比べて直感的でわかりやすいと思います。
特定のフレームワークへの依存性が小さいという点も、本来のエコシステムにより近づくように思います。

ちょっと古い記事ですが、jsのProxyを利用した状態管理ツールを試してみたで他にも軽く試したことがあるので、良かったら参考にしてください。

以上です。

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

GraphQLの勉強 part1~基本操作~

GraphQLとは

簡単に言うとモダンなREST APIといったところ。
「クライアント ー REST API / GraphQL ー サーバー」のようにどちらもクライアントとサーバーの橋渡しをしている点と、HTTPメソッドで動く点は同じ。HTTPメソッドで動くということは、GraphQLもRest APIと同様にどのような言語/データベース/クライアントにも対応できるということ。

Rest API との違いは、一つのエンドポイントで好きなデータを問い合わせできるという点。欲しいデータに対してその都度エンドポイントを変える必要が無く、リクエストに対してクライアント側で多様な GraphQL query なるものをくっつけてやれば、それで取得データを決定することができる。

例)
・REST API ⇒ GET/posts/123 とか GET/posts?author=3421 とか
・GraphQL ⇒ POST/graphql (w/ a graphQL query) オンリー

これはつまり以下のように言える
・REST API ⇒ エンドポイントをもとにサーバーが必要なデータを決定する
・GraphQL ⇒ GraphQL queryを利用してクライアントが必要なデータを決定する

GraphQLの利点

1. 速い
GraphQL query の記述を変えればHTTPリクエスト1回で必要なデータをごっそりと取得できる ⇒ 速い

2.柔軟
GraphQL query の記述を変えるだけでエンドポイントを増やさずに取得するデータの量や種類を決定できる(モバイルには少量のデータを送るとか)

3.簡単
一つのエンドポイントしか管理しなくてよいから

Hello GraphQL

サーバー ⇒ クライアント で既存のデータを取得してみる

1 ) プロジェクトの初期化とBabelのインスト-ル

> npm init
> npm install babel-cli babel-preset-env
※ babel-cli:Babelのコンパイルに必要なコマンドを実行するのに必要
※ babel-preset-env:Babelに何をパースすべきか伝える

2 ) package.json / .babelrc / index.js を以下のように作成
キャプチャ.JPG
3 ) 「npm run start」 でコンパイルしてBabelの動作確認

> npm run start

> graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql
> babel-node src/index.js

hello

4 ) GraphQL Yogaのセットアップ

GraphQLはクエリがどのように働くかを決めているだけで実装そのものではない。
つまり、環境によってどのように実装するかを決めるのは開発者の仕事。

例えるなら、JavascriptがECMAスクリプトでどのように働くかを記述されており、そのJavaScriptを動かすためにChromeはV8エンジンを、MozillaはSpiderMonkeyを実装しているといった感じ。

ということで、今回はまずGraphQLがNodejs上で動くような環境作りから始める必要があり、そのために GraphQL Yoga を使用する。GraphQL Yoga は多機能&高性能&簡単で人気。

> npm i graphql-yoga

ダミーのデータをサーバーに準備しつつセットアップ。

index.js
import { GraphQLServer } from 'graphql-yoga';

// Type definition (Schema)
const typeDefs = `
    type Query { 
        hello: String!
        name: String!
    }
`

// Resolvers 
const resolvers = {
    Query : {
        hello() {
            return 'Hello GraphQL'
        },
        name() {
            return 'Hugo'
        }
    }
}

const server = new GraphQLServer({
    typeDefs,
    resolvers
})

server.start(() => {
    console.log('server is up');
})

  1. typeDefs でサポートする型を定義する(スキーマの定義)
  2. resolvers でクエリに対する処理を定義する
  3. serverのインスタンスを作成する
  4. serverを立ち上げる

※ TypeDefsは「クエリ名: 型!」で定義する(末尾の「!」が無い場合は null 可能ということ)

5 ) 実行

>npm run start
> graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql
> babel-node src/index.js

server is up

この状態で「localhost:4000」につなぐと GraphQL の Playground につながるので、クエリの実行と Hello GraphQL を確認する。
キャプチャ.JPG

6 ) ついでに Live Reload セットアップ
プロジェクトを保存する度にサーバーを自動でリロードするように設定する。

> npm install nodemon --save-dev

nodemonをインストールしたら package.json の scripts を以下のように書き変える。

package.json
  "scripts": {
    "start": "nodemon src/index.js --exec babel-node"
  }

index.jsを自動で実行しなおす、babel-nodeを通して。という意味になる。
これで「npm run start」を実行することで自動リロード状態になる。

GraphQL Types

【Scalar Type】

5つのビルトイン type が存在する。
1. ID: ユニークなid
2. String: 文字列
3. Int: 整数
4. Float: 小数
5. Boolean: true/false

ScalarType
// Type definition (Schema)
const typeDefs = `
    type Query { 
        id: ID!
        name: String!
        age: Int!
        height: Float!
     isAdult: Boolean!
    }
`

【Custom Type】

開発者が定義する type。

CustomType
// Type definition (Schema)
const typeDefs = `
    type Query { 
        me: User!
    }
    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
    }
`

// Resolvers 
const resolvers = {
    Query : {
        me(){
            return {
                id: '12345',
                name: 'Hugo',
                email: 'hugo@example.com',
                age: 27
            }
        }
    }
}
`

※ クエリの形に注意
キャプチャ.JPG

Resolver Operation Arguments

index.js
// 引数に '!' が有る場合、その引数は required
const typeDefs = `
    type Query { 
        greeting(name: String, position: String): String!
        add(a:Float!, b:Float!): Float!
    }
`

const resolvers = {
    Query : {
        greeting(parent, args, ctx, info) {
            if(args.name && args.position) {
                return `Hello, ${args.name}! You are my favorite ${args.positino}`;
            } else {
                return 'Hello!'
            }
        },
        add(parent, args){
            return args.a + args.b
        }
    }
}

キャプチャ.JPG

Resolverには4つの引数が存在する

  1. parent : Type間にリレーションがある場合の親を指す
  2. args : operation arguments supplied
  3. ctx : contexutual data (ユーザの情報)
  4. info : サーバーに送信されたアクションの情報など

Array Type の扱い

【Arrays of Scalar types】

index.js
const typeDefs = `
    type Query { 
        add(numbers: [Float!]!): Float!
    }
`

const resolvers = {
    Query : {
        add(parent, args, ctx, info){
            if(args.numbers.length === 0 ) {
                return 0
            } 
            return args.numbers.reduce( ( accumulator, currentValue ) => accumulator + currentValue )

    }
}

キャプチャ.JPG

【Arrays of Custom types】

index.js
// Demo data
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'mike@ex.com'
}]

// Type definition (Schema)
const typeDefs = `
    type Query { 
        users(query: String): [User!]!
    }
    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
    }
`

const resolvers = {
    Query : {
        users(parent, args, ctx, info) {
            if (!args.query){
                return users
            } else {
                return users.filter(user => user.name.toLowerCase().includes(args.query))
            }
        }
}

キャプチャ.JPG

Relation

Type の ID を追加することでリレーションが生まれる。

index.js
// Demo data
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'aike@ex.com'
}]
const posts = [{
    id: '4',
    title: 'aaa',
    body: 'asdtfhaer',
    published: true,
    author: '1'   /// 注目
}, {
    id: '5',
    title: 'fff',
    body: 'hrtea',
    published: true,
    author: '1'   /// 注目
}, {
    id: '6',
    title: 'Ahwerike',
    body: 'aikreshe',
    published: true,
    author: '2'   /// 注目
}]

const typeDefs = `
    type Query { 
        users(query: String): [User!]!
        posts(query: String): [Post!]!        
    }

    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
        posts : [Post!]!  /// 注目
    }

    type Post {
        id: ID!
        title: String!
        body: String!
        published: Boolean!
        author: User!   /// 注目
    }
`

そして、Resolverにオブジェクトを追加することでクエリ時にリレーションをもつTypeを引きずってくることができる

index.js
const resolvers = {

    Query : {
        users(parent, args, ctx, info) {
            return users    // call posts() coded below (userの数だけcallする)
        },
        posts(parent, args, ctx, info) {
            return posts    // call author() coded below (postの数だけcallする)
        }
    },

    Post: {
        author(parent, args, ctx, info) {
            return users.find(user => user.id === parent.author) // parentはPost
       // 自分(Post)のauthor(UserのID)とマッチするUserを返却

        }
    },

    User: {
        posts(parent, args, ctx, info) {
            return posts.filter(post => post.author === parent.id) // parentはUser
       // 自分(User)のIDとマッチするauthorをもつPostを返却
        }
    }
}

キャプチャ.JPG

まとめ

index.js
import { GraphQLServer } from 'graphql-yoga';

// Demo data
// Arrayをフィールドに持ったりなんかしないですよ
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27,
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'aike@ex.com'
}]

const posts = [{
    id: '4',
    title: 'About React',
    body: 'intriguing',
    published: true,
    author: '1' // userのID
}, {
    id: '5',
    title: 'About GraphQL',
    body: 'Im now studying',
    published: true,
    author: '1'
}, {
    id: '6',
    title: 'Money',
    body: 'very little',
    published: true,
    author: '2'
}]

const comments = [{
    id: '11',
    text: 'oh my god!',
    author: '1',  // userのID
    post: '4'    // postのID
}, {
    id: '21',
    text: 'jesus christ',
    author: '1',
    post: '4'
}, {
    id: '31',
    text: 'goo goo dloo l doo',
    author: '2',
    post: '5'
}, {
    id: '41',
    text: 'ironman',
    author: '3',
    post: '6'
}]


// Type definition (Schema)
const typeDefs = `
    type Query { 
        users(query: String): [User!]!
        posts(query: String): [Post!]!
        comments: [Comment!]!        
    }

    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
        posts : [Post!]!
        comments: [Comment!]!
    }

    type Post {
        id: ID!
        title: String!
        body: String!
        published: Boolean!
        author: User!
        comments: [Comment!]!
    }

    type Comment {
        id: ID!
        text: String!
        author: User!
        posts: [Post!]!
    }
`

// Resolvers
const resolvers = {

    Query : {

        users(parent, args, ctx, info) {
            if (!args.query){
                return users
            } else {
                return users.filter(user => user.name.toLowerCase().includes(args.query.toLowerCase()))
            }
        },

        posts(parent, args, ctx, info) {
            if (!args.query){
                return posts    // call Post() coded below
            } else {
                return posts.filter(post => post.title.toLowerCase().includes(args.query))
            }
        },

        comments(parent, args, ctx, info){
            return comments
        }
    },

    Post: {
        author(parent, args, ctx, info) {
            return users.find(user => user.id === parent.author) // parentはPost
        },
        comments(parent, args, ctx, info){
            return comments.filter(comment => comment.post === parent.id)
        }
    },

    User: {
        posts(parent, args, ctx, info) {
            return posts.filter(post => post.author === parent.id) // parentはUser
        },
        comments(parent, args, ctx, info){
            return comments.filter(comment => comment.author === parent.id)
        }
    },

    Comment: {
        author(parent, args, ctx, info){
            return users.find(user => user.id === parent.author) // parentはComment
        },
        posts(parent, args, ctx, info){
            return posts.filter(post => post.id === parent.post);
        }
    }
}

const server = new GraphQLServer({
    typeDefs,
    resolvers
})

server.start(() => {
    console.log('server is up');
})

キャプチャ.JPG

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

Node.js&GraphQLの勉強 part1~基本操作~

GraphQLとは

簡単に言うとモダンなREST APIといったところ。
「クライアント ー REST API / GraphQL ー サーバー」のようにどちらもクライアントとサーバーの橋渡しをしている点と、HTTPメソッドで動く点は同じ。HTTPメソッドで動くということは、GraphQLもRest APIと同様にどのような言語/データベース/クライアントにも対応できるということ。

Rest API との違いは、一つのエンドポイントで好きなデータを問い合わせできるという点。欲しいデータに対してその都度エンドポイントを変える必要が無く、リクエストに対してクライアント側で多様な GraphQL query なるものをくっつけてやれば、それで取得データを決定することができる。

例)
・REST API ⇒ GET/posts/123 とか GET/posts?author=3421 とか
・GraphQL ⇒ POST/graphql (w/ a graphQL query) オンリー

これはつまり以下のように言える
・REST API ⇒ エンドポイントをもとにサーバーが必要なデータを決定する
・GraphQL ⇒ GraphQL queryを利用してクライアントが必要なデータを決定する

GraphQLの利点

1. 速い
GraphQL query の記述を変えればHTTPリクエスト1回で必要なデータをごっそりと取得できる ⇒ 速い

2.柔軟
GraphQL query の記述を変えるだけでエンドポイントを増やさずに取得するデータの量や種類を決定できる(モバイルには少量のデータを送るとか)

3.簡単
一つのエンドポイントしか管理しなくてよいから

Hello GraphQL

サーバー ⇒ クライアント で既存のデータを取得してみる

1 ) プロジェクトの初期化とBabelのインスト-ル

> npm init
> npm install babel-cli babel-preset-env
※ babel-cli:Babelのコンパイルに必要なコマンドを実行するのに必要
※ babel-preset-env:Babelに何をパースすべきか伝える

2 ) package.json / .babelrc / index.js を以下のように作成
キャプチャ.JPG
3 ) 「npm run start」 でコンパイルしてBabelの動作確認

> npm run start

> graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql
> babel-node src/index.js

hello

4 ) GraphQL Yogaのセットアップ

GraphQLはクエリがどのように働くかを決めているだけで実装そのものではない。
つまり、環境によってどのように実装するかを決めるのは開発者の仕事。

例えるなら、JavascriptがECMAスクリプトでどのように働くかを記述されており、そのJavaScriptを動かすためにChromeはV8エンジンを、MozillaはSpiderMonkeyを実装しているといった感じ。

ということで、今回はまずGraphQLがNodejs上で動くような環境作りから始める必要があり、そのために GraphQL Yoga を使用する。GraphQL Yoga は多機能&高性能&簡単で人気。

> npm i graphql-yoga

ダミーのデータをサーバーに準備しつつセットアップ。

index.js
import { GraphQLServer } from 'graphql-yoga';

// Type definition (Schema)
const typeDefs = `
    type Query { 
        hello: String!
        name: String!
    }
`

// Resolvers 
const resolvers = {
    Query : {
        hello() {
            return 'Hello GraphQL'
        },
        name() {
            return 'Hugo'
        }
    }
}

const server = new GraphQLServer({
    typeDefs,
    resolvers
})

server.start(() => {
    console.log('server is up');
})

  1. typeDefs でサポートする型を定義する(スキーマの定義)
  2. resolvers でクエリに対する処理を定義する
  3. serverのインスタンスを作成する
  4. serverを立ち上げる

※ TypeDefsは「クエリ名: 型!」で定義する(末尾の「!」が無い場合は null 可能ということ)

5 ) 実行

>npm run start
> graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql
> babel-node src/index.js

server is up

この状態で「localhost:4000」につなぐと GraphQL の Playground につながるので、クエリの実行と Hello GraphQL を確認する。
キャプチャ.JPG

6 ) ついでに Live Reload セットアップ
プロジェクトを保存する度にサーバーを自動でリロードするように設定する。

> npm install nodemon --save-dev

nodemonをインストールしたら package.json の scripts を以下のように書き変える。

package.json
  "scripts": {
    "start": "nodemon src/index.js --exec babel-node"
  }

index.jsを自動で実行しなおす、babel-nodeを通して。という意味になる。
これで「npm run start」を実行することで自動リロード状態になる。

GraphQL Types

【Scalar Type】

5つのビルトイン type が存在する。
1. ID: ユニークなid
2. String: 文字列
3. Int: 整数
4. Float: 小数
5. Boolean: true/false

ScalarType
// Type definition (Schema)
const typeDefs = `
    type Query { 
        id: ID!
        name: String!
        age: Int!
        height: Float!
     isAdult: Boolean!
    }
`

【Custom Type】

開発者が定義する type。

CustomType
// Type definition (Schema)
const typeDefs = `
    type Query { 
        me: User!
    }
    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
    }
`

// Resolvers 
const resolvers = {
    Query : {
        me(){
            return {
                id: '12345',
                name: 'Hugo',
                email: 'hugo@example.com',
                age: 27
            }
        }
    }
}
`

※ クエリの形に注意
キャプチャ.JPG

Resolver Operation Arguments

index.js
// 引数に '!' が有る場合、その引数は required
const typeDefs = `
    type Query { 
        greeting(name: String, position: String): String!
        add(a:Float!, b:Float!): Float!
    }
`

const resolvers = {
    Query : {
        greeting(parent, args, ctx, info) {
            if(args.name && args.position) {
                return `Hello, ${args.name}! You are my favorite ${args.positino}`;
            } else {
                return 'Hello!'
            }
        },
        add(parent, args){
            return args.a + args.b
        }
    }
}

キャプチャ.JPG

Resolverには4つの引数が存在する

  1. parent : Type間にリレーションがある場合の親を指す
  2. args : operation arguments supplied
  3. ctx : contexutual data (ユーザの情報)
  4. info : サーバーに送信されたアクションの情報など

Array Type の扱い

【Arrays of Scalar types】

index.js
const typeDefs = `
    type Query { 
        add(numbers: [Float!]!): Float!
    }
`

const resolvers = {
    Query : {
        add(parent, args, ctx, info){
            if(args.numbers.length === 0 ) {
                return 0
            } 
            return args.numbers.reduce( ( accumulator, currentValue ) => accumulator + currentValue )

    }
}

キャプチャ.JPG

【Arrays of Custom types】

index.js
// Demo data
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'mike@ex.com'
}]

// Type definition (Schema)
const typeDefs = `
    type Query { 
        users(query: String): [User!]!
    }
    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
    }
`

const resolvers = {
    Query : {
        users(parent, args, ctx, info) {
            if (!args.query){
                return users
            } else {
                return users.filter(user => user.name.toLowerCase().includes(args.query))
            }
        }
}

キャプチャ.JPG

Relation

Type の ID を追加することでリレーションが生まれる。

index.js
// Demo data
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'aike@ex.com'
}]
const posts = [{
    id: '4',
    title: 'aaa',
    body: 'asdtfhaer',
    published: true,
    author: '1'   /// 注目
}, {
    id: '5',
    title: 'fff',
    body: 'hrtea',
    published: true,
    author: '1'   /// 注目
}, {
    id: '6',
    title: 'Ahwerike',
    body: 'aikreshe',
    published: true,
    author: '2'   /// 注目
}]

const typeDefs = `
    type Query { 
        users(query: String): [User!]!
        posts(query: String): [Post!]!        
    }

    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
        posts : [Post!]!  /// 注目
    }

    type Post {
        id: ID!
        title: String!
        body: String!
        published: Boolean!
        author: User!   /// 注目
    }
`

そして、Resolverにオブジェクトを追加することでクエリ時にリレーションをもつTypeを引きずってくることができる

index.js
const resolvers = {

    Query : {
        users(parent, args, ctx, info) {
            return users    // call posts() coded below (userの数だけcallする)
        },
        posts(parent, args, ctx, info) {
            return posts    // call author() coded below (postの数だけcallする)
        }
    },

    Post: {
        author(parent, args, ctx, info) {
            return users.find(user => user.id === parent.author) // parentはPost
       // 自分(Post)のauthor(UserのID)とマッチするUserを返却

        }
    },

    User: {
        posts(parent, args, ctx, info) {
            return posts.filter(post => post.author === parent.id) // parentはUser
       // 自分(User)のIDとマッチするauthorをもつPostを返却
        }
    }
}

キャプチャ.JPG

まとめ

index.js
import { GraphQLServer } from 'graphql-yoga';

// Demo data
// Arrayをフィールドに持ったりなんかしないですよ
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27,
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'aike@ex.com'
}]

const posts = [{
    id: '4',
    title: 'About React',
    body: 'intriguing',
    published: true,
    author: '1' // userのID
}, {
    id: '5',
    title: 'About GraphQL',
    body: 'Im now studying',
    published: true,
    author: '1'
}, {
    id: '6',
    title: 'Money',
    body: 'very little',
    published: true,
    author: '2'
}]

const comments = [{
    id: '11',
    text: 'oh my god!',
    author: '1',  // userのID
    post: '4'    // postのID
}, {
    id: '21',
    text: 'jesus christ',
    author: '1',
    post: '4'
}, {
    id: '31',
    text: 'goo goo dloo l doo',
    author: '2',
    post: '5'
}, {
    id: '41',
    text: 'ironman',
    author: '3',
    post: '6'
}]


// Type definition (Schema)
const typeDefs = `
    type Query { 
        users(query: String): [User!]!
        posts(query: String): [Post!]!
        comments: [Comment!]!        
    }

    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
        posts : [Post!]!
        comments: [Comment!]!
    }

    type Post {
        id: ID!
        title: String!
        body: String!
        published: Boolean!
        author: User!
        comments: [Comment!]!
    }

    type Comment {
        id: ID!
        text: String!
        author: User!
        posts: [Post!]!
    }
`

// Resolvers
const resolvers = {

    Query : {

        users(parent, args, ctx, info) {
            if (!args.query){
                return users
            } else {
                return users.filter(user => user.name.toLowerCase().includes(args.query.toLowerCase()))
            }
        },

        posts(parent, args, ctx, info) {
            if (!args.query){
                return posts    // call Post() coded below
            } else {
                return posts.filter(post => post.title.toLowerCase().includes(args.query))
            }
        },

        comments(parent, args, ctx, info){
            return comments
        }
    },

    Post: {
        author(parent, args, ctx, info) {
            return users.find(user => user.id === parent.author) // parentはPost
        },
        comments(parent, args, ctx, info){
            return comments.filter(comment => comment.post === parent.id)
        }
    },

    User: {
        posts(parent, args, ctx, info) {
            return posts.filter(post => post.author === parent.id) // parentはUser
        },
        comments(parent, args, ctx, info){
            return comments.filter(comment => comment.author === parent.id)
        }
    },

    Comment: {
        author(parent, args, ctx, info){
            return users.find(user => user.id === parent.author) // parentはComment
        },
        posts(parent, args, ctx, info){
            return posts.filter(post => post.id === parent.post);
        }
    }
}

const server = new GraphQLServer({
    typeDefs,
    resolvers
})

server.start(() => {
    console.log('server is up');
})

キャプチャ.JPG

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

Node.js&GraphQL part1~基本操作~

GraphQLとは

簡単に言うとモダンなREST APIといったところ。
「クライアント ー REST API / GraphQL ー サーバー」のようにどちらもクライアントとサーバーの橋渡しをしている点と、HTTPメソッドで動く点は同じ。HTTPメソッドで動くということは、GraphQLもRest APIと同様にどのような言語/データベース/クライアントにも対応できるということ。

Rest API との違いは、一つのエンドポイントで好きなデータを問い合わせできるという点。欲しいデータに対してその都度エンドポイントを変える必要が無く、リクエストに対してクライアント側で多様な GraphQL query なるものをくっつけてやれば、それで取得データを決定することができる。

例)
・REST API ⇒ GET/posts/123 とか GET/posts?author=3421 とか
・GraphQL ⇒ POST/graphql (w/ a graphQL query) オンリー

これはつまり以下のように言える
・REST API ⇒ エンドポイントをもとにサーバーが必要なデータを決定する
・GraphQL ⇒ GraphQL queryを利用してクライアントが必要なデータを決定する

GraphQLの利点

1. 速い
GraphQL query の記述を変えればHTTPリクエスト1回で必要なデータをごっそりと取得できる ⇒ 速い

2.柔軟
GraphQL query の記述を変えるだけでエンドポイントを増やさずに取得するデータの量や種類を決定できる(モバイルには少量のデータを送るとか)

3.簡単
一つのエンドポイントしか管理しなくてよいから

Hello GraphQL

サーバー ⇒ クライアント で既存のデータを取得してみる

1 ) プロジェクトの初期化とBabelのインスト-ル

> npm init
> npm install babel-cli babel-preset-env
※ babel-cli:Babelのコンパイルに必要なコマンドを実行するのに必要
※ babel-preset-env:Babelに何をパースすべきか伝える

2 ) package.json / .babelrc / index.js を以下のように作成
キャプチャ.JPG
3 ) 「npm run start」 でコンパイルしてBabelの動作確認

> npm run start

> graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql
> babel-node src/index.js

hello

4 ) GraphQL Yogaのセットアップ

GraphQLはクエリがどのように働くかを決めているだけで実装そのものではない。
つまり、環境によってどのように実装するかを決めるのは開発者の仕事。

例えるなら、JavascriptがECMAスクリプトでどのように働くかを記述されており、そのJavaScriptを動かすためにChromeはV8エンジンを、MozillaはSpiderMonkeyを実装しているといった感じ。

ということで、今回はまずGraphQLがNodejs上で動くような環境作りから始める必要があり、そのために GraphQL Yoga を使用する。GraphQL Yoga は多機能&高性能&簡単で人気。

> npm i graphql-yoga

ダミーのデータをサーバーに準備しつつセットアップ。

index.js
import { GraphQLServer } from 'graphql-yoga';

// Type definition (Schema)
const typeDefs = `
    type Query { 
        hello: String!
        name: String!
    }
`

// Resolvers 
const resolvers = {
    Query : {
        hello() {
            return 'Hello GraphQL'
        },
        name() {
            return 'Hugo'
        }
    }
}

const server = new GraphQLServer({
    typeDefs,
    resolvers
})

server.start(() => {
    console.log('server is up');
})

  1. typeDefs でサポートする型を定義する(スキーマの定義)
  2. resolvers でクエリに対する処理を定義する
  3. serverのインスタンスを作成する
  4. serverを立ち上げる

※ TypeDefsは「クエリ名: 型!」で定義する(末尾の「!」が無い場合は null 可能ということ)

5 ) 実行

>npm run start
> graphql@1.0.0 start C:\Users\xxx\Desktop\GraphQL\graphql
> babel-node src/index.js

server is up

この状態で「localhost:4000」につなぐと GraphQL の Playground につながるので、クエリの実行と Hello GraphQL を確認する。
キャプチャ.JPG

6 ) ついでに Live Reload セットアップ
プロジェクトを保存する度にサーバーを自動でリロードするように設定する。

> npm install nodemon --save-dev

nodemonをインストールしたら package.json の scripts を以下のように書き変える。

package.json
  "scripts": {
    "start": "nodemon src/index.js --exec babel-node"
  }

index.jsを自動で実行しなおす、babel-nodeを通して。という意味になる。
これで「npm run start」を実行することで自動リロード状態になる。

GraphQL Types

【Scalar Type】

5つのビルトイン type が存在する。
1. ID: ユニークなid
2. String: 文字列
3. Int: 整数
4. Float: 小数
5. Boolean: true/false

ScalarType
// Type definition (Schema)
const typeDefs = `
    type Query { 
        id: ID!
        name: String!
        age: Int!
        height: Float!
     isAdult: Boolean!
    }
`

【Custom Type】

開発者が定義する type。

CustomType
// Type definition (Schema)
const typeDefs = `
    type Query { 
        me: User!
    }
    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
    }
`

// Resolvers 
const resolvers = {
    Query : {
        me(){
            return {
                id: '12345',
                name: 'Hugo',
                email: 'hugo@example.com',
                age: 27
            }
        }
    }
}
`

※ クエリの形に注意
キャプチャ.JPG

Resolver Operation Arguments

index.js
// 引数に '!' が有る場合、その引数は required
const typeDefs = `
    type Query { 
        greeting(name: String, position: String): String!
        add(a:Float!, b:Float!): Float!
    }
`

const resolvers = {
    Query : {
        greeting(parent, args, ctx, info) {
            if(args.name && args.position) {
                return `Hello, ${args.name}! You are my favorite ${args.positino}`;
            } else {
                return 'Hello!'
            }
        },
        add(parent, args){
            return args.a + args.b
        }
    }
}

キャプチャ.JPG

Resolverには4つの引数が存在する

  1. parent : Type間にリレーションがある場合の親を指す
  2. args : operation arguments supplied
  3. ctx : contexutual data (ユーザの情報)
  4. info : サーバーに送信されたアクションの情報など

Array Type の扱い

【Arrays of Scalar types】

index.js
const typeDefs = `
    type Query { 
        add(numbers: [Float!]!): Float!
    }
`

const resolvers = {
    Query : {
        add(parent, args, ctx, info){
            if(args.numbers.length === 0 ) {
                return 0
            } 
            return args.numbers.reduce( ( accumulator, currentValue ) => accumulator + currentValue )

    }
}

キャプチャ.JPG

【Arrays of Custom types】

index.js
// Demo data
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'mike@ex.com'
}]

// Type definition (Schema)
const typeDefs = `
    type Query { 
        users(query: String): [User!]!
    }
    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
    }
`

const resolvers = {
    Query : {
        users(parent, args, ctx, info) {
            if (!args.query){
                return users
            } else {
                return users.filter(user => user.name.toLowerCase().includes(args.query))
            }
        }
}

キャプチャ.JPG

Relation

Type の ID を追加することでリレーションが生まれる。

index.js
// Demo data
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'aike@ex.com'
}]
const posts = [{
    id: '4',
    title: 'aaa',
    body: 'asdtfhaer',
    published: true,
    author: '1'   /// 注目
}, {
    id: '5',
    title: 'fff',
    body: 'hrtea',
    published: true,
    author: '1'   /// 注目
}, {
    id: '6',
    title: 'Ahwerike',
    body: 'aikreshe',
    published: true,
    author: '2'   /// 注目
}]

const typeDefs = `
    type Query { 
        users(query: String): [User!]!
        posts(query: String): [Post!]!        
    }

    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
        posts : [Post!]!  /// 注目
    }

    type Post {
        id: ID!
        title: String!
        body: String!
        published: Boolean!
        author: User!   /// 注目
    }
`

そして、Resolverにオブジェクトを追加することでクエリ時にリレーションをもつTypeを引きずってくることができる

index.js
const resolvers = {

    Query : {
        users(parent, args, ctx, info) {
            return users    // call posts() coded below (userの数だけcallする)
        },
        posts(parent, args, ctx, info) {
            return posts    // call author() coded below (postの数だけcallする)
        }
    },

    Post: {
        author(parent, args, ctx, info) {
            return users.find(user => user.id === parent.author) // parentはPost
       // 自分(Post)のauthor(UserのID)とマッチするUserを返却

        }
    },

    User: {
        posts(parent, args, ctx, info) {
            return posts.filter(post => post.author === parent.id) // parentはUser
       // 自分(User)のIDとマッチするauthorをもつPostを返却
        }
    }
}

キャプチャ.JPG

まとめ

index.js
import { GraphQLServer } from 'graphql-yoga';

// Demo data
// Arrayをフィールドに持ったりなんかしないですよ
const users = [{
    id: '1',
    name: 'Hugo',
    email: 'hugo@ex.com',
    age: 27,
}, {
    id: '2',
    name: 'Taro',
    email: 'taro@ex.com'
}, {
    id: '3',
    name: 'Aike',
    email: 'aike@ex.com'
}]

const posts = [{
    id: '4',
    title: 'About React',
    body: 'intriguing',
    published: true,
    author: '1' // userのID
}, {
    id: '5',
    title: 'About GraphQL',
    body: 'Im now studying',
    published: true,
    author: '1'
}, {
    id: '6',
    title: 'Money',
    body: 'very little',
    published: true,
    author: '2'
}]

const comments = [{
    id: '11',
    text: 'oh my god!',
    author: '1',  // userのID
    post: '4'    // postのID
}, {
    id: '21',
    text: 'jesus christ',
    author: '1',
    post: '4'
}, {
    id: '31',
    text: 'goo goo dloo l doo',
    author: '2',
    post: '5'
}, {
    id: '41',
    text: 'ironman',
    author: '3',
    post: '6'
}]


// Type definition (Schema)
const typeDefs = `
    type Query { 
        users(query: String): [User!]!
        posts(query: String): [Post!]!
        comments: [Comment!]!        
    }

    type User {
        id: ID!
        name: String!
        email: String!
        age: Int
        posts : [Post!]!
        comments: [Comment!]!
    }

    type Post {
        id: ID!
        title: String!
        body: String!
        published: Boolean!
        author: User!
        comments: [Comment!]!
    }

    type Comment {
        id: ID!
        text: String!
        author: User!
        posts: Post!
    }
`

// Resolvers
const resolvers = {

    Query : {

        users(parent, args, ctx, info) {
            if (!args.query){
                return users
            } else {
                return users.filter(user => user.name.toLowerCase().includes(args.query.toLowerCase()))
            }
        },

        posts(parent, args, ctx, info) {
            if (!args.query){
                return posts    // call Post() coded below
            } else {
                return posts.filter(post => post.title.toLowerCase().includes(args.query))
            }
        },

        comments(parent, args, ctx, info){
            return comments
        }
    },

    Post: {
        author(parent, args, ctx, info) {
            return users.find(user => user.id === parent.author) // parentはPost
        },
        comments(parent, args, ctx, info){
            return comments.filter(comment => comment.post === parent.id)
        }
    },

    User: {
        posts(parent, args, ctx, info) {
            return posts.filter(post => post.author === parent.id) // parentはUser
        },
        comments(parent, args, ctx, info){
            return comments.filter(comment => comment.author === parent.id)
        }
    },

    Comment: {
        author(parent, args, ctx, info){
            return users.find(user => user.id === parent.author) // parentはComment
        },
        posts(parent, args, ctx, info){
            return posts.filter(post => post.id === parent.post);
        }
    }
}

const server = new GraphQLServer({
    typeDefs,
    resolvers
})

server.start(() => {
    console.log('server is up');
})

キャプチャ.JPG

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

[JavaScript] Ajaxのテスト時にFormからのSubmit送信に切り替えてデバッグする方法

こんにちわ。

最近、Ajaxで処理することが多いです。
やはり、UXを考えると、Ajax使いまくらないと。。。ですよね。

しかし、Ajaxの開発をしていると、サーバーサイドの連携部分でエラーが発生することがありますよね。
この場合、皆さんはどのようにデバッグをされているのでしょうか?

色々検索してみたのですが、これといった方法が見つからず、、、でした。
そこで、私はいつもは以下の方法でデバッグをしています。

問題がないとは言い切れませんが、デバッグするには十分かな、と考えています。

①まずは、エラーをResponseから取得し、Consoleに吐き出す

JQueryで、以下のように書きます。

$.ajax({
   type : 'POST',data: data,url : url,dataType : "json",
   success : function(json) { fn_callback(json);},
   error : function(XMLHttpRequest, textStatus, errorThrown) {
    console.log("XMLHttpRequest : " + XMLHttpRequest.status);
    console.log("textStatus : " + textStatus);
    console.log("errorThrown : " + errorThrown.message);
    //↓ここがキモ。本番では出力しない。
    console.log("responseText : " + XMLHttpRequest.responseText);
    return false;
}
});

最後のresponseTextというところを出すと、エラーログが見れるので、ここで解決することが多いです。
この辺は、検索すると出てくる方法ですね。

②GETに切り替えて、URLを取得し、直接ブラウザから叩く

でも、上記でも、原因がよくわからない場合ってありますよね。

この場合に、本当にもっといいやり方ないのかな、とは思いつつ、以下のようなやり方をしてます。

まず、送信typeをPOST => GETに変更します。
そして、サーバーサイドで、わざとエラーを起こします。(存在しない関数を呼んだりして)

そうすると、GETでアクセスしたパラメータ付きURLが取得できるので、
そのURLをコピーして、ブラウザで叩く、という方法。

そして、サーバーサイドのエラー箇所を特定していきます。
ここまでで大体潰せます。

③POSTデータをFormに埋め込みサブミットする

しかし、GETは送信できるデータに上限がありますよね。
そういう時は稀ですが、これが必要な場合は、formを作ってサブミットします。

でも、Ajaxで大量データを送る時って大抵、配列やオブジェクトで送ることが多いです。
そうすると結構面倒ですよね。

例えば、こんなデータを送る時、

{'a':123,'b':{'b1':'bbb1','b2':'bbb2'},'c':['x','y','z']};

いちいち手作業でformを作るのは不便ですよね?
(その結果、全然大したことないバグだったりすると・・・。)

たまたま、今回送信していたデータがこれ以上に複雑なデータだったので、
汎用的に送信する関数を作りました。

やってることはシンプルで、配列かオブジェクトなら、再帰的に階層を辿り、
hiddenタイプのinputに納めて送信する、という方法(ただの力技)です。

function makeForm(object,GetPost,url,id){
    var keylist = {};
    function extendObject(txt, key){
        if(typeof txt == 'object' && txt !== null){
            for(var i in txt){
                var tmpkey =  (!key)?i:key + '['+i+']';
                extendObject(txt[i], tmpkey);
            }
        }else{
            keylist[key] = txt;
        }
    }
    extendObject(object, '');

    var form = document.createElement("form");
    form.id = id;
    form.action = url;
    form.method = GetPost;

    for(var name in keylist){
        var ip = document.createElement("input");
        ip.type = 'hidden';
        ip.name = name;
        ip.value = keylist[name];
        form.appendChild(ip);
    }

    document.body.appendChild(form);
};

この関数を↓の感じで呼んでください。

var object = {'a':123,'b':{'b1':'bbb1','b2':'bbb2'},'c':['x','y','z']};
object['_token'] = $('meta[name="csrf-token"]').attr('content'); //CSRFトークンをセット
makeForm(object,'POST','dummyurl/test','submit_form');

$('#submit_form').submit()

これで、デバッグに困ることはあまりないかな、と。

ただ、こんな力技しかないのでしょうか?

もっと良いデバッグ方法があれば知りたいのですが。。。
これで十分間に合っている感はあります。

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

JWTでセッション管理してはいけない

世の中にはJWT(JOSE/JWS/JWE)でセッション管理をしてはいけないという記事が2017年から山ほどあるのに、なぜかJWTでセッション管理をしようとする人がいる。翻訳記事だったり暗号の説明が長すぎたりして、JWTをセッションに使ってしまうような人の心に刺さってないんじゃないだろうか。

前提

JWTでセッション管理というのは、暗号化したトークンをブラウザのCookieに持たせて、サーバー側ではトークンを復号化してユーザー判定などのセッション管理を行うことである。サーバー側で[sessionId: userId]のペアを管理する必要がないのでステートレスに扱えてスケールしやすいというメリットがある。

問題

すごく便利な図があったのでまずこれを読んで欲しい。セッション方式の策定/設計をする職位の人ならすんなり読めると思う。

jwt-sessions.png

co3k.orgより引用 1

左から4番目Local Storageは読んでそのままこれ以上補足することもない。残りの部分は下記の2点に集約される。

明示的にログアウトするにはサーバー側の秘密鍵の変更が必要

このため次の二択を迫られる。

  • 誰か一人でもログアウトしたいときは全員ログアウトになる(秘密鍵を更新した場合)
  • なりすましログインが発覚しても、トークンの有効期限が来るまではそのセッションは無効にできない(秘密鍵を更新せずに耐える場合)

これが許されるのは金銭の絡まないサービスか、ユーザーに損害があっても運営がポイントを配れば済む(詫び石対応)類のサービスなどに限られるだろう。でもセキュリティは固いにこしたことはない。仮想通貨取引サイトで後者を選択したら、犯人が外部に送金していくログを指を咥えて見ているしかない。

従来のセッション管理の方がマシ

前項の不便さを回避するために、次のような回避策を考えるかもしれない。

  • 明示的にログアウトしたユーザーを無効なトークンとして管理する
  • ユーザーがパスワードを変えたらトークンも再発行の必要があるようにする

結局のところサーバーサイドでのステートフルなセッション管理であり、JWTのメリットであるステートレスを捨てている。新規に実装するよりも、従来の枯れているセッション管理実装を使ったほうがわかりやすくバグの見落としも少ないだろう。

認証

セッション管理ではなくOAuth認証などに使うのは問題ない。一回認証したら終わりなので、有効期限を短くしておけば済む。「一人のために全員ログアウト」のような不便は発生しない。

所感

一部実装がalgヘッダにnone(暗号化なし)を許可していただとか、どの暗号化方式にどんな弱点がとか、話題が分散するとどこが重要なのかわかりにくくなる。RFCを読めなどと言えば拒否反応を示す人もいるのである。正しく手短に伝えるのは難しいですね。

参考URL


  1. オリジナルの英語版は@joepie91氏 

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

初心者めも

JavaScriptでは関数は変数と変わらない。
Javaでオブジェクトを変数に入れられるのと同じように、JavaScriptでは関数を変数に入れられる。

よくある書き方

function hogefunc() {
    document.write("よくある書き方");
}
hogefunc(); /* 『よくある書き方』と表示される */

変数に代入する書式

var hogefunc2 = function() {
    document.write("変数に代入する場合と同じ書式でかく");
}
hogefunc2();/* 『変数に代入する場合と同じ書式でかく』と表示される */

unction hogeという宣言はhogeという関数入りの変数を作ったということ。

関数を戻り値で返す関数

var sotofunc = function() {

    return function() {
        document.write("関数を返す関数");
    }
}

var func = sotofunc(); /* return の横にある関数が、func変数に代入される */
func(); /* 代入された関数が実行され『関数を返す関数』が表示される */

関数が変数と同じならば、他の数値や文字列と同じように扱うことができる。
たとえば関数の戻り値を関数にする方法などが可能。

関数を引数で関数に渡す

var hogefunc3 = function(func2) {
    func2();/* 引数で渡された何かしらの変数を関数として実行する */
}
/* 関数を引数で渡す。『関数を引数で受け取る関数』と表示される */
hogefunc3(function(){ document.write("関数を引数で受け取る関数"); });

関数も変数だから、引数として関数を渡すことができる。
これの使いドコロは、あらかじめ共通部分はつくっておいて、処理部分を後から作るとかができる。

関数内で生き残る変数(クロージャ)

var hogefunc4 = function() {
    var num = 0; /* 0を持つ変数を宣言 */

    return function() {
        document.write(num);
        num++;

var clfunc = hogefunc4(); /* returnの横にある無名関数を変数に代入する */
/* numはスコープから外れているので、clfuncに代入された関数が実行後も変数は生きている */
clfunc(); /* 0と表示される */
clfunc(); /* 1と表示される */
clfunc(); /* 2と表示される */

JavaScriptでは関数を関数内に定義することができる。
ある関数が関数の外にある変数にアクセスできるが、関数内にネストされた関数でもそれはできる。

関数を返す関数hogefunc4内にnumという変数があるが、
通常numの寿命はhogefunc4が実行されている間だが、
戻り値で返される関数がnumを使っている場合numの寿命が伸びる。

戻り値で返される関数が生きている間はnumは生き続けるし、またnumは戻り値関数のスコープの外にあるから
状態が保持される。

この場合、関数を実行するたびインクリメントされる。
状態を持った関数を作ることができる。

こういうものをクロージャと呼ぶ。

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

javascriptから別のjavascriptの処理を呼出し実行する

経緯、目的

会社で運用しているWebシステムのつくりが悪く、
ほとんど同じ処理にも関わらず、画面ごとに別々のjsを作って呼び出しているため、
改修案件が発生すると何本も同じような修正が必要になり、開発コストも障害リスクも高い。
そのため、共通した部分の処理を切り出して別のjsファイルを作成し、
それぞれのファイルから必要に応じて呼び出すようにできないかと考えた。

試したこと

共通関数func_a.jsの関数の処理結果を個別処理func_b.js経由で取得し、画面main.htmlに表示する。
というごくごく簡単なモノですが・・・
※共通関数func_a.jsの処理(関数)は、windowオブジェクトのグローバル変数としてセットする。

ソース

main.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="Shift_JIS">
    <title>JS共通化サンプル</title>
    <meta name="description" content="">
    <meta name="keywords" content=",,,,">
    <meta name="viewport" content="width=device-width">
    <script src="js/func_a.js"></script>
    <script src="js/func_b.js"></script>
  </head>
  <body>
    <h2>JS共通化サンプル</h2>
    <div>func_a.jsより、func_b.jsの処理を呼び出し実行する</div><br>
    <input type="button" value="テスト" onClick="pushBtn()"/><br>

  </body>
</html>
func_a.js
(function() {
    function hoge() {
        fuga = 'fuga';
        return fuga;
    }
    window.hogeLib= window.hogeLib|| {};
    window.hogeLib.hoge = hoge;
})();
func_b.js
function pushBtn() {
    // func_a.jsのhogeを呼び出す処理
    var hoge = window.hogeLib.hoge();

    //文字列がアラートに表示される。
    alert(hoge);
};

画面イメージ

◆画面表示時
image.png

◆「テスト」ボタン押下時(アラート表示)
WS000013.JPG

結果

ごくごく簡単なサンプルですが、あるjsから別のjsの処理を呼び出すことができることが確認できました。
(そんなコトをするより、各jsの差異を取り込んだ1本のjsに集約した方がむしろ早いし分かり易い、
という意見が社内的には強いですが。)

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

POST.body を Json形式で渡す方法[python-by.bottle]

form内のinputタグ全てをjsonにして...

POSTは、formデータをサーバへ送信する際に活用するタグで、その中のinputタグに含まれるデータをサーバ側で受け取るのは、特に面倒だったりもする...
例えば、<input type="text" name="id" id="id">とか、<input type="text" name="id" id="name">とか...紛らわしい上に扱いづらい、そしてサーバ側の言語には予約されていたりするので、さらに変数名を安易に考えられたものについては面倒...
そこで単純に、jsonデータにして受け渡し、サーバ側でも受け取りをjson形式にする事で、加工や挿入、代入などが可能になり、処理もフロント側だけでほぼ完結したりもできる。

準備するもの

サーバサイド

  • Python3.6<=x
  • Tinydb
  • bottle

フロントエンド

  • javascript
  • html
  • Chrome

実際のコード


実際と言いつつも、実行はしていないwけど、多分、動く...
※コメント頂ければ、修正します。

これらは、ただ抜粋しただけだから動かない可能性もあるが、
ポイントを押さえておけば問題ないはず...
まずは、js側から...その次に、サーバ側の処理について...説明したいと思う。

app.py
import bottle
from bottle import request
from tinydb import TinyDB as tydb


@route("/")
def index():
    index = """
       <!DOCTYPE html>
        <html lang="ja">
          <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Post 2 Json</title>
          </head>
          <body>
               <form action="/db" method="POST" id="form_cnts" name="form_cnts" enctype="multipart/form-data">

            USER: <input type="text" name="user" id="user">
            <br>
            PASSWORD: <input type="text" name="paswd" id="paswd">
            <input type="hidden" name="secret_key" id="secret_key" value="b42c66d06dc047e191aab859429a0105">
            </form>
          <script>
             /*---------------------------------------------------- $ ---------------------------------------------------------------*/
             /** ID と Classを変換する関数
              * $('#id')
              * @param  {String} - let id_name = $('#id')
              * @return {Object} - id-tag
              */
             if (String.prototype.$ == undefined){
               $ = (x) => {
                 const idn = /^#/g
                 if (typeof x == "string") {
                   if(x.match(idn)) return document.getElementById(x.replace(idn, ""))
                 }
                 return x
               }
             }

          let PostDB = (e) => {
             e.preventDefault()
             let JD           = {}

             let FORMCNTS     = $("#form_cnts")
             let INPUT_DATA   = document.getElementsByTagName('input')
             for(let d of Object.values(INPUT_DATA)){JD[d.name]=d.value}
             let JSON_DATA    = '['+JSON.stringify(JD).toString()+']'
             let CUSTOM_HEADER = "CUSTOM-HEADER-NAME"
             let customHeaders = new Headers({'Content-Type': 'application/json','X- 
             Custom-Header': CUSTOM_HEADER })
             let customInit = { method: 'POST',
                             headers: customHeaders,
                             Accept: 'application/json',
                             mode: 'cors',
                             cache: 'default',
                             credentials: 'same-origin',
                             body: JSON.stringify(JSON_DATA)}
            let url = FORMCNTS.action

            let customRequest = new Request(url, customInit)
            fetch(customRequest)
            .then((response) => {
              return response.json()
            })
            .then((resp) => {
              if(resp.status == "OK") {
                for(let d of Object.values(INPUT_DATA)){d.value=""}
                let fade_div          = $("#fade_div")
                fade_div.innerText    = resp.status
                fadeIn(fade_div,1500,function(){
                 fadeOut(fade_div,1500,function(){
                  })
              })
              }
             })
             .catch(function(error) { console.log(error) })
          }
          </script>
          </body>
        </html>
    """
    return index

@post('/db')
def do_db():
    TYDB      = f'{TMP_PATH}/{DATABASE["NAME"]}.json'
    _status   = "NG"
    json_data = {"status": _status}
    if request.json != "":
        with tydb(TYDB) as db:
            json_data  = json.loads(request.json)
            db.insert(json_data[0])
        _status = "OK"
    try:
        reurl = json_data
        request_data = {
            "headers":request.headers['Content-Type'],
            "json": json_data,
            "MEMFILE_MAX":request.MEMFILE_MAX,
            "remote_route":request.remote_route,
            "is_xhr":request.is_xhr,
            "remote_addr":request.remote_addr,
            "url":request.url,
            "method":request.method,
            "status":_status,
            "User_Agent":request.get_header('User-Agent')}

        response.content_type = 'application/json'
        r = HTTPResponse(status=302)
        r.set_header('Location', reurl)

        return dumps(request_data, ensure_ascii=False, indent=2)
    except:
        abort(500)

if __name__ == "__main__":
    bottle.run(host='0.0.0.0', port=8080)

と、実行されれば、form内のデータがデータベースに登録されるだろう...
まずは、jsの説明から...
ここが重要で、TagName=inputを全て、INPUT_DATAへ格納する。
その後、for文で全て抜き出す。その際、name(key)とvalueで紐付けしながら格納する。
もしかすると、もっといい方法があるだろう...

...
let INPUT_DATA   = document.getElementsByTagName('input')
for(let d of Object.values(INPUT_DATA)){JD[d.name]=d.value}
...

その後、一度連想配列にする。
何故か?は、サーバ側の処理を確認するといいだろう。
日本語などがあると通常通り引き渡せない事があったので、救済処置として、このような形にしてみた。

let JSON_DATA    = '['+JSON.stringify(JD).toString()+']'

あとは、非同期処理のfetchを使ってPOSTすればいい。
その時重要になるのが、Content-Typeだ。これをjsonにしておかないと、受け渡しの際に、サーバ側で処理が複雑になってしまう。

... customHeaders = new Headers({'Content-Type': 'application/json',...

最後となるが、サーバ側の処理をみてみよう。

...json.loads(request.json)
...db.insert(json_data[0])

と、上記のようにjson形式で呼び出した上で、データベースへ格納する際には、連想配列にて、[0]とし引き渡す事で、str処理などを気にせず、格納できる。
色々試したのだが、連想配列以外での受け渡しだとうまく受け取れないdictだったり、strだったり...typeが異なり、受け取りの処理が複雑かつ、面倒になりがちだった...

ひとまず、これで落ち着くと思う。

以上。

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

Electronで半透明なウィンドウを作る方法 2019年版

Electronで半透明なウィンドウを作る方法を調べても古い方法しかなかなか見つからなかったので、最新のやり方をメモしておきます。
2019/03 現在のバージョン(4.1系)ではウィンドウを半透明にするためにCSSを使う必要はありません

2つ方法があります。

方法1

win = new BrowserWindow({
  width: 800,
  height: 600,
  opacity: 0.5 // これだけ!
})

これだけでウィンドウが半透明になります。1で不透明、0で完全に透明です。

方法2

win = new BrowserWindow({
  width: 800,
  height: 600,
  transparent: true, // これと
  backgroundColor: '80FFFFFF' // これだけ!
})

こちらの方法の場合、背景色だけが半透明になるので、その上の要素(文字など)は半透明になりません。
backgroundColorの値はCSSと違いARGB(Alphaが最初)な点に注意しましょう。

参考

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

ES6のimportとexport

myModule.js
const message = 'some message'
const name = 'Hugo'
const location = 'Tokyo'
const getGreeting = name => {
    return `welcome ${name}`
}

export { message, name, getGreeting, location as default }   
index.js
import myCurrentLocation, { message, name, getGreeting } from './myModule';
console.log(myCurrentLocation);  // Tokyo
console.log(message, name);      // some message Hugo
console.log(getGreeting(name));  // welcome Hugo

・Named export  ⇒  import先で名前を変えられない
・Default export  ⇒  import先で名前を変えらる

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

document onloadをjQueryなしで書く

jQueryでおなじみの

$(function(){
  ...
});

をjQueryなしで書くにはどうしたらよいのか迷ってしまいましたので投稿します。

結論、

document.addEventListener('DOMContentLoaded', function(){
  ...
});

のように書きます。

参考 DOMContentLoaded - イベントリファレンス | MDN

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

JavaScript基礎文法まとめ

0.背景

Webアプリが作るために、今更ながらJavaScriptの文法をお勉強。
C Sharperなので、違和感ほぼ無し。

1.基本的なJavaScript実行方法

index.html:JavaScriptを適用するHTML

index.html
<!DOCTYPE html>
<html lang="ja">
  ~ headタグ中略 ~
 <body>
    <script src="main.js"></script>     ← ファイル指定
  </body>
</html>

main.js:JavaScriptファイル

main.js
"use strict"                             エラーチェック有効

console.log("Hello JavaScript")

[実行結果の確認 @ Chrome]

① Chromeでデベロッパーツールを起動する。

tool1.png

② Consoleで実行結果を確認する。


tool2.png

2.基本的なJavaScript文法

① コメント記載方法

// 一行コメント

/* 
   複数行コメント
*/

② 正規表現

.       // 改行以外の任意の文字 
\n      // 改行文字 
\r      // キャリッジリターン文字
\t      // タブ文字
\b      // 単語の区切り(単語の開始または終了)
\B      // 単語の区切り以外
\d      // 任意の数字 ( [0-9]と同じ)
\D      // 任意の数字のもの( [^0-9]と同じ)
\s      // シングル空白文字(スペース、タブ、改行など) 
\S      // シングル空白文字以外の文字 
\w      // 単語文字( [A-Za-z0-9_]と同じ)
\W      // 単語文字以外の文字( [^A-Za-z0-9_]と同じ)

③ 関数

main.js
"use strict"

function sum(a, b, c)           3つの引数の合計を返す関数を定義
{
  return a+b+c;
}

for (let i = 1; i <= 5; i++)    Forループ
{
  console.log(`${i} + ${i+1} + ${i+2} = ` + sum(i, i+1, i+2));
}

[実行結果]

result.PNG

情報ソース

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

Portalsをさわってみた

背景

Portalsとは

  • <portal>という新しいHTMLタグのことです
  • <iframe>のように別のページを埋め込むことができます
  • <iframe>と違うところは表示したページに遷移させることができる点です
  • すでにページ上に埋め込まれているページに遷移するため高速に遷移することができます
  • ただし<iframe>のように埋め込んだ中のページを操作することはできずプレビューのみといったイメージです

作ったもの

  • 言葉だけだとよくわからないと思うのでデモアプリを載せます

demo.gif

  • mouseEnter/mouseLeaveでPortalsの表示/非表示を切り替えてクリック時に遷移するように実装してあります
  • 普段使い慣れているのでReactで書いています
  • コードあまりきれいではないですがリポジトリはこちらです
  • デモアプリはこちらです
  • ※ChromeCanaryを引数付きで起動しないと動作しません

    • 上記ブログ記事より引用

    現状 Chrome Canary で Portal は試せます。起動フラグを付けて Canary を立ち上げてください。
    Mac: open -a Google\ Chrome\ Canary --args --enable-features=Portals
    Windows: ショートカットを右クリック、リンク先に --args -enable-features=Portals のオプションを付けて起動。
    Linux: Canary は Linux ではサポートされていません。代替として Chromium をご利用ください。

Portalsの使い方

  • デモアプリではややこしいことをしていますが単に使うだけであればとても簡単です
  • 以下のファイルを作成してブラウザで開いてみて下さい
    • ※ChromeCanaryを引数付きで起動するのを忘れずに
  • ①ページを表示するとQiitaのトップページが表示されます
  • ②画面をクリックするとQiitaへ遷移します
index.html
<portal src="https://qiita.com" style="height: 1000px; width: 1000px"></portal>
<script>
  const portal = document.querySelector('portal');
  portal.addEventListener('click', () => portal.activate());
</script>

demo2.gif

①ページを表示するとQiitaのトップページが表示される

  • <portal>タグのsrc属性に表示したいURLを設定するだけで表示することができます
  • 上のデモで表示/非表示を切り替えていたのはタグ自体を挿入したり削除したりしていただけです

②画面をクリックするとQiitaへ遷移する

  • Portalsでページ遷移させるためにはJavaScriptによる操作が必須となります
  • <portal>のDOMを取得し.activate()を実行するとPortal内に表示しているページに遷移することができます

まとめ

  • Portalsはiframeのようなものと冒頭で紹介しましたが、単に別のページを埋め込むだけでなく新たなUXの実現ができるような面白いタグだということがわかりました
  • まだ一般的なブラウザでは動作しませんが実装はとても簡単なので今後が楽しみです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npmライブラリの開発環境

概要

最近、Three.jsなどでよく使うオブジェクトはなんとなくモジュール化してコピペで複数のプロジェクトで使用していたりするのですが、コピペだとやっぱめんどくさいしせっかくならnpmで簡単にダウンロードできるようにして起きたいよね!ということで試してみました。
同時に、npmパッケージの開発環境ってどうするんだろうか...と試してみた記録です。

とりあえずnpmにあげてみよう

開発環境のことは考えずに、適当に作ったモジュールをnpmにあげて見たいと思います。
試しに、three.jsで回転する立方体(を持つモジュール)のパッケージを作ります。

ソースコードはこちら

1.npmにサインアップ&ログイン

下記からサインアップをします。
https://www.npmjs.com/signup

ログインします。

$ npm login

2.npmを初期化

初期化する前に、パッケージの名前が他の公開されているパッケージと被らないよう、先に検索をかけて確認します。
image.png

プロジェクトのディレクトリに移動し、npmを初期化します。

$ npm init

初期設定を行います。

package name: (threerotatecube) three-rotate-cube
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository: (https://github.com/Obshi/ThreeRotateCube.git)
keywords:
author:
license: (ISC)

依存パッケージをインストール

今回はThree.jsを使っているのでthreeをインストールします。
pakage.jsonに書き込むだけでも良いのかな...

$ npm install --save three

公開

公開します。

$ npm publish

確かめた

import * as THREE from 'three';
import RotateCube from 'three-rotate-cube'

window.THREE = THREE;

export default class ThreeGraphic{
    constructor(){
        this.canvas = document.querySelector('#canvas');
        this.renderer = new THREE.WebGLRenderer({
            canvas: this.canvas,
        });
        this.renderer.setSize(window.innerWidth,window.innerHeight);
        this.renderer.setPixelRatio(1);

        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 1000);
        this.camera.position.set(0,2,5);
        this.camera.lookAt(0,0,0);

        this.rotCube = new RotateCube();
        this.scene.add(this.rotCube.obj);
        this.animate();
    }

    animate(){
        if(this.rotCube){
            this.rotCube.update();
        }
        this.renderer.render(this.scene,this.camera);
        requestAnimationFrame(this.animate.bind(this));
    }
}

動いた!
image.png

パッケージ開発環境

さて、公開自体はとても簡単にすることができましたが、パッケージ自体の開発はどうすれば良いのでしょうか...
テストしながらそのまますぐ公開できる環境欲しい...

npm IN npm

テスト用の大枠のプロジェクトの中にパッケージプロジェクトをそのまま突っ込んでしまいます。

これで...いいのか...?
プロジェクトはこちら

.
├── gulpfile.babel.js
├── package-lock.json
├── package.json
├── public
├── src
│   ├── assets
│   ├── html
│   │   └── index.html
│   ├── js
│   │   ├── MainScene.js
│   │   ├── ThreeController.js
│   │   ├── main.js
│   │   └── utils
│   │       └── ThreeRotateCube (パッケージ)
│   │           ├── index.js
│   │           ├── package-lock.json
│   │           └── package.json
│   └── scss
│       └── style.scss
└── webpack.config.js

こんな感じのディレクトリ構造です。
開発環境中でのインポートはディレクトリをそのまま読ませます。

main.js
import RotateCube from './utils/ThreeRotateCube'

24時間立たないともう一度公開できませんが、おそらく正常に公開できるかと思います。

ukMac:ThreeRotateCube obshi$ npm publish
npm notice
npm notice ?  three-rotate-cube@1.0.0
npm notice === Tarball Contents ===
npm notice 444B package.json
npm notice 329B index.js
npm notice === Tarball Details ===
npm notice name:          three-rotate-cube
npm notice version:       1.0.0
npm notice package size:  515 B
npm notice unpacked size: 773 B
npm notice shasum:        3b267b75c042430bfa59dd49d01b4225c394cc09
npm notice integrity:     sha512-OIV5VXfN+yt4a[...]ZkufvWVWEG0Lw==
npm notice total files:   2
npm notice
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! three-rotate-cube cannot be republished until 24 hours have passed. : three-rotate-cube

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/obshi/.npm/_logs/2019-03-26T17_49_12_695Z-debug.log

しばらくはこんな感じでパッケージ、作っていこうと思います。

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

Googleの力を借りて ”もしかして” する

はじめに

誤字った単語から本来の単語を推測したいときがあるかもしれません。
しかしそれを実装するのは大変でしょう。
そんなときは偉大なGoogleの力を借りましょう。

コード

index.html
<html>
<head>
</head>
<body>
  <form id="form">
    <input type="text" id="query" />
    <div id="result"></div>
    <button type="submit">検索</button>
  </form>

  <script type="text/javascript">
    document.getElementById("form").onsubmit = (e) => {
      e.preventDefault();
      (async () => {
        let result = document.getElementById('result');
        let query = document.getElementById("query");

        result.innerText = "...";

        let res = await fetch(`https://cors-anywhere.herokuapp.com/www.google.com/search?q=${query.value}`);
        let html = await res.text();
        let parser = new DOMParser();
        let doc = parser.parseFromString(html, "text/html");

        result.innerText = (() => {
          let fprsl = doc.getElementById('fprsl');
          if (fprsl) {
            return "もしかして :" + fprsl.innerText;
          }
          for(let e of doc.getElementsByClassName('gL9Hy')){
            let href = e.getAttribute("href");
            if(href && !href.indexOf("/search?q=")){
              return "もしかして :" + e.innerText;
            }
          }
          return "";
        })();
      })()
    }
  </script>
</body>
</html>

実行してみるとこんな感じ↓

See the Pen ywrmPw by gyojir (@gyojir) on CodePen.

説明

Google検索の"もしかして"機能は2パターンある。
・推測結果で検索までしてくれる
image.png

・推測結果の提案だけする
image.png

それぞれのidとclassのパターンから適当にとってくればよい。

ただCORS制約によりGoogleに直接フェッチすることはできない。
そのためcors-anywhereというサービスを介してフェッチしている。

おしまい

どうせすぐに使えなくなると思う。

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