UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

【Github Actions】Jobのスキップ

Github Actionsでラベルの種類によってJobをスキップする方法を調べた

https://docs.github.com/ja/actions/using-jobs/using-conditions-to-control-job-execution

基本的な使い方はjobs.<job_id>.ifを使う

例えばこんな感じ

name: example-workflow
on: [push]
jobs:
  production-deploy:
    if: github.repository == 'octo-org/octo-repo-prod'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '14'
      - run: npm install -g bats

ラベルの種類によってJobをスキップする方法はいかに記載があった

次のように書くとできる

if: github.event.label.name == 'help-wanted'

他にどんな条件がかけるかはドキュメントにまとまってなくてわかりづらい印象

とりあえずこれでやりたいことはできた

他参考

https://docs.github.com/ja/actions/using-workflows/events-that-trigger-workflows#push

【Vitest】Vitestでテストする

以前Vitestについて軽く調べたが、実際に試してみる

【Vite】Viteとは - UGA Boxxx

こちらの記事を参考にさせていただく

dev.classmethod.jp

上の記事ではReactだが、今回はVueで同じようなことをやってみる

テストするページはテンプレートから作ったままのこちらのページ

以下をインストールする

$ npm i -D @testing-library/react
$ npm i -D @testing-library/jest-dom
$ npm i -D happy-dom
$ npm i -D @testing-library/user-event

vite.config.tsを編集する

/// <reference types="vitest" />
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

...

export default defineConfig({
  ...
  test: {
    globals: true,
    environment: 'happy-dom',
  }
})

まず、vite.config.tsに設定を書く場合は文頭に/// <reference types="vitest" />が必要なため記載する

次に、test.globalstest.environmentを設定する

test.globalsは明示的にdescription, test, expectのimportを書いたほうがよいと思ってfalseのままがよかったが、なぜかimportしているのに未定義エラーになってしまうので仕方なくtrueにした

test.environmentnode, 'js-dom', 'happy-dom'のいずれかが指定可能だが、記事通り'happy-dom'にしてみた

testファイルはつまづきポイントが多かった

import '@testing-library/jest-dom'
import { describe, expect, test } from 'vitest'
import App from './App.vue'
import { render, screen, fireEvent } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'

describe('Simple working test', () => {

  test('should render correctly', () => {
    const {container} = render(App)
    expect(container.firstChild).toMatchSnapshot()
  })

  // extend-expectのmatcher[toBeInTheDocument]
  test('the title is visible', () => {
    render(App)
    expect(screen.getByText(/Hello Vue 3 \+ TypeScript \+ Vite/i)).toBeInTheDocument()
  })

  // jest-extendedのmatcher[toBeEmptyDOMElement]
  test('should increment count on click', async() => {
    render(App)
    fireEvent.click(screen.getByRole('button'))
    expect(await screen.findByText(/count is: 1/i))
  })
})

まずimport '@testing-library/jest-dom'はimportしないと.toBeInTheDocument() などの matcher が未定義になりエラーとなった

次に、ReactであればuserEvent.click(screen.getByRole('button'))でイベントが発火するのだが、Vueだと発火しなかった

代わりにfireEvent.click(screen.getByRole('button'))を使ってあげる

これはこちらが参考になった
testing-library/user-eventではVueが期待するchangeイベントを発火できない

とりあえずこれでテスト実行することができた

【Vite】複数ページある場合の設定を考える

ViteはマルチページAppに対応しているので、複数ページの設定を調査した

Building for Production | Vite

マルチページAppとは上の図にあるように/だけではなく、/nest/というページがあるようなアプリケーション

やりかたはvite.config.jsrollupOptions.inputにエントリーポイントとして複数の.htmlファイルを指定するだけとある

// vite.config.js
const { resolve } = require('path')
const { defineConfig } = require('vite')

module.exports = defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        nested: resolve(__dirname, 'nested/index.html')
      }
    }
  }
})

ただ、前回やりたかった出力されるJSファイル名変更もやる場合は、もう少し工夫が必要

【Vite】ビルドして出力されるファイル名を変えたい - UGA Boxxx

前回のままだと/assets/bundle.jsが2つ(正確には、bundle.jsとbundle2.js)ができてしまったので、設定値を変える

[name]というプレースホルダーを使えば、エントリポイントにオブジェクトでない場合はエントリポイントのファイル名(拡張子なし)が埋め込まれ、エントリポイントにオブジェクトが指定された場合はキー名が埋め込まれるらしいので使ってみる

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        nested: resolve(__dirname, 'nested/index.html')
      },
      output: {
        entryFileNames: `assets/[name]/bundle.js`,
      }
    }
  }
})

するとassets/main/bundle.jsassets/nested/bundle.jsの2つが出力された

ただ、assets/main/bundle.jsassets/bundle.jsにしたい

いろいろ試行錯誤した結果、src/pagesを用意してそこをプロジェクトルートにした

そして、src/pages配下にサブディレクトリをつくっていく

こんな感じ

├── src
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   ├── env.d.ts
│   └── pages
│       ├── index.html
│       ├── App.vue
│       ├── app.ts
│       ├── nested
│       │   ├── App.vue
│       │   ├── app.ts
│       │   └── index.html
│       └── nested2
│           ├── App.vue
│           ├── app.ts
│           └── index.html

vite.config.jsは結果的にこうなった

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

const { resolve } = require('path')

// https://vitejs.dev/config/
export default defineConfig({
  root: 'src/pages', //----(1)
  plugins: [vue()],
  publicDir: resolve(__dirname, 'public'), //----(2)
  build: {
    outDir: resolve(__dirname, 'dist'), //----(3)
    emptyOutDir: true,  //----(4)
    rollupOptions: {
      input: {
        '': resolve(__dirname, 'src/pages/index.html'),
        'nested': resolve(__dirname, 'src/pages/nested/index.html'),
        'nested2': resolve(__dirname, 'src/pages/nested2/index.html')
      },
      output: {
        entryFileNames: `assets/[name]/bundle.js`,
        assetFileNames: (assetInfo) => {
          if (assetInfo.name === '.css') {  //----(5)
            return 'assets/index.css'
          }
          return `assets/[name].[ext]`
        },
        chunkFileNames: `assets/[name].js`,
      }
    }
  }
})

(1)ではsrc/pagesをプロジェクトルートにする

(2)では(1)によってpublicディレクトリの場所が見つからなくなってしまったので再定義している

(3)では(1)によって出力先のdistを明示的に定義している

(4)ではdistがプロジェクトルート外になってしまったため、ビルド時に自動でクリーンするので警告がでるようになった

falseを指定すると自動でクリーンされなくなるが、今回はしてほしいのでtrueにした

(5)がはまったところで、cssファイル名が[name].cssになるため、''をエントリーポイントのキーにしたプロパティでは.cssというファイルを出力してしまう

そこで、assetInfo.nameを使って、.cssの場合はindex.cssという名前をつけるようにした

これでいい感じに吐き出してくれるようになった

$ npm run build

> try-vite@0.0.0 build
> vue-tsc --noEmit && vite build

vite v2.9.8 building for production...
✓ 19 modules transformed.
Generated an empty chunk: "main"
../../dist/index.html                 0.32 KiB
../../dist/nested/index.html          0.53 KiB
../../dist/nested2/index.html         0.53 KiB
../../dist/assets/bundle.js    0.25 KiB / gzip: 0.21 KiB
../../dist/assets/nested/bundle.js    0.25 KiB / gzip: 0.21 KiB
../../dist/assets/nested2/bundle.js   0.26 KiB / gzip: 0.21 KiB
../../dist/assets/index.css          0.17 KiB / gzip: 0.14 KiB
../../dist/assets/nested.css          0.17 KiB / gzip: 0.14 KiB
../../dist/assets/nested2.css          0.17 KiB / gzip: 0.14 KiB

【Vite】ビルドして出力されるファイル名を変えたい

Viteを実プロジェクトで使うことを視野に入れていくつか試してみた

Viteについて

uga-box.hatenablog.com

このうち、buildして出力されるファイル名を変えたいのでどうやるか調査した

vite.config.ts を変更する

デフォルトではindex.[hash].js が出力されるのを、仮で build.js にしたい

buildの入出力を制御する場合は、vite.config.tsrollupoptionsの設定を変更する


https://vitejs.dev/config/#build-rollupoptions

設定の詳細はrollupを見ろということなので、こちらを参照する   rollupjs.org

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      output: {
        entryFileNames: `assets/bundle.js`,
      }
    }
  }
})

これで build.js という名前のファイルをつくることができた

$ npm run build

> sample-vite-project@0.0.0 build
> vue-tsc --noEmit && vite build

vite v2.9.8 building for production...
✓ 14 modules transformed.
dist/assets/logo.03d6d6da.png    6.69 KiB
dist/index.html                  0.41 KiB
dist/assets/index.f0ced7b7.css   0.34 KiB / gzip: 0.24 KiB
dist/assets/bundle.js            52.41 KiB / gzip: 21.15 KiB

【Qwik】ざっくりQwikとは?

QwikというReactに似た書き方で開発できるMPAフレームワークのお話を聞いたので自分でも調べてみる

qwik.builder.io

この前読んだJackさんの記事でもそうだが、いまのSPAには色々課題がある

NavigationAPIはその課題の解決策の一つであるが   uga-box.hatenablog.com

そもそもなんでもかんでもSPAにするなという記事もあったりして、今後もMPA(従来のページ遷移)を採用する可能性は十分ある

lealog.hateblo.jp

ただ、MPAにはもともとの問題(サーバーサイドテンプレートとの二重開発など)があったりするので、こっちはこっちで解決しなければならない

そんな中、MPAであるがテンプレートもイベントハンドラJavaScriptでやるという思想のフレームワークが登場してきているみたい

Qwikそのうちの一つ

vs ハイドレーション

いまのReact SSRにおけるハイドレーションはレンダリングが2回(SSR時とCSR時の2回)やることになるので無駄が多い

レンダリングを2回やらなければならない主な理由はイベントハンドラの復元で、SSRしたときではイベントハンドラが実行できないので、実行できるようにするためCSRが必要になる

これをしないようにするのがQwikのモチベーション

ソリューション

これを解決するため、イベントハンドラの状態をSSR時にHTMLにシリアライズしておいて、クライアントサイドでデシリアライズして復元しようというのがQwikのソリューションとのこと

シリアライズについて ↓

https://qwik.builder.io/guide/serialization/overview

具体的に一番わかりやすいのがJSON.stringifyだが、これだけではできないことがあるので、そこをQwikが補う

例えば

  • DOM references
  • Dates (not yet implemented)
  • Function closures (if wrapped in QRL).

ただ、Qwikでもできないことがあるので、そこを理解しながら開発するのが必要とのこと

例えば以下のことは解決できないと書いてある

  • Serialization of classes (instanceof and prototype)
  • Serialization of Promises, Streams, etc...

あとは、イベントに必要なJavaScriptのダウンロードをイベントが実行されるまで遅らせる(Fine-grained Lazy-Loading)などの仕組みがあるみたい

聞いたお話によると、イベント発生からイベント実行までが環境によっては遅いかもとのことで、まだまだ改善の余地がありそう

SPAの課題は感じているので、MPAフレームワークを今後も注目していきたい

【TypeScript】テンプレートリテラル型

下の本を読んでテンプレートリテラル型を知らなかったのでメモ

あったらいいのにと思っていた構文だが、v4.1(約2年前)からあったみたいで、うまく探せてないだけだった

本を読んで存在をしれてよかった

JavaScriptと同様にHello, ${string}!のような書き方でテンプレート化できるが、${}の中は型名がはいる

本を読んで面白い使い方だと思ったのは、戻り値をテンプレートリテラル型にする逆の関数をつくる話

つまりsliceした後の文字列を型にした戻り値をつくる関数

function fromKey<T extends string>(key: `user: ${T}`): T {
  return key.slice(5) as T;
}

const user = fromKey("user:test"); // userは"test"型になる

自分のプロジェクトで、あるIDをサービス名:親番号:子番号:言語というようにプロパティを駆使して整形している

いままで、stringにしていたので、このテンプレートリテラル型でより安全にできそう

【Chromatic】ターゲットブランチが見つからず差分が毎回すべて新規ファイルとして扱われてしまう

ChromaticでVRT環境をつくった

uga-box.hatenablog.com

ただ、プルリクを作って前回ビルドとの差分をみてみると毎回全部のファイルが新規作成のようになっていて前回ビルドとの差分が表示されない

おそらく対象ブランチがデフォルトでmainになっているが、自分のプロジェクトではmasterをベースブランチにしているのでその当たりかと考えた

そこで、ChromaticのPRsタブを開いてChangesetタブをみていみると以下のような画面になっている

Chromaticも比較すべきブランチが見つからないと警告してくれていた

修正するには以下のコマンドを実行してと書いてあるので、実行したら無事差分が表示された

$ npx chromatic --project-token=xxxxxxx --patch-build=[ブランチ名]...master

patch-buildのオプションについては以下

CLI • Chromatic docs

ブランチの考え方は以下を参照した

Branches and baselines • Chromatic docs