Nuxt.js でsanitize(サニタイズ)

Nuxt.jsでv-htmlを使用したら,xss 脆弱性を招くのでご遠慮くださいと怒られました。

 WARN  Compiled with 1 warnings                                                                                                                 
 WARN  ESLintError                                                                                                                                         
/Users/noreen/xxx/microcms-nuxt-jamstack-blog/pages/index.vue
  6:14  warning  'v-html' directive can lead to XSS attack  vue/no-v-html
  8:14  warning  'v-html' directive can lead to XSS attack  vue/no-v-html

というわけでサニタイズします。
apiも自社開発な時などにはそこまで気にしなくてもいいですし、今回はmicroCMSからの値が返ってきていることもわかっている(ユーザからの入力が含まれる可能性はない)のですが、ライブラリがあったので試しにそちらを使おうと思います。

v-html通すまでにxssを防ぐサニタイズ
やり方全部こちらに書いてあります。
v-sanitize

vue公式が出してるのはこちら(Nuxtでは使えないかも…?エラーが消えませんでした。)
## @braintree/sanitize-url

v-sanitizeを使用した手順

インストール

npm install --save v-sanitize

pluginsに追記

グローバルで使えるよう、プラグインに追記します。

plugins/v-sanitize.js

import Vue from 'vue'
import VSanitize from 'v-sanitize'

Vue.use(VSanitize)

nuxt.config.jsに追記

  // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
  plugins: [
    { src: '@/plugins/v-sanitize' },
  ],
  modules: [
    // https://go.nuxtjs.dev/axios
    '@nuxtjs/style-resources',
    [
      'v-sanitize/nuxt',
      {
        /* options */
      },
    ],
  ],
  sanitize: {
    /* options */
  },

コンポーネントで使用

<h2>サニタイズなし</h2>
<div v-html="test"></div>
<h2>サニタイズあり</h2>
<div v-html="$sanitize(test)"></div>
<script>
export default {
  data() {
    return {
      test: `
        <p class="script" style="color: red;"><a onclick=alert("aaa")>alert</a></p>
        <p><img src="http://placekitten.com/200/300"></p>
        <iframe width="350" height="200" src="https://www.youtube.com/embed/SB-qEYVdvXA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
      `,
    }
  },
}
</script>

余談

sanitize-htmlはエラーが消えず断念しました。
Nuxt.jsでsanitize-htmlを使ってみる

エラー
in ./node_modules/sanitize-html/node_modules/htmlparser2/lib/esm/index.js 59:9 Module parse failed: Unexpected token (59:9) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders | return getFeed(parseDOM(feed, options)); | } > export * as DomUtils from “domutils”; | // Old name for DomHandler | export { DomHandler as DefaultHandler };

vue3 componentの読み込み方

親要素からcomponent(コンポーネント)を読み込むときの手順を、復習。

まずは子componentを作成

コンポーネントはキャメルケース。
component/ChildComponent.vue

<template>
  <div class="child_component">
    <h2>This is child component</h2>
  </div>
</template>

読み込む親要素で<script>内にimportし、componentを記載する。

script内もキャメルケース。
views/Parent.vue

<script>
import ChildComponent from '@/components/ChildComponent.vue'

export default {
  name: 'parent',
  components: {
    ChildComponent,
  },
}
</script>

読み込む親要素で描画箇所を記述

描画箇所はスネークケース

<template>
  <div class="parent">
    <h1>Parent page</h1>
    <child-component></child-component>
  </div>
</template>

トラブルシューティング

親のパスは通っていますか?

router/index.jsに親を登録していないと、/parentのuriでページが見られません。

import { createRouter, createWebHistory } from 'vue-router'
import Parent from '../views/Parent.vue'


const routes = [
  {
    path: '/parent',
    name: 'parent',
    component: Parent,
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
})

export default router

viteを使っていますか?

Viteを使用し、@でパスの指定をしたい場合は、別途設定が必要です。
vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

[Vue.js]Viteを導入してみる

早い早いと話題のVite(ヴィート)。フランス語で「素早い」の意味らしいです。
Webpackではアプリケーション起動前に、アプリ全体を走査し依存を解決→バンドルしたソースを出力という流れだったのに対し、ViteはNative ESModulesのimportを利用し、ブラウザから直接モジュールを読み込むことで高速化を実現しています。

モジュールバンドラーRollup を使用してコードをバンドル…とあるので、ノーバンドルツールと言われつつもバンドルしない訳ではないようです。

この辺りの解説はANTEZさんの記事やっぱりwebpackがわからない(エピソード1)が超絶わかりやすかったです。

プロジェクト作成

前提

Viteを使うには、Node.jsが14.18+、16+でなければなりません。

% cd projects
% npm create vite@latest
Need to install the following packages:
  create-vite@3.2.0
Ok to proceed? (y) y
✔ Project name: … vite-app
✔ Select a framework: › Vue
✔ Select a variant: › JavaScript

Scaffolding project in /Users/meow/Documents/projects/vite-app...

プロジェクト名や使用するテンプレートはオプションでも指定可能。

# npm 6.x
npm create vite@latest my-vue-app --template vue

とりあえず起動

npm install
npm run dev

テーン!!

後からわかったのですが、`npm create vite’でプロジェクト作成した時はstoreやらrouteやらを自分で入れなければいけないみたいですね。というわけでこちらにまとめました。

Vite / vue.js / vue3

ほか参考

Vite

[Nuxt.js]フィルターを作成する

Nuxtのファイル構成を意識したNuxt.jsでのフィルターの作成方法です。Vueのやり方だと上手く行かなかったため、備忘録。

グローバルフィルター

pluginsディレクトリ内にjsフォルダを作成し、どのページでもフィルタが読み込めるようにする方法です。今回は、数字を3桁ごとにカンマで区切るフィルタと、英語の文字列を全て大文字にするフィルタを作成します。

詳しく

まずはpagesディレクトリ内にfilter.vueの雛形を作成します。

<template>
  <div>
    <Header />
    <v-container>
      <!-- local filter -->
      <h2>Mustash</h2>
      <p>{{ price | numberFormat }}</p>
      <p>{{ text | toUpperCase }}</p>
      <h2>b-vind</h2>
      <input type="text" :value="price | numberFormat" />
      <input type="text" :value="text | toUpperCase" />
    </v-container>
  </div>
</template>


<script>
export default {
    data() {
      return {
        price: 25400,
        text: 'Hello, Nuxt.js!',
    }
  },
}
</script>
グローバルフィルターはpluginsディレクトリ内にjsフォルダを作成し、その中にフィルターの機能を記述します。
import Vue from 'vue'

Vue.filter('toUpperCase', (value) => {
  return value.toUpperCase()
})

Vue.filter('numberFormat', (value) => {
  return value.toLocaleString()
}

最後に、nuxt.config.jsにグローバルフィルタの場所を追記します。

export default {
  plugins: [ { src: '~plugins/filter.js' } ],
}

ローカルフィルター

ローカルフィルターは、pagesディレクトリ内の各ページに定義するそのページ限定のフィルターです。せっかくなので、次は長い文字列を短く表現し、省略した以降の文字は「…」と表現させる「引数あり」のフィルターを作成します。

長文
これを

こう

詳しく

まずは雛形を作成します。

<template>
  <div>
    <v-container>
      <p>{{ longText }}</p>
    </v-container>
  <Footer />
  </div>
</template>

<script>
export default {
  data() {
    return {
      longText:
        'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
    }
  },
}
</script>

この時点ではこれです。

フィルターを加えます。

<script>
export default {
  filters: {
    readMore: (text, length, suffix) => {
      return text.substring(0, length) + suffix
    },
  }

  data() {
   // 省略
}
</script>

readMoreの第一引数が適用する文字列、lengthが長さ、suffixが接尾辞です。

文字列textに対し.substringメソッドで0番目の文字から第二引数で渡した文字数(length)の、文字列の部分集合を返し、最後に指定したsuffixを加える、という処理が書かれています。

上記フィルターをlongTextに適用すると、コード全文は下記のようになります。

<template>
  <div>
    <v-container>
      <p>{{ longText | readMore(20, '...') }}</p> // フィルタを適用
    </v-container>
  <Footer />
  </div>
</template>

<script>
export default {
  filters: {
    readMore: (text, length, suffix) => {
      return text.substring(0, length) + suffix
    },
  }

  data() {
    return {
      longText:
        'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
    }
  },
}
</script>

フィルターでは第三引数まで指定していたのに、textの引数は書かれていないことに注意してください。

 

フィルターはv-bind式でもできる

mustache構文を使わないでフィルターを適用させたい時もあるかと思います。2.1.0以降でサポート。

https://jp.vuejs.org/v2/guide/filters.html

<!-- mustaches -->
{{ message | capitalize }}

<!-- v-bind -->
<div v-bind:id="rawId | formatId"></div>

v-bind式の構文は、

<div v-bind:id=”フィルタに渡すデータ | フィルタ”></div>

です。

 

おしまい

[Nuxt.js]AccuWeatherのAPIを使ってお天気アプリを制作

Nuxt.jsでAPIを使う修行を自分に課していました。というわけで、今回はお天気アプリです。

トップページのカードに、このように表示させます。

取ってきている情報自体は、任意の場所のその日のお天気と気温(と日付)だけなのですが、なかなかどうして時間のかかったものです。さて、復習しますか。

雛形となるカードを用意

まずは雛形を用意。お馴染みVuetifyを使っています。

<template>
  <div>
    <v-card class="mx-auto" max-width="374">
      <v-img
        height="250"
        src="https://images.unsplash.com/photo-1558486012-817176f84c6d?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8"
        gradient="to top, rgba(255,255,255,.4), rgba(255,255,255,.9)"
      >
        <v-list-item two-line>
          <v-list-item-content>
            <v-list-item-title class="text-h5">
              CURRENT WEATHER
            </v-list-item-title>
            <v-list-item-subtitle>YYYY-MM-DD</v-list-item-subtitle> // 今日の日付を取ってくる
            <v-list-item-subtitle>TOKYO</v-list-item-subtitle>
          </v-list-item-content>
        </v-list-item>

        <v-card-text>
          <v-row align="center">
            <v-col text-h2 cols="12">
              <v-list-item-subtitle>お天気ステータス</v-list-item-subtitle>
            </v-col>
            <v-col cols="12">
              <v-card-subtitle class="text-h1 temp">温度
                <span class="celcius">&#8451;</span>
              </v-card-subtitle>
            </v-col>
          </v-row>
        </v-card-text>
      </v-img>
    </v-card>
  </div>
</template>

AccuWeatherから情報を取ってくる

さてこのAccuWeather、ちょっと苦戦しました。アクセス1日50回(だったかな?)なら無料のお天気APIです。

https://developer.accuweather.com/

必要なことは大体公式サイトに載っているのですが、ここからお天気の情報を取ってくるには何段階かステップが必要です。

  1. AccuWeatherに登録する
  2. AccuWeatherでAppを作る
  3. APIキーゲト
  4. ロケーションキーをゲト

AccuWeatherに登録する

まずは公式サイトでアカウントを作成します。

https://developer.accuweather.com/

右上のREGISTERからですね。

AccuWeatherでAppを作る

諸々入力し終え、アカウントが作成できたら、メニューバーのMY APPからAdd a new Appを選択します。Appの名前とかはどうでもいいです。今回は無料版を使うため、Productの箇所ではLimited Trialを選択しましょう。

 

APIキーゲト

上記の設定を終えたらCreate Appボタンを押します。

APIキーが取得できました。

ロケーションキーをゲト

APIキーをゲットしたら次はロケーションキーが必要ですね。

こちらにアクセスするとロケーションキーがわかります。

https://www.accuweather.com/

検索窓から取得したいロケーションを入力します。「東京」や「サンフランシスコ」などで選択肢が表示されます。

ページ遷移した先のURL最後の数字がロケーションキーです。

AccuWeatherのメニューバーAPI REFERENCE > Forecast APIを選択し、取りたいデータを選択します。今回はHour of Hourly Forecastsを選択しました。

https://developer.accuweather.com/accuweather-forecast-api/apis/get/forecasts/v1/hourly/1hour/%7BlocationKey%7D

一番上のResource URLの部分に、お天気情報を取得するためのURLが書かれていますね。

http://dataservice.accuweather.com/forecasts/v1/hourly/1hour/{LOCATIONKEY}

この{LOCATIONKEY}の部分を先ほど取得した数値に置き換え、APIキーをつけてアクセスすると格納されている情報が見られるようになります。

例:http://dataservice.accuweather.com/forecasts/v1/hourly/1hour/226396?apikey=XXX

XXXの箇所は、先ほど取得した自分のAPIに置き換えてください。

アクセス先

Chromeの拡張機能JSON Viewを使うと見やすいです。

こちらのURLをスクリプトのmounted部分で使います。

取得したお天気情報をaxiosでゲットし、変数に代入する

 

APIキーはconstantsディレクトリのdefine.jsに記載しました。またaxiosはダウンロードしている前提です。インストール方法はこちらから

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

export const API_KEY = 'xxx'
<script>
import axios from 'axios'
import { API_KEY } from '~/constants/define'


export default {
  data() {
    return {
      forecasts: [],
    }
  },
  mounted() {
    axios
      .get(
        'http://dataservice.accuweather.com/forecasts/v1/hourly/1hour/226396' +
        `?apikey=${API_KEY}`
      )
      .then((response) => (this.forecasts = response.data))
  },
}
</script>

これで、先ほどアクセスした http://dataservice.accuweather.com/forecasts/v1/hourly/1hour/226396?apikey=XXX の情報をforecasts[]に代入することができました。

画面に天気を表示させてみます。カードにv-forを追加しました。

    <v-card-text v-for="forecast in forecasts" :key="forecast.id">
      <v-row align="center">
        <v-col text-h2 cols="12">
          <v-list-item-subtitle>{{
            forecast.IconPhrase
          }}</v-list-item-subtitle>
        </v-col>
        <v-col cols="12">
          <v-card-subtitle class="text-h1 temp">
            {{ forecast.Temperature.Value | toCelsius | mathFloor }}
            <span class="celcius">℃</span>
          </v-card-subtitle>
        </v-col>
      </v-row>
    </v-card-text>

気温は華氏表記だったため、フィルターを作成し摂氏表記にします。

        <v-col cols="12">
          <v-card-subtitle class="text-h1 temp">
            {{ forecast.Temperature.Value | toCelsius | mathFloor }}
            <span class="celcius">℃</span>
          </v-card-subtitle>
        </v-col>

Scriptには下記を追記

<script>

// 省略
export default {
  filters: {
    toCelsius(value) {
    return ((value - 32) * 5) / 9 // fahrenheit to celsius
    },
    mathFloor(value) {
    return Math.floor(value)
    },
  },

 

 

<script>
import axios from 'axios'
import { API_KEY } from '~/constants/define'


export default {
  filters: {
    toCelsius(value) {
    return ((value - 32) * 5) / 9
    },
    mathFloor(value) {
    return Math.floor(value)
    },
  },
  data() {
    return {
      forecasts: [],
      hasError: false,
      loading: true,
      now: '',
    }
  },
  mounted() {
    this.now = this.$dayjs().format('YYYY-MM-DD')
    axios
      .get(
        'http://dataservice.accuweather.com/forecasts/v1/hourly/1hour/226396' +
        `?apikey=${API_KEY}`
      )
      .then((response) => (this.forecasts = response.data))
      .catch(function (error) {
        console.log(error)
        this.hasError = true
      })
      .finally(() => (this.loading = false))
  },
}
</script>

あとは、dayjsを使い今日の日付を表示、error時とloading時の表示を追加して完成です。

 

      <v-list-item-content>
        <v-list-item-title class="text-h5">
          CURRENT WEATHER
        </v-list-item-title>
        <v-list-item-subtitle>YYYY-MM-DD</v-list-item-subtitle> // 今日の日付を取ってくる
        <v-list-item-subtitle>TOKYO</v-list-item-subtitle>
      </v-list-item-content>

    <v-card-text v-for="forecast in forecasts" :key="forecast.id">
      <section v-if="hasError">Error.</section>. // エラーメッセージを表示
      <section v-else> // エラーじゃない時は正常に表示
        <div v-if="loading">Loading...</div> // ローディング時の表示を追加
          <v-row align="center">
          </v-row>
        </section>
      </v-card-text>
 // 省略

export default {
 // 省略
  data() {
    return {
      forecasts: [],
      hasError: false,
      loading: true,
      now: '',
    }
  },
  mounted() {
    this.now = this.$dayjs().format('YYYY-MM-DD')
    axios
      .get(
        'http://dataservice.accuweather.com/forecasts/v1/hourly/1hour/226396' +
        `?apikey=${API_KEY}`
      )
      .then((response) => (this.forecasts = response.data))
      .catch(function (error) {
        console.log(error)
        this.hasError = true
      })
      .finally(() => (this.loading = false))
  },
}
</script>

完成コード

すみません所々 インデントが合ってないかもしれないです

<template>
  <div>
    <v-card class="mx-auto" max-width="374">
      <v-img
        height="250"
        src="https://images.unsplash.com/photo-1558486012-817176f84c6d?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8"
        gradient="to top, rgba(255,255,255,.4), rgba(255,255,255,.9)"
      >
      <v-list-item two-line>
        <v-list-item-content>
          <v-list-item-title class="text-h5">
            CURRENT WEATHER
          </v-list-item-title>
         <v-list-item-subtitle>{{ now }}</v-list-item-subtitle> // 今日の日付を取ってくる
         <v-list-item-subtitle>TOKYO</v-list-item-subtitle>
       </v-list-item-content>
     </v-list-item>

      <v-card-text v-for="forecast in forecasts" :key="forecast.id">
        <section v-if="hasError">Error.</section>
        <section v-else>
          <div v-if="loading">Loading...</div>
          <v-row align="center">
            <v-col text-h2 cols="12">
              <v-list-item-subtitle>{{
                forecast.IconPhrase
              }}</v-list-item-subtitle>
            </v-col>
            <v-col cols="12">
              <v-card-subtitle class="text-h1 temp">
                {{ forecast.Temperature.Value | toCelsius | mathFloor }}
                <span class="celcius">℃</span>
              </v-card-subtitle>
            </v-col>
           </v-row>
         </section>
        </v-card-text>
      </v-img>
    </v-card>
  </div>
</template>


<script>
import axios from 'axios'
import { API_KEY } from '~/constants/define'


export default {
filters: {
toCelsius(value) {
return ((value - 32) * 5) / 9
},
mathFloor(value) {
return Math.floor(value)
},
},
  data() {
    return {
      forecasts: [],
      hasError: false,
      loading: true,
      now: '',
    }
  },
  mounted() {
    this.now = this.$dayjs().format('YYYY-MM-DD')
    axios
      .get(
        'http://dataservice.accuweather.com/forecasts/v1/hourly/1hour/226396' +
        `?apikey=${API_KEY}`
      )
      .then((response) => (this.forecasts = response.data))
      .catch(function (error) {
        console.log(error)
        this.hasError = true
      })
      .finally(() => (this.loading = false))
  },
}
</script>


<style scoped>
.temp {
  margin-top: -2rem;
}
.celcius {
  font-size: 1.7rem;
}
</style>

参考にしたのはこちらのサイト

https://www.npmjs.com/package/iobroker.accuweather

https://www.accuweather.com/

アクセスした際は日本語になっている可能性があるため注意。

axiosのmounted部分、なんでアロー関数じゃなきゃダメなんだろう?と思っていたら、こういうことでした。

なので、functionの外でページのインスタンスであるthisを違う変数に突っ込むと、アロー関数じゃない書き方でもthisが使えるようになるようです。

mounted() {
  this.now = this.$dayjs().format('YYYY-MM-DD')
  const self = this
  axios
    .get(
      'http://dataservice.accuweather.com/forecasts/v1/hourly/1hour/226396' +
      `?apikey=${API_KEY}`
    )
    .then(function (response) {
      console.log(response)
      self.forecasts = response.data
})
// 以下省略
あるいは宣言せずに.bind(this)する
.then(
  function (response) {
    console.log(response)
    this.forecasts = response.data
  }.bind(this)
)

 

【Nuxt.js】console.logでWarning :Unexpected console statementが出たときの対処法

いつもお世話になっております。今日もESLintに振り回されています。

検証のために入れたconsole.logにESLintがWarning出してきて、動くけどナンカキモチワルイ…という状態が続いておったのですね。

In JavaScript that is designed to be executed in the browser, it’s considered a best practice to avoid using methods on console. Such messages are considered to be for debugging purposes and therefore not suitable to ship to the client. In general, calls using console should be stripped before being pushed to production.

JavaScriptはブラウザで実行されるようデザインされているからconsoleで実行されるメソッドは避けるのがベストプラクティスだぜ!とESLintさんはおっしゃる訳です。そうは言ってもデバッグせにゃあ。

そんなわけで、プロジェクトディレクトリ直下の .eslintrc.js に下記を追加。

rules: {
'no-console': 'off', // console.log();OK
'no-unused-vars': 'off', // 使っていない変数あってもOK
},

しかしこちらを追記しても消えないWarning…なぜだ?

ESLint Warning

色々な記事を読み漁ってもルールを追記することしか書かれていません。同僚に相談したところ、「なんかESLint再起動?みたいなのした気がする」。とのこと。

yarn lintを実行。npm使ってる人はnpm run lintかな。

$ projectname % yarn lint
yarn run v1.22.10
$ yarn lint:js && yarn lint:style
$ eslint --ext ".js,.vue" --ignore-path .gitignore .
$ stylelint "**/*.{vue,css}" --ignore-path .gitignore
✨ Done in 3.24s.
$ projectname % yarn dev
yarn run v1.22.10

解決しました🥳🥳🥳

どうやらわざわざyarn devを実行しなくても、エディターとESLintをリンクさせる方法があるようです。

各種エディタでESLintのプラグインが提供されています。
これを使うことでいちいちコマンドを叩かずに済みます。
http://eslint.org/docs/user-guide/integrations#editors

Step by Stepで始めるESLint

[Mac] Nuxt.jsによるVue.js開発環境 その①Node.jsをインストール(anyenv + nodenv)

いつもお世話になっております。仕事でVue.jsを使うことになったのですが、当方、Vue.jsどころか Nuxt.jsもjavascript も全然使ったことのない、デザイナー上がりのディレクターです。こりゃやばいぞ…!ということで、備忘録も兼ねてしたためておくことにしました。結構焦ってます。

当方の環境は下記の通り。

MacOS Big Sur
バージョン11.2.3(20D91)
チップ Apple M1
メモリ16GB

早速、環境を構築していきます。

Node.jsはVue.jsの稼働に必要なJavaScript実行環境、

Vue.jsはJavaScriptフレームワーク、

Nuxt.jsはVue.jsでの開発に必要な機能を盛り込んだフレームワーク

という認識です。

いずれにせよNode.jsがないと始まらないっぽいのでNode.jsの環境を整えていくことにしました。

ここのサイトを参照→MacにNode.jsをインストール

https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09

順番としては

  1. Homebrewのインストール
  2. nodebrewのインストール
  3. Node.jsのインストール

としていけばいいのね…?と思ったら、anyenvとnodenvを使ってプロジェクト毎(フォルダ毎)にNodeのバージョンを指定して実行する方法もあるとのこと。

  1. Homebrewのインストール
  2. anyenvのインストール
  3. nodenvのインストール
  4. Node.jsのインストール

https://qiita.com/kyosuke5_20/items/eece817eb283fc9d214f

そんなん便利な方を使った方がええですやん、ということで今回は下の記事を参照して進めました。nodistを使うという手もあるようなのですが、この辺は宗教みたいなもんです、と弊社エンジニアが申しておりましたので、ひとまずは目を引かれたanyenvとnodenvを採用し、今後色々試してみたいと思います。

Homebrewのインストール

Homebrewとは

Homebrewは、Mac用のパッケージマネジャです。ツールのインストールなどをするのに便利なものなのですが、今回はanyenvをインストールするために(nodenvも?)このHomebrewをインストールします。まどろっこしいな!と思いつつも、これがなかったらもっと大変なんだろうなと想像し拝みながらインストールします。開発者の方々に足を向けて寝れませんね!まあ地球は丸いからどこ向いても足向けて寝ることにはなるのかな。

Homebrewのインストール手順

https://brew.sh/index_ja.html

サイトに貼ってあるスクリプトをMacのターミナル上で実行します。コピペしてEnterです。

すると何やらカリカリ始まります。(初心者丸出しの語彙力)

Next steps:
- Add Homebrew to your PATH in /Users/n.okawa/.zprofile:
&nbsp; &nbsp; echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/n.okawa/.zprofile
&nbsp; &nbsp; eval "$(/opt/homebrew/bin/brew shellenv)"
- Run `brew help` to get started
- Further documentation:&nbsp;
&nbsp; &nbsp; https://docs.brew.sh

とあります。

ほうほうNext stepsがあるのねと思いつつもこちとら初心者。具体的に何を打ち込めば良いのかまるでわかりません。

よく見るとこの記載の前に、

Warning: /opt/homebrew/bin is not in your PATH.

とあります。

PATHを追加しなきゃならんわけですね。PATHの通し方についてはこちらを参考にしました。

https://qiita.com/HORIZONium/items/b608f46c900ce527dbc9

そもそもパスを通すって何よ?という方はこちらをご覧ください。個人的にめっちゃ分かりやすくて感動しました。

https://qiita.com/Naggi-Goishi/items/2c49ea50602ea80bf015

さて、パスを通す上で場所が本当にWarningで指し示された場所で良いのか、試しに以下を実行します。

MacBook-Air:bin username$ /opt/homebrew/bin/brew -v

すると…おお!どうやらこの場所にHomebrewがあることは間違いなさそうです。

MacBook-Air: /opt/homebrew/bin/brew -v
Homebrew 3.1.8
Homebrew/homebrew-core (git revision af940f86e4; last commit 2021-05-21)

ではいよいよパスを通します。

MacBook-Air: export PATH="$PATH:/opt/homebrew/bin/"

通ったか確認

MacBook-Air: printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin/

パスが加わってますね!ではbrewコマンドがHOMEから実行できるか、もういっちょ試してみましょう。

brew -v

というのはHomebrewのバージョン確認です。

brew -v
Homebrew 3.1.8
Homebrew/homebrew-core (git revision af940f86e4; last commit 2021-05-21)

やったあやったあ。

anyenvをインストール

では今しがたインストールしたHomebrewを使って、anyenvをインストールしましょう。

$ brew install anyenv

anyenvのインストールが完了しました。

スクショ brew install anyenv

こちらを初期化します。すると…

$ anyenv init
# Load anyenv automatically by adding
# the following to ~/.zshrc:

eval "$(anyenv init -)"

訳:

anyenvを自動でロードするには
以下の記述を ~/.zshrc: に記載してね

eval “$(anyenv init -)”

と書いてありますね。

というわけで下記を実行。

echo 'eval "$(anyenv init -)"' >> ~/.zshrc

ターミナルでファイルに追記する際の記述はこのようになっています。

$ echo “追記する文字列” >> ファイル名

参照:echoコマンドの詳細まとめました【Linuxコマンド集】

今回は記述に””(ダブルクオーテーション)があったので、全体を”(シングルクオーテーション)で囲いました。

 

ターミナルを再起動します。すると…

/Users/xxx/.zshrc:1: command not found: anyenv
$

ぴえん🥺

困ったら公式だ!ということで公式をチェケラ

https://github.com/anyenv/anyenv

You’ll see a warning if you don’t have manifest directory.

echoコマンドの詳細まとめました【Linuxコマンド集】

echoコマンドの詳細まとめました【Linuxコマンド集】

echoコマンドの詳細まとめました【Linuxコマンド集】

$ brew -v
zsh: command not found: brew

echoコマンドの詳細まとめました【Linuxコマンド集】

$ printenv PATH
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

ぴえん🥺再起動したらさっき通したはずのパスが消えている…!

 

echoコマンドの詳細まとめました【Linuxコマンド集】

https://teratail.com/questions/159895

「PATHを通した後にターミナルを再起動すると、PATHが通っていない」…私じゃないですか。

新しいMacを使っている人やアプデをした人はbashでなくzsh が採用されたために、使い方を気をつけなければならんっちゅーわけですね。

というわけでエラー文言を元にググって解決法を発見。

【zsh, bash】macでPATHを通す方法 – not found と出てしまったら

https://qiita.com/k3ntar0/items/eb8cdbd8eba9da388def

 

ターミナルを開いた際、メニューバーにzshの記載があるため、私のデフォルトシェルはzshであることがわかりました。

ターミナルの設定ファイルが存在しない場合はシェルに応じた設定ファイルを作成してください。
– bashの場合 .bash_profile
– zshの場合 .zshrc

ひとまずホームディレクトリにて下記を実行し、.zshrcがあるかを確認します。

n.okawa@makkunoMacBook-Pro ~ % ls -a
.			.zsh_history		Downloads
..			.zsh_sessions		Library
.CFUserTextEncoding	.zshrc			Movies
.DS_Store		Applications		Music
.Trash			Creative Cloud Files	Pictures
.cups			Desktop			Public
.vscode			Documents
n.okawa@makkunoMacBook-Pro ~ % open ~/.zshrc

あった!

次に下記を実行。

$ touch .zshrc

.zshrcがテキストエディタで開くので、追記します。

私の場合は、先ほどanyenvのインストールを試みた際に入力した下記が記載されていました。

eval "$(anyenv init -)"

この下に、追加したかったパスの命令を書きます。

eval "$(anyenv init -)"
export PATH="$PATH:/opt/homebrew/bin/"

⌘+Sで保存し、再びターミナルへ戻り、追記した内容を下記で反映させます。

$ source ~/.zshrc

するとどうでしょう。待ち焦がれていた(?)Warningが…!Finally!!

Warning

ここでやっと、途中になっていたこちらの記事の「マニフェストディレクトリを作る」に戻れます。

https://qiita.com/kyosuke5_20/items/eece817eb283fc9d214f

途中check outするかってきたので「y(もろちん ちん!)」で承諾してます。

何やねんもろちんちんて。

 

$ source ~/.zshrc       
ANYENV_DEFINITION_ROOT(/Users/xxx/.config/anyenv/anyenv-install) doesn't exist. You can initialize it by:
> anyenv install --init
$ anyenv install --init
Manifest directory doesn't exist: /Users/xxx/.config/anyenv/anyenv-install
Do you want to checkout ? [y/N]: y
Cloning https://github.com/anyenv/anyenv-install.git master to /Users/xxx/.config/anyenv/anyenv-install...
Cloning into '/Users/xxx/.config/anyenv/anyenv-install'...
remote: Enumerating objects: 62, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 62 (delta 1), reused 1 (delta 0), pack-reused 57
Receiving objects: 100% (62/62), 10.52 KiB | 2.63 MiB/s, done.
Resolving deltas: 100% (8/8), done.

Completed!

Completed!!の記載を信じてドキドキしながらターミナルを再起動…

Last login: Fri May 21 15:16:17 on ttys000
$ .zshrc:1: command not found: anyenv

Ouch!! あ、でもbrewコマンドは使えるようになってる…

色々調べてみると、同じ症状で「zsh: command not found」表示が出る人々は多い模様

HOMEでls -aを実行してみると、ファイル群の中には.anyenvが。

ターミナル

http://blog.10rane.com/2014/08/19/install-anyenv/

上の記事を参考にしていると、anyenvのパスも通さなきゃダメっぽい?ということに気づく。そんな訳でファイル.zshrcを開き

$ open .zshrc

下記を追加。

export PATH="$HOME/.anyenv/bin:$PATH"

全部でこの3行が記載されたファイルとなりました。

export PATH="$PATH:/opt/homebrew/bin/"
export PATH="$HOME/.anyenv/bin:$PATH"
eval "$(anyenv init -)"

保存後はお決まり、エディタを閉じてターミナルにて下記を実行し、変更を適用します。

source ~/.zshrc

再びターミナルを終了して開くと…やった!エラー消えた!

.zshrcに記載するパスの書き方がいまいちわかっていなかったのですが、再び下記を実行してPATH一覧をみてみると、homebrewのパスは最後に、anyenvのパスは最初に追加されていますね。

$ printenv PATH     
/Users/xxx/.anyenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin/

つまり

export PATH="$PATH:/xxx/yyy/zzz/"

の形では最後に、

export PATH="$HOME/.xxx/yyy:$PATH"

の形では最初にパスが加えられます。

 

さてさて、やっと第三段階に入れます。

nodenvのインストール

さあ、入れたてホヤホヤのanyenvを使う時がやっとやって参りましたYO!

$ anyenv install nodenv

$ anyenv install nodenv
/var/folders/4f/2th8vsqs47d8s72sn6ffv14m0000gp/T/nodenv.20210521213024.25472 ~
Cloning https://github.com/nodenv/nodenv.git master to nodenv...
Cloning into 'nodenv'...
remote: Enumerating objects: 4021, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 4021 (delta 5), reused 4 (delta 1), pack-reused 4003
Receiving objects: 100% (4021/4021), 732.74 KiB | 6.72 MiB/s, done.
Resolving deltas: 100% (2636/2636), done.
~
~/.anyenv/envs/nodenv/plugins ~
Cloning https://github.com/nodenv/node-build.git master to node-build...
Cloning into 'node-build'...
remote: Enumerating objects: 20477, done.
remote: Counting objects: 100% (253/253), done.
remote: Compressing objects: 100% (154/154), done.
remote: Total 20477 (delta 92), reused 172 (delta 81), pack-reused 20224
Receiving objects: 100% (20477/20477), 3.65 MiB | 3.79 MiB/s, done.
Resolving deltas: 100% (12994/12994), done.
~
~/.anyenv/envs/nodenv/plugins ~
Cloning https://github.com/nodenv/nodenv-vars.git master to nodenv-vars...
Cloning into 'nodenv-vars'...
remote: Enumerating objects: 211, done.
remote: Total 211 (delta 0), reused 0 (delta 0), pack-reused 211
Receiving objects: 100% (211/211), 31.82 KiB | 5.30 MiB/s, done.
Resolving deltas: 100% (76/76), done.
~

Install nodenv succeeded!
Please reload your profile (exec $SHELL -l) or open a new session.

 

おっ今度はスムーズに行きました。

Please reload your profile (exec $SHELL -l) or open a new session.とありますね。

exec $SHELL -l

言われるがままexec $SHELL -lを実行してみました。I’m a nodenv’s bitch…

printenv PATHで確認。

PATH=/Users/xxx/.anyenv/envs/nodenv/shims:/Users/xxx/.anyenv/envs/nodenv/bin:/Users/xxx/.anyenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/xxx/.anyenv/bin:/opt/homebrew/bin/:/opt/homebrew/bin/

あ、今度は最初にパスが通ってますね。

同じ轍は踏むまいともう一度ターミナルを再起動しパスを確認してみましたが、今回は手動でファイルに上書きしなくても大丈夫そうです。

node.jsのインストール

はあ。ここまで長かった。

やっとnode.jsのインストールに参ります。

$ nodenv install -l

とすればインストールできる最新のnode.jsが確認できるのですが(めっちゃある)、実は最新バージョンが表示されないこともあるそうです。

それを防ぐためには、anyenv-updateプラグインをインストールし、最新のNodeバージョンが取ってこれるよう準備しておく必要があります。下記を実行。

mkdir -p $(anyenv root)/plugins
git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update

こちら参考。

mkdir
ディレクトリを作成する
書式:mkdir [オプション] [ディレクトリのパス]

-p・・・途中のディレクトリを含めて作成する

gushernobindsme/これから学ぶmacOSターミナル.md

mkdir -p $(anyenv root)/plugins で.anyenv のルートにpluginsディレクトリを作成しました。

これで.anyenvに移動し、中のファイルを確認すると、

$ cd .anyenv
.anyenv $ ls -a
.	..	envs	plugins

. .. envs plugins計4つのディレクトリが確認できました。このpluginsディレクトリにプラグインをクローンしてきます。

.anyenv $ git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
Cloning into '/Users/xxx/.anyenv/plugins/anyenv-update'...
remote: Enumerating objects: 87, done.
remote: Total 87 (delta 0), reused 0 (delta 0), pack-reused 87
Receiving objects: 100% (87/87), 13.33 KiB | 3.33 MiB/s, done.
Resolving deltas: 100% (33/33), done.

ヨシャこれでプラグインのインストールは完了。最後にanyenv updateを実行すれば、最新のNodeバージョンを取ってこれるようになります。

$ anyenv update
Skipping 'anyenv'; not git repo
Updating 'anyenv/anyenv-update'...
Updating 'nodenv'...
Updating 'nodenv/node-build'...
Updating 'nodenv/nodenv-vars'...
Updating 'anyenv manifest directory'...

更新されました!

先ほども確認したNode.jsのバージョン一覧を見てみます。

$ nodenv install -l

欲しいバージョンをインストールするには下記のように書けば良いのですが、

nodenv install {バージョン番号}

私、何を思ったのか、何も考えず最後に表示された数字で実行しちゃったんですよね。

nodeバージョン一覧

BUILD FAILED
BUILD FAILEDしてやんの

敢えなく撃沈。

あまり古いバージョンだと、 It no longer receives bug fixes or security updatesというわけでサポート対象外、インストールさえできないようですね。

気を取り直してもう一度。先ほどのリストから見ると16.2.0が最新のようです。その下はよくわからない。

node list

念には念を、ということで、node.jsの公式サイトにて最新バージョンも確認してみました(目視w)

node newest list
node.js

これで安心してインストールできるってもんです(涙目)

~ % nodenv install 16.2.0            
Downloading node-v16.2.0-darwin-arm64.tar.gz...
-> https://nodejs.org/dist/v16.2.0/node-v16.2.0-darwin-arm64.tar.gz
Installing node-v16.2.0-darwin-arm64...
Installed node-v16.2.0-darwin-arm64 to /Users/xxx/.anyenv/envs/nodenv/versions/16.2.0

はいお疲れ様でした。

 

さらに念には念を入れて、今入っているnodeのバージョンを確認しておきましょう。

$ nodenv whence npm
16.2.0

イエス!!!

nodenv global -16.2.0でコンピューターのデフォルトを指定し、node -vとすれば…

$ nodenv global 
$ node -v
<strong>v16.2.0
</strong>

ほあああ!

アンインストールするには

今お使いのnodeバージョンが何かしらの都合が悪くなりアンインストールしたくなったときは、~/.anyenv/envs/nodenv/versions内にあるディレクトリを削除すれば良いとのこと。