JavaScript
Node.js
タスクランナー

JSer のためのタスクランナー MEMI

TL;DR

NPM

  • https://github.com/3846masa/memi
  • JSer のためのタスクランナー MEMI
  • ES module で 1タスク 1Function で書ける
  • 依存関係を自動でインストールしてくれる

なんで作ったのか

きっかけは mimorisuzuko/memi

要約すると,

  • JavaScript で書けるタスクランナーが欲しい
    • Makefile やシェルスクリプトは,凝った処理を書くのが怠い
  • どのディレクトリでも手間なく使いたい
    • Node.js に関係ないディレクトリで node_modulespackage.json を置きたくない
  • タスクで使うモジュールのグローバルインストールは避けたい
    • npm root -g の場所とは別のところに置きたい

既存のタスクランナーと問題点

  • Makefile
    • シェルスクリプトで書かないといけないので,ちょっと複雑なことをしようとすると怠い
    • Windows で make を入れるのが怠い
  • npm scripts
    • package.json を置きたくない
  • gulp
    • 使うモジュールもグローバルかローカルに入れないといけない
    • node_modules を置きたくない
    • グローバルインストールは増やしたくない

JSer のためのタスクランナー MEMI

Memifile.js でタスクを定義できる.

import fs from 'fs-extra';
import path from 'path';
import execa from 'execa';

export default {
  async clean() {
    await fs.remove(path.join(__dirname, 'dist'));
  },
  async build() {
    await this.clean();
    await execa.shell('webpack');
  },
};

ネストが気になる人や Default export 排除派は, Named exports でも書ける.

import fs from 'fs-extra';
import path from 'path';
import execa from 'execa';

export async function clean() {
  await fs.remove(path.join(__dirname, 'dist'));
}

export async function build() {
  await clean();
  await execa.shell('webpack');
}

ついでに, CommonJS 方式でも書ける.

const fs = require('fs-extra');
const path = require('path');
const execa = require('execa');

module.exports = {
  async clean() {
    await fs.remove(path.join(__dirname, 'dist'));
  },
  async build() {
    await this.clean();
    await execa.shell('webpack');
  },
};

タスクは,memi <taskname> で実行する.例えば, build したいなら memi build とすれば動く.
もちろんサブディレクトリ内にいても,Memifile.js を再帰的に探しに行くので問題ない.
これを応用すると,ホームディレクトリに Memifile.js を入れておくことで,どこでも使えるコマンドも作れる.

memi <taskname> arg1 arg2 ... とすると,Function の引数に arg1 などが渡されるので,ちゃんとしたコマンドを作ることもできる.
Inquirer なども使えるので,対話式のコマンドも難なく作れる.

import inquirer from 'inquirer';
import cowsay from 'cowsay';

export default {
  async echo(text) {
    if (!text) {
      const res = await inquirer.prompt([{
        name: 'text',
        message: "How's it going?",
      }]);
      text = res.text;
    }
    console.log(cowsay.say({ text }));
  },
};

さらにここからが MEMI の真骨頂で,依存するモジュールを自動でインストールしてくれる
例えば,上の Memifile.js では, fs-extraexeca がインストールされる.
インストール先は $HOME/.memi%USERPROFILE%\.memi になるので,グローバルと混ざらないし,ローカルに入れる必要もない.


MEMI は 小さいスクリプトを手間なく書き始められて ,出来が良ければ そのままホームディレクトリに突っ込んで,いつでも使える CLI コマンドにできる

気軽にも使えるし,本格的にも使えるタスクランナー.
ぜひ使ってみてほしい.

余談: Qiita API と MEMI

この記事は VSCode から書いて API 経由で投稿したが,それも MEMI でざっくりコマンドを作った.

import 'dotenv/config';
import fs from 'fs-extra';
import fm from 'front-matter';
import axios from 'axios';
import opn from 'opn';

export async function qiita(file) {
  if (!file) {
    return;
  }

  const { attributes, body } = fm(await fs.readFile(file, 'utf8'));
  const { data: res } = await axios.request({
    method: 'post',
    url: 'https://qiita.com/api/v2/items',
    data: {
      body,
      title: attributes.title,
      tags: attributes.tags.map(name => ({ name })) || [],
      private: true,
    },
    headers: {
      Authorization: `Bearer ${process.env.QIITA_TOKEN}`,
      'Content-Type': 'application/json',
    },
  });

  console.log(res.url);
  opn(res.url);
}

memi qiita filename.md とすれば,投稿されて URL がブラウザで開く.
しっかり作るなら更新機能とかも欲しくなるが,取っ掛かりとしてはこの程度から始めるのでも良い気がする.

余談: MEMI とは

発案者曰く,

柿崎芽実さんはけやき坂46のメンバーで,memiとmakeが似てるから選びました.推しは齊藤京子です.