yucatio@システムエンジニア

趣味で作ったものいろいろ

date-fnsの日本語ファイルから月名を取得する

date-fnsの日本語ファイル( date-fns/index.js at master · date-fns/date-fns · GitHub )に、['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']という配列が用意されていたので、自分のプログラムから使えないかと調べました。

調査結果

ja.localize.month(index, options)

で配列の要素にアクセスできますが、配列そのものはdate-fnsから提供されていません。この関数を使用して自前で配列を作成してみましょう。

配列の要素にアクセスする関数のindexには月を表す数字(0から11)を指定します。例えば、5月であれば4を指定します。(ややこしいですね)

optionsのプロパティーのキーにはwidthが指定できます。 widthにはnarrowabbreviatedwideが指定でき、narrowが一番短い短縮形で、abbreviatedがそれよりも長い省略形、wideは月の完全名(一番長い形)です。 widthを指定したときの英語と日本語の表記は以下のようになります。英語、日本語ともデフォルトはwideです。

アメリカ英語(en-US) 日本語(ja)
{width:'narrow'} ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'] ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
{width:'abbreviated'} ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
{width:'wide'} ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']

日本語だとabbreviatedwideは同じですね。今回はabbreviatedを指定します。

まず、動作を確認してみましょう。

import { ja } from 'date-fns/locale'

console.log(ja.localize.month(0, {width: 'abbreviated'}))
console.log(ja.localize.month(1, {width: 'abbreviated'}))
console.log(ja.localize.month(2, {width: 'abbreviated'}))
console.log(ja.localize.month(3, {width: 'abbreviated'}))
console.log(ja.localize.month(4, {width: 'abbreviated'}))
console.log(ja.localize.month(5, {width: 'abbreviated'}))
console.log(ja.localize.month(6, {width: 'abbreviated'}))
console.log(ja.localize.month(7, {width: 'abbreviated'}))
console.log(ja.localize.month(8, {width: 'abbreviated'}))
console.log(ja.localize.month(9, {width: 'abbreviated'}))
console.log(ja.localize.month(10, {width: 'abbreviated'}))
console.log(ja.localize.month(11, {width: 'abbreviated'}))

実行結果です。月名が取得できました。

f:id:yucatio:20191214145658p:plain

これらを配列にしてみましょう。 JavaScriptにはRubyPythonのRangeに相当するものがないので、以下のように、 12個の要素を持つ空配列を作成してそれらをmapすることで月名を取得します。

import { ja } from 'date-fns/locale'

const monthNames = new Array(12).fill().map((_, i) => ja.localize.month(i, {width: 'abbreviated'}))
console.log(monthNames)

fill()を使用する理由については以下の記事をご覧ください。

yucatio.hatenablog.com

実行結果です。月名の配列が取得できました。

f:id:yucatio:20191214150005p:plain

しかし、プログラムの可読性から、日本語のみを扱うプログラムでは月名をプログラムに直書きで良いと思います。

const monthNames = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']

i18nが必要なアプリでは、以下のようにして月名の一覧が取得できます。

# localeはユーザが指定したロケール。jaとかen-USとかesとか。
const monthNames = new Array(12).fill().map((_, i) => locale.localize.month(i, {width: 'abbreviated'}))

あとがき

カレンダーで月を選択してジャンプするときに月名の一覧が必要でどこかで定義されている値を使おうかと思いましたが、 12要素しかなく変更もないのでプログラム直書きでもよいという結論に(自分の中では)なりました。

しかし日本語の月名を取得する関数の情報は有益だと思いブログ記事にしました。

環境

date-fns: 2.8.1

関連記事

曜日バージョン

yucatio.hatenablog.com

date-fnsの日本語ファイルから曜日を取得する

date-fnsの日本語ファイル( date-fns/index.js at master · date-fns/date-fns · GitHub )に、['日', '月', '火', '水', '木', '金', '土']という配列が用意されていたので、自分のプログラムから使えないかと調べました。

調査結果

ja.localize.day(index, options)

で配列の要素にアクセスできますが、配列そのものはdate-fnsから提供されていません。この関数を使用して自前で配列を作成してみましょう。

配列の要素にアクセスする関数のindexには曜日を表す数字(0から6)を指定します。indexの数字と曜日の関係です。

0 1 2 3 4 5 6

optionsのプロパティーのキーにはwidthが指定できます。 widthにはnarrowshortabbreviatedwideが指定でき、narrowが一番短い短縮形で、shortabbreviatedとなるにつれてそれよりも長い省略形になり、wideは曜日の完全名(一番長い形)です。 widthを指定したときの英語と日本語の表記は以下のようになります。英語、日本語ともデフォルトはwideです。

アメリカ英語(en-US) 日本語(ja)
{width:'narrow'} ['S', 'M', 'T', 'W', 'T', 'F', 'S'] ['日', '月', '火', '水', '木', '金', '土']
{width:'short'} ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] ['日', '月', '火', '水', '木', '金', '土']
{width:'abbreviated'} ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] ['日', '月', '火', '水', '木', '金', '土']
{width:'wide'} ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日']

日本語だとnarrowshortabbreviatedは同じですね。今回はshortを指定します。

まず、動作を確認してみましょう。

import { ja } from 'date-fns/locale'

console.log(ja.localize.day(0, {width: 'short'}))
console.log(ja.localize.day(1, {width: 'short'}))
console.log(ja.localize.day(2, {width: 'short'}))
console.log(ja.localize.day(3, {width: 'short'}))
console.log(ja.localize.day(4, {width: 'short'}))
console.log(ja.localize.day(5, {width: 'short'}))
console.log(ja.localize.day(6, {width: 'short'}))

実行結果です。曜日の短い名前が取得できました。

f:id:yucatio:20191214135700p:plain

これらを配列にしてみましょう。 JavaScriptにはRubyPythonのRangeに相当するものがないので、以下のように、 7個の要素を持つ空配列を作成してそれらをmapすることで曜日を取得します。

import { ja } from 'date-fns/locale'

const weekdays = new Array(7).fill().map((_, i) => ja.localize.day(i, {width: 'short'}))
console.log(weekdays)

fill()を使用する理由については以下の記事をご覧ください。

yucatio.hatenablog.com

実行結果です。曜日の配列が取得できました。

f:id:yucatio:20191214140713p:plain

しかし、プログラムの可読性から、日本語のみを扱うプログラムでは曜日名をプログラムに直書きで良いと思います。

const weekdays = ['日', '月', '火', '水', '木', '金', '土']

i18nが必要なアプリでは、以下のようにして曜日の一覧が取得できます。

# localeはユーザが指定したロケール。jaとかen-USとかesとか。
const weekdays = new Array(7).fill().map((_, i) => locale.localize.day(i, {width: 'short'}))

あとがき

カレンダーのヘッダ用に曜日の配列が必要でしたが、7要素しかなく、変更もないのでプログラム直書きでもよいという結論に(自分の中では)なりました。

しかし日本語の曜日名を取得する関数の情報は有益だと思いブログ記事にしました。

環境

date-fns: 2.8.1

関連記事

月名バージョン

yucatio.hatenablog.com

JavaScriptでn個ずつ配列を分割する

JavaScriptで配列を指定された個数ずつに分割します。

例えば、

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

という配列を3個ずつ分割するのであれば、

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

という配列になります。

実装方針

配列から一部を通り出すのには、 Array.prototype.slice() - JavaScript | MDN という関数が使えます。 この関数は、開始のインデックスと終わりのインデックスを渡すと、開始のインデックスから終わりのインデックスの1つ前までの部分の配列を返します。 pythonmylist[begin:end]rubyarray[begin...end]のような動作をします。

例えば、

const array=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

であれば、

array.slice(3, 6),

は、3、4、5のインデックスを含むので

[4, 5, 6]

という配列が返されます。

また、

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

という配列をこのように3個ずつ分割するのを、

[[1,2,3], [4,5,6], [7,8,9], [10]]

slice()を使用して書くと、

const result = [
  array.slice(0, 3),
  array.slice(3, 6),
  array.slice(6, 9),
  array.slice(9, 10)
]

となります。slice()の2つめの引数に配列の数より大きな値が指定された場合は、array.lengthが指定されたのと同じになりますので、下記のように書き換えても同様の結果になります。最後の要素を、array.slice(9, 10)からarray.slice(9, 12)に変更しました。これで各sliceの引数が全て3の倍数となりました。

const result = [
  array.slice(0, 3),
  array.slice(3, 6),
  array.slice(6, 9),
  array.slice(9, 12)
]

ところで、最終的な配列のサイズは、 分割する対象の配列を3で割って、端数が出たら切り上げた数です。今回分割する対象の配列の長さは10ですので、10/3=3.333..これを切り上げて4が最終的に作成される配列の長さです。 JavaScriptで切り上げを行うには、 Math.ceil() - JavaScript | MDN を使用します。

インデックスを3ずつずらしながら、array.slice()を、作成する配列の長さぶん繰り返せば、配列を分割することができそうです。

コード

コードをステップバイステップで作成します。

はじめに関数を定義します。引数に分割対象の配列と、いくつずつに分割するか指定します。

const sliceByNumber = (array, number) => {
  console.log('array', array)
  console.log('number', number)
  return array
}

console.log(sliceByNumber([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3))

実行結果です。疎通確認が完了しました。

array [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
number 3
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

作成する配列のサイズを計算します。

const sliceByNumber = (array, number) => {
  const length = Math.ceil(array.length / number)
  console.log('length', length)
  return array
}

console.log(sliceByNumber([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3))

実行結果です。作成する配列の長さ"4"が取得できました。

length 4
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

次に、長さ4の配列を作成し、配列の各要素に対してmapを呼び出します。この時、配列のインデックスの数にnumberを掛けたものを表示します。

const sliceByNumber = (array, number) => {
  const length = Math.ceil(array.length / number)
  return new Array(length).fill().map((_, i) => {
    console.log(`i=${i}, i*number=${i*number}, (i+1)*number=${(i+1)*number}`)
    return i
  })
}

console.log(sliceByNumber([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3))

Array(n)map()の間にfill()を挟む理由については、こちらの記事を参照してください。

yucatio.hatenablog.com

実行結果です。Array.sliceに渡す数が取得できました。

i=0, i*number=0, (i+1)*number=3
i=1, i*number=3, (i+1)*number=6
i=2, i*number=6, (i+1)*number=9
i=3, i*number=9, (i+1)*number=12
[0, 1, 2, 3]

最後に、i*number(i+1)*numberArray.sliceに渡せば、欲しい配列が出来上がります。

const sliceByNumber = (array, number) => {
  const length = Math.ceil(array.length / number)
  return new Array(length).fill().map((_, i) =>
    array.slice(i * number, (i + 1) * number)
  )
}

console.log(sliceByNumber([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3))

実行結果です。配列が3個ずつに分割されています。最後は要素数が1になっています。

[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
  [10]
]

array.lengthnumberで割り切れる例です。長さ10の配列を5個ずつに分割しました。

[
  [1, 2, 3, 4, 5],
  [6, 7, 8, 9, 10]
]

以上でn個ずつ配列を分割することができました。

date-fnsで「○分前」「約○時間前」「○日前」など現在時刻からのざっくりした時間経過を表示する

やりたいこと

Twitterのように、投稿日付を表示したい。「○分前」「○時間前」「○日前」など、現在時刻からの経過時間をざっくり表示したい。

f:id:yucatio:20181019222047p:plain

date-fnsのformatDistanceToNowでできる

date-fnsのformatDistanceToNowを使えば、現在からのざっくりした経過時間を表示することができます。

date-fns - modern JavaScript date utility library

経過時間と表示される時間の対応は以下のようになります。

範囲 表示
30秒未満 1分未満
30秒以上 1 分30 秒未満 1分
1分30秒以上 44分30秒未満 [2..44]分
44分30秒以上 89分30秒未満 約1時間
89分30秒以上 23時間59分30秒未満 約[2..24]時間
23時間59分30秒以上 41時間59分30秒未満 1日
41時間59分30秒以上 29日23時間59分30秒未満 [2..30]日
29日23時間59分30秒以上 44日23時間59分30秒未満 約1か月
44日23時間59分30秒以上 59日23時間59分30秒未満 約2か月
59日23時間59分30秒以上 1年未満 [2..12]か月
1年以上 1年3か月未満 約1年
1年3か月以上 1年9か月未満 1年以上
1年9か月以上 2年未満 2年近く
N年以上 N年3か月未満 約N年
N年3か月以上 N年9か月未満 N年以上
N年9か月以上 N+1年未満 N+1年近く

プログラム例

実際に使用してみます。Reactを使用しています。

import React from 'react';
import formatISO9075 from 'date-fns/formatISO9075'
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
import { ja } from 'date-fns/locale'

function App() {
  const targetDate = new Date(2019, 11, 8, 10, 13)

  return (
    <div>
      <div>現在時刻: {formatISO9075(new Date())}</div>
      <div>対象時間: {formatISO9075(targetDate)}</div>
      <div>経過時間: {formatDistanceToNow(targetDate, {locale: ja})}</div>
    </div>
  );
}

export default App;

実行結果です。経過時間が表示されました。(実際にはスタイルを適用しています。)

f:id:yucatio:20191208230907p:plain

色々な経過時間での表示を確かめる

色々な経過時間で実際に表示時間を確かめてみました。

デフォルトの設定では、"○分"、"約○時間"のように現在からの差分がどのくらいか表示されます。今回は、optionにaddSuffix: trueを指定して、"○分前"、"約○時間後"と、"前"または"後"を後ろにつけるようにしました。

現在時刻が2019-12-08 22:23:56のとき

説明 日時 formatDistanceToNow(date, {addSuffix: true, locale: ja})
3秒前 2019-12-08 22:23:53 約1分前
40秒前 2019-12-08 22:23:16 1分前
1分20秒前 2019-12-08 22:22:36 1分前
4分50秒前 2019-12-08 22:19:06 5分前
17分25秒前 2019-12-08 22:06:31 17分前
47分4秒前 2019-12-08 21:36:52 約1時間前
1時間25分56秒前 2019-12-08 20:58:00 約1時間前
1時間37分12秒前 2019-12-08 20:46:44 約2時間前
10時間5分32秒前 2019-12-08 12:18:24 約10時間前
23時間50分15秒前 2019-12-07 22:33:41 約24時間前
1日5分52秒前 2019-12-07 22:18:04 1日前
1日17時間2分47秒前 2019-12-07 05:21:09 1日前
1日18時間11分30秒前 2019-12-07 04:12:26 2日前
3日2時間45分7秒前 2019-12-05 19:38:49 3日前
12日20時間36分3秒前 2019-11-26 01:47:53 13日前
29日10時間46分51秒前 2019-11-09 11:37:05 29日前
30日1時間29分1秒前 2019-11-08 20:54:55 約1か月前
50日6時間27分48秒前 2019-10-19 15:56:08 約2か月前
63日22時間1分55秒前 2019-10-06 00:22:01 2か月前
120日19時間4分6秒前 2019-08-10 03:19:50 4か月前
361日9時間22分29秒前 2018-12-12 13:01:27 12か月前
1年10日15時間32分15秒前 2018-11-28 06:51:41 約1年前
1年120日14時間44分20秒前 2018-08-10 07:39:36 1年以上前
1年300日12時間3分30秒前 2018-02-11 10:20:26 2年近く前
3年36日2時間22分45秒前 2016-11-02 20:01:11 約3年前
3年170日5時間38分33秒前 2016-06-21 16:45:23 3年以上前
3年290日2時間39分10秒前 2016-02-22 19:44:46 4年近く前
10年150日13時間4分2秒前 2009-07-11 09:19:54 10年以上前

30秒未満の場合、suffix("前"や"後")をつけないと、"1分未満"と表示されますが、suffixをつけると"約1分前"になります。"1分未満前"にならないところに細かい気遣いがあります。(ソースコードはこちら→ date-fns/index.js at master · date-fns/date-fns · GitHub )

ソースコード

上記の表を出力するコードです。Reactを使用しています。

# date-fnsのインストール
$ yarn add date-fns
import React from 'react';
import formatISO9075 from 'date-fns/formatISO9075'
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
import subSeconds from 'date-fns/subSeconds'
import subMinutes from 'date-fns/subMinutes'
import subHours from 'date-fns/subHours'
import subDays from 'date-fns/subDays'
import subYears from 'date-fns/subYears'
import { ja } from 'date-fns/locale'
import './App.css';

const subFromNow = ({years=0, days=0, hours=0, minutes=0, seconds=0}) => {
  let timeAgo = new Date()
  let description = "";

  if (years) {
    timeAgo = subYears(timeAgo, years)
    description += `${years}年`
  }

  if (days) {
    timeAgo = subDays(timeAgo, days)
    description += `${days}日`
  }

  if (hours) {
    timeAgo = subHours(timeAgo, hours)
    description += `${hours}時間`
  }

  if (minutes) {
    timeAgo = subMinutes(timeAgo, minutes)
    description += `${minutes}分`
  }

  if (seconds) {
    timeAgo = subSeconds(timeAgo, seconds)
    description += `${seconds}秒`
  }

  description += "前"

  return {description, timeAgo}
}

function App() {
  const timeAgoArr = []

  timeAgoArr.push(subFromNow({seconds:3}))
  timeAgoArr.push(subFromNow({seconds:40}))
  timeAgoArr.push(subFromNow({minutes:1, seconds:20}))
  timeAgoArr.push(subFromNow({minutes:4, seconds:50}))
  timeAgoArr.push(subFromNow({minutes:17, seconds:25}))
  timeAgoArr.push(subFromNow({minutes:47, seconds:4}))
  timeAgoArr.push(subFromNow({hours:1, minutes:25, seconds:56}))
  timeAgoArr.push(subFromNow({hours:1, minutes:37, seconds:12}))
  timeAgoArr.push(subFromNow({hours:10, minutes:5, seconds:32}))
  timeAgoArr.push(subFromNow({hours:23, minutes:50, seconds:15}))
  timeAgoArr.push(subFromNow({days:1, hours:0, minutes:5, seconds:52}))
  timeAgoArr.push(subFromNow({days:1, hours:17, minutes:2, seconds:47}))
  timeAgoArr.push(subFromNow({days:1, hours:18, minutes:11, seconds:30}))
  timeAgoArr.push(subFromNow({days:3, hours:2, minutes:45, seconds:7}))
  timeAgoArr.push(subFromNow({days:12, hours:20 , minutes:36, seconds:3}))
  timeAgoArr.push(subFromNow({days:29, hours:10 , minutes:46, seconds:51}))
  timeAgoArr.push(subFromNow({days:30, hours:1 , minutes:29, seconds:1}))
  timeAgoArr.push(subFromNow({days:50, hours:6 , minutes:27, seconds:48}))
  timeAgoArr.push(subFromNow({days:63, hours:22 , minutes:1, seconds:55}))
  timeAgoArr.push(subFromNow({days:120, hours:19 , minutes:4, seconds:6}))
  timeAgoArr.push(subFromNow({days:361, hours:9 , minutes:22, seconds:29}))
  timeAgoArr.push(subFromNow({years:1, days:10, hours:15, minutes:32, seconds:15}))
  timeAgoArr.push(subFromNow({years:1, days:120, hours:14, minutes:44, seconds:20}))
  timeAgoArr.push(subFromNow({years:1, days:300, hours:12, minutes:3, seconds:30}))
  timeAgoArr.push(subFromNow({years:3, days:36, hours:2, minutes:22, seconds:45}))
  timeAgoArr.push(subFromNow({years:3, days:170, hours:5, minutes:38, seconds:33}))
  timeAgoArr.push(subFromNow({years:3, days:290, hours:2, minutes:39, seconds:10}))
  timeAgoArr.push(subFromNow({years:10, days:150, hours:13, minutes:4, seconds:2}))

  return (
    <div className="fromNowTable">
      <div className="currentTime">現在時刻: {formatISO9075(new Date())}</div>
      <table>
        <thead>
          <tr>
            <th>説明</th><th>日時</th><th>formatDistanceToNow({'{'}addSuffix: true, locale: ja})</th>
          </tr>
        </thead>
        <tbody>
          {timeAgoArr.map(({description, timeAgo}, index) => (
            <tr key={index}>
              <td className="description">{description}</td>
              <td>{formatISO9075(timeAgo)}</td>
              <td>{formatDistanceToNow(timeAgo, {addSuffix: true, locale: ja})}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default App;

date-fnsの他の経過時間を表示する関数

date-fnsには他にも date-fns - modern JavaScript date utility library という関数が用意されています。 こちらもざっくりとした経過時間を表示しますが、 "約"や"以上"などを表示しないのですっきりするかもしれません。 引数は2つとる必要があり、毎回現在時刻を渡す必要があります。

環境

  • Mac : Sierra 10.12.6
  • React : 16.12.0
  • date-fns : 2.8.1

関連記事

moment.jsで同様のことを行なった記事です。dete-fnsのformatDistanceToNowとは少し表示が違います。↓

yucatio.hatenablog.com

React(JSX)で波括弧をエスケープしたい

ReactのJSX内で波括弧({})を通常の文字として出力しようとしたところ、エラーになってしまったので、回避方法を記載します。

うまくいかない例

function App() {
  return (
    <div>
      <div>JavaScriptではオブジェクトを、{key: value} の形式で作成できます。</div>
    </div>
  );
}

こちらを実行すると、

Parsing error: Unexpected token, expected "}"

のエラーが表示されます。

f:id:yucatio:20191208214113p:plain

エラーの原因

JSXでは{}の間は式として解釈されます。上の例でいうと、{key: value}の、key: valueの部分は式でなければいけないのですが、これは式ではないためエラーになっています。 また、今回は式として認識されたくないので、何かしらのエスケープ処理が必要になります。

解決方法

Escaping curly brackets · Issue #1545 · facebook/react · GitHub

こちらに回答されている通り、 最初の開き波括弧を{}の中に文字列表現として記載します。{'{'}のようになります。

この解決策を使って最初のコードを書き換えたのが以下です。

function App() {
  return (
    <div>
      <div>JavaScriptではオブジェクトを、{'{'}key: value} の形式で作成できます。</div>
    </div>
  );

実行結果です。波括弧が文字列として表示されました。

f:id:yucatio:20191208215259p:plain

環境

nコマンドを使用してnode.jsをバージョンアップする

nを使用してnode.jsをアップデートしたときの記録です。

こちらの記事を参考にしました。

parashuto.com

nのインストール

nとは、node.jsのバージョンを管理するツールです。

nをインストールします。バージョン6.1.3がインストールされました。

$ npm install -g n
/usr/local/bin/n -> /usr/local/lib/node_modules/n/bin/n
+ n@6.1.3
added 1 package from 4 contributors in 1.085s

   ╭───────────────────────────────────────────────────────────────╮
   │                                                               │
   │       New minor version of npm available! 6.2.06.5.0       │
   │   Changelog: https://github.com/npm/cli/releases/tag/v6.5.0   │
   │               Run npm install -g npm to update!               │
   │                                                               │
   ╰───────────────────────────────────────────────────────────────╯

アップデート前のnodeのバージョン

アプデート前のnodeとnpmのバージョンです。それぞれ-vオプションで確認できます。

$ node -v
v10.8.0
$ npm -v
6.2.0

nodeの安定板を入手

最新の安定板のバージョンを確認します。

$ n --stable
12.13.1

最新の安定板をインストールします。

$ n stable

  installing : node-v12.13.1
       mkdir : /usr/local/n/versions/node/12.13.1
mkdir: /usr/local/n/versions/node/12.13.1: Permission denied

  Error: sudo required (or change ownership, or define N_PREFIX)

パーミッションエラーになってしまいました。

Error: sudo required (or change ownership, or define N_PREFIX)

「エラー: sudoが必要。(または、パーミッションを変更するか、N_PREFIXを使用してください)」と書いてあります。

今回はsudoを使用します。

$ sudo n stable
Password:

  installing : node-v12.13.1
       mkdir : /usr/local/n/versions/node/12.13.1
       fetch : https://nodejs.org/dist/v12.13.1/node-v12.13.1-darwin-x64.tar.gz
   installed : v12.13.1 (with npm 6.12.1)

nodeのバージョンを確認します。

$ node -v
v12.13.1

最新の安定板にアップデートされました。 ちなみに最新版を使用したい場合は、"stable"を"latest"に変更します。

N_PREFIXを使用する場合について

Error: sudo required (or change ownership, or define N_PREFIX)

こちらのエラーをN_PREFIXを使用して対処する場合。

N_PREFIXはnodeモジュールをインストールする場所です。デフォルトでは/usr/localです。 インストールする場所を、例えば${HOME}/.nに変更する場合は、GitHub - tj/n: Node version managementに書いてあるとおり、.bash_profileなどの起動シェルに以下を記載します。

export N_PREFIX=$HOME/.n
export PATH=$N_PREFIX/bin:$PATH

npmのアップデート

あわせて、npmもアップデートします。

$ npm update -g npm
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules/https-proxy-agent
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules/make-fetch-happen
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules/pacote/node_modules/minipass
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules/pacote
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules/read-cmd-shim
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules/smart-buffer
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules/socks
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/npm/node_modules/pacote/node_modules
npm ERR! code EACCES
npm ERR! syscall access
npm ERR! path /usr/local/lib/node_modules/npm/node_modules/https-proxy-agent
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, access '/usr/local/lib/node_modules/npm/node_modules/https-proxy-agent'
npm ERR!  [Error: EACCES: permission denied, access '/usr/local/lib/node_modules/npm/node_modules/https-proxy-agent'] {
npm ERR!   stack: "Error: EACCES: permission denied, access '/usr/local/lib/node_modules/npm/node_modules/https-proxy-agent'",
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'access',
npm ERR!   path: '/usr/local/lib/node_modules/npm/node_modules/https-proxy-agent'
npm ERR! }
npm ERR! 
npm ERR! The operation was rejected by your operating system.
npm ERR! It is likely you do not have the permissions to access this file as the current user
npm ERR! 
npm ERR! If you believe this might be a permissions issue, please double-check the
npm ERR! permissions of the file and its containing directories, or try running
npm ERR! the command again as root/Administrator.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/yucatio/.npm/_logs/2019-12-04T00_47_45_795Z-debug.log

エラーになりました。こちらもsudoが必要です。

$ sudo npm update -g npm
/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.js
/usr/local/bin/npx -> /usr/local/lib/node_modules/npm/bin/npx-cli.js
+ npm@6.13.2
updated 8 packages in 6.705s

npmのバージョンを確認します。

$ npm -v
6.13.2

npmもアップデートが完了しました。

環境

create-react-app実行時に@typescript-eslint/eslint-plugin@2.3.2: The engine "node" is incompatible with this module. Expected version "^8.10.0 || ^10.13.0 || >=11.10.1".のエラー

エラー内容

create-react-appのバージョンを2.0.4から3.2.0へアップグレードしたところ、yarn create react-appの実行時にエラーが発生しました。

$ yarn create react-app time-in-words
yarn create v1.9.4
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[------------------------------------------------------------------------] 0/91(node:54333) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Installed "create-react-app@3.2.0" with binaries:
      - create-react-app
[#######################################################################] 92/92
Creating a new React app in /Users/yucatio/react/time-in-words.

Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts...

yarn add v1.9.4
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[----------------------------------------------------------------------] 0/1317(node:54337) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
error @typescript-eslint/eslint-plugin@2.3.2: The engine "node" is incompatible with this module. Expected version "^8.10.0 || ^10.13.0 || >=11.10.1".
error Found incompatible module
info Visit https://yarnpkg.com/en/docs/cli/add for documentation about this command.

Aborting installation.
  yarnpkg add --exact react react-dom react-scripts --cwd /Users/yucatio/react/time-in-words has failed.

Deleting generated file... package.json
Deleting generated file... yarn.lock
Deleting time-in-words/ from /Users/yucatio/react
Done.
error Command failed.
Exit code: 1
Command: /usr/local/bin/create-react-app
Arguments: time-in-words
Directory: /Users/yucatio/react
Output:

info Visit https://yarnpkg.com/en/docs/cli/create for documentation about this command.

原因

画面には色々書かれていますが、エラーの原因はこの部分のようです。

error @typescript-eslint/eslint-plugin@2.3.2: The engine "node" is incompatible with this module. Expected version "^8.10.0 || ^10.13.0 || >=11.10.1".
error Found incompatible module

こちらで回答されているとおり、nodeのバージョンが古いことが原因ということがわかりました。

node.js - @typescript-eslint/eslint-plugin@2.3.2: The engine "node" is incompatible with this module - Stack Overflow

このエラーが発生した時の関連するパッケージのバージョンです↓

$ yarn info create-react-app version
3.2.0
$ node -v
v10.8.0
$ npm -v
6.2.0

nodeのバージョンが"^8.10.0 || ^10.13.0 || >=11.10.1"でないことがわかります。

対策

nodeのバージョンをあげました。こちらの記事を参考にしました。

parashuto.com

エラーがでた場合はこちらの記事もご覧ください。

yucatio.hatenablog.com

執筆時(2019年12月)のnodeのstableバージョンは12.13.1でしたので、このバージョンにアップデートしました。npmのバージョンは6.13.2にアップデートしました。

その後、yarn create react-appコマンドを実行したところ、正常に実行できました。

環境