最新のソフトウェアや自作することを選ばずあえて昔のソフトウェアを使った話

最近JavaScript周りのしごとが増えてきました。アドテクノロジーディビジョンの弓場です。

今日は技術選定の話になります。

業務で発生した課題

現在、私が開発に携わっている案件はSaaSでよくあるようなマルチテナンシー形式に近いWebアプリケーションです。
そのWebアプリケーションはVue2で開発されていました。しかし、2023年末にVue2のサポートが終了する予定になってしまったためVue3に段階的に移行を行っています。 Vue3移行作業はVue2で開発されたコードをVue3として新規に書き直してnginxで切り替える作業をしています。

Vue2のコードはいくつかのコードを複製することでほぼ要件通りに動かせるような作りをしています。Vue3においてもコード複製で済ませられる状態でした。 そこで今回私はこのコードの複製を効率的に行えるようにいろいろを試行錯誤したお話をします。

技術選定

今回実現したいことは以下の通りです。

  • テンプレートの元となるVueファイルやScssファイルやTypeScriptファイルだけを作成したい。 webpackやviteのconfファイルなどはコピーしない
  • インタラクティブなプロンプトでテンプレートを作れるようにしたい。(コンソール上でyes、noと答えられるもの)
  • あまり時間をかけずに構築出来るもの
  • 継続的にメンテナンスされているもの

そこで私は今回の課題を解決するソフトウェアはScaffolding系と考え、いろいろなソフトウェアを探しました。

vue-cli

vueのチュートリアルを実施する際に使われる vue-clivue init simple my-project といったコマンドは今回の課題に向いていると思いました。

ですが、vue-cliの公式サイト を見てもちゃんとしたテンプレートのチュートリアルを見つけることが出来ず、既存のテンプレートを持ってきて編集する方法しかなかったため時間がない関係でこちらを使用するのはリスクがあると思いました。

create-vue

vue-cliは既にメンテナンスモードに入っているので、次の候補となっている create-vue についても調査しました。
しかし、こちらもドキュメントを読んでも自作のテンプレートを開発出来る仕組みが用意されておりませんでした。

自作

shell scriptのみで構成されたコードはメンテナンスが少し大変です。しかしGoogleのzxというソフトウェアはJavaScriptでshell scriptを実行することが出来ます。また question() など便利な関数も用意されており、従来のshell scriptも一緒に使えて自作するには都合が良いと思います。

ですが、テンプレートファイルを出力するソフトウェアも決めなければなりません。その結果様々なソフトウェアが介入するとそれだけ学習コストも高くなり、あまりシンプルなライブラリ構成でなくなると判断したため断念しました。

yeoman

最後にyeomanです。こちらはまだyarnなどが存在していない2012年ごろにGoogleによって開発されたソフトウェアです。

こちらはScaffoldingを実現するためのソフトウェアで、公式ドキュメントもあるため初心者にも優しいものになっています。

今となってはあまり活発的に新機能が開発されるようなものでありませんが、ライブラリの更新やバグフィックスは未だに行われています。
執筆時では今年の7月にも更新が行われました。 https://github.com/yeoman/generator/releases
また、今回必要としていた機能も全て用意されてたので今回はこのソフトウェアを選びました。

yeomanを選ぶことのデメリットと私の技術選定の感覚

yeomanはあまりモダンとは言えないソフトウェアです。
最新のバージョン5は2021年に配信されましたが、内容もNode12以上を必須とする対応がメインで他はそれで起きた問題やバグフィックス作業がメインに見受けられます。
つまり、yeomanを使用していて何かほしい機能が出てきた場合、恐らく自分でカスタマイズすることになります。

ただ、私は今回の課題に対しては必要な機能は全て揃っていると思っており、結果的にこれで一通り必要な非機能要件を満たせるツールを作成することが出来ました。
ソフトウェアを自作しようとすると自社でメンテナンスすることになり、開発以外に時間を取られがちになってしまうため、こういう機能変更や互換性の無い修正がほぼ無いソフトウェアは助かる場面が多いと思います。

また、今回メンテナンスに必要になるドキュメントも公式で用意していることを気にしました。
私以外の人がメンテナンスするときに資料がなければメンテナンス出来ないと考えたためです。

yeomanでカスタムジェネレーターを動かす

ここからはyeomanのカスタムジェネレーターの動かし方を紹介します。
基本的に公式ドキュメントに沿った説明になります。

準備

まず、任意のディレクトリを作成して package.json を追加します。

mkdir example-project
vim package.json
// package.json
{
  "name": "example-project",
  "version": "0.1.0",
  "description": "",
  "dependencies": {
    "yo": "^4.3.0"
    "yeoman-generator": "^5.7.0",
  }
}

次にテンプレートの元となるディレクトリを作成して、yeomanジェネレーター処理を定義するindex.jsファイルを追加します。

yarn  # ライブラリのインストール
mkdir Japan
vim Japan/index.js
// Japan/index.js

"use strict";
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
    method1() {
        this.log('method 1 hello world');
    }
}

最後にpackage.jsonにジェネレーターを動かすスクリプトを追加して、実行してみます。

{
  "name": "example-project",
  "version": "0.1.0",
  "description": "",
+ "scripts": {
+   "generator": "yo ./Japan/"
+ },
  "dependencies": {
    "yeoman-generator": "^5.7.0",
    "yo": "^4.3.0"
  }
}
$ yarn generator
yarn run v1.22.17
warning package.json: No license field
$ yo ./Japan/
method 1 hello world

No change to package.json was detected. No package manager install will be executed.
✨  Done in 2.55s.

上記のような結果が得られたと思います。 これでgeneratorを動かす準備が整いました。

ファイルのコピー

https://yeoman.io/authoring/running-context.html: プロパティメソッドに関する公式ドキュメント
https://yeoman.io/authoring/file-system.html: ディレクトリの読み取りやファイル書き込みに関する公式ドキュメント

次にScaffoldingの基本的なファイルコピーを実装します。
yeomanには予め割り当てられたメソッド名に実行の優先順位をつけています。今回はコピーなどのファイル書き込みを目的とした writing メソッドにコピーの中身を実装します。

"use strict";
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
-   method1() {
-        this.log('method 1 just ran');
-   }
+   writing() {
+       this.fs.copyTpl(
+           this.destinationPath('Japan/index.vue'),
+           this.destinationPath('China/index.vue'),
+       );
+   }
}

それとコピー元のVueファイルも用意します。

// Japan/index.vue
<script setup lang="ts"></script>
<template>Hello Japan</template>

これで準備が整いましたので再度スクリプトを実行します。

$ yarn generator
...
   create China/index.vue

No change to package.json was detected. No package manager install will be executed.
✨  Done in 2.71s.
ls
China Japan ...

無事別のディレクトリにファイルをコピーすることが出来ました。

インタラクティブなプロンプトによって動作を変える

https://yeoman.io/authoring/user-interactions.html: ユーザーインタラクションに関する公式ドキュメント
https://yeoman.io/authoring/file-system.html: ファイルテンプレートの変数に関する公式ドキュメント

最後に実行時にインタラクティブなプロンプトを表示して、入力内容によってコピーの出力結果を変えられるようにします。 入力を受け取るメソッドは writing ではなく prompting のためそちらに受け取りの定義を記載します。

// Japan/index.js
...
+   async prompting() {
+       this.answers = await this.prompt([{
+           type    : 'input',
+           name    : 'country',
+           message : '任意の国名を入力してください',
+       }]);
+   }
    writing() {
...

そしてコピーする側も変数を使用するように変更します。 this のプロパティに設定された変数は別のメソッドでも使用出来るため
以下のようにコピーする箇所も変更します。また、テンプレートファイル上で変数を使えるようにオブジェクトも渡してあげます。

// Japan/index.js
...
    writing() {
        this.fs.copyTpl(
            this.destinationPath('Japan/index.vue'),
-           this.destinationPath('China/index.vue'),
+           this.destinationPath(`${this.answers.country}/index.vue`),
+           {country: this.answers.country}
        );
    }
...

テンプレートファイル上に変数を展開するように記載を変更します。 変数などを展開する場合には <%= %> 、if文やfor文を使用する場合には <%_ _%> で囲ってあげる必要があります。
今回は変数を展開するだけなので以下のような変更になります。

// Japan/index.vue
<script setup lang="ts"></script>
- <template>Hello Japan</template>
+ <template>Hello <%= country %></template>

最後にスクリプトを実行して動作確認をします。問題なく実装できていれば以下のような出力になります。

$ yarn generator
...
? 任意の国名を入力してください Korea
   create Korea/index.vue

✨  Done in 3.90s.
$ cat Korea/index.vue 
<script setup lang="ts"></script>
<template>Hello Korea</template>

今後気をつけること

自分もしくは他者の作ったものは、それ以外のメンバーが意識しなければ陳腐化してしまうものです。
今回作成したツールも同様に陳腐化する危険性があります。

これに対し私が今まで試したのはCIで動かしてアラートを流す、ドキュメントを整備する、ツールの存在を周知するといった行動を行ってきました。しかしどれも最後までしっかりと開発の文化として昇華したものはありませんでした。
そのため今回は仕組み化だけではなく、人に対して直接ヒアリングをかけるアプローチをしてこのツールが移行完了するまではしっかりと使われる状態を目指したいと思います。

おわりに

恐らく今回の記事で紹介したyeomanは2022年において新しいとは言えないソフトウェアであるため、他の筆者と違い目新しさが薄く面白さや役に立ったみたいな感想を抱く人は少ないと思います。
ですが、私が考えている技術選定のお話や、同じくyeomanでScaffoldingを作ろうとしている人にとって何か価値を届けられたらと思います。