Yet Another Bundler - Parcel + Vue.jsの可能性を模索した話 -

この記事は Vue.js #1 Advent Calendar 2017 - Qiita 15日目の記事です。

今年もアドベントカレンダーの季節がやってきました! ブログ書くのは1年ぶりですねw

まずはネタ探しということで最近はReact Nativeを書いている時間が長いのもあり、はじめはVue.jsでモバイルアプリが作れるWeexについての記事を書こうといろいろ準備していました。

そんな穏やかな冬の午後・・・

またなんか出た

qiita.com

ちょっと待ってくれ

先週社内でWebpackの勉強会したばっかなんだけど・・・

f:id:andoshin11:20171214210218p:plain

↑↑ やりきった感のある投稿の様子

正直、

(ほんともう勘弁してくれ〜〜〜)

って感じだしBrowserifyやWebpackをはじめとしたバンドラ論争に振り回されたみなさんの中には怒りすら覚えた人もいるのではないでしょうか

f:id:andoshin11:20171214211638j:plain

とは言っても

触ってみないことには良いも悪いもわからないので、話題のParcelについて調査してみました。

github.com

  • 筆者はフロントエンド歴1年未満
  • Parcelを触りはじめてから1週間くらい経つけど、実質触ったのは3日くらい
  • Parcel自体の開発速度が早く、閲覧いただいている時点では情報が古いかもしれません。記事執筆時点では v1.1.0を使ってます

上記にご留意の上、お楽しみください

本記事の対象

  • webpack.config.jsを書いたことがある人
  • なんとなく webpack.config.jsが読める人
  • とりあえず新しい技術に飛びついてみたい人

「フロントエンドよくわからん」って人にはよくわからんかも知れないけど、僕もよくわからんのでそのへんは雰囲気で

Parcelとは

本記事のテーマであるParcelとは、AdobeのエンジニアであるDevon Govettさんが開発したJavaScript製モジュールバンドラです。

モジュールバンドラとは複数のファイル(モジュール)に書かれたJSのコードを1つに合体させて(バンドリング)吐き出してくれるツールで、同様の機能を持ったものとしてはBrowserifyやWebpackなどが有名です。

f:id:andoshin11:20171214212523p:plain

開発自体がスタートしたのは今年(2017年)の8月のようですが、爆発的にGitHubスター数が増えはじめたのは12月の頭に v 1.0.1がリリースされてからのこと。 記事執筆時点ではそれから9日間で10,000近くのスターを集めています。

何が新しいの?

フロントエンドエンジニアの中には兼ねてよりバンドラ周りの設定の複雑さに辟易とする人が多くいました。(いわゆる「Webpack疲れ」)

エントリポイントと出力先を指定するだけなのに、なんでわざわざ外部ライブラリまで使ってあんな面倒な設定書かなあかんねんというご意見には、僕もわかりみしかない。。。

さらにそこにローダーの設定とそれらを通したいファイルの拡張子を正規表現で指定して、対象外のディレクトリを指定して、プラグインを設定して、といったオプションが上積みされていき、気づいたときには立派な秘伝のタレが出来上がっているわけです。(メンテを押し付けてしまった後輩くん、本当に申し訳ない・・・)

f:id:andoshin11:20171215002244p:plain

そんな中、流星の如くParcelは現れました

zero configuration web application bundler

というメッセージからもわかるように、Parcelはカオスな複雑化の一途を辿るフロントエンド界に新しい風を吹かせようという想いで産み出されたツールです。 後ほどまた触れますがParcelの主な特徴は以下の通り

  • 設定ファイルを全く書かなくてもモジュールバンドリングできる(zero config)
  • プロジェクト内でトランスパイラの設定ファイルを検知すると、自動でトランスパイルしてくれる(Parcelの設定は必要なし)
  • デフォルトでHMR(Hot Module Replacement)に対応済み
  • 速い!(*公式ベンチで対Browserify比最大8.7倍、対Webpack比最大7.8倍)

これが全て本当ならワクワクしますね。さっそく試してみましょう!

まずは動作確認

Yarnでインストール

$ yarn init -y
$ yarn add parcel-bundler

Parcelはモジュールバンドリングのエントリーポイントとしてどのような拡張子のファイルも受け取れるようですが、公式ではHTMLもしくはJavaScriptファイルを指定する事が推奨されています。

今回は index.htmlを起点にパス解決を試してみましょう。

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Parcel Test</title>
</head>
<body>
  <h1>Hello World!</h1>
  <script src="./index.js"></script>
</body>
</html>
// index.js
import { hoge } from './hoge';
console.log(hoge);
//hoge.js
export var hoge = 'Hoge';

ファイルの準備ができたら parcelコマンドにエントリーポイントとなるファイル名を指定して実行してみます。シンプルで分かりやすいですね。

$ node_modules/.bin/percel index.html

上記コマンドを実行するとNodeのホスティングサーバーが立ち上がるはず

デフォルトのポート番号として 1234が割り当てられるので http://localhost:1234/にブラウザからアクセスしてチェック・・・

ところがエラー!!

どうやらPercelがコンパイルしたファイルに構文エラーが含まれているようです

いろいろ試しても解決方法がわからず困っていたところ、コミュニティに質問を投げると秒速で回答をいただきました

感謝感謝!!

手元のNodeバージョンが v7.10.0だったので、アドバイスに従ってnodebrewで v8.1.0に変更 & 再チャレンジ。

今度はちゃんとモジュールバンドリングに成功しました :muscle: :muscle:

↑↑ hoge.jsの中身が index.htmlに読み込まれて出力されている様子

github.com

公式のissueにも上がっていましたね。

毎回コマンドを打つのが大変なので起動スクリプトを設定しておきましょう。

{
  "name": "parcel",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "./node_modules/.bin/parcel index.html",
    "watch": "./node_modules/.bin/parcel watch index.html"
  },
  "dependencies": {
    "parcel-bundler": "^1.1.0"
  }
}

また、Parcelを実行する際に watchコマンドを実行すると配信サーバーは起動せずにファイルの変更だけ検知させることができます

Parcelは何をしているのか

もう少し詳しくParcelの挙動を見ていきましょう

files tree

上記Parcelコマンドを実行するとディレクトリ内に /.cache/distという2つのディレクトリが新たに生成されます。

実際に ./dist/parcel.jsをのぞいて見ると、Webpackで生成した bundle.jsにそっくりなモジュールバンドリング済みJSファイルであることがわかりますね。

Parcelは起動時に /.cacheディレクトリの中身をヒントにコンパイルを行い、 /dist以下のファイルをNodeサーバーから配信しているようです

Babelでトランスパイルする

ここまで設定なしでモジュールバンドリングをすることができました。 今度はES7のトランスパイルを行なってみましょう。

Webpackでは正規表現でターゲットのファイルタイプを指定して babel-loaderを設定することでビルド時にトランスパイルが行えましたが、Parcelは実行したディレクトリ内に .babelrcがあるのを検知すると自動でやってくれます(← 便利)

まずは babelのインストール

$ yarn add babel-core babel-preset-env babel-polyfill

.babelrcを記述

{
   "presets": ["env"]
}

ES7からの新機能である async/awaitを用いてParcelのGitHhubレポジトリにいくつスターが付いているか fetchしてみます。

// index.js
import "babel-polyfill";
import { fetchStars } from './fetchStars';

const SOURCE = "https://api.github.com/repos/parcel-bundler/parcel";

fetchStars(SOURCE);
// fetchStars.js
export const fetchStars = async source => {

  // fetch
  console.log("Start fetching...")
  let response = await fetch(source);

  // parse
  console.log("Start parsing...");
  const json = await response.json();
  const name = json.name;
  const stargazers_count = json.stargazers_count;

  console.log(`${name} has ${stargazers_count} stars!`);
}

$ yarn run startを実行してParcelを起動

f:id:andoshin11:20171215024424p:plain

.babelrcを記述するだけで、無事にES7のコードが動きました!! うーん確かにこれは便利だ。

その他のローダーへの対応

筆者は未確認ですが、記事執筆時点で以下の項目についてzero config(Parcel側の設定なし)でトランスフォームしてくれるらしいです

  • Babel (.babelrc)
  • PostCSS
  • PostHtml
  • CSS Module
  • LESS
  • SASS
  • Stylus
  • TypeScript

Parcel + Vue.js

ParcelとVue.jsの相性はどうでしょうか?

まずはVueインスタンスを生成して動作確認してみます。

$ yarn add vue
<body>
  <div id="app">
    <h1>{{ msg }}</h1>
  </div>
  <script src="./index.js"></script>
</body>
// index.js
import Vue from 'vue/dist/vue.esm.js';

const vm = new Vue({
  el: "#app",
  data () {
    return {
      msg: "Hello Vue!"
    }
  }
})

ここまでは動作成功

シングルファイルコンポーネントとどう戦うか

Vue.jsにはSFC(Single File Component)という仕組みが備わっており、.vueという拡張子のファイルにHTMLライクなシンタックスコンポーネントの要素を記述できます。

これまで、SFCを組み込んだアプリケーションのビルド時にはvue-loader、もしくは vueifyというライブラリをWebpackまたはBrowserifyから呼び出すことでSFCコンパイルを行なっていました。

ではParcelでこの .vueを読み込むにはどうすれば良いのでしょうか?

先に結論

先に結論を言ってしまうと、

ParcelでSFCを扱うことはできませんでした!!

f:id:andoshin11:20171215022750j:plain

力及ばずでほんとうに申し訳ない・・・

だけど可能性は見えた(気がする)

「できませんでした」

だけでは師走の記事として少し寂しいので、今後チャレンジする人のために自分が試したことをツラツラと書き出してみたいと思います

1. Parcel Pluginについて調べてみた

f:id:andoshin11:20171215011412p:plain

Parcelの公式ドキュメントから「Advanced」の項目を見るとプラグインの作り方や、Parcel内部で任意のファイルタイプがどのようにパースされ、トランスフォームされるのかという概要を把握できます

🔌 Plugins

これを見ながらParcelでSFCを処理するためのダミープラグインをまずは作ってみました。また、Parcelがバズった初日に神速でTS向けのプラグインを作ってくれたfathybさんのソースがとても参考になったので感謝です

github.com

2. .vueファイルをパースしてみた

SFCtemplate, script, styleという3つの要素で構成されているため、コンパイルを実行する前に一度パースしてあげる必要があります。 パーサーを自分で書く能力がなかったのでこちらは本家の vue-template-es2015-compilerを利用しました

github.com

vue-template-es2015-compilervue-loadervueifyの内部で利用されているライブラリです。 ドキュメントが用意されていなかったので、実際の利用方法は vue-loadervueifyのソースを読んでぼんやり把握しました。

parse()メソッドを使ってSFCのパースに一応成功

3. コンパイルしてみた(かった)

実際に .vueファイルを .jsファイルにコンパイルする処理です。

これは現在も嵌り中...

vue-template-compilerだけでは上手くできなかったり、 vueifyのそれっぽいAPIもそれ単体で外部から呼び出してもうまく動かなかったり、そもそもParcel自身がコンパイルをミスってたりで記事執筆時点ではまだ成功していません。無念。

Parcel + Vue.jsのこれからとvue-component-compiler

今回の「 SFCをParcelで読み込みたい」という取り組みの中で、同様の思いを持った多くの人々に助けていただきました。 特に12/9(バズった日)より参加させていただいているParcelの公式Slackコミュニティではたくさんの「ParcelとVue.jsを一緒に使いたい!」という人々に出会う事ができ、12/12には #vueチャンネルが立ち上がるまでになりました

f:id:andoshin11:20171215013850p:plain

現在もこちらのSlackでParcelのアップデートやプラグインの追加について日夜議論が繰り広げられています(Slackの通知がほとんどこれになったw)

個人的にこの規模のOSSコミュニティの立ち上げを目にする経験は初めてなので、とても良い刺激をもらっています。

また、SFCコンパイルについて調べていくうちにVue公式チームが vue-component-compilerというライブラリをリリース予定であることも分かりました。

github.com

現在はまだDesigning Threadの状態ですが、「バンドラに依存しないSFCコンパイルAPI」という位置付けで2018年にリリース予定とのことです。 確かに今回 vue-loadervueifyのソースを読む中でロジックが全く共通化されてなくてマズイなーという感想を抱いたので、 vue-component-compilerがリリースされるのが楽しみです。

f:id:andoshin11:20171215015602p:plain

みんなでkazuponさんを信じて待ちましょう!!

(ParcelでSFC使えるようになったらvue-cliのテンプレート作ってOSS活動したいなー)

結局Webpackから乗り換えるべきなの?

今回実際にParcelをいろいろと触ってみましたが、

まだWebpackは手放せない!

というのが正直な感想です。

確かに「Zero configuration web application bundler」という触れ込みは大変魅力的です。

僕自身、みなさんと同じよう近年のフロントエンドの煩雑さは常軌を逸していると感じている一人であり、Webpackの設定など苦痛そのものだと考えています。

だからといって、

「煩雑な設定が多すぎる」

という問題に対して

「なにも設定しなくて大丈夫!」

というソリューションを採用するのもいささか極端な発想であり、みんなが幸せになる考えでもない気がしています。

最新のECMAScriptで全てのコードが記述され、あらゆるデータが型を持ち、そして地球上の全てのブラウザが遍く最新のWeb標準仕様に対応した世界こそ我々フロントエンドエンジニアにとってのユートピアですが、残念なことにまだ人類はその理想郷にたどり着く事ができていません。

これからもしばらくはプロジェクトごとに .babelrcを書き、ファイルの種類ごとに適切なローダーを割り当てる運用はなくならないでしょう。 そう考えたときに、まだリリース直後のParcelには自由度が少なすぎるため、開発要件を満たすのに必要十分とは言えません。

今後の可能性に期待!

Parcelはまだ歩き始めたばかりのプロジェクトです。裏を返せば伸び代しかありませんw この1週間、Slack上で秒速でバグがつぶされていく様子や、爆速で新機能が追加されている様子をみていると、その将来はとても明るいものになる気がしています。

また、コミュニティ全体で「zero config」の哲学が浸透しているだけでなく、

Zero config doesn't mean zero configurable

という合言葉も頻繁に用いられていることからもわかるよう、様々なユースケースに対応したPluginsやExamplesが絶賛開発中です。 本番投入はまだ無理でも、個人で開発しているプロダクトでガンガンドッグフーディングしていこうと思います。

長文に最後まで目を通していただき本当にありがとうございます。 Happy Vue Life!!

追記(2017/12/15 16:08)

lc60005457さんが parcel-plugin-vueを作ってくれたようです!

github.com

実際に .vueファイルを用意して試してみたところちゃんと動作しました!どうやら vueifyAPIを利用しているようですね。 これでVueアプリケーションの開発が捗ります。ありがとうございます!!