package.json
{
"name": "example-project",
"version": "1.0.0",
"scripts": {
"preinstall": "echo preinstall",
"install": "echo install",
"postinstall": "echo postinstall",
"prepare": "echo prepare"
},
"dependencies": {
"react": "^18"
}
}
npm i
結果
preinstall
install
postinstall
prepare
--ignore-scripts をつけるとこれらのスクリプトは実行されません
npm i --ignore-scripts
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
.bash_profile
source ~/.cargo/env
rustc --version
cargo install wasm-pack
インストールは不要です。npx コマンドで実行するので。どうしてもインストールしたい方はこちらのコマンドから
npm install -g npm-check-updates
例: 10.5.1 → 10.5.21 のようにパッチバージョンをアップグレードします
npx npm-check-updates -u --target patch
// その後プロジェクトのnpmパッケージをアップグレードする
npm i
例: 10.5.1 → 10.6.9 のようにマイナーバージョンをアップグレードします
npx npm-check-updates -u --target minor
// その後プロジェクトのnpmパッケージをアップグレードする
npm i
例: 10.5.1 → 11.1.0 のようにメジャーバージョンをアップグレードします
npx npm-check-updates -u
// その後プロジェクトのnpmパッケージをアップグレードする
npm i
この2つのコマンドで違うことは何か?
・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 i --yarn
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でどうやる?問題
一般的には、プロジェクトルート(ローカル)にpackage.jsonと一緒にbunfig.tomlファイルを追加することをお勧めします。
root ユーザーで行います
curl -fsSL https://bun.sh/install | bash
root ユーザーで行います
sudo mv /root/.bun/bin/bun /usr/local/bin/
sudo chmod a+x /usr/local/bin/bun
npm view パッケージ名 versions
npm install パッケージ名@バージョン
npm outdated
npm update は
セマンティック バージョニングに従った package.json で指定されたバージョン範囲内で最新のバージョンを探します。
たとえば、^1.0.0 と指定されている場合、1.x.x の最新バージョンにアップデートしますが、2.0.0 にはアップデートしません。
また メジャーバージョンが変わるような大きな変更(破壊的変更)は行いません。依存関係を最新の状態に保ちつつ、プロジェクトの互換性を保つために使用されます。
npm audit
npm audit --json | jq -r 'if .advisories then .advisories | to_entries[] | .value.module_name else .vulnerabilities | keys[] end' | sort -u
実際に修正を行う場合は、次のコマンドを実行する
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 という バージョンにする必要があるよという意味です。
npm install -g npm-check-updates
ncu --version
ncu
ncu -u
npm i
package.json
"my-package": "^3.1.5",
これは最大で 3.9.99 のように「同一のメジャーバージョン内で最大のバージョン指定」となります
package.json
"my-package": "~3.1.5",
これは最大で 3.1.99 のように「同一のマイナーバージョン内で最大のバージョン指定」となります
その他の指定方法はこちらがよくまとまっています。
brew install asdf
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 list
node.jsをインストールする
asdf plugin-add nodejs
asdf list all nodejs
asdf install nodejs 18.19.0
asdf global nodejs 18.19.0
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();
↓
https://qiita.com/majimaccho/items/4829f27c5514fbf37767
https://tech.buysell-technologies.com/entry/2023/09/26/083501
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);
@Type(() => Date)
birthday: Date;
// 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' プロパティは含まれません
以下のように配列でも変換できます。
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);
import { IsNotEmpty, IsEmail, Length } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
@Length(10, 20)
username: string;
@IsEmail()
email: string;
@IsNotEmpty()
password: string;
}
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>;
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;
});
}
}
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;
}
}
const currentFilename = __filename
const currentDirname = __dirname
const workDirectory = process.cwd()
prisma/schema.prisma
enum DeviceType {
mobile
desktop
}
zod で使用する
import { z } from 'zod'
export const DeviceTypeEnum = z.nativeEnum(DeviceType)
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);
});
npm install -D prisma-json-types-generator
src/types.ts
declare global {
namespace PrismaJson {
export type WordSimilarWords =string[]
}
}
export type { PrismaJson }
以下を prisma/schema.prisma に追加します
generator jsonTypes {
provider = "prisma-json-types-generator"
}
以下を prisma/schema.prisma に追加します
model Word {
id Int @id @default(autoincrement())
/// [WordSimilarWords]
similarWords Json
}
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,
},
})
https://www.prisma.io/docs/concepts/components/prisma-client/crud
const user = await prisma.user.create({
data: {
email: 'elsa@prisma.io',
name: 'Elsa Prisma',
},
})
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'
})
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',
},
})
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://github.com/unlight/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"
}
npx prisma generate
../src/@generated にファイルが生成されます。
続けてlintをかけておくといいと思います
npm run lint
npx prisma generate
.gitignore
# auto generated files
src/@generated
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)
}
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
}
model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id] , onDelete: Cascade) // 追加
authorId Int
}
model User {
id Int @id @default(autoincrement())
}
model Post {
id Int @id @default(autoincrement())
}
model User {
id Int @id @default(autoincrement())
posts Post[] // ● ← 追記
}
model Post {
id Int @id @default(autoincrement())
}
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 // ● 自動
}
あとは フィールド名やデータベースの物理カラム名など書き加えます。
const user = await this.prisma.user.findUnique({
where: { email: email },
include: { posts: true },
})
const user = await this.prisma.user.findUnique({
where: { email: email },
select: {
id: true,
email: true,
posts: {
select:{
id: true,
}
}
},
})
以下の2通りの方法があります。
・A. 中間テーブルを明示的に定義する方法 ( explicit )
・B. 中間テーブルを定義せずおまかせで自動生成する方法 ( implicit )
中間テーブル 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])
}
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;
「jestの設定」が2つ以上存在するというエラーです
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 エラー」が表示されなくなります。
ヘッダを確認して修正しましょう
'Content-Type: text/plain;charset=UTF-8'
↓
'content-type: application/json'
npm install --save-dev concurrently
以下のようなコマンドを並列実行することができます。
concurrently "yarn start:server" "yarn start:client"
これにより、start:serverとstart:clientの2つのyarnコマンドが同時に実行されます。
npm i -D esbuild-jest esbuild
jest.config.js
module.exports = {
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': 'esbuild-jest',
},
};
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
エンドポイント : localhost:4000/graphql で起動します
https://studio.apollographql.com/sandbox?endpoint=http://localhost:4000/graphql/
https://www.apollographql.com/docs/graphos/explorer/
graphql
mutation createSource {
createSource(createSourceInput:{
userId: 1,
locale: "ja",
text: "日本語のサンプルです"
})
{
id,userId,locale,text
}
}
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: "日本語のサンプルです"
}
}
npm install firebase
npm install 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),
});
Nest.jsにおけるGuardは実際には次の条件を全て満たすようなものです:
・クラスである
・@Injectable()デコレーターでアノテーションされている
・CanActivateというインターフェースをimplementsしている
・canActivateという、ExecutionContext型を引数にとり、同期または非同期でboolean値(trueまたはfalse)を返すメソッドを実装している
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 となるため 情報の再利用が可能となります
src/users/users.module.ts
import { AuthService } from '../auth/auth.service';
// AuthService を追加します
@Module({
providers: [UsersResolver, UsersService, AuthService],
})
@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);
}
以上です。
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/
デコレーターを作成し、そこから取り出します。
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
}
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/’,
});
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}`);
"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",
vi .env.development
試しに API_PORT という値をセットしてみます
DATABASE_URL="mysql://myuser:mypassword@localhost:3306/mydb?schema=public"
API_PORT=4000
src/main.ts
await app.listen(3000);
↓
await app.listen(Number(process.env.API_PORT) || 3000);
npm run start:dev
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
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
npm i -g @nestjs/cli
nest new myapp-api
cd myapp-api
npm i @prisma/client
npm i -D 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"
次のような簡単なリレーションを持つテーブルを作成します
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
}
docker ps
docker 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;
以下のようにすると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
マイグレーションファイルを作成せずに、同期する
マイグレーションファイルを生成せず、スキーマを同期する
npx prisma migrate devを実行すると、毎回スキーマファイルが作成されるので、
開発時など頻繁に変更するときには少しめんどくさい。
開発用のコマンドもあり、npx prisma db pushを使うと、
マイグレーションファイルを生成せず同期できる。
いろいろ試して、変更点が整理できたら、
マイグレーションファイルを作成するのがよい感じ。
引用 : https://www.memory-lovers.blog/entry/2021/10/13/113000
ファイル名 /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 へ次のコードを追記します。 scripts の一番最後の行に入れておくといいでしょう。
"scripts": {
...................
"seed": "ts-node prisma/seed.ts"
},
npm run seed
または 直接ファイルを実行してもokです
npx ts-node prisma/seed.ts
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" } ] } ] } ]
https://medium.com/yavar/prisma-relations-2ea20c42f616
テストを一通り実行して確認しておきます
npm run test
npm run test:e2e
npm run test:cov
npx nest generate resource users --dry-run
(実際に実行するときは、 --dry-run を外します)
npx nest generate resource users
( REST API を選択してエンター )
npm run start:dev
http://localhost:3000/ へ アクセスして Hello,world! が表示されることを確認します
http://localhost:3000/users へ アクセスして This action returns all users が表示されることを確認します
以下のように書き換えて保存します
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 へ アクセスしてユーザ一覧が返ってくることを確認します。
npm i @nestjs/graphql @nestjs/apollo graphql
schema.gql を自動生成させるために、あらかじめサーバを起動しておきます
npm run start:dev
起動後に必ずエラーが出ていないことを確認しましょう。
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 も生成されます。
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>;
}
クエリの操作を行うリゾルバ / サービスを修正します。
リゾルバ:一旦そのままです。変更しません。
サービス: 以下のように修正します
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 } });
}
http://localhost:3000/graphql へアクセスして
{
gusers{
id,name,email,createdAt,updatedAt
}
}
クエリを投げて、正しくデータが返ってくることを確認します。
getSources → getMySources に名前変更するには以下のようにします
@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
nest new my-api
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
cd my-api
npm install @prisma/client
npm install --save-dev prisma
npx prisma init
ディレクトリ直下に 2つのファイル prisma/schema.prisma と .env が生成されます
データベースがMySQLの場合は以下のように修正します
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
.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"
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
実行すると
本番サーバーではロールバックすることがない(修正する場合は、修正するバイブレーションを追加すると言う方式)ため
# データベースをリセットしDBへ反映
npx prisma migrate reset
# マイグレーションのステータスチェック
npx prisma migrate status
# マイグレーションの実行・DBへ反映
npx prisma migrate deploy
# 型情報の生成
npx prisma generate
npx prisma studio
package.json に追加しておくと、コマンド覚えなくていいです。
"scripts": {
.............
"prisma:studio": "dotenv -e .env.development -- npx prisma studio",
}
"prisma:studio": "dotenv -e .env.development -- npx prisma studio",
ファイル名 /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 次のコードを追記します
"prisma": {
"seed": "ts-node ./prisma/seed.ts"
},
npx prisma db seed
または 直接ファイルを実行してもokです
npx ts-node prisma/seed.ts
ターミナルから以下を実行します。
brew install anyenv
echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
echo $SHELL -l
一旦ターミナルを終了して、再度起動。
メッセージが出るので次のコマンドを実行
anyenv install --init
anyenv install nodenv
exec $SHELL -l
これでインストールは完了です。
nodenv install -l
nodenv install 18.14.0
M1/M2 Macには バージョン16以上しかインストールすることができませんのでご注意ください
nodenv global 18.14.0
フォルダ「my-project」以下は v16.19.0 を使用するようにセットします
cd my-project
nodenv install 16.19.0
nodenv local 16.19.0
これで、フォルダ「my-project」以下は必ず 16.19.0 になります。
node -v
正しくバージョンが表示されればOKです。
nodenv versions
touch $(nodenv root)/default-packages
cd ${NODENV_ROOT}
git pull
cd ${NODENV_ROOT}/plugins/node-build/
git pull
~/.bash_profile に記述しておきます
# Node.js Global pathをセット
node_global_path=$(npm root -g)
echo ${node_global_path}
export NODE_PATH=${node_global_path}
(この方法は現在となっては古いやり方です)
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": {
"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
その他参考 : https://7rikazhexde-techlog.hatenablog.com/entry/2022/08/05/020136
curl https://get.volta.sh | bash
(インストール後にターミナルを終了して、再起動します)
バージョン 14.17.0 をインストールする例
volta install node@14.17.0
バージョン 18系の 最新をインストールする例
volta install node@18
volta list all
(インストール後にターミナルを終了して、再起動します)
node -v
rm -rf ~/.volta
export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"
サーバーを立てたいディレクトリに移動した後
npx http-server
これだけで okです。
サーバーを立てたいディレクトリに移動した後
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. |
npm install @aws-sdk/client-s3
npm install @aws-sdk/s3-request-presigner
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
"type": "module"
node geturl.js
一言でモックと言っても、実は以下のように分類されます.
dummy
, fake
, spy
, stub
, mock
Jestでは 大きく分けて、次の2つ jest.mock
, jest.spyOn
を使います。
.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 の時付かない
・ヘッダ、ペイロード(データ本体)、署名をドットでつないで Base64 エンコードしたもの。
・中身は Base64エンコードしただけなので、ペイロードの中身は誰でも見ることができる。
・電子署名を使用して、ペイロードが改ざんされていないか?、正規の発行元以外から勝手に発行されたものかどうか?などを検証することができる。
・よく「JWT脆弱性」というワードが出てくるがJWT自体に脆弱性はない。
(正しくは「JWTを使用した認証システムに脆弱性が存在することがある」)
https://github.com/auth0/node-jsonwebtoken
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
https://jwt.io/ にトークンを入力するとヘッダに
{
"alg": "RS256",
"kid": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"typ": "JWT"
}
と表示されます。("alg": "RS256" なので 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が有効か?)」の検証
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("<検証したいトークン>");
○「HTTP OnlyでSecureなCookieに保存する」
×「LocalStorageに保存する」 ( local storageはあらゆるJavaScriptコードから自由にアクセスできてしまいます )
・共通鍵のアルゴリズムは選択しない
・鍵の長さは256bit以上とする
・検証をせずにデコードだけすると、改ざんまたは勝手に発行されたトークンを見ている可能性がある
(なので alg=none を使用するのはありえない)
・認証NGとなるjwtでも、データの中身は誰でも見れるので機密情報はjwtには含めない
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 変換によって回避する方法はこちらです
export NODE_OPTIONS=--experimental-vm-modules
"type": "module" を追加します。
{
"devDependencies": {
"jest": "^28.1.2"
},
"type": "module"
}
module.exports = {
testMatch: ['<rootDir>/**/*.test.js'],
};
↓
export default {
testMatch: ['<rootDir>/**/*.test.js'],
};
以上です。
npm test
または
npx jest
してみましょう
WSL2をインストールしたら、コマンドプロンプトではなく Windowsターミナルから Ubuntu を選択して、unixを起動します。
/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)"
brew install anyenv
anyenv init
echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
exec $SHELL -l
anyenv install --init
anyenv install nodenv
exec $SHELL -l
nodenv --version
node.jsインストール時に同時に自動的にyarnもインストールしてくれるように設定する
mkdir -p "$(nodenv root)/plugins"
git clone https://github.com/pine/nodenv-yarn-install.git "$(nodenv root)/plugins/nodenv-yarn-install"
nodenv install 16.13.2
nodenv global 16.13.2
node -v
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
以下の手順でGoogle Chromeからアクセスできるようにします
node
module.paths
( Ctrl+C 2回押して抜けます。)
SET NODE_PATH=C:\laragon\bin\nodejs\node-v14\node_modules
node
module.paths
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
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 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
npm run build
firebase deploy
Hosting URL: https://my-app-xxxxx.web.app
firebase hosting:disable
URLにアクセスしてNOT FOUNDになっていることを確認します。
"build": {
"mac": {
"icon": "./icon.png"
},
"win": {
"icon": "./icon.png"
},
}
package.json に以下の設定を追加します。 (アイコン画像は path/to/icon.icns に設置)
{
...
"config": {
"forge": {
...
"electronPackagerConfig": {
"icon": "path/to/icon.icns"
},
...
}
}
}
package.json に以下の設定を追加します。 (アイコン画像は path/to/icon.icns に設置)
{
...
"config": {
"forge": {
...
"packagerConfig": {
"icon": "path/to/icon.icns"
},
...
}
}
}
npm run package で再度ビルドを行います
なおアイコン画像の形式はMacの場合icns形式である必要があります。
こちらのウェブサイトからPing画像からicnsへ変換することができます
const { shell } = require('electron')
shell.openExternal('http://localhost/')
const { shell } = require('electron')
shell.openPath('/Users/hogehoge/テスト フォルダ/001');
vue create electron-vue-hello
cd cd electron-vue-hello
npm run serve
http://localhost:8080/ にアクセスして起動することを確認します
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"
},
npm run electron:serve
npm run electron:build
ここまで来れました準備は全て完了です次はプログラムを記述していきましょう。
npm run electron:build
例
"main": "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();
}
});
npm install electron --save-dev
npx create-electron-app my-hello-app
cd my-hello-app
npm start
アプリが起動します
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.html で renderer.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,
},
});
npm run make
out フォルダにアプリができます。 (ファイルサイズが大きいのは諦めましょう)
npx electron-forge make --arch=ia32
こちらを clone してビルドしてみましょう。 https://github.com/electron-react-boilerplate/electron-react-boilerplate
https://blog.craftz.dog/how-to-make-your-electron-app-launch-1000ms-faster-bcc2997fa1f7
npm install electron-builder --save-dev
node_modules/.bin/electron-builder --mac --x64
npx electron-builder --win --x64
npx electron-builder --win --ia32
npm install --save dotenv
「プロジェクトのトップディレクトリ」に .env ファイルを作成します
.env
HOGE=テスト文字列
index.js
//.envに記述したデータを読み込み
require('dotenv').config();
// 環境変数の表示
console.log('● process.env');
console.log( process.env );
ビルドしたときもビルドしたトップディレクトリに .env を設置する必要があります。
const { shell } = require('electron')
shell.openExternal('https://www.google.com/')
app.on('ready', createWindow);
↓
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
.... 何かしらの行いたい処理
setTimeout( function(app) {
app.exit();
}, 1500, app );
} else {
app.whenReady().then(() => {
.... 何かしらの行いたい処理
});
}
yarn でインストール
yarn add lodash
yarn add --dev @types/lodash
または
npm でインストール
npm install lodash
npm install --D @types/lodash
次のようなコレクション系データがあったとします。
var stageItems = [
{ id: 5, name: "山田" },
{ id: 6, name: "斎藤" },
{ id: 7, name: "朝倉" },
{ id: 8, name: "神山" },
{ id: 9, name: "山田" },
];
import find from 'lodash/find';
// id = 6 なアイテムを選択します
const item = find(stageItems, { id:6 });
// 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: "山田" },
};
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; });
},
drop : 先頭 n個 削除
dropRight : 末尾 n個 削除
const data2 = _.dropRight(data, 3) // 先頭 3つを削除
const data2 = _.dropRight(data, 2) // 末尾 2つを削除
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階層以降にオブジェクトがあった場合 参照コピー となります。
const clonedItem = _.cloneDeep(item);
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
https://www.labnol.org/code/import-lodash-211117
import capitalize from 'lodash.capitalize';
const capitalizeFirstName = (name) => {
const result = capitalize(name);
console.log(response);
};
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
https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore
https://youmightnotneed.com/lodash/
// 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; })
// 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);
// 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]
// 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]
// 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
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
で新規作成します。
npm run dev
npm run build
npm run start
新規ファイルを作成
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/ フォルダに書出が行われます。
JSON ファイルにコメントを記述することはできませんが以下のように無効なパラメーターとして登録しておくことでコメントを対応させることができます。
_comment にコメントを記述する
{
"_comment": "comment text goes here...",
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
}
}
}
}
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: cannot open shared object file: No such file or directory
と言うようなエラーが表示される場合があります
chromiumをインストールします(Centos stream)
dnf install -y chromium
libXScrnSaverをインストールします(Centos stream)
dnf install -y chromium
その他参考 : https://github.com/puppeteer/puppeteer/issues/5361
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
const browser = await puppeteer.launch();
↓
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
としておきましょう。
npm install -g vsce
npm install -g yo
yo doctor
vsce --version
2.15.0
バージョン番号が返ってくればOKです。
Azure Dev Ops にログインして「Profile」→「Personal access tokens」から Personal Access Tokens を作成します。
作成したトークンをコピーしておきます。
vsce login <ユーザー名>
(コピーしておいたトークンを使ってログインします)
作成した拡張をgithubにpushします。(詳細は割愛します)
"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 -p <token>
コマンドのヒストリにトークン文字列が残るので注意しましょう
npm ls で deduped がたくさん出るようなら yarn への乗り換えを検討しましょう。
● yarn を使用する
brew install yarn
● yarn のバージョンを確認する
yarn -v
1.19.1
● npmの代わりに yarn を使用する
npm install image-js
↓
yarn add image-js
Webpack4 + Babel を使用して IE11 での動作を確認する。
ローカルにインストールします( サイズはさほど大きくなく、46MB 程度なので複数のローカル環境に入れてもあまりディスクの負担になりません)
webpackとwebpack-cliとwebpack-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!!!');
}
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.config.js
watch: true を追加すると watch します
module.exports = {
........................
watch: true
};
./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>
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
}
};
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 を読み込むようにするには
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}},
],
},
]
}
● 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
ES6で書いたJavaScriptは IE11 では使えないので babel をwebpackから呼び出してIE11でも使える状態でコンパイルします
./src/Myclass.js
export class Myclass {
constructor(id,userName) {
this.id = id;
this.userName = userName;
}
alertUserName() {
alert(this.userName);
}
}
./src/Myclass.js
import {hellowebpack} from './f';
import {Myclass} from './Myclass.js';
hellowebpack();
var m = new Myclass(1, 'DOWN TOWN');
m.alertUserName();
npm install -D webpack webpack-cli babel-loader @babel/core @babel/preset-env
webpackと合わせると 70MB くらいになります
下記のように書き換えます。
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',
]
}
}]
}]
}
};
npx webpack
本番サーバへアップロードして IE11で確認します。
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://packagecontrol.io/packages/JsPrettier
Sublime Textのパッケージコントローラーからインストールします
(command + shift + p) →【Install Package を選択】→【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
(command + shift + p) →【JsPrettier:Format Codeを選択】
とても便利なのでぜひショートカットを設定しましょう。
// JsPrettier
{ "keys": ["ctrl+p"], "command": "js_prettier" } ,
今時ES6 JavaScriptで記述することが多くなったので
コード検証ツールにESLint、自動整形ツールにPrettierをインストールしてSublimeTextから使用できるようにします。
npm install -g eslint
npm install -g prettier
ここまでの状態で自動整形のテストを行います
test.js ファイルを用意して自動整形してみます
prettier test.js
ターミナル画面(標準出力)に自動整形した結果が表示されます。
実際に自動整形してファイルを更新するには( --write )オプションを使用します。
prettier --write test.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"
]
}
};
eslint test.js
でソースコードの検証が実行されます。
.eslintrc editor
https://pirosikick.github.io/eslintrc-editor/
「Airbnb JavaScript Style Guide」はなかなか厳格なルールなので
既存のプロジェクトにいきなり当てはめるには無理がありますが
どの程度厳格なソースフォーマット指定なのか体験しておくだけでも価値があると思います
https://github.com/airbnb/javascript
npm init -y
npm install --save-dev eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y eslint
{
"extends": "airbnb"
}
node.jsでオブジェクトの中身を一気に表示するにはconsole.logで表示しても良いのですが
次のようにすると楽に見れます
npm install util --save-dev
通常
console.log(results);
とする代わりに
var util = require('util');
console.log(util.inspect(results,false,null));
とします。
システムの開発がphpかつテンプレートエンジンTwigを使用する場合
デザイン過程で静的なhtmlを作成する場合にもTwigを使用しておくとプログラムへのテンプレート化する時に
作業を省くことができます
実はGulpにはPHPと同じTwigテンプレートがあるのでこちらを使用します。
node.js とgulp はあらかじめインストールしておきます。
npm install gulp-twig --save-dev
以下の仕様でコンパイルするよう設定を記述します
● ./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
テンプレートファイル内に以下のように記述します
{{ _target.relative }}
{{ _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 -->
electonとは html や css や javascript などといった web 制作の技術を使ってデスクトップ上のアプリケーションを制作しようというものです。 ( まず node js が必要になりますので node js のインストールを完了させておいてください )
npm install -g electron
npm install -g electron-packager
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 .
electron -v
(例 : v1.6.2)
electron-packager <ディレクトリ> <アプリ名(拡張子は除く)> --platform=darwin,win32 --arch=x64 --version=<バージョン> --overwrite
https://qiita.com/tags/electron
https://teratail.com/questions/search?q=electron&search_type=and
http://bit.ly/2nYCc4d
http://bit.ly/2n8dA47
https://github.com/djyde/iCultus
https://github.com/theodi/comma-chameleon
http://sachinchoolur.github.io/lightgallery-desktop/
nodebrew を使って以下のパッケージをインストールします
brew cask install xquartz(途中で管理者パスワードの入力を求められます)
brew install wine
brew uninstall nodebrew
brew uninstall node
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 ls-remote
nodebrew install-binary v9.5.0
nodebrew list
nodebrew use v9.5.0
node -v
バージョン名が正しく表示されればOKです。
npm install sqlite3
sqlite3 mydb.sqlite3
CREATE TABLE user_dt (
data_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_name TEXT,
modified_date DATETIME
);
.quit
```
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/
ヘッドレスブラウザとは「画面がないWEBブラウザ」の事です。
最近では Headless Chrome が人気です。 参考 : https://goo.gl/NQsFS2
が、npm だけでインストールできる phantomJS を紹介します。
WebKit(Safari) ベースのヘッドレス(画面なし)ブラウザ
npm install phantomjs
以下のパスを手動で追加します
./node_modules/phantomjs/bin/phantomjs
brew install phantomjs
yum -y install freetype
yum -y install fontconfig
npm install -g phantomjs
Gecko(firefox) ベースのヘッドレス(画面なし)ブラウザ
brew install slimerjs
npm install -g slimerjs
ヘッドレスブラウザを簡単に扱うライブラリ(JavaScript)です。 このcasperJSから「phantomJS」または「slimerJS」を操作します。
brew install casperjs
yum -y install freetype
yum -y install fontconfig
npm install -g casperjs
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 test.js
casperjs --engine=slimerjs test.js
slimerJSで起動するときは --engine=slimerjs を追加します。
https://chrome.google.com/webstore/detail/resurrectio/kicncbplfjgjlliddogifpohdhkbjogm
Mac OSX の場合は brew でインストールできます
brew install slimerjs
インストールが完了したらバージョンを確認しておきます
slimerjs -v
```
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下の環境でも実行できます。
Heroku上でヘッドレスWEBブラウザ phantom.js を使用するにはビルドパックを追加します。 ビルドパックをGithub上で公開してくれている方がいるのでありがたく利用させていただきます。
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
heroku run phantomjs -v
バージョンが帰ってくればOK
`
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();
});
git add .
git commit -m "add buildpack"
git push heroku master
heroku run phantomjs phantom_test.js
「Status: success」が帰ってくればOK
フォントは `
./fonts`
ディレクトリに .ttf フォントファイルを置いて git push すればOKです。
フォントファイルは著作権に注意して使用しましょう
https://www.google.com/get/noto/#sans-jpan
heroku run fc-match sans:lang=ja
Buildpacks | Heroku Dev Center
heroku buildpacks
heroku buildpacks:set "ビルドパック名またはURL"
heroku buildpacks:add "ビルドパック名またはURL"
heroku buildpacks:remove "ビルドパック名またはURL"
heroku buildpacks:clear
node.js から phantomJSを使用するには下記2つの方法がおすすめです。
npm install nightmare
npm install vo
`
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_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です。
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 に保存されれば正常に動作しています。
brew install phantomjs
http://phantomjs.org/download.html
console.log('Hello, world!');
phantom.exit();
phantomjs hello.js
Hello, world!が表示されればインストール成功です。
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 というキャプチャ画像があれば成功です。
(「ユーザー登録してWEBアプリをデプロイしてHeroku上で実行する」までの方法)
https://www.heroku.com/
からユーザー登録を行う
https://devcenter.heroku.com/articles/heroku-command-line
からインストーラをインストール
heroku login
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
cd "アプリのディレクトリ"
git init
git add .
git commit -m "first commit"
heroku create
git push heroku master
heroku open
git add .
git commit -m "change xxxxx"
git push heroku master
heroku open
heroku login
「メールアドレス」「パスワード」を入力してログインします。
heroku apps
と入力すると herokuアプリが
hogehoge-fugafuga-12345
hunihuni-furifuri-67890
という風に表示されます。
アプリ hogehoge-fugafuga-12345 をブラウザで表示するには
heroku open --app hogehoge-fugafuga-12345
と入力します。 またはブラウザのアドレス欄に直接
https://hogehoge-fugafuga-12345.herokuapp.com
と入力してもOKです。
アプリ hogehoge-fugafuga-12345 を削除するには
heroku apps:destroy --app hogehoge-fugafuga-12345 --confirm hogehoge-fugafuga-12345
heroku apps:destroy --app hogehoge-fugafuga-12345
heroku run bash
・Heroku上のホームディレクトリのファイル一覧を表示
heroku run pwd ; ls -la
・Heroku上のphantomjsのバージョンを表示
heroku run phantomjs -v
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
>Node.jsでWEBページのスクレイピングを行う際に必要となる文字コードの変換と、cheerioによってパースしたHTMLをjQueryのように操作できるHTTPクライアントモジュールです。
特徴 : 文字コード自動変換 , jqueryライクなセレクタ , フォーム簡易送信機能
https://www.npmjs.com/package/cheerio-httpcli
npm install cheerio-httpcli
>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/