Web関連
Android開発: -> Kotlin
asdf
asdf 自体のインストール
asdf グローバルなバージョン指定
| asdf global <lang-name> <version>
|
asdf 自動でインストールするパッケージの設定
~/.default-npm-packages
に書き込めばよい Python
の場合は~/.default-python-packages
などとすればよい
asdf 導入できる言語を調べる
| asdf plugin list all
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
|
asdf 特定言語・特定バージョンのアンインストール
| asdf uninstall <lang-name> <version>
|
asdf 特定言語のバージョンの一覧
asdf 特定言語のバージョンをインストール
| asdf install <lang-name> <version>
asdf install nodejs latest
asdf install nodejs 16.17.0
asdf install nodejs 18.12.0
|
asdf 特定ディレクトリでバージョン選択
- コマンド
asdf local nodejs 20.7.0
を実行する - そのディレクトリにファイル
.tool-versions
が置かれ, バージョンが指定される. - 手動で
.tool-versions
を作ってもよい.
| asdf local <lang-name> <version>
asdf local nodejs 16.17.0
asdf local nodejs 20.7.0
|
Chrome
拡張機能が表示されない
- 参考: 対処2
- ブラウザ画面の右上にある「拡張機能(パズルのピースのアイコン)」をクリック
- 表示された拡張機能の一覧から、表示したい拡張機能の「ピンマーク」をクリックして固定化
全体のスクリーンショットを撮る
- 参考
- 2022/05時点では少し違うUIになっているようなので注意する
- 手順
- デベロッパーツールを立ち上げる
- WindowsではCtrl+Shift+P, MacではCmd+Shift+Pを押す
- 入力欄でscreenshotと打つ
- 「フルサイズのスクリーンショットをキャプチャ」的な項目を選ぶ
CSS
CSS CSS道場的な何か
CSS style
タグでHTMLに直接指定
| <style>
body {
background-color: #00ff00
}
</style>
|
CSS アプリのようにフッタを画面下部に張り付ける
| #sp-fixed-menu{
position: fixed;
width: 100%;
bottom: 0px;
z-index: 99;
backgroundColor: white;
}
|
CSS 外部ファイルの読み込み
| <link rel="stylesheet" href="https://cdn.simplecss.org/simple-v1.css">
|
CSS 長い文を途中で切る
| .shortcut {
width: 300px; /* 要素の横幅を指定 */
white-space: nowrap; /* 横幅のMAXに達しても改行しない */
overflow: hidden; /* ハミ出した部分を隠す */
text-overflow: ellipsis; /* 「…」と省略 */
}
|
CSS ブロック要素を中心に置く
Dart/Flutter
インストール
Mac
まずは公式を見よう.
const, final
- 参考
- finalは変数の性質
- 「final変数に代入できない」など
- 代入の左辺になったときの変数としての扱いについて差が出る
- しかしfinal変数の保持する値には影響しない
- constは変数の性質かつconst変数が保持する値の性質も規定
- つまりconst変数の値を他の変数に代入したり、関数の引数として値を渡すときにconstが付く・付かないで制約回避できる・できないといった差異が出る場合あり
final指定
- プログラム開始後のある時点で一回だけ初期化される.
- 初期化以降は代入などを通じて変更されない/できないことが保証される(再代入不可)
- なお、finalな変数が「指す先」のメモリ領域の内容が変更されることについての制約はない
| final int i = 0;
i = 2; // Error(再代入不可)
final List<int> a = [1,2,3];
a[0] = 4; // OK(指す先は変更可能)
|
const指定
- 変数の値が「コンパイル時定数(compile-time constant)」
- constな変数の値はプログラムの実行開始に先立って初期化されていてプログラムの実行を通じて不変
- const変数は再代入も不可: finalの意味に加えてconstな変数が指す先のメモリ領域の内容も変更不可
| const int i = 0;
i = 2; // Error(再代入不可)
const List<int> a = [1,2,3]; // OK ※1
const List<int> b = const [1,2,3]; // OK(const値)
b[0] = 4; // コンパイルエラー(指す先も変更不可)
|
JavaScript/TypeScript
axios CSRF token mismatch
- 参考
- 特に
Laravel
のサーバーが提供するAPIを叩いたときにエラーでこれが出たとき Laravel
側に設定を追記してみよう app/Http/Middleware/VerifyCsrfToken.php
に次のように追記
| protected $except = [
'/api/*'
];
|
CORSエラー時の対処
express
の場合
yarn add cors
を実行 - バックエンド側に次のコードを実装
| const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())
app.get('/user/:userId', function (req, res, next) {
res.json({result: '任意のオリジンからすべてのAPIがアクセスOK'})
})
|
eslint 先頭に_
を含む未使用の変数を許容する
- URL
.eslint.json
のrules
オブジェクトに次の要素を追加する
| "rules": {
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_"
}
]
},
|
express エラー処理
express 単純なexpress serverのデプロイ, TypeScript
- 参考
yarn add -D typescript @type/node
- 適当に
server.ts
などを作る: index.js
のコピペそのままで可 package.json
のscripts
に追記
| {
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean",
"start": "yarn build; node dist/server.js"
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13 | {
"compilerOptions": {
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "es5",
"outDir": "dist"
},
"include": [
"**/*"
]
}
|
Procfile
をweb: yarn start
に修正 - ローカルで
yarn start
してからAPIにアクセスして正しく値が返ってくるか確認 - デプロイ
express prismaと連携したREST API
- prismaとexpressでつくるREST API
- データベースはPostgresにしているものの, すぐMySQLに書き換えられる上, 十分参考になる
- データベースは{docker}で構築している
- dockerについてはdockerを参考にすること.
- テストの
beforeEach
で呼ぶ初期化はPrisma テストで実際にデータベースを読み書きする
の項目を参考にすること
fastify, prisma
apps/fastify
に移動 npx prisma init
apps/fastify/prisma
にschema.prisma
が生成される schema.prisma
を編集 npx prisma migrate dev --name init
を実行 apps/fastify/.env.sample
をコピーして.env
を作る - TODO: ルートディレクトリにまとめられないか確認
npx prisma migrate dev --name init
- TODO:
workspace.json
にタスクとして登録したい - seedを入れたい場合は次のように進める
package.json
に"type": "module"
を設定 NODE_OPTIONS="--loader ts-node/esm" node prisma/seed.ts
を実行
fastify main.ts
への設定
1
2
3
4
5
6
7
8
9
10
11
12 | app.listen(
{
port: Number(process.env.PORT) || 3000,
host: process.env.HOST || '0.0.0.0', // ここも入れる
},
(err) => {
if (err) {
console.error(err);
process.exit(1);
}
}
);
|
Fastify+PrismaでJWT認証付きREST-APIサーバーを作る
Firebase Authentication
Firebase Cloud Functionsの環境変数設定
- URL
firebase functions:config:set slack.apikey="THE API KEY"
などのコマンドで設定する - サーバー側にも同時に設定される
- 変数名は全て小文字
- 【必須】ローカルで開発用に
firebase serve
する前に次のコマンド実行する functions
フォルダの下に置かないと認識してくれないらしい
| firebase functions:config:get > functions/.runtimeconfig.json
|
| const functions = require('firebase-functions');
exports.printenvSlack = functions.https.onRequest( (req, res) => {
const config = functions.config();
if( config.slack && config.slack.apikey ){
res.send( config.slack.apikey );
}
else{
res.send("Not Found Config");
}
});
|
Firebase Cloud Functionsのデプロイエラーログを見る
- URL
firebase functions:log
コマンドでデプロイ時のエラーログが見られる
Firebase v9: 認証の永続化
- URL
- 2022/7/28時点でこのページの「サポートされている認証状態の永続性のタイプ」の列挙型欄はおかしい
- ここの値参照: 公式ではどこにある?
- 結論: v9では次のように書けばよい
1
2
3
4
5
6
7
8
9
10
11
12 | setPersistence(auth, browserLocalPersistence).then(() => {
return signInWithEmailAndPassword(auth, email, password)
.then((res) => {
const user = res.user;
setLoginUser(user);
setLoginErrorMessage("");
}).catch((error) => {
setLoginErrorMessage(`${error.code}: ${error.errorMessage}`);
});
}).catch((error) => {
setLoginErrorMessage(`${error.code}: ${error.errorMessage}`);
});
|
Firebase エミュレーターを起動
Firebase 開発環境と本番環境をわける
- URL
- 環境ごとにプロジェクトを作る
firebase projects:list
で環境を確認 firebase use --add
で使う環境を追加 firebase use <alias>
で適切な環境を設定 firebase functions:secrets:set
で環境ごとに必要な環境変数を設定
Firebase サンプルプロジェクト
Firebase プロジェクトで使うアカウントの設定
| firebase login:use someuser@example.com
|
Firebase プロジェクトの初期化
Firebase プロジェクトの変更
| firebase login # 必要に応じてアカウント変更
firebase projects:list # 変更したいプロジェクトIDの確認
firebase use <project-id> # 変更コマンド
|
Firebase ログイン情報の確認
Firebase ログイン情報の追加
husky 設定
- URL
husky.sh
は.gitignore
に追加
| npm install husky --save-dev
npx husky install
|
| "scripts": {
"prepare": "husky install"
}
|
npx husky add .husky/pre-commit "npm run lint"
でファイルに追記できる
JavaScript: sleep()
は一行で書ける
| (async function (ms) { await new Promise(s => setTimeout(s, ms)) })(3000);
|
Javascript: Nullish Coalescing
とOptional Chaining
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | const users = [
{
name: 'Patty Rabbit',
address: {
town: 'Maple Town',
},
},
{
name: 'Rolley Cocker',
address: {},
},
null,
];
for (const u of users) {
const user = u ?? { name: '(Somebody)' };
const town = user?.address?.town ?? '(Somewhere)';
console.log(`${user.name} lives in ${town}`);
}
// Patty Rabbit lives in Maple Town
// Rolley Cocker lives in (Somewhere)
// (Somebody) lives in (Somewhere)
|
Javascript: オブジェクトに対する反復処理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | const user = {
id: 3,
name: 'Bobby Kumanov', username: 'bobby',
email: 'bobby@maple.town',
};
console.log(Object.keys(user));
// [ 'id', 'name', 'username', 'email' ]
console.log(Object.values(user));
// [ 3, 'Bobby Bear', 'bobby', 'bobby@maple.town' ]
console.log(Object.entries(user));
//[
// ['id',3],
// ['name','BobbyKumanov'],
// ['username','bobby'],
// ['email','bobby@maple.town']
//]
// キーと値のペアを反復処理の中で扱う
Object.keys(user).map((k) => { console.log(k, user[k]); });
Object.entries(user).map(([k, v]) => { console.log(k, v) });
|
JavaScript: オブジェクトのあるキーを上書きする
| const obj1 = {key1: "value1", key2: "value2"};
const obj2 = {key3: "value3"};
const obj3 = {...obj1, key1:"converted"};
{...obj1, ...obj2} // => { key1: 'value1', key2: 'value2', key3: 'value3' }
obj3 // => { key1: 'converted', key2: 'value2' }
|
JavaScript: オブジェクトの一部の要素しかほしくないとき
| const sns = ((obj) => (obj.width))(someBigObject);
|
JavaScript: オブジェクトに対してmap
したいとき
- 参考
- 対象のオブジェクト
obj
に対してObject.keys(obj).map(home => some)
となどすればよい - キーだけを得たいなら
keys
, 値だけならvalues
, 両方はentries
JavaScript: シャローコピーはスプレッド構文で
| constoriginal={a:1,b:2,c:3};
const copy = { ...original };
console.log(copy); // { a: 1, b: 2, c: 3 }
console.log(copy===original); // false
|
JavaScript: ディープコピー
- プロパティに
Date
オブジェクトや関数, undefined
などがない場合はJSON.stringify()
で書ける - 一般には
Lodash
などライブラリを使う
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | const patty = {
name: 'Patty Rabbit',
email: 'patty@maple.town',
address: { town: 'Maple Town' },
};
const rolley = JSON.parse(JSON.stringify(patty));
rolley.name = 'Rolley Cocker';
rolley.email = 'rolley@palm.town';
rolley.address.town = 'Palm Town';
console.log(patty);
//{
// name:'PattyRabbit',
// email:'patty@maple.town',
// address:{town:'MapleTown'}
//}
|
JavaScript: テーブルの行の並べ替え
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | <!DOCTYPE html>
<html>
<head>
<title>Drag-and-drop Sortable List Demo</title>
<meta charset="utf-8">
</head>
<body>
<table>
<thead>
<tr>
<th>EMAIL</th>
</tr>
</thead>
<tbody id="products">
<tr id="product-0" draggable="true">
<td>jhon@gmail.com</td>
</tr>
<tr id="product-1" draggable="true">
<td>marygirl@yahoo.com</td>
</tr>
<tr id="product-2" draggable="true">
<td>cha24@yahoo.com</td>
</tr>
</tbody>
</table>
<script>
let row;
for (let i = 0; i < 3; i++) {
const id = "product-" + i.toString();
const tr = document.getElementById(id);
tr.addEventListener("dragstart", event => {
row = event.target;
});
tr.addEventListener("dragover", event => {
const e = event;
e.preventDefault(event);
let children= Array.from(e.target.parentNode.parentNode.children);
if(children.indexOf(e.target.parentNode)>children.indexOf(row)) {
e.target.parentNode.after(row);
} else {
e.target.parentNode.before(row);
}
});
}
</script>
</body>
</html>
|
JavaScript: 配列に対するrange
, 反復処理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | > [...Array(3)]
[ undefined, undefined, undefined ]
> [...Array(3)].map((_, n) => { console.log(`${n + 1} times`); });
1 times
2 times
3 times
> [...Array(3).keys()]
[0,1,2]
> [...Array(3).keys()].map((n) => { console.log(`${n + 1} times`); });
1 times
2 times
3 times
|
JavaScript: リストの並べ替え
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 | <!DOCTYPE html>
<html>
<head>
<title>Drag-and-drop Sortable List Demo</title>
<meta charset="utf-8">
<style type="text/css">
.slist {
list-style: none;
padding: 0;
margin: 0;
}
.slist li {
margin: 10px;
padding: 15px;
border: 1px solid #dfdfdf;
background: #f5f5f5;
}
.slist li.hint {
border: 1px solid #ffc49a;
background: #feffb4;
}
.slist li.active {
border: 1px solid #ffa5a5;
background: #ffe7e7;
}
* {
font-family: Arial, Helvetica, sans-serif;
box-sizing: border-box;
}
</style>
<script src="sort-list.js"></script>
</head>
<body>
<ul id="sortlist" class="slist">
<li draggable="true" ondragstart="dragStart(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" ondragend="dragEnd(event)" ondragover="dragOver(event)" ondrop="drop(event)">First</li>
<li draggable="true" ondragstart="dragStart(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" ondragend="dragEnd(event)" ondragover="dragOver(event)" ondrop="drop(event)">Second</li>
<li draggable="true" ondragstart="dragStart(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" ondragend="dragEnd(event)" ondragover="dragOver(event)" ondrop="drop(event)">Third</li>
<li draggable="true" ondragstart="dragStart(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" ondragend="dragEnd(event)" ondragover="dragOver(event)" ondrop="drop(event)">Forth</li>
<li draggable="true" ondragstart="dragStart(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" ondragend="dragEnd(event)" ondragover="dragOver(event)" ondrop="drop(event)">Fifth</li>
</ul>
<script>
const target = document.getElementById("sortlist");
let items = target.getElementsByTagName("li");
let current = null;
function dragStart(e) {
current = e.target;
for (let it of items) {
if (it != current) { it.classList.add("hint"); }
}
}
function dragEnter(e) {
if (e.target != current) { e.target.classList.add("active"); }
}
function dragLeave(e) {
e.target.classList.remove("active");
}
function dragEnd(){
for (let it of items) {
it.classList.remove("hint");
it.classList.remove("active");
}
}
function dragOver(e) { e.preventDefault(); }
function drop(e) {
e.preventDefault();
if (e.target != current) {
let currentpos = 0, droppedpos = 0;
for (let it=0; it<items.length; it++) {
if (current == items[it]) { currentpos = it; }
if (e.target == items[it]) { droppedpos = it; }
}
if (currentpos < droppedpos) {
e.target.parentNode.insertBefore(current, e.target.nextSibling);
} else {
e.target.parentNode.insertBefore(current, e.target);
}
}
}
</script>
</body>
</html>
|
Jest: Macでテストが動かない, watchman warning
- 参考
brew uninstall watchman
でwatchman
をアンインストールする React Native
のテストをJest
で書く場合は対処が必要らしい
Jest: tsconfig.json
のpaths
設定をテストでも通したい
- URL
tsconfig.jest.json
を作って次のように書く.
| {
"extends": "./tsconfig.json",
}
|
yarn add -D ts-jest
を実行 jest.config.js
を次のように書く. - うまく動かない場合は
prefix: "<rootDir>/src"
で/src
があるかも確認
| const { pathsToModuleNameMapper } = require("ts-jest"); // 追加
const { compilerOptions } = require("./tsconfig"); // 追加
module.exports = {
roots: ["<rootDir>/src"],
testMatch: ["**/__tests__/**/*.+(ts|tsx|js)", "**/?(*.)+(spec|test).+(ts|tsx|js)"],
transform: {
"^.+\\.(ts|tsx)$": ["ts-jest", { tsconfig: "tsconfig.jest.json" }]
},
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/src" }) // 追加
};
|
KaTeX: 導入
KaTeXへのマクロ導入
LIFFアプリ初期化
| npx @line/create-liff-app
|
LIFF: liff-inspector
LocalStorage
Vercelでエラーがときの対応
localStorage.getItem()
はキーがないとnull
を返す - 文字列が常に返ってくる前提でいるとエラーが出る
Vercel
だとApplication error: a client-side exception has occurred (see the browser console for more information).
が出る Firefox
だとまともなエラーメッセージが出ない可能性があるのでChrome
で見てみよう
MISC
XAMPPでSSLを有効にする
(2021/9 時点で)httpsは標準的になっていて, 各所でhttpだといろいろな問題が起きる. localhostはいろいろと特殊という話もあり, 状況に応じて考えるべきだが, 現時点でここではWindowsでのいわゆる「オレオレ証明書」の発行とインストール法を書く. 特に次のページを参考にすればよい.
MUI HTML5 Validation
- URL
- 上記URLに沿って実装すればよい
- 念のため下記に転載:
const emailRef = useRef<HTMLInputElement>(null);
の型づけが重要
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 | import { Button, Container, TextField } from "@mui/material";
import React, { useRef, useState } from "react";
type OnChangeEvent = React.ChangeEvent<HTMLInputElement>;
const EmailVaildPattern =
"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$";
const App = () => {
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const confirmPasswordRef = useRef<HTMLInputElement>(null);
const [emailValue, setEmailValue] = useState("");
const [passwordValue, setPasswordValue] = useState("");
const [confirmPasswordValue, setConfirmPasswordValue] = useState("");
const [emailError, setEmailError] = useState(false);
const [passwordError, setPasswordError] = useState(false);
const [confirmPasswordError, setConfirmPasswordError] = useState(false);
const formValidation = (): boolean => {
let valid = true;
const e = emailRef?.current;
if (e) {
const ok = e.validity.valid;
setEmailError(!ok);
valid &&= ok;
}
const p = passwordRef?.current;
if (p) {
const ok = p.validity.valid;
setPasswordError(!ok);
valid &&= ok;
}
const c = confirmPasswordRef?.current;
if (c) {
if (confirmPasswordValue.length > 0 &&
passwordValue !== confirmPasswordValue) {
c.setCustomValidity("パスワードが一致しません");
} else {
c.setCustomValidity("");
}
const ok = c.validity.valid;
setConfirmPasswordError(!ok);
valid &&= ok;
}
return valid;
};
return (
<Container component="main" maxWidth="xs">
<TextField
margin="normal"
fullWidth
required
inputRef={emailRef}
value={emailValue}
error={emailError}
helperText={emailError && emailRef?.current?.validationMessage}
inputProps={ {required: true, pattern: EmailVaildPattern} }
onChange={(e: OnChangeEvent) => setEmailValue(e.target.value)}
label="Email"
/>
<TextField
margin="normal"
fullWidth
required
type="password"
inputRef={passwordRef}
value={passwordValue}
error={passwordError}
helperText={passwordError && passwordRef?.current?.validationMessage}
inputProps={ {required: true} }
onChange={(e: OnChangeEvent) => setPasswordValue(e.target.value)}
label="Password"
/>
<TextField
margin="normal"
fullWidth
required
type="password"
inputRef={confirmPasswordRef}
value={confirmPasswordValue}
error={confirmPasswordError}
helperText={confirmPasswordError &&
confirmPasswordRef?.current?.validationMessage}
inputProps={ {required: true} }
onChange={(e: OnChangeEvent) => setConfirmPasswordValue(e.target.value)}
label="Confirm password"
/>
<Button
variant="contained"
fullWidth
sx={ {mt: 3} }
onClick={() => { // ← ㉑
if (formValidation()) {
alert("OK!");
}
}}
>
Register
</Button>
</Container>
);
};
|
MUI ver5でstyledに引数を渡す
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | export default function Story({ image, profileSrc, title }) {
console.log(image);
console.log(`url(${image.imageurl ? image.imageurl : ""})`);
return (
<StoryWrapper imageUrl={`${image}`}>
<Avatar src={profileSrc} className="story__avatar" />
<h4>{title}</h4>
</StoryWrapper>
);
}
const StoryWrapper = styled("div")(({ imageUrl }) => ({
backgroundImage: `url(${imageUrl})`
}));
|
Nest.js
- 参考
- うまく動かないので
fastify
ではなく素のexpress
利用
| yarn prisma migrate dev --preview-feature
yarn prisma generate
nest g resource
|
Nest.js deploy
| heroku addons:create heroku-postgresql:hobby-dev
|
Next.js node.jsなしでリリース, SSG
next export
に対応するコマンドでビルドすればよい: 特にSSG. - 参考: サブディレクトリにリリースするときは
next.config.js
に次の設定をつける
| module.exports = {
assetPrefix: "/sub",
basePath: "/sub",
};
|
Next.js TypeScriptのプロジェクト生成
| npx create-next-app sample-next-ts --typescript
|
Next.js 初期化
| yarn create nx-workspace
yarn add @mui/material @emotion/react @emotion/styled @mui/styled-engine-sc styled-components
|
Next.js ビルド(エクスポート)
| module.exports = {
reactStrictMode: true,
trailingSlash: true,
}
|
Next.js ビルド時にFirebaseのfunctions
ディレクトリを除外する
tsconfig.json
のexclude
にfunctions
を指定すればよい
node.js asdf
の導入
| brew install asdf
echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ~/.zshrc
exec $SHELL -l
|
node.js asdf
でnode
のランタイムバージョン指定でインストール
| asdf plugin add nodejs
asdf plugin list
asdf list all nodejs
|
- インストール自体は
asdf install nodejs <version>
- 最新版を入れるなら次の通り
| asdf plugin install nodejs
asdf install nodejs latest
asdf global nodejs latest
|
node.js asdf
でのnode
のランタイムバージョンをグローバルに指定
| asdf global nodejs <version>
node -v
|
node.js asdf
で特定ディレクトリでのバージョン指定
| asdf local nodejs <version>
node -v
|
node.js CSS導入
pico.cssでの例
| npx create-next-app picosample --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"
npm install @picocss/pico
|
pages/index.js
にimport '@picocss/pico/css/pico.min.css'
を書けば反映される.
node.js docker軽量化
node.js dotenv
を使う
node.js
のソースコード中では次の通り.
| import * as dotenv from "dotenv";
dotenv.config(); // これがないと動かない
|
ReactやNext.jsで使っている場合は変数設定が特殊なので注意すること.
| REACT_APP_API_BASE_URL=http://localhost:9000
NEXT_PUBLIC_FOO=hogehoge
|
React
- 参考
.env
の変数には必ずREACT_APP_
をつけて次のように書く
| REACT_APP_API_BASE_URL=xxxxxxxxxxxx
REACT_APP_FIREBASE_API_KEY=xxxxxxxxxxxx
REACT_APP_FIREBASE_AUTH_DOMAIN=xxxxxxxxxxxxxxxx
|
- プログラム中では
process.env.REACT_APP_API_BASE_URL
と書けば値を取れる
node.js package.json
にあるパッケージのバージョンアップ方法
| npm i -g npm-check-updates
ncu -u
|
イベントの型
親から子にハンドラ関数を渡す
関数にコンポーネントを渡す
- 引数設定は
icon
で, コンポーネントを渡すときはicon={<InboxIcon />}
のように書けばよい
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 | type ItemProp = {
to: string;
text: string;
icon: any;
};
function Item({ to, text, icon }: ItemProp) {
return (
<Link to={to} key={to}>
<ListItem>
<ListItemButton>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
</Link>
);
}
export default function UpperLeftMenu() {
return (
<>
<List>
<Link to="/tempmenu/1" key="/tempmenu/1">
<ListItem>
<ListItemButton>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Home" />
</ListItemButton>
</ListItem>
</Link>
<Item to="/tempmenu/2" text="tempmenu/2" icon={<InboxIcon />} />
<Item to="/tempmenu/3" text="tempmenu/3" icon={<InboxIcon />} />
</List>
</>
);
}
|
node.js https.request
, API呼び出しサンプル
GET
- 何でもいいが, とりあえず使ったことがあるFlickrのAPIを実行する前提
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 | const API_KEY = "some value"; // https://www.flickr.com/services/apps/create/apply/ でFlickrで取ってこよう
// Flickr画像データのURLを返す
const getFlickrImageURL = (photo, size) => {
let url = `https://farm${photo.farm}.staticflickr.com/${photo.server}/${photo.id}_${
photo.secret
}`;
if (size) {
// サイズ指定ありの場合
url += `_${size}`;
}
url += '.jpg';
return url;
};
// Flickr画像の元ページのURLを返す
const getFlickrPageURL = (photo) => `https://www.flickr.com/photos/${photo.owner}/${photo.id}`;
// Flickr画像のaltテキストを返す
const getFlickrText = (photo) => {
let text = `"${photo.title}" by ${photo.ownername}`;
if (photo.license === '4') {
// Creative Commons Attribution(CC BY)ライセンス
text += ' / CC BY';
}
return text;
};
// リクエストパラメータを作る
const paramObj = {
method: 'flickr.photos.search',
api_key: API_KEY,
text: 'cat', // 検索テキスト
sort: 'interestingness-desc', // 興味深さ順
per_page: 12, // 取得件数
license: '4', // Creative Commons Attributionのみ
extras: 'owner_name,license', // 追加で取得する情報
format: 'json', // レスポンスをJSON形式に
nojsoncallback: 1, // レスポンスの先頭に関数呼び出しを含めない
};
let params = "";
params = Object.keys(paramObj).map(k => `${k}=${paramObj[k]}`).join("&");
const flickrUrl = `https://api.flickr.com/services/rest/?${params}`;
console.log(flickrUrl);
https.request(flickrUrl, { method: "GET" }, res => {
console.log('statusCode:', res.statusCode);
res.on('data', (d) => {
process.stdout.write(d); // ここでリクエストの値が取れる
});
res.on("end", () => {
console.log("END!");
});
}).on("error", (error) => {
console.log(`Error: ${error}`);
}).end();
|
node.js httpサーバーを立ち上げる
| npm install -g http-server
http-server
|
node.js 運用モード, productionモード
- アプリを運用モードで実行するには環境変数
NODE_ENV=production
をセットして実行
Nx Cannot find module
| - import renderMathInElement from "katex/dist/contrib/auto-render.js";
|
補足
- 具体的には
katex/dist/contrib/auto-render
で問題が出た. node_modules
配下にきちんとファイルはあった. - 何となく思い立ったため拡張子までつけてみたらどうかと思ってやってみて通った
Nx docker利用
Nx express静的ファイルの呼び出し
- 2022-08-09検証
- 参考
- まとめ: 各プロジェクトの
assets
配下にファイルを置いて次のコードを追加
| app.use(express.static(__dirname + "/assets"));
|
詳細メモ
- ルート直下の
workspace.json
を見る projects > <package-name> > targets > build > options > assets
を見ると静的ファイルを置くべき場所が指定されている - 静的ファイルを
public
に置きたいならpublic
までのパスを指定すればよいらしい(未検証)
Nx Firebaseとの連携・デプロイ設定
- URL: これに沿って設定すればよい
- 上記の設定のもとで
nx deploy functions
またはyarn nx deploy
を実行すればデプロイできる - firebaseの
functions
ディレクトリにnode
の適切なバージョンを使う設定を入れること asdf
を使っているなら.tool-versions
- 以下, 念のため実際の作業を転記
作業メモ: Cloud Functions
- 対応するアプリケーション名は
functions
で, apps/functions
ディレクトリだとする - インストール
Firebase
にログイン・初期化: 適切なアカウントかどうかも確認 - ログインユーザーの変更は
Firebase
関連の項を確認すること
| firebase login:list
firebase login
firebase init
|
| firebase
firebase-debug.log*
firebase-debug.*.log*
.firebase/
|
- プロジェクトがなければ次のコードでプロジェクトを生成
| yarn add -D @nrwl/node
nx g @nrwl/node:application functions
|
firebase init
が生成したpackage.json
に含まれているパッケージのうち足りない分をNx
ワークスペースに追加
| yarn add firebase-admin firebase-functions
yarn add -D firebase-functions-test
|
- 次の内容で
tools/scripts/build-firebase-functions-package-json.ts
を作成 - ワークスペースルートにある
package.json
を元にデプロイ用のpackage.json
を生成するスクリプト Functions
のソースコードが依存していないパッケージを除外 Node.js
ランタイムのバージョン指定を追加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 | import * as depcheck from 'depcheck';
import * as fs from 'fs';
import * as path from 'path';
import * as packageJson from '../../package.json';
const PACKAGE_JSON_TEMPLATE = {
engines: { node: '16' }, // 適切なバージョンを指定する
main: 'main.js',
};
async function main(): Promise<void> {
const args = process.argv.slice(2);
if (!args?.length || !args[0]) {
throw new Error('Application name must be provided.');
}
const APPLICATION_NAME = args[0];
console.log(`Application name: ${APPLICATION_NAME}`);
/*****************************************************************************
* package.json
* - Filter unused dependencies.
* - Write custom package.json to the dist directory.
****************************************************************************/
const ROOT_PATH = path.resolve(__dirname + '/../..');
const DIST_PROJECT_PATH = `${ROOT_PATH}/dist/apps/${APPLICATION_NAME}`;
console.log('Creating cloud functions package.json file...');
// Get unused dependencies
const { dependencies: unusedDependencies } = await depcheck(DIST_PROJECT_PATH, {
package: {
dependencies: packageJson.dependencies,
},
});
// Filter dependencies
const requiredDependencies = Object.entries(packageJson.dependencies as { [key: string]: string })
?.filter(([key, _value]) => !unusedDependencies?.includes(key))
?.reduce<{ [key: string]: string }>((previousValue, [key, value]) => {
previousValue[key] = value;
return previousValue;
}, {});
console.log(`Unused dependencies count: ${unusedDependencies?.length}`);
console.log(`Required dependencies count: ${Object.values(requiredDependencies)?.length}`);
// Write custom package.json to the dist directory
await fs.promises.mkdir(path.dirname(DIST_PROJECT_PATH), { recursive: true });
await fs.promises.writeFile(
`${DIST_PROJECT_PATH}/package.json`,
JSON.stringify(
{
...PACKAGE_JSON_TEMPLATE,
dependencies: requiredDependencies,
},
undefined,
2
)
);
console.log(`Written successfully: ${DIST_PROJECT_PATH}/package.json`);
}
main()
.then(() => {
// Nothing to do
})
.catch(error => {
console.error(error);
});
|
| cp apps/functions/*.json tools/scripts
cp apps/functions/.eslintrc.json tools/scripts
cp apps/functions/jest.config.ts tools/scripts
|
tools/scripts/tsconfig.json
に以下を追加
| "compilerOptions": { "resolveJsonModule": true, "module": "commonjs" },
|
workspace.json
またはプロジェクトのproject.json
に追記 - 大事なのはビルド設定
- ビルドと
firebase
コマンドの組み合わせ, firebase
コマンドを実行するだけ - さらに念のため
firebase init
が生成したpackage.json
のscripts
にある項目ものを移植
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 | // 最初の注意通り`apps/functions`にしている前提
"functions": {
// 略
"architect": {
// build の項目を build-node に変更: build 時に他にも実行したことがあるのでサブコマンド扱いにする
"build-node": {
// 略
},
// buildの項目を新しく追加: build-nodeを実行してTypeScriptビルドし, package.jsonを用意
"build": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"commands": [
{
"command": "nx run functions:build-node"
},
{
"command": "ts-node --project ./tools/scripts/tsconfig.json tools/scripts/build-firebase-functions-package-json.ts functions"
},
{
"command": "cd dist/apps/functions && npm install --package-lock-only"
}
],
"parallel": false
},
"configurations": {
"production": {
"prod": true
}
}
},
// ビルドしてFirebase Emulatorを使って実行するよう修正
"serve": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"command": "nx run functions:build && firebase emulators:start --only functions --inspect-functions"
}
},
// ビルドしてfunctions:shellを使う設定を追加
"shell": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"command": "nx run functions:build && firebase functions:shell --inspect-functions"
}
},
// shell と同じ
"start": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"command": "nx run functions:shell"
}
},
// デプロイ設定を追加
"deploy": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"command": "firebase deploy --only functions"
}
},
// lint と test はそのまま
}
}
|
firebase init
が生成したディレクトリ(ここではfunctions
)を削除 firebase.json
を編集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | // 作ったアプリケーション名で設定する
"functions": {
"source": "dist/apps/functions",
"ignore": [
"node_modules",
".git",
"firebase-debug.log",
"firebase-debug.*.log",
".eslintrc.json",
"jest.config.ts",
"tsconfig.app.json",
"tsconfig.json",
"tsconfig.spec.json"
],
"predeploy": [
"nx lint functions",
"nx build functions"
]
},
|
nx serve functions
で動作確認 nx deploy functions
でデプロイ・動作確認
Nx React+Fastify
- 参考
- 結論: 採用しない
fastify
のルートにReact
を連携させる - フロントエンドとバックエンドを分けずに一体でリリースする
Next
だと使えなさそう
Nx storybookアプリケーション作成
| nx g @nrwl/react:storybook-configuration --name=ui
|
Nx UIライブラリ追加
| nx g @nrwl/react:lib ui
nx g @nrwl/react:component button --project ui
|
Nx アプリケーション削除
apps
の下にあるモノがアプリケーション - 参考
- URL
| nx generate remove <project-name>
nx g rm <project-name>
|
Nx アプリケーション作成
next.js
アプリケーションを作る場合は次の通り.
| nx generate @nrwl/next:application --name=society --dry-run --no-interactive
|
Nx アプリケーションのリネーム
| nx g @nrwl/workspace:move --project oldNG newNG
nx g mv --project oldNG newN
|
Nx アプリケーションへのポート指定
- URL
project.json
のtargets.serve.option.port
に指定する
Nx ビルド生成物に依存関係を抜き出したpackage.json
を作成
- URL
Nx
自体が機能を持つ workspace.json
の各プロジェクトのconfigurations
中, assets
の下に"generatePackageJson": true
を追記
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | "outputPath": "dist/apps/api",
"main": "apps/api/src/main.ts",
"tsConfig": "apps/api/tsconfig.app.json",
- "assets": ["apps/api/src/assets"]
+ "assets": ["apps/api/src/assets"],
+ "generatePackageJson": true
},
"configurations": {
"production": {
"outputPath": "dist/apps/html",
"main": "apps/html/src/main.ts",
"tsConfig": "apps/html/tsconfig.app.json",
- "assets": ["apps/html/src/assets"]
+ "assets": ["apps/html/src/assets"],
+ "generatePackageJson": true
},
|
Nx コマンドメモ・チュートリアルのメモ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 | npx create-nx-workspace my-project
yarn nx list # we can confirm options
yarn add @nrwl/react
yarn nx list @nrwl/react # check schematics
yarn nx g @nrwl/react:application --help # confirm options
yarn nx g @nrwl/react:application store --dry-run
yarn nx g @nrwl/react:application store
yarn nx run <proj>:<target>
yarn nx run store:serve
yarn nx run store:serve --port 3000 # we can set port in workspace.json
yarn nx run store:lint
nx serve store
yarn nx run store:serve # run react in tutorial
yarn add @mui/material
yarn nx g @nrwl/react:lib ui-shared --directory=store --dry-run
yarn nx g @nrwl/react:lib ui-shared --directory=store
yarn nx g @nrwl/react:component header --project=store-ui-shared
yarn nx g @nrwl/workspace:lib util-formatters --directory=store --dry-run
yarn nx g @nrwl/workspace:lib util-formatters --directory=store
yarn nx graph
yarn nx @nrwl/react:lib feature-game-detail --directory=store --appProject=store
yarn add -D @nrwl/express # 公式ページから調べて適切なコマンドを実行
yarn nx g @nrwl/express:application api --frontendProject=store --dry-run
yarn nx g @nrwl/express:application api --frontendProject=store
yarn nx run api:serve
yarn nx dep
yarn nx run-many --target=serve --projects=api,store --parallel=true
yarn nx run store:serveAppAndApi
yarn nx dep
yarn nx g @nrwl/workspace:lib util-interfaces --directory=api --dry-run
yarn nx g @nrwl/workspace:lib util-interfaces --directory=api
yarn add @nrwl/storybook -D
yarn nx list @nrwl/storybook
yarn nx g @nrwl/react:storybook-configuration store-ui-shared --configureCypress --generateStories --dry-run
yarn nx g @nrwl/react:storybook-configuration store-ui-shared --configureCypress --generateStories
yarn nx run store-ui-shared:storybook
yarn nx run store-ui-shared-e2e:e2e --watch
yarn nx run store-ui-shared-e2e:e2e --headless
yarn nx run store:test
yarn nx run store:test --watch
yarn nx run store:build --configuration=production
yarn nx run store:lint
yarn nx affected:dep-graph --base=<branch-name>
yarn nx affected:test --base=<branch-name>
yarn nx affected:lint --base=<branch-name>
yarn nx affected:test --all
yarn nx migrate latest
nx run my-js-app:build
nx build my-js-app
nx run-many --target=build --projects=app1,app2
nx run-many --target=test --all # Runs all projects that have a test target, use this sparingly.
nx affected --target=build# 毎回すべてのプロジェクトを実行するよりも効率的
nx generate workspace-generator my-generator # ワークスペース用のカスタムジェネレーター
nx migrate latest # Updates the version of Nx in `package.json` and schedules migrations to be run
nx migrate --run-migrations # Runs the migrations scheduled by the previous command.
nx graph
nx graph --watch # Updates the browser as code is changed
nx affected:graph # Highlights projects which may have changed in behavior
nx list
nx list @nrwl/react # Lists capabilities in the @nrwl/react plugin
|
Nx プロジェクト・ライブラリ内でのコマンド実行
- cf: URL
- トップの
workspace.json
またはプロジェクト・ライブラリのproject.json
で次のように書く
| "frontend": {
"targets": {
//...
"ls-project-root": {
"executor": "nx:run-commands",
"options": {
"command": "ls apps/frontend/src"
}
}
}
}
|
| nx run frontend:ls-project-root
|
Nx プロジェクト作成
| yarn create nx-workspace@latest
npx create-nx-workspace@latest --package-manager=yarn
|
ついでにmui
をインストールすると便利.
| yarn add @mui/material @emotion/react @emotion/styled @mui/styled-engine-sc styled-components
|
Prisma
Prisma API setting
| heroku create -a ys-nx-express-prisma
heroku config:set -a ys-nx-express-prisma PROJECT_NAME=express
heroku config:set -a ys-nx-express-prisma PORT=80
heroku buildpacks:add -a ys-nx-express-prisma heroku/nodejs
|
Prisma cf. command test
Prisma yarn
| yarn init
yarn add @prisma/client
yarn add -D @types/node prisma ts-node typescript
|
| {
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": [
"esnext"
],
"esModuleInterop": true
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const userData = {
data: {
name: "Alice",
email: "alice@prisma.io",
posts: { create: { title: "Hello World" } },
profile: { create: { bio: "I like turtles" } },
},
};
async function main() {
// await prisma.user.create(userData);
const allUsers = await prisma.user.findMany({
include: { posts: true, profile: true },
});
console.log(allUsers, { depth: null });
const post = await prisma.post.update({
where: { id: 1 },
data: { published: true },
});
console.log(post);
}
main()
.catch((e) => {
throw e;
})
.finally(async () => {
await prisma.$disconnect();
});
|
| "scripts": {
"dev": "npx ts-node index.ts",
"start": "npx ts-node index.ts"
}
|
prisma/schema.prisma
を次のように設定する
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String @db.VarChar(255)
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
model Profile {
id Int @id @default(autoincrement())
bio String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
profile Profile?
}
|
| npx prisma migrate dev --name init
|
Prisma カラムへの制約
- 次のように書く
- 注意:
@mydb
と書いた部分はdatasource mydb
で指定したmydb
にする - ネット上のサンプルだとよく
@db
になっている datasource db
を前提にしているのだろう
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | generator client {
provider = "prisma-client-js"
}
// `mydb`はmysqlに作ったデータベース名を指定すること
datasource mydb {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String @mydb.VarChar(255)
email String @unique @mydb.VarChar(255)
password String @mydb.VarChar(255)
}
|
Prisma 初期化
| nx run prisma:init
nx run prisma:migrate
nx run prisma:seed
|
- TODO: 上記
seed
(または適切なdeploy
?)への注意 - 次の直接実行で無理やり実行できた
heroku run bash
libs/prisma
でnpx prisma db seed
実行 - 当面は最悪これで実行
- コマンドラインで
node --loader ts-node/esm libs/prisma/prisma/seed.ts
を打つと通る - expressの
main.js
でポートの変数をprocess.env.port
からprocess.env.PORT
と大文字に変更する Procfile
にrelease: npx prisma migrate deploy
を追記する package.json
に次の内容を追記する
| {
"prisma": {
"schema": "libs/prisma/prisma/schema.prisma"
}
}
|
| heroku addons:create heroku-postgresql:hobby-dev
|
Prisma テストで使うデータベースを変える
- 参考
- テストする時に環境変数として接続文字列を指定する
| "scripts": {
"test": "DATABASE_URL='mysql://root:root@localhost:3306/mydb_test' NODE_ENV=test jest"
},
|
Prisma テストで実際にデータベースを読み書きする
- 参考
- 上記リンク先は
PostgreSQL
前提のようなのでMySQLなら次の通り - 下記の関数を
beforeEach()
内で呼ぶ resetTable(["User"])
のように初期化したいテーブルを配列で指定する
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | import { PrismaClient } from "@prisma/client";
import { Prisma } from ".prisma/client";
const prisma = new PrismaClient();
export const resetTable = async (modelNames: Prisma.ModelName[]): Promise<void> => {
const tablenames = modelNames.map((modelName) => ({ tablename: modelName }));
for (const { tablename } of tablenames) {
try {
await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${tablename};`);
} catch (error) {
console.log({ error });
}
}
};
|
Prisma prismaを読み込むとts-node-dev
のリロードが走らない
Processing p5.js P5Wrapper Vector
を使う方法
- URL
p5.constructor.[something]
とconstructor
を挟む
React create-react-appでTypeScriptプロジェクト生成
| npx create-react-app {プロジェクト名} --template typescript
|
React+TypeScript useRefをTypeScriptで使うとObject is possibly 'null'
| const refSample = useRef(null); # もとのコード
const refSample = useRef<HTMLDivElement>(null); # HTMLDivElementとは限らないのできちんと調べる
const resetScroll = (): void => refSample.current.scrollTo(0, 0); # もとのコード
const resetScroll = (): void => refSample.current?.scrollTo(0,0); # 修正版
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
return (
<>
<Box component="section" sx={{ display: "flex", flexDirection: "column", width: "400px", gap: "10px" }}>
<TextField
variant="outlined" label="Email"
value={email} {/* ここが大事 */}
onChange={ev => setEmail(ev.target.value)} />
<TextField
variant="outlined" label="Password" type="password"
value={password} {/* ここが大事 */}
onChange={ev => setPassword(ev.target.value)} />
</Box>
</>);
|
React KaTeX連携
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | import React from 'react';
import PropTypes from 'prop-types';
import Markdown from 'markdown-to-jsx';
import renderMathInElement from 'katex/dist/contrib/auto-render';
const MarkdownViewer = ({ content }) => {
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current) {
renderMathInElement(ref.current, {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '\\[', right: '\\]', display: true },
{ left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: false },
],
});
}
}, [ref.current]);
return (
<div ref={ref}>
<Markdown>{content}</Markdown>
</div>
);
};
MarkdownViewer.propTypes = {
content: PropTypes.string.isRequired,
};
MarkdownViewer.defaultProps = {};
|
React useEffect
の第二引数
- URL
- 第二引数を空にするとレンダリング毎に実行
- 一般に第二引数を空にするのは危険
- コンポーネントは
state
やprops
などに変更がある度にレンダリングされる - 予期せず
useEffect
が実行されてしまう可能性がある
- 第二引数を指定
- 指定するが配列の中身は空: 初回のレンダリング後だけ実行
- 実際に値を設定: 指定した値に変化があった時に実行
- URL
useEffect(effect, deps);
useEffect
は第一引数に関数effect
を取り, 第二引数に配列deps
を取る. effect
: そのコンポーネントが返す仮想DOMの差分が実DOMに反映された直後に実行される deps
はeffect
が依存する値を書き込む React
はdeps
に渡された値が前回のレンダリング時と比べて更新されていた場合だけeffect
を実行
React useEffect
で画面がちらつくとき -> useLayoutEffect
React コンポーネント設計の参考
React 配列やオブジェクトの更新とともにUIも更新したいとき
新しい値を変数で保持する
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | import {useState} from "react"
export default function Index () {
const [count, setCount] = useState(0);
const onButtonClick = () => {
const new_count = count + 1;
setCount(new_count);
alert(`count:${new_count}`);
}
return (
<div>
<button onClick={onButtonClick}>押して!</button>
</div>
)
}
|
関数型の更新を使う
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | import {useState} from "react"
export default function Index () {
const [count, setCount] = useState(0);
const onButtonClick = () => {
setCount((pre_count) => pre_count + 1) //初回クリック時のpre_countは0
setCount((pre_count) => { //初回クリック時のpre_countは1
alert(`count:${pre_count}`);
return pre_count
})
}
return (
<div>
<button onClick={onButtonClick}>押して!</button>
</div>
)
}
|
React Native
iOS, for Mac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | brew install node
brew install watchman
sudo gem install cocoapods
sudo arch -x86_64 gem install ffi
arch -x86_64 pod install
rbenv install 2.7.4
npx react-native init AwesomeProject
npx react-native init AwesomeTSProject --template react-native-template-typescript
npx react-native start # at the directory, AwesomeProject
cd ios
pod install
cd /path/to/AwesomeProject
npx react-native run-ios # in another terminal
|
RESTクライアント
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | ### はコメント
### GETリクエストは単純に1行でよい
###
### 自分のIPアドレス
GET https://httpbin.org/ip
### xmlを表示する
GET https://httpbin.org/xml
### ヘッダをつける
POST https://httpbin.org/post?foo=bar
User-Agent: Emacs24
### POSTリクエスト
### ヘッダとデータの間には空行を入れる
POST https://httpbin.org/post?val=key
User-Agent: Emacs24
Content-Type: application/json
{
"foo": "bar"
}
|
styled-componentsでのReceived "true" for a non-boolean attribute
のようなエラー対応
- 参考
- TODO: その他も追記
- 結論: 次のように自分で追加した
prop
とそれ以外を分離すればよい
| const SidebarContainer = styled('aside')(({isOpen, ...props}) => {
return {
position: 'fixed',
opacity: isOpen ? '90%' : '0',
top: isOpen ? '0' : '-100%',
};
});
|
TypeScript Event
の型
| type Props = {
onClick: (event: React.MouseEvent<HTMLInputElement>) => void
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
onkeypress: (event: React.KeyboardEvent<HTMLInputElement>) => void
onBlur: (event: React.FocusEvent<HTMLInputElement>) => void
onFocus: (event: React.FocusEvent<HTMLInputElement>) => void
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
onClickDiv: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
}
|
TypeScript querySelector
を使うときの型引数指定, TS2339: Property 'style' does not exist on type 'Element'.
- 参考
- この要素は絶対存在するから
nullability
を除去したい: は後置演算子!
を使う
| const foo = document.querySelector<HTMLElement>('.foo');
if (foo) {
foo.style.display = '';
}
const bars = document.querySelectorAll<HTMLInputElement>('input[name="bar"]');
bars.forEach((bar) => {
console.log(bar.value);
});
|
TypeScript React開発時のprops.childrenの型
| import { ReactNode } from 'react';
function Parent({ children }: {
children: ReactNode
}) {
return (
<div>{ children }</div>
);
|
TypeScript windowオブジェクト未定義になった場合の対処, Property 'gtag' does not exist on type 'Window & typeof globalThis'. TS2339
| Property 'gtag' does not exist on type 'Window & typeof globalThis'. TS2339
|
- まず
@types/gtag.js
をインストールする
| yarn add -D @types/gtag.js
|
tsconfig.json
のtypes
を確認する - 未設定なら多分大丈夫
- どこかで設定しているなら
"gtag.js"
を追加する
- もう一度ビルドや実行する
TypeScript tsconfig.json
メモ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | {
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"lib": [
"es2020",
"dom",
"dom.iterable"
],
"jsx": "preserve",
"sourceMap": true,
"outDir": "./dist",
"rootDir": "/",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"noEmit": true,
"incremental": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["dist", "node_modules"],
"compileOnSave": false
}
|
TypeScript tsconfig.json
で設定したパスをts-node
で使う
- URL
yarn add -D tsconfig-paths
でインストール - 実行コマンドを
ts-node -r tsconfig-paths/register src/index.ts
にする - 特に
-r tsconfig-paths/register
を追加
TypeScript イベントの型
| handleDelete: (e: React.MouseEvent<HTMLInputElement>) => void;
|
TypeScript インターフェースでのインデックスシグネチャ
| interface Status {
[parameter: string]: number;
}
const myStatus: Status = {
level: 22,
experience: 3058,
maxHP: 156,
maxMP: 174,
attack: 39,
defense: 25,
};
|
TypeScript 2021/12 Jest
で型安全にモックを書く
TypeScript 型ガード
| const foo: unknown = '1,2,3,4';
if (typeof foo === 'string') { console.log(foo.split(',')); }
console.log(foo.split(',')); //コンパイルエラー
|
TypeScript 型定義の調査, TypeSearch
TypeScript 環境変数に型をつけつつ.env
を脱却する
TypeScript 関数型
| // type 型の名前 = (引数名: 引数の型) => 戻り値の型;
type Increment = (num: number) => number;
|
TypeScript 絶対パスでインポート
tsconfig.json
で次のように指定すると@/path/to
で指定できる
| "compilerOptions": {
"rootDir": "./src", // 必要に応じて設定
"baseUrl": "./src",
"paths": {
"@/*": ["*"]
}
}
|
yarn package-lock.json
からyarn.lock
に移行
yarn typesync
yarn add -D typesync
でインストール scripts
に"preinstall": "typesync || :"
を追加
yarn クリーンインストール
| rm -rf node_modules
yarn cache clean
|
yarn パッケージの更新: yarn upgrade <package-name>
- 全部最新化するのは
yarn upgrade --latest
yarn バージョン指定でインストール
| yarn add <package-name>@<version>
|
yarn viteで初期化
| yarn create vite --template react-ts
|
Heroku
Heroku デプロイの参考
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | yarn create nx-workspace --package-manager=yarn nx-fullstack
nx generate @nrwl/node:application api
yarn nx run-many --target=serve --projects=nx-fullstack,api --parallel=true
"build": "yarn nx run-many --target=build --projects=nx-fullstack,api --parallel=true"
"start": "node dist/apps/api/main.js"
heroku login
heroku create
web: yarn start
git push heroku master
heroku open
|
Heroku バックエンドの設定
| heroku create -a ys-jssamples-api
heroku config:set -a ys-jssamples-api PORT=80
heroku buildpacks:add -a ys-jssamples-api heroku/nodejs
|
HTML
CSSをHTMLに埋め込むときのコード片
ふだん書かないので余計に覚えていられません.
直接HTMLに書くとき
| <style type="text/css">
/* some code */
</style>
|
外部CSSを読み込むとき
| <link rel="stylesheet" type="text/css" href="sample.css" />
|
PDFをHTMLに埋め込む方法
いろいろありますが Google Document の URL で PDF を埋め込むのが便利です.
| <iframe
src="http://docs.google.com/gview?url=http://www.soumu.go.jp/main_sosiki/joho_tsusin/top/ninshou-law/pdf/law_1.pdf&embedded=true"
style="width:100%; height:80vh;" frameborder="0">
</iframe>
|
ここで ?url=
の指定を適当な URL に変えると PDF が埋め込めます.
ngrok
ngrok CORS設定
- 参考
- フロント・バックエンドともに設定が必要.
- フロントエンドで次のように設定する:
axios
前提の設定. - 他のライブラリやAPIでも同じように設定すればよいはず.
| import axios from "axios";
axios.defaults.baseURL = process.env.NEXT_PUBLIC_SERVER_URI;
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8';
axios.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
axios.defaults.withCredentials = true;
|
- バックエンドで次のように設定する: 例は
express
.
| import * as cors from "cors";
const app = express();
app.use(cors({
origin: true, // 許可するフロントエンド側のURL設定
credentials: true, // レスポンスヘッダーにAccess-Control-Allow-Credentials追加
optionsSuccessStatus: 200 // レスポンスstatusを200に設定
}));
|
ngrok 開発中のローカルのサーバーをhttp/httpsで公開できる
| ngrok http <port> --region ap
|
ngrok 設定ファイル置場確認
- 次のコマンドを実行するとアップグレードとともに設定ファイルの場所がわかる
- 2022/7時点でMacだとホーム直下には作られないので注意しよう
PWA
PWA サブディレクトリにリリース: Next.js
前提
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | {
"name": "Gallery for Physics", //適宜修正
"short_name": "gallery-phys", //適宜修正
"description": "物理学ギャラリー. 基礎方程式や物理学者名言集を収録.", //適宜修正
"start_url": "/service/phys-gallery", //適宜修正
"display": "standalone",
"orientation": "portrait-primary",
"background_color": "#fff",
"theme_color": "#fff",
"dir": "itr",
"icons": [
{
"src": "icon-192x192.png", //適宜修正, ルートディレクトリは`start_url`で指定しているのでそこからのパスを指定すればよい
"sizes": "192x192",
"type": "image/png"
}
]
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | // eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require('@nrwl/next/plugins/with-nx');
const withPWA = require("next-pwa");
const runtimeCaching = require("next-pwa/cache");
const SUB_DIRECTORY = "/service/phys-gallery"; // 適宜修正
const isProd = process.env.NODE_ENV === "production";
/**
* @type {import('@nrwl/next/plugins/with-nx').WithNxOptions}
* @type {import('next').NextConfig}
**/
const nextConfig = {
nx: {
// Set this to true if you would like to to use SVGR
// See: https://github.com/gregberge/svgr
svgr: false
},
reactStrictMode: true,
trailingSlash: true
};
module.exports = withNx(nextConfig);
module.exports = withPWA({
pwa: {
disable: process.env.NODE_ENV !== 'production',
dest: "public",
register: true,
skipWaiting: true,
runtimeCaching,
fallbacks: {
document: `${SUB_DIRECTORY}/_offline.html`
}
},
assetPrefix: isProd ? SUB_DIRECTORY : "",
basePath: isProd ? SUB_DIRECTORY : ""
});
|
pages
配下に_document.tsx
を次のような内容で設置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | import Document, {Head, Html, Main, NextScript} from "next/document";
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<title>物理学ギャラリー</title>
<link rel="shortcut icon" type="image/vnd.microsoft.icon" href="favicon.ico"/>
<link rel="icon" type="image/vnd.microsoft.icon" href="favicon.ico"/>
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon-180x180.png"/>
<link rel="icon" type="image/png" sizes="192x192" href="icon-192x192.png"/>
<link rel="manifest" href="manifest.json"/>
</Head>
<body>
<Main/>
<NextScript/>
</body>
</Html>
);
}
}
export default MyDocument;
|
pages
配下に_offline.tsx
を次のような内容で配置
| import Index from ".";
export default Index;
|
PWA ファビコン生成
PWA メタ情報設定
1
2
3
4
5
6
7
8
9
10
11
12 | <meta name='twitter:card' content='summary' />
<meta name='twitter:url' content='https://yourdomain.com' />
<meta name='twitter:title' content='PWA App' />
<meta name='twitter:description' content='Best PWA App in the world' />
<meta name='twitter:image' content='https://yourdomain.com/icons/android-chrome-192x192.png' />
<meta name='twitter:creator' content='@DavidWShadow' />
<meta property='og:type' content='website' />
<meta property='og:title' content='PWA App' />
<meta property='og:description' content='Best PWA App in the world' />
<meta property='og:site_name' content='PWA App' />
<meta property='og:url' content='https://yourdomain.com' />
<meta property='og:image' content='https://yourdomain.com/icons/apple-touch-icon.png' />
|
Vercel
Vercel フロントエンドNext.jsのデプロイ参考
Vercel リリース用の設定
- cf. deploy
GitHub
連携する Settings
タブを開く Build & Development Settings
を開く BUILD COMMAND
: yarn build --prod
package.json
のbuild
を正しく設定している前提
OUTPUT DIRECTORY
yarn
: dist/apps/line-minapp-sample/.next
nx
: TODO
Vercel リリース時の環境変数設定
Settings
からの左メニューでEnvironmental Veriables
から設定
Zoom 他の人も画面共有できるようにする
- cf. URL
- ミーティング中はメニューの「セキュリティ」から「画面共有」にチェックを入れる
フォント
- Windows 8.1以降とOS X Mavericks(10.9)以降: 「游ゴシック体」と「游明朝体」が共通で収録
- Noto Sans(源ノ角ゴシック): 日中韓3か国語に対応したオープンソースフォント