かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Node.js バージョン管理ツールを VOLTA にしてみた!

Node.js のバージョン管理ツールに asdf を使っていたのですが VOLTA が速くて良いよとオススメされたので乗り換えてみた

⚡ VOLTA の特徴 ⚡

  • Rust製で早い
  • プロジェクトディレクトリに入ると自動でバージョンが切り替わる (バージョンは package.json に記録される)
  • Windows およびすべての Unix シェルを含むクロスプラットフォームのサポート
  • yarn, npm などパッケージマネージャーのバージョンも固定できる
  • global インストールしたパッケージもインストールしたディレクトリが記憶され、自動でバージョンが切り替わる
  • インストールが簡単

cf. Introduction | Volta

asdf fnm VOLTA
言語 Shell Rust Rust
バージョン管理 Node.js 以外も可 Node.js のみ Node.js のみ
バージョン指定 .tool-versions, .node-version .node-version, .nvmrc package.json の独自フィールド

cf.

VOLTA のインストール

% curl https://get.volta.sh | bash
# シェルの再起動
% $SHELL -l
% volta -v
1.1.1

Mac では シェルを再起動するだけでパスなどの設定も不要だだった

node.js, npm は .volta ディレクトリにインストールされる

% which node
/Users/user_name/.volta/bin/node
% which npm
/Users/user_name/.volta/bin/npm

node のバージョンを追加

volta install node@version でバージョンを追加できる
node.js v18.16.0 を追加する場合は下記のようになる

% volta install node@18.16.0

バージョンを省いた場合は最新の LTS がインストールされる

yarn, pnpm のインストール
% volta install yarn
% volta install pnpm

Node バージョンの固定

volta pin node@{version}package.json にバージョンを固定するフィールドが追加される
volta をインストールしているとディレクトリに入るとバージョンが自動に切り替わる

% volta pin node@18.16
% volta pin yarn@4

package.json

"volta": {
  "node": "18.16.0",
  "yarn": "4.0.0-rc.42"
}

.node-version の作成

チームメンバーが必ずしも全員 VOLTA を使っているとは限らないので .node-version ファイルを作成しておくほうが安全

% node -v> .node-version

.node-version の書き方や有効なツールについては node-version-usage に詳しいリストがある

所感
  • asdf では node.js を扱う際に asdf node <command> とちょっと覚えにくかったのですが、VOLTA は node.js しか扱わない分コマンドがシンプルになって体験が良い
  • 一方バージョンの固定方法が package.json での独自フィールドなのでチーム全員が VOLTA を使っている場合は良いが、そうでない場合は .node-version を合わせてリポジトリ管理するのが良さそう

おわり ₍ ᐢ. ̫ .ᐢ ₎


[参考]

TypeScript jsonwebtoken で JWT を verify しようとしたら Right-hand side of 'instanceof' is not an object というエラーになった件

Next.js で jsonwebtoken を使って作成した JWT から中に含まれるデータを取り出そうとま verify しようとしたらエラーになってしまったので原因と解決策のメモ

環境

  • Next.js 13.2.1
  • React 18.2.0
  • TypeScript 4.9.5
  • jsonwebtoken 9.0.0

状況

// api
import { sign } from "jsonwebtoken";
export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>,
) {
  // 略
  const token = sign(payload, SECRET_KEY, {algorithm: 'HS256', expiresIn: '60'});
  return res.status(200).json({ token });  
}

フロントエンドで token の有効期限確認と token に含まれる値を取り出して使いたかった

import { verify } from "jsonwebtoken";
export const useAuth = () => {
  const token = getToken(token); // 保存場所から token を取得
  try {
    const decode = verify(token, SECRET_KEY, { algorithms: ["HS256"] });
    return { verify: true, data: decode };
  } catch (error) {
    // token が無効 / 有効期限切れ
    return { verify: false, data: null };
  }
};

JWT token の発行は問題がないが useAuth 内の verify(token, SECRET_KEY, { algorithms: ["HS256"] }) の部分で Right-hand side of 'instanceof' is not an object というエラーが発生しました

jsonwebtoken v9 からはブラウザで JWT を sign / verify するとエラーになる

jsonwebtoken is a Node.js module, its use in a browser was never intended. Versions before 9.0.0 could use webpack's polyfill of the Node.js built-in modules. This most likely a) relied on slow js cryptography that isn't maintained anymore and lacks feature parity with Node's crypto and b) increased your js bundle size considerably. FWIW webpack 5 also no longer defaults to using these questionable crypto polyfills anymore.

It is better to rely on JWT modules made to be consumed in browser environments. You can discover such modules on jwt.io under "JavaScript" libraries.
cf. Version 9.0.0 - `sign` function throws error on frontend (in browser) · Issue #863 · auth0/node-jsonwebtoken · GitHub

Update Node verson to V12.0+ works for me, hope it can help you

In addition to this, when using node with version 18, jsonwebtoken v.9 will not work anymore in browsers. At least not in Edge/Chrome
cf. Version 9.0.0 - `sign` function throws error on frontend (in browser) · Issue #863 · auth0/node-jsonwebtoken · GitHub

元々 Node module でありブラウザでの処理は推奨されてなかった。今までは polyfill が変換していたためにブラウザでも動作してしまっていたトノコト。
ブラウザで JWTトークンの sign / verify をしようとすると今回のエラー ( Right-hand side of 'instanceof' is not an object) が発生してしまうようです!

解決方法

  1. jsonwebtoken のバージョンをダウングレードする
  2. JWT をさわる処理は全てサーバーサイドで行うようにする

ダウングレードが楽ですが セキュリティの問題 があったり元々推奨されてない事だったので通信が増えてしまうのですが JWT token を Next.js の API に投げて結果を返すように変更しました

完全に理解した!!
おわり ₍ ᐢ. ̫ .ᐢ ₎


[参考]

React TypeScript: useRef の current に代入しようとしたら型エラーになった

React (TypeScript) で debounce な処理を作っていて timer を useRef で作った ref オブジェクトに格納しようとしたらエラーになった

環境

  • React 18.2.0
  • TypeScript 4.9.5

ref.current への代入で型エラー Cannot assign to 'current' because it is a read-only property.

import { useRef, useEffect } from 'react';

function MyComponent() {
  const timerRef = useRef<ReturnType<typeof setTimeout>>(null);
  // … 略
  useEffect(() => {
    timerRef.current = setTimeout(callback, timeout);
    // => Type Error
   return () => clearTimeout(timerRef.current);
  }, []);
}

timerRef.current = の代入の箇所で下記のような型エラーになった
Cannot assign to 'current' because it is a read-only property.ts(2540). (property) RefObject<NodeJS.Timeout>.current: any

null 型が含まれることを明記しせず null で初期化した ref オブジェクトは読み取り専用になる

In TypeScript, useRef returns a reference that is either read-only or mutable, depends on whether your type argument fully covers the initial value or not.
To access a DOM element: provide only the element type as argument, and use null as initial value. In this case, the returned reference will have a read-only .current that is managed by React.
cf. GitHub - typescript-cheatsheets/react: Cheatsheets for experienced React developers getting started with TypeScript

The issue is that when we pass null as an initial value to the useRef hook and don't include null in the type we passed to the hook's generic, we create an immutable ref object.
cf. (useRef) Cannot assign to 'current' because it is read-only property | bobbyhadz

解決方法: useRef の型に null を含める

ジェネリックでの型指定を null を含めた Union 型にすると ref.current に代入可能になる
上記の setTimeout のタイマーに利用したい例は下記のように変更すればOK

import { useRef, useEffect } from 'react';

function MyComponent() {
- const timerRef = useRef<ReturnType<typeof setTimeout>>(null);
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  // … 略
  useEffect(() => {
    timerRef.current = setTimeout(callback, timeout);
    // => OK
   return () => clearTimeout(timerRef.current);
  }, []);
}

Note: HTMLElement への ref

The same would be the case if your ref points to a DOM element.
You'd have to type the hook as const ref = useRef<HTMLElement | null>(null) if you need to change the value of the ref's current property.
Note that you wouldn't have to include null in the ref's type if you aren't directly assigning to its current property.

import { useRef } from 'react';

const App = () => {
  const ref = useRef<HTMLInputElement>(null);
  return <input ref={ref} type="text" defaultValue="" />;
};

The ref in the example is used to focus the input element.
There is no assignment to the ref's .current property, so it's not necessary to include null in its type.
cf. (useRef) Cannot assign to 'current' because it is read-only property | bobbyhadz

input タグなど ref= で ref オブジェクトを指定する使い方の場合 .current への代入ではないので型定義に null を含めなくても問題がないとのこと

.current に代入する ref オブジェクトは useRef<some type | null>(null) と型定義に null を含めて初期化する必要がある!
おわり


[参考]