Node.jsでアルファベット混じりの短めのユニークID生成

Node.jsでおなじみのuuidパッケージを使ってUUIDを生成していました。

https://www.npmjs.com/package/uuid

ただこれだと

23b7f61b-5d92-40bf-9c2d-1a6da91ac059

のように長くなってしまいます。URLに使うなら長さが短くアルファベットも交えたものにしたい。noteの記事のURLのように。 探していたらnanoidパッケージが良さそうなので使うことにしました。

https://github.com/ai/nanoid#custom-alphabet-or-size

 

いつものyarnでインストール。

$ yarn add nanoid

このようにするとa〜zの英小文字と数字で構成された10桁の文字列が得られます。

import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('1234567890abcdefghijklmnopqestuvwxyz', 10)
const id = await nanoid()

Nuxtでstore.commitを呼び出すラッパー関数を作る

NuxtのPageやComponentからStoreに値を更新する時、以下のようにstore.commitを呼びます。

this.$store.commit("analytics.incrementClickCount")

サーバーサイドでのみ実行されるasyncDataではContextを通じて呼びます。

// Nuxt公式のサンプルではcontextの部分がappになっていて混乱するのですが、
// 実体はContextです。
async asyncData(context) {
   context.store.commit("analytics.incrementClickCount")
}

store/analytics.jsは以下のように実装されているとします。

[store/analytics.js]
export const state = () => ({
   clickCount: 0
})
export const mutations = {
   incrementClickCount(state) {
       state.clickCount++
   }
}

このincrementClickCountを頻繁に呼ぶことがある場合、毎回、

this.$store.commit("analytics.incrementClickCount")

と書くのはめんどくさいと思うかもしれません。
これを、

this.$incrementClickCount()

のように呼べると楽ですよね。Pluginを作ってそれを実現してみます。

[plugins/analytics.js]
export default ({ context }, inject) => {
   inject("incrementClickCount", () => {
       context.store.commit("analytics.incrementClickCount")
   })
}

nuxt.config.jsでこのPluginを有効にします。 

[nuxt.config.js]
plugins: [
   { src: '~/plugins/analytics.js' }
   // serverのみならmodeをserverにする。clientのみならmodeをclientにする
   { src: "~/plugins/analytics.js", mode: "server" }
]

これで、PageやComponentでthis.$incrementClickCount()で呼び出せるようになります。

Pluginの実装をTypeScriptでしたい場合は以下のようにします。

[plugins/analytics.ts]
import { Plugin } from '@nuxt/types'
// PageやComponentでthis.$incrementClickCount()と書いた時、
// トランスパイルエラーにならないよう宣言
declare module 'vue/types/vue' {
   interface Vue {
       $incrementClickCount(): void
   }
}
// asyncData(context)などでcontext.$incrementClickCount()と書いた時、
// トランスパイルエラーにならないよう宣言
declare module '@nuxt/types' {
   interface Context {
       $incrementClickCount(): void
   }
}
const myPlugin: Plugin = (context, inject) => {
   inject("incrementClickCount", () => {
       context.store.commit("analytics/incrementClickCount")
   })
}
export default myPlugin

こちらが参考になります。

プラグイン Nuxt.js 向け TypeScript サポート

GCP FirestoreのEmulatorをKillしたい

以下のようにFirestoreのエミュレータを指定のポートで起動できます。

$ gcloud beta emulators firestore start --host-port=localhost:8081

起動した後、[firestore] Dev App Server is now running.と表示されます。そこでCtrl + Cを押すとターミナルが入力状態に戻るので、エミュレータは終了しているのかと思ったらバックグラウンドで動いたままになっていました。私の環境問題?なんにせよ、停止したい😫

そこでポートを指定して(ここでは8081)、プロセスをKillすることにしました。

$ kill -9 $(lsof -t -i:8081)

参考にした記事。ありがたや🙏

コマンド1発で特定のポートのプロセスをkillする - Qiita

1つのプロジェクト成り立たせるためにGCPのApp EngineとCloud Functionsで動作させています。App Engine、Cloud Functions両方共通のコードがあります。この共通コードをどこに置くかで小一時間悩みました。

結論から言うと共通コードはリポジトリを分けて、git submoduleで取り込むことにしたのですが、その経緯を書きます。

最初は以下のようにしようと思いました。
common
└ **/*.js
appEngine
├ package.json
├ node_modules/**
└ **/*.js
cloudFunctions
├ package.json
├ node_modules/**
└ **/*.js

そしてappEngine、cloudFunctions以下のjsファイルから相対パスでcommon以下のjsをimport。

[appEngine/index.js]
import "../common/hoge.js"

しかし、common以下にはnode_modulesがない。appEngineやcloudFunctions以下のnode_modulesにあるモジュールをcommon以下のjsでimportすると、そんなものはないと怒られる。

[common/hoge.js]
import {Request, Response} from "express" -> expressはないとエラーになる

common以下にpackage.jsonを置いてnpm install, yarn installでcommon/node_modulesを作ると動くけど、common、appEngine、cloudFunctionsそれぞれのnode_modules以下のモジュールのバージョンが食い違うと上手く動作しなくなることがある。

というわけで以下のようにcommonは別リポジトリにして、それぞれgit submoduleで取り込むのが良いと判断しました。
appEngine
├ package.json
├ node_modules/**
├ common/**/*.js <- commonはgit submodule
└ **/*.js
cloudFunctions
├ package.json
├ node_modules/**
├ common/**/*.js <- commonはgit submodule
└ **/*.js

appEngineとcloudFunctionsが同じリポジトリで、異なるディレクトリでcommonを別々にgit submodleで取り込むこともできます。が、結局、appEngine、cloudFunctionsもリポジトリ分けましたけどね・・・

TypeScript、Jest、Webpackでimportの際、aliasを使えるようにする

モジュールをimportする際にパスが長くなる場合、aliasを使うことがあります。Node.jsの開発でTypeScriptでソースコードを書いて、Jestでテストして、Webpackでバンドルしてデプロイしている人も多いと思います。aliasの設定は各々の設定ファイルでする必要があります。また書き方がそれぞれ違います。

仮にプロジェクトのルートディレクトリを<rootDir>とします。<rootDir>/dir1/dir2/dir3以下のhoge.tsをimportする時、以下のようにディレクトリを~/dir3で省略したいとします。

import {hoge} from "~/dir3/hoge"

TypeScriptのtsconfig.jsonの場合

pathsに書きます。これは直感的でわかりやすいです。

{
   "compilerOptions": {
     "paths": {
       "~/dir3/*": [
         "./dir1/dir2/dir3/*"
       ]  
     }

Jestのjest.config.jsの場合

こちらは正規表現になります。また<rootDir>を明示的に書きます。

module.exports = {
  moduleNameMapper: {
    '^~/dir3/(.*)$': '<rootDir>/dir1/dir2/dir3/$1'
  }

WebPackのwebpack.config.jsの場合

path.resolveを使います。

const path = require('path');

module.exports = {
   resolve: {
       alias: {
           "~/dir3": path.resolve(__dirname, "./dir1/dir2/dir3")
       }

ちなみに<rootDir>を~で指定できるようにしたいだけの場合は以下の通り。

const path = require('path');

module.exports = {
   resolve: {
       alias: {
           "~": __dirname
       }