bun create next-app と npx create-next-app の違い

● bun create next-app と npx create-next-app の違い

この2つのコマンドで違うことは何か?

・bun.lockb が生成される。

つまり package.json に変更はありません。では bun.lockb を後から生成してみましょう。

● package.json から bun.lockb を生成する

 bun i

以下のように表示され、同時に bun.lockb が生成されます。

bun install v1.0.27 (c34bbb2e)
[7.78ms] migrated lockfile from package-lock.json

 15 packages installed [87.00ms]

● bun.lockb が git で扱いにくい問題

bun.lockb は バイナリファイル です。 次の点で git 扱いにくくなります

・人間が見える形で差分を取れない。
・ コンフリクトの解消ができない

対応策 → yarn.lock を生成させてそれで差分比較する

● ( yarn.lockの生成方法 1. )package.json から bun.lockb と yarn.lock を生成する

bun i --yarn

● ( yarn.lockの生成方法 2. )bunfig.toml を設定してから bun.lockb と yarn.lock を生成する

vi bunfig.toml

bunfig.toml

[install.lockfile]

# whether to save the lockfile to disk
save = true

# whether to save a non-Bun lockfile alongside bun.lockb
# only "yarn" is supported
print = "yarn"

あとは bun i するだけです。

bun i 

bun.lockbのVersion管理をGitでどうやる?問題

● bun の設定ファイル bunfig.toml のオプション一覧

一般的には、プロジェクトルート(ローカル)にpackage.jsonと一緒にbunfig.tomlファイルを追加することをお勧めします。

bunfig.toml – Runtime | Bun Docs

No.2471
02/19 10:04

edit

bun を全てのユーザーが使えるようにする

● bun のインストール(rootユーザー)

root ユーザーで行います

curl -fsSL https://bun.sh/install | bash

● bun を全てのユーザーが使えるようにする

root ユーザーで行います

sudo mv /root/.bun/bin/bun /usr/local/bin/
sudo chmod a+x /usr/local/bin/bun
No.2470
02/18 09:45

edit

npm で指定したパッケージだけupgradeしたい

● 特定のnpmパッケージのバージョン一覧を表示

npm view パッケージ名 versions

● npm で指定したパッケージだけupgradeする

npm install パッケージ名@バージョン
No.2465
02/13 09:21

edit

プロジェクト内の npm パッケージの更新手順

● 1. npm outdated で パッケージが最新かどうかを確認する。

npm outdated

● 2. npm update で メジャーバージョンをまたがない update を行う

npm update は
セマンティック バージョニングに従った package.json で指定されたバージョン範囲内で最新のバージョンを探します。
たとえば、^1.0.0 と指定されている場合、1.x.x の最新バージョンにアップデートしますが、2.0.0 にはアップデートしません。

また メジャーバージョンが変わるような大きな変更(破壊的変更)は行いません。依存関係を最新の状態に保ちつつ、プロジェクトの互換性を保つために使用されます。

● 3. npm audit で パッケージの脆弱性の検査を行う

npm audit

実際に修正を行う場合は、次のコマンドを実行する

npm audit fix

これでも 修正が完了しない場合は、メジャーバージョンをまたいだアップデートを行う必要があります。

npm audit fix の 結果、出力に、例えば以下のような出力があります。

Severity: high
Inefficient Regular Expression Complexity in nth-check - https://github.com/advisories/GHSA-rp65-9cf3-cjxr
fix available via `npm audit fix --force`
Will install @storybook/preset-create-react-app@4.1.0, which is a breaking change

@storybook/preset-create-react-app@4.1.0 という バージョンにする必要があるよという意味です。

ncu をインストールしてアップデートを自動で行う

npm install -g npm-check-updates
ncu --version

ncuで一括バージョンアップするとどのバージョンになるのか確認する

ncu

ncuで一括バージョンアップをpackages.jsonに書き込んでupdateする

 ncu -u
npm i
No.2464
02/06 16:32

edit

npm バージョンをマイナーで留めておく

● キャレット指定(メジャーバージョンで止める)

package.json

"my-package": "^3.1.5",

これは最大で 3.9.99 のように「同一のメジャーバージョン内で最大のバージョン指定」となります

● チルダ指定(マイナーバージョンで止める)

package.json

"my-package": "~3.1.5",

これは最大で 3.1.99 のように「同一のマイナーバージョン内で最大のバージョン指定」となります

その他の指定方法はこちらがよくまとまっています。

【package.json】バージョン指定の書き方と調べ方【11種類】 - 空色彩開発ブログ

No.2463
02/06 10:17

edit

色々バージョン切り替え asdf

● asdfのインストール

1.

brew install asdf

2. シェルの設定

https://asdf-vm.com/guide/getting-started.html の 「3. Install asdf」 からシェルごとの設定をコピペして実行

Bash & Homebrew (macOS) の場合は

echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bash_profile
echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bash_profile

● asdf を使ってみる

インストールされているコマンドのリスト

asdf list

node.jsをインストールする

asdf plugin-add nodejs
asdf list all nodejs
asdf install nodejs 18.19.0

● グローバルで使用可能にする

asdf global nodejs 18.19.0
No.2461
02/02 10:52

edit

Prisma orm で スコープをつけたい

● Prisma orm で スコープをつけたい

PrismaはORMとしてのカスタマイズ性に制限があり、モデル名や関数名を動的に変更するような直接的な機能は提供していません。 ただし、Prisma Clientのインスタンスを拡張して、独自のメソッドを持つクラスを作成することで、似たような機能を実装することは可能です。

以下は、PrismaClientを拡張して、アクティブなユーザーだけを取得するためのカスタムメソッドactiveUsersメソッドを作成してみます。

import { PrismaClient, Prisma, User } from '@prisma/client'

class ExtendedPrismaClient extends PrismaClient {
  constructor() {
    super();
  }

  // アクティブなユーザーだけを取得するカスタムメソッド
  async activeUsers(): Promise<User[]> {
    return this.user.findMany({
      where: {
        isActive: true,
      },
    });
  }
}

// 使用例
const run = async () => {
  const prisma = new ExtendedPrismaClient();
  const activeUsers = await prisma.activeUsers();
  console.log(activeUsers);
}

run();

No.2456
01/25 09:40

edit

nest.js で GraphQL Code Generatorを使うとき、Date関連の型をどうするか

● nest.js で GraphQL Code Generatorを使うとき、Date関連の型をどうするか

https://qiita.com/majimaccho/items/4829f27c5514fbf37767

● クライアント側 Apollo Clientでカスタムデシリアライズする

https://tech.buysell-technologies.com/entry/2023/09/26/083501

No.2455
01/24 18:27

edit

プレーンなオブジェクトをクラスに変換する class-transformer

HumanData型の普通のオブジェクトを Humanクラスのインスタンスに変換します。 変換は plainToInstance() メソッドを使用します。(plainToClassはdeprecated)

npm i class-transformer reflect-metadata
import {
  Exclude,
  Expose,
  plainToInstance,
  Transform,
  Type,
} from 'class-transformer';
import 'reflect-metadata';

interface HumanData {
  id: number;
  gender: string;
  name: string;
  myoji: string;
  birthday: string;
}

enum Gender {
  MAN = 'man',
  WOMAN = 'woman',
}

class Human {
  id: number;

  gender: Gender;

  @Expose({ name: 'name' })
  firstName: string;

  @Expose({ name: 'myoji' })
  lastName: string;

  // @Type: 文字列からDate型に変換する
  @Type(() => Date)
  birthday: Date;

  // @Transform: 文字列からDate型に変換する
  // toClassOnly: プレーンオブジェクトからクラスインスタンスへの変換時にのみこの変換を適用
  // @Transform(({ value }) => new Date(value), { toClassOnly: true })
  // birthday: Date;

  // // 指定したプロパティを削除する
  // @Exclude()
  // birthday: string;
}

const humanData: HumanData = {
  id: 1,
  name: 'ichiro',
  myoji: 'sato',
  gender: 'man',
  birthday: '1991-01-01',
};

const humanInstance = plainToInstance(Human, humanData);

console.log('● humanInstance');
console.log(humanInstance);

● 文字列からDate型に変換する(その1)

  @Type(() => Date)
  birthday: Date;

● 文字列からDate型に変換する(その2)

  // toClassOnly: プレーンオブジェクトからクラスインスタンスへの変換時にのみこの変換を適用
  @Transform(({ value }) => new Date(value), { toClassOnly: true })
  birthday: Date;

● 指定したプロパティを削除する

  @Exclude()
  birthday: string;

● 変換もとオブジェクト側に持っているプロパティはそのままクラスのプロパティとなってしまう

class-transformerでオブジェクトからクラスに変換する時、クラスに定義していなくても変換もとオブジェクト側に持っているプロパティは
そのままクラスのプロパティとなってしまいます
これを防ぐには

{ excludeExtraneousValues: true } を使用します。このオプションを使用すると @Expose() しないとそのプロパティは削除されます。

import { plainToInstance } from 'class-transformer';

class User {
    @Expose()
    id: number;

    @Expose()    
    name: string;
}

const plain = {
    id: 1,
    name: 'John Doe',
    password: 'secret' // User クラスには存在しないプロパティ
};

const classObj = plainToInstance(User, plain, { excludeExtraneousValues: true });

console.log(classObj); // 'password' プロパティは含まれません

● インスタンスをプレーンなオブジェクトに変換する instanceToPlain()

以下のように配列でも変換できます。

      const sampleClassList = [
        new SampleClass({
          id: "1",
          name: `${input.name}_1`,
          updatedAt: undefined,
        }),
        new SampleClass({
          id: "2",
          name: `${input.name}_2`,
          updatedAt: undefined,
        }),
      ];
      return instanceToPlain(sampleClassList);
添付ファイル1
No.2454
01/25 21:10

edit

添付ファイル

nest.js で class-validator or zod

● バリデーション+クラス(型)の記述

A. class-validator

import { IsNotEmpty, IsEmail, Length } from 'class-validator';

export class CreateUserDto {
  @IsNotEmpty()
  @Length(10, 20)
  username: string;

  @IsEmail()
  email: string;

  @IsNotEmpty()
  password: string;
}

B. zod

import { z } from 'zod';

export const CreateUserDtoSchema = z.object({
  username: z.string().min(10).max(20),
  email: z.string().email(),
  password: z.string().nonempty(),
});

export type CreateUserDto = z.infer<typeof CreateUserDtoSchema>;

● バリデーションの実行

A. class-validator

import { IsNotEmpty, IsEmail, Length, validateOrReject } from 'class-validator';

export class CreateUserDto {
  @IsNotEmpty()
  @Length(10, 20)
  username: string;

  @IsEmail()
  email: string;

  @IsNotEmpty()
  password: string;

  constructor(username: string, email: string, password: string) {
    this.username = username;
    this.email = email;
    this.password = password;
    validateOrReject(this).catch(errors => {
      console.error('Validation failed:', errors);
      throw errors;
    });
  }
}

B. zod

import { z } from 'zod';

const CreateUserDtoSchema = z.object({
  username: z.string().min(10).max(20),
  email: z.string().email(),
  password: z.string().nonempty(),
});

export class CreateUserDto {
  username: string;
  email: string;
  password: string;

  constructor(username: string, email: string, password: string) {
    const parsedData = CreateUserDtoSchema.parse({ username, email, password });
    this.username = parsedData.username;
    this.email = parsedData.email;
    this.password = parsedData.password;
  }
}
No.2451
01/23 12:17

edit

node.js で 「実行中のファイル」「ワーキングディレクトリ」を取得する

● node.js で 「実行中のファイル」を取得する

const currentFilename = __filename

● node.js で 「実行中のファイルがあるディレクトリ」を取得する

const currentDirname = __dirname

● node.js で 「ワーキングディレクトリ」を取得する

const workDirectory = process.cwd()
No.2440
01/13 10:25

edit

Prismaのちょっとしたテクニック

● PrismaのEnumをZodのスキーマとして使う

prisma/schema.prisma

enum DeviceType {
  mobile
  desktop
}

zod で使用する

import { z } from 'zod'

export const DeviceTypeEnum = z.nativeEnum(DeviceType)

https://qiita.com/nuinteedev/items/4f6bf01dc1a768c9ea82

No.2436
12/26 09:43

edit

lodash の debounce を非同期関数(async , await)で使用する

import debounce from 'lodash/debounce';

// 非同期関数の例
async function fetchData(query) {
  // API呼び出しなど
  return await someAsyncOperation(query);
}

// debounce関数を使用してfetchDataをデバウンス
const debouncedFetchData = debounce(async (query, resolve) => {
  try {
    const result = await fetchData(query);
    resolve(result);
  } catch (error) {
    resolve(Promise.reject(error));
  }
}, 200); // 200ミリ秒のデバウンス時間

// デバウンスされた関数を呼び出す
function callDebouncedFetchData(query) {
  return new Promise((resolve) => {
    debouncedFetchData(query, resolve);
  });
}

// 使用例
callDebouncedFetchData('query').then(result => {
  console.log('結果:', result);
}).catch(error => {
  console.error('エラー:', error);
});
No.2434
12/23 14:15

edit

Prisma で MySQL の JSON型に TypeScriptの型をつける

npm install -D prisma-json-types-generator

src/types.ts

declare global {
  namespace PrismaJson {
    export type WordSimilarWords =string[]
  }
}
export type { PrismaJson }

1. 設定を追加

以下を prisma/schema.prisma に追加します

generator jsonTypes {
  provider = "prisma-json-types-generator"
}

2. テーブルの json型に Type を指定する

以下を prisma/schema.prisma に追加します

model Word {
  id      Int  @id @default(autoincrement())
  /// [WordSimilarWords]
  similarWords Json
}

引用 : https://bit.ly/49sczw9

No.2417
11/24 11:58

edit

prisma で 複数のフィールドに対してユニーク制約をつける

● prisma で 複数のフィールドに対してユニーク制約をつける

schema.prisma

model YourModel {
  // 他のフィールド定義...

  name     String
  langCode String

  // 任意の名前をつけられます。例: word_identifier
  @@unique(fields: [name, langCode], name: "word_identifier")
}

word_identifier はupsertなどで where句に使用できます。

const data = await prisma.word.upsert({
  where: {
    word_identifier: {
      name: d.name,
      langCode: d.langCode,
    },
  },
  create: {
    ...d,
  },
  update: {
    ...d,
  },
})
No.2415
11/13 09:16

edit

prisma メソッド

https://www.prisma.io/docs/concepts/components/prisma-client/crud

  • create( create a single record )
const user = await prisma.user.create({
  data: {
    email: 'elsa@prisma.io',
    name: 'Elsa Prisma',
  },
})
  • createMany ( create multiple records )
const createMany = await prisma.user.createMany({
  data: [
    { name: 'Bob', email: 'bob@prisma.io' },
    { name: 'Bobo', email: 'bob@prisma.io' }, // Duplicate unique key!
    { name: 'Yewande', email: 'yewande@prisma.io' },
    { name: 'Angelique', email: 'angelique@prisma.io' },
  ],
  skipDuplicates: true, // Skip 'Bobo'
})
  • Create records and connect or create related records
  • Read
  • Get record by ID or unique identifier
  • Get record by compound ID or compound unique identifier
  • Get all records
    • Get the first record that matches a specific criteria
  • Get a filtered list of records
    • Filter by a single field value
    • Filter by multiple field values
    • Filter by related record field values
  • Select a subset of fields
    • Select a subset of related record fields
  • Select distinct field values
  • Include related records
    • Include a filtered list of relations
  • Update
  • Update a single record
  • Update multiple records
  • upsert ( update or create records )
const upsertUser = await prisma.user.upsert({
  where: {
    email: 'viola@prisma.io',
  },
  update: {
    name: 'Viola the Magnificent',
  },
  create: {
    email: 'viola@prisma.io',
    name: 'Viola the Magnificent',
  },
})
  • Update a number field
  • Connect and disconnect related records
  • Delete
  • Delete a single record
  • Delete multiple records
  • Delete all records
    • Cascading deletes (deleting related records)
  • Delete all records from all tables
    • Deleting all data with deleteMany
    • Deleting all data with raw SQL / TRUNCATE
    • Deleting all records with Prisma Migrate
  • Advanced query examples
  • Create a deeply nested tree of records
No.2411
11/11 16:45

edit

No.2354
05/25 14:39

edit

WebStorm で graphql のクエリでエラーが出る場合の対応

● WebStorm で graphql のクエリでエラーが出る場合の対応

webstorm などで 以下のようなエラーが出ることがあります

apollo client the parent selection or operation does not resolve

対策 graphql.config.yml をプロジェクトトップに作成する

graphql.config.yml

{
  "name": "Gatsby Schema",
  "schemaPath": "gatsby.graphql",
  "extensions": {
    "endpoints": {
      "Gatsby GraphQL Endpoint": {
        "url": "http://localhost:4000/graphql/",
        "headers": {
          "user-agent": "JS GraphQL"
        },
        "introspect": false
      }
    }
  }
}

参考: https://bit.ly/45rB2zC

No.2350
05/23 15:07

edit

schema.prisma から graphql の コード を自動生成する便利なジェネレータ prisma-nestjs-graphql

● prisma-nestjs-graphql

https://github.com/unlight/prisma-nestjs-graphql

● prisma-nestjs-graphql の インストール

prisma-nestjs-graphql に加えて class-transformer もインストールしておきます

npm i -D prisma-nestjs-graphql class-transformer 

# エラーが出る場合は
npm i  add -D prisma-nestjs-graphql@17.1.0

● 設定

schema.prisma ( npm の場合 )

generator nestgraphql {
    provider = "node node_modules/prisma-nestjs-graphql"
    output = "../src/@generated"
}

schema.prisma ( yarn の場合 )

generator nestgraphql {
    provider = "prisma-nestjs-graphql"
    output = "../src/@generated"
}

● generate !

npx prisma generate

../src/@generated にファイルが生成されます。

続けてlintをかけておくといいと思います

npm run lint

● 自動生成の実行

npx prisma generate

● .gitignore に 自動生成されたファイルを除外するように記述する

.gitignore

# auto generated files
src/@generated
No.2307
05/19 18:09

edit

Prisma で 1対1( One to One ) 1対多( One to Many )リレーションの定義

● Prisma で 1対1( One to One )リレーションの定義

・呼び出す側(メインとなるモデル)

model User {
  id      Int      @id @default(autoincrement())
  profile Profile?
}

・呼び出される側(サブとなるモデル)

model Profile {
  id     Int  @id @default(autoincrement())
  user   User @relation(fields: [userId], references: [id])
  userId Int  @unique // relation scalar field (used in the `@relation` attribute above)
}

https://bit.ly/3Ok87XZ

● Prisma で 1対多( One to Many )リレーションの定義

model User {
  id    Int    @id @default(autoincrement())
  posts Post[]
}

model Post {
  id       Int  @id @default(autoincrement())
  author   User @relation(fields: [authorId], references: [id])
  authorId Int
}

● 設定項目

・親テーブルを削除したときに関連テーブルも同時に削除する onDelete: Cascade

model Post {
  id       Int  @id @default(autoincrement())
  author   User @relation(fields: [authorId], references: [id] , onDelete: Cascade) // 追加
  authorId Int
}

● 1対多( One to Many )リレーションの書き方のコツ

・1. まずこのようにidだけを定義したそれぞれのモデルを作成しておきます。

model User {
  id    Int    @id @default(autoincrement())
}

model Post {
  id       Int  @id @default(autoincrement())
}

・2. 親がわ(1対多の多 側)にリレーションを追記して保存します。

model User {
  id    Int    @id @default(autoincrement())
  posts Post[] // ● ← 追記
}

model Post {
  id       Int  @id @default(autoincrement())
}

・3. すると、子がわに自動で追記されます。

model User {
  id    Int    @id @default(autoincrement())
  posts Post[]
}

model Post {
  id       Int  @id @default(autoincrement())
  user   User @relation(fields: [userId], references: [id])  // ● 自動
  userId Int  // ● 自動
}

あとは フィールド名やデータベースの物理カラム名など書き加えます。

● 1対多( One to Many )リレーションでSELECT時にリレーションも取得する

・include (全てのフィールドをとる場合)

const user = await this.prisma.user.findUnique({
  where: { email: email },
  include: { posts: true },
})

・select (指定したカラムのみ取得する場合)

const user = await this.prisma.user.findUnique({
  where: { email: email },
  select: {
    id: true,
    email: true,
    posts: {
      select:{
        id: true,
      }
    }
  },
})
No.2346
12/28 19:08

edit

Prisma で 多対多( Many to Many )リレーションの定義

● Prisma で 多対多( Many to Many )リレーションの定義

以下の2通りの方法があります。

・A. 中間テーブルを明示的に定義する方法 ( explicit )
・B. 中間テーブルを定義せずおまかせで自動生成する方法 ( implicit )

・A. 中間テーブルを明示的に定義する方法 ( explicit )

公式 : https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations#explicit-many-to-many-relations

中間テーブル CategoriesOnPosts を定義します。
命名は「2つのテーブルをアルファベット順に並べてToで繋げる。」 としておくと implicit の時と似た命名になります。

model Post {
  id         Int                 @id @default(autoincrement())
  title      String
  categories CategoriesOnPosts[]
}

model Category {
  id    Int                 @id @default(autoincrement())
  name  String
  posts CategoriesOnPosts[]
}

model CategoriesOnPosts {
  post       Post     @relation(fields: [postId], references: [id])
  postId     Int // relation scalar field (used in the `@relation` attribute above)
  category   Category @relation(fields: [categoryId], references: [id])
  categoryId Int // relation scalar field (used in the `@relation` attribute above)
  assignedAt DateTime @default(now())
  assignedBy String

  @@id([postId, categoryId])
}

・B. 中間テーブルを定義せずおまかせで自動生成する方法 ( implicit )

公式 : https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations#implicit-many-to-many-relations

model Post {
  id         Int        @id @default(autoincrement())
  title      String
  categories Category[]
}

model Category {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[]
}

これで npx prisma migrate dev とすると
以下のような中間テーブル _CategoryToPostが自動生成されます。
命名は「2つのテーブルをアルファベット順に並べてToで繋げる。 先頭に_をつける」 で命名されます。

-- CreateTable
CREATE TABLE `_CategoryToPost` (
    `A` INTEGER NOT NULL,
    `B` INTEGER NOT NULL,

    UNIQUE INDEX `_CategoryToPost_AB_unique`(`A`, `B`),
    INDEX `_CategoryToPost_B_index`(`B`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

参考 : Prisma Schemaの書き方あれこれ

No.2342
05/11 15:43

edit

jest のMultiple configurations found エラーの対応

● jest のMultiple configurations found エラー

「jestの設定」が2つ以上存在するというエラーです

● jest の設定の記述箇所

https://archive.jestjs.io/docs/ja/22.x/configuration

Jestの設定は
・1. プロジェクトの package.json ファイルか、 
・2. jest.config.jsファイルまたは
・3. --config オプション
から、定義することができます。 

package.json に Jest の構成を保存する場合は、Jest が設定を見つけられるように "jest" キーをトップレベルに設定する必要があります:

これらの重複を取り除いてあげると「Multiple configurations found エラー」が表示されなくなります。

No.2340
05/09 16:03

edit

graphqlリクエストが「This operation has been blocked as a potential Cross-Site Request Forgery (CSRF).」になる現象の対策

ヘッダを確認して修正しましょう

'Content-Type: text/plain;charset=UTF-8'

  ↓

'content-type: application/json'
No.2339
05/09 07:30

edit

concurrently で npm スクリプトを並列実行

● concurrently のインストール

npm install --save-dev concurrently

以下のようなコマンドを並列実行することができます。

concurrently "yarn start:server" "yarn start:client"

これにより、start:serverとstart:clientの2つのyarnコマンドが同時に実行されます。

No.2325
04/22 08:21

edit

nestjs の テストを高速化する ( esbuild / swc )

● A. nestjs の テストを esbuild で高速化する

npm i -D esbuild-jest esbuild

jest.config.js

module.exports = {
  testEnvironment: 'node',
  transform: {
    '^.+\\.tsx?$': 'esbuild-jest',
  },
};

● B. nestjs の テストを swc で高速化する

npm i -D @swc/core @swc/jest

jest.config.js

module.exports = {
  testEnvironment: 'node',
  transform: {
    '^.+\\.tsx?$': ['@swc/jest'],
  },
};

.swcrc

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true
    },
    "target": "es2017",
    "keepClassNames": true,
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    }
  },
  "module": {
    "type": "commonjs",
    "noInterop": true
  }
}

参考 : @swc/jestで手間をかけずにテストを早くする - ドワンゴ教育サービス開発者ブログ

参考 : JestでTypeScriptを高速化する | miyauci.me

NestJS 製のプロジェクトのテストを@swc/jest で高速化する - Mobile Factory Tech Blog

No.2322
02/28 10:41

edit

No.2321
04/20 10:55

edit

graphql の mutation で値を渡す方法

● (graphql Mutation)で直接値を渡す

graphql

mutation createSource {
  createSource(createSourceInput:{
    userId: 1,
    locale: "ja",
    text: "日本語のサンプルです"
  })
  {
    id,userId,locale,text
  }
}

● (graphql Mutation)でvariables で値を渡す方法

graphql

mutation createSource($input:CreateSourceInput!) {
  createSource(createSourceInput:$input)
  {
    id,userId,locale,text
  }

1行目の $input:CreateSourceInput! で 変数を定義します。( 変数名: 型)
型は schema.gql と揃える必要があります。(最後の ! が抜けたとしてもエラーとなります)

schema.gql の例

type Mutation {
  createSource(createSourceInput: CreateSourceInput!): Source!

json

{
  "input": {
    "userId": 1,
    "locale": "ja",
    text: "日本語のサンプルです"
  }
}
No.2317
04/14 12:16

edit

nestjs + graphql + で firebase auth token認証

● nestjs + graphql + で firebase auth token認証

引用 : https://bit.ly/3K7o4x5

● firebase-admin SDK を追加する

・firebase-adminの インストール

npm install firebase
npm install firebase-admin

・firebase-adminの設定

src/main.ts の bootstrap() に以下を追加する

firebaseコンソールからサービスアカウントのjsonファイルをダウンロードしておきます(コンソール→プロジェクトの設定→サービスアカウント→新しい秘密鍵を生成)

import * as admin from 'firebase-admin';
import * as serviceAccount from '/PATH/TO/YOUR/SERVICE/ACCOUNT.json';


async function bootstrap() {
  // firebase-admin
  const params = {
    type: serviceAccount.type,
    projectId: serviceAccount.project_id,
    privateKeyId: serviceAccount.private_key_id,
    privateKey: serviceAccount.private_key,
    clientEmail: serviceAccount.client_email,
    clientId: serviceAccount.client_id,
    authUri: serviceAccount.auth_uri,
    tokenUri: serviceAccount.token_uri,
    authProviderX509CertUrl: serviceAccount.auth_provider_x509_cert_url,
    clientC509CertUrl: serviceAccount.client_x509_cert_url,
  };
  admin.initializeApp({
    credential: admin.credential.cert(params),
  });

A. @nestjs/passport を使用して認証機能を作成する場合

● guard を作成する

・Nest.jsにおけるGuardとは?

Nest.jsにおけるGuardは実際には次の条件を全て満たすようなものです:
 ・クラスである
 ・@Injectable()デコレーターでアノテーションされている
 ・CanActivateというインターフェースをimplementsしている
 ・canActivateという、ExecutionContext型を引数にとり、同期または非同期でboolean値(trueまたはfalse)を返すメソッドを実装している

引用 : https://bit.ly/41kceXx

src/auth/guard/auth.guard.ts

import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from "@nestjs/common";
import { AuthService } from "../auth.service";
import { GqlExecutionContext } from "@nestjs/graphql";

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly authService: AuthService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const ctx = GqlExecutionContext.create(context);
    const requestHeaders = ctx.getContext().req.headers;
    if (!requestHeaders) {
      throw new Error("ヘッダが正しく設定されていません。");
    }
    const idToken: string = requestHeaders.authorization.replace("Bearer ", "");
    try {
      const user = await this.authService.validateUser(idToken);
      ctx.getContext().req["user"] = user;
      return user !== undefined;
    } catch (error) {
      throw new UnauthorizedException("認証情報が正しくありません。");
    }
  }
}

「CanActivate」 は 結果の返却は、Booleanのため、情報の再利用はできません。
「AuthGuard」を 使用すると結果の返却は Object | Boolean となるため 情報の再利用が可能となります

● guard をリゾルバに適用させる

・モジュールで読み込み

src/users/users.module.ts

import { AuthService } from '../auth/auth.service';

// AuthService を追加します
@Module({
  providers: [UsersResolver, UsersService, AuthService],
})

・リゾルバに @UseGuards(AuthGuard) を追加

  @Query(() => User, { name: 'user' })
  findOne(@Args('id', { type: () => Int }) id: number) {
    return this.usersService.findOne(id);
  }

  ↓

メソッドのレベルで使用するために、次のように @UseGuards() デコレータを使用します

import { UseGuards } from '@nestjs/common';

@UseGuards(AuthGuard)
  @Query(() => User, { name: 'user' })
  findOne(@Args('id', { type: () => Int }) id: number) {
    return this.usersService.findOne(id);
  }

以上です。

B. @nestjs/passport を使用して認証機能を作成する場合

https://docs.nestjs.com/recipes/passport

@nestjs/passport の インストール

npm install --save @nestjs/passport passport passport-jwt passport-http-bearer

以下の「ストラテジ」「モジュール」「ガード」を作成して @UseGuards(FirebaseAuthGuard) という記述でガードを使用できるようにします。

src/auth/firebase.strategy.ts

import {
  HttpException,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-http-bearer';
import { DecodedIdToken } from 'firebase-admin/lib/auth';
import * as admin from 'firebase-admin';

@Injectable()
export class FirebaseStrategy extends PassportStrategy(Strategy, 'firebase') {
  constructor() {
    super();
  }

  async validate(idToken: string): Promise<DecodedIdToken> {
    if (!idToken) {
      throw new UnauthorizedException('認証が必要です。');
    }

    try {
      return await admin.auth().verifyIdToken(idToken);
    } catch (error) {
      throw new HttpException('Forbidden', error);
    }
  }
}

src/auth/firebase/firebase.module.ts

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { FirebaseStrategy } from './firebase.strategy';

@Module({
  imports: [PassportModule.register({ defaultStrategy: 'firebase' })],
  providers: [FirebaseStrategy],
  exports: [PassportModule],
})
export class FirebaseModule {}

src/auth/firebase/firebaseAuth.guard.ts

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class FirebaseAuthGuard extends AuthGuard('firebase') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req;
  }
}

使い方は同じです。 ガードを加えます。

  @Query(() => User, { name: 'user' })

  ↓

  @UseGuards(FirebaseAuthGuard)
  @Query(() => User, { name: 'user' })

参考 : https://hi1280.hatenablog.com/

● ガードでfirebaseトークン検証時に取り出したユーザーの値を再利用する

デコレーターを作成し、そこから取り出します。

src/auth/firebase/current-user.decorator.ts

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

export type CurrentFirebaseUserData = {
  uid: string;
  name: string;
  email: string;
};

export const CurrentUser = createParamDecorator(
  (data: unknown, context: ExecutionContext) => {
    const ctx = GqlExecutionContext.create(context);
    const requestUser = ctx.getContext().req['user'];
    const currentUser: CurrentFirebaseUserData = {
      uid: requestUser.uid,
      name: requestUser.name,
      email: requestUser.email,
    };
    return currentUser;
  },
);

使い方

  @UseGuards(FirebaseAuthGuard)
  @Query(() => User, { name: 'profile' })
  profile(@CurrentUser() user: CurrentFirebaseUserData) {
    return this.usersService.findOneFromEmail(user.email);
  }

このようにして profile メソッドに user を渡すことができます。

graphql クエリ例

{
  profile{
    id,name,email,authProvider,authId,createdAt,updatedAt
  }
No.2311
12/18 08:42

edit

NestJS で CORS を有効にする

● NestJS で CORS を有効にする

src/main.ts

  const app = await NestFactory.create(AppModule);

 ↓ 以下のように変更します。

  const app = await NestFactory.create(AppModule, { cors: true });

オプションを加える場合は、以下のようにします

  const app = await NestFactory.create(AppModule);
  app.enableCors({
    origin:’http://localhost:3000/’,
  });
No.2308
03/29 17:10

edit

NestJS / Prisma で 本番環境( .env.production ) / 開発環境( .env.development )ごとの .env を 使用する

● NestJS で 本番 / 開発 環境ごとの .env を 使用する

・ パッケージのインストール

npm i --save @nestjs/config

・ファイルの修正

src/app.module.ts の imports に下記を追加

import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV}`,
    }),

また、確認ように、次のようなコンソール表示を追加しておくのも良いです

console.log(`NODE_ENV の値は : ${process.env.NODE_ENV}`);

・package.json の修正

    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",

  ↓ NODE_ENV=development または NODE_ENV=production を追加します

    "start": "NODE_ENV=development nest start",
    "start:dev": "NODE_ENV=development nest start --watch",
    "start:debug": "NODE_ENV=development nest start --debug --watch",
    "start:prod": "NODE_ENV=production node dist/main",

・.env.development の作成

vi .env.development

試しに API_PORT という値をセットしてみます

DATABASE_URL="mysql://myuser:mypassword@localhost:3306/mydb?schema=public"
API_PORT=4000

・ポートを API_PORT の値に設定する

src/main.ts

  await app.listen(3000);

  ↓

  await app.listen(Number(process.env.API_PORT) || 3000);

・ サーバーを起動してテストする

npm run start:dev

http://localhost:4000

● Prisma CLI で 本番 / 開発 環境ごとの .env を 使用する

dotenv-cli を使って環境ごとにファイルを振り分けます

 npm i -D dotenv-cli

dotenv-cli を利用するので、実行コマンドを package.json の scripts に記述します。

例えば、以下のように設定しておきます

    "prisma:reset": "dotenv -e .env.development -- npx prisma migrate reset --force --skip-seed",
    "prisma:migrate": "dotenv -e .env.development -- npx prisma migrate dev",
    "prisma:seed": "dotenv -e .env.development -- npx prisma db seed"

次のコマンドで実行します

npm run prisma:reset

公式サイト : https://www.prisma.io/docs/guides/development-environment/environment-variables/using-multiple-env-files

No.2306
05/02 17:07

edit

NestJS + Prisma で O/R マッピング

● 1. MySQL の作成 ( Docker )

vi docker-compose.yml
version: "3.8"
services:
  db:
    container_name: myapp-api-db
    image: mysql:8.0
    restart: always
    ports:
      - 13306:3306
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: myapp_api_db
      MYSQL_USER: myapp_api_db
      MYSQL_PASSWORD: db_password
    volumes:
      - ./db/data:/var/lib/mysql

● 2.NestJS + prisma アプリの作成

・nestjs アプリの作成

npm i -g @nestjs/cli
nest new myapp-api

・prismaの インストール

cd myapp-api
npm i @prisma/client
npm i -D prisma

・prismaの 初期化と 設定ファイルの記述

npx prisma init
vi prisma/schema.prisma

prisma/schema.prisma を以下の内容で保存

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

src/prisma.service.ts を以下の内容で保存

import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close();
    });
  }
}
vi .env

.env を以下の内容で保存

DATABASE_URL="mysql://myapp_api_db:db_password@localhost:13306/myapp_api_db?schema=public"

● 3.NestJS と mysql の 接続の確認とマイグレーションの実行

次のような簡単なリレーションを持つテーブルを作成します

・prisma/schema.prisma の一番下に テーブル情報を追記します

vi prisma/schema.prisma
model User {
  id          Int           @id @default(autoincrement())
  email       String        @db.Text
  name        String        @db.Text
  createdAt   DateTime      @default(now()) @db.Timestamp(0)
  updatedAt   DateTime      @default(now()) @updatedAt @db.Timestamp(0)
  source      Source[]      @relation("source_fk_1")
  // translation Translation[] @relation("translation_fk_2")
}

model Source {
  id          Int           @id @default(autoincrement())
  userId      Int         
  user        User          @relation(name: "source_fk_1", fields: [userId], references: [id])
  text        String        @db.Text
  translation Translation[] @relation("translation_fk_1")
}

model Translation {
  id          Int           @id @default(autoincrement())
  sourceId    Int
  source      Source        @relation(name: "translation_fk_1", fields: [sourceId], references: [id])
  // userId      Int
  // user        User          @relation(name: "translation_fk_2", fields: [userId], references: [id])
  locale      String        @db.VarChar(8)
  text        String        @db.Text
}

・A. mysqlにマイグレーション実行ユーザを作成(手動でする場合)

docker コンテナ名を確認する

 docker ps

docker コンテナの中に入る

docker exec -it <コンテナ名> bash

mysql ユーザを作成する

mysql -uroot -p mysql
create user translate_api_db@localhost identified by 'db_password';
grant create, alter, drop, references on *.* to translate_api_db;

・B. mysqlにマイグレーション実行ユーザを作成(dockerコンテナ初回実行時に自動でする場合)

以下のようにするとdockerコンテナの初回起動時に ./db/initdb.d/initdb.sql を実行してマイグレーションに必要なユーザーを作成することができます

mkdir db
cd db
mkdir initdb.d
cd initdb.d
vi initdb.sql

initdb.sql を以下の内容で保存します

grant select, insert, update, delete, create, alter, drop, references on *.* to 'myapp_api_db'@'%' with grant option;

docker 起動

docker compose up

うまくいかない場合は次のようなエラーがでているはずです

[Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sql
myapp-api-db  | ERROR 1410 (42000) at line 1: You are not allowed to create a user with GRANT
myapp-api-db exited with code 1

・マイグレーションを実行

npx prisma migrate dev

マイグレーションファイル名入力を促されるので次のように 行った操作を簡単に入力します。(実行日時は自動的にセットされます)

add_tables

実行すると「DBへテーブルが作成され」「実行されたsql文が次のファイル名にて保存され」ます

・ テーブルが作成されたことを確認

MySQLを操作するアプリを起動するか 次のコマンドから確認することもできます

npx prisma studio

http://localhost:5555

マイグレーションファイルを作成せずに、同期する

マイグレーションファイルを生成せず、スキーマを同期する
npx prisma migrate devを実行すると、毎回スキーマファイルが作成されるので、
開発時など頻繁に変更するときには少しめんどくさい。

開発用のコマンドもあり、npx prisma db pushを使うと、
マイグレーションファイルを生成せず同期できる。

いろいろ試して、変更点が整理できたら、
マイグレーションファイルを作成するのがよい感じ。

引用 : https://www.memory-lovers.blog/entry/2021/10/13/113000

● 4. Prisma のDBシーダーを実行してテストデータを投入する

・シーダーファイルの作成

ファイル名 /prisma/seed.ts を作成します。

vi prisma/seed.ts

prisma/seed.ts を 以下の内容で保存します

import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();

const userData: Prisma.UserCreateInput[] = [
  {
    email: 'test@user.com',
    name: 'テスト太郎',
    createdAt: new Date(),
    updatedAt: new Date(),
    source: {
      create: [
        {
          text: 'おはようございます。今日は晴れていますね。',
          translation: {
            create: [
              {
                locale: 'en',
                text: "Good morning. It's a sunny day today.",
              },
              {
                locale: 'pt_BR',
                text: 'Bom dia. Hoje está ensolarado.',
              },
            ],
          },
        },
        {
          text: '昨日の晩御飯は誰と何を食べましたか?',
          translation: {
            create: [
              {
                locale: 'en',
                text: 'Who did you have dinner with and what did you eat yesterday?',
              },
              {
                locale: 'pt_BR',
                text: 'Com quem você jantou e o que comeu ontem à noite?',
              },
            ],
          },
        },
      ],
    },
  },
];

async function main() {
  console.log(`Start seeding ...`);
  for (const u of userData) {
    const user = await prisma.user.create({
      data: u,
    });
    console.log(`Created user with id: ${user.id}`);
  }
  console.log(`Seeding finished.`);
}

main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });

create以外にも次のような便利なメソッドがあります
引用 : https://zenn.dev/takky94/scraps/448d1ad68d969a

・データが存在すれば update、存在しなければ create がしたい 
 → Upsert

・あるモデルのcreateやupdate時、そのモデルの関係モデルにデータが存在すればupdate、しなければcreateしたい
 → connectOrCreate

・package.json へ追加

package.json へ次のコードを追記します。 scripts の一番最後の行に入れておくといいでしょう。

  "scripts": {
    ...................
    "seed": "ts-node prisma/seed.ts"
  },

・シーダーを実行する

npm run seed

または 直接ファイルを実行してもokです

npx ts-node prisma/seed.ts 

● 5. prismaを使用してDBからデータ取得する

DBの構成を自動でPrisma Schemaに渡す

graphql のコードも自動生成したい場合は、こちらのパッケージをインストールしておきます
node.js|プログラムメモ

npx prisma db pull
npx prisma generate

(実行すると 型のサポートを受けれるようになります)

.env の代わりに .env.development を 使用している場合は、こちらのコマンドを実行します

npx dotenv -e .env.development --  npx prisma db pull
npx dotenv -e .env.development --  npx prisma generate

サンプルスクリプトを作成する

(ユーザ一覧とその先のリレーション、すべてを取得するスクリプトを記述します)

vi prisma/sample-script.ts

prisma/sample-script.ts

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function main() {
  const users = await prisma.user.findMany({
    include: {
      source: {
        include: {
          translation: true,
        },
      },
    },
  });
  console.log(JSON.stringify(users, null, 4));
}

main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });

include で リレーション先テーブルの すべてのカラムを取得しています

スクリプトを 実行する

npx ts-node prisma/sample-script.ts 

結果

[
    {
        "id": 1,
        "email": "test@user.com",
        "name": "テスト太郎",
        "createdAt": "2023-03-27T04:20:47.000Z",
        "updatedAt": "2023-03-27T04:20:47.000Z",
        "source": [
            {
                "id": 1,
                "userId": 1,
                "text": "おはようございます。今日は晴れていますね。",
                "createdAt": "2023-03-27T04:20:47.000Z",
                "updatedAt": "2023-03-27T04:20:47.000Z",
                "translation": [
                    {
                        "id": 1,
                        "sourceId": 1,
                        "locale": "en",
                        "text": "Good morning. It's a sunny day today.",
                        "createdAt": "2023-03-27T04:20:47.000Z",
                        "updatedAt": "2023-03-27T04:20:47.000Z"
                    },
                    {
                        "id": 2,
                        "sourceId": 1,
                        "locale": "pt_BR",
                        "text": "Bom dia. Hoje está ensolarado.",
                        "createdAt": "2023-03-27T04:20:47.000Z",
                        "updatedAt": "2023-03-27T04:20:47.000Z"
                    }
                ]
            },
            {
                "id": 2,
                "userId": 1,
                "text": "昨日の晩御飯は誰と何を食べましたか?",
                "createdAt": "2023-03-27T04:20:47.000Z",
                "updatedAt": "2023-03-27T04:20:47.000Z",
                "translation": [
                    {
                        "id": 3,
                        "sourceId": 2,
                        "locale": "en",
                        "text": "Who did you have dinner with and what did you eat yesterday?",
                        "createdAt": "2023-03-27T04:20:47.000Z",
                        "updatedAt": "2023-03-27T04:20:47.000Z"
                    },
                    {
                        "id": 4,
                        "sourceId": 2,
                        "locale": "pt_BR",
                        "text": "Com quem você jantou e o que comeu ontem à noite?",
                        "createdAt": "2023-03-27T04:20:47.000Z",
                        "updatedAt": "2023-03-27T04:20:47.000Z"
                    }
                ]
            }
        ]
    }
]

prismaの O/Rマッピングリレーション一覧

https://medium.com/yavar/prisma-relations-2ea20c42f616

● 6. NestJSの REST API エンドポイントの作成

NestJS テストの確認

テストを一通り実行して確認しておきます

npm run test
npm run test:e2e
npm run test:cov

エンドポイント作成の練習

npx nest generate resource users --dry-run

(実際に実行するときは、 --dry-run を外します)

エンドポイント /users 作成

npx nest generate resource users
( REST API  を選択してエンター )

http://localhost:3000/users へアクセス

npm run start:dev

http://localhost:3000/ へ アクセスして Hello,world! が表示されることを確認します
http://localhost:3000/users へ アクセスして This action returns all users が表示されることを確認します

src/users/users.service.ts の修正

以下のように書き換えて保存します

import { Injectable } from '@nestjs/common';
import { CreateGuserInput } from './dto/create-guser.input';
import { PrismaService } from '../prisma.service';

@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  async findAll() {
    return this.prisma.user.findMany({
      include: {
        source: true,
      },
    });
  }

  async findOne(id: number) {
    return this.prisma.user.findFirst({ where: { id: id } });
  }

  create(createGuserInput: CreateGuserInput) {
    return this.prisma.user.create({
      data: {
        email: createGuserInput.email,
        name: createGuserInput.name,
      },
    });
  }
}

http://localhost:3000/users へ アクセスしてユーザ一覧が返ってくることを確認します。

● 7. NestJSの graphql エンドポイントの作成(code first)

graphql関連パッケージのインストール

npm i @nestjs/graphql @nestjs/apollo graphql

NestJS の起動

schema.gql を自動生成させるために、あらかじめサーバを起動しておきます

npm run start:dev

起動後に必ずエラーが出ていないことを確認しましょう。

graphql 関連ファイルの自動生成

npx nest generate resource gusers

(GraphQL (code first) を選択してエンターを押します)

以下のファイルが新規作成または更新されます

CREATE src/gusers/gusers.module.ts (238 bytes)
CREATE src/gusers/gusers.resolver.spec.ts (545 bytes)
CREATE src/gusers/gusers.resolver.ts (1181 bytes)
CREATE src/gusers/gusers.service.spec.ts (467 bytes)
CREATE src/gusers/gusers.service.ts (653 bytes)
CREATE src/gusers/dto/create-gguser.input.ts (198 bytes)
CREATE src/gusers/dto/update-gguser.input.ts (251 bytes)
CREATE src/gusers/entities/gguser.entity.ts (189 bytes)
UPDATE src/app.module.ts (454 bytes)

また schema.gql も生成されます。

1. Entity の修正

src/gusers/entities/guser.entity.ts を修正します

import { ObjectType, Field, Int } from '@nestjs/graphql';

@ObjectType()
export class Guser {
  @Field(() => Int, { description: 'Example field (placeholder)' })
  exampleField: number;
}

↓ 以下のように修正します

import { ObjectType, Field, ID } from '@nestjs/graphql';
import { Source } from '../../sources/entities/source.entity';

@ObjectType()
export class Guser {
  @Field(() => ID)
  id: number;

  @Field()
  name: string;

  @Field()
  email: string;

  @Field()
  createdAt: Date;

  @Field()
  updatedAt: Date;

  @Field(() => [Source], { nullable: true })
  source?: Array<Source>;
}

リレーション source も返すように設定します

npx nest generate resource sources

src/sources/entities/source.entity.ts

import { ObjectType, Field, ID } from '@nestjs/graphql';

@ObjectType()
export class Source {
  @Field(() => ID)
  id: number;

  @Field()
  useId: number;

  @Field()
  text: string;

  @Field()
  createdAt: Date;

  @Field()
  updatedAt: Date;

  @Field(() => [Source], { nullable: true })
  source?: Array<Source>;
}

2. Resolver / Service の修正

クエリの操作を行うリゾルバ / サービスを修正します。

リゾルバ:一旦そのままです。変更しません。

サービス: 以下のように修正します

src/gusers/gusers.service.ts

  findAll() {
    return `This action returns all gusers`;
  }

findOne(id: number) {
    return `This action returns a #${id} guser`;
  }

↓ このように findAll() , findOne()メソッドを入れ替えます

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
  async findAll() {
    return prisma.user.findMany({
      include: {
        source: true,
      },
    });
  }

  async findOne(id: number) {
    return prisma.user.findFirst({ where: { id: id } });
  }

graphqlクエリの確認

http://localhost:3000/graphql へアクセスして

{
  gusers{
    id,name,email,createdAt,updatedAt
  }
}

クエリを投げて、正しくデータが返ってくることを確認します。

● @Query の名前を変更する

getSourcesgetMySources に名前変更するには以下のようにします

  @Query(() => [Source])
  getSources(@CurrentUser() firebaseUser: CurrentFirebaseUserData) {
    ........
  }

方法1. { name: 'getMySources' } で変える

  @Query(() => [Source], { name: 'getMySources' })
  getSources(@CurrentUser() firebaseUser: CurrentFirebaseUserData) {
    ........
  }

方法2. メソッド名を変える

  @Query(() => [Source])
  getMySources(@CurrentUser() firebaseUser: CurrentFirebaseUserData) {
    ........
  }

その他参考: omar-dulaimi/prisma-class-validator-generator: Prisma 2+ generator to emit typescript models of your database with class validator
Prisma でメソッドはやせない問題どうしたらいいんだ | きむそん.dev

添付ファイル1
No.2305
06/02 09:43

edit

添付ファイル

NestJS + Prisma

● NestJS アプリの初期化

nest new my-api

● DB (MySQL)を docker で用意する

vi docker-compose.yml

version: "3.8"
services:
  db:
    container_name: translate-api-db
    image: mysql:8.0
    restart: always
    ports:
      - 13306:3306
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: translate_api_db
      MYSQL_USER: translate_api_db
      MYSQL_PASSWORD: db_password
    volumes:
      - ./db/data:/var/lib/mysql

docker で mysqlの起動

docker compose up

● PrismaでDB接続の作成

(Prisma) 1. ・Prisma CLIとPrisma Clientをインストール

cd my-api
npm install @prisma/client
npm install --save-dev prisma

(Prisma) 2. ・Prismaのセットアップ

npx prisma init

ディレクトリ直下に 2つのファイル prisma/schema.prisma.env が生成されます

(Prisma) 3. ・prisma/schema.prisma の 変更

データベースがMySQLの場合は以下のように修正します

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

(Prisma) 4. ・.env の 変更

.env 例えば以下のように設定します

DATABASE_URL="mysql://adi_db:adi_db_pass@localhost:3307/adi_db?schema=public"

コンテナ名を指定する場合は

DATABASE_URL="mysql://adi_db:adi_db_pass@docker-nest-db-1/adi_db?schema=public"

● Prisma のDBマイグレーション

schema.prisma に テーブル情報を追記します

model User {
  id          Int           @id @default(autoincrement())
  email       String        @db.Text
  name        String        @db.Text
  createdAt   DateTime      @default(now()) @db.Timestamp(0)
  updatedAt   DateTime      @default(now()) @updatedAt @db.Timestamp(0)
  source      Source[]      @relation("source_fk_1")
}

model Source {
  id          Int           @id @default(autoincrement())
  userId      Int         
  user        User          @relation(name: "source_fk_1", fields: [userId], references: [id])
  text        String        @db.Text
  translation Translation[] @relation("translation_fk_1")
}

model Translation {
  id          Int           @id @default(autoincrement())
  sourceId    Int
  source      Source        @relation(name: "translation_fk_1", fields: [sourceId], references: [id])
  locale      String        @db.VarChar(8)
  text        String        @db.Text
}

マイグレーション時に仮のテーブルを作成するので権限を付与します。

d exec -it <コンテナ名> bash
mysql -uroot -p mysql
create user translate_api_db@localhost identified by 'db_password';
grant create, alter, drop, references on *.* to translate_api_db;

実行後は exit でコンテナから抜けます。

次のコマンドでマイグレーションを実行します

npx prisma migrate dev

マイグレーションファイル名を入力されるので、次のように 行った操作を簡単に入力します。(実行日時は自動的にセットされます)

add_tables

実行すると

  1. DBへテーブル Todo が作成されます
  2. 実行されたsql文が次のファイル名にて保存されます prisma/migrations/20230321072643_add_tables/migration.sql

・ マイグレーションの ロールバックコマンドは用意されていません

本番サーバーではロールバックすることがない(修正する場合は、修正するバイブレーションを追加すると言う方式)ため

・ マイグレーション関連コマンド

# データベースをリセットしDBへ反映
npx prisma migrate reset

# マイグレーションのステータスチェック
npx prisma migrate status

# マイグレーションの実行・DBへ反映
npx prisma migrate deploy

# 型情報の生成
npx prisma generate

・ Prisma Studioの起動

npx prisma studio

package.json に追加しておくと、コマンド覚えなくていいです。

  "scripts": {
     .............
    "prisma:studio": "dotenv -e .env.development -- npx prisma studio",
  }
"prisma:studio": "dotenv -e .env.development -- npx prisma studio",

・マイグレーションのロールバック

Prisma2でDB Resetを無理やり行う

● Prisma のDBシーダーの実行

・シーダーファイルの作成

ファイル名 /prisma/seed.ts を作成します。

vi prisma/seed.ts

prisma/seed.ts を 以下の内容で保存します

import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();

const insertData: Prisma.TodoCreateInput[] = [
  {
    title: 'タイトル01',
    description: '説明01',
    status: 'AAA01',
    createdAt: new Date(),
    updatedAt: new Date(),
  },
  {
    title: 'タイトル02',
    description: '説明02',
    status: 'AAA02',
    createdAt: new Date(),
    updatedAt: new Date(),
  },
];

const transfer = async () => {
  const coffeeShops = insertData.map((c) => {
    return prisma.todo.create({ data: c });
  });
  return await prisma.$transaction(coffeeShops);
};

const main = async () => {
  await transfer();
};

main()
  .then(() => {
    console.log('seed finished.');
  })
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

・package.json へ追加

package.json 次のコードを追記します

  "prisma": {
    "seed": "ts-node ./prisma/seed.ts"
  },  

・シーダーを実行する

npx prisma db seed

または 直接ファイルを実行してもokです

npx ts-node prisma/seed.ts 
No.2301
05/08 12:05

edit

Macに便利な node.js バージョン自動切り替え nodenv をインストールする

● まず anyenv をインストール

ターミナルから以下を実行します。

brew install anyenv
echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
echo $SHELL -l

一旦ターミナルを終了して、再度起動。

メッセージが出るので次のコマンドを実行

anyenv install --init

・2. anyenv を使って nodenv をインストール

anyenv install nodenv
exec $SHELL -l

これでインストールは完了です。

・3. インストール可能な node.js の全てのバージョンを表示する

nodenv install -l

・4. 例) node.js version 18.14.0 をインストールする

nodenv install 18.14.0

M1/M2 Macには バージョン16以上しかインストールすることができませんのでご注意ください

・5. 例) システム全体で使用する node.js のバージョンを 18.14.0 にする

nodenv global 18.14.0

・6. あるフォルダ以下では 使用する node.js のバージョンを 6.3.0 にする

フォルダ「my-project」以下は v16.19.0 を使用するようにセットします

cd my-project
nodenv install  16.19.0
nodenv local  16.19.0

これで、フォルダ「my-project」以下は必ず 16.19.0 になります。

・6. node.js が使えることを確認する

node -v

正しくバージョンが表示されればOKです。

● マシンにインストールした node の全バージョンを表示する

nodenv versions

● nodenv のインストール時に default-packages file not found と表示される時の修正方法

touch $(nodenv root)/default-packages

引用 : https://bit.ly/2VGjCxp

● nodenvのインストール可能なリストを更新する

cd ${NODENV_ROOT}
git pull

cd ${NODENV_ROOT}/plugins/node-build/
git pull

● Node.js Global pathをセットする

~/.bash_profile に記述しておきます

# Node.js Global pathをセット
node_global_path=$(npm root -g)
echo ${node_global_path}
export NODE_PATH=${node_global_path}

● (古いやり方) node.jsインストール時に同時に自動的にyarnもインストールしてくれるように設定する

(この方法は現在となっては古いやり方です)

yarn をインストールする新しいやり方

corepack enable yarn

yarn自動インストールの設定

mkdir -p "$(nodenv root)/plugins"
git clone https://github.com/pine/nodenv-yarn-install.git "$(nodenv root)/plugins/nodenv-yarn-install"

これを行うだけでnode.jsインストール時に同時に自動的にyarnもインストールしてくれます

yarn を手動でインストールする 場合はこちらのコマンド実行

npm install --global yarn

● package.json の engines に自動でバージョンを合わせてくれるプラグイン

package.json

{
  ........ ,
  "engines": {
    "node": "20.x"
  }
}

インストール

git clone https://github.com/nodenv/nodenv-package-json-engine.git $(nodenv root)/plugins/nodenv-package-json-engine

https://github.com/nodenv/nodenv-package-json-engine

No.1186
03/19 17:04

edit

node.jsのバージョン管理 volta

● install

curl https://get.volta.sh | bash

(インストール後にターミナルを終了して、再起動します)

● volta を 使って node.js の特定バージョンをインストールする

バージョン 14.17.0 をインストールする例

volta install node@14.17.0

● volta を 使って node.js の特定メジャーバージョンの最新版をインストールする

バージョン 18系の 最新をインストールする例

volta install node@18

● volta を使ってインストールしたnode.jsのすべてのバージョンを表示させる

volta list all

(インストール後にターミナルを終了して、再起動します)

● インストールしたnode.jsのバージョンの確認

node -v

● volta をアンインストールする

1. volta 本体と voltaを使って インストールしたnode.jsのアンインストール

rm -rf ~/.volta

2. bash_profile から以下のパスを削除

export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"
No.2281
02/07 17:54

edit

node http-server でさくっとローカルサーバーを立てる

● npx を使用する場合

サーバーを立てたいディレクトリに移動した後

npx http-server

これだけで okです。

● npm install を使用する場合

サーバーを立てたいディレクトリに移動した後

npm install http-server
./node_modules/http-server/bin/http-server

これだけで okです。

● オプション

例えばポート番号を変更するには以下のように指定します

./node_modules/http-server/bin/http-server  --port 50001
Command Description Defaults
-p or --port Port to use. Use -p 0 to look for an open port, starting at 8080. It will also read from process.env.PORT. 8080
-a Address to use 0.0.0.0
-d Show directory listings true
-i Display autoIndex true
-g or --gzip When enabled it will serve ./public/some-file.js.gz in place of ./public/some-file.js when a gzipped version of the file exists and the request accepts gzip encoding. If brotli is also enabled, it will try to serve brotli first. false
-b or --brotli When enabled it will serve ./public/some-file.js.br in place of ./public/some-file.js when a brotli compressed version of the file exists and the request accepts br encoding. If gzip is also enabled, it will try to serve brotli first. false
-e or --ext Default file extension if none supplied html
-s or --silent Suppress log messages from output
--cors Enable CORS via the Access-Control-Allow-Origin header
-o [path] Open browser window after starting the server. Optionally provide a URL path to open. e.g.: -o /other/dir/
-c Set cache time (in seconds) for cache-control max-age header, e.g. -c10 for 10 seconds. To disable caching, use -c-1. 3600
-U or --utc Use UTC time format in log messages.
--log-ip Enable logging of the client's IP address false
-P or --proxy Proxies all requests which can't be resolved locally to the given url. e.g.: -P http://someurl.com
--proxy-options Pass proxy options using nested dotted objects. e.g.: --proxy-options.secure false
--username Username for basic authentication
--password Password for basic authentication
-S, --tls or --ssl Enable secure request serving with TLS/SSL (HTTPS) false
-C or --cert Path to ssl cert file cert.pem
-K or --key Path to ssl key file key.pem
-r or --robots Automatically provide a /robots.txt (The content of which defaults to User-agent: *\nDisallow: /) false
--no-dotfiles Do not show dotfiles
--mimetypes Path to a .types file for custom mimetype definition
-h or --help Print this list and exit.
-v or --version Print the version and exit.
No.2273
01/17 11:55

edit

AWS S3 get signed URL

● パッケージのインストール

npm install @aws-sdk/client-s3
npm install @aws-sdk/s3-request-presigner

● signed URL を取得する

geturl.js

import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
// ************************
const accessKeyId = 'XXXXX'; 
const secretAccessKey = 'XXXXX';
const region = 'XXXXXXXXXX';
const bucket = 'my-bucket';
const key = 'my-file.png';
// ************************
const client = new S3Client({
  region: region,
  credentials: {
    accessKeyId: accessKeyId,
    secretAccessKey: secretAccessKey,
  },
});

const url = await getPresignedUrl(client, bucket, key, 60*60*8);
console.log( `the url is ${url}` );

async function getPresignedUrl(client, bucket, key, expiresIn) {
  const objectParams = {
    Bucket: bucket,
    Key: key,
  };

  const url = await getSignedUrl(client, new GetObjectCommand(objectParams), { expiresIn });
  return url
};

● package.json で esmoduleを読み込めるようにする

package.json

"type": "module"

● 実行する

node geturl.js
No.2269
01/05 13:34

edit

jestのモック

● モックの Test Double 分類

一言でモックと言っても、実は以下のように分類されます.
dummy , fake , spy, stub, mock

● Jest で使用するTest Doubleは?

Jestでは 大きく分けて、次の2つ jest.mock , jest.spyOn を使います。

No.2262
12/15 16:50

edit

typescript prettier おすすめ設定

.prettierrc.js

module.exports = {
  arrowParens: 'always',
  bracketSameLine: false,
  bracketSpacing: true,
  htmlWhitespaceSensitivity: 'css',
  insertPragma: false,
  jsxSingleQuote: true,
  printWidth: 120,
  proseWrap: 'preserve',
  quoteProps: 'as-needed',
  requirePragma: false,
  semi: false,
  singleQuote: true,
  tabWidth: 2,
  trailingComma: 'none',
  useTabs: false
}
 arrowParens : alwaysの時 Arrow関数の引数が1つでも括弧がつく。avoid の時付かない
No.2246
11/28 13:46

edit

JWTまとめとnode.js で JWTを検証(verify)する auth0 / node-jsonwebtoken

● JWTをざっくり説明すると以下の通り

・ヘッダ、ペイロード(データ本体)、署名をドットでつないで Base64 エンコードしたもの。

・中身は Base64エンコードしただけなので、ペイロードの中身は誰でも見ることができる。

・電子署名を使用して、ペイロードが改ざんされていないか?、正規の発行元以外から勝手に発行されたものかどうか?などを検証することができる。

・よく「JWT脆弱性」というワードが出てくるがJWT自体に脆弱性はない。
(正しくは「JWTを使用した認証システムに脆弱性が存在することがある」)

● node.js で jwt を扱うパッケージ auth0 / node-jsonwebtoken

https://github.com/auth0/node-jsonwebtoken

● node.js 以外の他の言語で jwt を扱うパッケージ

https://jwt.io/libraries

● JWTの署名のアルゴリズム

alg Parameter Value Digital Signature or MAC Algorithm
HS256 HMAC using SHA-256 hash algorithm
HS384 HMAC using SHA-384 hash algorithm
HS512 HMAC using SHA-512 hash algorithm
RS256 RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
RS384 RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm
RS512 RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm
PS256 RSASSA-PSS using SHA-256 hash algorithm (only node ^6.12.0 OR >=8.0.0)
PS384 RSASSA-PSS using SHA-384 hash algorithm (only node ^6.12.0 OR >=8.0.0)
PS512 RSASSA-PSS using SHA-512 hash algorithm (only node ^6.12.0 OR >=8.0.0)
ES256 ECDSA using P-256 curve and SHA-256 hash algorithm
ES384 ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 ECDSA using P-521 curve and SHA-512 hash algorithm
none No digital signature or MAC value included

引用: https://github.com/auth0/node-jsonwebtoken

jwtのアルゴリズムを知る方法

https://jwt.io/ にトークンを入力するとヘッダに

{
  "alg": "RS256",
  "kid": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "typ": "JWT"
}

と表示されます。("alg": "RS256" なので RS256)

HS256とRS256の違いは何ですか?

HS256

HS256( _ hmac _ with SHA-256)は、 対称アルゴリズム で、2者間で共有される唯一の(秘密)キーを持ちます。署名の生成と検証の両方に同じキーが使用されるため、キーが危険にさらされないように注意する必要があります。

RS256

RS256( SHA-256 を持つRSA署名)は 非対称アルゴリズム で、公開鍵と秘密鍵のペアを使用します。アイデンティティプロバイダには署名の生成に使用される秘密(秘密)鍵があります。 JWTの利用者は、署名を検証するための公開鍵を入手します。秘密鍵ではなく公開鍵を保護する必要がないため、ほとんどのアイデンティティプロバイダは、(通常メタデータURLを通じて)消費者が簡単に入手して使用できるようにしています。

HS256は危険です。RS256を使いましょう。
Google Firebase Auth , Amazon AWS Cognito の accessToken は RS256 です。

● サーバーサイドでクライアントから受け取った「jwtの検証」

サーバーサイドではクライアントから受け取ったjwtの以下の点を検証します

・jwtのペイロード(データ本体)に「改ざんがあるかどうか?」の検証
・想定している発行元以外から「勝手に発行されたjwtかどうか?」の検証
・jwtの有効期限が「有効期限内であるかどうか?(jwtが有効か?)」の検証

● node-jsonwebtoken を使った検証(verify)のサンプル

Google Firebase の Authentication で受け取ったトークンを検証します。
検証に使用する公開鍵はGoogleのサーバから取得します。

npm install  jsonwebtoken axios
const axios = require("axios").default;
const jwt = require("jsonwebtoken");

/**
 * 
 * @param {string} accessToken 
 */
const verifyJWT = async (accessToken) => {
  const kid = getKidFromJwtHeader(accessToken);
  const publicKey = await getPublicKeyFromFirebase('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', kid);

  jwt.verify(accessToken, publicKey, function (err, decoded) {
    if (err) {
      console.log('● verifyでエラーが発生');
      console.log(err);
    }
    else if (decoded) {
      console.log('● verify OK');
      console.log(decoded);
    }
    else {
      console.log('● JWTのデコードができませんでした');
    }
  });
}

/**
 * jwtからヘッダの中のkidを取得
 * @param {string} token 
 * @returns {string} key 
 */
const getKidFromJwtHeader = (token) => {
  const jwtHeader = getJwtHeader(token)
  const obj = JSON.parse(jwtHeader)
  return obj.kid
}

/**
 * jwtからヘッダを取得
 * @param {string} token 
 * @returns {string} key 
 */
const getJwtHeader = (token) => {
  const tokenHeader = token.split('.')[0]
  const jwtHeader = Buffer.from(tokenHeader, 'base64').toString()
  return jwtHeader
}

/**
 * Firebaseのjwt検証用公開鍵を取得する
 * @param {string} url 
 * @param {string} kid 
 * @returns {string} key 
 */
const getPublicKeyFromFirebase = async (url, kid) => {
  const res = await axios.get(url)
  const publicKeys = res.data
  const key = publicKeys[kid]
  return key
}


verifyJWT("<検証したいトークン>");

● クライアント(WEBブラウザ)で受け取ったJWTの保存先

○「HTTP OnlyでSecureなCookieに保存する」
×「LocalStorageに保存する」 ( local storageはあらゆるJavaScriptコードから自由にアクセスできてしまいます )

引用: https://bit.ly/3fMiqVy

● 他にjwtを取り扱う上で気をつけること

・共通鍵のアルゴリズムは選択しない
・鍵の長さは256bit以上とする
・検証をせずにデコードだけすると、改ざんまたは勝手に発行されたトークンを見ている可能性がある
(なので alg=none を使用するのはありえない)
・認証NGとなるjwtでも、データの中身は誰でも見れるので機密情報はjwtには含めない
No.2222
10/07 14:17

edit

vanilla js の jest で impor export を使用する

vanilla js で jest で テストファイルの中にimpor export を使用すると次のようなエラーが表示されることがあります

   Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

これを node.js 変換によって回避する方法はこちらです

● 1. シェルから次のコマンドを実行する

export NODE_OPTIONS=--experimental-vm-modules

● 2. package,json に追加

"type": "module" を追加します。

{
  "devDependencies": {
    "jest": "^28.1.2"
  },
  "type": "module"
}

● jest.config.js を esモジュール形式にする

module.exports = {
  testMatch: ['<rootDir>/**/*.test.js'],
};

   ↓

export default {
  testMatch: ['<rootDir>/**/*.test.js'],
};

以上です。

● jest の実行

npm test

または

npx jest

してみましょう

No.2182
07/01 13:48

edit

Windowsに便利な node.js バージョン自動切り替え nodenv をインストールする

● 1. WSL2のインストール

WSL2をインストールしたら、コマンドプロンプトではなく Windowsターミナルから Ubuntu を選択して、unixを起動します。

● 2. homebrew のインストール

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/home/myuser/.linuxbrew/bin/brew shellenv)"' >> /home/myuser/.bash_profile
eval "$(/home/myuser/.linuxbrew/bin/brew shellenv)"

● 3. anyenvのインストール

brew install anyenv
anyenv init
echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
exec $SHELL -l
anyenv install --init

● 4. nodenvのインストール

anyenv install nodenv
exec $SHELL -l
nodenv --version

● 5. Yarn プラグイン のインストール

node.jsインストール時に同時に自動的にyarnもインストールしてくれるように設定する

mkdir -p "$(nodenv root)/plugins"
git clone https://github.com/pine/nodenv-yarn-install.git "$(nodenv root)/plugins/nodenv-yarn-install"

● 6. Node.js のインストール

nodenv install 16.13.2
nodenv global 16.13.2
node -v
No.2163
09/25 11:43

edit

Macのローカルマシンの Express で https:// なサーバを立ち上げて Google Chromeからアクセスする

● 1. Macのローカルマシンに Express で https:// なサーバを立ち上げる

server.js

'use strict';
const express = require('express');
const serveIndex = require('serve-index');
const fs = require('fs');
const port = 3000;
const app = express();
const server = require('https').createServer({
    key: fs.readFileSync('./privatekey.pem'),
    cert: fs.readFileSync('./cert.pem'),
}, app)
app.use(express.static('.'));
app.use(serveIndex('.', {icons: true}));
server.listen(port, () => console.log(`Server Started    https://127.0.0.1:${port}`))

nodeモジュールのインストール

npm init -y
npm i -S express serve-index

cert.pem , privatekey.pem の作成

openssl req -x509 -newkey rsa:2048 -keyout privatekey.pem -out cert.pem -nodes -days 365

サーバー起動

node server.js


● 2. Google Chromeからオレオレ証明書のhttpsサーバーにアクセスする

以下の手順でGoogle Chromeからアクセスできるようにします


・1. Firefoxから証明書の名前を確認する。


・2. Safariでアクセスしてキーチェーンに証明書を追加する。


・3. キーチェーンアプリから常に信頼する設定にする。

添付ファイル1
添付ファイル2
添付ファイル3
No.2077
10/15 14:59

edit

添付ファイル

Windows の node.js に module.paths を追加する

● Windows の node.js に module.paths を追加する

1. 現在の node.js の module.paths を確認する

node
module.paths

( Ctrl+C 2回押して抜けます。)

2. 環境変数 NODE_PATH にパスを追加する

SET NODE_PATH=C:\laragon\bin\nodejs\node-v14\node_modules

3. 再度 node.js の module.paths を確認する

node
module.paths
No.2045
09/22 20:26

edit

reactアプリとfirebase

● reactアプリの初期化(TypeScript)

reactアプリの初期化(TypeScript)

npx create-react-app test-app --template typescript

reactアプリの初期化(JavaScript + TailwindCSS)

npx create-next-app my-first-next-with-tailwind -e with-tailwindcss

● firebase の 初期化

firebase-tools のインストール

npm install -g firebase-tools

バージョン8を指定して入れる場合は以下のようにインストールします
(バージョン9からファイルサイズが小さくなったため 9がオススメですが。)

npm install -g firebase-tools@8
firebase --version
8.20.0

一旦ログアウトしてから再度ログイン

firebase-tools のバージョン違いによりエラーが出ることがあるので一旦ログアウトを行ってから再度ログインします

 firebase logout
 firebase login

プロジェクト一覧の表示

firebase projects:list

firebaseの設定

firebase init

例えば以下のように質問に答えます

? Which Firebase features do you want to set up for this directory?
→ (ホスティングを行う場合) ◯ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
→ (Firestoreを使う場合) ◯ Firestore: Configure security rules and indexes files for Firestore


? What do you want to use as your public directory? 
→ build

? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) 
→ y

? Set up automatic builds and deploys with GitHub? (y/N) n
→ n

● firebase へデプロイ

npm run build
firebase deploy
Hosting URL: https://my-app-xxxxx.web.app

● firebaseへデプロイしたアプリを削除

firebase hosting:disable

URLにアクセスしてNOT FOUNDになっていることを確認します。

No.2036
10/01 15:02

edit

electron で アプリのアイコンを設定する

● electron で アプリのアイコンを設定する

・electron-builder を使用している場合

  "build": {
    "mac": {
      "icon": "./icon.png"
    },
    "win": {
      "icon": "./icon.png"
    },
  }

・Electron-Forge v5 を使用している場合

package.json に以下の設定を追加します。 (アイコン画像は path/to/icon.icns に設置)

{
  ...
  "config": {
    "forge": {
      ...
      "electronPackagerConfig": {
        "icon": "path/to/icon.icns"
      },
      ...
    }
  }
}

・Electron-Forge v6: を使用している場合

package.json に以下の設定を追加します。 (アイコン画像は path/to/icon.icns に設置)

{
  ...
  "config": {
    "forge": {
      ...
      "packagerConfig": {
        "icon": "path/to/icon.icns"
      },
      ...
    }
  }
}

npm run package で再度ビルドを行います

なおアイコン画像の形式はMacの場合icns形式である必要があります。
こちらのウェブサイトからPing画像からicnsへ変換することができます

● cloudconvert

https://cloudconvert.com/

No.2019
11/30 14:44

edit

electron で URL や ローカルのフォルダを開く

● electron で URLを開く

const { shell } = require('electron')
shell.openExternal('http://localhost/')

● electron で ローカルのフォルダを開く

const { shell } = require('electron')
shell.openPath('/Users/hogehoge/テスト フォルダ/001');
No.1977
03/29 17:24

edit

electron + Vue.js で Hello World !

● 1. Vue アプリのインストールと起動の確認

vue create electron-vue-hello
cd cd electron-vue-hello
npm run serve

http://localhost:8080/ にアクセスして起動することを確認します

● 2. electron-builder のインストールと起動の確認

control + c でプロセスをkillして次のコマンドからインストールします

vue add electron-builder

package.json 次のコマンドが追加されたことを確認します

  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "electron:build": "vue-cli-service electron:build",
    "electron:serve": "vue-cli-service electron:serve",
    "postinstall": "electron-builder install-app-deps",
    "postuninstall": "electron-builder install-app-deps"
  },

● 3. electronアプリを実行する

npm run electron:serve

● 4. electronアプリをビルドする

npm run electron:build

ここまで来れました準備は全て完了です次はプログラムを記述していきましょう。

● 5. プログラムコードを記述する

npm run electron:build
No.1976
03/29 16:11

edit

electron アプリの記述方法

● electron アプリの記述個所参照方法

1. package.json の main

  "main": "src/index.js",

2. src/index.js

index.js の createWindow にイベントを記述していきます

const createWindow = () => {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
  });

  // and load the index.html of the app.
  mainWindow.loadFile(path.join(__dirname, 'index.html'));

  // Open the DevTools.
  mainWindow.webContents.openDevTools();
};


// ● 続けてイベントを記述 ↓

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});
No.1975
03/29 15:30

edit

electron アプリ で hello World !

● electron のインストール

npm install electron --save-dev

● electron アプリの作成

npx create-electron-app my-hello-app

● my-hello-app アプリの起動

cd my-hello-app
npm start

アプリが起動します

● メインプロセスとレンダラープロセス(View)とのデータのやり取り

touch src/renderer.js

renderer.js

const { ipcRenderer } = require('electron')
// DOM Ready
document.addEventListener("DOMContentLoaded", function () {
    /**
     * my-btn を押すとメインプロセスへデータを渡す
     */
    const btn = document.getElementById('my-btn')
    btn.addEventListener('click', () => {
        ipcRenderer.send('MessageMain', { message: 'レンダラーからメインプロセスへメッセージです。' });
        console.log( '● レンダラーからメッセージを送信しました' );
    });
});

index.htmlrenderer.js を読み込みます。

index.html

    <script src="renderer.js"></script>

index.js でメッセージを受けれるようにしておきます。

index.js

/**
 * レンダラープロセスからデータの受信 (MessageMain)
 *
 */
ipcMain.on('MessageMain', (event, arg) => {
  console.log( '● arg' );
  console.log( arg );
})
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600
  });

  ↓

  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    },
  });

● my-hello-app アプリのビルド

npm run make

out フォルダにアプリができます。 (ファイルサイズが大きいのは諦めましょう)

windows 32bit用にビルド

npx electron-forge make --arch=ia32

● electronアプリのビルドサイズを小さくする

こちらを clone してビルドしてみましょう。 https://github.com/electron-react-boilerplate/electron-react-boilerplate

● Electron製アプリの起動速度を1,000ミリ秒速くする方法

https://blog.craftz.dog/how-to-make-your-electron-app-launch-1000ms-faster-bcc2997fa1f7

● electronアプリの 配布用パッケージ.dmg インストーラを作成する

electron-builderのインストール

npm install electron-builder --save-dev

● パッケージ作成を実行する

・mac 64bit 用にアプリの 配布用パッケージ.dmg インストーラを作成する

node_modules/.bin/electron-builder --mac --x64

・windows 64bit 用にアプリの 配布用パッケージを作成する

npx electron-builder --win --x64

・windows 32bit 用にアプリの 配布用パッケージを作成する

npx electron-builder --win --ia32

● .env ファイルに設定情報を記述しelectronアプリから読み込む

npm install --save dotenv

「プロジェクトのトップディレクトリ」に .env ファイルを作成します

.env

HOGE=テスト文字列

index.js

//.envに記述したデータを読み込み
require('dotenv').config();

// 環境変数の表示
  console.log('● process.env');
  console.log( process.env );

ビルドしたときもビルドしたトップディレクトリに .env を設置する必要があります。

● デフォルトのブラウザでURLを開く

const { shell } = require('electron')
shell.openExternal('https://www.google.com/')

● electronアプリをシングルインスタンスを強制する

app.on('ready', createWindow);

   ↓

const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
   .... 何かしらの行いたい処理
  setTimeout( function(app) {
    app.exit();
  }, 1500, app );
} else {
  app.whenReady().then(() => {
   .... 何かしらの行いたい処理
  });
}
No.1973
11/10 16:17

edit

javascript , lodash の コレクション操作の便利メソッド

● lodashのインストール

yarn add lodash
yarn add --dev @types/lodash

または

npm install lodash
npm install --D @types/lodash

● javascript , lodash の コレクション操作の便利メソッド

次のようなコレクション系データがあったとします。

var stageItems = [
	{ id: 5, name: "山田" },
	{ id: 6, name: "斎藤" },
	{ id: 7, name: "朝倉" },
	{ id: 8, name: "神山" },
	{ id: 9, name: "山田" },
];

● 検索して1件のみ返す find

import find from 'lodash/find';

// id = 6 なアイテムを選択します
const item = find(stageItems, { id:6 });

● 検索して複数件返す filter

// name:'山田' なアイテムを全て返します
const item = _.filter(stageItems, { name:'山田' });

なお、コレクションだけでなくこのようなオブジェクトのオブジェクトでも検索可能です

var stageItems = {
	5: { id: 5, name: "山田" },
	6: { id: 6, name: "斎藤" },
	7: { id: 7, name: "朝倉" },
	8: { id: 8, name: "神山" },
	9: { id: 9, name: "山田" },
};

● オブジェクトの 指定したキー以外の要素を取得する omit

const obj1 = {
  id: "hoge",
  name: "ほげ",
  phone: "123-4567"
};

const newObj = _.omit(obj1, ["id", "phone"]); // {name: "ほげ"}

● 何番目の要素なのかを検索

// id = 6 が 何番目の要素なのかを検索 ( 0 が先頭 )
const added_i = this.stageItems.findIndex(({id}) => id === 6);
alert( added_i ); // 2番目の要素なので 1

● コレクションのソート

const sortedItems = sortBy(items, "name");

// 逆順にする場合は .reverse() を実行します
const sortedItems = sortBy(items, "name").reverse();

// =>
// [
//   { name: 'coffee', amount: 500 },
//   { name: 'juice', amount: 500 },
//   { name: 'lunch-a', amount: 1000 },
//   { name: 'lunch-b', amount: 1200 },
//   { name: 'smile', amount: 0 },
//   { name: 'suger' }
// ]

引用: https://hbsnow.dev/blog/js-sort/

● 削除

    itemDelete: function ( argId ) {
      alert("このidを削除します" + argId);
      this.stageItems = _.remove(this.stageItems, function (o) { return o.id !== argId; });
    },

● 配列の先頭 または 末尾から n個 削除

drop : 先頭 n個 削除
dropRight : 末尾 n個 削除

const data2 = _.dropRight(data, 3)    // 先頭 3つを削除
const data2 = _.dropRight(data, 2)    // 末尾 2つを削除

● コレクションからあるメンバだけを取り出したコレクションを作成して返す ( pluck )

const originalAttachFilesIDs = _.map(originalAttachFiles, 'fileID')
    const simpleItems = _.map(stageItems , (data) => {
      return { name: data.name }
    })

pluckメソッドは version 4 から .map() に統合されたようです。

// in 3.10.1
_.pluck(objects, 'a'); // → [1, 2]
_.map(objects, 'a'); // → [1, 2]

// in 4.0.0
_.map(objects, 'a'); // → [1, 2]

● オブジェクトのディープコピー

・JavaScriptではオブジェクトのコピーは参照コピーとなります。
・JavaScriptでは スプレッド構文やObject.assignを使ったコピーは1階層目は値がコピーされますが、
2階層以降にオブジェクトがあった場合 参照コピー となります。

オブジェクトをディープコピーしたい場合はこちらの cloneDeep()メソッドを使用します

const clonedItem = _.cloneDeep(item);

● javascript スプレッド構文とObject.assignを使ったコピーでは2階層目が参照コピーとなる例

const taguser1 = {
  id: 1,
  name: "hoge",
  tags:[
    {
      id:1,
      name: "Windows"
    } ,
    {
      id:2,
      name: "mac"
    }
  ]
};
const taguser2 = { ...taguser1 }; // 参照のコピー(オブジェクトをコピーした場合は参照がコピーされる)
const taguser3 = Object.assign({}, taguser1);

taguser1.id = 99999;
taguser1.tags[0].name = "ウィンドウズ"

console.log( '● taguser1' );
console.log( taguser1 );

console.log( '● taguser2' );
console.log( taguser2 );

console.log( '● taguser3' );
console.log( taguser3 );

結果

● taguser1 ​​​​​
{ id: 99999,
  name: 'hoge',
  tags: [ { id: 1, name: 'ウィンドウズ' }, { id: 2, name: 'mac' } ] }

● taguser2
{ id: 1,
  name: 'hoge',
  tags: [ { id: 1, name: 'ウィンドウズ' }, { id: 2, name: 'mac' } ] }

● taguser3
{ id: 1,
  name: 'hoge',
  tags: [ { id: 1, name: 'ウィンドウズ' }, { id: 2, name: 'mac' } ] }

参考 : https://qiita.com/waterada/items/62b32930d3c3668ff3d2

● lodash のバンドルサイズを小さくする

https://www.labnol.org/code/import-lodash-211117

import capitalize from 'lodash.capitalize';

const capitalizeFirstName = (name) => {
  const result = capitalize(name);
  console.log(response);
};

● memoize を 複数のキーで使用する

import memoize from 'lodoash/memoize';

const expensiveFunction = (input) => {
  return input * input;
};

const memoizedFunction = memoize(expensiveFunction);

console.log(memoizedFunction(5)); // Calculates the square of 5
console.log(memoizedFunction(5)); // Returns the cached value

 ↓

const multiply = (a, b) => {
  return a * b;
};

const resolver = (...args) => {
  return JSON.stringify(args);
};

const memoizedMultiply = _.memoize(multiply, resolver);

console.log(memoizedMultiply(1, 2)); // Calculates the product of 1 and 2 and caches the result
console.log(memoizedMultiply(1, 3)); // Calculates the product of 1 and 3 and caches the result
console.log(memoizedMultiply(1, 2)); // Returns the cached value
No.1961
06/07 16:02

edit

You-Dont-Need-Lodash-Underscore

● You-Dont-Need-Lodash-Underscore

https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore

● lodash代替コード 検索サイト

https://youmightnotneed.com/lodash/

_.find の例

// Native JavaScript
var users = [
  { 'id': 1 , 'user': 'barney',  'age': 36, 'active': true },
  { 'id': 2 , 'user': 'fred',    'age': 40, 'active': false },
  { 'id': 3 , 'user': 'pebbles', 'age': 1,  'active': true }
]

const id = 3;
const user = users.find(function (o) { return o.id === id; })

_.pluck の例

// Underscore/Lodash
var array1 = [{name: "Alice"}, {name: "Bob"}, {name: "Jeremy"}]
var names = _.pluck(array1, "name")
console.log(names)
// output: ["Alice", "Bob", "Jeremy"]

// Native
var array1 = [{name: "Alice"}, {name: "Bob"}, {name: "Jeremy"}]
var names = array1.map(function(x){
  return x.name
})
console.log(names)
// output: ["Alice", "Bob", "Jeremy"]

アロー関数でこのように記述できます

const names = array1.map(x => x.name);

_.uniq の例

// Underscore/Lodash
var array = [1, 2, 1, 4, 1, 3]
var result = _.uniq(array)
console.log(result)
// output: [1, 2, 4, 3]

// Native
var array = [1, 2, 1, 4, 1, 3];
var result = [...new Set(array)];
console.log(result)
// output: [1, 2, 4, 3]

複数の配列の共通する値を取得する _.intersection の例

// Underscore/Lodash
console.log(_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]))
// output: [1, 2]

// Native
var arrays = [[1, 2, 3], [101, 2, 1, 10], [2, 1]];
console.log(arrays.reduce(function(a, b) {
  return a.filter(function(value) {
    return b.includes(value);
  });
}));
// output: [1, 2]

// ES6
let arrays = [[1, 2, 3], [101, 2, 1, 10], [2, 1]];
console.log(arrays.reduce((a, b) => a.filter(c => b.includes(c))));
// output: [1, 2]

コレクション内に特定の キーの存在を確認する _.has

// Lodash
var object = { a: 1, b: 'settings', c: { d: 'test' } };
  
var hasA = _.has(object, 'a');
var hasCWhichHasD = _.has(object, 'c.d')

console.log(hasA);
// output: true
console.log(hasCWhichHasD);
// output: true

// Native
const has = function (obj, key) {
  var keyParts = key.split('.');

  return !!obj && (
    keyParts.length > 1
      ? has(obj[key.split('.')[0]], keyParts.slice(1).join('.'))
      : hasOwnProperty.call(obj, key)
  );
};

var object = { a: 1, b: 'settings' };
var result = has(object, 'a'); 
// output: true

参考 : https://bit.ly/3guGUik

No.1927
10/21 16:01

edit

(Next.js) next.js サイトのインストールと起動

● next.js サイトのインストールと起動(create-next-app コマンド)

npx create-next-app

実行するとサイト名を

What is your project named? … 

と聞かれますので入力します。( 例: test-app)

アプリ名を指定して新規作成する場合は

npx create-next-app --example parameterized-routing test-app

のようにすると

example指定: parameterized-routing
アプリ名: test-app

で新規作成します。

Next.js公式examples集を分類

https://bit.ly/396iZE8

next.js の 起動 ( 1. dev )

npm run dev

next.js の 起動 ( 2. build & start )

npm run build
npm run start

next.js の ページを追加する

新規ファイルを作成

vi pages/page01.js
import Head from 'next/head'
import styles from '../styles/Home.module.css'

export default function Home() {
  return (
    <div>
      <h1>Hello page01</h1>
    </div>
  )
}

これが URL
http://localhost:3000/page01

にあたります。

ボタンの作成

<Link href="/page02">
  <button>page02</button>
</Link>

● 静的サイトとしてビルドする

package.json

{
  "scripts": {
    ...
    "build_static": "next build && next export -o dist",
  },

( "build_static"の行を追加します )

ビルドの実行

npm run build_static

/dist/ フォルダに書出が行われます。

● examples

https://github.com/vercel/next.js/tree/master/examples

No.1902
11/22 14:42

edit

next.js

json に コメントを記入する

● json に コメントを記入する

JSON ファイルにコメントを記述することはできませんが以下のように無効なパラメーターとして登録しておくことでコメントを対応させることができます。

_comment にコメントを記述する

{
   "_comment": "comment text goes here...",
   "glossary": {
      "title": "example glossary",
      "GlossDiv": {
         "title": "S",
         "GlossList": {
         }
      }
   }
}

引用: http://bit.ly/2PbxnQS

No.1709
02/21 11:34

edit

npmパッケージ検索サイト

● npmパッケージ検索サイト

おすすめ順に紹介します

● 1. npms.io

https://npms.io/

● 2. npmjs.com

https://www.npmjs.com/

● 3. node-modules.com

http://node-modules.com/

No.1705
05/26 13:08

edit

ヘッドレスChrome puppeteer / puppeteer-core をインストールする

● puppeteerのインストールと検証

npm i puppeteer
vi test_puppeteer.js

以下の内容で保存します。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });

  const page = await browser.newPage();
  await page.goto('https://google.co.jp/');
  await page.screenshot({path: __dirname + '/test_puppeteer.png'});
  await browser.close();
})();

実行します

node test_puppeteer.js

● libX11-xcb.so.1 が読み込めないというエラーが表示される場合

libX11-xcb.so.1: cannot open shared object file: No such file or directory

と言うようなエラーが表示される場合があります

・libX11-xcb.so.1の場合

chromiumをインストールします(Centos stream)

dnf install -y chromium

・libXss.so.1の場合

libXScrnSaverをインストールします(Centos stream)

dnf install -y chromium

その他参考 : https://github.com/puppeteer/puppeteer/issues/5361

● puppeteer-core のインストールと検証

puppeteer と puppeteer-coreの違いは?

puppeteerをインストールした場合はGoogle Chromeのバイナリを追加でダウンロードします。
puppeteer-coreは使用しているマシンに既にインストール済みのGoogle Chromeを使用します。(Google Chromeのダウンロードは行いません)
npm i puppeteer-core
vi test02.js
const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
        executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',    
  });
  const page = await browser.newPage();
  await page.goto('https://google.co.jp/');
  await page.screenshot({path: 'test02.png'});

  await browser.close();
})();
node test02.js

● Sandboxエラーとなる場合は

  const browser = await puppeteer.launch();

  ↓

  const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });

 としておきましょう。

No.1667
01/20 09:24

edit

Visual Studio Code (vs code)の拡張機能を公開する

● npmパッケージをインストールする

npm install -g vsce
npm install -g yo
yo doctor
vsce --version
2.15.0

バージョン番号が返ってくればOKです。

● Publisherの作成

Azure Dev Ops にログインして「Profile」→「Personal access tokens」から Personal Access Tokens を作成します。

・Azure Dev Ops へのログイン

https://dev.azure.com/

・設定はこちらを参考にします。

https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page



作成したトークンをコピーしておきます。

● ログインの確認

vsce login <ユーザー名>

(コピーしておいたトークンを使ってログインします)

● githubにpushする

作成した拡張をgithubにpushします。(詳細は割愛します)

● package.json に以下を追加する

	"icon": "icon.png",
	"repository": {
		"type": "git",
		"url": "https://github.com/xxxxx/YOUR-GIT-PROJECT-NAME"
	},
	"bugs": {
		"url": "https://github.com/xxxxx/YOUR-GIT-PROJECT-NAME/issues"
	},

● vsce publish する

vsce publish

● vsce publish する(ログイン確認を行わずいきなりパブリッシュする場合)

vsce publish -p <token>

コマンドのヒストリにトークン文字列が残るので注意しましょう

添付ファイル1
添付ファイル2
No.1648
12/01 17:18

edit

添付ファイル

npmの代わりに yarn を使用する

npm ls で deduped がたくさん出るようなら yarn への乗り換えを検討しましょう。

● yarn を使用する

 brew install yarn

● yarn のバージョンを確認する

 yarn -v
1.19.1

● npmの代わりに yarn を使用する

npm install image-js

yarn add image-js
No.1613
11/05 09:23

edit

Webpack4 + Babel を使用して .js .css ファイルを require できるようにする

Webpack4 + Babel を使用して IE11 での動作を確認する。

● webpackのインストール

ローカルにインストールします( サイズはさほど大きくなく、46MB 程度なので複数のローカル環境に入れてもあまりディスクの負担になりません)

webpackwebpack-cliwebpack-dev-serverをインストールする

cd YOUR-PROJECT-FOLDER
npm install webpack webpack-cli webpack-dev-server -D
npm init -y

なお、オプションの -D は --save-dev と同じです
https://docs.npmjs.com/misc/config

● ソースファイルを用意する

./src/index.js

import {hellowebpack} from './f';
hellowebpack();

./src/f.js

export function hellowebpack() {
  alert('hellowebpack!!!');
}

● webpacのビルドの実行

npx webpack

./dist/main.js が以下のような内容で作成されます

!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";r.r(t),alert("helloメソッドが実行された。")}]);

javascriptのコードが圧縮されています。
これは 「 mode: 'production' 」な本番仕様です。
開発環境では圧縮しない 「 mode: 'development' 」を使用することでコンパイル時間を圧倒的に短縮することができます。
( ↓ やり方はこの後で)

● webpackの ソースファイルをwatchしながらビルドの実行

webpack.config.js

watch: true を追加すると watch します

module.exports = {
    ........................
    watch: true
};

● index.htmlを作成し、表示させる

1. index.html の作成

./dist/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>webpack test</title>
	<script src="./main.js"></script>
</head>
<body>
</body>
</html>

2. webpack.config.js の作成

mode: 'development' の記述はここに行います。

module.exports = {
    devtool: 'inline-source-map' ,
    mode: 'development',	// (development: 開発   production: 本番)
    entry: './src/index.js',
    output: {
        path: `${__dirname}/dist`,
        filename: 'main.js'
    },
    devServer: {
        contentBase: 'dist',
        open: true
    }
};

3. npx webpack-dev-server で サーバを起動する

npx webpack-dev-server

これでサーバが起動し、自動的に ./dist/index.html を読み込んで「hellowebpack!!!」アラートが表示されれば成功です。

● ファイル変更時の自動更新を確認する

webpack-dev-serverを起動しておくと、対象ファイルが更新された時に自動でリコンパイルがかかります。 それを確認してみます。

./src/f.js を以下のように変更して保存する

export function hellowebpack() {
  alert('hellowebpack!!! 変更しました。');
}

ブラウザに切り替えて、自動的に更新されていることを確認します。
自動更新機能は便利ですね!

● 本番サーバへファイルをアップロードして動作を確認する

「control + c 」で webpack-dev-serverを終了してから webpack ビルドを行う。

npx webpack

./dist/ 以下の全てのファイルを本番WEBサーバへアップロードして表示させて確認します。

● webpack で js 以外に css も読み込めるようにする

webpack ではデフォルトでは js しか読み込めません。 css を読み込むようにするには

style-loader css-loader をインストールする

npm i -D style-loader css-loader

webpack.config.js の設定項目の一番下に以下を追加します。

 module: {
      rules: [
        {
          test: /\.css/,
          use: [
            'style-loader',
            {loader: 'css-loader', options: {url: false}},
          ],
        },
      ]
    }

引用: https://goo.gl/QhDqE9

● webpack で js 以外に scss (sass) も読み込めるようにする こちらが今一番WEB制作的には一番現実的でしょう。

npm i -D webpack webpack-cli sass-loader sass style-loader css-loader

webpack.config.js を次のようにします。

// [定数] webpack の出力オプションを指定します
// 'production' か 'development' を指定
const MODE = "development";

// ソースマップの利用有無(productionのときはソースマップを利用しない)
const SOURCEMAP_FLAG = MODE === "development";  // true or false

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  // モード値を production に設定すると最適化された状態で、
  // development に設定するとソースマップ有効でJSファイルが出力される
  mode: MODE ,
  module: {
    rules: [
      {
        // 対象となるファイルの拡張子(scss)
        test: /\.scss$/,
        // Sassファイルの読み込みとコンパイル
        use: ExtractTextPlugin.extract([
          // CSSをバンドルするための機能
          {
            loader: 'css-loader',
            options: {
              // オプションでCSS内のurl()メソッドの取り込まない
              url: false,
              // ソースマップの利用有無
              sourceMap: true,
              // Sass+PostCSSの場合は2を指定
              importLoaders: 2
            },
          },
          // PostCSSのための設定
          {
            loader: 'postcss-loader',
            options: {
              // PostCSS側でもソースマップを有効にする
              sourceMap: true,
              // ベンダープレフィックスを自動付与する
              plugins: () => [require('autoprefixer')]
            },
          },
          // Sassをバンドルするための機能
          {
            loader: 'sass-loader',
            options: {
              // ソースマップの利用有無
              sourceMap: SOURCEMAP_FLAG,
            }
          }
        ]),
      },
    ],
  },
  plugins: [
    new ExtractTextPlugin('style.css'),
  ],
  // source-map方式でないと、CSSの元ソースが追跡できないため
  devtool: "source-map"
};

引用元 : https://qiita.com/hiroseabook/items/383d54a87fd5ab4108bc

● babelを使ってIE11でも表示ができるようにする

ES6で書いたJavaScriptは IE11 では使えないので babel をwebpackから呼び出してIE11でも使える状態でコンパイルします

1. ./src/Myclass.js ファイルの追加

./src/Myclass.js

export class Myclass {
  constructor(id,userName) {
	this.id       = id;
	this.userName = userName;
  }
  alertUserName() {
  	alert(this.userName);
  }
}

2. ./src/index.js ファイルの変更

./src/Myclass.js

import {hellowebpack} from './f';
import {Myclass} from './Myclass.js';

hellowebpack();

var m = new Myclass(1, 'DOWN TOWN');
m.alertUserName();

3. babel のインストール

npm install -D webpack webpack-cli babel-loader @babel/core @babel/preset-env

webpackと合わせると 70MB くらいになります

4. webpack.config.js の設定変更

下記のように書き換えます。

module.exports = {
    mode: 'production', // development or production
    entry: './src/index.js',
    output: {
        path: `${__dirname}/dist`,
        filename: 'main.js'
    },
    devServer: {
        contentBase: 'dist',
        open: true
    } ,
    module: {
        rules: [{
            test: /\.js$/,
            use: [{
                loader: 'babel-loader',
                options: {
                    presets: [
                        '@babel/preset-env',
                    ]
                }
            }]
        }]
    }
};

5. webpackのビルドの実行

npx webpack

6. IE11 ブラウザでの確認

本番サーバへアップロードして IE11で確認します。

No.1314
06/30 22:28

edit

node.js で ミニマルなWEBサーバを立ち上げる

Express を立ち上げるほどではないときは次のようにしてサーバを立ち上げます。

myserver.js

var fs   = require('fs');
var http = require('http');
var server = http.createServer();

server.on('request', function(req, res) {
    fs.readFile(__dirname + '/index.html', 'utf-8', function (err, data) {
        if (err) {
            res.writeHead(404, {'Content-Type' : 'text/plain'});
            res.write('page not found');
            return res.end();
        }
        res.writeHead(200, {'Content-Type' : 'text/html'});
        res.write(data);
        res.end();
    });
});
server.listen(1337, '127.0.0.1');

console.log( 'Server Started.\nopen "http://127.0.0.1:1337/"' );

実行方法

node myserver.js

でWEBサーバを立ち上げた後 http://127.0.0.1:1337/ へアクセスします。

引用 : https://goo.gl/xJBmYF

No.1313
10/20 10:44

edit

JavaScript自動整形PrettierをSublime Textから使用できるようにする

● JavaScript自動整形PrettierをSublime Textから使用できるようにする

・Js Prettier

https://packagecontrol.io/packages/JsPrettier

● Js Prettierのインストール

Sublime Textのパッケージコントローラーからインストールします

(command + shift + p) →【Install Package を選択】→【Js Prettierを選択】

● Js Prettierの設定

【Package settings】→【JsPrettier】→【settings - Default】を選択して設定ファイルを開きます
次の2つの項目のパスを設定します。

例:)

	"prettier_cli_path": "/Users/hogehoge/.nodebrew/current/bin/prettier",
	"node_path": "/Users/hogehoge/.nodebrew/current/bin/node",

#・パスの調べ方

which prettier
which node

● Js Prettierの実行

(command + shift + p) →【JsPrettier:Format Codeを選択】

とても便利なのでぜひショートカットを設定しましょう。

● Js Prettierのショートカット設定(ctrl+pで起動する場合)

    // JsPrettier
    { "keys": ["ctrl+p"], "command": "js_prettier" } ,
No.1152
06/16 11:33

edit

node.js

JavascriptのソースチェックESLintと自動整形ツールPrettierをインストールする

今時ES6 JavaScriptで記述することが多くなったので
コード検証ツールにESLint、自動整形ツールにPrettierをインストールしてSublimeTextから使用できるようにします。

● ESLintのインストール

npm install -g eslint

● Prettierのインストール

npm install -g prettier

● PrettierによるJavaScriptソースコード自動整形のテスト

ここまでの状態で自動整形のテストを行います
test.js ファイルを用意して自動整形してみます

prettier test.js 

ターミナル画面(標準出力)に自動整形した結果が表示されます。
実際に自動整形してファイルを更新するには( --write )オプションを使用します。

prettier --write test.js

● ESLintによるJavaScriptソースコード検証のテスト

※1. .eslintrc.js の生成

eslint test.js と行きたいところですが、まず設定ファイルを作成します。 懸賞したいファイルがあるディレクトリから

eslint --init

を実行すると対応形式で設定ファイル( .eslintrc.js )が生成されます こんなファイルです

module.exports = {
    "env": {
        "browser": true,
        "es6": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "sourceType": "module"
    },
    "rules": {
        "indent": [
            "error",
            4
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
};

※2. eslintの実行

eslint test.js 

でソースコードの検証が実行されます。

● ESLint検証ルール( .eslintrc.js )をカスタマイズする

.eslintrc editor
https://pirosikick.github.io/eslintrc-editor/

● Airbnb JavaScript Style Guideに沿ったESLintを設定する

「Airbnb JavaScript Style Guide」はなかなか厳格なルールなので
既存のプロジェクトにいきなり当てはめるには無理がありますが
どの程度厳格なソースフォーマット指定なのか体験しておくだけでも価値があると思います

・ Airbnb JavaScript Style Guide

https://github.com/airbnb/javascript

・ Airbnb JavaScript Style Guide に沿ったESLintを設定します

npm init -y
npm install --save-dev eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y eslint
{
  "extends": "airbnb"
}

● Sublime Textから使用できるようにする

JavaScript自動整形PrettierをSublime Textから使用できるようにする

No.1151
06/16 11:34

edit

node.js

node.jsでファイル一覧を取得する

ファイル一覧を取得する

Node.js でファイル一覧を取得するサンプルコード (2015-11-10) http://bit.ly/2q7nxjW

No.1136
04/25 00:50

edit

node.jsでオブジェクトの中身を一気に表示する

node.jsでオブジェクトの中身を一気に表示するにはconsole.logで表示しても良いのですが
次のようにすると楽に見れます

● utilのインストール

npm install util --save-dev

● utilの使い方

通常

console.log(results);

とする代わりに

var util = require('util');
console.log(util.inspect(results,false,null));

とします。

No.1135
04/25 00:41

edit

gulp-twig を使用して node.jsでphpと同じTwigテンプレートを使用する

● node.jsでphpと同じTwigテンプレートを使用する

システムの開発がphpかつテンプレートエンジンTwigを使用する場合
デザイン過程で静的なhtmlを作成する場合にもTwigを使用しておくとプログラムへのテンプレート化する時に
作業を省くことができます

実はGulpにはPHPと同じTwigテンプレートがあるのでこちらを使用します。
node.js とgulp はあらかじめインストールしておきます。

● gulp でTwig を扱うgulp-twigをインストールする

npm install  gulp-twig  --save-dev

● gulp-twigでコンパイルするための設定を gulpfile.js に作成する

以下の仕様でコンパイルするよう設定を記述します

● ./twig/ ディレクトリ以下の全ての html ファイルをTwigテンプレートを使用してコンパイルし、./www/ ディレクトリ内に生成する。
● アンダースコアで始まるファイルはコンパイルしない。

gulpfile.js 以下のとおり作成する

// twig コンパイル設定
var gulp   = require('gulp');
var twig   = require('gulp-twig');
var rename = require('gulp-rename');
gulp.task('twig', function () {
    'use strict';
    gulp.src(['./twig/*.html', './twig/*/*.html', '!./twig/_*.html'])
    .pipe( twig({}) )
    .pipe(gulp.dest('./www/'));
});

● gulp-twig によるコンパイルの実行

gulp twig

● テンプレートファイル内で書き出される html のファイル名を取得する

テンプレートファイル内に以下のように記述します

・gulp-twigの場合

{{ _target.relative }}

・PHP twigの場合

{{  _self.getTemplateName().__toString }}

● テンプレート例(レイアウトとそれを継承するテンプレート)

_layout.htm

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<header></header>
<div class="container">
	{% block content %}{% endblock %}
</div>
<footer></footer>
</body>
</html>
<!-- {{ template_filename }} -->

index.htm

{# 継承元 #}
{% extends "_layout.htm" %}

{# ファイル名 #}
{% if _self.getTemplateName() %}
	{% set template_filename = _self.getTemplateName().__toString %}
{% else %}
	{% set template_filename = _target.relative %}
{% endif %}

{# ページタイトル #}
{% set title = 'ユーザー管理・データ編集の完了' %}
{% block title %}{{ title }}{% endblock %}

{# コンテンツ #}
{% block content %}
<!-- ここにコンテンツを記述します -->
...
...
...
<!-- ここにコンテンツを記述します -->
{% endblock %}

上の2つのファイルを用意して test.htm を gulp-twig でコンパイルすると次のようなファイルが生成されます。

test.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ユーザー管理・データ編集の完了</title>
</head>
<body>
<header></header>
<div class="container">
	<!-- ここにコンテンツを記述します -->
...
...
...
<!-- ここにコンテンツを記述します -->
</div>
<footer></footer>
</body>
</html>
<!-- test.html -->
No.1119
04/14 22:20

edit

Twig

node js でwindowsやMacのアプリケーションを制作することができる electron をインストールする

● electronとは?

electonとは html や css や javascript などといった web 制作の技術を使ってデスクトップ上のアプリケーションを制作しようというものです。 ( まず node js が必要になりますので node js のインストールを完了させておいてください )

● electron をインストールする

 npm install -g  electron
 npm install -g  electron-packager 

● Helloworldプログラムに必要な3つのファイルを用意する

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello Electron!</title>
</head>
<body>
<h1>Hello Electron!</h1>
Node version: <script>document.write(process.versions.node)</script>,
Chrome version: <script>document.write(process.versions.chrome)</script>,
Electron version: <script>document.write(process.versions.electron)</script>.
</body>
</html>

main.js

const { app, BrowserWindow } = require('electron');
let win;
function createWindow() {
    win = new BrowserWindow({ width: 800, height: 600 });
    win.loadURL(`file://${__dirname}/index.html`);
    win.on('closed', () => {
        win = null;
    });
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});
app.on('activate', () => {
    if (win === null) {
        createWindow();
    }
});

package.json

{
  "name": "myapp",
  "version": "0.0.1",
  "main": "main.js"
}

● プログラムを実行する

electron .

● アプリを作成する(ビルド)

  1. electonのバージョンを調べる
    electron -v
    

    (例 : v1.6.2)

  1. electron-packagerアプリケーションを作成する
    electron-packager <ディレクトリ>  <アプリ名(拡張子は除く)>  --platform=darwin,win32  --arch=x64  --version=<バージョン>  --overwrite
    

● electron 関連リンク

https://qiita.com/tags/electron
https://teratail.com/questions/search?q=electron&search_type=and
http://bit.ly/2nYCc4d
http://bit.ly/2n8dA47

● 実際にelectronを使って制作されたプログラム

※ iCultus(常駐型カレンダー)

https://github.com/djyde/iCultus

※ comma-chameleon(CSVエディタ)

https://github.com/theodi/comma-chameleon

※ lightgallery

http://sachinchoolur.github.io/lightgallery-desktop/

● MacからWindowsビルド用パッケージのインストール

nodebrew を使って以下のパッケージをインストールします

brew cask install xquartz(途中で管理者パスワードの入力を求められます)
brew install wine
No.1110
12/31 11:42

edit

node.js の 複数のバージョンを管理できる nodebrew をインストールする

● (Mac)まず homebrew でインストールしてある node.js nodebrew をアンインストールする

brew uninstall nodebrew
brew uninstall node

● (Mac または Linux)に nodebrew をインストールする

curl -L git.io/nodebrew | perl - setup

.bashrc の最後にパスを追加

cd
vi .bash_profile

下記の2行を追加します

# nodebrew
export PATH=$HOME/.nodebrew/current/bin:$PATH

 
正しくインストールされたか確認します。

source ~/.bash_profile
nodebrew help

バージョンが表示されればOKです。

● nodebrew を使って好きなバージョンの node.js をインストールする

・インストール可能な node.js の全バージョンを表示する

nodebrew ls-remote

・(例)Node.js v9.5.0 バージョンをインストールする

nodebrew install-binary v9.5.0

・現在インストール済みの node.js 全バージョンを表示

nodebrew list

・(例)Node.js v9.5.0 バージョンに切り替える

nodebrew use v9.5.0

・Node.js が正しくインストールされていることを確認する

node -v

バージョン名が正しく表示されればOKです。

No.1069
06/04 15:41

edit

node.js

node.js で sqlite3 を扱う

● node.js 用 sqlite3 モジュール「sqlite3」をインストールする

npm install sqlite3

● sqlite3のデータベース「mydb.sqlite3」を作成する

sqlite3 mydb.sqlite3

● 続けてデータベース内にテーブル「user_dt」を作成する

CREATE TABLE  user_dt  (
  data_id       INTEGER PRIMARY KEY AUTOINCREMENT,
  user_name     TEXT,
  modified_date DATETIME
);

● .quit コマンドでsqliteを抜ける

.quit

● スクリプトファイル test.js を以下の内容で作成する

```
var sqlite3 = require('sqlite3').verbose();
var mydb    = new sqlite3.Database('./mydb.sqlite3');
mydb.run("INSERT INTO user_dt (user_name,modified_date) VALUES ('taro yamada', datetime('now', 'localtime') )");
```

##● スクリプトを実行してデータベースにデータを1件登録する
```
node test.js
```

##● node.js + sqlite3 で使用できる クエリービルダー
http://knexjs.org  
https://hiddentao.com/squel/  

##● node.js + sqlite3  で使用できる O/Rマッパー
http://docs.sequelizejs.com/en/v3/  
No.1068
12/09 16:35

edit

SQLite

ヘッドレスブラウザ(phantomJS, slimerJS )で動的なWEBサイトをスクレイピングする

ヘッドレスブラウザとは「画面がないWEBブラウザ」の事です。
最近では Headless Chrome が人気です。 参考 : https://goo.gl/NQsFS2
が、npm だけでインストールできる phantomJS を紹介します。

● phantomJS

WebKit(Safari) ベースのヘッドレス(画面なし)ブラウザ

・phantomJSのインストール方法(npm のみでインストールする方法)

npm install phantomjs

以下のパスを手動で追加します

./node_modules/phantomjs/bin/phantomjs

・phantomJSのインストール方法(Mac の brew を使う方法)

brew install phantomjs

・phantomJSのインストール方法(CentOS の yum を使う方法)

yum -y install freetype
yum -y install fontconfig
npm install -g phantomjs

● slimerJS

Gecko(firefox) ベースのヘッドレス(画面なし)ブラウザ

・slimerJSのインストール方法(Mac)

brew install slimerjs

・slimerJSのインストール方法(CentOS7)

npm install -g slimerjs

● casperJS

ヘッドレスブラウザを簡単に扱うライブラリ(JavaScript)です。 このcasperJSから「phantomJS」または「slimerJS」を操作します。

・casperJSのインストール方法(Mac)

brew install casperjs

・casperJSのインストール方法(CentOS7)

yum -y install freetype
yum -y install fontconfig
npm install -g casperjs

● casperJSからブラウザを操作してxpathで要素を取得し、画面のスクリーンショットを撮る

test.js で下記コードを保存

var AnchorArrays = [];
var casper = require('casper').create();
casper.start('http://flatsystems.net/kakunin.php', function() {
});
casper.then(function() {
	casper.wait(3000, function() {
	    // xpath要素の取得
	    var xpath = "//*[@id='my_id']";
	    var element = this.evaluate(function(path){
	                return __utils__.getElementByXPath(path).innerHTML;
	    },xpath);
	    console.log( element );

	//png
    	this.capture('kakunin.png');
    	console.log( 'キャプチャを作成しました。' );
	});
});
casper.run();

● casperJSからphantomJSで起動する

casperjs  test.js

● casperJSからslimerJSで起動する

casperjs  --engine=slimerjs test.js

slimerJSで起動するときは --engine=slimerjs を追加します。

● casperJSコードを実際のブラウザソースから生成する Chrome拡張機能

・Resurrectio

https://chrome.google.com/webstore/detail/resurrectio/kicncbplfjgjlliddogifpohdhkbjogm

No.1067
01/29 13:40

edit

スクレイピング
xpath
slimerjs
phantomjs

ブラウザ slimerjs(firefox)の使い方

slimerjs のインストール

Mac OSX の場合は brew でインストールできます

brew install slimerjs

slimerjs のバージョンを確認する

インストールが完了したらバージョンを確認しておきます

slimerjs -v

slimerjs でWEBページのスクリーンショットを撮る

```
var page = require("webpage").create();
page.open('http://zozo.jp/')
    .then(function(status){
        if(status === 'success'){
            console.log(page.title);
            page.render('test.png');
        }
        phantom.exit();
    });
```
実行します
```
slimerjs test.js
```


# slimerjs をCUI環境で実行させる
slimerjsはGUI環境下でないと動作しません。
CUIで実行すると
```
slimerjs --debug=true XXXXX.js 
```
```
Error: no display specified
```
というエラーになります。

そこで  CUI でも実行できるように Xvfb( X virtual framebuffer ) をインストールします。

```
yum -y install xorg-x11-server-Xvfb
```
    
Xvfbの使い方は 実行したい処理の前に ```xvfb-run``` を付け加えます
```
Xvfb slimerjs --debug=true XXXXX.js 
```
これでCUI下の環境でも実行できます。


No.1063
12/17 22:34

edit

phantomjs
slimerjs

Herokuでphantomjsを使う

Heroku上でヘッドレスWEBブラウザ phantom.js を使用するにはビルドパックを追加します。 ビルドパックをGithub上で公開してくれている方がいるのでありがたく利用させていただきます。

● heroku-buildpack-phantomjs

https://github.com/stomita/heroku-buildpack-phantomjs

ターミナルから以下を実行

cd "アプリのあるディレクトリ"
heroku login
heroku buildpacks:add --index 1 https://github.com/stomita/heroku-buildpack-phantomjs
git push heroku master

● phantomjs がHeroku上にあるか確認する

heroku run phantomjs -v

バージョンが帰ってくればOK

● Heroku上のphantomjsから他のWEBサイトにアクセスできるか確認する

・1.アプリのあるディレクトリに `phantom_test.js` ファイルを以下の内容で作成します。

var page = require('webpage').create();
page.open('http://yahoo.co.jp/', function(status) {
  console.log("Status: " + status);
  if(status === "success") {
    page.render('example.png');
  }
  phantom.exit();
});

・2.Heroku上に再デプロイします。

git add .
git commit -m "add buildpack"
git push heroku master

・3.phantomjsでスクリプトを実行します。

heroku run phantomjs phantom_test.js

「Status: success」が帰ってくればOK

● 日本語フォントを追加して文字化けを解消する

フォントは `./fonts` ディレクトリに .ttf フォントファイルを置いて git push すればOKです。
フォントファイルは著作権に注意して使用しましょう

・フリーの日本語フォントファイル Google Noto Sans

https://www.google.com/get/noto/#sans-jpan

・Heroku上の日本語フォントが正しく認識されているか確認する

heroku run fc-match sans:lang=ja
No.1061
12/17 17:33

edit

Heroku
phantomjs

Herokuのビルドパック(buildpack)の扱い方

Buildpacks | Heroku Dev Center

● 現在使用中の buildpack を表示する

heroku buildpacks

● buildpack をセットする

heroku buildpacks:set "ビルドパック名またはURL"

● buildpack を追加する

heroku buildpacks:add "ビルドパック名またはURL"

● buildpack を削除する

heroku buildpacks:remove "ビルドパック名またはURL"

● 全てのbuildpack を削除する

heroku buildpacks:clear
No.1060
10/03 14:47

edit

Heroku

node.js で phantomJSを使用する

node.js から phantomJSを使用するには下記2つの方法がおすすめです。

  • 1.モジュール Nightmare を使う方法(実行経過が見えるのでおすすめです。)
  • 2.モジュール node-horseman を使う方法

● 1.モジュール Nightmare.js を使って phantomJS を操作する

● モジュール nightmare , vo のインストール

npm install nightmare
npm install vo

● テストプログラムを実行する(Googleへアクセスしてタイトルを取得)

・下記コードを `test.js` で保存

var Nightmare = require('nightmare');
var google = new Nightmare()
    .viewport(1000, 1000)
    .useragent("Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36")
    .goto('http://google.com')
    .evaluate(function () {
      return {
        title: document.title,
        href : location.href
      };
    })
    .run(function(err, nightmare) {
        if (err) return console.log(err);
        console.log('Done!');
        console.log(nightmare);
    });

・テストコードを実行

node test.js

GoogleホームページのタイトルとURLが表示されればOKです。

● テストプログラムを実行する(niconicoへアクセスしてログインする)

・下記コードを `niconico_login.js` で保存

var Nightmare = require('nightmare');
var vo = require('vo');
vo(function* () {
  var nico_id   = '###メールアドレス###';
  var nico_pass = '###パスワード###';
  var nightmare = Nightmare({ show: true });
  var link = yield nightmare
    .goto('https://account.nicovideo.jp/login')
    .type('input[id="input__mailtel"]', nico_id)
    .type('input[id="input__password"]', nico_pass)
    .click('#login__submit')
    .wait(2000)
    .then(function() {
      console.log('終了しました');
    });
  yield nightmare.end();
  return link;
})(function (err, result) {
  if (err) return console.log(err);
});

・テストコードを実行

node niconico_login.js

ニコニコ動画にログインできればOKです。

● 2.モジュール node-horseman を使って phantomJS を操作する

● モジュール node-horseman のインストール

npm install node-horseman

● テストプログラムを実行する

・下記コードを `test.js` で保存

var Horseman = require('node-horseman');
var horseman = new Horseman();
var filename = 'test.png';
horseman
    .userAgent('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0')
    .open('http://www.google.com')
    .type('input[name="q"]', 'github')
    .click('[name="btnK"]')
    .keyboardEvent('keypress', 16777221)
    .waitForSelector('div.g')
    .count('div.g')
    .log() // prints out the number of results
    .screenshot(filename)
    .log(filename)
    .close();

・テストコードを実行

node test.js

Googleの検索結果の画面が test.png に保存されれば正常に動作しています。

参考 : https://www.sitepoint.com/?p=124173

No.1059
10/03 12:44

edit

PhantomJSの使い方

●インストール (MacOSX Homebrew を使ってインストール)

brew install phantomjs

●インストール (その他のインストール方法)

http://phantomjs.org/download.html

●PhantomJSの動作確認

hello.js という名前で下記のコードを保存します。

console.log('Hello, world!');
phantom.exit();

・実行します

phantomjs hello.js 

・実行結果

Hello, world!が表示されればインストール成功です。

●PhantomJSを使ってWEBページ(例:ヤフー)のページのスクリーンショットを撮る

・page.js という名前で下記のコードを保存します。

var page = require('webpage').create();
page.open('http://www.yahoo.co.jp', function(status) {
  console.log("Status: " + status);
  if(status === "success") {
    page.render('page.png');
  }
  phantom.exit();
});

・実行します

phantomjs page.js 

・実行結果

同じフォルダに page.png というキャプチャ画像があれば成功です。

No.1058
09/20 16:14

edit

Herokuの使い方

● Herokuの使い方

(「ユーザー登録してWEBアプリをデプロイしてHeroku上で実行する」までの方法)

・1. Herokuへのユーザー登録

https://www.heroku.com/
からユーザー登録を行う

・2. Heroku Command Line(Heroku CLI)のインストール

https://devcenter.heroku.com/articles/heroku-command-line
からインストーラをインストール

・3. Herokuへログイン

heroku login

・4. ひな形を使ってデプロイをテスト

git clone https://github.com/heroku/ruby-getting-started.git
cd ruby-getting-started
heroku create

ここまで行うとURLが自動発行されます

https://XXXXX.herokuapp.com/ | https://git.heroku.com/XXXXX.git

git pushします

git push heroku master

No such app as XXXXX. というエラーが出るときは

git remote rm heroku

としてから

heroku create
git push heroku master

とします。

ブラウザで開く

heroku open

・5. node.js アプリをherokuにデプロイ

cd  "アプリのディレクトリ"

git init
git add .
git commit -m "first commit"

heroku create
git push heroku master
heroku open

・6. node.js アプリを修正した時の再デプロイ

git add .
git commit -m "change xxxxx"
git push heroku master
heroku open

・よく使うherokuコマンド

▼ herokuへログイン

heroku login

「メールアドレス」「パスワード」を入力してログインします。

▼ heroku上のアプリを表示

heroku apps

と入力すると herokuアプリが

hogehoge-fugafuga-12345
hunihuni-furifuri-67890

という風に表示されます。

▼ herokuアプリをブラウザで表示

アプリ hogehoge-fugafuga-12345 をブラウザで表示するには

heroku open --app hogehoge-fugafuga-12345

と入力します。 またはブラウザのアドレス欄に直接

https://hogehoge-fugafuga-12345.herokuapp.com

と入力してもOKです。

▼ heroku上のアプリを削除

アプリ hogehoge-fugafuga-12345 を削除するには

● 確認なしで削除
heroku apps:destroy --app hogehoge-fugafuga-12345 --confirm hogehoge-fugafuga-12345
● 確認ありで削除
heroku apps:destroy --app hogehoge-fugafuga-12345

▼ heroku上でbashを使用する

heroku run bash

▼ heroku上で任意のコマンドを使用する

・Heroku上のホームディレクトリのファイル一覧を表示

heroku run pwd ;  ls -la

・Heroku上のphantomjsのバージョンを表示

 heroku run phantomjs -v

●その他 Node.js が使える Paas

  • openshift
  • Google App Engine

node.js - How can I run latest version of node on Openshift? - Stack Overflow
Google App Engine Node.jsを試してみる。 GAE/Node.js - Qiita
Google App Engineを無料で運用する方法(2016年版) - koni blog

No.1057
06/16 12:33

edit

Heroku

node.jsのスクレイピングモジュール

● cheerio-httpcli

>Node.jsでWEBページのスクレイピングを行う際に必要となる文字コードの変換と、cheerioによってパースしたHTMLをjQueryのように操作できるHTTPクライアントモジュールです。

特徴 : 文字コード自動変換 , jqueryライクなセレクタ , フォーム簡易送信機能

https://www.npmjs.com/package/cheerio-httpcli

・インストール

npm install cheerio-httpcli

● osmosis

>HTML/XML parser and web scraper for NodeJS.

特徴 : xpathで要素の取得が可能 , Aタグのリンクを自動的に辿れる

https://www.npmjs.com/package/osmosis
マニュアル : https://github.com/rchipka/node-osmosis/wiki

・インストール

npm install osmosis

・スクレイピング例

【osmosis node.js】で検索した結果のリンクURLをスクレイピングします。

var osmosis = require('osmosis');
var url     =  'https://www.google.co.jp/search?client=safari&rls=en&q=osmosis&ie=UTF-8&oe=UTF-8&gfe_rd=cr&ei=JXMyWLv2NOjC8AernaPYAg#q=osmosis+node.js';
osmosis
.get(url)
.paginate("//*[@id='pnnext']",3)    // 最大 3ページ目まで
.set({
    'link_url'  :  ["//*[@id='rso']//h3/a/@href"] ,
    'link_title':  ["//*[@id='rso']//h3/a"] ,
})
.then(function( context, data){
    // console.log(context.request);
    console.log(data);
})
.done(function(){
    console.log("=================================\nscrape done.\n");
});

その他のスクレイピングモジュール : http://blog.webkid.io/nodejs-scraping-libraries/

No.1051
01/16 23:13

edit

xpath
スクレイピング