開拓馬の厩

いろいろやる

FuseBoxというイケてるmodule bundlerがあるらしい

FuseBoxというmodule bundlerがイケてるらしいので使ってみた github.com

どこらへんがイケてるのか

公式サイトGitHubのREADMEによると以下のような利点があるらしい。

  • 早い
  • デフォルトでTypeScriptやJSX記法に対応している
  • devサーバー内蔵
  • HMR(hot module replacement)がサポートされている
  • configが楽

実際に使った感想としては、configファイルが簡素な点、で専用のCLIを使わない点、後述のWebIndexPluginなどが主な魅力だと思われる。

実際に使ってみた

実際に使ったソースコードこちら

以降は、実際に書いたコードを提示しながら*1、FuseBoxの使い方を説明する。 「ただbuildするだけ→devサーバーを使う→pluginを入れる」のように後半になるにつれて高度な機能の説明になっていく。

とりあえずbuild

まず適当なtsファイルをbuildしてみる。src/ディレクトレリを作って、その中にindex.tsという名前で以下のソースコードを保存する。

// index.ts

const msg: string = "Hello World";
console.log(msg);

次にyarnを初期化して、fuse-boxtypescriptをインストールする。

$ yarn init
$ yarn add -D fuse-box typescript

READMEに習って以下のようなconfigファイルを書き、fuse.jsの名前で保存する。

// fuse.js
const { FuseBox } = require("fuse-box");

const fuse = FuseBox.init({
  homeDir: "src",
  output: "dist/$name.js",
  sourceMaps: true,
});

fuse.bundle("app").instructions("> index.ts");

fuse.run();

fuse.bundle()ソースコードを一つのjsファイルにまとめるコマンドで あり、.instructions()でビルドの起点になるファイルを指定する(webpack.config.jsでいうところのentry)。

設定ファイルを記述したら、以下のコマンドを実行する。FuseBoxはwebpack-cliの専用のCLIコマンドは用意されておらず、configファイルをそのまま実行する形式を取っている(npm i -gしたくない派の人喜びそう)。

node fuse

すると、dist/ディレクトリが作られ、中にビルド結果がapp.jsapp.js.mapの名前で出力される。

ちなみに今回のように、tsconfig.jsonを作らずに実行すると、src/以下に自動生成してくれる。 targetbaseUrlなどの設定項目はfuse.jsに書いた通りにしてくれるのでちょっと便利。

devサーバーを動かす

fuse.jsにdevサーバーを立てる設定を追記する。 ついでに出力されるjsとsource mapをブラウザ向けのものにしておく。

// fuse.js
const { FuseBox } = require("fuse-box");

const fuse = FuseBox.init({
  homeDir: "src",
  output: "dist/$name.js",
  target: "browser@es5", // ブラウザ向けのコードを吐かせる。ついでにバージョンもes5に指定
  sourceMaps: {vender: true}, // ブラウザ向けのsourceMapを出すようにしておく
});

// devサーバーの設定
fuse.dev({
    port: 8888 // port番号。デフォルトは4444。
    root: 'dist' // devサーバーのルートディレクトリ。デフォルトはFuseBox.init()の`output`で指定したディレクトリ。
});

fuse.bundle("app")
    .instructions(`> index.ts`)
    .watch() // tsを書き換えるたびに差分コンパイルを行う
    .hmr(); // hot module replacementを有効にする

fuse.run();

ES5で出力するようにしたので、index.tsもES6以降の機能を使ったやつにしてみる。

enum LogLevel {
    Info,
    Error,
}
type Message = { type: LogLevel; message: string };

const showMessage = (m: Message): string => {
    switch (m.type) {
        case LogLevel.Info:
            return `📢 ${m.message}`;
        case LogLevel.Error:
            return `⚠️ ${m.message}`;
        default:
            throw new Error("invalid message");
    }
};

const hello: Message = { type: LogLevel.Info, message: "Hello World" };
document.querySelector("body").innerHTML = showMessage(hello);

最後にdist/index.htmlを作成すれば準備完了。

<!-- index.html -->

<body>
  <div id="app"></div>
  <script src="/app.js"></script>
</body>

node fuseを実行し、localhost:8888にアクセスすると「📢 Hello World」が表示される。 ついでにapp.jsを確認すると、ES6やTypeScript特有の記法がES5に変換されていることが確認できる。

// app.js の抜粋
var LogLevel; // TypeScriptのenumで書いた箇所
(function (LogLevel) {
    LogLevel[LogLevel["Info"] = 0] = "Info";
    LogLevel[LogLevel["Error"] = 1] = "Error";
})(LogLevel || (LogLevel = {}));
var showMessage = function (m) { // ES6のarrow関数で書いた箇所
    switch (m.type) {
        case LogLevel.Info:
            return "\uD83D\uDCE2 " + m.message;

WebIndexPluginでindex.htmlを自動生成

前章ではindex.htmlを手動でdist/に置いた。しかし、dist/は自動出力専用にして、手書きのコードは他のディレクトリで管理したいという要求もあるだろうと思われる(私はそうしたい)。 そこで、WebIndexPluginというものを使ってそれを実現する。

まず、src/public/template.htmlを作る。

<!-- hemplate.html(Emmetのテンプレをちょっと書き換えただけ) -->

<!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>My Page</title>
</head>
$css<!-- ここにstyleタグが挿入される -->
<body>
  <div id="app"></div>
  $bundles <!-- ここにscriptタグが挿入される。設定次第で、src=""で別ファイルから読み込むかinlineか選べる -->
</body>
</html>

fuse.jsプラグインの設定を追加する。

// fuse.js

const { FuseBox, WebIndexPlugin } = require("fuse-box"); // WebIndexPluginもimportする

const fuse = FuseBox.init({
  homeDir: "src",
  output: "dist/$name.js",
  target: "browser@es5",
  sourceMaps: {vender: true},
  plugins: [WebIndexPlugin({ template: "src/public/template.html" })], // これを書き足す
});

実行するとdist/index.htmlが自動生成される。ちなみに、WebIndexPluinの引数でtemplateを指定しない場合、適当なhtmlファイルを自動生成が作られる。便利。

まだまだあるぞ

まだ色々紹介したい機能があるが、長くなったので記事を分けたい。 残っているネタは以下の通り。

  • AltCSSを使ってみた
  • JSXを使ってみた
  • fuse-box/sparkyで静的ファイルのコピーをしてみた

ついでにwebpackあたりとの比較もやってみたい(未着手)。

ただ、7月17日時点で、依存ライブラリに脆弱性が見つかっているようなので本格的に使うなら次のバージョンを待った方がよさそう*2f:id:pf_siedler:20190717180724p:plain

*1:清書の段階で修正した箇所もあるためgitのlogと不整合な箇所もある

*2:version 4の準備が進んでいるらしく、hot fixするのかv 4公開のタイミングで直すのかよくわからない。7月17日時点でdependencyのバージョンを上げるissue/PRは出てないのでcontributionチャンスかも?