at backyard

Color my life with the chaos of trouble.

DenoのプロジェクトでGitHub Actionsを使う

DenoのプロジェクトでGitHub Actionsを使うために設定ファイルを書いてみたので、備忘録として残しておきます。

deno fmtdeno testGitHub Actionsで行うための設定ファイルです。

name: Deno CI

on:
  push:
  pull_request:

jobs:
  build:
    name: Test app

    runs-on: ubuntu-latest

    steps:
     - name: Checkout repo
       uses: actions/checkout@v2

     - name: Setup Deno
       uses: denoland/setup-deno@v1
       with:
         deno-version: v1.x
     - name: Check format
       run: deno fmt --check

     - name: Run tests
       run: deno test

同じような内容で、実際に動いているものは下記のリポジトリに上げています。

記述されているDenoプロジェクトのコード自体はなんてことのないFizz Buzzとなります。
GitHub Actions を実際に動かしてみたくて作成しました。

github.com

Denoについては以前Deno Deployについてもポストしていますが、こちらももうちょっと使っていきたいと思っているこの頃。
今朝起きたらGitHubアカウントに対する権限の更新が来ていましたが、そちらについても下記のポストで追記しています。

shinshin86.hateblo.jp

iCloudの写真と動画のバックアップを停止して、iCloudの容量を確保するために調べたこと

子供が生まれてからというもの、写真や動画を取る頻度が大幅に増え、iCloudのバックアップ容量は既にいっぱいいっぱいになっている。

容量が足らなくてバックアップに失敗しました的な通知が常にiPhoneに表示されていたはずだが、いつの間にかそんな通知も出なくなっている秋深まるこの頃(通知を切ったのだろうか?)、そろそろiCloudに写真のバックアップを取るのをやめることを検討している。

ただ、iCloudのバックアップを停止させようとすると、こんなメッセージが出てきてちょっと怖気づいたので、今回はiCloudの写真バックアップを停止した際の影響(要は写真はiPhone上には残るのかどうか?)について調べてみた。

iCloudの画像や動画のバックアップを停止させるとiPhone上の画像や動画が削除される?

f:id:shinshin86:20211011222344p:plain

"iCloud写真"のコピーをこのiPhoneにダウンロードしますか? 空き領域を確保するために最適化された写真とビデオはこのiPhoneから削除されます。オリジナルの完全版は、"iCloud写真"を使用しているデバイス上では引き続き利用できます。

こんなメッセージが出てきて、気軽にバックアップを停止する気も失せたのだが、そもそも最適化された写真やビデオとはなんのことだろうか?と思い調べてみた。

「ストレージを最適化」というデバイスの容量を節約できる機能がある

support.apple.com

上のドキュメント内の デバイスの容量が足りなくなった場合 を読んだところ、「ストレージを最適化」という機能を有効にすると、iCloud 写真がデバイス上のライブラリのサイズを自動的に管理してくれるようになるらしい。

この機能をオンにすると、iCloud にはオリジナルの写真とビデオが保管されるようになり、iPhone上には省スペースのバージョンが保存されるらしい。この省スペースのバージョン、というのが 最適化された写真とビデオ ということになる。

ライブラリが最適化されるのは容量が必要になったときに限られるようで、アクセス頻度の低い写真やビデオから先に処理されていくらしい。どの画像やビデオが最適化されたかの確認方法はわからない。 また、オリジナルの写真やビデオが必要な場合は、iCloudからダウンロードすることも可能らしい。

ちなみにこの「ストレージを最適化」というオプションについてはデフォルトでオンになっているようなので、明示的にオフにしていない限りは最適化された写真やビデオがiPhone上に存在している可能性はある。

f:id:shinshin86:20211011223309p:plain
赤枠は私がつけたもの。ここにチェックが付いていると、ストレージを最適化が有効になっている。

最適化された画像やビデオがある場合、iCloudのバックアップを停止すると消える

そしてこの最適化された画像やビデオがある場合、iCloudのバックアップを停止すると消えるということを上に貼ったメッセージは伝えているようだ。

これを理解するのにまずはiCloudの機能を理解する必要があるので、いきなり「空き領域を確保するために最適化された写真とビデオはこのiPhoneから削除されます。」なんて言われても知らんがな!という気分にはなる。

iCloudの写真のバックアップを停止して、iCloudの容量を確保するための手順

ここまでの話を踏まえた上で、iCloudの写真のバックアップを停止してiCloudの容量を確保するための手順をまとめておこうと思う。

  1. iCloudのバックアップを停止させる
  2. iPhone上から写真やビデオが消えるかもしれないが、この時点であればiCloud上にオリジナルの写真やビデオは残っているので、消えてしまったけどとっておきたい写真やビデオは別途バックアップする
  3. iCloudのバックアップは停止しても一度アップロードされた画像やビデオは消えないので、iCloudにログインして不要な写真やビデオを削除する
  4. これで容量は確保できる。画像バックアップをオンにすると、再びアップロードが開始されると思うので、画像バックアップはオンにさせないで別の方法でバックアップを行う。

なお、この別の方法でバックアップ、というところについては、画像や動画のバックアップには結局外付けHDDにバックアップを取っておくのが一番コスパは良さそうという文章を先日ポストしたので、バックアップどうしようと考えている方はぜひ読んでみてください。

shinshin86.hateblo.jp

【クラウドストレージ vs 外付けHDD】iPhoneで撮りまくった動画はどこにバックアップするのが良いか?

何ヶ月も前に書いていた文章が下書きフォルダの中で塩漬けになっていたのを発見したので公開することにした。
(またやってしまった...)

目次

結論

2021年になっても外付けHDDに動画のバックアップを取っておくのが一番良さそう!

シンプルで分りやすく、そのうえ一番大容量。

[いきなり脇道] 家族での写真共有はみてねがおすすめ

お父さんお母さんあるあるだと思うが、子供ができると、iPhoneなどで子供の姿を動画に収める機会がものすごく増える。

我が家ではmixiが運営しているみてねというサービスを利用して、夫婦&それぞれの家庭の家族に子供の写真を共有する運用を行っている。

mitene.us

毎日たくさんの写真や動画を上げているが、動作が重くなることもなく、アプリの動作は非常にサクサクと動く。

我が家での みてね満足度 は非常に高く、おすすめの写真共有アプリはなにか?と聞かれたら みてね を勧めたい。

動画のバックアップに最適なクラウドストレージサービスはあるのか?

さて少し脇道にそれてしまったが、本題の動画のバックアップである。

というわけで、順に検討していく。

なお、ここでの検討内容には私個人の私情が多く挟まっている。
(例えばDropboxは別の用途で既に使っているので却下...など)

ただクラウドストレージの料金体系などもメモがてら残しておくので、そういう面で参考になれば幸いである。

Dropbox

Dropboxは別の用途で既に使ってしまっており、写真や動画で容量を圧迫したくない。よってこちらは却下。

なおDropboxの料金体系は下記のようになっている。

f:id:shinshin86:20211011224537p:plain

写真は月払いの値段となっているが、年間払にするともう少し安くなるようだ。

Googleフォト

無制限無料が解除されるので候補からは外れてしまった。無制限無料だったら、間違いなく第一なく候補だった....

また、2021年6月からGoogle ドライブの容量としてカウントされるようになるらしいので、別途Google ドライブを利用している身としては使えない。

f:id:shinshin86:20211011224929p:plain
キャプチャはGoogle Oneの料金体系となる

キャプチャはGoogle Oneの料金体系となる。Google Photo単体での料金表は見つからなかったが、今はそれ単体では契約できないのだろうか?

ちなみに妻はGoogle Photoの2TBプランに加入しており、月額1,150円のようだ。
(iPhone上でサブスクリプションしている)

Box

こちらも既に別途利用しているのでその時点で候補から外れる。

やはり既に使っているストレージサービスに動画をバックアップ用途で上げるというのは、そもそも残容量を圧服するという観点からきつい。。

なお個人の無料プランだと10GBまで可能なようだが、一度に上げられるファイルの容量に250MBまでの制限がある。

f:id:shinshin86:20210430220405p:plain

Amazon Photos

まだ使ったことがなかったので、もしかしたら銀の弾丸になるかもしれない...!という期待を抱いてドキュメントを読む。

調べた概要は下記の通り。

Amazon Photosはプライム会員向けの無制限のフォトストレージである。そして注意点したいのは動画は無制限ではなく、5GBまでという制限がついている。

https://www.amazon.co.jp/gp/help/customer/display.html?nodeId=G6PT8TMLM9NVZCSL

おしい!今回はあくまで動画バックアップ用途としてのクラウドストレージ選別だったので、これは非常に残念ではあるが、それでも写真が無制限にバックアップできるのは嬉しい限り。

せっかくプライム会員なのだから、今度写真のバックアップ用途に限定して使ってみようかしら。

iCloud Drive

iPhoneユーザならこれでいいのでは?と思うが、すでに月額130円の50GBまで使用可能なプランに入っている。

動画を撮るたびに勝手にバックアップされていき、いまでは容量が足りませんというエラー通知ばかり来る。
(動画のみ自動バックアップしないようする方法はないものだろうか?)

というわけで、こちらも却下。

結局、最強はバックアップ用の外付けHDD!?

結局調べていくと、無料プランだと保存できる量には限りがあり、また有料の場合の料金プランを考えると、外付けHDDにガンガンバックアップをとっていくのが一番コスパは良さそうだということに気づいた。
(勿論自分でバックアップを取ることの労力はかかるが、それでもこちらのほうが個人的には良さそうかなと)

なお、iPhone上の動画をバックアップする手順としては下記の通り。

  • iPhoneからMacBookairdropを使って、大量に動画を送る
  • 受け取った動画を外付けHDDに移していく

2021年においても、このシンプルすぎるぐらいシンプルなバックアップ方法が(古臭くはあるが)一番確実でした。

webpack5を用いてReactでTop level awaitを有効にした環境を作成する

目次

最初に

create-react-appは用いず、自身でwebpackをインストールしてReact環境を作成する方法で実現する。 (というか2021.10時点ではcreate-react-appのwebpackはv5ではないので、たぶんtop level awaitを実現するにはejectなどする必要がある?)

ライブラリのインストール

必要なライブラリをインストール

yarn add -D webpack webpack-cli webpack-dev-server babel-loader @babel/core  @babel/preset-env @babel/preset-react html-webpack-plugin
yarn add react react-dom

必要なファイルの作成

次に必要なファイルを生成する。

# webpackの設定ファイルを作成
touch webpack.config.js

# src配下にアプリケーション関連のファイルを作成する
mkdir src
touch src/index.html src/index.js src/App.js

webpack.config.jsの記述

webpack.config.jsには下記のように記述する。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  output: {
    path: path.join(__dirname, '/dist'),
    filename: 'index.bundle.js',
  },
  devServer: {
    port: 3000,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env', '@babel/react'],
            },
          },
        ],
      },
    ],
  },
  experiments: {
    topLevelAwait: true,
  },
  plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};

ここで下記のような記述を加えることでTop level awaitが有効になる

    experiments: {
        topLevelAwait: true
    },

アプリケーションコードの記述

さきほど作成したsrc配下の3ファイルをそれぞれ記述していく。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Top level await sample React</title>
  </head>

  <body>
    <div id="app"></div>
    <script src="index.bundle.js"></script>
  </body>
</html>

index.js

import React from 'react';
import ReactDom from 'react-dom';
import App from './App';

ReactDom.render(<App />, document.getElementById('app'));

App.js

import React from 'react';

const text = await Promise.resolve('Hello World');

function App() {
  return (
    <div>
      <h1>{text}</h1>
    </div>
  );
}

export default App;

ソースコード

ソースコードは下記に格納した。

github.com

上で書いた以外にprettierのインストールや .gitignore などの作成を別途行っているが、top level awaitを行う際の必要最低限な記述はここまでの内容でカバーできるはず。

ちなみに webpack.config.js内の Top level awaitに関する記述を削除して yarn dev でアクセスすると、下記のようなエラーが表示される。

Module parse failed: The top-level-await experiment is not enabled (set experiments.topLevelAwait: true to enabled it)

Top level awaitについて整理した

トップレベルawait(Top level await)について改めて自分の中で整理することにした。

これはその際の備忘録的なものとなる。

目次

Top level awaitとは?

そもそもTop level awaitとは?というところだが、これはawaitキーワードを非同期関数(async function)の外で使用できるようにするものである。

いままではasync 関数の外で await を使おうとすると SyntaxError が発生した。

await Promise.resolve(console.log('Hello World'))
// これは構文エラー(Syntax error)となる

そのため開発者側は上記のようにawait関数を利用するための手段として、すぐに呼び出される非同期関数(async function)を定義していた。

(async function () {
    await Promise.resolve(console.log('Hello World'));
}());

だが、Top level awaitが利用できる環境であれば、最初に書いた下記の書き方でも問題なく動作する。

await Promise.resolve(console.log('Hello World'))
// Hello World

上は単純な例だが、例えば下記のようにCDN経由でjQueryを読み込むなども行える。

const jQuery = await import('https://code.jquery.com/jquery-3.6.0.slim.min.js')
console.log(jQuery)
// Module {Symbol(Symbol.toStringTag): 'Module'}

try~catchも可能なため、下記のようなコードも書ける

let aModule;
try {
  aModule = await import('http://example.com');
} catch {
 aModule = await import('http://example.net');
}

Top level awaitをブラウザで試すシンプルな方法

Chromeがtop level awaitに対応した際に、開発者ツールのコンソール上で await が使えるようになったので便利になった、というツイートを見かけた気がする。

例えば下記のようなコードをChromeの開発者ツールのconsole上で書くことができる。

const response = await fetch(url);
response.status
// => 200

私自身、開発者ツール上であれこれデバッグしたいときにawait周りで無駄に思考を張り巡らせていた経験があるので、シンプルに上のように書けるのはとても便利で感動した記憶がある。

さて、では実際にファイル上にtop level awaitを書きたい場合、どのように書けばよいか?

実際に読み込まれたJSファイル上でTop level awaitを行う場合は type="module"をつけて呼び出す

一応、今回の動作環境となるChromeのversionを先にメモしておく。

Google Chrome: 94.0.4606.71 (Official Build) (arm64)

まずはTop level awaitを定義したJSファイルを用意する( src/index.js)

const a = await Promise.resolve("<h1>hello</h1>");
document.getElementById("app").innerHTML = a;

そして次にHTMLファイルを用意する。

<!DOCTYPE html>
<html>
  <head>
    <title>Top level await sample (Vanilla JS)</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app"></div>
    <script type="module" src="src/index.js"></script>
  </body>
</html>

type="module"と書かないと、src/index.jsのTop level awaitは有効にならない。

これでローカルサーバを経由してアクセスすることで想定した結果が得られる。
なお、ファイルを直接ブラウザで開くやり方の場合、下記のようなエラーが表示される。

Access to script at {file path} from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.

type="module"をつけない場合はsyntax errorとなる

なお、下記のように type="module"をつけない場合は、従来の通り、構文エラーとなる。

<!DOCTYPE html>
<html>
  <head>
    <title>Top level await sample (Vanilla JS)</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app"></div>
    <script src="src/index.js"></script>
  </body>
</html>

この場合、下記のようなエラーがコンソール上に出力されるはず。

Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules

Top level awaitをVanilla JSで試す際の最もシンプルなサンプル

勿論ファイル経由で呼び出さず、下記のように書くこともできる。

たぶん、これが一番シンプルなTop level awaitの呼び出しサンプルかと思います。

<!DOCTYPE html>
<html>
  <head>
    <title>Top level await sample (Vanilla JS)</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app"></div>
    <script type="module">
      const a = await Promise.resolve("hello");
      document.getElementById("app").innerHTML = a;
    </script>
  </body>
</html>

type="module"のあるファイルと、ないファイルが混在している場合

次にtype="module"があるファイルと、ないファイルが混在している場合だが、type="module"がついているファイルは最後に実行されることになる。 よって、awaitしている間にHTMLのパース自体がブロックされるということはない。

サンプルを下に上げる。

<!DOCTYPE html>
<html>
  <head>
    <title>Top level await sample (Vanilla JS)</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app"></div>
    <script type="module" src="src/index.js"></script>
    <script src="src/index2.js"></script>
  </body>
</html>
console.log('call index.js');
console.log('call index2.js');

このようなファイル構成の場合、Chromeの開発者ツールからコンソールを確認するとログは下記のようになる。

call index2.js
call index.js

CodeSandbox上だとtop level awaitは有効になっていない?

ちなみに上に書いたようなコードをCodeSandbox上で試そうとしても、エラーになるようだった。

これは単にCodeSandbox環境だとTop level awaitに対応していないだけだろうか?

f:id:shinshin86:20211010065322p:plain
Vanilla JSのテンプレートで作成して実行してみたところ

ReactでTop level awaitを試す(webpack使用)

これについてはセットアップなどを記載していくと長くなりそうだったので別ポストに書いた。

shinshin86.hateblo.jp

Top level awaitを試すための方法をサクッと書いてしまうと、webpackのv5を用いて、webpack configに下記の記述を追加することで対応できる。

experiments: {
    topLevelAwait: true,
  },

Rollupの場合は experimentalTopLevelAwait というオプションを利用するようだが、こちらについては試していません。

Node.jsでTop level awaitを試す

Node.js環境では 14.8.0以降、標準にTop level awaitが組み込まれている。

node/CHANGELOG_V14.md at master · nodejs/node · GitHub

例えば下記のようなコードを書く。
(ちなみに今の環境内のNode.jsのversionを見ると、 v14.17.1 だったので併せて明記しておく)

const text = await Promise.resolve('Hello World');
console.log(text);

これを通常通りに実行すると、下記のようなエラーが表示される。

const text = await Promise.resolve('Hello World');
             ^^^^^

SyntaxError: await is only valid in async function

Node.jsから実行する場合はpackage.jsontype: moduleを追加する。

{
  ・
  "type": "module",
  ・
  ・
}

もしくは、ファイルの拡張子を mjs にすることで実行可能となる。

DenoでTop level awaitを試す

確か記憶だとDenoは最初からTop level awaitが使えるようになっていた気がする。

自分の環境のDenoは最新から比べるとやや古くなっていた。せっかくなのでupgradeする。

denoでは下記のコマンドで最新にupgradeできるのでかんたん。

deno upgrade

というわけで、動作させるバージョンは 1.14.3

下記のようなコードを書く。
(といっても先程と同じコード)

// main.ts
const text = await Promise.resolve("Hello World");
console.log(text);

そして下記のコマンドで実行する。

 deno run main.ts

これで問題なく実行できる。

JavaScriptのimport時のファイル読み込み時、複数から呼ばれたJSファイルの挙動について

JavaScriptを書いていて、挙動を理解していない箇所があるので今日はそのメモを書く。

例えば下記のような3つのファイルがある。

// sample1.js
import { userList } from "./sample2";
import sample3 from "./sample3";

console.log('===> call sample1.js')
console.log("call sample1: ", sample3);

export const getUserNameList = () => {
  return userList.map(({ name }) => name);
};
// sample2.js
import sample3 from "./sample3";

console.log('===> call sample2.js')
console.log("call sample2: ", sample3 );

export const userList = [
  { id: 1, name: "test1" },
  { id: 2, name: "test2" },
  { id: 3, name: "test3" }
];
// sample3.js
const sample3 = "sample3";
console.log("===> call sample3.js!!!!!!!!!!");
export default sample3;

sample1.jssample2.js はどちらも sample3.js を呼び出している。
(より正確には sample1.jssample2.jssample3.js を呼び出しており、さらに呼び出された sample2.jssample3.js を呼び出す形となっている)

そしてこのファイルのうち、 sample1.js は下記のコードから読みこまれている。
(create-react-app を使って生成したプロジェクト内で利用している。実際のコードはGitHubに置いておく)

import logo from './logo.svg';
import './App.css';
import { getUserNameList } from './samples/sample1';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        <ul>
          {getUserNameList().map((userName, index) => (
            <li key={index}>{userName}</li>
          ))}
        </ul>
      </header>
    </div>
  );
}

export default App;

一見すると、sample3.js は2回呼び出されているので、下記の処理も2回動きそうにも思えるが、このログは1度しか表示されない。

console.log("===> call sample3.js!!!!!!!!!!");

下記が実際に動かした際の開発者ツールのキャプチャ。

f:id:shinshin86:20211008071531p:plain
sample3.jsの処理が動いているのはログを見る限り一度だけ

なお、ブラウザは下記の環境で実行している。

Google Chrome    94.0.4606.71 (Official Build) (arm64)

複数箇所から参照されているファイルは一度読まれると、内部のメモリ(この場合、ブラウザ内のメモリ)でその情報を持つようになっているのだろうか?

そこがよくわからない。分かり次第こちらに追記したいと考えている。

追記:依存関係グラフに属する各モジュールは葉から順に(帰りがけ順で)実行される?

Top level awaitについて調べていたときに出会ったこちらのポスト

qiita.com

内容としてはtop level awaitやESModuleについての話だが、その中に下記のような記述がある。

具体的な実行順は、依存関係グラフを深さ優先探索することで決められます。依存関係グラフに属する各モジュールは葉から順に(帰りがけ順で)実行されます。ただし、同じモジュールを訪れるのは1回だけです。

これが今回の事例にも当てはまる、と仮定するなら挙動としてはこれと同じ挙動をとっていることになる。

今回のコードも依存関係の深い順から記載していくと sample3.js -> sample2.js -> sample1.js という順となるからだ。
この依存家計を元に帰りがけ順に実行されていくとするならば、この挙動も納得がいく。

ただ、上のポストはop level awaitやESModuleについての話なので、もしかしたら今ここで書いていることとは何も無関係の可能性もある。

が、一応備忘録としてこちらに残しておく。

実際のコード

実際のコードはGitHubに置いてある。

github.com

今年もHacktoberfestの季節

Hacktoberfest 2021について

暑い夏も終わり、用がなくとも外を散歩したくなるような季節。また、この時期はHacktoberfestが開催される時期でもある

f:id:shinshin86:20211003230612p:plain
Hacktoberfest 2021

わたしは去年初参加して無我夢中で頑張ってみた結果、なんとかTシャツをもらえた。

今年もお祭り気分で参加してみようと思い、今日参加登録だけ行った。

参加方法などは下記のページに詳しく載っている。

dev.to

なお、去年参加したときのことは下記のポストに書きました。

shinshin86.hateblo.jp

参加した進捗などはこちらのポストに追記する形で書いていこうと思います。

追記: Hacktoberfest、無事に完了

無事にHacktoberfest絡みのPRが4つマージされ、Hacktoberfestは完了となった。

あとは14日間の待機期間(?)があり、その間にこれらのPRが不正なものと言う判断がされない限り、これで完了となる。
もし不正であると判断された場合は、再度Hacktoberfest関連のPRを作成する必要があるが、勿論これらのPRは不正なものではないので、ほぼほぼこれでOKかなと思っている。

去年に引き続き、私はJavaScript関連のプロジェクトにコントリビュートさせてもらいました。

ちなみにそれぞれのコントリビュートの内容はDEVの方に書いています。

dev.to

Tシャツ楽しみである。