WordPressで “name lookup timed out”のエラーがでた場合の対処方法

2016年8月2日

テーマ・プラグインの更新の際に、”name lookup timed out”のエラーメッセージが表示される場合には、インストールされているサーバーのDNSの設定を変更することで解消することがあります。レンタルサーバーなどでWPを運用している場合はこの対処方法はつかえないと思いますが、開発環境・テスト環境の場合にはおそらく適応でしょう。

このエラーの原因はWordpressのAPIサーバーにWordpressがアクセスする際にDNSサーバーで名前解決するのに時間がかかっている場合に起きるエラーです(WordpressがPHPのcURLモジュールを使って通信するのですが、そのデフォルトタイムアウトは3秒となっています)。(注:WP4.5.3で確認)

WP-CLIを使っている場合は、コマンドラインでプラグインの追加や言語の追加をガシガシできるので重宝しますが、この場合にもapi.wordpress.orgにアクセスするので、DNSの設定によってはエラーになってしまう場合があります(僕がまさにそうでした)。

% wp core language list
Error: An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="https://wordpress.org/support/">support forums</a>. "name lookup timed out"

僕の場合、開発環境がVagrant上のUbuntuだったので、サーバーのDNS設定にGoogleのパブリックDNS(8.8.8.8)を追加して、解決しました。

最近のUbuntuは直接resolv.confを編集できないので、まずconf.d内のファイル/etc/resolvconf/resolv.conf.d/headを編集:
(直接編集するな、とかいてありますが、大丈夫みたいです)

Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 8.8.8.8

そしてresolvconf コマンドを打てば完了:

$ sudo resolvconf -u
$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 8.8.8.8
nameserver 10.0.2.3

 

Rivets.js – テンプレートとロジックを「分離しない」試み

2016年5月27日

前回、Backbone.Viewのソースを読みながら、UIの設計についてとりとめなく考えてみました。

UIの役割を担うオブジェクトの振る舞いは、極めてシンプルにまとめると:

  1. ユーザー入力による、DOMオブジェクトからのメッセージに対する応答
  2. アプリケーション内部の変化による、モデルオブジェクトからのメッセージに対する応答
  3. これらのメッセージに対する応答としての、DOMツリーの操作

の3つとなります。UIの設計は、この3つをいかに作るか、ということに注力することになりそうです。

Backbone.Viewでこの3つを実装しようとすると、1.についてはBackbone.View#eventsプロパティのハッシュテーブルが司令塔となります。そして、Viewオブジェクトのパブリックメソッドをコールバックとして定義すれば、完成です。2.については、モデルオブジェクトのメッセージをViewオブジェクトがlistenToすればOKです。

それぞれ、コールバックのbind/unbind(あるいはlisten/stopListen)の処理を考慮しなくてはなりません。が、前回Backbone.Viewプロットタイプの実装を眺めたとおり、1.については、Viewオブジェクトの初期化時・setElement時に、DOMエレメント<->コールバック間のbind, unbindを内部で行ってくれるのでアプリケーション側ではイベントのハッシュテーブルとコールバックだけを実装すればOK。2.についても、モデルオブジェクトのライフサイクルに合わせてBackbone.Eventsモジュールが自動的にstopListeningしてくれるので、これまたアプリではviewオブジェクトの初期化時にlistenToするだけでOK。

問題は3.です。

Backboneは、DOMツリーの操作について、一切関与しません。Backbone.View.prototype.renderはなにもしない関数です。なので、アプリケーション側でDOM操作をしなくてはなりません。テンプレートに関してもBackboneは一切関与しません。

このままだと、懸案が出てきます。大雑把に言うと、「DOMツリーの操作をするのに、新たなBackbone.Viewが必要になる」という点です。つまり、ユーザーの入力や、アプリケーションの内部変化に応じて、Viewをダイナミックに生成かつ操作する必要がでてくるのです。Backbone.jsにはその責務がありません。Backbone.jsはミニマルを志向するフレームワーク(どちらかというとモデルレイヤーとそのイベントハンドリングに重きを置いてる気がします)なので、Backboneと付き合う以上は、これはアプリケーション側の責務になります。このビューのオーケストレーションのロジックは、どこに書けば良いか?単にrenderメソッド内でjQueryのapiを駆使しDOMツリーを操作したり、ほかのviewインスタンスを生成する処理を実装し、view同士で分担してDOM操作をする、といったやり方が考えられます。アプリケーションで独自の設計方針を立てなければ、コードのメンテナンスが複雑になる恐れが出てきます。

テンプレートについても考慮が必要です。BackboneはViewの設計と同じく、テンプレートの設計はアプリケーションに全てを委ねています。DOMエレメントに求められる役割が大きくなるにつれて、テンプレートとロジックの書き方にもアプリ側に工夫が求められると考えられます。

話がそれますが、Railsに代表されるサーバーサイドのアプリケーションの設計思想に、ビューとビジネスロジックの分離があります。サーバーサイドのアプリケーションには、当然のことながら、DOMのストラクチャーの操作をする責務がないので、DOMは、アプリケーションの扱うデータの、単なる表現型の1つと捉えられるからです。非同期的なユーザーアクションの要請に応える必要もないので、設計はモデルレイヤーに重きが置かれます。しかし、今問題としているのはDOMストラクチャーの振る舞いの設計です。単なる静的なデータ表現型ではなく、DOMをダイナミックに「動かす」設計が求められるのです。テンプレートにロジックを入れられる、そのようなフレームワークが必要になってきそうです。

この要請に応えるためには、ビューとロジックの分離というサーバーサイド的な思想を一旦クリアし、新たな思想で設計をしたほうがいいんじゃないか、、、ということなのかもしれません。

* * *

前置きがすごく長くなりましたが、これから紹介したいRivets.jsは、軽量な、データバインドとテンプレートとを統合したフレームワークです。Backbone.jsのように、イベント駆動型のモデルレイヤーと相性が良く、さらにフレームワーク非依存なので、モデルの設計に関わらず、データバインディングとテンプレートを統合することができる、、、ということが公式サイトの冒頭に書かれています。

なぜRivets.jsなのか?なぜ、僕が今、Rivets.jsにコミットするのか?

・・・あまり確固とした根拠はありません。強いて言えば、ドキュメントが非常にシンプルでわかりやすかった。そして、Backbone.jsと同様、開発者がとてもcoolである(公式サイトからもその雰囲気が伝わって来る)、少なくともホットでない、冷静な感じがする。欲や流行(フレームワークって、どうしても機能が肥大化しがちなので)に流されず、コードに求められる「責務」をしっかり認識している感じがするんです。

そして何よりもソースコードがcoffeeで書かれている、という点。読みやすいです、coffeescript。curly brace「{」がないだけで、こんなにも視認性が高まるのかと、感心してしまいます。フレームワークが何を意図しているかが、わかりやすく伝わってきます。信頼がもてるのです。

あああ、、結局Rivets.jsについてはほとんど語ることができなかった。新たな記事で、Rivets.jsについて踏み込んでいきたいと思います。See you!

Backbone.Viewをどう使えばいいのか、ソースコードから読みとる

2016年5月19日

Backbone.View。

Backboneの中の登場人物としては、サッカーで言うところのフォワード、、、なのかな。いわゆる「ユーザーインターフェイス」の役割を担うわけですから、まあ、最前線ですよね。「ユーザーインターフェイス」、というからには何かと何かの橋渡しをするのですが、それは「ユーザーの動作」と「モデルオブジェクト(or コレクションオブジェクト)」との橋渡し、ということになります。

もうちょっと具体的に言い換えてみると・・・:

  • 【役割1】ユーザーからのイベント(クリックとかキー入力、etc…)を受け取り、それ対して適切な指示をモデルオブジェクト(orコレクションオブジェクト)に送る
  • 【役割2】モデルオブジェクト(とそのコレクションオブジェクト)の動作(オブジェクトの変更・削除・追加、etc…)に応じて、適切な描画をブラウザに指示する

と考えてみます。ここでキーワードは:

  • 「ユーザーイベント」
  • 「モデルイベント」(もしくはコレクションイベント)
  • 「描画」

の3つに絞ることができそうです。ビュー同士のコミュニケーション(たとえばメインビューがサブビューに描画の指示をだす、など)は、ビューの本来的な動きではなく、ビューの役割をアプリケーションの中で分担するための動きと考えることにします。つまり「イベントの処理」と「描画の処理」がとても重要になってくると考えられます。

では、いったいどんなコンストラクターとプロトタイプが実装されているのでしょうか?ソースコードを追いながら、フレームワークの使い手である我々が、どのようにBackbone.Viewを使えばいいのか、考えてみたいと思います。すごく短いコードなので、僕でも理解できそうだったので。注釈付きの親切なソースは、こちらから見れます。

まず、コンストラクターをみてみましょう。

#javascript
var View = Backbone.View = function(options) {
    this.cid = _.uniqueId('view');
    _.extend(this, _.pick(options, viewOptions));
    this._ensureElement();
    this.initialize.apply(this, arguments);
  };

「オブジェクトにユニークなid(cid)を与え、与えられたオプションを身につけ、initialize()を実行する」と書いてあります、、、が、1つ大事な処理を内部で行ってますね。this._ensureElement();がそれです。注釈を読むと「初期化時にDOMに結びついていない場合にはDOMツリーの外にオブジェクトを生成する」という感じですかね。_ensureElement()のコードを追ってみると「tagNameプロパティからdocument.createElementして、DOMエレメントオブジェクトを生成して、それをsetElementする、elプロパティが与えられている場合はそれをそのままsetElementする」と言えそうです。setElement()はパブリックなメソッドですが、実はコンストラクタも内部的に呼び出しているのですね。

では、setElement()は何をするメソッドなんでしょうか?注釈には「ビューのエレメント(elプロパティ)を変更し、ビューのイベントを新しいエレメントに委譲し直す」と書かれています。ソースを見てみましょう。

#javascript 
    setElement: function(element) {
      this.undelegateEvents();
      this._setElement(element);
      this.delegateEvents();
      return this;
    },
#javascript 
   _setElement: function(el) {
      this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
      this.el = this.$el[0];
    },
#javascript
    undelegateEvents: function() {
      if (this.$el) this.$el.off('.delegateEvents' + this.cid);
      return this;
    },
 #javascript
   delegateEvents: function(events) {
      events || (events = _.result(this, 'events'));
      if (!events) return this;
      this.undelegateEvents();
      for (var key in events) {
        var method = events[key];
        if (!_.isFunction(method)) method = this[method];
        if (!method) continue;
        var match = key.match(delegateEventSplitter);
        this.delegate(match[1], match[2], _.bind(method, this));
      }
      return this;
    },

「与えられたエレメントを、jQueryでラップして$elプロパティにアサインする。また、$elの先頭要素をelプロパティにアサインする。だたし、すでに$elがアサイン済みの場合は、一旦それに委譲されているイベントを全てオフにし、新しくアサインされたエレメントに委譲し直す」と、言葉に翻訳できそうです。

つまり、viewオブジェクトに何かしらエレメントをセットする時(言い換えると、初期化時、もしくはsetElement()を起動する時)には、前にセットされてたエレメントにバインドされてるコールバックを全てアンバインドして(初期化時は関係ない)、もう一度新しいエレメントにバインドし直す、という動作をしていることがわかります。

jeremyはここで「委譲(delegate)」という言葉を使っていますが、これは「viewオブジェクトに定義されたイベントの処理を、jQueryオブジェクトのonメソッドに委譲する」という意味の「委譲」ということといえそうです。実際にdelegateメソッドのソースを覗いてみると$elオブジェクトのonメソッドを呼び出していることがわかります。

 #javascript
   delegate: function(eventName, selector, listener) {
      this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
      return this;
    },

マニュアルを読むと「By default, delegateEvents is called within the View’s constructor for you, so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself. 」とあります。つまり、「初期化時に自動的に呼び出してくれるので、自分で呼び出す必要はないよ」といっています。ここまできて、【役割1】はeventsハッシュ(と、viewオブジェクトに結びついたコールバック関数)を定義すれば、あとは自動的にBackbone.Viewが処理してくれることで果たせそうです。また、viewに紐付いているDOMエレメントそのものを変更する場合も、setElementメソッドを使えば、適切にコールバックをはバインドし直してくれるのでgoodですね。

次にrenderメソッドを見てみましょう。

#javascript
    render: function() {
      return this;
    },

そうですね、空っぽです。注釈には「適切なhtmlをthis.elに埋める(populate)ためにオーバーライドしてね」とかいてあります。また、renderと対になるのがremoveです。こちらは「this.elをDOMツリーの外に出し、stopListenする」と書かれています。また、renderも、removeも自分自身を返しています(実はsetElementやdelegateEventsも自分自身を返しています)。なにかしらチェイナブルな実装を期待しているようです。
こういうことでしょうか。アプリケーション内のview同士は、setElement, render, remove(あるいはdelegateEventsも)のインターフェイスを介して協調動作させることができる、それぞれのインターフェースは内部処理を行った後、自分自身を返すので、さらに数珠繋ぎ的に処理を続けることができる、と。

、、、これでソースコードに書かれていることは全てです。ほんとにシンプルですね。
余談ですが、Jeremyはマニュアルの冒頭に書いています:

Philosophically, Backbone is an attempt to discover the minimal set of data-structuring (models and collections) and user interface (views and URLs) primitives that are generally useful when building web applications with JavaScript.

「Backboneは、JavaScriptによるウェブアプリケーションを構築する際の、データ構造とユーザーインターフェースのミニマルセットを見つけるための試みである」
まさにミニマルだなと、思います。ソースを読むだけで、余計なものがまったくないとわかります。

さて、【役割2】はどのようにして果たせばよいでしょうか。これにはBackbone.Eventモジュールが役に立ちます。実はBackbone.ViewはBackbone.Eventモジュールをmix-inしている(=Backbone.ViewのプロトタイプオブジェクトをBackbone.Eventオブジェクトで拡張している)ので、「他のオブジェクトの動きに応じた動作をさせる」ことが、デフォルトで可能です。実はBackbone.ModelオブジェクトもBackbone.Collectionオブジェクトも同じくデフォルトでBackbone.Eventモジュールをmix-inしています。Backbone.Eventモジュールをmix-inすると、「自分に向けられた動作に応じた動き」ではなく、他人の動作に応じた動きをオブジェクトに定義できるのです。コレがBackbone.Eventの特徴と言えるでしょう。具体的にはlistenToメソッドが実現しています。
構文は「this.listenTo other event(s) callback」というものです。コレを使えば、ビューオブジェクトはアプリケーション内の任意のオブジェクトの動作に応じた描画を行うことができます。構文は「view.listenTo someModel(or collection) event(s) view.render」という感じになります。initialize時にこれを実装しておけば、アプリケーションの内部状態の変更を自動的に描画に反映することができます。【役割2】はこれでOKです。

ちなみに、今述べた協調関係はView対モデル(orコレクション)の協調関係ですが、View対Viewの協調関係もlistenToで築けるのか?でも、それはあまり良くないだろうなと僕は考えます。listenToはデータストラクチャーの変更を意図して設計されたメソッドと僕は考えるからです。Viewはデータストラクチャーを表現していません彼らはユーザーインターフェースを表現しているのですから。

具体例を書かずにここまで来てしまいました。Jeremyも公式サイトで簡単なToDoアプリのチュートリアル以外は具体例を挙げていません。でも、それでいいんじゃないかと思います。「ミニマルセット」を追求した結果のBackboneなわけですから、後は開発者の想像力と構築力の問題になるのだと思います。いかようにでも作れる、でも、もっとも必要なモノがそこにある、それがBackboneフレームワークだと捉えられそうです。

webpackでフロントエンド開発[ユニットテスト編:2]

2016年5月16日

ずっと前回の続きです。やっとこさ、テストが出来ます。コマンドはkarma startです!

$ karma start

すると、、、やたら長い出力が。。。webpackのバンドルの結果がだらだらでてくるですね。
んで、残念なことに結局エラーが出てきてしまいます:

webpack: bundle is now VALID.
16 05 2016 22:38:17.701:WARN [karma]: No captured browser, open http://localhost:9876/
16 05 2016 22:38:17.710:INFO [karma]: Karma v0.13.22 server started at http://localhost:9876/
16 05 2016 22:38:17.724:INFO [launcher]: Starting browser PhantomJS
16 05 2016 22:38:19.432:INFO [PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Connected on socket /#wmEhwZU0LM4lxZ_BAAAA with id 18605804
PhantomJS 2.1.1 (Mac OS X 0.0.0) showcase 1. 取り扱い中のアイテムの一覧を表示する FAILED
 Can't find variable: assert
 /Users/showtarow/tutorials/project/spec/showcaseSpec.coffee:60:16
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 11 (1 FAILED) (skipped 6) (0 sePhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 11 (1 FAILED) (skipped 10) ERROR (0.016 secs / 0.004 secs)

Can’t find variable: assert と言われてしまいました。そうです、assertモジュールをrequireし忘れました。早速入れましょう。でも、せっかく数回前にpower-assertというassertの強力バージョンをいれたので、これを使うことにしましょう。前回述べたwebpack.ProveidePluginでpower-assertをオートロードすればOKです。また、power-assert形式のjsファイルをrequireするには変換が必要なのでwebpack-espower-loaderを使うようwebpackに伝えます。詳しくはpower-assertのREADMEを。

to gain power-assert output, you need to transform your test code to produce power-assert output

https://github.com/power-assert-js/power-assert/blob/master/README.md

“power-assert” poweredなwebpack.config.coffeeは以下のようになります:

#webpack.config.coffee
module.exports=
  #中略#
  module:
    loaders:[
      {test: /\.coffee$/, loader: "coffee"}
      {test: /\.jade$/, loader: "jade"}
      #...その他いろいろ
      {test: /\.json$/, loader: "json"}#これが必要
    ]
    postLoaders:[
      {test: /Spec\.coffee$/, loader: "webpack-espower-loader"}
    ]
  plugins:[
    new webpack.ProvidePlugin
      assert: "power-assert"
      #....
  ]

{test: /\.json$/, loader: "json"}が必要なのは、理由があるからです。
webpack-espower-loaderはpostLoadersに入れておきます。上のようにしておくと、「Specファイルは全てのローダーチェーンを通過した一番最後にwebpack-espower-loaderをロードする」という動作をしてくれます。また、”assert”識別子にpower-assertを割り当てたので、もはやSpecファイルを書き換える必要はなくなりました。

もう一度karma start!

PhantomJS 2.1.1 (Mac OS X 0.0.0) showcase 1. 取り扱い中のアイテムの一覧を表示する FAILED
    # showcaseSpec.coffee:9
    
    assert(showcase.items.every(function (item) {return item.status === true;}))
           |        |     |                                                     
           |        |     false                                                 
           |        Collection{length:4,models:#Array#,_byId:#Object#}          
           Object{items:#Collection#}                                           
    
  concreteAssert@/Users/showtarow/tutorials/project/spec/showcaseSpec.coffee:12876:56
  decoratedAssert@/Users/showtarow/tutorials/project/spec/showcaseSpec.coffee:20605:45
  powerAssert@/Users/showtarow/tutorials/project/spec/showcaseSpec.coffee:12763:38
  /Users/showtarow/tutorials/project/spec/showcaseSpec.coffee:64:23
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 11 (1 FAILED) (skipped 10) ERROR (0.029 secs / 0.023 secs)

じゃじゃーん!すばらしい。ちゃんとテストが失敗して、その原因がエレガントに出力されています!コレがpower-assertの力、ということなんですね。

ふむ、どうやら、showcase.itemsが、適切にリセットされていない模様。。。showcaseSpec.coffeeを修正しましょう。エラーから察するに、everyメソッドに渡しているコールバックの処理に問題がありそうです・・・そう、itemオブジェクトにはstatusプロパティが定義されていません。itemはItemオブジェクトですんで、これにstatusメソッドを実装して、テストからはstatus()メソッドをコールすることにしましょう。item#status()は、いわゆるread-onlyなアクセッサメソッドということになるので、ちょっとしたマクロを継承元であるBackbone.Modelに追加してやりましょう:

#app/showcase/index.coffee
#Backbone.Modelコンストラクターにattr_readerクラスメソッドを追加
Backbone.Model.attr_reader= (attrs)->
  _.each attrs, (attr)=>
    @prototype[attr]= ->@get(attr)

class Item extends Backbone.Model
  @attr_reader ["status"] #status属性読み取りメソッドを追加
  defaults:
    status: off 
class Collection extends Backbone.Collection
  model: Item
module.exports.items= new Collection

#spec/showcaseSpec.coffee
showcase= require "showcase"
beforeEach ->@fixture= require "./fixture.yaml" 
describe "showcase", ->
  it "1. 取り扱い中のアイテムの一覧を表示する", ->
    availableItems= _.filter (_.values @fixture), (item)->item.status is on
    showcase.items.reset availableItems
    assert showcase.items.length > 0 #なにかしらアイテムは必ず表示される
    #ここを修正
    assert showcase.items.every (item)->item.status() is on #表示されるアイテムは全て取り扱い中である
  #...

さてコンソールを眺めると(そう、karmaもファイルの変更を監視してくれちゃってるんです、リロードは不要です)、、、

WARNING in ./~/power-assert/~/power-assert-formatter/~/acorn/dist/acorn.js
Critical dependencies:
1:478-485 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.
 @ ./~/power-assert/~/power-assert-formatter/~/acorn/dist/acorn.js 1:478-485
webpack: bundle is now VALID.
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 11 (skipped 10) SUCCESS (0.01 secs / 0.004 secs)

ちょっと意味不明なWARNINGがでちゃってますが、ともかく、

PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 11 (skipped 10) SUCCESS (0.01 secs / 0.004 secs)

うむ、どうやらテストは無事合格している模様です。今回はここまで。。。

webpackでフロントエンド開発[ユニットテスト編:1]

2016年5月12日

前回の続き。それではテストを書いてみましょう。テストするにもテストするモジュールがないので(「こんちは!」のテストをしてもしょうがない)、なにか簡単なアプリをつくってみましょう。今回はショップカートを作ってみることにします。

調味料を販売するショップサイトを想定してみます。サイトのトップページにアクセスすると、取り扱い中の調味料がずらりと表示され、ユーザーがカートに入れて商品を選べる、とりあえずそこまでのスペックを設計して、モジュールとして実装してみましょう。

スペックはspecフォルダに書きます。

~/tutorials/project% mkdir spec && cd spec

スペックファイルの命名規則は、とりあえずモジュール名Spec.coffeeにしてみます。karmaからテストを行う際に、このファイル名のルールに従って自動的にスペックファイルが読み込まれテストが実行されます。

ショップカートのサイトですので、とりあえず「ショーケース(showcase)」と「カート(cart)」のモジュールを作ることにします。この二つのモジュールのスペックをそれぞれshowcaseSpec.coffeecartSpec.coffeeファイルで設計してゆきましょう。具体的なモジュールの実装はこのスペックファイルを書きながら同時進行で進めていきます。まずは思いつくままに書いてみます。

#showcaseSpec.coffee

showcase= require "showcase"
describe "showcase", ->
  it "1. 取り扱い中のアイテムの一覧を表示する"
  it "2. 各アイテムの行にはタイトルとデフォルトの数量と金額を表示する"
  it "3. アイテムを選択するとカートに商品が入り、ショーケース内の商品の数量がひとつ減る"
  it "4. 数量指定のない商品についてはいくつでもカートに入れられる"
  it "5. カートに入れられる商品の上限はデフォルト数量までとする"
#cartSpec.coffee

cart= require "cart"
describe "cart", ->
  it "1. カートの中は最初は空である"
  it "2. 商品がカートに入れられたらそのタイトルごとに分類しリスト表示し、それぞれの数量を表示する"
  it "3. カートの中身が変わるごとに合計金額を更新して表示する"
  it "4. あるタイトルのアイテムの数量を変更すると、それに応じてショーケースのアイテムの数量も更新する"
  it "5. あるタイトルのアイテムを一括して削除することができる"
  it "6. 中身を空にすることができる"

ここで、モジュールの名前解決のルールをwebpackに伝えておきましょう。

#webpack.config.coffee
path= require "path"

module.exports= 
  #中略#
  resolve:
    extensions: ["", ".coffee", ".js"]
    root:[path.resolve "./app"]

こうすることで、./appディレクトリがモジュール探索のルートとして新たに追加されます。また、requireする際に、拡張子.coffeeも省略できます。

もう一つ、webpackの設定を追加しておきます。今回は多くのモジュールがBackbone.jsに依存するので、モジュール内に自動的にbackboneモジュールが読み込まれるようにwebpackを設定します(ついでにunderscoreとjqueryも入れておきます)。これにはwebpackのビルトインプラグインのProvidePluginを使用します:

#wepback.config.coffee
path= require "path"
webpack= require "webpack"

module.exports= 
  #中略#

  plugins:[
    new webpack.ProvidePlugin
      $: "jquery"
      Backbone: "backbone"
      _: "underscore"
  ]

こうすると、”Backbone”という名前の変数には、自動的にbackboneモジュールのexportsがアサインされますので、モジュール内でrequireする必要がなくなります。

設計に戻ります。

showcaseSpec.coffeeで具体的にモジュールを設計してゆきます。
今回はBackbone.jsを使いますので、showcaseモジュールには、ビューオブジェクト(showcase.view)と、コレクションオブジェクト(showcase.items)を1つずつ用意しておけばよさそうです。“1. 取り扱い中のアイテムの一覧を表示する”は、showcase.collectionをリセットしたときの状態、ということになりそうです。

なにかしらリセットするためのデータが必要なようです。テスト用のダミーデータはspec/fixture.yamlの中に準備しておき、テストの度に使用することにしましょう。showcaseSpec.coffeeでbeforeEach関数を呼んでおきます:

#showcaseSpec.coffee
showcase= require "showcase"
beforeEach -> @fixture= require "./fixture.yaml" #追加
describe "showcase", ->
 it "1. 取り扱い中のアイテムの一覧を表示する"
 #....


spec/fixture.yamlは以下

#spec/fixture.yaml
blackPepper:
 title: 胡椒
 status: true
 price: 300
 amount: 20
kumin:
 title: クミン
 status: true
 price: 400
 amount: 3
coriander:
 title: コリアンダー
 price: 450
 amount: 8
turmeric:
 title: ターメリック
 status: true
 price: 250
cayennePepper:
 title: カイエンヌペッパー
 status: true
 price: 280
 amount: 0
cardamon:
 title: カルダモン
 status: false
 price: 420

ここで、yamlデータをバンドルに含める必要が出てきました。yaml-loaderとjson-loader(あ、これは前回いれてますね)をインストールし、webpackにその旨伝えておきましょう:

% npm i -D yaml-loader json-loader
#webpack.config.coffee
loaders:[
  {test: /\.coffee$/, loader:"coffee"}
  {test: /\.yaml$/, loader:"json!yaml"}#これを追加
]

上のloadersの2行目は「.yamlファイルはyaml-loaderでjson形式に変換しそれをjson-loaderでjs形式に変換してロードする」という意味になります。ローダーは!でチェーンできるわけですね。

さらに、"1. 取り扱い中のアイテムの一覧を表示する"の詳細な仕様を書いておきましょう:

showcaseSpec.coffeeは以下のようになりました:

#showcaseSpec.coffee
showcase= require "showcase"
beforeEach ->@fixture= require "./fixture.yaml" 
describe "showcase", ->
  it "1. 取り扱い中のアイテムの一覧を表示する", ->
    availableItems= _.filter (_.values @fixture), (item)->item.status is on
    showcase.items.reset availableItems
    assert showcase.items.length > 0 #なにかしらアイテムは必ず表示される
    assert showcase.items.every (item)->item.status is on #表示されるアイテムは全て取り扱い中である
  it "2. 各アイテムの行にはタイトルとデフォルトの数量と金額を表示する"
  it "3. アイテムを選択するとカートに商品が入り、ショーケース内の商品の数量がひとつ減る"
  it "4. 数量指定のない商品についてはいくつでもカートに入れられる"
  it "5. カートに入れられる商品の上限はデフォルト数量までとする"

今度はshowcaseモジュールを書いてみましょう。app/showcase/index.coffeeに書きます:

#app/showcase/index.coffee
class Item extends Backbone.Model
 defaults:
   status: off
class Collection extends Backbone.Collection
 model: Item
module.exports.items= new Collection

cartモジュール(app/cart/index.coffee)はからっぽにしておきます:

#app/cart/index.coffee
module.exports={}

、、、これでひとまず、スペックとモジュールが最低限そろいました。

テストはkarmaコマンドを使って行います。karmaはwebpack同様設定ファイルで細かい設定が出来ますので、そちらを書いておきましょう。ファイル名はデフォルトでkarma.conf.coffeeです(注:webpackは「webpack.config.coffee」です、間違えやすいです!!)、webpack同様プロジェクトのルートに置きます:

#~/tutorials/project/karma.conf.coffee
module.exports= (config)->
  config.set
    frameworks: ["mocha"]
    files:["spec/**/*Spec.coffee"]
    preprocessors:
      "spec/**/*Spec.coffee":["webpack"]
    webpack: require("./webpack.config.coffee")
    browsers:["PhantomJS"]

さて!これで、やっとテストを起動できそうです。次回に・・・。

webpackでフロントエンド開発[テスト環境構築編]

2016年5月12日

前回までで、超簡単ではありますがwebpackを使ったフロントエンドの開発環境ができあがりました。ここいらで、ブラウザから一旦離れて、テストにスポットを当ててみます。

ところで、テストとブラウザの画面でのチェック、両方大事ですよね。ユニットテストとかテスト駆動開発とか色々言われているようですけど、僕は両方大事にしたいな、と思う派です。テストフレームワークを用いたテストの自動化も大事だし、効率と無関係に「きちっとブラウザでチェックする」というのも大事だし。

ともかくテスト。最初の方針のとおりテストはmocha+power-assert+karmaの組み合わせでいきます。mochaRSpecに似たDSLでテストを記述するためのフレームワーク。テストはこのmochaのDSLに従って書きます(もちろんcoffeeで)。power-assertはNodejsのassertモジュールをより一層強化した名前通りpowerなassertを提供するモジュール(ルー大柴みたいですが)。assertionに失敗した時に威力を発揮します。karmaはテストを実行するフレームワーク。Chrome, Safari, IE, Opera, そしてPhantomJSなどなど、多くのブラウザ上でテストを自動的に実行してくれます。

なんだか関連人物が多くて混乱しそうですが(僕はかなり混乱しました)、整理しながらいきます。取りまとめるのはwebpackです。

まずは、全部インストールを済ませてしまいましょう。

まずはmochaのインストール:

%npm i -D mocha

次にpower-assertと、それに関連するパッケージのインストール:

%npm i -D power-assert webpack-espower-loader json-loader

webpack-espower-loaderとjson-loaderは、webpackでpower-assertをサポートしたテストコードをバンドルする際に必要です。(webpack-espower-loaderは現時点でβバージョンだそうです)。なおjson-loaderも必要です

次にkarma(とPhantomJS)と関連するパッケージのインストール:

%npm i -D karma karma-mocha karma-phantomjs-launcher phantomjs-prebuilt karma-webpack

karma-mochaはkarmaからmochaベースのテストを実行するためのkarmaプラグイン。karma-phantomjs-launcherはPhantomJS上でテストを実行するためのkarmaプラグイン。phantomjs-prebuiltはnpm経由でPhantomJSをインストールするためのパッケージ(すでにインストールされている場合は不要です)、karma-webpackはwebpackをkarmaから利用するためのkarmaプラグイン。

karma-phantomjs-launcherはPhantomJSのインストールも込みでやってくれるので、ちょい時間がかかります。そもそもnpm iはいつも時間がかかるものですが。

やたらパッケージを入れなきゃならないのが面倒臭いですね。なんとかならんのか!と言いたくなりますが、フロントエンド開発の世界は、本当につくづく現在進行形なのでしょう。巨大なプロジェクトの中で整然と開発が行われるのではなくて、ダイナミックで自由気ままに世界中で開発が進んでいる、そんな印象を受けます。なにか一つ入れれば全部面倒見てくれる、というものではないんだなと、一旦この状況を受け入れるのがいい気がします(そしてそれがいいんじゃないかと)。話は逸れますが、今後紹介しようと思うautoprefixerなんて、その最たるもんじゃあないかと思ったりします。すごいですよ、autoprefixer。

これで、とりあえず環境は整った感じです。次回、実際にテストを書いて動かしてみます。

webpackでフロントエンド開発[webpack-dev-server編]

2016年5月12日

さて前回$を参照できないエラーが出ました。jQueryを読み込んでいなかった、というやつです。jQueryは前々回にすでにnpmでインストールしてあるので、それをrequireすれば良いわけです。./index.coffeeに追加しましょう。

#~/tutorials/project/index.coffee
$= require "jquery" #これを追加
module.exports= ->
  $("body").append """
    <h1>こんちは!</h1>
  """

この

#coffee
$= require "jquery"

の行で、$という変数にjqueryをアサインしています。

webpackはwebpack.config.coffeeと同一のディレクトリのnode_modulesディレクトリ以下のモジュールをディレクトリ名で参照します。なので、”jquery”の一言で、jqueryのモジュールをrequireできるのです(実際にはjqueryパッケージのpackage.jsonのmainプロパティの記述に従ってjsファイルがrequireされます)。

なので、もう一度コンパイルして、ブラウザをリロードしてみましょう、、、、って言う間もなく、すでにブラウザには「こんちは!」と表示されています!

スクリーンショット 2016-05-12 0.11.28 のコピー 2

じつはwebpack-dev-serverはwebpack.config.coffeeのentryオプションで指定したソースファイル(とそのrequire先のファイル群)の変更を監視し、リアルタイムでコンパイルし、自動でリロードまで行ってくれます。(Chromeのコンソールにエラーが残っているのはSocketで非同期リロードを行っているため)。つくづく便利ですね。。。

1つ注意点。webpack-dev-serverはwebpack.config.coffeeの設定通りにバンドル作業をリアルタイムにこなしてくれますが、バンドル結果を「実際にファイルに出力していません」。全てはwebpack-dev-serverのプロセスの中でバンドル作業が行われ、バンドルされたjsはメモリから仮想的にサーブされます。今回の例でいえばbundled.jsがファイルとして吐かれていてもそうでなくてもwebpack-dev-serverには無関係です。バンドルをサーブする上でwebpack側で必要な設定は1つだけ、webpack.config.coffeeのoutput.publicPathオプションです。

#coffee
publicPath: "http://127.0.0.1:3000/assets/"

このオプションによって、サーバーはブラウザからのhttp://127.0.0.1:3000/assets/bundled.jsへのリクエストに対して適切にバンドルされたjsファイルをサーブしてくれます。

これが、nodejsの特徴の一つと呼ばれる、non blocking IOというやつなのでしょうか。僕自身まだ詳しくないのですが、コストの高いファイルシステムへのIOを回避し、効率の良い動きを実現している、、、ということなのでしょう。ともあれ、開発環境の動作としてはこれは歓迎すべきことですよね、開発中のモジュールの変更をすぐにブラウザでもフォローできるということは。

このことからもみえてくるのですが、webpackは「モジュールバンドル」という開発ワークフローに、柔軟なオプションを与えてくれます。例えば、ブラウザで確認しながらコーディングなりデザインするという開発フェーズでは、バンドルをファイルに逐一落としていては時間がかかる、、、なのでwebpack-dev-serverのデーモンプロセスの中でバンドルされていれば良い。ひと段落したのち、本番環境への移行フェーズでは、webpackコマンドで本番環境用のバンドリング(Minifyしたり、バンドルするモジュールを変えたりetc)を行う、といった具合に。コマンドラインでwebpackに渡す環境変数を切り替えることで、webpack.config.coffeeの挙動もダイナミックに変更が効くので、バンドリングのレシピは、いかようにでも作ることができる、というわけです。

このあたりの実例は今後取り上げてみたいと思います。

さて、このwebpack-dev-serverはiframeをつかってレンダリングしていますが、iframeなしで動かすこともできます。ブラウザでの動作確認は、iframeだとちょっと気持ち悪いかんじがしますので、以下のようにwebpack.config.coffeeのentryオプションを変更します(その前にCtrl-C で一度サーバーを落としましょう):

#coffee
  entry: [
    "./index.coffee"
    "webpack-dev-server/client?http://127.0.0.1:3000"
  ]

webpack-dev-serverは新たに加えたエントリの指示(webpack-dev-server/client?http://127.0.0.1:3000)によって、開発サーバー用のスクリプト(自動リロードなど)もバンドルの中に含めるようになり、http://127.0.0.1:3000へリクエストすればフレームレスで開発ができます。

スクリーンショット 2016-05-12 1.04.01 のコピー

webpackでフロントエンド開発[webpack.config.coffee]

2016年5月11日

前回の続き。以下のファイル構成になっています。

.
├── app
│   └── index.coffee
├── index.coffee
├── package.json
└── public
    └── index.jade
(node_modulesは略)

webpack.config.coffee ファイルを書きましょう。設定ファイルといっても、jsonと異なり、nodejsが実行可能なjsファイル(僕はcoffeeで書きますが)なのです。非常に柔軟性に富んだ設定ができます。まずはシンプルに書いてみましょう。

#~/tutorials/project/webpack.config.coffee
module.exports=
  entry: ["./index.coffee"] #最初に読むファイル(相対パスで!)
  output:
    path: __dirname + "/bundled" #出力先のフォルダ(絶対パスで!)
    filename: "bundled.js" #出力するファイル名
  module:
    loaders:[
      {test: /\.coffee$/, loader:"coffee"}
    ]

(僕はCommonJSのスタイルしか習得していないのでこう書いていますが、AMDスタイルにもwebpackは対応しています。くわしくはドキュメントを。)

こう書くと、webpackは

./index.coffeeを読みこみ、ソースファイルを解析し、結果を./bundled/bundled.jsに吐き出す。解析中に名前が/\.coffee$/の正規表現にマッチするファイルをrequireするときにはcoffee-loaderを通してjsファイルにコンパイルしてファイルを纏めてゆく。」

という動作をします。

moduleのオプションの中に、loadersというオプションがありますね。ここで、ローダー、すなわちwebpackがモジュールをどのようにロードするかを設定します。

#coffee
{test: /\.coffee$/, loader:"coffee"}

という行(というかJS的に言えばオブジェクト)があります。これが「/\.coffee$/の正規表現にマッチするファイルをrequireするときにはcoffee-loaderを通してjsファイルにコンパイルしてファイルを纏めてゆく。」ということを言っています。

coffee-loaderというのは前回インストールしたnpmパッケージ “coffee-loader”のことです。つまりJSONをrequireしたければjson-loaderを、yamlをrequireしたければyaml-loaderを、テキストファイルをrequireしたければfile-loaderを、、、、といった具合に、いろんなloaderを使ってwebpackは一つのJSファイルにバンドルしてしまうのです。すごいです。公式のドキュメントにloaderのリストがあります。npmにもいっぱい転がっています。

このwebpack.config.coffeeで設定できるオプションは多彩です。詳しくはドキュメントを。

改めてwebpack してみます。

$ webpack
Hash: 1c86edd68ab753881b94
Version: webpack 1.13.0
Time: 160ms
     Asset     Size  Chunks             Chunk Names
bundled.js  1.54 kB       0  [emitted]  main
   [0] multi main 28 bytes {0} [built]
    + 1 hidden modules

今度はバンドルしてくれました。bundledフォルダに作られています。

.
├── app
│   └── index.coffee
├── bundled
│   └── bundled.js #<----コレ!
├── index.coffee
├── package.json
├── public
│   └── index.jade
└── webpack.config.coffee

ではこれをブラウザで動かします。その前に、サーバーの返すhtmlファイルをコンパイルしておきましょう。

$ jade public/index.jade -o public

index.htmlファイルが作られました。サーバーはこのindex.htmlファイルをサーブします。

├── public
│   ├── index.html
│   └── index.jade

サーバーの挙動もセットアップしておきましょう。webpack.config.coffeeにいくつかのオプションを追加します:

#~/tutorials/project/webpack.config.coffee
module.exports=
  entry: ["./index.coffee"]

  output:
    path: __dirname + "/bundled"
    filename: "bundled.js"
    #webpack-dev-serverがサーブする、バンドルされたJSファイルのURLのルートを指定
    publicPath: "http://127.0.0.1:3000/assets/"

  #webpack-dev-serverの設定
  devServer:
    contentBase: "./public" #相対パス!
    host: "127.0.0.1"
    port: 3000

  module:
    loaders:[
      {test: /\.coffee$/, loader:"coffee"}
    ]

サーバーを起動してみましょう。webpack-dev-serverコマンドを打てばOK.

$ webpack-dev-server

すると、、、

$ webpack-dev-server
http://127.0.0.1:3000/webpack-dev-server/
webpack result is served from http://127.0.0.1:3000/assets/
content is served from ./public
Hash: 3fe70e59b1eb7c75c588
Version: webpack 1.13.0
Time: 210ms
     Asset     Size  Chunks             Chunk Names
bundled.js  1.57 kB       0  [emitted]  main
chunk    {0} bundled.js (main) 64 bytes [rendered]
    [0] multi main 28 bytes {0} [built]
    [1] ./index.coffee 36 bytes {0} [built]
webpack: bundle is now VALID.

expressサーバーのデーモンが起動しました。

最初に、

http://127.0.0.1:3000/webpack-dev-server/
webpack result is served from http://127.0.0.1:3000/assets

とあります。サーバーのURLはhttp://127.0.0.1:3000/webpack-dev-server/で、バンドルされたjsファイルはhttp://127.0.0.1:3000/assetsからサーブされますよ、という意味です。

先ほどのindex.htmlには以下のタグが含まれているので、

<script src="http://127.0.0.1:3000/assets/bundled.js" async>

ブラウザからhttp://127.0.0.1:3000/webpack-dev-server/にアクセスすると、DOMの構築が終わった時点(async属性!)でbundled.jsが読み込まれ、無事に$("body").append...を実行してくれるはずです。

では試しにChromeブラウザからアクセスしてみます。

スクリーンショット 2016-05-11 23.53.48 のコピー

あれ、、、「こんちは!」がでてこない!!

スクリーンショット 2016-05-11 23.55.23 のコピー

コンソールで確認すると、、、「ReferenceError: $ is not defined」とあります。
$が定義されていない、とのこと。どうやらjQueryを読み込むのを忘れたようです。読み込みましょう。

。。。長くなってしまうので、次の記事に!

webpackでフロントエンド開発[環境構築]

2016年5月11日

前提:

  • javascriptはCoffeeScriptで
  • cssはsassで
  • フレームワークはBackbone.js
  • テンプレートエンジンはjade(今はpugに改名したようですが)
  • ユニットテストはmocha+power-assert+karmaの組み合わせ

この人たちを束ねてくれるのが、webpack、というわけです。ではスタート!

まずnodejs, npmの環境をチェック。ともあれ、最新が望ましいでしょう。

 ~$ node --version
v4.4.3

 ~$ npm --version
2.15.1

プロジェクトのフォルダを作成

 ~$ mkdir -p tutorials/project && cd tutorials/project

プロジェクトはnpm パッケージとします

 $ npm init #(全てリターンでOK)

次に簡単なアプリケーションを作成してみます。

 $ mkdir app && cd app

ブラウザにテキストを表示させる超単純もの。アプリケーションのフォルダにindex.coffeeを作って、そこにbodyタグにh1を追加する関数をmodule.exportsにアサインします。

#~/tutorials/project/app/index.coffee

module.exports= ->
  $("body").append """
    <h1>こんちは!</h1>
  """

module.exportsにアサインするのは、この関数をCommonJSの仕様に従ってモジュール化するためです。これをトップレベルのコードから利用します。

webpackについてる開発サーバーを使うので、そのサーバーのルートフォルダを作っておきます。

~/tutorials/project$ mkdir public

そのサーバーの返すhtmlは、jadeで書いておきます。

#~/tutorials/project/public/index.jade

doctype html
html(lang="ja")
  head
    //- async属性を入れておく!
    script(src="http://localhost:3000/assets/bundled.js" async)
  body

次にnpmパッケージをインストールします。まずは最低限のものを。

$ npm i -D coffee-script jquery underscore backbone jade

ではwebpackをインストール。webpackはさまざまな機能をnpmパッケージとして追加することで威力を発揮します。なので、それらも併せてインストール。
下のcoffee-loader, jade-loaderというのは.coffeeファイル、.jadeファイルを「バンドル」してくれる「ローダー」と呼ばれるwebpack用のnpmパッケージです。

$ npm i -D webpack #webpack本体

$ npm i -D webpack-dev-server #開発用サーバー

$ npm i -D coffee-loader jade-loader #ローダー

バンドル時に使うwebpackコマンドはnode_modulesフォルダの下にもぐっているので、あらかじめパスを通しておきます(既に通っている場合は不要)

 $ export PATH=./node_modules/.bin:$PATH

ルートフォルダにwebpackが最初に読み込むファイルを作っておきます。名前をindex.coffeeとしておきます。このファイルには、ブラウザがトップレベルで実行するコードも書きます。

#~/tutorials/project/index.coffee

do require("./app/index.coffee") #読み込まれたモジュールを実行する

僕は、最初なかなか感じが掴めなかったのですが、appフォルダ以下のコードは、すべてモジュールとしてカプセル化することになります。CommonJSの仕様に従って、webpackがこのトップレベルのコードからモジュールを全て辿ってrequireしていくわけです。ブラウザはrequireできないので、webpackで事前に必要なモジュールを全てバンドルしておく必要があるのです。

なお、このコードはwindow.onload(function()…)でラップする必要はありません。スクリプトはhtmlの中でasyncで読み込みますので、DOMが構築されるタイミングをJSで調整する必要はありません。

これで環境は整った感じです。インストールも済んだしパスも通ったことですので、webpackを起動してみましょう。index.coffeeがappフォルダのファイルを読み込み、その結果をブラウザが解釈できるjsファイルとして出力してくれるはずです。

$ webpack

すると、、、

$ webpack
webpack 1.13.0
Usage: https://webpack.github.io/docs/cli.html

Options:
  --help, -h, -?
  --config
  --context
  --entry
  --module-bind
  --module-bind-post
  --module-bind-pre
(以下略)

、、、これでは動かないのです。それもそのはずで、まだ、webpackの挙動をきちんと設定していません。上の結果はwebpack --helpと同じで、コマンドラインオプションのリストです。これらオプションを入れればよいのですが、微に入り細にいたるまで設定してこそwebpackの威力を発揮できます。設定ファイルを書きましょう。webpackコマンドはデフォルトでカレントディレクトリのwebpack.config.coffeeを読みますので、次の記事で!

webpack事始め

2016年5月11日

フロントエンドの仕事をするにあたり、どのようにしてJSのソースをモジュール化すればいいのか、ずっと考えていました。<head>にずらっと並んだ<script>タグはどうにもまずいだろうと。何とかしなくてはならない。おそらく、解はnodejs, RequireJSあたりにあるのだろうな、、と思っていたのですが、色々調べた結果webpackに行き着きました。

webpackはnodejsのパッケージで、jsファイルやcss, json, png, svg、、、その他諸々を一緒くたにしてくれるもの。すなわち「module bundler」。トランスパイル(coffee->js、scss->cssなど)を間に挟むこともできるので、coffeeやES6で書かれたJSやscssなども全て1つのjavascriptファイルに(そう、cssもすべてJSのなかに詰め込んでしまう!)バンドルしてくれる。バンドルされたJSは、ブラウザで実行可能なjavascriptファイルであり、バンドル時にwebpackがファイルの最適化なども行ってくれたり、js以外のアセットは他ファイルに出力してくれたり、、など細かい設定が可能。expressサーバーをベースにした簡易的な開発用サーバーもビルトインされているので、フロントエンドの開発環境にはもってこいでは?!と考え、今後の仕事に使っていこうと判断した次第。

当初の僕のモチベーションはjavascriptファイルのモジュール化だったのですが、webpack導入による最も大きいメリットは「サイトの表示速度を上げる」ことになるのでしょう。もちろんその背景にはモバイル対応というのがあり、ページロードのスピードというのはとても大事、避けて通れないわけです。同時にCDNの導入なども検討しなくてはならないし、これもフロントエンドのモジュール設計に大きくかかわってくる。一言でmodule bundlerといっても、実はかなり奥が深いものなのだと改めて認識した次第です。(参考:12 expert tips on how to boost web performance)

というわけで、このwebpackを軸にしたフロントエンド開発環境と、僕が基本としている開発フローについて今後書いてきたいと思います。