なぽろぐ

気ままに感じたことを記事にまとめます。

DIVでBlurっぽいことをやる(React)

DIVでもInput要素のblur的なことがしたい!

InputElementは別のところを触るとBlurイベントを発火してフォーカスが外れます.

developer.mozilla.org

MDNのサイトで動作は確認できるかと思います.

これをDIV Elementでもやりたいなぁと言うのが今回の議題です.

Image from Gyazo

今回はBlur的なことをしたくなったSelectorUIを用意しました.https://naporin0624.github.io/napoblog-assets/#/div-blur

  • 上のセレクターを出すボタンを押すとセレクターを表示する
  • 要素を選択するところを触っている時はセレクターを消さない
  • 背後の要素はスクロールできる
  • それ以外のところを触るとセレクターが閉じる

ようなUIを考えます.

方針

セレクターUIはdivの中に存在するようになっていて,次のようなDOM構成になっています.

  • ContainerDIV
    • ButtonDIV
    • MenuDIV
      • OptionGroupDIV
        • OptionDIV
        • OptionDIV
        • ...

ButtonDIVもOptionGroupDIVもOptionDIV触っても親の要素は常にContainerDIVになるので

developer.mozilla.org

こちらのclosestメソッドを使ってクリックされた要素の親にContainerがあるかどうかでフォーカスがはずれたかどうかを検知するようにしていきます!関係ないところをクリックしてもその親にはContainerはいないのでフォーカスは外れる仕組みです!

実装

こんな感じでできるかと思います!InputElementが自分でblurイベントを発火してくれるところを自分でclosestメソッドを使ってblurイベント相当のものを生成しています.

function Selector() {
  // closestにSelectorをわたすためにContainerのrefをとります
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const handler = (e: Event) => {
      // Blurを制御したい親Elementのselector(クラス)refからを作る
      const selector = ref.current?.className
        .split(" ")
        .map(s => `.${s}`)
        .join();

      // eはクリックしたところから発火されるイベント
      // eをclosestで比較し,eの親にrefが含まれるか確認する
      const blur = !(e.target as HTMLElement).closest(selector || "");

      // 親にrefが含まれていなければフォーカスを外す
      if (blur) setIsFocus(false);
    };

    document.addEventListener("click", handler);
    return () => document.addEventListener("click", handler);
  }, []);

  return (
    <Container ref={ref}>
      <Button></Button>
      <Menu>
        <OptionGroup>
          <Option>hoge</Option>
          <Option>huga</Option>
          <Option>nyan</Option>
        </OptionGroup>
      </Menu>
   </Container>
  )
}

ここなんですが

const selector = ref.current?.className
        .split(" ")
        .map(s => `.${s}`)
        .join();

javascriptだと

const selector = [...ref.current.classList].map(s => `.${s}`).join()

みたいなことができたんですけど,TSだと怒られましたw

最後に

今回使ったプログラムはここに置いてあります!必要であれば使ってください. github.com

自作のUI(DIVで作ったやつ)でフォーカス制御してみたいなことがあって困っていろいろ模索した結果こんな感じの解にたどり着きました.MDNじろじろ見る癖が少しづつついてきたかなぁって感想と,CSS筋が前よりついてきた気がします.

もっとこうしたらいいよとかあれば@naporin24690に教えてください〜!

LottieWebでチェックボックスの状態を管理する

Lottieで消えちゃうCheckBox作るぞ!

こういうのを作りたい

https://naporin0624.github.io/napoblog-assets/#/

Image from Gyazo

今回はこちらのチェックボックスアニメーションを使わせていただきました.

lottiefiles.com

コードの全体像

Lottieのマウントにはreact-lottieを使っています.animを直で触りたいのでそこの部分だけ型定義を拡張して使っている感じですね.

github.com

index.tsx

import React, { useRef, useEffect } from "react";
import Lottie from "react-lottie";
import checkbox from "./checkbox.json";
import { AnimationItem } from "lottie-web";

interface LottieType extends Lottie {
  anim: AnimationItem;
}
interface Props {
  checked: boolean;
}
const options = {
  loop: false,
  autoplay: false,
  animationData: checkbox,
  rendererSettings: {
    preserveAspectRatio: "xMidYMid slice",
  },
};
export const CheckBox = (props: Props) => {
  const { checked } = props;
  const ref = useRef<LottieType>(null);
  const prev = useRef<boolean>();

  useEffect(() => {
    // 初回マウントときはレンダリングしない
    // falseでは行ってきたときにgoToAndPlayされるのを防ぐため
    if (prev.current === undefined) {
      prev.current = checked;
      // 初回マウント かつ チェックボックスが選択されているならフレームの最後からアニメーションを実行する
      checked && ref.current?.anim.goToAndPlay(ref.current?.anim.getDuration(true), true);
      return;
    }

    if (checked) {
      ref.current?.anim.goToAndPlay(0, true);
    } else {
      ref.current?.anim.setDirection(-1);
      ref.current?.anim.goToAndPlay(15, true);
    }
  }, [checked]);
  return <Lottie ref={ref} options={options} width={300} />;
};

最後に

lottie-webにはここでフレームを止めるみたいなことができないぽいのでこのような措置を取りました.もっといい方法があれば教えてください🙇‍♂️

初めはこの子

lottiefiles.com

で実装していてチェックされてから消えるまでセットのアニメーションだったために以下のようにしていたのですが

  useEffect(() => {
    ref.current?.anim.playSegments([30, 75], true);
    ref.current?.anim.stop();
  }, []);

なぜかこうなる・・・・・・

Image from Gyazo

消すとうまくいくので明らかにplaySegmentが悪いのはわかっているんですけど,どう直したらいいのかわからなかったので上で紹介したアニメーションを使いました

有識者教えてください!!!!

わかっていること

  • destroyの前に動き始めている
    • useEffectのreturnにconsole.logをしこませて検証
  • playSegmentの上にconsole.logをおいても初期レンダリング以外では反応なし

ぐらいです・・・・・・

react-transition-groupでリストを表示するときはkeyにindexを設定するのはやめよう

リストで表示したものにトランジションを適用したい

こんなやつをやりたかった

Image from Gyazo

なんかこうなる

Image from Gyazo

🤔

コードを比較

styles.tsについては一番下に書いておきます.

変な挙動をするコード

import React, { Fragment, useState, useCallback, useMemo } from "react";
import { Container, AnimationBox, FormContainer, Input, Submit } from "./styled";

export const ListTransition = () => {
  const [state, setState] = useState<string[]>(["hoge", "huga"]);
  const [value, setValue] = useState<string>("");
  const onAdd = useCallback(() => {
    if (value.length <= 0) return;

    setState(ls => [...ls, value]);
    setValue("");
  }, [value]);

  const onRemove = useCallback((s: string) => setState(l => l.filter(el => el !== s)), []);
  const disabled = useMemo(() => value.length <= 0, [value]);
  return (
    <Fragment>
      <Container>
        {state.map((s, idx) => (
          <AnimationBox key={idx} unmountOnExit timeout={800} onClick={() => onRemove(s)}>
            {s}
          </AnimationBox>
        ))}
      </Container>
      <FormContainer>
        <Input value={value} onChange={e => setValue(e.target.value)} />
        <Submit onClick={onAdd} disabled={disabled}>
          Add Item
        </Submit>
      </FormContainer>
    </Fragment>
  );
};

想定している動きをするコード

import React, { Fragment, useState, useCallback, useMemo } from "react";
import { Container, AnimationBox, FormContainer, Input, Submit } from "./styled";

export const ListTransition = () => {
  const [state, setState] = useState<string[]>(["hoge", "huga"]);
  const [value, setValue] = useState<string>("");
  const onAdd = useCallback(() => {
    if (value.length <= 0) return;

    setState(ls => [...ls, value]);
    setValue("");
  }, [value]);

  const onRemove = useCallback((s: string) => setState(l => l.filter(el => el !== s)), []);
  const disabled = useMemo(() => value.length <= 0, [value]);
  return (
    <Fragment>
      <Container>
        {state.map(s => (
          <AnimationBox key={s} unmountOnExit timeout={800} onClick={() => onRemove(s)}>
            {s}
          </AnimationBox>
        ))}
      </Container>
      <FormContainer>
        <Input value={value} onChange={e => setValue(e.target.value)} />
        <Submit onClick={onAdd} disabled={disabled}>
          Add Item
        </Submit>
      </FormContainer>
    </Fragment>
  );
};

原因

Image from Gyazo

keyをindexにしていたからでした.keyが変わるので最後のコンポーネントが消えたと認識されてしまってトランジションが最後のコンポーネントにしか効かなかったというオチでした.

styles.ts

import styled from "styled-components";
import { TransitionGroup } from "react-transition-group";
import transition from "styled-transition-group";

export const Container = styled(TransitionGroup)`
  position: absolute;
  top: 0px;

  display: flex;
  width: 300px;
  overflow-x: scroll;
  margin: 12px;
`;

export const AnimationBox = transition.p`
  padding: 8px 16px;
  font-size: 13px;
  border: solid 1px #c1c1c1;
  margin: 0 8px;
  border-radius: 3px;
  background-color: #c1c1c1;
  color: white;
  cursor: pointer;

  &:enter {
    opacity: 0;
  }
  &:enter-active {
    opacity: 1;
    transition: all 0.8s ease-out;
  }
  &:exit {
    opacity: 1;
  }
  &:exit-active {
    opacity: 0;
    transition: all 0.8s ease-out;
  }
`;

export const FormContainer = styled.div`
  display: flex;
  margin-top: 56px;
`;
export const Input = styled.input``;
export const Submit = styled.button``;

2020年なぽりたん

2020年

Twitterが面白くないと言われたので2020年は面白いツイートを心がけます

みんな頑張って僕を面白くしてください!

まじめなやつ

  • アウトプット中心にする
    • webアプリをたくさん作っていきたい
  • 就職するので,来年末には社内で1年で1番ページ作った人間になりたい
  • OSSにコミットしたい
    • 今までは全くやったことがない
  • クリエイターになれるような活動をする
  • 遅刻を少なくする
  • 人みたいな生活をする

終わりに

これを書いているMacBookProのバッテリー残量が2%.本当に最後の抱負の人みたいな生活ができるかどうか怪しい.あと,大江戸温泉からコミケにいくのは凄まじくしんどいのでやめよう

【NestJSアドベントカレンダー】NestJSとwebpack-dev-middlewareを組み合わせる【23日目】【遅刻】

NestJSにwebpack-dev-middlewareを組み込む

2020-04-09追記

ライブラリとして公開しました

www.npmjs.com

遅刻しました.今日は25日.クリスマスです.名取の配信見てたら24日もいつのまにか終わっていました. 23日目は研究室の実験があり,徹夜でNestJSを書いていたので実質NestJSアドベントカレンダー書いたようなものじゃないですか?許してください・・・・・・・

qiita.com

本日のリポジトリ

github.com

webpack-dev-middlewareって何?

github.com

こいつです.こいつは開発時のみ使える良きもの.webpack-dev-serverだと内部のexpressが使われてしまいまサーバーがフロントエンド用と,バックエンド用の2つできてしまいますが,こいつを使えば自分のサーバの上でwebpack-dev-server的なことができてしまうわけです.よいですね.

Usageを見てみましょう

const webpack = require('webpack');
const middleware = require('webpack-dev-middleware');
const compiler = webpack({
  // webpack options
});
const express = require('express');
const app = express();

app.use(
  middleware(compiler, {
    // webpack-dev-middleware options
  })
);

app.listen(3000, () => console.log('Example app listening on port 3000!'));

シンプルですね.これをNestJSに組み込んでいきます.

NestJS & webpack-dev-middleware

僕 <「webpack-dev-middlewareを使う.」,「本番でもそのまま動くようにする.」両方やらなくちゃいけないのが〇〇の辛いところだな.*〇〇にしたのは思いつかなかったため

webpack-dev-middlewareを使うmoduleを作る

client.module.tsみたいな名前でやりましょう.名前をつけるのが下手なのは目を瞑ってください

nest g mo client
import { Module, DynamicModule, OnModuleInit, Inject } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';

import { CLIENT_MODULE_OPTIONS } from './client-options.constant';
import { ClientOptions } from './client-options.interface';

import { ClientProvider } from './client.provider';

@Module({
  providers: [ClientProvider],
})
export class ClientModule implements OnModuleInit {
  constructor(
    @Inject(CLIENT_MODULE_OPTIONS)
    private readonly clientOptions: ClientOptions,
    private readonly httpAdapterHost: HttpAdapterHost,
    private readonly clientProvider: ClientProvider,
  ) {}

  static forRoot(options: ClientOptions): DynamicModule {
    return {
          module: ClientModule,
          providers: [
            {
              provide: CLIENT_MODULE_OPTIONS,
              useValue: options,
            },
          ],
        };
  }

  public async onModuleInit() {
    if (process.env.NODE_ENV !== 'development') return;

    const httpAdapter = this.httpAdapterHost.httpAdapter;
    const { webpackConfig } = this.clientOptions;
    this.clientProvider.register(httpAdapter, webpackConfig);
  }
}

moduleではforRootを使ってDynamicModuleを返すようにします.MongooseModuleとかTypeOrmModuleとかと同じですね.

@Inject(CLIENT_MODULE_OPTIONS) private readonly clientOptions: ClientOptions

これ頭いいなぁって思ったんですけど,forRootで返したproviderから拾ってきています.forRootはstaticmethodなのでonModuleInitに対して変数を渡すことはできないのですがこれで実現しています.

こんなことは自分では考え付かない・・・後述するServeStaticModuleの内部実装がこうなっていたのを真似しました.

続いてProviderを作っていきます. Providerではwebpackのコンフィグをwebpack-dev-middlewareに食わせて,httpAdapter.つまり,NestJSの本体に登録していきます.

import { Injectable } from '@nestjs/common';
import { Configuration, Entry, EntryFunc } from 'webpack';
import webpack from 'webpack';
import { AbstractHttpAdapter } from '@nestjs/core';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';

@Injectable()
export class ClientProvider {
  async register(app: AbstractHttpAdapter, config: Configuration) {
    const compiler = webpack({
      ...config,
      mode: 'development',
      entry: await this.appendHotMiddlewareToEntry(config.entry),
      plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()],
    });

    app.use(webpackDevMiddleware(compiler));
    app.use(webpackHotMiddleware(compiler));
  }

  private async appendHotMiddlewareToEntry(
    entry: string | string[] | Entry | EntryFunc,
  ) {
    const hot: string =
      'webpack-hot-middleware/client?reload=true&timeout=1000';
    let e = entry;
    e = e instanceof Function ? await e() : e;
    if (e instanceof Array) return [...(e as string[]), hot];
    else if (typeof e === 'string') return [e, hot];
    else {
      for (const key in e) {
        if (e[key] instanceof Array) e[key] = [...e[key], hot];
        else e[key] = [e[key], hot] as string[];
      }
      return e;
    }
  }
}

どうせならHMRも登録してしまえ!ということでやっています.appendHotHiddlewareToEntryではwebpackのentry全てにhot-middlewareを付け加えています.なんかヤバそうな気がする・・・・

ここでやるのはwebpack-dev-middleware周りの設定だけにしています. 特に説明すべきことはないと思いますので次に行きます.

configの型を決めましょう.

configだけくれればそれでええ!

import { Configuration } from 'webpack';

export interface ClientOptions {
  webpackConfig: Configuration;
}
export const CLIENT_MODULE_OPTIONS = 'CLIENT_MODULE_OPTIONS';

moduleを登録する

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ClientModule } from './client/client.module';
import config from '@/webpack.config.client.js';
import { Configuration } from 'webpack';


@Module({
  imports: [
    ClientModule.forRoot({
      webpackConfig: config as Configuration,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

実行する

やることリスト

  • [ ] webpack.config.jsを書く
  • [ ] フロントエンドのコードをcliet/App.tsxみたいな感じで作っとく

わからなかったら僕の作ったリポジトリをそのまま使えば大丈夫です

動かしてみる

NODE_ENV=development yarn ts-node src/main.ts

で動くはず

僕のリポジトリを使っている場合は

yarn build:dev
yarn start:dev

でも動きます.

http://localhost:3000

プロダクションではwebpack-dev-middlewareを使わずにやる

serve-staticを使うと静的サイトを配信できます.これと今作ったwebpack-dev-middlewareのmoduleをNODE_ENVの状態で切り替えれば完璧に動くはずです.

github.com

moduleを変更する

import { Module, DynamicModule, OnModuleInit, Inject } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';

import { CLIENT_MODULE_OPTIONS } from './client-options.constant';
import { ClientOptions } from './client-options.interface';

import { ServeStaticModule } from '@nestjs/serve-static';
import { ClientProvider } from './client.provider';

@Module({
  providers: [ClientProvider],
})
export class ClientModule implements OnModuleInit {
  constructor(
    @Inject(CLIENT_MODULE_OPTIONS)
    private readonly clientOptions: ClientOptions,
    private readonly httpAdapterHost: HttpAdapterHost,
    private readonly clientProvider: ClientProvider,
  ) {}

  static forRoot(options: ClientOptions): DynamicModule {
    return process.env.NODE_ENV !== 'development'
      ? ServeStaticModule.forRoot(options)
      : {
          module: ClientModule,
          providers: [
            {
              provide: CLIENT_MODULE_OPTIONS,
              useValue: options,
            },
          ],
        };
  }

  public async onModuleInit() {
    if (process.env.NODE_ENV !== 'development') return;

    const httpAdapter = this.httpAdapterHost.httpAdapter;
    const { webpackConfig } = this.clientOptions;
    this.clientProvider.register(httpAdapter, webpackConfig);
  }
}

interfaceを変更する

import { ServeStaticModuleOptions } from '@nestjs/serve-static';
import { Configuration } from 'webpack';

export interface ClientOptions extends ServeStaticModuleOptions {
  webpackConfig: Configuration;
}

app.module.tsに変更を反映する

静的サイトはdist/publicにbuildされるように僕のリポジトリでは設定されているのでそのように設定します.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ClientModule } from './client/client.module';
import config from '@/webpack.config.client.js';
import { Configuration } from 'webpack';
import { getMetadataArgsStorage } from 'typeorm';
import { UserModule } from './user/user.module';
import ormConfig from '@/ormconfig.json';
import { join } from 'path';

const { cli, migrations, ...typeOrmConfig } = {
  ...ormConfig,
  entities: getMetadataArgsStorage().tables.map(tbl => tbl.target),
};

@Module({
  imports: [
    ClientModule.forRoot({
      renderPath: '/',
      rootPath: join(__dirname, 'public'),
      webpackConfig: config as Configuration,
    }),
    TypeOrmModule.forRoot(typeOrmConfig as TypeOrmModuleOptions),
    UserModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

以上です.これで先ほどと同じように

NODE_ENV=development yarn ts-node src/main.ts

僕のリポジトリを使っている場合は

yarn build:dev
yarn start:dev

で動くかと思います.clientもビルドして配信されるかどうか試してみてください.

僕のリポジトリでは

yarn build
yarn start:prod

でできると思います.

TypeORMとwebpack-dev-middlewareとaspidaと俺と

Reactで作ったUserのCreateとid検索ができるサンプルアプリを作りました.aspidaで型安全にapiを叩きながらNestJSでapiを実装する.それを開発時はwebpack-dev-middlewareから配信されるReactから叩く,プロダクション時はserve-staticで配信されるReactからapiが叩かれる.なかなか型で守られていていいのではないでしょうか?僕はいいと思って作りました.

github.com

github.com

動かし方

開発時

docker-compose up --build -d
yarn typeorm migration:run
yarn build:dev
yarn start:dev

それ以外

mysqlはdocker-composeで立ち上げたままで,開発環境と別にしたい時は,ormconfig.jsonをdevとprodでわけるといいでしょう.

yarn build
yarn start:prod

こんな画面になるはずです f:id:Naporitan:20191225030223g:plain

おわりに

お疲れ様でした.長い記事になってしまいました.きっとDXが上がるはず!と信じてやってみました.なかなか良さそうではないでしょうか?

今回はReactでやっていますが,Vueでもできると思います.Angularはコマンド一発でできるのでこんなことはしなくていいです.

どうでもいいのですが.明日卒論の目次案のゼミがあります.書くことが何もありません・・・・・NestJSの記事貼り付けようかな・・・・

これと関連している記事でTypeORMとwebpackという記事も書いているのでよかったらみてください naporitan.hatenablog.com

【NestJSアドベントカレンダー】NestJS & TyoeORMをWebackでつかう!!!【21日目】

NestJS & TypeORM & Webpackで優勝したい!

NestJsはmoduleが膨れ上がっていくと, ts-nodeだと起動がめちゃくちゃ遅くなって開発時にすごいストレスになります。それを解決できるのがHMRで、webpackです

qiita.com

はじめに結論

github.com

entities: getMetadataArgsStorage().tables.map(tbl => tbl.target)をTypeOrmModule.forRootのコンフィグに設定すれば行けます

今回のリポジトリです。

github.com

NestJS & TypeORMの環境を作る

プロジェクトを作って, 必要なものをいれる

nest new sample
cd sample
yarn add @nestjs/typeorm typeorm mysql class-transformer class-validator

ormconfig.jsonをつくる

ormconfig.jsonにしたがってmigrationファイルとかentiiyファイルをtypeorm cliが動きます。 typeorm cliがあるとmigrationファイルを生成したり、entityファイルを生成できるので設定しておきましょう。

{
    "type": "mysql",
    "host": "localhost",
    "port": 3307,
    "username": <user_name>",
    "password": <mysql_password>,
    "database": <database_name>,
    "entities": ["src/entities/*.ts"],
    "migrations": ["src/migrations/*.ts"],
    "synchronize": false, 
    "cli": {
        "migrationsDir": "src/migrations",
        "entitiesDir": "src/entities"
    }
}

cliについてはこちらでより詳細に見れるかと思います。

typeorm.io

app.module.tsにtypeormをimportする

先ほど作ったjsonを元にTypeOrmModuleがデータベースと接続を試みます。

import { Module } from '@nestjs/common';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';

import ormConfig from '../ormconfig.json';

@Module({
  imports: [TypeOrmModule.forRoot(ormConfig as TypeOrmModuleOptions)],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

以下のようなエラーが出るときはdatabase周りの設定が間違っているので見直してみましょう

[TypeOrmModule] Unable to connect to the database. Retrying

一応これでTypeORMが使えるようになってWebpackでビルドの方に行ってもいいのですが、どうせならentityとマイグレーションとCR(UD)を作って、Webpackでビルドしても動くことを確かめましょう。

User Entityを作る

package.jsonのscriptsに

"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js"

を追加しておきましょう。yarn typeorm ...でコマンドを使えるようになって便利です。

Userエンティティを作っていきます。

yarn typeorm entity:create -n User

f:id:Naporitan:20191221182110p:plain

Userには名前を保存するようにしましょう

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id!: number;

    @Column('varchar')
    name: string;
}

エンティティ作成おわり

Migrationを作る

yarn migration:generate -n CreateUserTable

これだけでentities配下にあるファイルとデータベースの差分を計差してtimestamp-CreateUserTableという名前でファイルが作られます。 f:id:Naporitan:20191221182500p:plain

これをデータベースに反映させたい時は以下のコマンドでできます

yarn migration:run

データベースにはmigrationsテーブルが勝手に作られそこでどこまでマイグレーションしたかを保存しているみたいです。 f:id:Naporitan:20191221182642p:plain

UserCR(UD)をつくる

nest g mo user
nest g s user
nest g co user
  • user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../entities/User';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}
  • user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../entities/User';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dot/createUser.dto';

@Injectable()
export class UserService {
    constructor(@InjectRepository(User) private readonly userRepository: Repository<User>) {}

    async findById(id: number) {
        return await this.userRepository.findOne(id);
    }

    async create(createUserDto: CreateUserDto) {
        return this.userRepository.save(createUserDto);
    }
}
  • user.controller.ts
import { Controller, Get, Param, Post, UsePipes, ValidationPipe, Body } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dot/createUser.dto';

@Controller('user')
export class UserController {
    constructor(private readonly userService: UserService) {}

    @Get(':id')
    async findUserById(@Param('id') id: string) {
        return this.userService.findById(parseInt(id, 10));
    }

    @Post()
    @UsePipes(new ValidationPipe())
    async create(@Body() createUserDto: CreateUserDto) {
        return this.userService.create(createUserDto);
    }
}
  • src/user/createUserDto.ts
import { IsString, IsNotEmpty } from 'class-validator';

export abstract class CreateUserDto {
    @IsString()
    @IsNotEmpty()
    name: string;
}

実行する

これで動けばNestJSとTypeORMの設定は終わりです。

yarn ts-node src/main.ts

f:id:Naporitan:20191221191304g:plain

WebpackでNestJSをビルドする

without cliをやっていきます docs.nestjs.com

必要なライブラリをインストールする

yarn add -D webpack-node-externals webpack webpack-cli webpack-node-externals ts-loader

webpackでHMRをやる

const webpack = require("webpack");
const path = require("path");
const nodeExternals = require("webpack-node-externals");

module.exports = {
  entry: ["webpack/hot/poll?100", "./src/main.ts"],
  target: "node",
  externals: [
    nodeExternals({
      whitelist: ["webpack/hot/poll?100"],
    }),
  ],
  module: {
    rules: [
      {
        test: /.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  output: {
    path: path.join(__dirname, "dist"),
    filename: "server.js",
  },
};
  • main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

declare const module: any;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

  if (module.hot) {
    module.hot.accept();
    module.hot.dispose(() => app.close());
  }
}
bootstrap();

これをしてyarn webpack --config webpack.cofig.jsしてビルドできれば成功なのですが、できません。

f:id:Naporitan:20191221190637p:plain

こうなると思います。これはormconfig.jsonのentitiesに書いてあるファイルをTypeORMModuleが読みに行こうとするが、commonjsでないため起動できないという感じになります。

これをどう解決するかが、本題なのですが長くなりました。すみません。

答えはこちらです。

github.com

app.modules.tsのTypeOrmModuleでconfigを読み込むところを以下のように変更してください。

import { Module } from '@nestjs/common';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';

import { getMetadataArgsStorage } from 'typeorm';
import ormConfig from '../ormconfig.json';

const { cli, migrations, ...typeOrmConfig } = {
  ...ormConfig,
  entities: getMetadataArgsStorage().tables.map(tbl => tbl.target), // < --これを設定することで
};

@Module({
  imports: [TypeOrmModule.forRoot(typeOrmConfig as TypeOrmModuleOptions), UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

f:id:Naporitan:20191221191539g:plain

できた

おわりに

自分の使っているプロジェクトでやってみたんですけど、起動時間が大幅に短縮されています。 f:id:Naporitan:20191221191720g:plain f:id:Naporitan:20191221191713g:plain

明日は @potato4d さんのExpress + TypeORM NestJS 移行です

にじめぐりをAirPodsProで聞いてみた感想

にじめぐりをAirPodsProで聞いてみる

セットで売られている骨伝達BluetoothイヤホンではなくAirPodsProで聞いてみたオタクの感想ブログです。外音取り込みでにじめぐりを聞いたらテンションが上がってしまっていてもたってもいられなくなり、文章に綴りました。

AirPodsProを書いました

2週間くらいまってようやくきました。めっちゃ待ちました・・・・

AirPodsProのいいところ

  • ノイキャンがなかなかいい
    • エアコンの音とか車の音、換気扇の音などが消えます。音で気が散る人はおすすめです
  • 外音取り込みがすごい
    • これがにじめぐりとの相性の良さを発揮します。
    • 僕はこの機能が良すぎて常にAirPodsProを装着して人と話しています。(補聴器かな?)
  • ヘッドホン・イヤホンと比べて顔に発生する違和感がない
    • これは人それぞれかと思います。
    • ヘッドホンの頭に乗っかる感じは僕はあまり好きじゃないので完全ワイヤレスのイヤホンが気に入っています。

にじめぐりとは

  • 秋葉原と両国を歩いているとライバーから話しかけてくれる
  • チェックポイントに到達するとライバー同士の会話が聴ける

という感じのGPSと音声を利用したアプリケーションです。

秋葉原に寄ったときに5箇所くらいだけ回ったのですが、本当に最高でした・・・・・・近々時間を空けて再チャレンジする予定です

にじめぐり × AirPodsProの良さ

外音取り込みの精度のよさ。これに尽きると思います。AirPodsProはイヤホンの外にあるマイクで拾った音をスピーカーで再生しているようなものなので(厳密にはどうなっているかは知りませんが)本当に街の雑踏の中にライバー同士の会話が紛れ込んでいるような印象を受けました。

これは少し慣れが必要なのですが、僕は補聴器のように使っていると述べたように1日のほとんどをAirPodsProをつけてすごしています。そうするとつけている感覚が薄れるのです。完全ワイヤレスなのも感覚が薄れる1つの要因になっているでしょう。薄れている状態で、外音取り込みをしながらにじめぐりを聞く!はい、最高。良すぎ!そこにいる。ライバーたちが見えた!!!!!!

最高すぎてイケメンになっちゃいました。

さいこう
さいこう

こんなかんじになれるのでにじめぐり × AirPodsProは相性がいいよって話でした。

AirPodsPro3万だけど常につけてるしライバーが見える(幻覚)ので0円!!!!!!!!!!!!!!