yuw27b’s blog

技術メモと雑記

第2世代のCloud Functionsで公開APIを作る

基本的にはただfunctionを作れば良いが、いったん非公開(未認証の呼び出しを許可しない)設定で作ってしまうと、Functionsの編集画面からは直せない。

作成画面:

これを後から「未認証の呼び出しを許可」に変えるには、functionの詳細画面右上の「Powered by Cloud Run」のリンクからCloud Runのページへ行き、セキュリティタブで設定変更する必要がある。


第2世代のCloud FunctionsはCloud Run上で動いているので、Cloud Runの一覧ページにもfunctionが存在するし、相互にリンクが置いてある。そちらからもログの確認や設定変更ができるし、どちらかにしかない設定項目もある。

Cloud Runのページでは編集はコンテナ単位になってしまうので、コードだけ編集したい場合はCloud Functionsのページから行う。

Astroでトップページを最新記事にしたい

トップページは常に最新記事にしたい、つまりビルドするごとに内容が変わる(かも)という状態にしたい。 WordPressなんかでトップページを「最新の投稿」にする、みたいな設定がある思うんですが、ああいうイメージです。

Routing -> Rest parameters -> undefined でできる

結論からすると、[...slug].astroで、pathundefinedを指定するとトップレベルのindex.htmlになりました。痒いところに手が届いてて助かる……。

ドキュメント:Routing 🚀 Astro Documentation

index.astroに書けば…?

pages/index.astro に最新記事を取得して出力するコードを書いてももちろんできるんですが、個別記事を出力するファイル(つまりpages/posts/[slug].astroみたいなやつ)とほぼ同じことを書くことになるので、1つのファイルで済ませたいな、というのが前提でした。

もうちょっと具体的に

  1. pages/[...slug].astroを用意する
  2. getStaticPaths()関数に、記事を取得してきて配列を返すコードを書く
  3. 配列の最後に、最新記事をコピーしてparams.pathundefinedに変更した要素を入れておく

こんな感じ

const paths = posts.map(post => {
  //...なんか記事データの処理
  return {
    params: {path: `posts/${post.id}`},
    props: {
      //
    }
  }
})

const lastItem = Object.assign({params: {}, props: {}}, paths[paths.length - 1])
lastItem.params = {path: undefined}
paths.push(lastItem)

return paths

こうすると、pathundefinedな要素は親階層のパスになるので、/dist/index.htmlに書き出される。

JavaScriptでクリップボードにコピー(2022年版)

2017年に書いた記事が、2022年現在では非推奨になっているAPIを使っているので、改めて。

変わったところ

document.execCommand('copy')が仕様からなくなり、非推奨になりました(MDN)。今のところどのブラウザでも動作しますが、今後も動く保証はありません(とはいえ後方互換のために当面は動くと思いますが)。代わりにClipboard APIという仕様が整備されているので、新たに実装する場合はこちらを使うべきです。

HTMLとJavaScriptのコード
<p class="copyTarget">コピーしてもらいたいテキスト</p>
<button class="copyBtn">クリップボードにコピーする</button>
function copyText () {
  const $target = document.querySelector('.copyTarget');
  if (!$target) {
    return false;
  }
  const range = document.createRange();
  range.selectNode($target);
  window.getSelection().removeAllRanges();
  window.getSelection().addRange(range);

  // document.execCommand('copy'); // ←非推奨に。
  navigator.clipboard.writeText($target.innerText);

  return false;
}
document.querySelector('.copyBtn').addEventListener('click', copyText, false);
テキストエリア内のコンテンツをコピーする場合
<textarea class="textarea">テキストエリアのテキスト</textarea>
<button class="copyBtn">クリップボードにコピーする</button>
function copyText () {
  const $target = document.querySelector('.textarea');
  if (!$target) {
    return false;
  }

  $target.select();
  navigator.clipboard.writeText($target.value);

  return false;
}
document.querySelector('.copyBtn').addEventListener('click', copyText, false);
クリップボードからコピーする
<textarea class="textarea">テキストエリアのテキスト</textarea>
<button class="copyBtn">クリップボードからコピーする</button>
function copyText () {
  const $target = document.querySelector('.textarea');
  if (!$target) {
    return false;
  }

  navigator.clipboard.readText().then(clipText => {
    $target.value = clipText;
  });

  return false;
}
document.querySelector('.copyBtn').addEventListener('click', copyText, false);

ユーザーの許可を求める表示が出ますが、許可するとクリップボードにあるテキストがテキストエリアの内容に置き換わります。

ElectronでPuppeteerを使う場合の複数プラットフォーム対応

Electronで作っているデスクトップアプリ内で、Puppeteerを使った時のメモです。
結果的には、各プラットフォーム用のChromiumを手動でダウンロードして追加し、Puppeteerに対してChromiumのパスを明示的に指定する必要がありました。


npm install puppeteerして開発しているうちは問題なかったのですが、開発環境と異なるプラットフォーム用にElectronをビルドして、単体のアプリケーションとして実行するとうまくいきませんでした。

Chromium revision is not downloaded. Run "npm install" or "yarn install" at Launcher.launch

というようなエラーが出ます。Chromiumが入ってないよ、とのこと。npm installしろ、と言われても、ビルドしてしまっているのでできません。

ビルドするプラットフォーム向けのChromiumを手動で追加する

Electronをビルドし、macOS版のアプリケーションのパッケージ内容を確認すると、/Contents/Resources/app.asar.unpacked/node_modules/puppeteer以下に、.local-chromiumというディレクトリがあり、さらにその中にmac-722234というディレクトリがありました。(6ケタの数字はバージョンのようなので、環境により異なると思います。)この中にChromium本体が入っています。

私の場合はMacで開発していて、Windows用にもビルドしましたが、win-unpacked/resources/app.asar.unpacked/node_modules/puppeteer/.local-chromiumの中にはmac-722234ディレクトリしかありません。そこで、ここにwin64-722234というディレクトリを作成し、Windows用のChromiumをダウンロードしてきて入れました。ディレクトリ構造としては、win64-722234/chrome-winの中にchrome.exeなどのファイルが入っている状態です。
Chromiumのダウンロードはこちらからできます:https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html

続いて、Puppeteerの起動時にこのChromiumを見つけられるようパスを指定します。GitHubにそのままのissueがありましたので参考にしました:Run from Electron · Issue #2134 · puppeteer/puppeteer · GitHub

これでMacでもWindowsでも問題なく、単体のアプリケーション上でPuppeteerが実行できるようになりました。

また、エラーの原因を調べるにあたっては、electron-logというnpmパッケージが便利でした。こちらが詳しいです:
[Electron] ログをファイルに記録する - electron-log




最後に蛇足ながら、

  • Puppeteerは外部サイトにアクセスできるので、使い方によっては何かしらの脆弱性が生まれるかもしれません。起動時のオプションや、アクセス先の限定など、適宜設定するべきと思います。
  • Electronで1つ、Puppeteerで2つChromiumを内包したアプリケーションになるので、なんというか富豪的ではありますね…。

Web上のExcelファイルからデータを読み取る(Node.js)

備忘録。

const request = require('request');
const Excel = require('exceljs');

const getBuffer = (url) => {
  return new Promise((resolve, reject) => {
    request(url, {encoding: null}, (error, response, body) => {
      if (error !== null) {
        reject(null);
      }
      const buffer = new Buffer.from(body);
      resolve(buffer);
    });
  });
}

const buffer = await getBuffer('https://...');
if (buffer === null) {
  console.log('Couldn\'t get the file');
  return;
}

const excel = new Excel.Workbook();
await excel.xlsx.load(buffer);

const sheet = excel.worksheets[0];
const row = sheet.getRow(1);
const cellValue = row.getCell('C').value;
//...

exceljsというnpmを使った。ライセンスはMIT。
HTTP(S)リクエストのところで、{encoding: null}を入れておかないとExcelファイルとして読み込めずにエラーになる。

excel.xlsx.load()のところでawaitしてるので、asyncな関数内で使ってください。