GAミント至上主義

Web Monomaniacal Developer.

PWAのGoogle Play登録がもうすぐできそう

Google Map GoはPWAなのにPlayに登録できてて、上のアドレスバーも表示されずに使えて、Googleずるいなぁと思っていたら、Trusted Web Activityというものを使えば、Android アプリから直でPWAを実行できるようになるらしい。

アプリ側、WEB側で鍵を持つことで、読み込んでいるURLが間違いないことを確認するため、Custom Tubsで消せなかった上のバーも非表示にできる模様。

詳しくはアップされたてのこの動画。
www.youtube.com

まだChromeの開発版が必要らしいが、2018年の夏には通常のChromeでも使えるようになる予定らしい。

社内にAndroidエンジニアがいるので、Android側は任せて、こちらは認証用のJSONを作って指定されたディレクトリにアップするだけだった。

JSONは下記のジェネレータで作って渡して、翌日には実機で動いているのを見せてもらった。

Statement List Generator and Tester  |  Google Digital Asset Links  |  Google Developers

Appleは遅そうだけど、これでPWAの欠点が一つ減りそう。

HeadlessChromeを使ったPDF変換サーバーにサイズ変更オプションを追加した

いまいち名前が気に入っていないHeadlessChrome、Express、Puppeteerを使ったPDF変換サーバーのhcep-pdf-server
github.com

今まではA4余白なしのみで、CSSなんとかすればいいと思ってたけど、中身が長くなり改ページされるとそのページの間が余白なしになってしまい、印刷の時に問題があるのでデフォルトは余白ありにした。

  const pdfOptions = {
    'A3': new PdfOption({ 'format': 'A3' }),
    'A3Full': new PdfOption({ 'format': 'A3', margin: '0mm' }),
    'A3Landscape': new PdfOption({ 'format': 'A3', landscape: true, margin: '0mm' }),
    'A3LandscapeFull': new PdfOption({ 'format': 'A3', landscape: true }),
    'A4': new PdfOption({ 'format': 'A4' }),
    'A4Full': new PdfOption({ 'format': 'A4', margin: '0mm' }),
    'A4Landscape': new PdfOption({ 'format': 'A4', landscape: true }),
    'A4LandscapeFull': new PdfOption({ 'format': 'A4', landscape: true, margin: '0mm' })
  }

とりあえずA4,A3の余白ありなし、縦横だけ用意して、リクエスト時にpdf_optionパラメータでkeyを渡せば変更できるようになった。

今回はじめてClassとconstructorを使ってみた。

そのほかのオプションは公式参照
puppeteer/api.md at master · GoogleChrome/puppeteer · GitHub


A0~A6まではデフォルトでformatで用意されているけど、薄い本を中心に日本でまだ出番がありそうなBはないので、width、heightで自分で指定するしかなさそう。

vue-router + Firebaseで404ページをちゃんとやる方法を考える

Vue.js も SPAも Firebaseも初めてなので、WEB Frameworkでやるような404をどうすればできるか考えてやってみたメモ。

Vue.jsは下記のPWAのテンプレートを利用し、vue routerも使っている
github.com

routerで指定していないパスで404を表示

これは簡単だった。
routesの最後にキャッチオール的に404、NotFound用コンポーネントを指定して表示させる
stackoverflow.com

const ROUTER_INSTANCE = new VueRouter({
    mode: "history",
    routes: [
        { path: "/", component: HomeComponent },
        // ... other routes ...
        // and finally the default route, when none of the above matches:
        { path: "*", component: PageNotFound }
    ]
})

しかし、これだけだとパスにID等を含めた動的ルートマッチングの場合はできない。

動的ルートマッチングで404を表示する

router.vuejs.org

例えば今回だと

 path: '/format/:id/edit/:cardId',

のようなpathがあり、format idと、card idと二つの変数が含まれていた。

idと
cardIdは、別のJSONファイルに含まれており、コードでは表現できない。

最初表示した先のコンポーネントのmountedでどうにかしようと思ったけど、タイミング等によって出るエラー対処が面倒くさすぎてrouter側で事前にやってしまうのがいいと実感。

そのためにはナビゲーションガードのルート単位ガード、beforeEnterを使用する。

router.vuejs.org
router.vuejs.org

今回はformatExistsOr404 という名前で関数を準備した

# コンテンツが入ったJSONファイル
import formatsData from '@/assets/formats/index'

const SITE_TITLE = 'サイト名'
const NOT_FOUND_PATH = '/404'

const formatExistsOr404 = function (to, from, next) {
  if (!to.params.id) {
    next({name:'notFound'})
    return
  }
  const id = to.params.id
  let formatResult = formatsData.filter(
    function (format) {
      if (String(format.id) === String(id)) {
        return true
      }
    }, this
  )
  if (!formatResult.length) {
    next({name: 'notFound'})
    return
  }
  if (!to.params.cardId) {
    next()
    return
  }
  const cardId = to.params.cardId
  let cardResult = formatResult[0].cards.filter(
    function (card) {
      if (String(card.id) === String(cardId)) {
        return true
      }
    }, this
  )
  if (!cardResult.length) {
    next({name: 'notFound'})
    return
  }
  next()
}

const router = new Router({
  mode: 'history',
  routes: [
  {
      path: '/format/:id/edit/:cardId',
      component: formatEdit,
      props: true,
      name: 'formatEditCard',
      beforeEnter: formatExistsOr404,
      meta: {
        title: '書式編集 - ' + SITE_TITLE
      }
    },
// 省略

まだ書き慣れてなくて汚いけど、toのparamsに含まれる変数を見て、コンテンツがあったらnext()し、無かったらnext({name: 'notFound'})で404ページに飛ばす処理を書けばいい。

今回はfilterでやったけど、Firestoreに入れている場合など状況によって書き換える必要があると思う。

これで存在しないidが指定されたときは404のURLに変わり、専用のコンポーネントが表示されるようになった。

Firebase HostingにWebpackでつくった404.htmlデプロイ

上記のPWAテンプレートではindex.html以外のHTMLファイルがなく、手で404.htmlファイルを作っただけではnpm run buildでdist/には書き出されない。
また置いたとしてもビルドのたびに消されてしまうので、設定が必要になる。

app\build\webpack.prod.conf.js

  plugins: [
    // 省略

    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: process.env.NODE_ENV === 'testing' ?
        'index.html' : config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency',
      serviceWorkerLoader: `<script>${loadMinified(path.join(__dirname,
        './service-worker-prod.js'))}</script>`
    }),
    /* 404 ↓ここ追加 */
    new HtmlWebpackPlugin({
      filename: "404.html",
      template: "404.html",
      inject: false,
    }),

こんな感じでシンプルにappディレクトリ直下の404.htmlを追加しただけ。
404はエラー時などhtml単体で動くよう、VueやCSSを使用しないのでinject: falseとした。

他の設定はreadme.mdにある
github.com

これでbuildするとdist以下に書き出されるので、Firebase Hostingにデプロイ( firebase deploy )する。

そうすると/404.htmlで表示出来て、ステータスコードも404になる。

Vue側の404用ページコンポーネント、Firebase Hostingで404.htmlと、404用のファイルが二つあるのが若干気持ち悪いけど、FHで404.htmlは必須だし、vue-routerでそれを読み込ませるのは難しいのであきらめた。

Service Workerで404ページをprecacheするのを除外する

PWAのデフォ設定だと、すべての.htmlをキャッシュしようとするが、404.htmlは当然404を返すのでエラーになってしまう。

chromeのconsoleだと下記のようなエラーがでる

service-worker.js:1 Uncaught (in promise) Error: Request for https://{project_id}.firebaseapp.com/404.html?_sw-precache=99120a382671cea98d8e49ff29caea44 returned a response with status 404
    at service-worker.js:1
(anonymous) @ service-worker.js:1

そのため404がパスに含まれる場合、precacheの対象から外すことにした。
SWPrecacheWebpackPluginの設定を追加する

app\build\webpack.prod.conf.js

// 省略
    // service worker caching
    new SWPrecacheWebpackPlugin({
      cacheId: 'shanyang20180531-03',
      filename: 'service-worker.js',
      staticFileGlobs: ['dist/index.html', 'dist/**/*.{js,css,png,jpg,svg}'],
      minify: true,
      stripPrefix: 'dist/'
    })]
  ]
})

www.npmjs.com
最初staticFileGlobsIgnorePatternsに404.htmlを渡したが、いろいろ試しても除外されないのであきらめてstaticFileGlobsでindex.htmlだけ指定した。

実際に消えているかは書き出されたdist/service-worker.jsでファイル名検索かければわかる。

htmlが他にもある場合は正規表現の調整が必要なると思うけど今回はとりあえず除外できたのでおk。

/404へのサーバーリクエストは404.htmlにリダイレクト

今回、vue-routerではなく直接サーバーに/404をリクエストした場合、Vueで受けてしまうとindex.htmlを返し、ステータスコードは200になってしまう。
そのためHistory APIではなく、サーバーリクエストの場合、Hosting側の404.htmlにリダイレクトさせてしまうことにした。


これはfirebase.jsonで指定する

{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "redirects": [{
      "source": "/404",
      "destination": "/404.html",
      "type": 301
    }],
    "rewrites": [{
      "source": "**",
      "destination": "/index.html"
    }]
  }
}

今のところ404でやってみたのはこれぐらい。

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js