20211128のvue.jsに関する記事は5件です。

【Vue 3+TypeScript+Vite】Chrome拡張機能(MV3) + FirebaseでGoogle認証する

経緯 Chrome拡張機能のMV2でFirebaseを使ってGoogle認証するという記事は見つかるのですが、2021/11/27時点でMV3で作られている記事はほとんどなくハマりポイントが多かったので整理も兼ねて記事にしてみようと思います。 前半(Docker + Vueでビルド環境を作る)は主に開発環境構築の話となります。 Chrome拡張機能(MV3) + FirebaseでGoogle認証するに関する部分は後半になります。こちらから後半部に遷移できます。 リポジトリ 今回の記事で使ったコードは下記リポジトリにあげているので参考にしてください。 https://github.com/snyt45/chrome-extensions-mv3-google-authenticator-with-firebase Docker + Vueでビルド環境を作る Chrome拡張機能はHTML、CSS、JSで書けるのですが今回は勉強も兼ねてVueを使ってChrome拡張機能に必要なファイルを生成するためのビルド環境を構築していきます。 今回の構成 Docker docker-composeを使います。 Node.js JavaScriptの実行環境 npm Node Package Manager.Node.jsにデフォルトで付属しているコマンドでNode.jsのパッケージ管理をするためのツール。 npx npm v5.2.0からnpmに同梱されるようになったコマンドでNode.jsのパッケージを実行するためのツール。 本記事では登場しませんが、実際に動作確認する際にnpxは便利なので結構使っています。 Vue3 + TypeScript JavaScriptフレームワークのVue3とAltJS(コンパイルすることで最終的にJavaScriptのコードを生成する)の一つのTypeScriptを使います。 Vite Vue.jsのプロジェクト作成や実行ができるツール Docker上に環境構築 今回はNode.jsがインストールされているDockerイメージをベースに構築していきます。 ViteにはNode.jsのバージョンが12以上必要みたいなので、今回はNode.jsのバージョン14のイメージを使います。 docker-compose.yml version: '3' services: app: container_name: vue-app-container image: node:14 working_dir: /app ports: - 3000:3000 volumes: - .:/app stdin_open: true stdin_open: trueは、stdin_open: trueがないとdocker-compose up後にコンテナが終了してしまうのでコンテナを起動し続けるために必要でした。 docker-compose.ymlを作成したら、コンテナのビルド&起動を行います。 docker-compose up -d コンテナが起動したら、コンテナ内でbashを対話シェルとして実行します。 docker-compose exec app /bin/bash コンテナはNode.jsがすでにインストールされている環境なので、node npm npxのコマンドが使えることを確認します。 root@64985558fba6:/app# node -v v14.18.1 root@64985558fba6:/app# npm -v 6.14.15 root@64985558fba6:/app# npx -v 6.14.15 Viteでプロジェクトを生成する Viteでプロジェクトを生成するコマンドを実行します。 npm init vite@latest コマンドライン上で対話形式で入力していくと、プロジェクトが生成されます。 (3つ入力または選択するだけでプロジェクトが生成されました…ほんとに高速で驚きです!) root@64985558fba6:/app# npm init vite@latest npx: installed 6 in 3.059s ✔ Project name: … sample-project ✔ Select a framework: › vue ✔ Select a variant: › vue-ts Scaffolding project in /app/sample-project... Done. Now run: cd sample-project npm install npm run dev app/sample-projectディレクトリに生成されたファイルをappディレクトリ配下に展開するために下記コマンドを実行します。 # sample-projectに移動 cd sample-project # sample-project内の全てのファイルを一階層上(app/)に移動 mv * ../ # sample-projectディレクトリは不要になるので削除 cd ../ rm -rf sample-project サーバーを起動して、ブラウザーで「http://localhost:3000/」にアクセスします。 # package.jsonのライブラリをインストール npm install # 開発用のWebサーバー起動 npm run dev ですが、Vueの初期ページがされず.. 下記を参考にvite.config.jsに設定を追加したところ、正常に表示されました。 dockerにviteを入れてvueで開発しようとしたときlocalhostに繋がらなかった話 | SEとDIY vite.config.js export default defineConfig({ plugins: [vue()], + server: { + host: true + } }) 正常に表示されている画像。 動作確認できたので、今回不要なファイルを削除しておきます。 # index.html削除 rm -rf index.html # public削除 rm -rf public # src配下のファイルを削除 cd src rm -rf * Chrome拡張機能用のファイルを生成できるようにする 今回Vueを使いますが最終的にはHTMLやJSなどをextension/フォルダにビルドし、そのファイルをChrome拡張用のファイルとして使います。 Chrome拡張機能用のビルド後のディレクトリ構成 ビルドを実行した際に下記のようなディレクトリ構成が作成されるようにします。 extension |- dist/ | |- assets/ # index.htmlから読み込むjsなどが配置される | |- popup/ # popup用のUIファイル | |- index.html |- manifest.json # Chrome拡張機能のエントリポイント manifest.jsonを生成するためのビルド環境を作る Chrome拡張機能のエントリポイントであるmanifest.jsonをextension/にビルドできるようにしていきます。 extensionディレクトリを作成しておきます。 mkdir extension 先に必要になるライブラリをインストールしておきます。 npm install --save-dev @types/node # pathモジュールの型情報を取得するため npm install --save-dev @types/fs-extra # fs-extraモジュールの型情報を取得するため npm install --save-dev fs-extra # fs-extraモジュールを使えるようにするため npm install --save-dev esno # manifest.tsをバッチのように実行するビルドツール manifest.jsonを作成するためのmanifets.tsを作成します。 (よく使うメソッドはscripts/utils.tsに作成しておきます。) scripts/utils.ts import { resolve } from 'path' export const r = (...args: string[]) => resolve(__dirname, '..', ...args) src/manifest.ts import fs from 'fs-extra' import { r } from '../scripts/utils' const getManifest = () => { return { manifest_version: 3, name: 'Getting Started Example', description: 'Build an Extension!', version: '1.0', action: { default_popup: './dist/popup/index.html' } } } function writeManifest() { fs.writeJSON(r('extension/manifest.json'), getManifest(), { spaces: 2 }) } writeManifest() package.jsonにmanifest.jsonを生成するnpmスクリプトを追加します。 package.json "scripts": { "build:manifest": "esno src/manifest.ts" }, npm run build:manifestを実行してextensionディレクトリにmanifest.jsonが作られれば成功です。 popup用のUIファイルを生成するためのビルド環境を作る src配下にpopupフォルダを作成します。 mkdir src/popup Viteではindex.htmlがエントリポイントになります。 エントリポイントとなるファイルを作成します。 ビルド時にエントリポイントで読み込まれているmain.tsもバンドルされます。 src/popup/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Popup</title> </head> <body style="min-width: 100px"> <div id="app"></div> <script type="module" src="./main.ts"></script> </body> </html> エントリポイントから読み込むファイルも作成します。 src/popup/main.ts import { createApp } from 'vue' import App from './Popup.vue' const app = createApp(App) app.mount('#app') src/popup/Popup.vue <template> <div> Hello World!! </div> </template> <script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ name: 'Popup', setup() { return {} }, }) </script> このままだとvueファイルを認識できていなかったため、env.d.tsを追加しました。 (src配下を一度削除しましたが、そのときに必要なenv.d.tsも削除してしまっていたようです…) src/env.d.ts /// <reference types="vite/client" /> declare module '*.vue' { import { DefineComponent } from 'vue' // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types const component: DefineComponent<{}, {}, any> export default component } popup用のUIファイルをビルドするためにvite.config.tsに設定を追加します。 vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { r } from './scripts/utils' // https://vitejs.dev/config/ export default defineConfig({ root: r('src'), build: { outDir: r('extension/dist'), emptyOutDir: false, rollupOptions: { input: { popup: r('src/popup/index.html'), }, }, }, plugins: [ vue(), // rewrite assets to use relative path { name: 'assets-rewrite', enforce: 'post', apply: 'build', transformIndexHtml(html) { return html.replace(/"\/assets\//g, '"../assets/') }, }, ], server: { host: true } }) package.jsonにextensionディレクトリにdistディレクトリを生成するnpmスクリプトを追加します。 package.json "scripts": { "build:manifest": "esno src/manifest.ts", + "build:popup": "vite build" }, npm run build:popupを実行して、extensionディレクトリにdistディレクトリが生成されて中にassetsやpopupディレクトリが生成されていれば成功です。 ビルドスクリプトを微調整 まずは必要になるライブラリをインストールします。 npm install --save-dev rimraf # ファイル・ディレクトリ削除用のライブラリ npm install --save-dev npm-run-all # npmスクリプトをシーケンシャル(ひとつずつ順番に)実行できるライブラリ npmスクリプトを修正します。 buildコマンドとclearコマンドが新たにできたのでビルドするとき便利になりました。 package.json "scripts": { + "build": "run-s clear build:manifest build:popup", "build:manifest": "esno src/manifest.ts", "build:popup": "vite build", + "clear": "rimraf extension/dist extension/manifest.json" }, ビルドがうまくいったか動作確認 ここまででChrome拡張機能に最低限必要なファイルがビルドできるようになったので、実際に確認します。 npm run build chrome://extensions/ にアクセスします。 左上の「パッケージ化されていない拡張機能を読み込む」から生成したextensionディレクトリを指定します。 追加に成功すると次のように表示されます。 ブラウザ右上のChrome拡張機能をクリックしてHello Worldが表示されれば成功です! Chrome拡張機能(MtV3) + FirebaseでGoogle認証する ビルド環境ができたので、やっと本題になります。 最終的にはchrome.identity.getAuthToken+signInWithCredentialを使う方法でChrome拡張機能(MV3) + FirebaseでGoogle認証することができました。 chrome.identity.getAuthTokenとは chromeで使えるAPI。OAuth2アクセストークンを取得します。 https://developer.chrome.com/docs/extensions/reference/identity/ signInWithCredentialとは Firebaseで使えるAPI。指定された資格情報を使用して非同期にサインインします。 https://firebase.google.com/docs/reference/js/auth.md#signinwithcredential Chrome拡張機能(MtV3) + FirebaseでGoogle認証するまでの流れ 1. Firebaseのプロジェクトを作成する Firebaseのログインを済ませ、FirebaseコンソールのFirebaseプロジェクトから「プロジェクトを追加」します。 プロジェクト名を入力します。ここでは、「sample-project」にしています。 Googleアナリティクスは今回必要ないので無効にします。 少し待つとプロジェクトが作成されます。 続行を押して、FIrebaseのプロジェクトに移動して次のような画面が表示されればプロジェクトが作成できています。 2. Firebaseのマイアプリを登録する。 Chrome拡張機能からGoogle認証するうえでマイアプリが必要になるので、「プロジェクトの設定」から登録します。 マイアプリの赤枠アイコンをクリックして登録していきます。 アプリのニックネームを入力します。 FirebaseのSDKの追加を行います。 今回は、「npmを使用する」にチェックします。 この手順に従って、SDKをインストールしておきます。 (SDKをインストールしておくことで、Chrome拡張機能プロジェクト内にFirebaseスクリプトを含むことになるので、エラーにならずに使用することができます。) npm install --save-dev firebase # Firebase SDKのインストール 「コンソールに進む」ことでマイアプリが登録されます。 ひとまず、ここでFirebaseの初期化までエラーなく動くか試しておきます。 src/popup/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Popup</title> </head> <body style="min-width: 100px"> <div id="app"></div> <script type="module" src="./main.ts"></script> + <script type="module" src="./firebase.ts"></script> </body> </html> src/popup/firebase.ts import { initializeApp } from 'firebase/app' const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_APIKEY, authDomain: import.meta.env.VITE_FIREBASE_AUTHDOMAIN, projectId: import.meta.env.VITE_FIREBASE_PROJECTID, storageBucket: import.meta.env.VITE_FIREBASE_STORAGEBUCKET, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGINGSENDERID, appId: import.meta.env.VITE_FIREBASE_APPID } const app = initializeApp(firebaseConfig) console.log(app) 環境変数は.env.localを作ってそちらから読み込むようにしました。 (.gitignoreでgit管理対象外にしておきましょう) env.local # Firebase configuration VITE_FIREBASE_APIKEY=XXXXXXXXXXX VITE_FIREBASE_AUTHDOMAIN=XXXXXXXXXXX VITE_FIREBASE_PROJECTID=XXXXXXXXXXX VITE_FIREBASE_STORAGEBUCKET=XXXXXXXXXXX VITE_FIREBASE_MESSAGINGSENDERID=XXXXXXXXXXX VITE_FIREBASE_APPID=XXXXXXXXXXX npm run buildして、Chrome拡張機能を再読み込みします。 ブラウザ右上の拡張機能のアイコンを右クリックして「ポップアップ検証」からコンソールを開きます。 エラーが出ていなければここまでの動作確認は成功です。 3. Google Cloud Platform(GCP)のプロジェクトの確認 ここが重要なのですが、Firebaseでプロジェクトを作成した時点でGoogle Cloud Platform(GCP)側で同じプロジェクト名でプロジェクトが作成されています。 ここでは、sample-projectをクリックしてGCPのプロジェクトのダッシュボードに移動します。 プロジェクトのダッシュボードの左メニューにある「APIとサービス」の認証情報をクリックします。 「APIキー」、「OAuth 2.0 クライアント ID」がFirebaseによって自動で作成されていることが分かります。 後述のハマりポイント③にも書いていますが、自動で作成されている「OAuth 2.0 クライアント ID」を削除するとFIrebase認証でGoogleを追加する際にエラーが発生するので残しておきます。 4. chrome.identity.getAuthTokenを使うための準備 こちらを参考にして準備していきます。 https://developer.chrome.com/docs/apps/app_identity/ 4.1 マニフェストに権限を追加します。 src/manifest.ts 略 const getManifest = () => { return { manifest_version: 3, name: 'Getting Started Example', description: 'Build an Extension!', version: '1.0', action: { default_popup: './dist/popup/index.html' }, + permissions: [ + 'identity' + ], } } 略 4.2 OAuth2クライアントIDを取得する すでにFirebaseが自動でOAuth 2.0 クライアント IDを作成していますが、種類が「ウェブ アプリケーション」になってしまっているため新たに作成していきます。 その前に、OAuth同意画面の設定を行います。 User Typeを「外部」にチェックして、作成します。 アプリ名、ユーザーサポートメール、デベロッパーの連絡先情報を入力して保存して次へ。 スコープは「Googleアカウントのメールアドレスの参照」にチェックを入れて保存して次へ。 省略可能な情報は、何もせず保存して次へ。 再度、OAuth 同意画面に戻りテストユーザーに自分のGoogleアカウントのメールアドレスを登録しておきます。 続いて、OAuthクライアントIDを作成していきます。 アプリケーションの種類を「Chromeアプリ」、名前を「sample-project」、アプリケーションIDはChrome拡張機能の画面から取得したIDを入力して作成します。 アプリケーションIDは下記から取得します。 これでOAuth2クライアントIDを取得できるようになりました。 4.3 chrome.identity.getAuthTokenでアクセストークンを取得 アクセストークンを取得できる準備ができたので実装をしていきます。 まずは、chromeのAPIを使う際に必要なライブラリをインストールしておきます。 npm install --save-dev @types/chrome # chromeの型情報を取得するため マニフェストに取得したOAuth2クライアントIDとスコープを追加します。 src/manifest.ts const getManifest = () => { return { manifest_version: 3, name: 'Getting Started Example', description: 'Build an Extension!', version: '1.0', action: { default_popup: './dist/popup/index.html' }, permissions: [ 'identity' ], + oauth2: { + client_id: 'XXXXXXXXXXXX.apps.googleusercontent.com', + scopes: ['https://www.googleapis.com/auth/userinfo.email'] } } } アクセストークンを取得するための処理を追加します。 src/manifest.ts import { initializeApp } from 'firebase/app' const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_APIKEY, authDomain: import.meta.env.VITE_FIREBASE_AUTHDOMAIN, projectId: import.meta.env.VITE_FIREBASE_PROJECTID, storageBucket: import.meta.env.VITE_FIREBASE_STORAGEBUCKET, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGINGSENDERID, appId: import.meta.env.VITE_FIREBASE_APPID } initializeApp(firebaseConfig) // ChromeアプリからGoogleログインしてトークン取得 chrome.identity.getAuthToken( {interactive: true}, (token: string) => { console.log('token', token) } ) npm run buildを行い、Chrome拡張機能を再読み込みしてChrome拡張機能のアイコンをクリックすると、Googleのサインインのポップアップが表示されるので、ログインするユーザーを選択します。 「Continue」を選択します。 「Allow」を選択します。 コンソールを開いて、ログインユーザーのtokenが表示されていれば成功です。 5. FirebaseでGoogle認証するための準備 5.1 AuthenticationでGoogle認証を追加する Authenticationで「始める」をクリックします。 Sign-in methodの中から「Google」を選択します。 「有効にする」を選択し、プロジェクトの公開名、プロジェクトのサポートメールを入力・選択して保存します。 Googleが有効になっていればOKです。 5.2 signInWithCredentialを使ってFirebaseでGoogle認証する firebase.tsを下記のように修正します。 src/popup/firebase.ts import { initializeApp } from 'firebase/app' import { getAuth, GoogleAuthProvider, signInWithCredential } from 'firebase/auth' const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_APIKEY, authDomain: import.meta.env.VITE_FIREBASE_AUTHDOMAIN, projectId: import.meta.env.VITE_FIREBASE_PROJECTID, storageBucket: import.meta.env.VITE_FIREBASE_STORAGEBUCKET, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGINGSENDERID, appId: import.meta.env.VITE_FIREBASE_APPID } initializeApp(firebaseConfig) const auth = getAuth() // ChromeアプリからGoogleログインしてトークン取得 chrome.identity.getAuthToken( {interactive: true}, (token: string) => { console.log('token', token) // Googleログイン成功時に受け取るトークンを使ってGoogleのクレデンシャル作成 const credential = GoogleAuthProvider.credential(null, token) console.log('credential:', credential) console.log('auth:', auth) // Googleユーザーのクレデンシャルを使ってサインイン signInWithCredential(auth, credential).then((result) => { console.log("Sign In Success", result) }).catch((error) => { console.log("Sign In Error", error) }) } ) いつも通りnpm run buildを行って、Chrome拡張機能を再読み込みしてアイコンをクリックしてコンソールを開きます。 下記のようにSign In SuccessとなっていればFirebaseでGoogle認証できています。 ここまで長かったですね!!お疲れ様でした! 最後にChrome拡張機能(MV3)とFirebaseでGoogle認証のハマりポイントを紹介していきます。 Chrome拡張機能(MV3) + FirebaseでGoogle認証する際のハマりポイント ハマりポイント① MV3からはリモートでホストされるコードは実行できなくなります これがMV3で作成する際の一番のハマりポイントだと思います。 Chrome拡張機能の開発について検索する際にまだまだMV2で作成したときの記事が多く、APIを叩くような拡張機能を作る際には記事通りに作っているので動かないということが多かったです。 下記はGoogle公式サイトからの引用です。 リモートでホストされるコード MV3 では、拡張機能が JavaScript や Wasm ファイルなどのリモート コードを読み込めないようになったことが、セキュリティ上の重要な改善点です。これにより、Chrome ウェブストアに送信される拡張機能の安全な動作を、より確実かつ効率的に審査できるようになりました。具体的には、すべてのロジックが拡張機能のパッケージに含まれている必要があります。 https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/#remotely-hosted-code リモートでホストされるコードとは具体的には下記を指すようです。 リモートでホストされるコードとは、拡張機能のパッケージにロード可能なリソースとして含まれていないコードを指します。たとえば、次の両方がリモートでホストされているコードと見なされます。 - リモートサーバーからプルされたJavaScriptファイル - 実行時にevalに渡されるコード文字列 https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#remotely-hosted-code こちらについては、Chrome拡張機能に必要なスクリプトは拡張機能に含めることで回避できます。 MV2のサポートタイムラインはこちらにあり、段階的にMV3に移行していくようですが、2023年6月には全てのMV2のChrome拡張機能が使えなくなるようです。 ハマりポイント② firebaseの例でよく紹介されているsignInWithPopup自体はローカルからメソッド呼び出しできるがさらにそこからGoogleのリモートコードを実行するため使えない Firebaseでの認証にGoogle認証を使う方法があるのですが、その中でsignInWithPopupを使う方法が紹介されています。 私の環境で動作するコードに書き換えていますが、下記のようなイメージです。 import { initializeApp } from 'firebase/app' import { getAuth, GoogleAuthProvider, signInWithPopup } from 'firebase/auth' const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_APIKEY, authDomain: import.meta.env.VITE_FIREBASE_AUTHDOMAIN, projectId: import.meta.env.VITE_FIREBASE_PROJECTID, storageBucket: import.meta.env.VITE_FIREBASE_STORAGEBUCKET, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGINGSENDERID, appId: import.meta.env.VITE_FIREBASE_APPID } initializeApp(firebaseConfig) const auth = getAuth() const provider = new GoogleAuthProvider() signInWithPopup(auth, provider) .then((result) => { console.log(result) }).catch((error) => { console.log(error) }) こちらを実行するとコンソールに下記エラーが表示されます。 Content Security Policy違反しているよ。外部のスクリプトを実行する場合はCSPヘッダに追加してねという感じです。 Refused to load the script 'https://apis.google.com/js/api.js?onload=__iframefcb717050' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback. manifest.jsonに下記のようにhttps://apis.google.com/js/api.jsを許可リストに含めてみます。 manifest.json # NG "content_security_policy": { "extension_pages": "script-src 'self' https://apis.google.com/js/api.js; object-src 'self'" }, すると、下記のように読み込み失敗するようになってしまいました。 manifest.jsonの書き方が間違っているのか確かめるために、https://apis.google.com/js/api.jsを削除した状態だと読み込むことができました。 manifest.json # OK "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'" }, CSPの読み込みが失敗する原因まではわかりませんでしたが、基本的にはリモートスクリプトはダメという方針なので、そもそもCSPで許可してあげても実行できないので意味がないです。 これらのことから分かるように、signInWithPopupは内部でhttps://apis.google.com/js/api.jsのスクリプトを呼び出しているため、MV3では使うことができません。 最終的には下記の記事を参考にchrome.identity.getAuthToken+signInWithCredentialを使う方法で回避しました。 Chrome拡張+FirebaseでGoogle認証するいくつかの方法 – rinoguchiブログ ハマりポイント③ FirebaseのAuthenticationでGoogleを有効にし用とした際に「Google の更新中にエラーが発生しました」と表示されて保存できない この問題は、Firebaseによってデフォルトで自動生成されたOAuthクライアントを削除することで発生するようです。 私は諦めて、再度Firebaseのプロジェクトを作成するところからやり直すことで解決しました… 参考記事紹介 kawamataryo/sync-raise-hand: Your real raised hand will be synchronized with the action of the raise button in Google Meet. Chrome拡張機能をVue.js+TypeScriptで作成されていて、ビルド環境を作る際などとても参考にさせて頂きました。人のコードを見るのは本当に学びが多く大変勉強させていただきました。 Getting started - Chrome Developers Google公式のChrome拡張機能を開発するための入門記事です。一通り触ることでmanifest.json、background.js、popup.htmlを使ってブラウザ右上の拡張機能をクリックしたときに表示されるUIを作ることができるようになります。 Content scripts - Chrome Developers 上記の入門記事ではコンテンツスクリプトについて触れられていないので、画面のDOMを操作したChrome拡張機能を作りたいという方はこちらも見ておきましょう。 Overview of Manifest V3 - Chrome Developers MV2で作られた記事が多いため、私のようにハマる前に公式を見てMV3に学びましょう。MV2からMV3に変わったことで、セキュリティが厳しくなったため、例えばChrome拡張機能からFirebaseなどのリモートサーバーのスクリプトは実行できなくなっているので注意です(実行するためのコードは全てChrome拡張機能側にダウンロードしておく必要があります)。 2017-07-11のJS: npm 5.2.0(npx)、lerna 2.0.0、WebRTC - JSer.info npxのコマンドについて詳しいです。 話題の爆速CLIツール「Vite」をVue.jsの定番ツール「Vue CLI」と徹底比較! (1/3):CodeZine(コードジン) Viteを使って作成した場合とVue CLIを使って作成した場合の比較など参考になりました。 まとめ 今回はビルド環境にViteを使ってみたり、初めてのChrome拡張機能開発で調査がしんどいうえにFirebaseのGoogle認証までやったのでハマりどころが多すぎてやりたいことができずに辛かったです… 今後、MV3でChrome拡張機能される方の少しでも参考になれば記事を頑張って書いてよかったなと思えるのでLGTM頂けると嬉しいですmm 現場からは以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

compositionAPIでyupとvee-validateを使ったバリデーションをする

はじめに yupとvee-validateを用いれば簡単にフォームのバリデーションを行うことができます。 ただvee-validateは非常に便利なバリデーションライブラリなのですが、compositionAPIを用いている場合の情報があまりなかったので記事にしてみました。 yupとは yupはJavaScriptでフォームのバリデーションルールを宣言的に記述することのできるライブラリです。 vee-validateとは vee-validateは「素早い」「柔軟」「実装が容易」といった多くの特徴を持つVue.jsのバリデーションライブラリです。 検証環境 vue 3.0.0 TypeScript 4.1.5 vee-validate 4.5.6 yup 0.32.11 プロジェクトの作成 vue-cliを用いてプロジェクトを作成します。 vue-cliのバージョンが4.5以上でないとvue3のセットアップができないのでアップグレードします。 yarn global upgrade --latest @vue/cli vue createコマンドでプロジェクトを作成し、vue3を選択します。 vue create <your-project-name> Vue CLI v4.5.15 ? Please pick a preset: Default (Vue 3) ([Vue 3] babel, eslint) 作成したプロジェクトのディレクトリに移動します。 cd <your-project-name> vue addコマンドでTypeScriptを導入します。質問には以下のように答えてください。 vue add typescript ? Use class-style component syntax? No ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes ? Convert all .js files to .ts? Yes ? Allow .js files to be compiled? No ? Skip type checking of all declaration files (recommended for apps)? Yes yarn serveでページにアクセスできたら準備完了です。 ライブラリのインストール yarn add vee-validate@next yarn add yup 上記コマンドを使用してyupとvee-validateをインストールします。 vee-validateは4系でないとVue3に対応していないので注意してください。 バリデーションの実装 App.vueを編集してフォームを作ります。 src/App.vue <template> <div class="container"> <form> <div class="input-field"> <input type="text" v-model="text" /> <p class="error-message">{{ textError }}</p> </div> <button type="submit">送信</button> </form> </div> </template> <script lang="ts"> import { defineComponent, ref } from "vue"; export default defineComponent({ setup() { const text = ref<string>(""); return { text, }; }, }); </script> <style scoped> .container { padding: 10px; width: 300px; } .input-field { margin-bottom: 16px; } .error-message { height: 16px; color: red; } </style> vee-validateとyupをimportしてバリデーションスキーマを作成します。 src/App.vue import { defineComponent } from "vue"; import { useField, useForm } from "vee-validate"; import * as yup from "yup"; export default defineComponent({ setup() { const formSchema = yup.object({ text: yup.string().required('テキストは必須項目です') }) useForm({ validationSchema: formSchema }) const { value: text, errorMessage: textError } = useField<string>("text"); return { text, textError, }; }, }); ここでフォームのスキーマを定義しています。こちらの記述はtextはstring型の必須項目という意味です。またrequiredの中に任意のテキストを指定し、バリデーションエラーメッセージを作成することもできます。このようにyupを用いるとバリデーション関数をとても簡単に作成することができます。 const formSchema = yup.object({ text: yup.string().required('テキストは必須項目です') }) そしてここで作成したスキーマをフォームに適用しています。 useForm({ validationSchema: formSchema }) 最後にuseField関数を用いてリアクティブな変数を定義しています。テキストフォームに入力された値がtextに代入され、バリデーションエラーが発生するとerrorMessageにエラーメッセージが代入されます。 const { value: text, errorMessage: textError } = useField<string>("text"); formタグにsubmitイベントを付ければバリデーションエラー発生時にデータを送信させないようにすることもできます。 src/App.vue <div class="container"> + <form @submit="handleSubmit"> <div class="input-field"> <input type="text" v-model="text" /> <p class="error-message">{{ textError }}</p> </div> <button type="submit">送信</button> </form> </div> src/App.vue + const handleSubmit = (e: Event) => { + if (text.value && !textError.value?.length) { + return true + } + text.value = ""; + e.preventDefault() + } return { text, textError, + handleSubmit, }; アカウントIDのバリデーション ここからはWEBアプリでよくあるフォームのバリデーション実装例を紹介します。 src/App.vue <div class="container"> <form @submit="handleSubmit"> <div class="input-field"> <input type="text" v-model="text" /> <p class="error-message">{{ textError }}</p> </div> + <div class="input-field"> + <div><p>アカウントID</p></div> + <input type="text" v-model="accountId" /> + <p class="error-message">{{ accountIdError }}</p> + </div> <button type="submit">送信</button> </form> </div> アカウントIDのフォームを追加します。 アカウントIDは下記のように要件が決まっています。 半角英数字をそれぞれ1種類以上含み8文字以上32文字以下であること このバリデーションを実現するにはmatchesを用います。 src/App.vue const formSchema = yup.object({ text: yup.string().required("テキストは必須項目です"), + accountId: yup + .string() + .matches( + /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,32}$/i, + "アカウントIDの形式が間違っています。" + ), }); このようにmatchesを用いることでフォームに入力された値が正規表現にマッチするかバリデーションすることができます。あとは残りのコードを記述します。 src/App.vue + const { value: accountId, errorMessage: accountIdError } = useField<string>("accountId"); const handleSubmit = (e: Event) => { if ( text.value && !textError.value?.length && + accountId.value && + !accountIdError.value?.length ) { return true; } text.value = ""; + accountId.value = ""; e.preventDefault(); }; return { text, textError, + accountId, + accountIdError, handleSubmit, }; パスワード一致のバリデーション 2つのフォームにパスワードを入力した時、パスワードが一致するかをバリデーションします。 まずは2つのフォームを追加します。なお今回は説明のためinputタグのtypeはtextで行います。(本来はpasswordにするべき) src/App.vue <div class="container"> <form @submit="handleSubmit"> <div class="input-field"> <input type="text" v-model="text" /> <p class="error-message">{{ textError }}</p> </div> <div class="input-field"> <div><p>アカウントID</p></div> <input type="text" v-model="accountId" /> <p class="error-message">{{ accountIdError }}</p> </div> + <div class="input-field"> + <div><p>パスワード</p></div> + <input type="text" v-model="password" /> + <p class="error-message">{{ passwordError }}</p> + </div> + <div class="input-field"> + <div><p>パスワード確認用</p></div> + <input type="text" v-model="passwordConfirm" /> + <p class="error-message">{{ passwordConfirmError }}</p> + </div> <button type="submit">送信</button> </form> </div> バリデーションスキーマは以下のように記述します。 src/App.vue const formSchema = yup.object({ text: yup.string().required("テキストは必須項目です"), accountId: yup .string() .matches( /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,32}$/i, "アカウントIDの形式が間違っています。" ), + password: yup.string().required("パスワードは必須項目です"), + passwordConfirm: yup + .string() + .required("パスワード確認用は必須項目です") + .oneOf([yup.ref("password")]), }); ポイントはoneOf([yup.ref("password")])oneOf([yup.ref("password")])の部分です。oneOfは引数として与えられた配列の文字列に完全一致しているかをバリデーションできます。ここにyup.ref("password")を与えることでpasswordとpasswordConfirmが一致するかをテストすることができます。残りのコードも記述していきます。 src/App.vue const { value: text, errorMessage: textError } = useField<string>("text"); const { value: accountId, errorMessage: accountIdError } =useField<string>("accountId"); + const { value: password, errorMessage: passwordError } =useField<string>("password"); + const { value: passwordConfirm, errorMessage: passwordConfirmError } =useField<string>("passwordConfirm"); const handleSubmit = (e: Event) => { if ( text.value && !textError.value?.length && accountId.value && !accountIdError.value?.length && + password.value && + !passwordError.value?.length && + passwordConfirm.value && + !passwordConfirmError.value?.length ) { return true; } text.value = ""; accountId.value = ""; + password.value = ""; + passwordConfirm.value = ""; e.preventDefault(); }; return { text, textError, accountId, accountIdError, + password, + passwordError, + passwordConfirm, + passwordConfirmError, handleSubmit, }; おわりに yupとvee-validateを組み合わせることで簡単にバリデーション機能を実装することができます!またcompositionAPIで記述することでより、見通しの良いコードが書けます。興味が湧いた方は是非試してみてください。 参考 https://vee-validate.logaretm.com/v4/ https://github.com/jquense/yup https://reffect.co.jp/vue/veevaliate4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vuex】ミューテーションの利用

はじめに こんにちは! 今回は【Vuex】ミューテーションの利用についてアウトプットしていきます! mutationsとは ミューテーション(mutations)とはVuexのストアの状態を変更したいときに使うものです。分かりやすくいうとハンドラに近いものです。 記述法 前記事の続きの内容でアウトプットしていきます。 store/index.js const createStore = () => { return new Vuex.Store({ //⏬stateに値を保管する処理 state: function() { return { message: 'Hello Vuex!' } }, mutations:{ updateMessage: function(state){ // ⏬state.messageを書き換える処理 state.message = 'Updated!' } } }) } pages/index.vue <template> <section class="container"> <div> <!--⏬storeフォルダのstateの値messageを利用することができる--> <p>{{ $store.state.message }}</p> <!--buttonをクリックすると指定したmutationsがstore側に送られ記述した処理が行われる--> <button v-on:click="$store.commit('updateMessage')">Update</button> </div> </section> </template> index.jsにstateに続きmutationsを作り、state.messageを書き換える処理を記述します。 index.vue(テンプレート側)ではbuttonをクリックすると指定したmutationsがstore側に送られ記述した処理が行われるといったように記述します。 stateで記述し<p>{{ $store.state.message }}</p>で表示させていた部分が、 このようにmutationsの処理内容に書き換えられています。 値渡し ミューテーションで値渡しする方法を記述します。 store/index.js mutations:{ updateMessage: function(state,payload){ state.message = payload } } pages/index.vue <!--⏬mutationsに値を渡すにはcommitメソッドに追加の引数を渡す。追加の引数は特定のmutationsに対する(paylod)と呼ぶ--> <button v-on:click="$store.commit('updateMessage','Commit with payload')">Update</button> ミューテーションで値を渡したいときは、commitメソッドの第2引数に渡したい値を入れ、mutations側の第2引数で値を受け取り、利用する記述を書くことで値を渡すことができます。 このように書きかわります。 最後に 今回はミューテーションの利用についてアウトプットしました。 今回の記事を読んで質問、間違い等あればコメント等頂けると幸いです。 今後ともQiitaにてアウトプットしていきます! 最後までご愛読ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js・TailwindCSS】条件によって動的にクラスを付与する方法

はじめに こんにちは。 こちらの記事では、動的にクラスを付与する方法を記しています。 誤っている点がございましたらコメントいただけると幸いです。 実装手順 バインドしたクラスbuttonColorに対して、computedを使用することで条件ごとにTailwindCSSのクラスを変更する実装を行います。 template <button v-bind:class="buttonColor"> 送信 </button> script export default { data: function () { return { formData: "" }; }, computed: { buttonColor: function () { return { "bg-gray-200": this.formData === "" , "bg-red-500": this.formData !== "", }; }, }, } フォームに何も入力されていないときはbg-gray-200を適用してボタンの背景色を変更する。フォームにデータが入力されている場合は、bg-red-500を適用する。 computedでは、データの参照にはthisを使用してreturnで値を返す。 参考 クラスとスタイルのバインディング computed で classを動的に指定 おわりに ここまで動的にクラスを付与する方法についてまとめました。 これからも転職活動と並行してアウトプットも継続しながら、技術力の向上に努めていきます! 以上、最後まで読んでいただきありがとうございました! よければLGTMを押してくれると嬉しいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactとVueとAngular、今どれを選択するか

ReactとVueとAngular、今どれを選ぶかを考えてみたい。ここでは内製でサービスを開発しているケースを考える。 ※かなり私見です。 前提として: Reactがかなり有力ではある 現状、一番人気が高いのはReactである。「これを使えば間違いない」みたいな言い方もよくされている。では、実際はどうか。 ReactはTypeScriptと相性が良いことが一番重要なポイントだと思う。TypeScriptは、誤解を恐れずに言えばJavaのような感覚で書けるJavaScriptだ。2016年くらいにあるプロジェクトで導入した時、いろいろなところから集められたメンバーのほとんどはJavaの経験はあってもJavaScriptを書いたことがなく、教育の時間もなかった。それでも意味のあるコードが生産され、進捗が生まれていた(良いやり方だとは言い難いが、ハードな状況はどうしても起こるものである)。素のJavaScriptで書いていたら、そもそもどうコードを構築すべきかから学んでいく必要があり、なかなか進捗も生まれなかっただろうと考えている。 このTypeScriptとReactが組み合わされることで何が可能になるかというと、 「普段はJavaなどを中心に書いているバックエンドエンジニアがUIも作ることができ、一人でサービス全体を作れる」 ということだ。これがサービス作りの観点からもっとも革命的なポイントだと思う。1人でサービス全体の構築ができ、ドッグフーディングしてもらえる状況に持ち込める。これによってプロトタイプ作りも機能の改善もこれまでより遥かに高速にできる。 日本の情報系の大学や学校では、CやJavaで教育をしていることが多いので、静的型言語で品質良くコードを書く能力を持つエンジニアは多い。その技能を活用できる。 Vueはどうか Vueはすごく簡単だと言われることがある。この簡単さがサービスづくりの観点で現実的な需要とどう対応するかというと、じつはReact + TypeScriptと似ていて、「バックエンドエンジニアがUIも作ることができ、一人でサービス全体を作れる」だと思う。 しかしTypeScriptのサポートが弱いことが課題である。実際、静的型言語に慣れたバックエンドエンジニアにとっては、ちょっとAPIが簡単なことよりも「あそこでは型が効くがここでは効かない」といった事がないほうが使いやすいだろう。Vueは一般的な簡単さを志向しているものの、多分バックエンドエンジニアにとってはReact + TypeScriptよりすこし難しい。1人でサービス全体を構築する力はやや劣るといえる。 VueでJSXを使うこともできるが、そのくらいならReactを使うべきだと考える。依存関係を少なくし一般的な方法で使う方がメンテナンスは簡単になる。 Angularはどうか Angularは最初からTypeScriptに対応しているが、そもそも手に入る情報が少ないのでバックエンドも同時に担当する場合簡単とは言い難い。一人でサービス全体を立ち上げられるほどの簡単さには至っていないと思う。ただ、Ionicを使うときは考慮に入るだろう。 では、Reactなのか? そうだとも言い難い。理由を説明するため、まず私が2016年にフロントエンドの技術選定を行った当時の、各々のフレームワークの状況を見てみたい。 フレームワーク 状況 AngularJS 開発終了が決まっていた。 Angular (2) 当時βだった。 Vue 当時Version 1.xであったが、私の確認した限り公式のサンプルでもブラウザによってうまく動いていないものが多かった。 React 特に技術的な問題はなく、非常に人気が高まっていた。 この状況では消去法で考えてもReactだったが、Reactは選ばなかった。 なぜかというと、当時のReactのライセンスは「BSD + PATENTS」という特許条項の含まれたものだったからだ。このライセンスは2017年にネット上で話題になり、ReactのライセンスがMITライセンスに変わる結果となった。この事件の詳細はこの記事を参考。 → スタートアップはReactを使うべきではない (BSD + patentsライセンスを考慮して) — もし、いつか大企業に買収されたいと望むなら これは大企業が提供するOSSのリスクを示す例だと思う。もちろん、今のFacebookが急にReactのライセンスを戻すとは思わない。しかし、 React以外のフレームワークが全部衰退して、React一強の状況になったらどうか? 事業が萎んでいき、どこかに買収されることになったらどうか? メタバース事業が大成功し、Web技術がさほど重要でなくなったらどうか? ということは考える。他にも以下のようなことを考える。 サーバーサイドレンダリングでReactが完全に含まれない静的ページが生成できるとしたら、ライセンス的な問題は完全になくなるか? GoogleのAngularはライセンスについてはリスクは低そうだが、AngularJSの互換性を捨ててAngular2を始めた判断は賛否が分かれる。 TypeScriptはMicrosoftだが、トランスパイラなのでライセンスで問題になることはなさそう? Reactのライセンスが変更になったとしたら、おそらくその時点からのフォークが開発されるだろうが、それがどのくらいの品質になりえるか? Next.jsはSvelteで使えるようになるのだろうか? などなど… 結論 歯切れの良い結論は出ない。 現時点では、やはり開発効率の観点からReactが第一選択となる。そのなかでもNext.js + TypeScriptは最初に検討される。ただ、できるだけシンプルで軽量にできないかの検討も行うだろう。フレームワークなしで書けるプロジェクトであれば、それも積極的な選択肢になる。 Vueは、大企業の提供するOSSのリスクを考えれば、Vueを推し・使い・研究するのは重要だと考える。実験的な・個人的なプロジェクトには積極的に使いたい。 Angularは、Ionicを使うときは選択肢になる。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む