Yet Another Bundler - Parcel + Vue.jsの可能性を模索した話 -
この記事は Vue.js #1 Advent Calendar 2017 - Qiita 15日目の記事です。
今年もアドベントカレンダーの季節がやってきました! ブログ書くのは1年ぶりですねw
まずはネタ探しということで最近はReact Nativeを書いている時間が長いのもあり、はじめはVue.jsでモバイルアプリが作れるWeexについての記事を書こうといろいろ準備していました。
そんな穏やかな冬の午後・・・
またなんか出た
ちょっと待ってくれ
先週社内でWebpackの勉強会したばっかなんだけど・・・
↑↑ やりきった感のある投稿の様子
正直、
(ほんともう勘弁してくれ〜〜〜)
って感じだしBrowserifyやWebpackをはじめとしたバンドラ論争に振り回されたみなさんの中には怒りすら覚えた人もいるのではないでしょうか
とは言っても
触ってみないことには良いも悪いもわからないので、話題のParcelについて調査してみました。
- 筆者はフロントエンド歴1年未満
- Parcelを触りはじめてから1週間くらい経つけど、実質触ったのは3日くらい
- Parcel自体の開発速度が早く、閲覧いただいている時点では情報が古いかもしれません。記事執筆時点では
v1.1.0
を使ってます
上記にご留意の上、お楽しみください
本記事の対象
webpack.config.js
を書いたことがある人- なんとなく
webpack.config.js
が読める人 - とりあえず新しい技術に飛びついてみたい人
「フロントエンドよくわからん」って人にはよくわからんかも知れないけど、僕もよくわからんのでそのへんは雰囲気で
Parcelとは
本記事のテーマであるParcelとは、AdobeのエンジニアであるDevon Govettさんが開発したJavaScript製モジュールバンドラです。
モジュールバンドラとは複数のファイル(モジュール)に書かれたJSのコードを1つに合体させて(バンドリング)吐き出してくれるツールで、同様の機能を持ったものとしてはBrowserifyやWebpackなどが有名です。
開発自体がスタートしたのは今年(2017年)の8月のようですが、爆発的にGitHubスター数が増えはじめたのは12月の頭に v 1.0.1
がリリースされてからのこと。
記事執筆時点ではそれから9日間で10,000近くのスターを集めています。
何が新しいの?
フロントエンドエンジニアの中には兼ねてよりバンドラ周りの設定の複雑さに辟易とする人が多くいました。(いわゆる「Webpack疲れ」)
エントリポイントと出力先を指定するだけなのに、なんでわざわざ外部ライブラリまで使ってあんな面倒な設定書かなあかんねんというご意見には、僕もわかりみしかない。。。
さらにそこにローダーの設定とそれらを通したいファイルの拡張子を正規表現で指定して、対象外のディレクトリを指定して、プラグインを設定して、といったオプションが上積みされていき、気づいたときには立派な秘伝のタレが出来上がっているわけです。(メンテを押し付けてしまった後輩くん、本当に申し訳ない・・・)
そんな中、流星の如く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
に読み込まれて出力されている様子
公式の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の挙動を見ていきましょう
上記で 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を起動
.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を扱うことはできませんでした!!
力及ばずでほんとうに申し訳ない・・・
だけど可能性は見えた(気がする)
「できませんでした」
だけでは師走の記事として少し寂しいので、今後チャレンジする人のために自分が試したことをツラツラと書き出してみたいと思います
1. Parcel Pluginについて調べてみた
Parcelの公式ドキュメントから「Advanced」の項目を見るとプラグインの作り方や、Parcel内部で任意のファイルタイプがどのようにパースされ、トランスフォームされるのかという概要を把握できます
これを見ながらParcelでSFCを処理するためのダミープラグインをまずは作ってみました。また、Parcelがバズった初日に神速でTS向けのプラグインを作ってくれたfathybさんのソースがとても参考になったので感謝です
2. .vue
ファイルをパースしてみた
SFCは template
, script
, style
という3つの要素で構成されているため、コンパイルを実行する前に一度パースしてあげる必要があります。
パーサーを自分で書く能力がなかったのでこちらは本家の vue-template-es2015-compiler
を利用しました
vue-template-es2015-compiler
は vue-loader
と vueify
の内部で利用されているライブラリです。
ドキュメントが用意されていなかったので、実際の利用方法は vue-loader
と vueify
のソースを読んでぼんやり把握しました。
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チャンネルが立ち上がるまでになりました
現在もこちらのSlackでParcelのアップデートやプラグインの追加について日夜議論が繰り広げられています(Slackの通知がほとんどこれになったw)
個人的にこの規模のOSSコミュニティの立ち上げを目にする経験は初めてなので、とても良い刺激をもらっています。
また、SFCのコンパイルについて調べていくうちにVue公式チームが vue-component-compiler
というライブラリをリリース予定であることも分かりました。
現在はまだDesigning Threadの状態ですが、「バンドラに依存しないSFCのコンパイル用API」という位置付けで2018年にリリース予定とのことです。
確かに今回 vue-loader
と vueify
のソースを読む中でロジックが全く共通化されてなくてマズイなーという感想を抱いたので、 vue-component-compiler
がリリースされるのが楽しみです。
みんなで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
を作ってくれたようです!
実際に .vue
ファイルを用意して試してみたところちゃんと動作しました!どうやら vueify
のAPIを利用しているようですね。
これでVueアプリケーションの開発が捗ります。ありがとうございます!!