電波ビーチ

☆(ゝω・)v

nodeに入門して1週間経ったので整理がてらnpmをpublishしてみた記録

フロントエンドにはあまり興味ないんだけど(は?)、就活で狙えそうなのはWeb系ってことで学習している(え...)。このご時世にJSに一から触れることの複雑さ・困難さは、独学自習で未経験で転職だと伸び代やcontribute数なんかよりも使い捨て兵士としての心構えが求められるみたいな、新規参入組に冷たすぎる構図と似ている。もちろんこじつけだ。

日本企業の多くの面接ではアルゴリズムの能力よりも、「インターンや何かアプリを作った経験」とか、「その人がどんな人であるか」にスポットを当てることが多かったです。その人を表す指標の一つとして、なぜ競技プログラミングに出ているのか、どの辺が好きなのか、などはたまに聞かれました(が、他の経験についての方が深く聞かれました)。 ひらめの日常 - 就活終えました

閑話休題(新卒でつよつよでこんな状況になっちゃうならおじさんなんかにお鉢はまわってくるわけないね、ごめんね)

適当に手を動かしてJSになじんでいきたいのと、適当な書捨てプログラムをでっちあげるのが性に合っているので、今回はその一環としてnpmを作ってみるたメモ。ブラウザを介するのがめんどくさい(は?)ため単機能CLI用npmです

For Whom

対象とする層は、nodeに入門したくらいです。対象とするパーソナリティは、せっかく夏休みに入ったのに構ってくれないお兄ちゃんの真似をしてnodeをやってみたけど具体的になにが出来てなにをすればいいのか分からないけれど、そのことをお兄ちゃんに素直に聞けない、少しだけ歳の離れた妹です

なお妹はお兄ちゃんが知らないだれかにマウントを取られている姿を見るのはとても堪えられないタイプなので、ご指摘はバレぬようこっそり且つお気軽にどうぞ!

Goal

  • 適当に書くための環境を整えられる
  • 適当に書いたやつをローカルでコマンド呼び出しする
  • npmにアップロードする・インストールしてCLIで使えるようにする

やらないこと

  • CI
  • テスト
  • リーダブルコード
  • JSの記法・文法・構文等
  • TypeScript
  • Babel, Webpack等のビルドツール
  • republish, アップデート

いずれも現時点で知識が無ですが、特にCIやテストなどの開発ツールはプライベートでのみ書いてきたなら当然だし、むしろ業務でやってる人がみな習得しているかというとそんなわけないでしょうし、むしろ「やらないんじゃなくて、知らねぇことは出来ないんだよガッハハハ〜〜」と開き直るくらいでいいです

成果物

今回はコードはでっちあげるだけなので、HelloWorldで構いませんが、実際に作ってみた最終成果物はこちらになります

www.npmjs.com

Prerequests knowledge

これだけ知っていれば出来る、などというものではなく、今回これらを使うけどとくに説明はしないよ、という程度です。もちろんこれら以外も使ったりするよ

Environment

いつも使っている、使い捨ての環境です。ぶっちゃけnodeとnpmが入ってりゃどうでもいいと思います

$ cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
$ npm -v
6.9.0
$ node -v
v12.6.0

0. Project Building

githubリポジトリの作成

基本的にはnpmに公開するものはgithubへのリンクがついてるのが常な気がするし、最初に作るくらいならそんな秘匿するほどすごいもんでもないでしょう。公開する予定がなくローカルだけで使うってんなら必要ないよ。いつものように適当にNew Repositoryしてこれです

git clone https://github.com/your_username/rannum.git
cd rannum

べつに公開しないんならふつうにフォルダを作成するだけでもいいよ

mkdir rannum
cd rannum

package.jsonの作成

package.jsonは、nodeのプロジェクトをパッケージとして公開・配布するときに必要となる情報や依存するライブラリ、開発時に使うスクリプトのタスクなんかを記載したものです。そういうのを名前("package.json")で判断できないから教えるのが下手糞だと妹が怒っていました。でも「じゃあnode-package.config.jsonならどうだっただろうか」と聞いたらキモい近づくな変態と言われたので難しいね

雛形はnpm initで作成します。これは使わないにしろ、基本的に脳死で打っても構わないコマンドだよ

npm init

対話的に各情報を尋ねられるけど、あとで変えられるのでEnterキーで飛ばしても構いません。-yオプションで聞かれる項目をすべてデフォルトにすることも可能です。yは「ysoideirunde(いそいでるんでちょっと...)」 ではなくyes(そうだね〜)の意味です

npm init -y

ここで同階層に.gitがあると、githubレポジトリ名やユーザー名そのほかの情報を自動的にストーキングして読み取ってくれます。

{
  "name": "hanson",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/halllllll/rannum.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/halllllll/rannum/issues"
  },
  "homepage": "https://github.com/halllllll/rannum#readme"
}

.gitがないとこんなかんじ

{
  "name": "rannum",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

各フィールドの説明は、ここではとくに個別にやったりはしません。package.jsonに関しては本稿でも登場するのはこのあと2,3回だけで、デフォルトには無い項目を直接編集して追加する際およびnpmをpublishする際に再登場することになります

依存パッケージのインストール

これはべつに必要に応じて都度やればいいので、外部のnpmパッケージを使用しない場合は飛ばしても構いませんし、ここでやらなければいけないということもないです。必要になったら入れる、でいいです

テストやビルド時のように開発時のみ必要なパッケージは--save-devオプション、実行に必要な依存パッケージは--saveオプションをつけてインストールすると、いい感じにやってくれるのでそのとおりに任せます。インストールしたパッケージの情報は自動的にpackage.jsonに反映され、パッケージ本体はその依存先も含めてnode_modulesにぶちこまれます。 今回はコマンドライン引数のパーサーであるっぽいcommanderと、配列操作をいい感じにできるっぽいlodashを入れたよ

$ npm i --save commander lodash

コマンド自体の詳しい説明や他の使い方は本稿では書きません

なおnode_modulesにはアホほどファイルが含まれるし配布には必要ない(どうせ使うときはインストール時にpackage.jsonをみてダウンロードされる)ので.gitignoreに書いて外に出さないようにしておくのが一般的です

ここまでの構成です

.
├── .git
├── .gitignore
├── node_modules
├── package-lock.json
└── package.json

1. Coding

ここまでは単なる開発のための設定で、一切コーディングはしてないです。妹でもできます。こっから実際のロジックを作る段階です

1ファイルだけの簡易なプロジェクトだと直下にファイルを置くのが多いっぽいんですが、今回は直下にbinフォルダを作り、そこに置くことにします。このやり方も特別というわけではなく、べつに1ファイルだけでもbinに置くパターンはあるので、臨機応変にやっていこうね

mkdir bin
touch bin/index.js

nodeコマンドで実行

index.jsを適当にでっちあげて実行してみます

console.log("Yo");

nodeで実行するにはnodeコマンドを使うようです

node bin

無事出力されたでしょうか。このようにnodeコマンドはデフォルトでindex.jsを探して実行してくれるようです。かしこいね。でもmain.jsとかexe.jsとかはダメだよ

ほかのソースをみているとindex.jsのほかにプロジェクト名.jscli.jsとしているのが多いようです。bin.jsexe.jscmd.jsも見かけました。ちなみに拡張子は必要なく、むしろ付けないものが多い気がしました

ここではcli.jsを採用することにします。なんでもいいけどね

任意のコマンドで実行

node path/to/jsはかっこわるいので、ふつうのCLIコマンドっぽくしてみます

シェバンを付けて、システム側からshellで実行できるようにしとく

#!/usr/bin/env node
console.log("Yo!");

package.jsonbinフィールドを追加。keyに登録したコマンドでvalueのファイルを実行するようにしたよ

{
  "name": "rannum",
  "version": "1.0.0",
  "description": "",
  "main": "cli.js",
  .
  .
  "bin": {
    "yo": "./bin/cli.js"
  },
  .
  .

この設定をシステムのnodeとひもつけて呼び出せるようにする。単にシンボリックリンクを貼っているだけです

npm link

すると、指定したコマンドで実行できるようになりました。よかったね

$ yo
Yo!

コード作成

ほいだら、そんな感じで各自好きに作りましょう。いまつくった./bin/cli.jsがコードポイントになるので、複数ファイルに分ける場合は./lib以下に作ってexportして、cli.jsからrequireするのが(TypeScriptを./srcに作るのと同じ程度に)一般的な気がするけどこれもなんかわりと自由っぽい感じなのでもうどうでもいいよ

#!/usr/bin/env node

const commander = require('commander');
const _ = require("lodash");

commander
  .option('-l, --lower <number>', 'lower num.', parseInt)
  .option('-u, --upper <number>', 'upper num.', parseInt)
  .option('-a, --amount <number>', 'size of choice.', parseInt)
  .option('--shuffle', 'shuffle result.')
  .option('--separator <string>', 'separator charctor for result.')
  .option('--exclude <string>', 'exclude num (should be put quote around).', (x) => {
    if (x !== undefined) {
      x = x.trim().split(" ");
      if (x.every((xi) => {return !isNaN(xi) && typeof xi !== "boolean" && xi !== undefined && xi%1 === 0})) {
        return x.map(xi => parseInt(xi));
      } else {
        // ダメなやつ含んでてもエラーは出さない
        return [];
      }
    } else {
      return [];
    }
  })
  .parse(process.argv);


// defaults
if (commander.lower === undefined) commander.lower = 1;
if (commander.upper === undefined) commander.upper = 6;
if (commander.separator === undefined) commander.separator = " ";
if (commander.lower > commander.upper) {
  console.error(`>>>>>>>>>> lower number must be lower than upper, and vice versa. <<<<<<<<<<`);
  process.exit(1);
}
if (commander.amount === undefined) {
  commander.amount = commander.upper - commander.lower + 1;
} else {
  commander.amount = Math.min(commander.upper - commander.lower + 1, commander.amount);
}

// calc 
const dif = commander.upper - commander.lower + 1;
let range = Array.from(Array(dif), (v, k) => k + commander.lower);
if (commander.exclude !== undefined) range = _.without(range, ...commander.exclude);
// ほんとは先にピックしたほうがシャッフルが軽くなりそうなんだけどなー
if (range.length > 0 && commander.shuffle !== undefined) range = range.map((a) => [Math.random(), a]).sort((a, b) => a[0] - b[0]).map((a) => a[1]);
range = _.take(range, commander.amount);
console.log(range.map((a) => a.toString()).join(commander.separator));

作りました

2. Publish

npmにアップロードするにはまずnpmに登録する必要があります。逆にべつにそんなもんはしねぇぜ!って人は本稿はもう役に立つことはないのでお疲れ様でした

npmアカウント作成

これもコマンドで済ませられます。対話的に設定すオプションはadduserです

$ npm adduser

設定した情報はuser/.npmrcに入ってます。以下のコマンドで参照できるよ

$ npm config ls

package.jsonの編集

アップロードするとname, versin, descriptin, keywords, author, license, repositoryなどがいい感じに公開されるので、世に出しても恥ずかしくないように編集しときましょう。githubにも公開するのを前提しにているのでそのへんのページのURLとか間違ってないか指差し声出し確認です

README.md作成

これはアップロードするまえに作っとくことをおすすめします。npmjsで各パッケージのページはREADMEに書かれた内容が自動的にレンダリングされます。publishはすぐに反映されるので、READMEを作成せずにアップロードし、つくったnpmを見に行くと寂しいし怪しいです。githubにも同じのを流用するので、そんな気持ちで適当に作っときます。Usageは歳の離れた妹に分かるように教えてあげるように書くと世界がよりよくなります

publish

軽率にアップロードしていきましょう

npm publish

うまくアップロードできたか・インストールできるかを確かめておきます。実際にやってみようね。よくできました。

push

ついでにgithubにもpushしときます。よくできました

3. Conclusion

いかがでしたか?(いっぺん書いてみたかった)

nodeわけわからんしいっそJSもわけわからん、という時には適当なnpmパッケージをでっちあげて練習するといいかもしれません。わりとネタ的なペライチで終わるものもたくさんあるのでコードリーディングにもなると思います。夏休みが終わる頃には妹も立派なnodestですね