tacamy--blog

JavaScriptを勉強中の人のブログです。

タブを折り返して多段表示するAtomのプラグインをつくった

花金なので、multiline-tabっていうAtomプラグインをつくりました🍻

atom.io

Atomって開いてるタブが多くなると、タブ幅が小さくなりすぎてファイル名が読めない上に、エディタの幅に収まらないタブは横スクロールで画面外に消えてしまって使いづらすぎるので、タブ幅の最小値を大きくして、エディタの幅を超える場合はタブを折り返して表示するためのプラグインです。

https://i.github-camo.com/602640c3d96c274180b08dd6db3d7c02bbc1e1b1/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f746163616d792f61746f6d2d6d756c74696c696e652d7461622f6d61737465722f73637265656e73686f742e706e67

もともと、開いているタブを縦に並べるプラグイン(👇)を使わせてもらっていたんだけど、

GitHub - 1000ch/atom-vtab: Verticalize tabs on Atom.

横並びのままで折り返したいなって思って、上記のコードをパク参考にさせてもらいました。ありがとうございます :bow:

やってることはCSSしか触ってないくらい簡単なんだけど、いろんなテーマで崩れないように調整するのが結構つらかったです。結局、あまりいじらないのがベストだってことになりました。

タブの幅はとりあえず固定になってるけど、本当は設定で自由に変更できるようにしたかったので、時間とれたら実装するかも。でもプルリクも待ってます。

最初はwrap-tabっていう名前だったんだけど、apm publishが最初うまくできなくて、あれこれしてるうちにAPI tokenのキーをうっかり作り直してしまって、権限がなくなって削除もできなくなってしまって残骸になってしまったので、multiline-tabという別の名前に変えました。どうやって消そうかな。

jQuery.ajax()の代替としてFetch APIをざっくり使ってみる

jQueryを使わずにAjaxをしたくて、とはいえ生のXHR(XMLHttpRequest )を扱うのはめんどくさいっていうときに、Fetch APIを使ってみました。そのとき調べたことの覚え書きです。

Fetch APIって?

ここに、Jxck先生のすばらしい記事があります。

jxck.hatenablog.com

正直ぜんぜん理解できてないのですが(🙇)、ものすごくざっくりいうと、

  • jQueryAjax並に簡潔に記述できる
  • Promiseベースの設計で、結果はPromiseで返される
  • XHRよりも細かな制御ができる

みたいな感じなのかなと思いました。

ちなみに、先の記事では単なるXHRの代わりじゃないと記載されてるので、FetchとかFetch APIの理解にはそちらを読んでもらった方がいいかと思います。。

ブラウザサポート状況

Can I use... Support tables for HTML5, CSS3, etcを見る限り、IESafariiOS Safari以外は対応してるみたいです。

私の場合はそれでもよかったので使ってないのですが、pollyfilもあるみたいです。

github.com

GET

次のような感じでGETができます。簡単!

fetch(url)
  .then(response => {
    return response.text();
  })
  .then(body => {
     document.body.innerHTML = body;
  });

先述したとおり、Fetch APIではPromiseが返ってくるため、then()で繋げて書いていくことができます。

レスポンスは、以下のいずれかの形式で取得できます。

  • プレーンテキスト : response.text()
  • JSON : response.json()
  • ArrayBuffer : response.arrayBuffer()
  • Blob : response.blob()

POST

POSTする場合は、fetch()の第二引数にオプションでmethod: 'POST'を指定するだけです。$.ajax()っぽい!

fetch(url, {
    method: 'POST',
    body: new FormData(document.getElementById('form'))
  })
  .then(response => {
    return response.json();
  })
  .then(json => {
    ...
  });

サーバーに値を渡すには、bodyオプションに指定できます。

クロスオリジン通信

オリジンが異なる(別ドメインの)サーバーと通信したい場合は、次のようにCORSを使います。

1. クライアント側で、fetch()modeオプションに'cors' を指定する

fetch(url, {
    mode: 'cors'
  })
  .then(response => {
    ...
  });

2. サーバー側でリクエストヘッダのOriginをチェックする

GET /api HTTP/1.1
Origin: http://xxx.com

3. サーバー側でレスポンスヘッダにAccess-Control-Allow-Originを追加する

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://xxx.com

これで、クロスオリジン通信が可能になります。

Access-Control-Allow-Origin: *APIの場合は1.の指定だけでできると思います。

Cookieを許可

Cookieを許可するには、次のように指定します。

1. クライアント側でfetch()credentialsオプションに'include'を指定する

fetch(url, {
    mode: 'cors',
    credentials: 'include'
  })
  .then(response => {
    ...
  });

2. サーバー側でレスポンスヘッダにAccess-Control-Allow-Credentials: trueを追加する

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://xxx.com
Access-Control-Allow-Credentials: true

独自のリクエストヘッダを追加

とあるAPIを利用するときに、独自のリクエストヘッダを追加して通信する必要がありました。その場合、次のように指定できます。

1. クライアント側でfetch()headersオプションに、オブジェクトの形式で独自ヘッダーを指定する

fetch(url, {
    method: 'GET',
    mode: 'cors',
    headers: {
      'X-MyRequest': 'hoge'
    }
  })
  .then(response => {
    ...
  });

2. サーバー側でプリフライトリクエストのリクエストヘッダを確認する

OPTIONS /api HTTP/1.1
Origin: http://xxx.com
Access-Control-Request-Method: GET,POST,HEAD,OPTIONS
Access-Control-Request-Headers: X-MyRequest

3. 許可する場合は、サーバー側からレスポンスヘッダを返す

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://xxx.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: X-MyRequest

4. 実際のリクエストがクライアントからサーバーに送られる

サーバー側からAccess-Control-Allow-MethodsAccess-Control-Allow-Headersで許可されたものしか送れないという仕組みのようです。

独自のレスポンスヘッダを読み出す

まず最初に、ダメだった方法を。

1. サーバー側から独自のレスポンスヘッダを追加したレスポンスを返す

HTTP/1.1 200 OK
X-MyResponse: hoge

2. クライアント側でresponse.headers.get()にて取得しようとするもできない

fetch(url, {
    method: 'GET',
    mode: 'cors'
  })
  .then(response => {
    console.log(response.headers.get('X-MyResponse'));
  });

この場合、セキュアではないヘッダにアクセスしようとしたと見なされてアクセスが許可されなかったようです。

なお、以下のヘッダには、問題なくアクセス可能です。

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

そのため、次のようにする必要があるようです。

1. サーバー側でアクセスを許可する独自ヘッダをAccess-Control-Expose-Headersで指定する

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://xxx.com
Access-Control-Allow-Methods: GET,POST,HEAD,OPTIONS
Access-Control-Allow-Headers: X-MyRequest
Access-Control-Expose-Headers: X-MyResponse

2. クライアント側でresponse.headers.get()にて取得する

fetch(url, {
    method: 'GET',
    mode: 'cors'
  })
  .then(response => {
    console.log(response.headers.get('X-MyResponse'));
  });

サーバー側のエラーの処理

fetch()では、サーバー側でエラーが起こってもレスポンスはreject()されません。つまりcatch()できないわけです。

では、どういう場合にreject()されるのかというと、ネットワークエラーの場合のみのようです。そのため、サーバーエラーの処理は、then()の中でresponse.okをチェックする必要があります。

fetch(url)
  .then(response => {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response;
  })
  .then(response => {
    ...
  })
  .catch(err => {
    ...
  });

まとめ

jQuery.ajax()が使えるときはそっちのが安心感あるけど、個人プロジェクトで少し触ってみる程度ならよいのかも?

※実際に使ってみたのは1年以上前だし、内容が古いかもしれないです。

package.jsonのnpmのバージョンを一括で書き変えてくれるncuが便利だった

npm-check-updatesというNode.jsのモジュールを使ってみたら便利でしたという話です。

2013年のブックマークとかもあったので、別に新しいものではないと思うけど、私は知らなくて感動したのでした。

なにこれ?

  • package.jsonに書かれているnpmのバージョンを、一括で最新に書き変えてくれるNode.jsのモジュール。
  • ncuコマンドひとつで一度に書き変えできて便利。
  • 実際に書き変える前に確認だけすることもできる。
  • 「メジャーバージョンは固定で、それ以下を最新にしたい」などの細かい設定もオプションで可能。

どんなときに便利なの?

  • 既存のpackage.jsonを流用して、同じnpmをそのまま使いたいけど、バージョンだけは新しくしたいとき。
  • 昔つくったサイトのメンテナンスで、npmのバージョンだけを新しくしたいとき。

使い方

初回のみ、npm-check-updatesをグローバルにインストールする必要があります。

$ npm install -g npm-check-updates

👆によって、ncuコマンドが使えるようになるので、その後は、対象のpackage.jsonのあるディレクトリに移動して、ncuコマンドを実行するだけ。

確認と実行

どう書き変わるのかを確認する

ncuコマンドだけだと、変更前と変更後のリストが表示されるだけで、package.jsonはまだ書き替わらないので安心。

$ ncu

 express           4.12.x  →   4.13.x
 multer            ^0.1.8  →   ^1.0.1
 react-bootstrap  ^0.22.6  →  ^0.24.0
 react-a11y        ^0.1.1  →   ^0.2.6
 webpack          ~1.9.10  →  ~1.10.5

実際に書き変える

-uオプションをつけることで、実際にpackage.jsonが書き変えられる。

$ ncu -u

npmを個別に指定

特定のnpmのみをバージョンアップしたい場合は、対象のnpmの名前を指定するだけ。

$ ncu express, webpack

npmの名前の指定には、正規表現も使えて便利。

$ ncu '/^(?!gulp-).*$/'

バージョンを細かく指定

メジャーバージョンも含めて最新にする

デフォルトではメジャーバージョンも含めて最新になります。

$ ncu

 gulp-eslint  2.0.0  →  3.0.1

メジャーバージョンは固定、それ以下を最新に

--semverLevel majorオプションをつけることで、メジャーバージョンは固定で、それ以下を最新にできます。

メジャーバージョンアップをして、既存のコードが動かなくなるのを避けたい場合などに。

$ ncu --semverLevel major

 gulp-eslint  2.0.0  →  2.1.0

マイナーバージョンまで固定、それ以下を最新に

--semverLevel minorオプションをつけることで、マイナーバージョンまでを固定して、それ以下を最新にできます。

$ ncu --semverLevel minor

 gulp-eslint  2.0.0  →  2.0.1

気の利くところ

既存のpackage.jsonの記法を尊重して、アップデート後も同じように書き変えてくれて気が効くなと思いました。やさしさ。

$ ncu

 1.2 → 1.3
 0.1.0 → 0.2.1
 ^1.2.0 → ^2.0.0
 1.x → 2.x

gulpfile.jsをES6で書いてみよう

なぜgulpfile.jsをES6で書くの?

ES6にとりあえず触れて慣れてみたいという場合に、まずはgulpfile.jsで試してみるとよさそうという風潮があるみたい。理由はたぶん次のような感じかなと思ってる。

  • 他への影響が少ない
  • コードの分量が少ない
  • 実装にあまり頭を使わないで書ける
  • 環境によってはbabel等のトランスパイラをインストールせずにES6がそのまま使える

環境を準備する

gulpfile.jsでES6を使えるようにするには、gulp側で対応するか、Node.js側で対応するかどちらかになります。

gulp側でES6に対応する

gulpはv3.9.0からbabelを標準サポートするようになったので、babelを介してES6構文を利用できるようになりました。

ただし、ES6構文を使う場合は、ファイル名をgulpfile.babel.jsにする必要があります

  • gulpのバージョンを3.9.0以上にupdate
  • gulpfile.jsのファイル名を、gulpfile.babel.jsに変更

Node.js側でES6に対応する

Node.jsはv4.0.0からES6に(一部)対応したので、Node.jsのバージョンが4.0.0以上であれば、そのままES6の構文を使うことができます。

  • Node.jsのバージョンを4.0.0以上にupdate
  • ファイル名はgulpfile.jsのままでよい

ちなみに、この記事はNode.js 5.0.0で動作確認しているので、それ以下のバージョンの場合は動かないものもあるかも?(参考:Node.jsでサポートしているES6の構文の一覧

gulpfile.jsで使いそうなES6構文いろいろ

ES6の構文を使うと言っても、gulpfile.jsでは簡単なものしか使う機会はなさそうですが、実際に使いそうな構文をいくつか挙げておきます。

const / let

varconstまたはletに置き換えられます。

ただし、letはあまり使う箇所はなさそう。

今までの書き方

var gulp = require('gulp');

ES6の書き方

const gulp = require('gulp');

Arrow Functions

function ()() =>と書けるようになります。

本来の外側のthisが使えるという機能はgulpfile.jsの場合あまり関係なさそうで、単にタイプ数が少し減ったり、見た目がスッキリするといったメリットくらいかな。

今までの書き方

gulp.task('copy', function () {
  return gulp.src('src/favicon.ico')
    .pipe(gulp.dest('dist'));
});

ES6の書き方

gulp.task('copy', () => {
  return gulp.src('src/favicon.ico')
    .pipe(gulp.dest('dist'));
});

Template Strings

文字列と変数を結合するような場合に、'string' + fooのように+を用いていた部分を、string#{foo}のように書けるようになります。

今までの書き方

gulp.task('sprite', function () {
  var spriteData = gulp.src('images/*.png').pipe(spritesmith({
    cssVarMap: function(item) {
      item.name = 'b-icon--' + item.name;
    }
  }));
  return spriteData.pipe(gulp.dest('path/to/output/'));
});

ES6の書き方

gulp.task('sprite', () => {
  const spriteData = gulp.src('images/*.png').pipe(spritesmith({
    cssVarMap: item => {
      item.name = `b-icon--${item.name}`;
    }
  }));
  return spriteData.pipe(gulp.dest('path/to/output/'));
});

注意点

gulpfile.babel.jsの場合はbabelを使うのでimportが使えるけれど、gulpfile.jsの場合はimportが使えません。

Node.js(v5.0.0時点)ではimportがサポートされていないからです。そのため、ES6をどうやって実行するかによって(ファイル名によって)、次のように記述が変わります。

ややこしいのでどっちの場合でもimportは使わない方がいいのかもしれません。

gulpfile.babel.js

import gulp from 'gulp'

gulpfile.js

const gulp = require('gulp');

まとめ

とくに問題なく使えたので、これからgulpfile.jsを新たに書く場合はES6の構文でよさそう。

CSSではみ出した文字を省略する「text-overflow: ellipsis;」がいつの間にかPCでも使えるようになってた!

領域をはみ出したときに三点リーダー(…)で省略するtext-overflow: ellipsis;は、スマホサイトでは普通に使っていたけど、PCでは数年前に使おうとしてなんかのブラウザでダメだった記憶があって、使えないものとばかり思い込んでいた。

でも、ふとCan I use CSS3 Text-overflowをみたら、めっちゃ対応してた!IEなんて6から対応してるし、Firefoxも7から使えるようになってた。

実際に書いてみたら、いつものこんな感じのコードで普通に使えました。

overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;

昔、JSでなんとかそれっぽくできないかみたいに、誰かががんばっていたのはなんだったのか。

わたしみたいに使えないと思い込んでて、意外としらない人いるかもしれないので、ブログに書いてみました。

楽天カンタン送料表示(Chrome拡張)を公開したよ!

今週の月曜に突如Chrome拡張機能に興味をもって、火曜に勉強を始めて、木曜の今日Chromeウェブストアに公開しました!(∩´∀`)∩ワーイ

f:id:tacamy:20140123143708p:plain

ウェブストアに出てる!!!感慨深い!!!Developer感アル。

どういう機能かというと、楽天の商品ページにいくと、画面左下に送料をすぐに見られるためのボタンが表示されるというものです。

楽天の送料ページは、http://www.rakuten.co.jp/shopName/info2.htmlで見られるのですが、いろいろ面倒です。

  • 送料を見るために、商品ページから画面遷移が必要
  • 送料以外の情報も一緒に書かれている
  • 長すぎてどこを見ればいいのかぱっと見で分からない
  • ショップによって送料ページへのリンクの位置がバラバラ

これらのことを解決したくて、送料の部分のHTMLだけをスクレイピングして取得してきて、商品ページ内で送料をすぐに見られる機能がほしかったのでつくりました。

オススメの勉強方法

Chrome拡張機能についての勉強は実質2時間くらいしかしてないけど、すごくお手軽につくれてしまった。

事前知識ゼロからだったので、まずはドットインストールで勉強しました。1時間くらい。

これだけでかなり概要がわかって、私が作りたいものはカンタンそうだったので、勉強とかいいからとりあえずすぐにつくろうと思って、ほかにはとくに勉強してないです。

公式サイトには、マニフェストの書き方とか、パーミッションの与え方とか、APIの使い方とかぜんぶここに書いてあるのですが、英語なので困ったら見る程度しか使ってない・・。

技術的なこと

ソースコードGitHub上で公開してます。

jQuery使ってるし、とくに大したことは何もしてないのでアレですけど、次のようなことをしています。

パーミッションの設定

楽天の商品ページのURLはhttp://item.rakuten.co.jp/*なのですが、送料ページはhttp://www.rakuten.co.jp/*ドメインが異なるので、manifestにpermissionsの指定が必要でした。

"permissions": [
  "http://www.rakuten.co.jp/*"
],

スクレイピング

送料部分のHTMLにクラス名なりID名がついてればカンタンだったのですが、案の定テーブルレイアウトでそんなものなかったので、下記のような感じで、見出しにあたる部分の文字列を拾ってきて、そこから次のhrまでの部分をとってくるみたいなことしてます・・。

var $data = $(data)
  .find('font:contains("配送について")')
  .parent()
  .nextUntil('hr');

ショップによって、なぜか微妙にこのへんが違う場合があって、たまにこれで取得できない場合もあります。その場合は、ボタン自体出ないようにしました。

われながらめちゃ便利だと思うので、ぜひ使ってみてください!

手っ取り早く Mac でローカルサーバーを立てる方法

ローカルサーバーを立てると、同じネットワークにつながってる別の端末から簡単にアクセスできるようになるから、スマホの表示確認とかに便利。でもいちいち httpd.conf とかいじるのはダルい。このやり方なら一瞬でできるからちょう楽ちん。

ターミナルでサーバー起動

サーバーのルートにしたいディレクトリまで cd コマンドで移動したら、下記コマンドを実行。

$ python -m SimpleHTTPServer 8888

最後の数字はポート番号で、他に使われていなさそうな番号に適当に変えれば、複数のサーバーを同時に起動できる。

ブラウザで表示確認

localhost で確認

ブラウザのアドレスバーに http://localhost:8888/{ファイル名} と入力して表示確認。

表示できてるなら、ローカルサーバーを立てるのが成功してる。表示できなかったら、ターミナルを見て、さっきのサーバー起動時になんかエラー出てないか確認してググろう。

IP アドレスで確認

localhost だと他の端末からアクセスできないので、IP アドレスを使ってアクセスします。

  1. ターミナルで ifconfig と打つか、ネットワーク環境設定の画面を見て、自分の IP アドレスを調べる。
  2. ブラウザのアドレスバーに http://{IP アドレス}:8888/{ファイル名} と入力して表示確認。

これで見れてれば、同じネットワークにいる別の端末から、IP アドレス経由でアクセスできます。

CTO に教えてもろた。