Open masayuki-0319 opened 3 years ago
ディレクトリイメージ
https://qiita.com/isihigameKoudai/items/4b790b5b2256dec27d1f
yarn init -y
mkdir dist src webpack
yarn add -D typescript ts-node nodemon
yarn tsc --init
コードは元記事参照
yarn add -D eslint eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier
touch .eslintrc
yarn add express
yarn add -D @types/express body-parser
コードは元記事参照
touch src/index.ts
コードは元記事参照
touch nodemon.json
yarn add -D webpack webpack-cli webpack-merge \
webpack-node-externals ts-loader \
@types/webpack-dev-server @types/webpack-node-externals
touch webpack/base.config.ts webpack/dev.config.ts webpack/prod.config.ts
import { Configuration } from 'webpack';
import path from 'path';
import webpackNodeExternals from "webpack-node-externals"
const BUILD_ROOT = path.join(__dirname, "../dist");
const SRC_ROOT = path.join(__dirname, "../src");
export const baseConfig: Configuration = {
context: SRC_ROOT,
entry: path.resolve("src", "index.ts"),
externals: [webpackNodeExternals()],
output: {
filename: "server.js",
path: BUILD_ROOT
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
loader: "ts-loader",
options: {
configFile: "tsconfig.json"
}
}
]
},
resolve: {
extensions: [".ts", ".js", ".json"],
alias: {
"@": path.join(__dirname, "/src/")
}
}
};
~TS にしたいけど Issue 未解決らしい~ ~https://shunbiboroku.com/post/webpack-config-ts-error~
@type/webpack-dev-server
だけで解決した。
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/27570
contentBase は、static
に置き換わった。
https://ja.stackoverflow.com/questions/82189/webpack%E3%81%AEdevserver-%E3%81%AF%E3%81%A9%E3%81%86%E3%82%84%E3%81%A3%E3%81%A6%E7%AB%8B%E3%81%A1%E4%B8%8A%E3%81%92%E3%82%8B%E3%81%AE%E3%81%A7%E3%81%97%E3%82%87%E3%81%86%E3%81%8B
import { Configuration } from 'webpack';
import { merge } from "webpack-merge"
import { baseConfig } from "./base.config"
const devConfig: Configuration = {
mode: "development",
devtool: "inline-source-map",
devServer: {
host: "0.0.0.0",
port: 3000,
static: {
directory: "dist",
}
}
};
const config = merge(baseConfig, devConfig);
export default config;
import { Configuration } from 'webpack';
import { merge } from "webpack-merge"
import { baseConfig } from "./base.config"
const prodConfig: Configuration = {
mode: "production"
};
const config = merge(baseConfig, prodConfig);
export default config;
{{
"scripts": {
"dev": "nodemon -L",
"build": "webpack --config ./webpack/prod.config.ts",
"start": "npm run build && node dist/server.js"
}}
git init
touch .gitignore
# .gitignore
dist
node_modules
Express は default logger として morgan を採用している。 しかし、高機能な他ライブラリと連携するケースが存在する様子。 https://www.npmtrends.com/morgan-vs-pino-vs-winston-vs-roarr
ここでは morgan + winston で設定してみる。
https://github.com/expressjs/morgan https://github.com/winstonjs/winston https://github.com/pinojs/pino https://github.com/gajus/roarr
記事
ライブラリ導入・設定
yarn add morgan winston
yarn add -D @types/morgan
ディレクトリ構成
winston
import path from 'path';
import winston, { LoggerOptions } from 'winston';
const LOG_DIR = path.join(__dirname, '../../log');
const APP_ENV = process.env.NODE_ENV || 'development';
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
debug: 4,
};
const level = () => {
const isDevelopment = APP_ENV === 'development';
return isDevelopment ? 'debug' : 'http';
};
const format = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format.colorize({ all: true }),
winston.format.printf((log) => `${log.timestamp},${log.level},${log.message}`)
);
const APPLICATION_LOGFILE = `${LOG_DIR}/${APP_ENV}.log`;
const ERROR_LOGFILE = `${LOG_DIR}/${APP_ENV}.error.log`;
const transports = [
new winston.transports.Console(),
new winston.transports.File({
filename: ERROR_LOGFILE,
level: 'error',
}),
new winston.transports.File({ filename: APPLICATION_LOGFILE }),
];
const loggerOptions: LoggerOptions = {
level: level(),
levels,
format,
transports,
};
const levelColorOption = {
error: 'red',
warn: 'yellow',
info: 'green',
http: 'magenta',
debug: 'white',
};
winston.addColors(levelColorOption);
const logger = winston.createLogger(loggerOptions);
export { logger };
morgan
import morgan, { StreamOptions } from 'morgan';
import { logger } from './winston';
const stream: StreamOptions = {
write: (message) => {
logger.http(message);
},
};
const skip = () => {
const env = process.env.NODE_ENV || 'development';
return env !== 'development';
};
const format = ':method :url :status :res[content-length] - :response-time ms';
const requestLogger = morgan(format, {
stream,
skip,
});
export default requestLogger;
config ファイル
import { Express } from 'express';
import { json, urlencoded } from 'body-parser';
import { requestLogger } from './logger/morgan';
const config = (server: Express) => {
server.use(json());
server.use(urlencoded({ extended: false }));
server.use(requestLogger);
};
export default config;
初心者にとってデバッグ環境は重要。 自分の書いたコードがどのように実行されているのか、特定地点における scope の状態がイメージできないため。
なので、Rails の byebug-pry のように step-by-step でデバッグできるようにしたい。
https://qiita.com/nemutas/items/b6dcaea6aab9f64ecbe4#%E3%83%87%E3%83%90%E3%83%83%E3%82%B0 https://code.visualstudio.com/docs/typescript/typescript-debugging
mkdir .vscode
touch launch.json
launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Node: Nodemon",
"processId": "${command:PickProcess}",
"restart": true,
"protocol": "inspector"
}
]
}
デバッグモード起動
F5 ボタン押下するだけで起動する。 赤丸のブレークポイントで変数等を確認できる。
プロジェクトの構造がディレクトリの外観だけで理解できると素敵な気がする。 Rails に寄せても十分だけど、Express 特有の考え方あれば知りたい。
https://qiita.com/baby-degu/items/f1489dd94becd46ab523 https://qiita.com/nkjm/items/2016e331f74f1b8ab465 https://memo.sugyan.com/entry/20120110/1326197416 https://qiita.com/_takwat/items/558e721516a88071e962 https://teratail.com/questions/1409 https://swallow-incubate.com/archives/blog/20190425 https://t-yng.jp/post/consideration-typescript-monorepo
SDK テスト用のモックプロジェクトなので、気にしないことにした。
完成イメージは notion SDK https://github.com/makenotion/notion-sdk-js
npm モジュールの基本 https://qiita.com/TsutomuNakamura/items/f943e0490d509f128ae2
TS で npm モジュール作成 https://zenn.dev/arark/articles/cc58ba9bb79d80ec9cd3 https://infltech.com/articles/Qd8UZv https://qiita.com/i-tanaka730/items/c85daa3ee2dcde9bd728
npm 非公開モジュールを自分用で利用 https://final.hateblo.jp/entry/nodejs-publish-library
mkdir speedrun-sdk-js
cd speedrun-sdk-js
yarn init -y
yarn add -D typescript
yarn tsc --init --target es6 --module esnext --declaration true --outDir ./build --rootDir ./src
.npmignore
/node_modules
/src
tsconfig.json
package.json
{
"name": "speedrun-sdk-js",
"version": "0.0.1",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"publishConfig": {
"access": "public"
},
"type": "module",
"license": "MIT",
"devDependencies": {
"typescript": "^4.4.3"
}
}
https://github.com/facebook/jest
https://qiita.com/mima_ita/items/558ec8cee2c0e1005ffd https://jestjs.io/docs/getting-started https://sbfl.net/blog/2019/01/20/javascript-unittest/
install
yarn add -D jest @types/jest
yarn run jest --init
yarn add -D babel-jest @babel/core @babel/preset-env @babel/preset-typescript
babel.config.cjs 作成
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
};
test作成
// test/module.test.ts
import { IamExported } from "../src/module";
test("IamExported returns greeting", () => {
expect(
IamExported("arark")
).toContain("Hello, arark!!");
});
rootDir エラー対策 root directory に test を配置したいため、エラーが発生した。 https://qiita.com/masato_makino/items/bf640a253d56b708fe0b
{
"compilerOptions": {...},
"include": [
"src"
],
"exclude": [
"build"
]
}
jest.config.js
/*
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
export default {
clearMocks: true,
};
git 設定 省略
開発環境において nodemon がファイルの変更を検知して自動更新してくれない
上記の通り、自動更新できるようにしたい
ext
が不足してた様子
https://chaika.hatenablog.com/entry/2020/09/28/083000
nodemon.json
{
"restartable": "rs",
"env": {
"NODE_ENV": "development"
},
"ext": "ts, js, json",
"watch": [
"src"
],
"ignore": [
"tests/**/*.ts"
],
"exec": "ts-node ./src/index.ts"
}
第一歩として、1つの Endpoint に着目して開発してみる
modular SDK の代表格 https://github.com/firebase/firebase-js-sdk https://github.com/aws/aws-sdk-js-v3
SDK の粒度的にかなり参考にできそう https://github.com/bizon/selling-partner-api-sdk https://github.com/contentful/contentful-sdk-core https://github.com/cosmos/cosmos-sdk https://github.com/onelogin/onelogin-node-sdk
modular SDK 使用例 https://firebase.google.com/docs/auth/web/start#web-version-9 https://zenn.dev/hiro__dev/articles/605161cd5a7875
response の型定義 https://github.com/SlackAPI/node-slack-sdk
なぜ modular SDK が良いのか 以前からの課題として firebase の bundle size が巨大なため、通信が遅いユーザにとって UX が悪くなるらしい。 https://dev.to/chroline/why-the-new-firebase-web-v9-modular-sdk-is-a-game-changer-nph
[index, css, custom hook, test]
のような構成事例ありprivate function もテストしたい場合 module の振る舞いを維持できれば良いと思うため、 export しない function は流石にテストの必要性が低い気もする。 また、同じファイルに https://zenn.dev/ptpadan/articles/jest-same-test-file
しかし、テスト対象を近いディレクトリに設置するのは、非常に共感できる。 https://github.com/YasushiKobayashi/samples/pull/59/files#diff-921972e127dc76b6da29b456d7b53e73cc3965a32e30d4cd980df497abe041b0R27-R31
jest 公式が tests ディレクトリを紹介 https://jestjs.io/ja/docs/configuration https://qiita.com/hogesuke_1/items/8da7b63ff1d420b4253f https://golang.hateblo.jp/entry/2021/03/12/214501
慣例及び jest のチュートリアルに従い、__tests__
ディレクトリを選択する。
個人的には、テスト対象と同じディレクトリに置きたい。 しかし、jest は初めて触れるため、基本に従う。
https://jestjs.io/ja/docs/configuration https://github.com/facebook/jest/issues/11453#issuecomment-877653950
ts-node が必要
yarn add -D ts-node @types/node
tsconfig.json 編集
{
"compilerOptions": {...},
+ "ts-node": {
+ "moduleTypes": {
+ "jest.config.ts": "cjs"
+ }
+ },
}
他にもライブラリあるけど、axios がデファクトなのかも https://nodejs.libhunt.com/axios-alternatives
axios で良し
いや...node-fetch が押してる? SDK も node-fetch による実装が多い。 https://www.npmtrends.com/axios-vs-node-fetch
しかし、高機能なのはやはり axios https://blog.openreplay.com/fetch-vs-axios-which-is-the-best-library-for-making-http-requests
一部ライブラリでは、node-fetch を default として、option で設定可能にしてる事例もある。 後から node-fetch に切り替えられるようにコードを分離すること。
結論として modular SDK で進める。
全ての path を型定義 https://github.com/firebase/firebase-js-sdk/blob/d3041d875a/packages/auth/src/api/index.ts
endpoint の振る舞いを定義 ( params, Request, Response ) https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/api/authentication/sign_up.ts
共通の HTTP Client https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/api/index.ts#L80
HTTP ライブラリの使用箇所
platform 毎にライブラリが異なるため、FetchProvider.fetch()
で隠してる。
https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/api/index.ts#L116-L124
platform 毎に HTTP ライブラリを initialize してる。 firebase では内部的に node-fetch を使用してる。 https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/platform_node/index.ts#L33-L37
HTTP Client に必要な Config 情報
https://github.com/onelogin/onelogin-node-sdk/blob/master/lib/http_clients/onelogin_http_client.ts https://github.com/contentful/contentful-sdk-core/blob/master/src/create-http-client.ts https://github.com/bizon/selling-partner-api-sdk/blob/master/packages/auth/src/selling-partner-api-auth.ts
公式 https://jestjs.io/ja/docs/tutorial-async
実装例 外部 API https://tech.bitbank.cc/lets-test-by-jest/ https://zenn.dev/chida/articles/cec625e3b6aa7b https://medium.com/@lachlanmiller_52885/%E9%9D%9E%E5%90%8C%E6%9C%9F%E3%81%AB%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92jest%E3%81%A7%E3%83%86%E3%82%B9%E3%83%88-e7f40fb6cffa
内部 API https://www.agent-grow.com/self20percent/2019/03/25/only-express-and-jest-testing/ https://dev.to/franciscomendes10866/testing-express-api-with-jest-and-supertest-3gf
公式ドキュメントが十分すぎる。
https://github.com/speedruncomorg/api
全てのベースとなる games を選定 https://github.com/speedruncomorg/api/blob/master/version1/games.md
確か API の response を定義するとか聞いたことある。 https://github.com/speedruncomorg/api/blob/master/version1/json-schema/definitions.json
https://tech.degica.com/ja/2015/10/16/json-schema-ja/ https://dev.classmethod.jp/articles/json-validation/ https://myenigma.hatenablog.com/entry/2019/05/12/195935 https://qiita.com/arumi8go/items/a9530cbd39ff545a7bbb
自由に設定できてしまう json を定義するツール
OpenAPI と同じ目的 https://blog.stoplight.io/openapi-json-schema
Speedrun.com のリポジトリ見ると、ファイルも少ないので気にしなくてよさそう
mock 用の json を手に入れる
games
https://www.speedrun.com/api/v1/games
games/:id
( SMS )
https://www.speedrun.com/api/v1/games/v1pxjz68
※ 他にもあるが最初は2つのみで良い
...
以下の通りで良いか?
Twitter 取得対象の object が必要な key の所有確認 https://github.com/plhery/node-twitter-api-v2/blob/master/test/tweet.v2.test.ts#L14-L38
楽天 リクエストの stub 用意して、 mock 用 JSON をレスポンスを設置して、 module の実行結果が、期待する JSON であるか否かをテストしてる。 これがいつものイメージに近い。 https://github.com/rakuten-ws/rws-ruby-sdk/blob/master/spec/rakuten_web_service/books/book_spec.rb
here map 楽天と同じ。 ただ、mock コードは分離してた方が好みだが、JS で用意するのが普通なのか? https://github.com/heremaps/here-data-sdk-typescript/blob/master/tests/functional/StreamLayerReadData.test.ts
DMM リクエストの path, query、コールバック処理のみ。 最低限のテストっぽい。 https://github.com/dmmlabo/dmm-js-sdk/blob/master/test/genre_spec.js
square sandbox にリクエスト投げてる...!リッチ! https://github.com/square/square-nodejs-sdk/blob/master/test/testClient.ts
正常系 https://github.com/dropbox/dropbox-sdk-js/blob/main/test/integration/user.js#L49-L64
異常系 https://github.com/dropbox/dropbox-sdk-js/blob/main/test/integration/user.js#L130-L154
Background
JS 用の firebase SDK がいつの間にか V9 で構造が変わってた。 https://github.com/firebase/firebase-js-sdk
モジュラー SDK と呼ばれて、package/* に firebase の機能単位で library が存在している。 https://firebase.google.com/docs/web/modular-upgrade https://zenn.dev/hiro__dev/articles/605161cd5a7875
lerna って library を採用して実現しているので、試してみる。
Goals
References
official
SDK 実装例 ( lerna )
SDK 実装例
実装イメージに近い https://github.com/dropbox/dropbox-sdk-js https://github.com/facebook/facebook-nodejs-business-sdk https://github.com/spree/spree-storefront-api-v2-js-sdk
jest https://github.com/contentful/contentful-sdk-core https://github.com/meilisearch/meilisearch-js
node-fetch https://github.com/entur/sdk
others https://github.com/dilame/instagram-private-api https://github.com/paypal/Checkout-NodeJS-SDK https://github.com/line/line-bot-sdk-nodejs https://github.com/square/square-nodejs-sdk https://github.com/Vonage/vonage-node-sdk https://github.com/plhery/node-twitter-api-v2 https://github.com/onelogin/onelogin-node-sdk https://github.com/heremaps/here-data-sdk-typescript https://github.com/strapi/strapi-sdk-javascript
hands-on