vue-scrollto などの smooth scroll を利用する対象に、 nuxt-link を指定すると正しくスクロールしない

nuxt 利用時、URLに hash ( #hoge ) を設定すると同時に、スムーズにするロールする場合を行い場合、一般の方が書いた記事には、to をカラにすればいいと書いてありますが、正しく動作しない条件が存在します。

<div id="note"></div>
<nuxt-link to v-scroll-to="'#note'">noteクリック</nuxt-link>
<div id="memo"></div>
<nuxt-link to v-scroll-to="'#memo'">memoクリック</nuxt-link>

このように記載した上で、URLにハッシュを設定しブラウザをリロード。この状態で、nuxt-link をクリックすると、window.pageYOffset が 0 が入り、スクロール開始位置が間違っているため、とくにスクロール方向が逆になった時に違和感が大きくなってしまいます。フッターに # がついたリンクがあると特に発生しやすいはずです。

vue-scrollto や smooth-scroll が悪いわけではなく、
初回表示時にnuxt-linkをクリックすると、nuxt-link がまず pageYOffset = 0 にスクロール設定することが原因でした。
その後スクロールが開始するため、正し位置からスクロールしません。

対処するためには、以下のように、nuxt-link 代わりの component を作るのがよいのではないでしょうか。

<template lang="pug">
a(:href="to" @click.stop="onClick")
  slot
</template>

<script lang="typescript">
import {
  defineComponent,
  getCurrentInstance
} from '@nuxtjs/composition-api'
import TWEEN from '@tweenjs/tween.js'

type Props = {
  path: string,
  hash: string | null,
  anker: string,
}

export default defineComponent({
  props: {
    path: {
      required: true,
      type: String
    },
    hash: {
      type: String
    },
    anker: {
      required: true,
      type: String
    }
  },
  setup ({ path, hash, anker }: Props, { emit }) {
    const current = getCurrentInstance()

    const scrollTo = (anker: string) => {
      const dom = document.querySelector(anker)
      if (!dom) { return }
      const end = dom.getBoundingClientRect().top + window.pageYOffset

      // tween.js の例
      const data = { y: window.pageYOffset }
      new TWEEN.Tween(data)
        .to({ y: end }, 500)
        .easing(TWEEN.Easing.Exponential.InOut)
        .onUpdate(() => { window.scrollTo({ top: data.y }) })
        .start()

      const animate = (time: number) => {
        if (TWEEN.update(time)) {
          requestAnimationFrame(animate)
        }
      }
      animate(0)
    }
    const to = hash === undefined ? `${path}${anker}` : `${path}${hash}`
    const onClick = () => {
      if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
        // do nothing
      } else
      if (current?.$route.path === path) {
        e.preventDefault()
        window.history.replaceState(null, '', to)
        scrollTo(anker)
      } else {
        e.preventDefault()
        current?.$router.push(to)
      }
    }
    return {
      to,
      onClick
    }
  }
})
</script>

serverless framework で 日本語のPOSTされたデータを文字化けせず受け取る

serverless framework を使い、AWS Lambda や API Gateway を用いたシステムで、multipart/form-data 形式のデータを受け取る場合、日本語データのとき、文字化けが発生する。

元々AWS Lambda は、input / output に text を扱うようになっているので、バイナリデータとして送信される multipart/form-data に、何も設定しないままでは対応できない。

serverless framework を使うときは、まず serverless-apigw-binary を追加。

$ npm i serverless-apigw-binary

serverless.ts や serverless.yml に以下の項目を追加する事で、lambdaに渡すときにbase64エンコードされ、lambda上でアクセスするときには自動でデコードされる。

  custom: {
    apigwBinary: {
      types: [
        'multipart/form-data',
      ]
    },
  }
  // ...
  plugins: [
    'serverless-apigw-binary',
    // ...
  ],

Vue3 を利用するときに、どうやって this.$scrollmagic を取得するのか

Vue2 から Vue3 になり、出来ることは変わりませんが、
以前では root context にアクセスするためには、this という書き方でよかったものが、composition-api に変わった今後、どのように書けばよいのかを書きます。

例えば、this.$scrollmagic と書けていた、pluginsで設定された $scrollmagic などに、どうやってアクセスすればよいのか。

context の中の root から、取得することが出来ますが、こちらは deprecated になっているため、おすすめしません。

export default defineComponent({
  setup (_props, context) {

    // any にするのはおすすめしませんが、簡単に表現するため
    const scrolmagic = (context.root as any).$scrollmagic
  }
})

getCurrentInstance を使い取得する

import {
  defineComponent,
  getCurrentInstance
} from 'vue' 
// or from '@vue/composition-api'
// or from '@nuxtjs/composition-api'

export default defineComponent({
  setup () {
    const root = getCurrentInstance()
    const scrolmagic = (root as any).$scrollmagic
  }
})

AWS copilot でサーバー構築

AWS copilot は、webサーバー、バックグラウンドで常時起動するサーバー、定期ジョブ(Cronのような)が、かなり簡単なymlとコマンドだけでセットアップでき、オートスケールにも対応できます。GCPKubernetesに近いです。
コンテナ内でコマンドを実行し、DBへコマンド実行なども、task コマンドが用意されているため行えます。

$ copilot init
$ copilot env init
$ copilot svc init
$ copilot svc deploy

この程度のコマンドと、Dockerfile があれば、アクセスできるALBのURLが作成されます。

やっていることは、CloudFormation が実行され、リソースが自動で作成される、ビルド&デプロイが実行されるコマンドが用意されている というだけです。
追加で、RDSやCloudFront、Redisなど使いたいリソースを Terraform や手動で作成することになります。

他サービスと比較すると、

  • GCP の Cloud Run では、常時起動が想定されていなかったり、同時リクエストにも制限がある
  • Serverless Framework (AWS)では、常時起動ができない、システムに制限がある
  • AWSKubernetes では、ログの出力など所々全自動ではなく面倒
  • GCPKubernetes では、セットアップはほぼ自動だが、ビルド・デプロイはコマンド1つになっていない
  • GCP では、CloudFront ほどのカスタマイズ性、動作のわかりやすいCDNがないため、AWSを利用したい
  • AWS Elastic Beanstalk は、Amazon Linux2などOSが変わったときに、設定ファイル書き換えが必要など、OSやElastic Beanstalkの設定変更に依存してしまう

Serverless Framework などで、大抵のものは対応できますが、さらに一歩進んだサーバー構成が必要なときに、どれも手軽さがないと思っていましたが、ちょうどいいツールとなっていました。
Fargate やALBが起動し、特有の制限もなく使えるため、どんなサーバー構成が必要な場合も使えると思います。

しかし、Terraform などで AWSリソースの定義がすんでいて、ビルド、デプロイまでの環境が作れている場合は、特に利用する必要はないツールです。

最初に困ったこと

  • インストール方法の記載に、古いバージョンが指定されているものがいくつかあります。copilot 1.0.0 以前のバージョンは`copilot init`もろくに動かないため、インストールは1.0.0 以降を利用します。
  • `~/.aws/credentials` に profile を記載しただけでは認識されず、`~/.aws/config` に書かなければcopilotコマンドに認識されません。

今後対応されそうだが困っていること

デプロイ後のヘルスチェックに失敗すると、その後デプロイができなくなる。

https://github.com/aws/copilot-cli/issues/1180
デプロイコマンドがなかなか終わらなかったら、手動でタスク定義を修正し、前のリビジョンを指定する、または必要なタスク数を0に(Webサーバーの場合は当然アクセスできなくなる)すればば再度デプロイできる。

docker build コマンドのARGが、環境ごとに切り替えられない

https://github.com/aws/copilot-cli/pull/1059

M1 Mac で deploy ができない

arm64 でimageをビルドすると、サービスが起動できないようです。

必要最小限なIAM Policy の定義をドキュメントに書いてほしい

調査が面倒で、ALLアクセスにする人が多くなってしまうので、教えてほしい。

デプロイ時に保持されるリビジョンが2つのため、ロールバックできなくなってしまう

そもそもロールバックのコマンドはまだないが、手動でリビジョンを戻せばロールバックできる。しかし、リビジョンが2つしか保持されないため、2回デプロイに失敗すると戻せなくなってしまう。

なんとかなるが困っていること

  • コンテナが書き出すログの、datetime_format を指定できないため、cloudwatch logsに、誤った時間で取り込まれてしまう
  • コンテナ起動時の command がyamlで指定できない

気になった点

CloudFormation を使いたくない

CloudFormationは、内部エラーが起きると、復旧には copilot だけではどうにもならなくなるため、使いたくない気持ちになりました。ただ、AWSのツールなので、AWSのサービスを使わなければいけないはずで、そこが問題の1つかもしれません。

pipineという、CodeBuildとCodePipeline を利用した CI/CD が存在するが、CircleCI や Github Actions を使いたい

こちらもAWSのツールのため自社サービスを使わなければいけないという問題だと思います。
Github Actions で独自にビルド、デプロイを用意するのはとても簡単なので、利用されないかもしれません。

追加リソースを定義するアドオン

S3 をアプリからストレージとして使うことが多いため、S3などを定義し、さらにRoleを設定するアドオンがコマンドで追加できます。
今後も定義できるリソースは増えそうで、便利ではありますが、設定ファイルにCloudFormationの定義ファイルが大量に取り込まれ、見苦しいです。Terraformを使って定義するので、特にいらないと思いました。

GCPKubernetesぐらいの手軽さ+デプロイも楽というツールだと思いました。
Kubernetesをまだ理解していない人には、より簡単に感じる可能性もあります。

同じコンセプトで、AWSに依存しすぎないないツールが生まれた場合には、使われなくなってしまう気がしますが、現状は存在しないので、このツールを積極的に使っていきたいと思っています。

CircleCI や Github Actions などで、 firebase deploy を行う

firebase deploy には、 token を利用した方法と、サービスアカウントを利用した方法がある。

token を利用する記事は多くあるが、問題点があり、特定ユーザーの権限が全て circleci に渡ってしまうため、別プロジェクトも操作できてしまう事と、ユーザーをプロジェクトから外したときに、デプロイができなくなってしまう。

なので、こちらにあるような、`firebase login:ci` でtokenを取得する方法はおすすめしない。

Firebase CLI リファレンス

サービスアカウントを利用したデプロイ

サービスアカウントの作成

これはとても簡単に作成できる。
歯車 > ユーザーと権限 > サービスアカウント > [新しい秘密鍵の生成]
これでjsonファイルが取得できる。

こちらはFirebaseの操作が何でもできるアカウントなので、ここからさらに権限を減らしたサービスアカウントを作る場合もある。

json ファイルを使ったデプロイ

まずはパソコン上でデプロイを試すと思うが、
`GOOGLE_APPLICATION_CREDENTIALS ` この環境に、json ファイルのパスを設定する。
project id は、歯車 > プロジェクトを設定 から確認できる。
あとは、自動で環境変数が読まれ、デプロイすることができる。

export GOOGLE_APPLICATION_CREDENTIALS=******/***.json
firebase deploy --project <project id>

注意点として、すでに firebase login をしている場合は、そちらが優先されてしまうため、かならず firebase logout でログアウトを行う。

デプロイの確認方法

サービスアカウントでデプロイできているかを確認するには、
Hosting > リリース履歴 を確認し、`firebase-adminsdk-****` と書いてあれば、正しくサービスアカウントでデプロイできている。
ユーザーのメールアドレスの場合は、ユーザーアカウントでデプロイしたことになっている。

vue.js や react を使用したとき、formrun のバリデーションが機能しない

オリジナルデザインのformを作るときに利用するサービスに formrun というサービスがありますが、
React や Vue.js を使うと、ビルドをすれば動くが、npm start した local 実行の時や、ページ遷移後に、form の validation が動かない問題があります。

動かない原因は、form の dom が存在するタイミングで、formrunの初期化処理が実行されなければいけないようです。

formrun に問い合わせたところ、初期化処理を実行するメソッドがあるようです。

// .formrun の部分は、formのclassを指定する
Formrun.init('.formrun')

つまり、`nuxt generate` (SSG) されたサイトで、ページ遷移時を直す場合は、React ならこう書きます。

  // ...
  useEffect(()=>{
    // ページ遷移時はこちらで解決する
    window.Formrun?.init('.formrun')
  }, [])
  // ...

  return (
    <>
      <Helmet>
        <script src="https://sdk.form.run/js/v2/formrun.js"></script>
      </Helmet>
      { // .... }
    </>)


また、ローカル開発時や、SPAの時は、スクリプトのロード後に再度初期化をする必要がありました。SSGの場合もこちらで動きます。

import Script from 'react-load-script' // 動的な script の読み込みを楽にする npm
// ...

  const onLoadFormrun = useCallback(() => {
    window.Formrun?.init('.formrun')
  }, [])

  // ...

  return (
    <>
      <Script onLoad={onLoadFormrun} url="https://sdk.form.run/js/v2/formrun.js"></Script>
      { // ... }
    </>)

NTT Com の Cloudn がサービス終了と言うことなので、CloudObjectのデータを移行する。

S3互換APIの場合、Amazon S3AWS署名バーション2を利用しているサービスが多いようだ。
awscli を使いたい場合、古いバージョンをインストールする必要がある。

awscli 1.11.107 のインストール

pip install
$ curl -O https://bootstrap.pypa.io/get-pip.py
$ get-pip.py --user
local に install

pipenv をインストールし、ディレクトリごとに環境を変える。

$ pip install pipenv --user

pipenv 有効化

$ pipenv shell

古いバージョンインストール

$ pipenv install awscli==1.11.107
$ aws --version
動作確認
$ aws --endpoint-url https://str.cloudn-service.com/ s3 ls