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


## TL;DR

[![NPM](https://nodei.co/npm/memi.png?compact=true)](https://www.npmjs.com/package/memi)

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

## なんで作ったのか

きっかけは [mimorisuzuko/memi](https://github.com/mimorisuzuko/memi)．

要約すると，

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

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

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

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

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

```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 でも書ける．

```js
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 方式でも書ける．

```js
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](https://github.com/SBoudrias/Inquirer.js/) なども使えるので，対話式のコマンドも難なく作れる．

```js
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-extra` と `execa` がインストールされる．
インストール先は `$HOME/.memi` か `%USERPROFILE%\.memi` になるので，グローバルと混ざらないし，ローカルに入れる必要もない．

---

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

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

## 余談： Qiita API と MEMI

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

```js
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が似てるから選びました．推しは齊藤京子です．