20200126のNode.jsに関する記事は8件です。

削除したnode_modulesを再インストールする

削除したnode_modulesフォルダを再インストールしたいが、可能であるか?

結論
package.jsonにモジュールが記載されているので、再インストールは可能である。

入れたいプロジェクトフォルダ上でnpm installコマンドを打つことで、新しくnode_moduleフォルダを作成することができる。

*自分はPCを移行する際に、node_modulesフォルダだけ移行に時間がかかったので先に削除した。
新しいPC上で、再度インストールすることにした。時間の短縮です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パッケージ版nodeをmacからアンインストールする。

はじめに

nodebrewをインストールするつもりが、pkg版nodeをインストールしてしまったため、削除したくなりました。

解決法

以下のコマンドで、まずnodeの存在を確認。

terminal
User$ /usr/local/bin/node -v
v12.14.1

バージョンが表示されたので、やはりパッケージ版がインストールされてしまっていることがわかります。
以下のコマンドを続けて打ってください。

terminal
User$ sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}

これでもう一度、冒頭で確認したコマンドを打ってみます。

terminal
User$ /usr/local/bin/node -v
-bash: /usr/local/bin/node: No such file or directory

きちんとアンインストールできました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nodeをパッケージでインストールして、ほとんどのコマンドが "command not found"になったときの解決法

はじめに

新しいMacbook Proを買ったので、早速nodeの設定をしようと思いました。
そこでnodebrewを入れるつもりが、誤ってパッケージ版のnode.jsをインストールしてしまい、ターミナルからほとんどのコマンドが使えない事態になりました。
ex. rm, ls, which など
rmも使えないため、nodeもアンインストールできなくなる事態になりました。

原因

簡潔に書くと、nodeをインストールしたことにより、複数ファイルにパスが書かれてしまっているため、コマンドが使えなくなっていることがいえます。

nodeをパッケージ版でインストールした人ですとおそらく
/usr/local/bin/node -v
/usr/bin/vi ~/.bash_profile
などは通るのではないでしょうか。
試してみて、実行されるかどうか確認してみてください。もし実行されれば、筆者と同じ状況なので、以下の解決策が有効かもしれません。

解決策

~/.zshrcsource $HOME/.bashrcを追加してください。

よくわからないようであれば、以下コピペしてお使いください。
/usr/bin/vi ~/.zshrc //vimでファイルを弄る
source $HOME/.bashrc //追加

しかし

本来であればパスは一つに集約されるのが理想です。
筆者は上記の目的(nodeをアンインストール)を果たした後は、再度 ~/.zshrcの内容を削除しています。
あくまでも暫定的処置であることをご理解ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ajaxで取得したデータでrenderすると、Cannot read property xx of undefined エラーになる

問題点

例えば以下のように、バックグラウンドで取得したデータを表示させたいとします。

xxx.js
  constructor(props) {
    super(props);
    this.state = {
      result: {}
    }

    fetch("http://localhost/api/test")
      .then(response => response.json())
      .then(json => this.setState({ result: json }));
    // /api/testは、以下のようなレスポンスを返す
    //  { 
    //    test1 : "aaa",
    //    test2 : {
    //      test3 : "bbb"
    //    }
    //  }
  }

  render() {
    return (
      <div>
        <p>{this.state.result.test1}</p>        // "aaa"が表示される 
        <p>{this.state.result.test2.test3}</p>  // "bbb"が出てほしいがエラーになる
      </div>
    )
  }

このとき、this.state.result.test2.test3の部分で、以下のようなエラーが出てしまいます。
Cannot read property 'test3' of undefined
test3 の親であるthis.state.result.test2がundefinedですよ、というエラーです。

原因

恐らく、this.state.resultの中身が全部セットされる前にrenderメソッドが動いてしまっているためだと思います。

解決法

1.描画前にチェックを行う

undefinedチェックを入れてあげると、エラーなく動くようになります。

xxx.js
  render() {
    return (
      <div>
        <p>{this.state.result.test1}</p>        // "aaa"が表示される 
        <p>{this.state.result.test2 ? this.state.result.test2.test3 : ""}</p>  // "bbb"が表示される
      </div>
    )
  }

JSX内ではif文が使えないので、三項演算子を使っています。

2.あらかじめ空のプロパティをセットしておく

どのような要素が返ってくるかが最初からわかっているのであれば、以下のように予めセットしておくことでも対応できます。

xxx.js
  constructor(props) {
    super(props);
    this.state = {
      result: {      
        test2 : {}   // あらかじめ空をセットしておく
      }
    }

    fetch("http://localhost/api/test")
      .then(response => response.json())
      .then(json => this.setState({ result: json }));
  }

  render() {
    return (
      <div>
        <p>{this.state.result.test1}</p>        // "aaa"が表示される 
        <p>{this.state.result.test2.test3}</p>  // "bbb"が表示される
      </div>
    )
  }

最後に

もっといい方法がありそうな気もするので、何かわかったら追記します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コード内で環境名 (dev,stg,prod) を使用しないことのすすめ

サービスを運用する場合、開発、運用で複数の環境でサーバーを動かしている場合がよくあると思います。このとき、パラメータ設定の口が柔軟でないと設定値の変更がしにくかったり、サーバーのビルドを環境毎にしなければならなず(特に docker 使っている場合)、デプロイに時間がかかったりします。
そうならないために環境名 (dev,stg,prod) を使用して動作変更するのをやめることをおすすめします。記事内では以下のような環境があると想定し、Javascript (Node.js) でいくつかの例を示します。

  • 開発時ローカル PC
  • ステージングサーバー環境
  • 本番サーバー環境

変数を環境名に依存させるのをやめよう

サーバーでエラーが起きたときに管理者にメールを送るというケースで送り先を分ける場合で説明します。

悪い例(コード内で環境ごとのメールアドレスを決めている)

コード内で設定されているアドレスにしか送ることができません、送信するメールアドレスが変更になったときはコードの変更が必要。

// process.env.NODE_ENV に環境名が指定されている
function sendErrorMail() {
  let email = 'default@example.com';
  switch(process.env.NODE_ENV) {
  case 'development':
    email = 'dev@admin.com';
    break;
  case 'staging':
    email = 'stg@admin.com';
    break;
  case 'production':
    email = 'prod@admin.com';
    break;
  default:
    break;
  }
  // sendMail は email アドレスとメール本文を指定してメールを送る関数
  sendMail(email, 'Server Error Occurred');
} 

良い例(環境変数としてメールアドレス自体を指定している)

メールアドレスが変更になったときでも環境変数の指定を変更するだけで送信先が変更できる

// process.env.ADMIN_EMAIL_ON_ERROR にエラー時のメール送り先が指定されている
function sendErrorMail() {
  // sendMail は email アドレスとメール本文を指定してメールを送る関数
  sendMail(process.env.ADMIN_EMAIL_ON_ERROR, 'Server Error Occurred');
} 

動作を環境名に依存させるのをやめよう

次も似た例ですが、ログのレベル設定を例に説明します。 logWarn という警告ログを出力する関数があるとします。開発環境だったら debug ログを出す、というパターンはよくあると思いますが、一つの環境変数としてログレベルを設定すると柔軟です。

悪い例(コード内で環境名でレベルを決めている)

// process.env.NODE_ENV に環境名が指定されている
function logWarn(message) {
  if (process.env.NODE_ENV == 'development') {
    console.warn(message);
  }
}

良い例(ログレベルを環境変数として設定する)

const LogLevel = {
  DEBUG: 0,
  INFO: 1,
  WARN: 2,
  ERROR: 3,
  TRACE: 4,
};

// process.env.LOG_LEVEL にログレベルが設定されている
function logWarn(message) {
  if (process.env.LOG_LEVEL <= LogLevel.WARN) {
    console.warn(message);
  }
}

最後に

この話は主流にありつつある(すでに主流?) Docker でのサーバーデプロイの背景が大きいです。Docker image を build して デプロイしてという流れで運用している場合、ちょっとした動作の変更によって Docker image の build し直ししなければならないとか、環境毎に image を build し直さなければけ無いとなると、build 時間はわりとかかるケース(色々なケースがありますが、数分から10分は最低でもかかると思います)が多いので、build し直ししなくてもいいように(実行時に環境変数として設定すればいいだけにするために)設定類は個別の設定毎に外出ししていくと便利だと考えています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Boltを使ったSlack BotをTypescriptで開発する方法

はじめに

今までslack用のbotを開発するのにhubotやerrbotを利用してきたのですが、昨年slackがリリースしたBoltフレームワークのチュートリアルをいくつかやってみて簡単にbot実装ができそうだなと思ったので、それをTypeScriptで実装する場合の方法を紹介します。

Bolt開発時に参考にしたサイト

Boltを使ったslack botアプリの作成方法や実装サンプルなどは、下記を参考にさせて頂きました。

開発環境

主に下記の開発環境で実装及び動作確認をしました。

Glitchは、ウェブ上で動くIDEを持っているのですが、ローカルでVSCodeを使うことでコード補完やESLintなども使えるようになるのでローカル環境でVSCodeを利用して実装します。

プロジェクトの作成

Bolt入門ガイドに記載のある通り、下記を実行しプロジェクトを作成します。

$ mkdir first-bolt-app
$ cd first-bolt-app
$ npm init -y
$ npm install @slack/bolt

typescript実装のために必要なパッケージをインストールします。

$ npm install -D typescript @types/node

その他ローカルで開発する上で利用するパッケージをインストールします。

$ npm install dotenv # .envファイルを読み込ませる為に追加
$ npm install -D eslint eslint-config-prettier eslint-plugin-prettier prettier @types/eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser

各種設定ファイルの作成

slackアプリのbot tokenとsigning secretを記載した.envファイルを作成します。

.env
SLACK_SIGNING_SECRET=<your-signing-secret>
SLACK_BOT_TOKEN=xoxb-<your-bot-token>

Typescriptプロジェクトをコンパイルするのに必要なコンパイラのオプションを指定するtsconfig.jsonファイルを作成します。

tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "target": "es6",
        "noImplicitAny": true,
        "moduleResolution": "node",
        "sourceMap": true,
        "outDir": "dist",
        "baseUrl": ".",
        "paths": {
            "*": [
                "node_modules/*",
                "src/types/*"
            ]
        }
    },
    "include": [
        "src/**/*"
    ]
}

ESLintの設定ファイル.eslintrc.eslintignoreを作成します。

.eslintrc
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint"
  ],
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "rules": {
    "semi": [
      "error",
      "always"
    ],
    "quotes": [
      "error",
      "double"
    ],
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": 1,
    "@typescript-eslint/no-inferrable-types": [
      "warn",
      {
        "ignoreParameters": true
      }
    ],
    "@typescript-eslint/no-unused-vars": "warn"
  }
}
.eslintignore
# /node_modules/* in the project root is ignored by default
# build artefacts
dist/*
coverage/*
# data definition files
**/*.d.ts
# 3rd party libs
/src/public/
# custom definition files
/src/types/

tscコンパイルやbot実行の為に、package.jsonを下記の通りに変更します。

package.json
{
〜省略〜
  "scripts": {
    "start": "npm run build && npm run serve",
    "build": "npx tsc",
    "serve": "node --require dotenv/config dist/app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
〜省略〜
}

コード作成と実行

ソースコードはsrc/以下に作成します。
下記はBolt入門ガイドの「メッセージのリスニングと応答」にあるコードをTypescriptコードにしたものです。
ファイル拡張子が.jsから.tsに変わったのとrequireをimport文に変えただけですね(笑)
もう少し複雑なサンプルコードを持ってくるとTypescriptの型エラーが発生し、適宜修正が必要になってきます。

src/app.ts
import { App } from "@slack/bolt";

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

// Listens to incoming messages that contain "hello"
app.message("hello", ({ message, say }) => {
  // say() sends a message to the channel where the event was triggered
  say(`Hey there <@${message.user}>!`);
});

(async () => {
  // Start your app
  await app.start(process.env.PORT || 3000);

  console.log("⚡️ Bolt app is running!");
})();

Typescriptのコードをコンパイルします。

$ npm run build

> first-bolt-app@1.0.0 build /Users/xxxx/first-bolt-app
> npx tsc

Javascriptに変換されたdist/app.jsが作成されます。

dist/app.js
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const bolt_1 = require("@slack/bolt");
const app = new bolt_1.App({
    token: process.env.SLACK_BOT_TOKEN,
    signingSecret: process.env.SLACK_SIGNING_SECRET
});
// Listens to incoming messages that contain "hello"
app.message("hello", ({ message, say }) => {
    // say() sends a message to the channel where the event was triggered
    say(`Hey there <@${message.user}>!`);
});
(() => __awaiter(void 0, void 0, void 0, function* () {
    // Start your app
    yield app.start(process.env.PORT || 3000);
    console.log("⚡️ Bolt app is running!");
}))();
//# sourceMappingURL=app.js.map

botを実行します。⚡️ Bolt app is running!が出力されれば成功です!

$ npm run server

> first-bolt-app@1.0.0 serve /Users/xxxx/first-bolt-app
> node --require dotenv/config dist/app.js

⚡️ Bolt app is running!

コンパイルと実行を同時に行う場合は、npm run startを実行します。

$ npm run start

> first-bolt-app@1.0.0 start /Users/xxxx/first-bolt-app
> npm run build && npm run serve

> first-bolt-app@1.0.0 build /Users/xxxx/first-bolt-app
> npx tsc

> first-bolt-app@1.0.0 serve /Users/xxxx/first-bolt-app
> node --require dotenv/config dist/app.js

⚡️ Bolt app is running!

ここまで動作確認ができたらGlitchに作成したファイルアップすることでローカルマシンまでのトンネルするアプリの公開URLを用意できないと確認ができないslackからのevent受信などの確認もできるようになります。

アップするファイル一覧
first-bolt-app/
├── .env
├── package.json
├── src
│   └── app.ts
└── tsconfig.json

Glitch上でbotが起動するときは、npm run startが実行されるのでコンパイル済みのdist/app.jsはアップしなくても動作します。

おまけ

Bolt フレームワークを使って Slack Bot を作ろうの「Learn More」の「もうちょっと複雑な Glitch サンプル」をTypescript実装したソースコードは下記においてますので参考になれば、幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AppiumでFlutterアプリのテストを自動化する 実践編(JavaScript)

はじめに

AppiumでFlutterアプリのテストを自動化する 環境構築編 - Qiita
の続きになります。
実際にテストコードを書いて、それを実行し、レポートを出力するところまでやります。
今回は「JavaScript」を使います。

前提条件

AppiumでFlutterアプリのテストを自動化する 環境構築編 - Qiita
で、Appiumの環境構築が完了していること

なぜAppiumで自動化するのか

Flutterには、「Integration Test」という仕組みが存在します。

これは結合テストを行うための仕組みですが、この仕組みを利用することで、UIテストを自動化することも可能になります。また、各OSごとにUIテストを用意せずとも、ワンソースで実装することができます。
ですが、テストしたいWidgetに対して逐一Keyを設定していかなければならないのと、全て手動で実装しないといけないのが難点になります。
Appiumはワンソースで実装することが容易ではありませんが、OSごとのデバイス設定やWidgetの取得方法などをうまく共通化することができればワンソースでも実現可能ですし、何よりレコード機能があるため、Appiumで自動化する選択肢もありだと考えています。
ただ何を優先するかはプロジェクトによりけりではあるので、どちらが正解というわけでもないと思います。

プロジェクトの準備

appium/sample-code/javascript-wd at master · appium/appium · GitHub
をベースにプロジェクトを準備します。
なぜこのプロジェクトを流用したかというと、レコード機能で取得した内容をコピペするだけで実装が容易にできるのと、Mochaというテスティングフレームワークがなかなか良さげ(レポート機能ついてたりとか)だったので採用しました。
基本的にはほぼ流用している形になります。

package.json
{
  "name": "appium_test_js_wd",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "mocha test/**/*.test.js",
    "clean": "rm -rf node_modules && rm -f package-lock.json && npm install"
  },
  "author": "",
  "license": "ISC",
  "engines": {
    "node": ">=6",
    "npm": ">=6"
  },
  "devDependencies": {
    "@babel/register": "^7.0.0",
    "@babel/core": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "chai": "^4.1.2",
    "mocha": "^6.0.0",
    "wd": "^1.5.0"
  }
}
.babelrc
{
    "presets": [
      [
        "@babel/preset-env",
        {
          "targets": {
            "node": "6"
          }
        }
      ]
    ]
  }

mocha.optsでオプションを設定することができます。
--reporterオプションでレポートの出力形式を設定することができます。

test/mocha.opts
--require @babel/register
--timeout 1800000
--reporter spec
test/.eslintrc
{
    "rules": {
        "func-names": 0
    }
}

Appium Desktopでテストコードを記録

テストコードの記録方法については以下に記載していますので参照してください。
AppiumでFlutterアプリのテストを自動化する 実践編(Python) - Qiita
使用するアプリは上記ページと同様です。
記録する言語は、「JS(wd)」を選択します。

記録したテストコードを実行できるようにする

例えば以下のように記載します。

test/top/top.test.js
import wd from 'wd';
import chai from 'chai';

const {assert} = chai;

describe('カウントアップアプリ', function () {
  let driver;

  before(async function () {
    driver = await wd.promiseChainRemote("http://127.0.0.1:4723/wd/hub");

    const caps = {
      "platformName": "Android",
      "automationName": "Appium",
      "deviceName": "Android Emulator",
      "app": "/Users/Hitoshi/AndroidStudioProjects/flutter_app_for_appium/build/app/outputs/apk/release/app-release.apk"
    };

    await driver.init(caps);
    // ここで待たないと要素の取得に失敗してしまうので待つ
    await driver.setImplicitWaitTimeout(5000);
  });

  after(async function () {
    await driver.quit();
  });

  it('初期状態', async function () {
    let el1 = await driver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.view.View[3]");
    const countText = await el1.text();
    assert.equal(countText, '0');
  });

  it('カウントアップされるか', async function () {
    let el1 = await driver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.widget.Button");
    await el1.click();
    let el2 = await driver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.view.View[3]");
    const countText = await el2.text();
    assert.equal(countText, '1');
  });
});

テストコードの実行

コマンドでテストコードを実行します。

npm test

すると、以下のように表示されるはずです。
端末側もアプリが起動し、ボタンが押されてカウントアップされるはずです。

> appium_test_js_wd@1.0.0 test /Users/Hitoshi/src/appium-test/appium_test_js_wd
> mocha test/**/*.test.js



  カウントアップアプリ
    ✓ 初期状態 (2195ms)
    ✓ カウントアップされるか (2199ms)


  2 passing (42s)

ソースコード

以下にアップしましたので参考にしてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

expressによるウェブページテンプレート化の手法(express-ejs-layoutsを使用)

概要

Expressにおけるwebページのテンプレート化の方法をまとめてみる。
使用するパッケージは以下の通り。
・express
・express-ejs-layouts

作成するファイルは以下の通り。
(1) /main.js
テンプレート化に必要なパッケージの読み込み、レスポンスのロジックを実装

(2) /views/layout.ejs
テンプレートエンジンが使用するレイアウトのウェブページのテンプレート。このファイルでページのレイアウトとコンテンツの埋め込み箇所を定義する。

(3) /views/index.ejs
テンプレートエンジンがレイアウトに埋め込むファイル。
ここではindexのページを定義しているが、ユーザーのアクセスするパスに応じて他のejsファイルも作成する。

Step1. main.jsの実装

まず、以下のようにmain.jsを実装する。
(1) パッケージのインポート
以下3つの定数を定義する。

const express =require(“express”)
const layouts = require(“express-ejs-layouts”)
const app = express()

(2) view engineの指定
appに対して、view engineの属性設定。

app.set(“view engine”,”ejs”)

https://expressjs.com/ja/guide/using-template-engines.html

(3) リクエストパスに対するレスポンスロジックの実装
以下のようにrenderに/view/index.ejsのファイルをテンプレートとして読み込むことを指定する。拡張子指定不要。

app.get(“/”, (req, res) => {
    res.render(“index”)
})

Step2. layout.ejsの定義

layout.ejsはページ間で共通のコンテンツを定義する。
その上で、各ページ固有のコンテンツを埋め込む場所を定義する。

<html>
<head> ... </head>
<body> 
レイアウトのコンテンツを記載

<%- body %>

</body>
</html>

上記でも記載のあるように、以下の記述が各ページのコンテンツの埋め込み先になる。

<%- body %>

Step3. index.ejsの定義

index.ejsには、laytout.ejsの<%- body %>に埋め込むコンテンツを定義することになる。

<h1>これがhttp://xxxxxx.xx.xx/にアクセスした際に表示されるページのコンテンツ部分です</h1>

ポイントは、HTMLを記載しているのだが、お決まりのから始まるタグの記載をするのではなく、layout.ejsに埋め込まれることを想定したhtmlの記述をする必要がある。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む