公開日: 2025年3月16日

【JavaScript】 Gulp フリーなタスクランナーもどきを自作する

当サイトのビルドにはこれまで Gulp とそのプラグインを利用してきましたが、継続的なメンテナンスに不安があり脱却することにしました。代替ツールはどれも当サイトには過ぎたるプロ仕様?だったため、簡易的なビルドシステムを自作してみることにしました。その経緯を手短かに解説します。

きっかけ

開発環境を久しぶりに更新したところ、次のような警告が。

npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm warn deprecated css-mqpacker@7.0.0: Package no longer supported. Contact support@npmjs.com for more info.

なんか「メモリリークするから使うな」とか「ノーロンガーサポーテッド」とかいろいろいわれております。調べてみると、どうも私が使いたい「gulp-sass-glob」という Gulp のプラグインが「glob」というパッケージに依存しており、その glob がさらに「inflight」というパッケージに依存しており、この inflight がメモリリークの犯人のようでした。2025 年 3 月現在で inflight はすでに更新停止、glob は inflight から脱却、gulp-sass-glob は 6 年前から更新されておらず、今も古い glob と inflight に依存中。という状況のようです。

こうして眺めてみると、他の Gulp プラグインでも長らく更新されていないものがちょくちょく見られ、使い続けることに不安を禁じ得ませんでした (ちなみに Gulp 本体は昨年、6 年くらいぶりとなるバージョン 5.0.0 がリリースされています)。いい意味で枯れているならよいのですが、実際今回のようなケースもあったわけですからね…

タスクランナーの自作に踏みきる

「じゃあ代替をどうしようか」と検討を始めましたが、冒頭にも書いたとおりで、なかなかゆるふわなツールが見つかりませんでした。当サイト場合、ビルドの際に行っていることは基本的に

  • EJS というテンプレートエンジンを使って .ejs から .html を作る
  • Sass を使って style.scss から style.css を作る

これだけです。この程度なら JavaScript で書けそうだなと思いました。イメージはこんな感じ:

const ejsfiles = *.ejs ファイルをグロブする;
for (ejsfile of ejsfiles) {
    const ejstext = ejsfile からテキストデータを読み込む;
    const html = ejs.render(ejstext);
    html を HTML ファイルに書き出す;
}

問題は私が JavaScript の書きかたをろくに知らないことですが、この際一念発起して勉強することにしました。

自作のメリット / デメリット

説明に入る前に、私が実際に自作してみて感じたメリットとデメリットを挙げておきます (あくまで私の場合。スキルがあればデメリットとして挙げた点はいくらでもカバーできると思います)。

メリット:

  • Gulp 依存から脱却できる
  • かゆいところに手が届く (自分のやりたいようにやれる)
  • JavaScript の勉強になる

デメリット:

  • 設定ファイルが gulpfile.js のようにスマートに書けない
  • 汎用性に欠ける (自サイト専用になる)
  • JavaScript の勉強が大変

Gulp 脱却への目論見

さて、実際には EJS や Sass 以外にもさまざまなパッケージを利用してビルドを行っています。まずはどんな Gulp プラグインを使っているのか現状把握します。自分でも知らないあいだに、けっこういろいろ使っていました。主要なものを挙げると次のようになります:

Gulp プラグイン説明代替策
gulp-sass-glob

src/sass/**/*.scss をグロブする

不要(*)

gulp-sass

Sass コンパイル

sass

gulp-postcss, css-mqpacker

メディアクエリをまとめる

postcss-sort-media-queries

gulp-clean-css

CSS 圧縮

cssnano

gulp-data

ファイルの絶対/相対パスを取得

自作

gulp-ejs

EJS のレンダリング

ejs

gulp-rename

拡張子置換 .ejs → .html

自作

gulp-htmlmin

HTML 圧縮

html-minifier-terser

gulp-uglify-es

JS 圧縮

terser

gulp-sitemap

sitemap.xml 自動生成

自作

(*) 別記事 「【Sass】 FLOCSS 指向で @import から @use / @forward へ移行する」参照

これらを、表の右端の列にある代替策に置き換えていきます。Gulp プラグインが内部で呼び出しているであろうパッケージは、プラグインを経由せず自作ツールから直接呼び出すように変更します。呼び出すパッケージ自体が古かったり更新停止状態だったりした場合は、代替品に乗り換えます (上の例でいうと postcss-sort-media-queries など)。または自作できそうなら自作します (拡張子の置換など)。

タスクランナー自作の実際

ここでは EJS ファイルを HTML に変換し、圧縮 (minify) する工程を例に、Gulp から 自作ツールへの移行を説明します。都合上、この一連の工程を「EJS タスク」と呼ぶことにします。

サンプルプロジェクトのソースコードの全文は GitHub にあります。

task_ejs.js や builder.js などが今回説明する部分を含んだソースファイルになります。当サイトに特化した作りになっているので、あまり参考にならないかもしれませんが。

移行前 (gulpfile.js)

移行前の gulpfile.js では、EJS タスクの部分をだいたい次のように書いていました (最新版の Gulp ではまた別の書きかたをするみたいです)。

gulpfile.js (一部疑似コードを含む)
const gulp = require('gulp');
const ejs = require('gulp-ejs');
const rename = require('gulp-rename');
const htmlmin = require('gulp-htmlmin');

function ejs_task() {
    return gulp.src(['src/ejs/**/*.ejs', '!src/ejs/**/_*.ejs'])
        .pipe(ejs({EJS オプション}))
        .pipe(rename({extname: '.html'}))
        .pipe(htmlmin({圧縮オプション})
        .pipe(gulp.dest('public/'))
        ;
}

...

exports.default = gulp.series(
    sass_task,
    ejs_task,
    sitemap_task,
);

6 ~ 13 行目がタスクの記述です。gulp.src() で処理対象の EJS ファイルをグロブし、ファイルごとにテキストデータを読み出してパイプラインに流し込みます。gulp.pipe() から呼び出される各フィルタによりテキストを加工したのち、gulp.dst() で HTML ファイルに保存します。

17 ~ 21 行目が各タスクの実行部分です。gulp.series() により、EJS タスクを含めた各タスクを実行します。

移行後

移行後のコードは次のような感じになります。説明のために簡略化して書いてはいますが、基本はこうです。ファイル名はなんでもいいですが、とりあえず build.js にしておきます。

build.js (一部疑似コードを含む)
import { glob } from "glob";
import ejs from "ejs";
import { minify } from "html-minifier-terser";

async function taskEjs(conf) {
    const files = await glob(["**/*.ejs"]);
    for (const file of files) {
        let text = file からテキストデータを読み込む;

        // テキストを加工する
        text = ejs.render(text, {EJS オプション});
        text = minify(text, {圧縮オプション});

        text を HTML ファイルに保存する;
    }
}

...

async function build() {
    await taskSass();
    await taskEjs();
    await taskSitemap();
}

とても簡易的な実装ですが、やっていることは移行前とほぼ同じです。難点は 1 ファイル分のテキストデータをまるごとメモリに読み出してしまう点でしょうか。その点、Gulp は「ストリーム」というしくみを使って、データをちょっとずつ読みながらパイプラインに流していく作りになっているみたいです。私はとてもそこまではできそうにないので、現代の潤沢なリソースにものをいわせますが、あまりにも長大な記事を書いてしまうと、メモリを圧迫する可能性があります (笑)。

開発環境の構築

開発環境の構築については、次のリンク先などを参照してください。

プログラムの実行方法

自作した build.js は、次のようにして実行します。

> node build.js

または package.json に次のように書いておけば、npm スクリプトとして実行できます。プロジェクトの規模が大きくなったり、コマンドライン引数を扱ったりする場合は、こちらを使ったほうが楽かもしれません。

package.json (抜粋)
{
  ...
  "scripts": {
    "build": "node build.js",
    ...
  },
  ...
}
> npm run build

あとは他のタスクも同様にして作っていけばいいわけですが、ここから先はサイトによって事情がちがうと思うので、このへんでやめておきます。

おまけ: XML サイトマップの自動生成

当サイトでは gulp-sitemap というプラグインを使って生成した XML サイトマップを設置していました。これにはひとつ欠点がありまして (たぶんプラグインの欠点というよりは、当サイトのビルドプロセスの問題)、次の例のようにどの記事の lastmod (最終更新日) も実際の更新日ではなく、サイトをビルドした日時がセットされてしまっていました

gulp-sitemap で生成した XML サイトマップ (見やすいように整形済み)
    ...
    <url>
        <loc>https://retrotecture.jp/etc/emacs_win.html</loc>
        <lastmod>2025-01-14T19:45:24.241Z</lastmod>
    </url>
    ...
    <url>
        <loc>https://retrotecture.jp/freebsd/japanese.html</loc>
        <lastmod>2025-01-14T19:45:24.649Z</lastmod>
    </url>
    ...
    <url>
        <loc>https://retrotecture.jp/freebsd/xmodmap.html</loc>
        <lastmod>2025-01-14T19:45:24.727Z</lastmod>
    </url>
    ...

このように XML サイトマップの日時と実際の日時が異なると、SEO 的にも不利になるようです。まあちゃんと確かめたわけではないので真偽は不明ですが、項目が存在するからには真面目にセットしておいたほうがいいに決まっています。

というわけで、Gulp から脱却するついでに XML サイトマップの生成処理も自作することにしました。当サイトでは記事のメタデータを CSV ファイルで一元管理しているので、そこから記事の最終更新日 (なければ公開日) を取ってきて記事の URL とともに書き出せばよいので、そんなに難しいことではありませんでした。

記事のメタデータが書かれた CSV ファイル
# key, category, title, release, lastmod, fname,
xmodmap, freebsd, "Xorg のキーマップをカスタマイズしようと思う", 2021-07-04, , "xmodmap.html",
japanese, freebsd, "Fcitx と Mozc で日本語入力", 2021-07-26, 2023-08-04, "japanese.html",
emacs, freebsd, "GNU Emacs TIPS", 2021-08-22, 2023-10-05, "emacs.html",
自作ツールで生成した XML サイトマップ
    ...
    <url>
        <loc>https://retrotecture.jp/freebsd/xmodmap.html</loc>
        <lastmod>2021-07-04</lastmod>
    </url>
    <url>
        <loc>https://retrotecture.jp/freebsd/japanese.html</loc>
        <lastmod>2023-08-04</lastmod>
    </url>
    <url>
        <loc>https://retrotecture.jp/freebsd/emacs.html</loc>
        <lastmod>2023-10-05</lastmod>
    </url>
    ...

移行後の XML サイトマップでは時分秒の情報が欠落していますが、1 日に何回も更新するわけではないので問題ないと思われます。

ソースコードの全文は、サンプルプロジェクトの task_sitemap.js などにあります。

広告