yuw27b’s blog

技術メモと雑記

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な関数内で使ってください。

CSS: overscroll-behavior

端までスクロールした時の振る舞いを、CSSで指定できる、というものです。

仕様:CSS Overscroll Behavior Module Level 1
対応状況:Can I use... Support tables for HTML5, CSS3, etc
MDNが分かりやすいです:
overscroll-behavior - CSS: カスケーディングスタイルシート | MDN

仕様としてはまだ草案段階で、Safariは対応していません。
ですが、「適切な場面で指定しておけばWebページが使いやすくなるが、なくても問題が起こるわけではない」タイプの機能なので、いわゆる"プログレッシブエンハンスメント"なアプローチとして採用するのは有効だと思います。

スクロールバーが入れ子になった状態で使う

MDNでは、縦に長いWebページ内に、小さなチャットウィンドウが乗っかっている例を取り上げています。ほとんどのブラウザでは、チャットを最後までスクロールしたら背後のメインコンテンツがスクロールし始めますが、overscroll-behaviorにnoneを指定すると、これを止める(スクロールを伝播させない)ことができます。

デモ:


See the Pen
vYXKWJg
by Yu Watanabe (@yuw27b)
on CodePen.

「overscroll-behavior: none」のボタンを押してから、水色のボックス内をスクロールしてみてください。端まで行っても後ろのメインコンテンツはスクロールしないはずです。

「none」は、ブラウザ既定の振る舞いも止まる

overscroll-behaviorにnoneを指定すると、上記のように外側の要素へのスクロール伝播が止まりますが、同時にスクロールが端に達した時のブラウザ既定の振る舞いが全て止まっています。例えばChromeでは上端までスクロールすると、跳ね返るようなアニメーションをしますが、body要素に{overscroll-behavior: none}を指定すると、これがなくなって、ピタッと止まります(この感覚久しぶりです)。
ブラウザ既定の振る舞いはさせたいけど、スクロール伝播は止めたい、という場合はoverscroll-behavior: containを指定すればOKです。

スマートフォンでは、「上端までスクロールしてさらに引っ張る」と、ページの再読み込みが行われますが、AndroidChromeではこの振る舞いも止まるようです(未検証)。iOSChromeは未対応のようで、いつも通り再読み込みが行われてしまいました…。

x,y 個別指定もできます

「overscroll-behavior-x」「overscroll-behavior-y」というプロパティもあります。


「背後のコンテンツまでスクロールしないほうがいいな」という状況は時々あるので、そういう時は「overscroll-behavior」が有効に使えそうです。ですが、ユーザーが期待している「いつもの動作」を止めることもできてしまうので、あまり濫用しないほうが良さそうとも思いました。

CSS:画面にぴったり収まる碁盤の目を作りたい

こういうものを作りたい:

  • 画面にぴったり収まってほしい(background-sizeのcontainみたいな)
  • 画面が横長なら縦の余白がゼロ、縦長なら横の余白がゼロ
  • 画面リサイズに応じてこちらもサイズが変わる

作ったもの:


See the Pen
Square grid
by Yu Watanabe (@yuw27b)
on CodePen.



まず碁盤の目を作る

とりあえず200px四方の3×3マスで。

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
<!--省略-->
  <div class="item">9</div>
</div>
.container {
  display: grid;
  grid-template-columns: repeat(3, 200px);
  grid-template-rows: repeat(3, 200px);
  align-content: center;
  justify-content: center;
}
.item {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}
.item:nth-child(odd) {
  background-color: pink;
}
.item:nth-child(even) {
  background-color: seagreen;
}

gridを画面いっぱいにする

.container {
  display: grid;
  grid-template-columns: repeat(3, min(calc(100vh/3), calc(100vw/3)));
  grid-template-rows: repeat(3, min(calc(100vh/3), calc(100vw/3)));
  align-content: center;
  justify-content: center;
}

3重括弧で読みにくいですが、画面幅の1/3と画面高さの1/3のうち、短いほうをマスの辺の長さにしています。
これで画面をリサイズしても、縦長でも横長でも、画面に収まる最大サイズの正方形になります。

マスの数をHTML側に書いておきたい
<div class="container" style="{--items: 5}">
  <div class="item">1</div>
  <div class="item">2</div>
<!--略-->
  <div class="item">25</div>
</div>
.container {
  display: grid;
  grid-template-columns: repeat(var(--items), min(calc(100vh/var(--items)), calc(100vw/var(--items))));
  grid-template-rows: repeat(var(--items), min(calc(100vh/var(--items)), calc(100vw/var(--items))));
  align-content: center;
  justify-content: center;
}

数が変わってもHTMLだけ変更すればOK。
CSSの変数はIE以外であれば問題なく使えます:Can I use... Support tables for HTML5, CSS3, etc

Sassで書く場合
.container {
  display: grid;
  grid-template-columns: repeat(var(--items), unquote("min(calc(100vh/var(--items)), calc(100vw/var(--items)))"));
  grid-template-rows: repeat(var(--items), unquote("min(calc(100vh/var(--items)), calc(100vw/var(--items)))"));
  align-content: center;
  justify-content: center;
}

そのままでは、min()を、CSSの関数ではなくSassの関数と認識してしまうため、unquote("")で囲みます。



数年前だったら、JavaScriptでウィンドウのリサイズイベントを監視して、マス目のサイズを変更していたところですが、CSSに変数や比較関数などが実装されて、CSSのみでやりたいことができてしまいました。
括弧だらけでコードが読みにくいので、適宜コメントなども入れておくと良さそうです。

MySQL:外部キー制約のあるカラムを削除する

いつもカラムが消せない!って調べることになるのでメモ。

FOREIGN KEYが設定されたカラムを削除しようとするとエラーが出る。

ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails

まずFOREIGN KEY を削除する必要があるが、削除するためにはそのキーのシンボル値を知る必要がある。

シンボル値の確認

SHOW CREATE TABLE table_name

レスポンス:

CONSTRAINT `table_name_ibfk_1` FOREIGN KEY (column_name)...

FOREIGN KEY削除

ALTER TABLE table_name DROP FOREIGN KEY table_name_ibfk_1

カラム削除

ALTER TABLE table_name DROP COLUMN yyyy