「swiper/react」をちょっとだけ使いやすくする
「swiper/react」をさわった時に少し触りやすいようにしたので、その時のコードです。
結果
使っているバージョン
- "react": "^18.2.0",
- "swiper": "^9.2.0",
コンポーネント側
import type { FC, ReactNode } from 'react'; import { Children } from 'react'; import 'swiper/css'; import { Swiper, SwiperSlide } from 'swiper/react'; import styles from './index.module.scss'; export type SliderProps = { children: ReactNode; slidesPerView: number; spaceBetween: number; loop?: boolean; visible?: 'visible'; }; export const Slider: FC<SliderProps> = ({ children, slidesPerView, spaceBetween, loop, visible }) => { return ( <div className={styles['slider']} data-visible={visible}> <Swiper slidesPerView={slidesPerView} spaceBetween={spaceBetween} speed={500} className={styles['slider__swiper']} loop={loop} > {Children.map(children, (slide, index) => ( <SwiperSlide key={index}>{slide}</SwiperSlide> ))} </Swiper> </div> ); };
$root :'.slider'; .slider { // 何かのスタイル } .slider__swiper { #{$root}[data-modifier='visible'] & { overflow: visible !important; } }
ページ側
<Slider slidesPerView={3} spaceBetween={96}> <p style={{ border: '1px solid #000' }}>テキスト1</p> <p style={{ border: '1px solid #000' }}>テキスト2</p> <p style={{ border: '1px solid #000' }}>テキスト3</p> <p style={{ border: '1px solid #000' }}>テキスト4</p> <p style={{ border: '1px solid #000' }}>テキスト5</p> </Slider>
TypeScirptで配列・オブジェクトの値をEnums化して型を安全にする
以下の記事を参考にTypeScriptでの配列の値を記載する際にEnums化を行ったため、その時のメモになります。
行いたかったこと
react-hook-formで以下のようなことをする際にテキストだと型がうまく反応しなかったため、反応するようにしたい
const inputNameList = { group: ['input1', 'input2'] }
<!-- 以下の「input1」の部分を「inputNameList」以外の名前の場合にエラーにしたい --> <input {...register("input1")} /> <!-- OKパターン(inputNameListの中にinput1はあるので、OK) --> <input {...register("input1")} /> <!-- NGパターン(inputNameListの中にinput3はないので、NG) --> <input {...register("input3")} />
今回実装したコード
オブジェクトのkey部分を使用したい場合
一覧の定義・使い方
// 定義の内容 const inputNameList = { group1: ['data1', 'data2', 'data3'], }; // 使い方 /** * inputNameList.group1 // 'group1'のテキスト */
関数の定義
/** * Function to create an enum based on an array of names * @template { string[] } T Type of list of names in object first level * @template { string } S List name type of object first level names * @param { T } keys List of names in object first level */ export function convertArrayToEnum<T extends unknown[], S extends string>(keys: T) { const result = {} as { [key in S]: S }; keys.forEach((k: S) => { result[k] = k; }); return result; }
実際の使い方
// 一覧の定義 const inputNameList = { group1: ['data1', 'data2', 'data3'], group2: ['test1'], }; // オブジェクトのkey部分を配列にて、取得 // 結果: ['group1', 'group2'] const objectList = Object.keys(inputNameList) as (keyof typeof inputNameList)[]; // 各種型の定義 // 結果: ('group1' | 'group2')[] type InputGroupKeys = typeof validateGroupKeys; // 結果: 'group1' | 'group2' type InputGroupKeysNumber = typeof validateGroupKeys[number] // enum化 /** 結果 * const enum = { * group1: "group1"; * group2: "group1"; * } */ const enum = convertArrayToEnum<InputGroupKeys, InputGroupKeysNumber>(objectList); // 実際に使用 enum.group1 //「group1」のテキスト
オブジェクトの配列部分を取得したい場合
一覧の定義・使い方
// 定義の内容 const inputNameList = { group1: ['data1', 'data2', 'data3'], }; // 使い方 /** * group1.data1 // 'data1'のテキスト * group1.data2 // 'data2'のテキスト * group1.data3 // 'data3'のテキスト */
関数の定義
/** * Function to create an enum based on the name of an array in a property * @template { string[] } T Type of list of names in object first level * @template { string } S List name type of object first level names * @template { Object<string> } K List name type of object first level names * @param { Object<S, string[]> } group group list * @param { T } keys List of names in object first level */ export function convertObjectArrayToEnum<T extends unknown[], S extends string, K extends object>( group: { [key in S]: string[] }, keys: T ) { type InputLabel = { [key in keyof K]: string }; const result = {} as { [key in S]: InputLabel }; keys.forEach((k: S) => { result[k] = {} as InputLabel; group[k].forEach((value: string) => { result[k][value] = value; }); }); return result; }
実際の使い方
// 一覧の定義 const inputNameList = { group1: ['data1', 'data2', 'data3'], group2: ['test1'], }; // 一覧の型の定義 export type State = { data1: string; data2: string; data3: number; test1: string; }; // オブジェクトのkey部分を配列にて、取得 // 結果: ['group1', 'group2'] const objectList = Object.keys(inputNameList) as (keyof typeof inputNameList)[]; // 各種型の定義 // 結果: ('group1' | 'group2')[] type InputGroupKeys = typeof validateGroupKeys; // 結果: 'group1' | 'group2' type InputGroupKeysNumber = typeof validateGroupKeys[number] // enum化 const enum = convertObjectArrayToEnum< InputGroupKeys, InputGroupKeysNumber, State >(inputNameList, objectList); // 実際に使用 const {group1} = enum // group1の中にdata1はあるので、OK // <input {...register('data1')} />と結果は同じ <input {...register(group1.data1)} /> // group1の中にdata100はないので、NG <input {...register(group1.data100)} />
結論
今回作成した関数についてはこちらになります。
/** * Function to create an enum based on an array of names * @template { string[] } T Type of list of names in object first level * @template { string } S List name type of object first level names * @param { T } keys List of names in object first level */ export function convertArrayToEnum<T extends unknown[], S extends string>(keys: T) { const result = {} as { [key in S]: S }; keys.forEach((k: S) => { result[k] = k; }); return result; } /** * Function to create an enum based on the name of an array in a property * @template { string[] } T Type of list of names in object first level * @template { string } S List name type of object first level names * @template { Object<string> } K List name type of object first level names * @param { Object<S, string[]> } group group list * @param { T } keys List of names in object first level */ export function convertObjectArrayToEnum<T extends unknown[], S extends string, K extends object>( group: { [key in S]: string[] }, keys: T ) { type InputLabel = { [key in keyof K]: string }; const result = {} as { [key in S]: InputLabel }; keys.forEach((k: S) => { result[k] = {} as InputLabel; group[k].forEach((value: string) => { result[k][value] = value; }); }); return result; }
Nuxt2 から Nuxt3にアップデートを行う
個人で開発しているものをNuxt2からNuxt3にアップデートしたため、その時に対応したこと等を記載していきます。
Nuxt2からNuxt3にアップデートしようと思った理由について
Vue2が2023年末にサポートが切れてしまうためVue3にアップデートさせる必要があった
vue2が2023年12月31日にサポート終了 (EOL) でNuxt2がVue2のみのサポートのため、Vue3サポートがされているNuxt3にアップデートする必要があったため。
Vue2のサポートについて
Nuxt2, Nuxt Bridge, Nuxt3で対応されているものについて
Nuxtの3.0がアップデートされた
2022/11/17にNuxt3.0にアップデートされたため、個人開発レベルのものであればNuxt3を使うことができると思ったため。
It's time for 3.0 ✨https://t.co/PXTNWKmkbq pic.twitter.com/Hz9FBGoavK
— Nuxt (@nuxt_js) 2022年11月16日
一部パッケージでVue3にしかサポートされていない
パッケージの最新版を使用する場合、Vue3にしか対応されていない & 新たにインストールするのに古いバージョンを使う理由もなかったため。
一例としては以下パッケージになります。
Nuxt3の機能について
変更前と変更後の各種バージョン
before
{ "@nuxt/components": "^2.2.1", "@nuxtjs/composition-api": "^0.32.0", "nuxt": "^2.15.8", "vue": "^2.6.14", }
after
{ "nuxt": "3.0.0", }
Nuxt3へのアップグレード手順について
変更した箇所について
npm scripts
の変更- configファイルの編集
- VueファイルをNuxt3にあった書き方に変更
process.browser
からprocess.client
に変更- layoutファイル等の書き方を変更
- 動的なページのpathの書き方の変更
1. npm scripts
の変更
変更した箇所は大きくNuxt.jsを新規作成する際にnuxt generate
した結果を確認するコマンドnuxt preview
が追加されました。
nuxt start
とnuxt preview
の違いについて
変更箇所
before
{ "scripts": { "build": "nuxt build", "dev": "nuxt", "generate": "nuxt generate", "start": "nuxt start", }, }
after
{ "scripts": { "build": "nuxt build", "dev": "nuxt dev", "generate": "nuxt generate", "start": "nuxt start", "preview": "nuxt preview", }, }
2. configファイルの編集
今回対応した箇所については以下になります。
srcDir
: ソースファイルのroot部分の定義。Nuxt3の変更にあたっては変更はなし
css
: ページ全体で読み込まれるcssファイルの定義。Nuxt3の変更にあたっては変更はなし
buildModules
: 開発時やビルド時にのみ必要となるもののパッケージの一覧。Nuxt3の変更にあたって外部モジュールで使用しているものはなかったため、この項目自体削除。
components
: Nuxt.jsでコンポーネントファイルを自動で読み込みを行うかの設定。静的なコンポーネントのみの場合components
の設定は不要だが、今回は動的に取得したいパターンもあるため、components
を設定。
今回のコンポーネントの変更で component/Atoms/HogeFuga
コンポーネントの場合、以下のように修正は必要。
before: <hoge-fuga /> after: <AtomsHogeFuga />
動的コンポーネントがある場合のcomponent
の設定方法参考
変更箇所
before
import type { NuxtConfig } from '@nuxt/types' const config: NuxtConfig = { srcDir: 'src', css: ['~/assets/scss/main.scss'], buildModules: [ // https://go.nuxtjs.dev/typescript '@nuxt/typescript-build', // https://go.nuxtjs.dev/stylelint '@nuxtjs/stylelint-module', // https://github.com/nuxt-community/composition-api '@nuxtjs/composition-api/module', // https://Vueuse.org/guide/index.html '@Vueuse/nuxt', // https://github.com/nuxt/components '@nuxt/components', ], components: (() => { const common = { extensions: ['Vue', 'ts'], ignore: ['**/_*', '**/constants.ts', '**/hooks.ts'], } return [ { path: '~/components/Atoms/', ...common }, { path: '~/components/Molecules/', ...common }, { path: '~/components/Organisms/', ...common }, ] })(), } export default config
after
import { resolve } from 'path' // https://v3.nuxtjs.org/api/configuration/nuxt.config export default defineNuxtConfig({ srcDir: 'src/', css: ['~/assets/scss/main.scss'], alias: { '~': resolve(__dirname, './src') }, components: { global: true, dirs: ['~/components'] } })
3. VueファイルをNuxt3にあった書き方に変更
Nuxt3からVue3に対応されたため、composition-api
の書き方をする際に@nuxtjs/composition-api
のパッケージが不要に。
Vue3でsetup
関数を使用することができるため、より簡単に記載することができるようになりました。
以下の2点も変更しています。
Vue3でのsetupの書き方について
Vue3でのマルチルートノードコンポーネントについて
変更箇所
before
<template> <div> <component-name1 /> <component-name2 :array-list="arraySlice" /> </div> </template> <script lang="ts"> import { defineComponent } from '@nuxtjs/composition-api' import { array } from '~/filePath' export default defineComponent({ name: 'Page', setup() { const arraySlice = array.slice(0, 7) return { arraySlice } }, }) </script>
after
<template> <AtomsComponentName1 /> <MoleculesComponentName1 :array-list="arraySlice" /> </template> <script lang="ts" setup> import { array } from '~/filePath' const arraySlice = array.slice(0, 7) </script>
4. process.browser
からprocess.client
に変更
一部hooks部分でprocess.browser
を使っていた箇所がNuxt3の変更に伴って動かなくなったため、process.client
に変更。
変更箇所
before
import { reactive, toRefs } from '@nuxtjs/composition-api' import { useWindowScroll } from '@vueuse/core' const { x, y } = ? useWindowScroll() : { ...toRefs(reactive({ x: 0, y: 0 })) }
after
// Nuxt3のauto importにより、reactive, toRefsのimportは不要。 import { useWindowScroll } from '@vueuse/core' const { x, y } = process.client ? useWindowScroll() : { ...toRefs(reactive({ x: 0, y: 0 })) }
5. layoutファイル等の書き方を変更
Nuxt3でのlayoutファイル群の書き方も異なっていたため、以下のように変更しました。
変更箇所
before
layoutファイル側
<template> <nuxt /> </template>
pagesファイル側
<template> <component-name /> </template> <script lang="ts"> import { defineComponent, useRoute, useMeta } from '@nuxtjs/composition-api' export default defineComponent({ name: 'PageName', layout: 'LayoutName', setup() { const route = useRoute() const paramId = Number(route.value.params.id) useMeta({ title: 'ページタイトル名', meta: [{ hid: 'robots', name: 'robots', content: 'noindex' }], }) return { paramId } }, head: {}, }) </script>
after
layoutファイル側
<template> <slot /> </template>
pagesファイル側
<template> <NuxtLayout name="iframe"> <ComponentName /> </NuxtLayout> </template> <script lang="ts" setup> const route = useRoute() const paramId = Number(route.params.id) useHead({ title: 'ページタイトル名', meta: [{ hid: 'robots', name: 'robots', content: 'noindex' }] }) definePageMeta({ layout: false }) </script>
6. 動的なページのpathの書き方の変更
Nuxt3での動的ページの生成の方法も異なっていたため、以下のように変更しました。
変更箇所
before
File: /pages/users/_id.Vue Url: /users/123 $route.params = { id: "123" }
after
File: /pages/users/[id].Vue Url: /users/123 $route.params = { id: '123' }
今回は対応していないこと
1. storybookの対応
Nuxt3でのstorybookの対応がまだされていないため、今回のアップデートでは対応していません。
以下の方法で一部コンポーネントの表示は行うことはできましたがNuxtのauto importで読み込みを行っているものがエラーになってしまったため、今回は見送りました。
例
<template> <div class="hoge" ref="ref"> コンテンツ </div> </template> <script lang="ts" setup> // ここでrefの読み込みがないとのエラーが出る。 const ref = ref() </script>
cssの「:where」と「:is」の違いを理解する
cssの:where
と:is
の違いについて理解できていなかったため、違いなどについてまとめていこうと思います。
MDNの情報から
:where
について
:where() は CSS の擬似クラス関数で、セレクターリストを引数として取り、列挙されたセレクターのうちの何れかに当てはまるすべての要素を選択します。
/* ヘッダー、メイン、フッターの何れかの中にある段落に カーソルをかざしたときに選択 */ :where(header, main, footer) p:hover { color: red; cursor: pointer; } /* 上記のものは下記のものと同等です。 */ header p:hover, main p:hover, footer p:hover { color: red; cursor: pointer; }
:is
について
:is() は CSS の擬似クラス関数で、セレクターのリストを引数に取り、リスト中のセレクターの何れか一つに当てはまる要素をすべて選択します。数多くのセレクターを小さくまとめて書くのに便利です。
/* header, main, footer 要素の中の段落で マウスポインターが通過中のものをすべて選択 */ :is(header, main, footer) p:hover { color: red; cursor: pointer; } /* 上記のものは下記のものと同等です。 */ header p:hover, main p:hover, footer p:hover { color: red; cursor: pointer; }
:where
と:is
の違いについて
この 2 つの違いは、 :is() がセレクター全体の詳細度にカウントされる(最も詳細な引数の詳細度を取る)のに対し、 :where() は詳細度の値が 0 であることです。これは、 :where() 参照ページの例で実証されています。
今回検証したものについて
classの上書きについて
- 「テキスト」の部分が対象
- 上2つに
.card
のclassを付与。.card
に対して黒色を指定 :is
に赤色を指定:where
に青色を指定
See the Pen where and is by miwa_shuntaro (@miwashutaro0611) on CodePen.
「:is」のちょっとした例について
card
という同じUIスタイルa
タグの場合は文字色を赤色・その他の場合はスタイルを適応させない- 今回は文字色でやっていますが、ホバー時の追加などしたい時に有効かもしれないです。
See the Pen link text add color by miwa_shuntaro (@miwashutaro0611) on CodePen.
「:where」のちょっとした例について
- 「タイトル」の部分が対象
- class名部分で
font-size: 20px
、h1部分でfont-size: 10px
で宣言している
See the Pen title reset where by miwa_shuntaro (@miwashutaro0611) on CodePen.
sanitize.cssで:where
を用いたスタイルのリセットを用いられています。
結論
Deno のフルスタック・ウェブフレームワーク「Fresh」を触ってみる
Freshについて少し調べてみたので、その時のメモになります。
Freshとは
Fresh is a new full stack web framework for Deno. By default, web pages built with Fresh send zero JavaScript to the client. The framework has no build step which allows for an order of magnitude improvement in deployment times. Today we are releasing the first stable version of Fresh.
実行手順
以下のコマンド通り実行し、 http://localhost:8000/
を開くと以下のような画面になります。
$ deno run -A -r https://fresh.deno.dev my-project $ cd my-project $ deno task start
補足
denoのエラーがでる場合
denoをインストールしていない場合は以下のようなエラーが出るため、deno
のインストールが必要です。
エラー内容
$ deno run -A -r https://fresh.deno.dev my-project zsh: command not found: deno
denoのインストールについて
macの場合は以下でインストールできます。
$ brew install deno
deno run -A -r https://fresh.deno.dev my-project
を実行した際の挙動
deno run -A -r https://fresh.deno.dev my-project
を実行した場合、以下のようにダウンロードされます
$ deno run -A -r https://fresh.deno.dev my-project Download https://fresh.deno.dev/ Download https://deno.land/x/fresh@1.0.2/init.ts Download https://deno.land/x/fresh@1.0.2/src/dev/deps.ts Download https://deno.land/x/fresh@1.0.2/src/dev/error.ts Download https://deno.land/x/fresh@1.0.2/src/dev/mod.ts Download https://deno.land/std@0.150.0/flags/mod.ts Download https://deno.land/std@0.150.0/fs/walk.ts Download https://deno.land/std@0.150.0/path/mod.ts Download https://deno.land/std@0.150.0/semver/mod.ts Download https://deno.land/std@0.150.0/_util/assert.ts Download https://deno.land/std@0.150.0/fs/_util.ts Download https://deno.land/std@0.150.0/_util/os.ts Download https://deno.land/std@0.150.0/path/_interface.ts Download https://deno.land/std@0.150.0/path/common.ts Download https://deno.land/std@0.150.0/path/glob.ts Download https://deno.land/std@0.150.0/path/posix.ts Download https://deno.land/std@0.150.0/path/separator.ts Download https://deno.land/std@0.150.0/path/win32.ts Download https://deno.land/std@0.150.0/path/_constants.ts Download https://deno.land/std@0.150.0/path/_util.ts Do you want to use 'twind' (https://twind.dev/) for styling? [y/N] y Do you use VS Code? [y/N] y The manifest has been generated for 3 routes and 1 islands.
freshのファイル内容について
ディレクトリ全体については以下のようなイメージになります。
my-app/ ├── README.md ├── deno.json ├── dev.ts ├── fresh.gen.ts ├── import_map.json ├── islands │ └── Counter.tsx ├── main.ts ├── routes │ ├── [name].tsx │ ├── api │ │ └── joke.ts │ └── index.tsx └── static ├── favicon.ico └── logo.svg
dev.ts
プロジェクトの開発エントリポイント
main.ts
プロジェクトのプロダクションエントリーポイント
fresh.gen.ts
routes
とislands
に関する情報を含むマニフェストファイル
import_map.json
プロジェクトの依存関係を管理するために使用するインポートマップ
deno.json
プロジェクトの依存関係ファイル(import_map.json
)の読み込みやプロジェクトで実行したいコマンドの登録を行うファイル
routes/
ページを開く際のルートフォルダ。設定箇所で開くページについては以下を参照
routes/index.tsx
:http://localhost:8000/
routes/[name.]tsx
:http://localhost:8000/hoge
hgoe
の部分は任意で設定可能
islands/
クライアントサイドのインタラクティブ性を実現するためのファイル。 JavaScriptの読み込みが大きい場所などで使用していない箇所はScriptタグの読み込みを行わないことにより、ページのパフォーマンスを効率化することができます。
static/
静的なファイルを格納するフォルダ。
デプロイについて
GitHubにpush, deno deployで設定を行うことでデプロイすることができます
最後に
初期からルーティングのリンクの追加したのみですが、デプロイしたものについてはこちらになります。
eslintを使用してimportの部分をいい感じにする
以下のルール・パッケージを使用するといい感じにimport文の整形を行うことができたので、設定方法などを記載したものになります。
※ 執筆時点(2022/07/18)の情報になるため、ご認識ください。
結論としては以下のルール・パッケージになります。
ルール・パッケージについて
実行結果
eslint --fix
を実行した場合
変更前
import './App.css'; import { TypeHoge } from '~/types/Hoge'; import { useState, FC } from 'react'; import { ComponentHoge } from '~/components/Hoge';
変更後
import './App.css'; import type { FC } from 'react'; import { useState } from 'react'; import { ComponentHoge } from '~/components/Hoge'; import type { TypeHoge } from '~/types/Hoge';
1. consistent-type-imports
{ rules: { // ここから記載 "@typescript-eslint/consistent-type-imports": [ "error", { prefer: "type-imports" } ], // ここまで記載 }, }
設定できるプロパティについて
type Options = { prefer: 'type-imports' | 'no-type-imports'; disallowTypeAnnotations: boolean; }; const defaultOptions: Options = { prefer: 'type-imports', disallowTypeAnnotations: true, };
prefer
importしたtypeに対して、type
を付与するかしないか
type-imports
の場合
import type { Foo } from 'Foo'; import type Bar from 'Bar'; type T = Foo; const x: Bar = 1;
no-type-imports
の場合
import { Foo } from 'Foo'; import Bar from 'Bar'; type T = Foo; const x: Bar = 1;
disallowTypeAnnotations
trueの場合、以下のコードだとエラーになります。
type T = import('Foo').Foo; const x: import('Bar') = 1;
2. eslint-plugin-import
eslintのimport文の整形をしてくれるパッケージです。
$yarn add --save-dev eslint-plugin-import
各種ルールについて
全体は省略。
今回設定しているものについては以下になります。(デフォルトのルールではありますが、後々パッケージ側で変更されても問題ないように個別にも記載しています。)
{ rules: { "import/order": [2, { "alphabetize": { "order": "asc" } }] }, }
import/order
今回の場合は以下のようなイメージでの並び替えになります。
- アルファベット順
- 昇順
変更前
import React, { PureComponent } from 'react'; import aTypes from 'prop-types'; import { compose, apply } from 'xcompose'; import * as classnames from 'classnames'; import blist from 'BList';
変更後
import blist from 'BList'; import * as classnames from 'classnames'; import aTypes from 'prop-types'; import React, { PureComponent } from 'react'; import { compose, apply } from 'xcompose';
TypeScriptと併用する場合、以下のように設定
$ yarn add --dave-dev @typescript-eslint/parser
{ extends: [ // なにかのルール "plugin:import/typescript" // 追記 ], plugins: [ 'import', // 追記 ], }
React.js + TypeScript環境でnpx eslint --init
を実行し、以下の選択肢を選んだ場合 + 上記の設定を行ったものになります。
※ Vue.jsや通常のTypeScriptの場合でも上記と同様の方法で入れることができます。
ESLintの設定
✔ How would you like to use ESLint? · style ✔ What type of modules does your project use? · esm ✔ Which framework does your project use? · react ✔ Does your project use TypeScript? · No / Yes ✔ Where does your code run? · browser ✔ How would you like to define a style for your project? · guide ✔ Which style guide do you want to follow? · airbnb ✔ What format do you want your config file to be in? · JavaScript ✔ Would you like to install them now? · No / Yes ✔ Which package manager do you want to use? · yarn
ESLintのコード
module.exports = { env: { browser: true, es2021: true, }, extends: [ 'plugin:react/recommended', 'airbnb', "plugin:import/typescript" ], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 'latest', sourceType: 'module', }, plugins: [ 'react', '@typescript-eslint', 'import', ], rules: { "@typescript-eslint/consistent-type-imports": [ "error", { prefer: "type-imports" } ], "import/order": [2, { "alphabetize": { "order": "asc" } }] }, };
Next.jsでLighthouse CIを導入して、PRの時点でLighthouseのスコアを確認できるようにする
Next.jsでのプロジェクトでLighthouse CIを確認できるようにしたので、その時に行ったことになります。
参考にした記事
Lighthouseとは
Webアプリのパフォーマンス、品質、および正確性を向上させるためのオープンソースの自動化ツールです。
Lighthouse is an open-source, automated tool for improving the performance, quality, and correctness of your web apps.
こちらのようなイメージで各種の状態を確認することができます。
今回のゴール
PR作成時点で以下のようになる
ci/circle: lhci
の部分にビルドの結果が表示
Details
の部分をクリックすると以下のようにlighthouseの結果が表示される
実装した方法
1. Github Actionsに「Lighthouse CI 」をインストール
以下のページからインストールを行う
2. CIの記載
以下のページから実行したいものを選択し、コードの記載
対応しているものは以下になります。
circleCIの場合は以下になります。
version: 2.1 jobs: build: docker: - image: circleci/node:15.12-browsers working_directory: ~/your-project steps: - checkout - run: npm install - run: npm run build - run: sudo npm install -g @lhci/cli@0.8.x - run: lhci autorun
3. .lighthouserc
などのlighthouse用の設定ファイルを記載
以下の部分を参考に、.lighthouserc
の設定を行う
4. build
, start
コマンドがない場合、追加
ci上で計測を行うため、build
, start
のコマンドを追加
"scripts": { "build": "next build", "start": "next start" },
startについては以下のタイミングで呼び出されています。
以上でPR作成時にlighthouseが追加されるようになります。
詰まった箇所について
1. Chrome installation not found
のエラーが出る
こちらについては以下のissuesを参考に--collect.settings.chromeFlags=--no-sandbox
を追加
// その他の処理については省略 module.exports = { ci: { collect: { settings: { chromeFlags: '--no-sandbox' }, // これを追加 }, }, }
2. circleci/node:16
などの-browsers
でないとビルドエラーになる
dockerのimageがcircleci/node:16
の場合、以下のエラーがでるため、修正
// NG docker: - image: circleci/node:16 // OK docker: - image: circleci/node:16-browsers