venvで作成したPythonの仮想環境内にインストールされているバージョンを変更してみました。
]]> <![CDATA[Pythonのバージョン管理にはpyenvを利用します。
brewを使ってインストール
brew install pyenv
.zshrcに以下の設定を追加します。
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
変更後にsourceコマンドで変更した内容を反映します。
source ~/.zshrc
以下のコマンドでインストール可能なバージョンを調べることができます。
pyenv install --list
今回はPythonの3.9.7をインストールしたかったので以下のコマンドでインストールしてローカル環境に反映します。
pyenv install 3.9.7
pyenv local 3.9.7
python --version # Python 3.9.7
ローカル環境のPythonのバージョンを変更して仮想環境内を確認してみましょう
source venv/bin/activate
python --version # Python 3.12.2
まだ、仮想環境内はまだバージョンが変わっていません。
一度--clearオプションをつけて仮想環境を作り直すことで現在のPythonバージョンが反映されるようになります。
deactivate
python3 -m venv venv --clear
source venv/bin/activate
python --version # Python 3.9.7
venvで作成した仮想環境内のPythonバージョンを変更したい
pyenvで複数のバージョンのPythonをインストールしよう!
pyenvの使い方
Tailwind CSSでは簡単にメディアクエリの指定が可能です。
]]> <![CDATA[基本的にはtailwind.config.jsのscreensに指定されているブレークポイントをもとにメディアクリの指定が可能です。
// tailwind.config.js
module.exports = {
theme: {
screens: {
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px"
},
},
};
このように指定されていれば、以下のように各ブレークポイントに対する指定を行うことができます。
<div class="p-4 sm:p-5 lg:p-6 xl:p-10">
....
</div>
細かいスタイルの調整を行う際にもscreensに追加すればscreensの値がどんどんと肥大化してしまいます。
任意のブレークポイントは min-[500px]:クラス という指定になります。
以下のサンプルではウィンドウ幅が500pxと900pxと1200px時にパディングがきりかわるように指定されています。
<div class="p-4 min-[500px]:p-5 min-[900px]:p-6 min-[1200px]:p-10">
....
</div>
参考: Tailwind CSS v3.2: Dynamic breakpoints, multi-config, and container queries, oh my! - Tailwind CSS
]]>Tailwind CSSでdisabled属性がついた要素に対してスタイルを当てたい場合には、disabled擬似クラスを利用して指定を行います。
]]> <![CDATA[以下のようにdisabled:をつけることでdisabled属性がついた場合のスタイルが定義できます。
<input type="text" disabled class="disabled:bg-gray-400" />
disabled属性がついた要素の兄弟要素にスタイルを指定したい場合はpeerを利用します。
disabled属性がついた要素にclass属性「peer」を指定して、兄弟要素には「peer-disabled:」としてdisabled属性がついた場合のスタイルが定義できます。
<input type="text" disabled class="peer" />
<label class="peer-disabled:bg-gray-400">text</label>
arbitrary variantsを利用するとCSSセレクタを利用してより柔軟に指定ができます。
[:disabled+&]:と指定をすると隣接セレクタを利用してdisabled属性が指定された要素の次に登場する要素の場合という条件の場合のスタイルが定義できます。
<input type="text" disabled class="peer" />
<label class="[:disabled+&]:bg-gray-400">text</label>
]]>
Vue.jsでボタンコンポーネントなどを作成する場合、ユースケースに応じてHTMLの実体をa要素とbutton要素と切り替えたいケースというのが発生します。
]]> <![CDATA[そういったケースはVue.jsの動的コンポーネントを利用することで対応ができます。
以下はasHtml Propsでa要素とbutton要素を切り替えているサンプルです。
<template>
<component :is="asHtml" :href="href">
<slot />
</component>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
asHtml?: 'a' | 'button';
href?: sting;
}>(),
{
asHtml: 'a',
href: undefined,
},
);
</script>
デフォルトはbutton要素が利用できるようにしているのでa要素を利用したい場合のみas-html porpsを指定して利用します。
<template>
<div>
<!-- button要素として利用 -->
<Button>button</Button>
<!-- a要素として利用 -->
<Button as-html="a" href="https://www.to-r.net">a</Buttons>
</div>
</template>
]]>
案件でNuxt3のESLint設定をすることがあったので手順をメモしておきます。
]]> <![CDATA[Nuxt3はNo PrettierポリシーなのでPrettierは推奨されておらずフォーマッティングも含めてESLintを利用することが推奨されています。
まずは以下のコマンドでNuxt3のインストール。マネージメントツールはnpmにします。
npx nuxi@latest init my-app
インストールしたら起動しておきます。
cd my-app
npm run dev
以下のパッケージをインストールします。
npm install -D eslint eslint-plugin-nuxt @nuxtjs/eslint-config-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser
.eslintrcを以下のような内容でルートに配置します。
基本の設定にmulti-word-component-namesのルールからlayoutsディレクトリやpagesディレクトリを除外するように追加で設定しています。
{
"root": true,
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
"@nuxtjs/eslint-config-typescript",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"overrides": [
{
"files": ["layouts/*.vue", "pages/**/*.vue"],
"rules": { "vue/multi-word-component-names": "off" }
}
]
}
package.jsonのscriptsに実行コマンドを追加
{
...
"scripts": {
...
"lint": "eslint --fix './**/*.vue'"
},
....
}
これでESLintが利用できますので `npm run lint` でESLintが実行されるか確認しておきます。
以下の内容を `.vscode/settings.json` に記述しておきます。
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.fixAll.eslint": true
}
}
これで自動保存時にESLintの内容でフォーマッティングが行われます。
Auth0は認証・認可の仕組みを提供してくれるIDaaS(Identity as a Service)です。
]]> <![CDATA[公式サイトに実装サンプルも豊富でNext.jsを利用した認証のサンプルコードなども公式サイトに用意されており手軽に利用できます。
Auth0 Next.js SDK Quickstarts: Login
ただ、Next.jsのAPI Routesを利用した認可用のサンプルコードがなかったので作成してみました。
src/features/auth0/util.tsなど適当なファイルに以下のような確認用の関数を作成します。
jsonwebtokenはJWTの確認に利用されるライブラリで、jwk-to-pemはjwkをpemファイルに変換するライブラリです。
import jwt from 'jsonwebtoken';
import jwkToPem from 'jwk-to-pem';
/**
* Auth0の公開鍵をpemに変換して返す
* @returns 公開鍵のpem
*/
const jwks = async () => {
// Auth0の公開鍵のURL
const path = `${process.env.AUTH0_ISSUER_BASE_URL}/.well-known/jwks.json`
// Auth0の公開鍵を取得
const response = await fetch(path, {
method: 'GET'
});
const jwk = (await response.json());
// 公開鍵の1つ目をpemに変換する
const pem = jwkToPem(jwk.keys[0]);
return pem;
};
/**
* 受け取ったidトークンが認証済みかどうかを確認して結果を返す
* @param idToken
* @returns 認証済みかどうか
*/
export const authCheck = async (idToken: string) => {
const pem = await jwks();
try {
jwt.verify(String(idToken), pem, { algorithms: ['RS256'] });
return true
} catch (e) {
console.error(e)
return false
}
}
src/pags/api/aaaa.tsなどのAPIでは先程作成したauthCheck関数を利用して認可確認をしてから実際の処理を記述していきます。
import { NextApiRequest, NextApiResponse } from "next"
import { getSession } from '@auth0/nextjs-auth0';
import { authCheck } from "@/features/auth0";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession(req, res);
const idToken = session?.idToken;
if (!idToken || !await authCheck(idToken)) {
return res.status(401).json({
message: 'ログインしてください。'
})
}
// 認可済みの処理を書く
res.status(200).json({ aaa: 'bbb' })
};
]]>
AstroでgetStaticPathsの返り値としてpropsが指定できます。
]]> <![CDATA[export async function getStaticPaths() {
return [
{
params: { id: "foo" },
props: { text: "abcd" },
},
{
params: { id: "var" },
props: { text: "efg" },
},
];
}
TypeScriptで型指定を行いたい場合に、この場合はporpsの方は{ text:string }なのでファイル中に以下のような型定義を行えば型の指定が可能です。
export type Props = {
text: string;
}
propsはgetStaticPathsの返り値として定義されているので頑張ればのgetStaticPaths返り値の型推論として取り出すこともできます。
以下のコードでは、`ReturnType
export type Props = (ReturnType extends Promise
? T
: never)[0]["props"];
可能ではあるものの、ややこしいだけなので別途型情報を明記したほうがよさそうです。
]]>SveltKitでVSCodeのPrettierの自動フォーマッティングを有効にするにはプロジェクトのルートに .vscodeディレクトリを配置して、その中のsettings.jsonに以下の記述を追加する。
]]> <![CDATA[{
"[svelte]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "svelte.svelte-vscode"
},
}
保存したファイルだけではなくプロジェクト全体にPrettierのフォーマッティングを適用したい場合はCLI上から 「npm run format」コマンドを実行すればよい
]]>Cloudflare PagesにSvelteKitで作成したアプリケーションをデプロイする際のNodeバージョンでハマってしまったのでちょっとメモ
]]> <![CDATA[執筆時(2022/1/24)でのSvelteKitのバージョンは「1.1.1」でCloudflare Pagesにデプロイしようとすると以下のエラーがでてしまいました。
20:20:59.114 npm ERR! code ELIFECYCLE
20:20:59.114 npm ERR! errno 1
20:20:59.114 npm ERR! @sveltejs/[email protected] postinstall: `node postinstall.js`
20:20:59.114 npm ERR! Exit status 1
20:20:59.114 npm ERR!
20:20:59.115 npm ERR! Failed at the @sveltejs/[email protected] postinstall script.
20:20:59.115 npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
Node.jsのLTSの最新バージョンは 「18.13.0」だがCloudflare PagesのBuildで利用されるデフォルトのNode.jsのバージョンが「12.18.0」とだいぶ古くインストールに失敗するようです。
参考: Build configuration · Cloudflare Pages docs
ちなみにSvelteKitの要求するNodeバージョンは16.14以上の16.x.xか18.x.xのようです。
"engines": {
"node": "^16.14 || >=18"
},
Cloudflare PagesではNode.jsのバージョンを1.7.xまで指定することができるので16系統の最新を指定すれば、SvelteKitの要求とCloudflare Pageで利用できるバージョンが合致します。
Cloudflare PageではSettingsのEnvironment variablesでNODE_VERSIONの指定が可能ですので、16系統の最新である「v16.19.0」を指定することでビルドとデプロイが正常に動作します。
17系統でもインストール可能という記事を見かけたのですが、試したところ以下のようなエラーで止まってしまいます。
20:43:14.630 npm ERR! engine Not compatible with your version of node/npm: @sveltejs/[email protected]
20:43:14.630 npm ERR! notsup Not compatible with your version of node/npm: @sveltejs/[email protected]
20:43:14.630 npm ERR! notsup Required: {"node":"^16.14 || >=18"}
20:43:14.630 npm ERR! notsup Actual: {"npm":"8.11.0","node":"v17.9.1"}
SvelteKitは執筆時点でもかなりの速度でアップデートがおこなわれているので一過性の現状の可能性もありますがハマったひとは参考にしてみてください。
]]>Next.js + TypeScript においてgetStaticPropsの型を定義する方法はいくつか存在します。
]]> <![CDATA[getStaticPropsの型を定義したいモチベーションとしてはNextPageのPropsとして受け取る値とgetStaticPropsが返すpropsの値を揃えたいという観点が一番でしょう。
import type { NextPage } from 'next'
type Props = {
aaa:string
bbb:string
}
const Home:NextPage<Props> = (props) => {
const {aaa,bbb} = props
return (
<div>page</div>
)
}
export const getStaticProps = async () => {
return {
props:{
aaa:"aa",
bbb:"bb",
}
}
}
export default Home
このままでは以下のようにPropsとしてbbbが欠落している状態を検出できません。
export const getStaticProps = async () => {
return {
props:{
aaa:"aa",
}
}
}
必要な値の不足に関してはNext.jsが提供するGetStaticProps型にGenericsとしてPropsを渡して上げると型エラーの検出が可能になります。
import type { NextPage , GetStaticProps } from 'next'
type Props = {
aaa:string
bbb:string
}
const Home:NextPage<Props> = (props) => {
const {aaa,bbb} = props
return (
<div>page</div>
)
}
export const getStaticProps: GetStaticProps<Props> = async () => {
return {
props:{
aaa:"aa",
}
}
}
export default Home
ただ、GetStaticProps<Props>では以下のように余剰にあるPropsの検出はできません。
export const getStaticProps = async () => {
return {
props:{
aaa:"aa",
bbb:"bb",
ccc:"cc",
}
}
}
Propsの値を正確に精査したい場合には、返り値としてPromise<GetStaticPropsResult<Props>>を指定します。
import type { NextPage , GetStaticPropsResult } from 'next'
type Props = {
aaa:string
bbb:string
}
const Home:NextPage<Props> = (props) => {
const {aaa,bbb} = props
return (
<div>page</div>
)
}
export const getStaticProps = async ():Promise<GetStaticPropsResult<Props>> => {
return {
props:{
aaa:"aa",
bbb:"bb",
ccc:"cc",// エラー
}
}
}
ミニマムとしてはGetStaticPropsResultの定義のみで当初の目的は達成可能です。
また、Next.jsでは逆のアプローチでgetStaticPropsが返した値の型情報をPageのPropsとして利用する、InferGetStaticPropsTypeも用意されているので状況によって使い分けると良いでしょう。
]]>今年の最終営業日なので一年を振り返ってみたいと思います。
]]> <![CDATA[2021年は10人のスタッフでスタートしましたが1月に1名増え、2月に1名増え、8月に1名減り、10月に1名減り、12月に1名増えるという、これまでで一番人の入れ替えが多い年でした。
昨年がまったく増減がなかったのでエンジニアの人材市場が活発化しているということで、コロナの影響が仕事面では払拭されており業界的にはよい傾向だと思います。
来年1名増えることも確定しておりますが無理のないように規模は拡大していきたいと考えております。
コロナ前からリモートワークを行っていたこともあり、コロナ禍ではほとんど出社するスタッフがいないので2部屋借りていたオフィススペースを1部屋解約して事務所減床をおこないました。
そのときの状況はこちら(事務所減床|Kazuma Nishihata|note)を参考にしてください。
これまで、Reactがメインでしたが少しづつNext.jsがメインにシフトしてきています。
フレームワーク別の案件割合は以下のようになります。(規模がまちまちなので12人月を超えるような案件は大規模としています。)
React案件 5件(内大規模3件)
Next案件 3件(内大規模1件)
Vue案件 1件(大規模)
Nuxt案件1件
Angular案件 1件(大規模)
ほかにもコーディングのみの案件やWordPress案件など多数のお仕事をいただきました。
また、これまでのトゥーアールの案件傾向としてはメガベンチャーやベンチャー支援が多かったのですが、官公庁のシステムやサイト開発などのご依頼をチラホラといただくようになり時代の変化を感じられたのも今年の特徴でした。
いろいろとありましたが今年1年ありがとうございました。
来年はオフラインのイベントなどが開かれるぐらい世の中の状況がよくなっていると良いなと思いつつ、引き続き頑張っていきたいと思います。
業務でマークダウンパーサーを作成していてJestでmockデータのマークダウンを読み込もうとするとエラーになりました。
]]> <![CDATA[const markdownText = require('./sample.md')
エラー内容としては以下のような感じでJavaScriptの文法おかしいよとのこと。
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.
マークダウンをstringデータとして取得したい場合は、jest-raw-loaderを利用します。
以下のコマンドでjest-raw-loaderをインストールして
npm install jest-raw-loader -D
jest.config.jsのtransformに以下を追加します。
transform: {
'\\.md$': 'jest-raw-loader'
},
これでマークダウンをテキストデータとして読み込みテストができるようになります。
]]>next/routerを利用しているコンポーネントを単体でテストする場合にはNext Routerのモックを事前に作成しておく必要があります。
]]> <![CDATA[以下のようにnext/routerを利用している場合に
import { useRouter } from 'next/router';
export const Component = () => {
const router = useRouter();
return (<h1>{router.pathname}</h1>)
}
次のようなtestを書くと、router.pathnameがundefindになるとエラーになってしまいます。
import React from 'react';
import { render, screen } from '@testing-library/react';
import Component from './Component';
describe('Component', () => {
it('should render', () => {
const { asFragment } = render(<Component />);
expect(asFragment()).toMatchInlineSnapshot()
});
});
その場合は事前にnext/routerにjest.mockでルーター情報を指定してあげることで、その情報をもとにテストが実行されるようになります。
import React from 'react';
import { render, screen } from '@testing-library/react';
jest.mock('next/router', () => ({
useRouter() {
return {
route: '/',
pathname: '/',
query: '',
asPath: '',
};
},
}));
import Component from './Component';
describe('Component', () => {
it('should render', () => {
const { asFragment } = render(<Component />);
expect(asFragment()).toMatchInlineSnapshot()
});
});
]]>
Next.jsのアプリケーションのデプロイにGithub Actionsを利用していたところ以下のような警告が出ているのに気づきました。
]]> <![CDATA[warn - No build cache found. Please configure build caching for faster rebuilds. Read more: https://err.sh/next.js/no-cache
info - Creating an optimized production build...
Attention: Next.js now collects completely anonymous telemetry regarding usage.
This information is used to shape Next.js' roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
これによると .next/cache ディレクトリをキャッシュしておくことによりbuildタスクの高速化が図れるようです。
例示されているGitHub Actionsのcacheコードをデプロイタスクに追加
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Use Node.js 14.15.4
uses: actions/setup-node@v1
with:
node-version: '14.15.4'
- name: Use Github Action cache
uses: actions/cache@v2
with:
path: ${{ github.workspace }}/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- name: npm install
run: npm ci
- name: npm build
run: npm run build --if-present
env:
CI: true
こうすることでbuildタスクの所要時間が30秒ちょっとから20秒ちょっとに短縮されました。キャッシュの取得と保存に3秒づつ追加されましたので実感はありませんがビルドするページの総量が増えてきた場合に威力を発揮しそうです。
これまで雰囲気でGithub Actionsを使っていましたがまだ知らない便利機能がありそうなので改めて確認したいと思いました。
Next.js で Storybookを利用しようとした際にsass-loaderがエラーを出したのでまとめてみました。
]]> <![CDATA[まずは以下のコマンドでNext.jsをインストールして
npx create-next-app
次に以下のコマンドでStorybookをインストールします。
npx sb init
これで以下のコマンドでStorybookが起動できるようになります。
yarn storybook
ひとまずNext.jsのReactコンポーネントが表示できるか確認するため、stories/Home.stories.jsxに以下のファイルを作成してStorybookに表示できるか確認します。
import React from 'react';
import Home from '../pages/index';
export default {
title: 'Example/Home',
component: Home,
};
export const Template = () => <Home />;
これで表示は確認できるはずです。
以下のコマンドでSassをインストール
yarn add sass -D
styles/globals.cssをstyles/globals.scss というファイル名に変更して、Sassの動作確認ができるように以下の内容を変更しておきます。
$color: red;
body {
background: $color;
}
pages/_app.jsのCSS読み込みも以下のように変更しておきます。
import '../styles/globals.css'
↓
import '../styles/globals.scss'
これで以下のコマンドでNextアプリケーションを起動すると背景が赤に変わってSassがちゃんと動作しているのが確認できます。
yarn dev
.storybook/preview.jsの冒頭に以下を追加してSassを利用するとエラーになってしまいます。
import '../styles/globals.scss'
以下がエラー内容。
ERROR in ./styles/globals.scss 3:5
Module parse failed: Unexpected token (3:5)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
webpackのloaderを追加しろとのことなので以下のコマンドで追加します。
yarn add sass-loader -D
.storybook/main.jsにwebpackFinalを追加します。
module.exports = {
"stories": [
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
],
webpackFinal: async (config) => {
config.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
});
return config;
}
}
これでStorybookを起動しようとすると別のエラーが表示されるようになります。
ERROR in ./styles/globals.scss (./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./styles/globals.scss)
Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
TypeError: this.getOptions is not a function
実はsass-loaderの11以上がwebpack5にしか対応しておらずStorybookで利用されるのがwebpack4なのでこのようなエラーになってしまいます。
そのため、以下のコマンドでversion 10.1.1のsass-loaderがインストールするとエラーが出ずにStorybookでSassを利用できるようになります。
yarn add [email protected] -D
上記の10.1.1のsass-loaderを利用するでもよいのですが今回はMigrationを参考にStorybookでwebpack5を利用できるように変更して最新のsass-loaderが利用できるように変更します。
まずは以下のコマンドで必要なパッケージをインストールします。
yarn add webpack @storybook/builder-webpack5 @storybook/manager-webpack5 -D
.storybook/main.jsにcoreを追加します。
module.exports = {
core: {
builder: "webpack5",
},
"stories": [
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
],
webpackFinal: async (config) => {
config.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
});
return config;
}
}
これでStorybookで最新のsass-loaderが利用できるようになります。
AWS CloudFrontを利用すれば特定ディレクトリを別のS3ストレージを割り当てることができます。
]]> <![CDATA[以下のような感じでexample.comにアクセスした場合はメインS3にアクセスしますがsub-dirディレクトリ以下にアクセスした場合はサブS3のS3ストレージのファイルを表示します。
https://example.com/* [メインS3]
https://example.com/sub-dir/* [サブS3]
まずはサブS3にファイルを配置しておきます。配置する際にはルートパスから指定する必要があります。
つまり、sub-dir/を作成してその内側にhttps://example.com/sub-dir/*以下のファイルの指定を行います。
CloudFrontではメインS3にアクセスを設定しているディストリビューションを選択します。
オリジンタブに遷移してから「オリジンを作成」を押下します。
オリジンドメインにサブS3のを選択
S3 バケットアクセスにはサブS3にアクセスできるOAIを指定
ビヘイビアタブに遷移してから「ビヘイビアを作成」を押下します。
パスパターン sub-dir/*
オリジンとオリジングループ S3のオリジン
こちらを設定して「ビヘイビアを作成」を押下します。
そうするとhttps://example.com/sub-dir/* にアクセスした際にサブS3で設定したファイルの内容が表示されるようになります。
]]>GitHub Actionsを利用するとNuxt.jsやNext.jsなどで作成したWebアプリケーションを簡単にAWS S3にデプロイすることができます。
]]> <![CDATA[まずはAWSの管理画面からAWS S3にデプロイ可能なIAMポリシーとIAMユーザーを作成しましょう。
IAMポリシーは作成画面でJSONを指定することで、当該S3バケットを操作可能なポリシーを作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::[バケット名]",
"arn:aws:s3:::[バケット名]/*"
]
}
]
}
IAMユーザーはアクセスの種類を「プログラムによるアクセス」を選択して、アクセス権の設定では既存のポリシーを直接アタッチを選択して先程作成したIAMポリシーにチェックを入れます。
これでAWS S3にデプロイ可能なIAMユーザーが作成できますのでアクセスキーIDとシークレットアクセスキーをメモしておきましょう。
まずはGitHubの管理画面からSecretsとしてAWSの情報を登録します。
今回は以下のように設定しましょう。
S3_ACCESS_KEY_ID : アクセスキーID
S3_SECRET_ACCESS_KEY : シークレットアクセスキー
S3_BUCKET : S3バケット名
コードでは、.github/workflows/deploy.ymlというファイルを以下の内容で作成します
これは aws-serverブランチにpushが行われた際にNuxt.jsアプリケーションのビルドを行い、先程のS3にアップを行います。
workflow_dispatchの指定を行っているのでGitHubの管理画面から手動でワークフローを実行することも可能です。
name: deploy to aws
on:
workflow_dispatch:
push:
branches:
- aws-server
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
uses: actions/setup-node@v1
- run: |
npm install
npm run generate
- uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.S3_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.S3_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Copy Files to s3
run: |
aws s3 sync ./dist s3://${{ secrets.S3_BUCKET }}
Next.jsの場合も基本的には同じですがいくつかコマンドが変わります。
package.jsonに以下のbuildコマンドを追加して
"scripts": {
"dev": "next dev",
"start": "next start",
"build": "next build && next export",
},
.github/workflows/deploy.ymlは以下のように指定を行います。
name: deploy to aws
on:
workflow_dispatch:
push:
branches:
- aws-server
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
uses: actions/setup-node@v1
- run: |
npm install
npm run build
- uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.S3_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.S3_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Copy Files to s3
run: |
aws s3 sync ./out s3://${{ secrets.S3_BUCKET }}
これまでAWSのS3にアップした静的ファイルにBasic認証を設定するには CloudFront経由でLambda@EdgeでBasic認証を指定する必要がありましたがCloudFront Functionsの登場によって、CloudFront Functionsを利用して少しだけ簡単に設定できるようになりました。
]]> <![CDATA[CloudFront FunctionsはCloudFrontのサイドバーの「関数」から作成できます。
関数ページでは右上の「関数を作成」から適当な名前をつけてBasic認証用の関数を作成します。
CloudFront Functions はLambda@Edgeと違いECMAScript 5.1相当のJavaScriptしか記述できないので注意が必要です。
function handler(event) {
var authUser = 'user';
var authPass = 'pass';
var authString = 'Basic ' + (authUser + ':' + authPass).toString('base64');
var request = event.request;
var headers = request.headers;
if (
typeof headers.authorization === "undefined" ||
headers.authorization.value !== authString
) {
return {
statusCode: 401,
statusDescription: "Unauthorized",
headers: { "www-authenticate": { value: 'Basic realm="Please Enter Your Password"' } }
};
}
return request;
}
作成後は変更を保存して発行するとCloudFrontで利用可能になります。
CloudFrontのディストリビューションからBasic認証を設定したいディストリビューションを選択して、ビヘイビアからオリジンを選択し編集します。
関数の関連付けのビューワーリクエストに
関数タイプ : CloudFront Functions
関数 ARN/名前 :作成していた Basic認証用の関数
を指定することでCloudFront FunctionsでBasic認証が可能になります。
]]>develop や staging 、 productionなどの複数の環境で template.yaml を変更したいことがあります。
]]> <![CDATA[まずtemplate.yaml に Parametersを追加します。ここで追加した値が sam deploy時の --parameter-overridesに利用できる値が定義できますので今回はデプロイ環境を定義します。
Parameters:
Environment:
Type: String
AllowedValues:
- development # 開発環境
- staging # ステージング環境
- production # 本番環境
Default: development
次にMappingsで各環境ごとの設定したい値を指定します。今回はデプロイ環境ごとにlambdaのRoleを切り替えたかったので以下のように定義しています。
Mappings:
EnvironmentMap:
development:
lambdaRole: 'arn:aws:iam::xxxxxx'
staging:
lambdaRole: 'arn:aws:iam::yyyyy'
production:
lambdaRole: 'arn:aws:iam::yyyyy'
最後に可変させたい箇所を !FindInMap [EnvironmentMap, !Ref Environment, lambdaRole]とします。
Resources:
SetFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: xxx/
Handler: app.lambdaHandler
Runtime: nodejs14.x
Role: !FindInMap [EnvironmentMap, !Ref Environment, lambdaRole]
Events:
HelloWorld:
Type: Api
Properties:
Path: /xxx/
Method: post
これでdeploy時に--parameter-overridesを指定することでdeploy環境ごとtemplate.yamlの値を切り替えることが可能になります。
sam deploy .... --parameter-overrides Environment=development
]]>
Node.jsでバリデーションを行うには sharpがよく利用されますが、libvipsで書かれたsharpは環境によってはうまくインストールできないことがあります。
]]> <![CDATA[今回、AWS Lambdaで画像のバリデーションを行うにあたりインストール時にハマってしまったこともあり、sharpではなく Pure JavaScriptで書かれた Jimpを利用しました。
利用方法はnpmでパッケージインストールを行い。
npm install jimp
ファイルの冒頭などで読み込んでおきます。
const jimp = require('jimp')
今回のバリデート内容はファイルの実体が画像種であるか、画像のサイズが1000x1000以下かです。
フロントでも同様のバリデートを行っているためこれらのルールから外れる場合は強制的に処理を中断します。
// 画像データのバリデート
const image = await jimp.read(FileData).catch(function (err) {
console.log('画像データが不正です')
throw new Error('画像データが不正です')
})
if (image.bitmap.width > 1000 || image.bitmap.height > 1000) {
console.log('画像サイズが不正です')
throw new Error('画像サイズが不正です')
}
jimp.readで画像パスは画像バッファを指定することで画像以外の場合はエラーを返します。
また、jimp.read()は画像データを返してくれて、image.bitmap.widthやimage.bitmap.heightで画像のサイズを取得することができますのでサイズの判定に利用します。
Microsoftが「Internet Explorer は Microsoft Edge へ – Windows 10 の Internet Explorer 11 デスクトップアプリは 2022 年 6 月 15 日にサポート終了 - Windows Blog for Japan」というリリースを行いました。
よくよく読み進めていくと、これまでの「セキュリティアップデートを行なわない」といったサポート中止とは異なるすごく強制力のある発表で事実上来年以降にIE11を使った開発がほぼできなくなってしまうのではないかと感じました。
サポート終了後 にどうなるかというと FAQによるとMicrosoft Edge にリダイレクトされてしまうとのことでIE11は起動すらできなくなってしまうようです。
Windows 10における Internet Explorer デスクトップ アプリケーションを 2022 年 6 月 15 日に廃止し、サポートを終了します。この日以降、IE 11 デスクトップ アプリケーションを利用しようとすると Microsoft Edge にリダイレクトされます。
「Internet Explorer 11 デスクトップ アプリケーションのサポート終了」の発表に関連する FAQ
ただ、これによりIE11のレンダリングでサイトが全く見れなくなるわけではなくMicrosoft Edgeの Internet Explorer モードという機能を利用するとIE11のレンダリングで確認することができます。
以下、トゥーアールのサイトをMicrosoft Edge の Internet Explorer モードで表示した状態です。
ただ、Internet Explorer モードはFlagをonにするというだいぶ上級者向けの設定を行わないと使うことができません。
参考:Microsoft Edge の Internet Explorer モード - Office サポート
上級者向けとはいえ閲覧が可能なのでお客さんがどうしても表示したいと主張される場合には対応することもあるのかなと考えていたのですが、なんとInternet Explorer モードでは開発者ツールが使えないようです。
以下、Microsoft Edge の Internet Explorer モードで開発者ツールを表示した状態です。
「DevToosがページから切断されました。ページを再度読み込むと、DevTools は自動的に再接続されます」と表示されますがページを再読み込みしても繋がらずに開発者ツールを使うのは無理そうでした。
もしかしたら2022 年 6 月 15 日までに利用できるようになるかもしれませんが、このまま利用できなさそうな空気感は否めません。
そのため、IE11でこれまで通り開発するにはアップデートが適応されるまえのWindows 10 OSをアップデートせずに持ち続ける必要があります。
実際に昔はこのような方法で旧バージョンのブラウザ確認をするというのはよくあったのですが、昨今では中々に敷居が高いものだと思います。
そのためお客さんがどうしても表示したいと主張されてもなかなか難しいですので、我々製作者がIE11に対応するというのはほぼなくなってしまうのでないかと思いました。
2021/05/25 追記
IEChooserを利用すればIE modeのデバッグが可能らしいです。
参考: 2021-05-25のJS: IEの単体アプリとしてのサポート終了、WebContainers、User-Agent Client Hints - JSer.info
昨年12月に引っ越してから自宅のネットワークがすごく貧弱だなと不満を持っていたところに、緊急事態宣言を受けて強制的にテレワークの実施を余儀なくされてしまいました。
]]> <![CDATA[研修等のお仕事もフルオンラインに切り替わり、私の自宅回線が貧弱だと色々と迷惑をかけてしまうので自宅ネットワーク高速化を色々とトライしました。
結論はIPv6化したというお話ですがそこに至るまでの足取りを記録しておきます。
光回線を引いているのですがマンションがVDSL方式のためベストエフォートで100Mbpsとそれほどの速度が出ません。ここを変えるには大掛かりな工事が必要でもっと小手先の対応でどうにかしたいというのが本エントリーの趣旨です。
プロバイダーは申込みの後の確認電話で勧められたぷららを契約してました。本記事の最後ではぷららを契約したことがプラスに繋がります。
プロバイダーから貸与されたモデムにルーターとしてAirMacを繋げてWifiを飛ばしているが当初の環境でした。
モデム -> AirMac -> PC
通信速度にムラがありますが19時頃に下りで
リビング 8Mbps
仕事場 0.8Mbps
となりWifiルーターを設置しているリビングから離れている仕事場では通常のブラウジングも苦しい状態になることもありました。
流石に仕事にならないので改善案としてGoogle Nest Wifiを導入しました。Google Nest Wifiは拡張ポイントと合わせて利用することでWifiの接続快適距離を伸ばしてくれるメッシュWifiに対応しており、リビングから仕事場までの減速を軽減してくれることを期待していました。
通信速度にムラがありますが19時頃に下りで
リビング 8Mbps
仕事場 2Mbps
ぐらいの誤差で収まるようになりました。下り2MbpsあればYouTube見ながらでもお仕事できますね。ちょいちょ詰まりますが。
Google Nest Wifiの導入で満足していたのですがTwitterで以下の様な投稿が流れてきました。
ネットワーク情弱な私でもIPv6が早いは知っており、意識してプロバイダ(ぷらら)もルーター(Google Nest Wifi)もIPv6に対応しているのを確認して契約、購入しているので勝手にIPv6になっているとおもっていました。
ただちゃんど調べてみると現状はIPv4での通信を行っておりIPv6では通信してないです。
もっと調べてみると、ぷららはIPoE方式接続という方式でIPv6を実現しておりGoogle Nest WifiはIPoE方式接続に対応していないらしいです。
IPv6形式で接続するためにはIPoE形式に対応したルーターが必要とのこと。3万円近くかけてNestWifiと拡張ポイントを揃えた私としては受け入れがたい現実です。
しかし、ぷららのサイトをみるとIPv6形式に対応したルーター(Aterm WG1200Hs4)を無料で貸与してくれるとのことで無料なら試すしかないとレンタルしてみました。
そして、本日届いて設置してみたところ速度のムラはかなり大きいですが、
リビング 30〜40Mbps
仕事場 30〜40Mbps
とよくわからない速度が出るようになりました。当初の0.8Mbpsと比較するとかなりの高速化に対応することができました。
IPv6導入しましょう
デザイナーがコーディングできるべきかという議論がSNS上で白熱していますが個人的にはあまり興味がなく、コーディング経験の有無に関係なくWebに適したデザインデータを作ってもらえたらあとはフロントエンドの領分であると思っています。
とはいえ、Webに適したデザインデータをつくれないデザイナーが多いのでああいった議論が白熱するのではないかとも思っています。
Webに適していないデザインデータとはどういったものかというと、古くから言われているものではPhotoshopの『乗算』を使うとCSSで表現できないためダメというものがあったりします。
CSSでもmix-blend-modeという機能で対応できるけどブラウザの対応状況を鑑みるに、よほどの理由がないかぎりPhotoshopの『乗算』を使っていはいけないのが今の現実です。
参考:mix-blend-mode - CSS: カスケーディングスタイルシート | MDN
最近、落ち着いたかなと思うことにはアイコンなどのグラフィックパーツは極力ベクターデータで作成するというお作法。
ラスター画像でしか書き出しできないパーツはpngで出力することになり高精細度ディスプレイや高精細度モニタでぼやけて見えてしまうので結局最終的にsvgに差し替えることになり実装や確認をするプレーヤーのコストも考えると2度手間では済まないコストが掛かってしまうので、最初からsvgで出力できるベクターデータで作成するのが好ましいのが現在の多くの現場の現実です。(これもPhotshopのデザインデータにありがちな現象です。)
そして最近思っていることであまり議論されていないものとして、SP用のデザインを倍の解像度で作成する必要がないというもの、FigmaやAdobe XDで渡されるデザインデータにはないのですが、やはりPhotshopのデザインデータにありがちな傾向です。
9年ほど前に「スマートフォンサイトをデザインする上で知っておくべき10のTIPS」という記事を書いてそこそこバズったのですがそこで書いた倍の解像度でデザインするという慣習が残っているようですが、レスポンシブが普及してSVGが使えるようになった今、倍の解像度で作る必要が全く無いです。
そもそも、なぜ倍の解像度でデザインする必要があったかというと、iPhone4が発売された当初は先程述べたSVGが利用できない端末も多く高精細度ディスプレイ対応としてSVGを利用することができなかったのでpngで表現するしかなくデザイン作成のタイミングから高精細度ディスプレイ用に倍の解像度で作っておこうというものでした。
また当時のサイトは割とグラフィカルなサイトが多かったのですが、フラットデザインから始まる昨今のシンプルなデザイントレンド上においては写真以外でラスター画像が必要なケースというのが少なくなってきています。(当然例外もあります)
そして、レスポンシブが一般になった今、最終的にPCとSPで同一の画像データを扱うのが一般的なのでSPのデザインデータの解像度を倍にする理由がほとんどありません。高解像度の画像が必要なケースもありますがこれは高解像度の画像をデザインデータに埋め込めばよいのでわざわざベースの解像度を倍で作成する必要はないでしょう。
というわけでSP用のWebデザインを倍の解像度で作成するメリットはほとんど無くなってきています。
PC用とSP用のデザインデータを作成する際に各パーツのサイズを変えて配置するのは本当に無駄な作業なのでやめたほうがよいでしょう。
(もう、WebデザインはPhotshopで作らないほうが良いのではとも思っておりますが、これは私の領分ではないので深くは言及しません。
]]>React HooksではuseStateなどで定義された値が更新された場合にそのコンポーネントの再描画を行う。
]]> <![CDATA[以下のコンポーネントではaddボタンを押されるたびにuseStateで管理しているcountが更新されAppコンポーネントの再描画が行われconsole上に「render」の文字列が出力されます。
export default function App() {
const [count, changeCount] = useState(0);
console.log("render");
return (
<div className="App">
<input
type="button"
value="add"
onClick={() => {
console.log(count);
changeCount(count + 1);
}}
/>
</div>
);
}
これはReact上のエコシステムとして仕方ないですが表示に関係ない内部的なフラグの変化など再描画が望ましくないケースもあり、そういったケースではuseStateの代わりにuseRefを利用することで再描画されない(LifeCycleに影響を与えない)Stateを作成することができます。
useRefは以下のように利用して、参照する場合はcount.currentを、変更する場合はcount.currentを直接変更します。
export default function App() {
const count = useRef(0);
console.log("render");
return (
<div className="App">
<input
type="button"
value="add"
onClick={() => {
console.log(count.current);
count.current += 1;
}}
/>
</div>
);
}
]]>
TypeScriptを指定してオブジェクトのキーに自由な値を指定するためのインデックス シグネチャという機能が用意されています。
type Bar = {
[key: string]: number
}
// OK
const bar01: Bar = {
aaa: 1
}
// OK
const bar02: Bar = {
bbb: 1,
ccc: 2
}
このままではキー使える文字列に制約がありませんのでユニオンタイプなどを利用して制約を入れようとしてもうまくいきません
type Foo = 'aaa' | 'bbb'
type Bar = {
[key: Foo]: number
}
パラメーターの型にはユニオンタイプではなくマップされたオブジェクト型に変更して以下のようにすれば指定することができます。
type Foo = 'aaa' | 'bbb'
type Bar = {
[key in Foo]: number
}
// OK
const bar01: Bar = {
aaa: 1,
bbb: 2
}
// NG
const bar02: Bar = {
ccc: 1,
eee: 2
}
// NG
const bar03: Bar = {
aaa: 1,
bbb: 2,
ccc: 3
}
この場合はユニオンタイプで指定されたすべての値をキーに持つオブジェクトしか作れませんがオプショナルプロパティ(?のこと)を利用することで任意のキーを持つオブジェクトを指定することができます。
type Foo = 'aaa' | 'bbb'
type Bar = {
[key in Foo]?: number
}
// OK
const bar01: Bar = {
aaa: 1,
bbb: 2
}
// OK
const bar02: Bar = {
aaa: 1,
}
// NG
const bar03: Bar = {
aaa: 1,
bbb: 2,
ccc: 3
}
]]>
GatsbyJSで gatsby buildコマンド実行時に「WebpackError: ReferenceError: IDBIndex is not defined」とエラーが出たのでその対応方法のメモ。
]]> <![CDATA[具体的にはFirebaseを読み込んでいる以下のようなコードでエラーが発生しました。
import firebase from "firebase"
const config = {
apiKey: "xxx",
authDomain: "xxx",
databaseURL: "xxx",
projectId: "xxx",
storageBucket: "xxx",
messagingSenderId: "xxx",
appId: "xxx",
}
firebase.initializeApp(config)
export const functions = firebase.functions()
importしているサードパティーのライブラリがwindowオブジェクトなどにアクセスしている場合に発生するらしいです。
これは以下のようにrequireを利用してwindowオブジェクトがある場合にのみ読み込むようにすることで対応することができます。
let _functions
if (typeof window !== "undefined") {
const firebase = require("firebase")
const config = {
apiKey: "xxx",
authDomain: "xxx",
databaseURL: "xxx",
projectId: "xxx",
storageBucket: "xxx",
messagingSenderId: "xxx",
appId: "xxx",
}
firebase.initializeApp(config)
_functions = firebase.functions()
}
export const functions = _functions
]]>
Reactでは作成したアプリケーションはwebpackにより全てのコードが1つのJavaScriptファイルにバンドルされます。
]]> <![CDATA[小さなアプリケーションならよいですが大きなアプリケーションですと初期表示に読み込むJSファイルが増加していく問題を抱えています。
React.lazy は JavaScriptファイルを分割して利用しているコンポーネントが呼び出されたタイミングでロードするための機能です。
次のように記述すると遅延読み込み用のコンポーネントが作成されます。
const SomeComponent = React.lazy(() => import('./SomeComponent'));
React.lazyはReact.Suspense と一緒に利用して、このコンポーネントが呼び出されたタイミングで通信を行いコンポーネントファイルを取得します。
const MyComponent = () => {
return (
<React.Suspense fallback={null}>
<div>
<SomeComponent />
</div>
</React.Suspense>
);
}
React.Suspenseのfallback属性にはファイルの読み込み時に表示したいコンポーネントを指定することができ、表示が不要ならnullを指定します。
多くのケースではReact Routerなどと合わせて、ページごとに読み込みを行うと不要なコンポーネントの情報を読み込まずにページの描画が可能になんるでしょう。
React Routerと利用する場合にはRouterコンポーネントとSwitchコンポーネントの間にSuspenseを記述して利用を行います。
import React, {lazy,Suspense} from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const HomeComponent = () => <div>home</div>
const Page1Component = lazy(() => import('./Page1Component'));
const Page2Component = lazy(() => import('./Page2Component'));
const LoadingComponent = () => <div>Loading...</div>
function App() {
return (
<Router>
<Suspense fallback={LoadingComponent}>
<Switch>
<Route path="/" exact component={HomeComponent} />
<Route path="/page1" component={Page1Component}/>
<Route path="/page1" component={Page2Component}/>
</Switch>
</Suspense>
</Router>
);
}
export default App;
ReactのSyntheticEventとDebounce
ReactのContext APIを利用してコンポーネント間の情報を共有
WordPressのRest APIのユーザー情報に項目を追加する必要があったのでその際のメモ書き。
]]> <![CDATA[具体的には以下のようなスタッフ一覧のページを作成したかったですがデフォルトでは役職の表記や各種SNSアカウントなどの項目に対応できなたかったのでカスタマイズをしました。
Rest APIに限らずWordPressでユーザー情報に項目を追加する場合は以下の内容をfunctions.phpに追加します。
function my_user_meta($wb) {
$wb['position'] = '役職';
$wb['twitter'] = 'twitter';
$wb['facebook'] = 'facebook';
$wb['github'] = 'github';
$wb['instagram'] = 'instagram';
return $wb;
}
add_filter('user_contactmethods', 'my_user_meta', 10, 1);
代入している値は管理画面上のラベルに利用されます。
通常のテンプレートで上記の内容を取得する場合はget_the_author_meta関数を利用して以下のように記述します。
foreach($users as $user):
$uid = $user->ID;
print get_the_author_meta('twitter',$uid);
endforeach;
ただしRest APIではこれらの情報が付与されませんのでカスタマイズする必要があります。
Rest APIではrest_api_initアクション時に必要んあ項目を付与することが可能です。
先ほど追加した項目を
function adding_user_meta_rest() {
register_rest_field(
'user',
'user_meta',
array(
'get_callback' => 'facebook_get_user_field',
'update_callback' => null,
'schema' => null,
)
);
}
function facebook_get_user_field( $user, $field_name, $request ) {
return array(
'position' => get_the_author_meta('position',$user['id']),
'twitter' => get_the_author_meta('twitter',$user['id']),
'facebook' => get_the_author_meta('facebook',$user['id']),
'github' => get_the_author_meta('github',$user['id']),
'instagram' => get_the_author_meta('instagram',$user['id']),
);
}
add_action( 'rest_api_init', 'adding_user_meta_rest' );
こうすることで各ユーザーのレスポンスにuser_metaが追加され、ユーザー管理画面で入力した内容が反映され以下のようなJSONが出力されるようになります。
{
"name": "西畑 一馬",
...
"user_meta": {
"position": "代表取締役 / フロントエンドエンジニア",
"twitter": "KazumaNishihata",
"facebook": "kazuma.nishihata",
"github": "KazumaNishihata",
"instagram": "kazumanishihata"
}
}
デフォルト項目である姓名などもRest APIに出力されません。
正確にはContextをeditにすれば出力されるのですが今回は認証なしに取得したかったのでContextをeditがviewのまま出力したいという意味です。
この場合は先程記載したfacebook_get_user_field内の項目をカスタマイズすることで対応が可能です。
具体的にはget_user_metaで姓(first_name)を取得して出力しています。
function facebook_get_user_field( $user, $field_name, $request ) {
return array(
'position' => get_the_author_meta('position',$user['id']),
'twitter' => get_the_author_meta('twitter',$user['id']),
'facebook' => get_the_author_meta('facebook',$user['id']),
'github' => get_the_author_meta('github',$user['id']),
'instagram' => get_the_author_meta('instagram',$user['id']),
'retirement' => get_the_author_meta('retirement',$user['id']),
'first_name' => get_user_meta( $user['id'], 'first_name', true )
);
}
Rest APIは柔軟にカスタマイズできるのですが慣れていないとドキュメント等を読み込まないとわからないことが多いので
register_rest_routeなどで独自のエンドポイントを作るのが早いかもと思ってしまいました。
WordPressのRest API でユーザー一覧を取得した場合はデフォルトの設定では記事の投稿があるユーザーしか表示されません。
]]> <![CDATA[非投稿者も Rest APIに表示したい場合はfunctions.phpに以下のfilterを追加します。
function prefix_remove_has_published_posts_from_wp_api_user_query( $prepared_args, $request ) {
unset( $prepared_args['has_published_posts'] );
return $prepared_args;
}
add_filter( 'rest_user_query', 'prefix_remove_has_published_posts_from_wp_api_user_query', 10, 2 );
参考: WP Users Endpoint doesn't return all users · Issue #2300 · WP-API/WP-API
]]>Reactではevent設定時にSyntheticEvent(合成イベント)が生成されネイティブのイベントと設定した関数の中継ぎをします。
]]> <![CDATA[例えば下記のようにinput要素のonChangeイベントにhandleChange関数を設定した場合直接ネイティブのChangeイベントがhandleChange関数を実行するのではなく間に生成されているSyntheticEventを仲介してhandleChange関数を実行がされます。
通常は気にすることはないのですが、SyntheticEventはパフォーマンスの観点からイベントオブジェクトを保持しておらず非同期処理の場合には注意が必要です。
下記のようにhandleChange関数内で非同期処理をおこないイベントオブジェクトにアクセスした場合にエラーになってしまいます。
これを防ぐにはハンドル関数内で e.persist(); を設定することでイベントオブジェクトが破棄されずに利用できるようになります。(e.currentTargetなどの一部のプロパティはe.persist();を記述しても破棄されてしまいます。)
この性質で厄介なのがDebounceによるハンドリングが上手に行かないこと。Debounce自体が内部的に非同期処理で制御をしているため制御後にe.persist(); を指定しても意味がなく制御前に指定をしなくてはいけない。
そのためhandleChange関数内でe.persist(); を実行してdebouncedHandleChange関数内で実際の処理を行うという順番を変えることで対応が可能です。
ただこのままではe.persist();が何度も実行され、せっかくDebounceしている意味が薄れる。その場合はイベントオブジェクではなくイベントオブジェクトのターゲットオブジェクトをdebouncedHandleChange関数に引き渡すことでも対応ができる。
合成イベント (SyntheticEvent) – React
React SyntheticEvent reuse – Trabe – Medium
[React] Debounce SyntheticEvent | DailyEngineering