to-R tag:blog.webcreativepark.net,2013-08-10://2 2024-05-22T02:17:04Z Movable Type 5.2.7 pyenvを利用してvenvで作成した仮想環境内のPythonバージョン tag:blog.webcreativepark.net,2024://2.1746 2024-05-22T01:57:37Z 2024-05-22T02:17:04Z venvで作成したPythonの仮想環境内にインストールされているバージョンを変... 西畑一馬 <![CDATA[

venvで作成したPythonの仮想環境内にインストールされているバージョンを変更してみました。

]]> <![CDATA[

pyenvのインストール

Pythonのバージョン管理にはpyenvを利用します。

brewを使ってインストール

brew install pyenv

.zshrcに以下の設定を追加します。

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

変更後にsourceコマンドで変更した内容を反映します。

source ~/.zshrc

Pythonバージョン変更

以下のコマンドでインストール可能なバージョンを調べることができます。

pyenv install --list

今回はPythonの3.9.7をインストールしたかったので以下のコマンドでインストールしてローカル環境に反映します。

pyenv install 3.9.7
pyenv local 3.9.7
python --version # Python 3.9.7

venvの仮想環境内に反映

ローカル環境の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で任意のメディアクエリを指定する tag:blog.webcreativepark.net,2023://2.1745 2023-11-01T15:40:08Z 2023-11-02T01:29:32Z Tailwind CSSでは簡単にメディアクエリの指定が可能です。... 西畑一馬 <![CDATA[

Tailwind CSS

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 tag:blog.webcreativepark.net,2023://2.1744 2023-10-19T06:32:45Z 2023-10-19T07:11:43Z Tailwind CSSでdisabled属性がついた要素に対してスタイルを当... 西畑一馬 <![CDATA[

Tailwind CSS

Tailwind CSSでdisabled属性がついた要素に対してスタイルを当てたい場合には、disabled擬似クラスを利用して指定を行います。

]]> <![CDATA[

以下のようにdisabled:をつけることでdisabled属性がついた場合のスタイルが定義できます。

<input type="text" disabled class="disabled:bg-gray-400" />

disabled属性がついた要素の兄弟要素

peer

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

arbitrary variantsを利用するとCSSセレクタを利用してより柔軟に指定ができます。

[:disabled+&]:と指定をすると隣接セレクタを利用してdisabled属性が指定された要素の次に登場する要素の場合という条件の場合のスタイルが定義できます。

<input type="text" disabled class="peer" />
<label class="[:disabled+&]:bg-gray-400">text</label>
]]>
Vue.jsでコンポーネントの要素をpropsによって切り替える tag:blog.webcreativepark.net,2023://2.1743 2023-09-15T02:18:36Z 2023-09-15T02:29:15Z Vue.jsでボタンコンポーネントなどを作成する場合、ユースケースに応じてHT... 西畑一馬 <![CDATA[

Vue.png

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の設定をする tag:blog.webcreativepark.net,2023://2.1742 2023-09-13T04:51:05Z 2023-09-13T06:09:41Z 案件でNuxt3のESLint設定をすることがあったので手順をメモしておきます... 西畑一馬 <![CDATA[

Nuxt3

案件でNuxt3のESLint設定をすることがあったので手順をメモしておきます。

]]> <![CDATA[

Nuxt3はNo PrettierポリシーなのでPrettierは推奨されておらずフォーマッティングも含めてESLintを利用することが推奨されています。

Nuxt3のインストール

まずは以下のコマンドでNuxt3のインストール。マネージメントツールはnpmにします。

npx nuxi@latest init my-app

インストールしたら起動しておきます。

cd my-app
npm run dev

パッケージのインストール

以下のパッケージをインストールします。

  • eslint
  • eslint-plugin-nuxt
  • @typescript-eslint/eslint-plugin
  • @typescript-eslint/parser
  • @nuxtjs/eslint-config-typescript
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が実行されるか確認しておきます。

VS Code用の設定を追加

以下の内容を `.vscode/settings.json` に記述しておきます。

{
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.fixAll": false,
        "source.fixAll.eslint": true
    }
}

これで自動保存時にESLintの内容でフォーマッティングが行われます。

]]>
Next.jsのAPI RoutesでAuth0の認可を実装する tag:blog.webcreativepark.net,2023://2.1741 2023-03-24T02:18:12Z 2023-03-24T03:07:58Z Auth0は認証・認可の仕組みを提供してくれるIDaaS(Identity a... 西畑一馬 <![CDATA[

Next.js+Auth0-1.png

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をTypeScriptの型推論で型情報を取りだす tag:blog.webcreativepark.net,2023://2.1740 2023-02-20T09:31:55Z 2023-03-24T02:10:11Z AstroでgetStaticPathsの返り値としてpropsが指定できます... 西畑一馬 <![CDATA[

AstroでgetStaticPathsで指定したpropsを型推論で取りだす

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`で返り値の型を取得してPromise内の配列内のオブジェクトのpropsの型を抽出しています。

export type Props = (ReturnType extends Promise
  ? T
  : never)[0]["props"];

可能ではあるものの、ややこしいだけなので別途型情報を明記したほうがよさそうです。

追記:Astro 2.1でもっと手軽に対応できるInferGetStaticPropsTypeが追加されました。

]]>
VSCodeでSveltKitのPrettierのフォーマッティングをエディタ保存時に適用する tag:blog.webcreativepark.net,2023://2.1739 2023-02-14T08:53:42Z 2023-02-14T09:03:03Z SveltKitでVSCodeのPrettierの自動フォーマッティングを有効... 西畑一馬 <![CDATA[

SveltKitでVSCodeの自動フォーマッティングを有効にする

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バージョン tag:blog.webcreativepark.net,2023://2.1738 2023-01-25T10:33:21Z 2023-01-25T11:48:50Z Cloudflare PagesにSvelteKitで作成したアプリケーション... 西畑一馬 <![CDATA[

Cloudflare PagesにSvelteKitをデプロイする際のNodeバージョン

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」を指定することでビルドとデプロイが正常に動作します。

Cloudflare PagesでNODE_VERSIONの指定を行っている画面のキャプチャ

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の型を定義する方法 tag:blog.webcreativepark.net,2022://2.1736 2022-01-21T05:03:14Z 2022-01-21T05:39:47Z Next.js + TypeScript においてgetStaticProps... 西畑一馬 <![CDATA[

Next+TypeScript.png

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も用意されているので状況によって使い分けると良いでしょう。

]]>
株式会社トゥーアールの2021年を振り返る tag:blog.webcreativepark.net,2021://2.1735 2021-12-28T04:42:38Z 2021-12-28T05:37:33Z 今年の最終営業日なので一年を振り返ってみたいと思います。... 西畑一馬 <![CDATA[

今年の最終営業日なので一年を振り返ってみたいと思います。

]]> <![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年ありがとうございました。
来年はオフラインのイベントなどが開かれるぐらい世の中の状況がよくなっていると良いなと思いつつ、引き続き頑張っていきたいと思います。

過去の振り返り

2020年の振り返り(Facebook)
2018年の振り返り

]]>
Jestでマークダウンを読み込みテストをする tag:blog.webcreativepark.net,2021://2.1734 2021-12-17T07:08:43Z 2021-12-17T07:25:21Z 業務でマークダウンパーサーを作成していてJestでmockデータのマークダウン... 西畑一馬 <![CDATA[

Jest

業務でマークダウンパーサーを作成していて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を利用しているコンポーネントのテストを行う tag:blog.webcreativepark.net,2021://2.1733 2021-12-02T03:19:32Z 2021-12-02T03:32:03Z next/routerを利用しているコンポーネントを単体でテストする場合にはN... 西畑一馬 <![CDATA[

Next

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()
  });
});
]]>
Github Actionsのキャッシュ機能を利用してNext.jsのデプロイを高速化 tag:blog.webcreativepark.net,2021://2.1732 2021-11-23T12:53:30Z 2021-11-23T13:16:18Z Next.jsのアプリケーションのデプロイにGithub Actionsを利用... 西畑一馬 <![CDATA[

Github Next

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:

https://nextjs.org/telemetry

これによると .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を使っていましたがまだ知らない便利機能がありそうなので改めて確認したいと思いました。

関連エントリー

Nuxt.js / Next.jsで作成したサイトをGitHub ActionsでAWS S3にデプロイする

]]>
Next.js のStorybookでSassを利用する(webpack5対応) tag:blog.webcreativepark.net,2021://2.1731 2021-11-19T02:20:33Z 2021-11-19T03:37:34Z Next.js で Storybookを利用しようとした際にsass-load... 西畑一馬 <![CDATA[

Storybook + Next.js

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 />;

これで表示は確認できるはずです。

Next.jsにSassにインストール

以下のコマンドでSassをインストール

yarn add sass -D

styles/globals.cssstyles/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でSassを利用

.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

Storybookでwebpack5を利用

上記の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ストレージを割り当てる tag:blog.webcreativepark.net,2021://2.1730 2021-11-03T01:53:01Z 2021-11-03T06:43:35Z AWS CloudFrontを利用すれば特定ディレクトリを別のS3ストレージを割... 西畑一馬 <![CDATA[

AWS CloudFrontを利用すれば特定ディレクトリを別のS3ストレージを割り当てることができます。

]]> <![CDATA[

以下のような感じでexample.comにアクセスした場合はメインS3にアクセスしますがsub-dirディレクトリ以下にアクセスした場合はサブS3のS3ストレージのファイルを表示します。

https://example.com/* [メインS3]
https://example.com/sub-dir/* [サブS3]


S3にファイルを配置

まずはサブS3にファイルを配置しておきます。配置する際にはルートパスから指定する必要があります。
つまり、sub-dir/を作成してその内側にhttps://example.com/sub-dir/*以下のファイルの指定を行います。

CloudFrontの設定変更

CloudFrontではメインS3にアクセスを設定しているディストリビューションを選択します。

オリジン

オリジンタブに遷移してから「オリジンを作成」を押下します。

オリジンドメインにサブS3のを選択
S3 バケットアクセスにはサブS3にアクセスできるOAIを指定

ビヘイビア

ビヘイビアタブに遷移してから「ビヘイビアを作成」を押下します。

パスパターン sub-dir/*
オリジンとオリジングループ S3のオリジン

こちらを設定して「ビヘイビアを作成」を押下します。

そうするとhttps://example.com/sub-dir/* にアクセスした際にサブS3で設定したファイルの内容が表示されるようになります。

]]>
Nuxt.js / Next.jsで作成したサイトをGitHub ActionsでAWS S3にデプロイする tag:blog.webcreativepark.net,2021://2.1729 2021-11-02T07:06:21Z 2021-11-23T13:17:01Z GitHub Actionsを利用するとNuxt.jsやNext.jsなどで作成... 西畑一馬 <![CDATA[

GitHub Actionsを利用するとNuxt.jsやNext.jsなどで作成したWebアプリケーションを簡単にAWS S3にデプロイすることができます。

]]> <![CDATA[

AWS S3にアップできるIAMユーザーを作成する

まずは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 ActionsにAWS S3にデプロイ設定をする

まずは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の場合

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 }}

関連エントリー

Github Actionsのキャッシュ機能を利用してNext.jsのデプロイを高速化

]]>
AWS CloudFront FunctionsでBasic認証を設定する tag:blog.webcreativepark.net,2021://2.1728 2021-11-01T05:08:11Z 2021-11-01T05:58:56Z これまでAWSのS3にアップした静的ファイルにBasic認証を設定するには Cl... 西畑一馬 <![CDATA[

これまで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でFunctionsを設定

CloudFrontのディストリビューションからBasic認証を設定したいディストリビューションを選択して、ビヘイビアからオリジンを選択し編集します。

関数の関連付けのビューワーリクエストに

関数タイプ : CloudFront Functions
関数 ARN/名前 :作成していた Basic認証用の関数

を指定することでCloudFront FunctionsでBasic認証が可能になります。

]]>
AWS SAM のtemplate.yamlをdeploy環境ごとに切り替える tag:blog.webcreativepark.net,2021://2.1727 2021-10-28T07:20:51Z 2021-10-28T07:38:00Z develop や staging 、 productionなどの複数の環境で ... 西畑一馬 <![CDATA[

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
]]>
AWS Lambda(Node.js)でJimpを使って画像のバリデーションを行う tag:blog.webcreativepark.net,2021://2.1726 2021-10-27T12:57:07Z 2021-10-27T13:19:29Z Node.jsでバリデーションを行うには sharpがよく利用されますが、lib... 西畑一馬 <![CDATA[

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で画像のサイズを取得することができますのでサイズの判定に利用します。

]]>
IE11が終わる日 tag:blog.webcreativepark.net,2021://2.1725 2021-05-24T03:06:42Z 2021-05-25T06:58:47Z Microsoftが「Internet Explorer は Microsoft... 西畑一馬 <![CDATA[

Microsoftが「Internet Explorer は Microsoft Edge へ – Windows 10 の Internet Explorer 11 デスクトップアプリは 2022 年 6 月 15 日にサポート終了 - Windows Blog for Japan」というリリースを行いました。

]]> <![CDATA[

よくよく読み進めていくと、これまでの「セキュリティアップデートを行なわない」といったサポート中止とは異なるすごく強制力のある発表で事実上来年以降に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 モードで表示した状態です。

画像:トゥーアールのサイトをIE11モードで表示

ただ、Internet Explorer モードはFlagをonにするというだいぶ上級者向けの設定を行わないと使うことができません。

参考:Microsoft Edge の Internet Explorer モード - Office サポート

上級者向けとはいえ閲覧が可能なのでお客さんがどうしても表示したいと主張される場合には対応することもあるのかなと考えていたのですが、なんとInternet Explorer モードでは開発者ツールが使えないようです。

以下、Microsoft Edge の Internet Explorer モードで開発者ツールを表示した状態です。

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

]]>
自宅ネットワーク高速化の道のり tag:blog.webcreativepark.net,2020://2.1724 2020-05-07T14:56:36Z 2020-05-08T01:56:37Z 昨年12月に引っ越してから自宅のネットワークがすごく貧弱だなと不満を持っていたと... 西畑一馬 <![CDATA[

昨年12月に引っ越してから自宅のネットワークがすごく貧弱だなと不満を持っていたところに、緊急事態宣言を受けて強制的にテレワークの実施を余儀なくされてしまいました。

]]> <![CDATA[

研修等のお仕事もフルオンラインに切り替わり、私の自宅回線が貧弱だと色々と迷惑をかけてしまうので自宅ネットワーク高速化を色々とトライしました。

結論はIPv6化したというお話ですがそこに至るまでの足取りを記録しておきます。

当初のネットワーク環境

光回線を引いているのですがマンションがVDSL方式のためベストエフォートで100Mbpsとそれほどの速度が出ません。ここを変えるには大掛かりな工事が必要でもっと小手先の対応でどうにかしたいというのが本エントリーの趣旨です。

プロバイダーは申込みの後の確認電話で勧められたぷららを契約してました。本記事の最後ではぷららを契約したことがプラスに繋がります。

プロバイダーから貸与されたモデムにルーターとしてAirMacを繋げてWifiを飛ばしているが当初の環境でした。

モデム -> AirMac -> PC

通信速度にムラがありますが19時頃に下りで

リビング 8Mbps
仕事場 0.8Mbps

となりWifiルーターを設置しているリビングから離れている仕事場では通常のブラウジングも苦しい状態になることもありました。

改善案1:Google Nest Wifi導入

流石に仕事にならないので改善案としてGoogle Nest Wifiを導入しました。Google Nest Wifiは拡張ポイントと合わせて利用することでWifiの接続快適距離を伸ばしてくれるメッシュWifiに対応しており、リビングから仕事場までの減速を軽減してくれることを期待していました。

通信速度にムラがありますが19時頃に下りで

リビング 8Mbps
仕事場 2Mbps

ぐらいの誤差で収まるようになりました。下り2MbpsあればYouTube見ながらでもお仕事できますね。ちょいちょ詰まりますが。

改善案2:IPv6導入

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導入しましょう

]]>
もう、SP用のWebデザインを倍の解像度で作るのをやめよう tag:blog.webcreativepark.net,2020://2.1722 2020-01-16T03:16:29Z 2020-01-18T00:11:10Z デザイナーがコーディングできるべきかという議論がSNS上で白熱していますが個人的... 西畑一馬 <![CDATA[

デザイナーがコーディングできるべきかという議論がSNS上で白熱していますが個人的にはあまり興味がなく、コーディング経験の有無に関係なくWebに適したデザインデータを作ってもらえたらあとはフロントエンドの領分であると思っています。

]]> <![CDATA[

とはいえ、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のuseRefを再描画がかからないState管理として利用する tag:blog.webcreativepark.net,2020://2.1721 2020-01-09T11:11:28Z 2020-01-09T12:01:44Z React HooksではuseStateなどで定義された値が更新された場合にそ... 西畑一馬 <![CDATA[

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のインデックス シグネチャでオブジェクトのキーの型を指定する tag:blog.webcreativepark.net,2020://2.1720 2020-01-06T02:57:28Z 2020-01-09T03:15:54Z TypeScriptを指定してオブジェクトのキーに自由な値を指定するためのインデ... 西畑一馬 <![CDATA[

TypeScriptを指定してオブジェクトのキーに自由な値を指定するためのインデックス シグネチャという機能が用意されています。

]]> <![CDATA[
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で "WebpackError: ReferenceError: IDBIndex is not defined"とエラーが出た場合の対処方法 tag:blog.webcreativepark.net,2019://2.1719 2019-05-26T15:20:36Z 2019-05-26T15:33:10Z GatsbyJSで gatsby buildコマンド実行時に「WebpackEr... 西畑一馬 <![CDATA[

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

参考: Debugging HTML Builds | GatsbyJS

]]>
React.lazyを利用してJavaScriptファイルを分割ロード tag:blog.webcreativepark.net,2019://2.1717 2019-05-21T11:06:27Z 2019-05-22T06:50:50Z Reactでは作成したアプリケーションはwebpackにより全てのコードが1つの... 西畑一馬 <![CDATA[

Reactでは作成したアプリケーションはwebpackにより全てのコードが1つのJavaScriptファイルにバンドルされます。

]]> <![CDATA[

小さなアプリケーションならよいですが大きなアプリケーションですと初期表示に読み込むJSファイルが増加していく問題を抱えています。

React.lazy は JavaScriptファイルを分割して利用しているコンポーネントが呼び出されたタイミングでロードするための機能です。

React.lazyの利用

次のように記述すると遅延読み込み用のコンポーネントが作成されます。

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との利用方法

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のユーザー情報に項目を追加する tag:blog.webcreativepark.net,2019://2.1715 2019-05-06T12:25:54Z 2019-05-06T13:05:50Z WordPressのRest APIのユーザー情報に項目を追加する必要があったの... 西畑一馬 <![CDATA[

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では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 のUser一覧で非投稿者も表示する

]]>
WordPressのRest API のUser一覧で非投稿者も表示する tag:blog.webcreativepark.net,2019://2.1714 2019-05-06T04:06:33Z 2019-05-06T04:15:03Z WordPressのRest API でユーザー一覧を取得した場合はデフォルトの... 西畑一馬 <![CDATA[

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のSyntheticEventとDebounce tag:blog.webcreativepark.net,2019://2.1713 2019-04-03T11:18:09Z 2021-04-20T06:29:02Z Reactではevent設定時にSyntheticEvent(合成イベント)が生... 西畑一馬 <![CDATA[

Reactではevent設定時にSyntheticEvent(合成イベント)が生成されネイティブのイベントと設定した関数の中継ぎをします。

]]> <![CDATA[

ReactのSyntheticEvent

例えば下記のようにinput要素のonChangeイベントにhandleChange関数を設定した場合直接ネイティブのChangeイベントがhandleChange関数を実行するのではなく間に生成されているSyntheticEventを仲介してhandleChange関数を実行がされます。

通常は気にすることはないのですが、SyntheticEventはパフォーマンスの観点からイベントオブジェクトを保持しておらず非同期処理の場合には注意が必要です。

下記のようにhandleChange関数内で非同期処理をおこないイベントオブジェクトにアクセスした場合にエラーになってしまいます。

これを防ぐにはハンドル関数内で e.persist(); を設定することでイベントオブジェクトが破棄されずに利用できるようになります。(e.currentTargetなどの一部のプロパティはe.persist();を記述しても破棄されてしまいます。)

SyntheticEventとDebounce

この性質で厄介なのがDebounceによるハンドリングが上手に行かないこと。Debounce自体が内部的に非同期処理で制御をしているため制御後にe.persist(); を指定しても意味がなく制御前に指定をしなくてはいけない。

そのためhandleChange関数内でe.persist(); を実行してdebouncedHandleChange関数内で実際の処理を行うという順番を変えることで対応が可能です。

ただこのままではe.persist();が何度も実行され、せっかくDebounceしている意味が薄れる。その場合はイベントオブジェクではなくイベントオブジェクトのターゲットオブジェクトをdebouncedHandleChange関数に引き渡すことでも対応ができる。

参考

合成イベント (SyntheticEvent) – React
React SyntheticEvent reuse – Trabe – Medium
[React] Debounce SyntheticEvent | DailyEngineering

]]>