at backyard

Color my life with the chaos of trouble.

Reactを自作するための学習メモ #2

1から続いて、引き続き行っていく。

shinshin86.hateblo.jp

参照しているドキュメントも変わらない。今はまだドキュメントをなぞっているだけなので、ここに乗っているコードは下記の記事に乗っているものと同じだ。

React.createElement関数を自作する

下記のようなコードがある。

import ReactDOM from "react-dom"

const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)

const container = document.getElementById("root")
ReactDOM.render(element, container)

描画結果は下記のようになる。

<div id="root">
  <div id="foo"><a>bar</a><b></b></div>
</div>

変数elementに格納しているのはJSXになるため、まずはここをJSに書き換える。 すると、こうなる。 (といってもここに書いてあるコードは参照元のコードを参照しているに過ぎない)

import React from 'react'
import ReactDOM from "react-dom"

const element = React.createElement(
  "div",
  { id: "foo" },
  React.createElement("a", null, "bar"),
  React.createElement("b")
)

const container = document.getElementById("root");
ReactDOM.render(element, container);

ちなみにこのelement変数には下記のような値が格納される

{
  type: "div",
  key: null,
  ref: null,
  props: {
    id: "foo",
    children: [
      {
        type: "a",
        key: null,
        ref: null,
        props: { children: "bar" },
        _owner: null,
        _store: {}
      },
      { type: "b", key: null, ref: null, props: {}, _owner: null, _store: {} }
    ]
  },
  _owner: null,
  _store: {}
};

ここでcreateElement関数の仕様を改めて確認する。

https://ja.reactjs.org/docs/react-api.html#createelement

https://ja.reactjs.org/docs/jsx-in-depth.html

React.createElement は下記のような仕様となっている。

React.createElement(
  type,
  [props],
  [...children]
)

type 引数にはタグ名の文字列('div' や 'span' など)やReactコンポーネント(クラスや関数)、Reactフラグメントのいずれかを指定することが可能となっている。

JSXで書かれたコードはこのReact.createElementを利用するようになっているので、JSXで普段書いている場合はこの createElement 関数を用いることはないようだ。

ちなみに上のelementに予めReact.createElementを通して作成したObjectをそのまま通しても、エラーになる。

import ReactDOM from "react-dom";

/*
const element = React.createElement(
  "div",
  { id: "foo" },
  React.createElement("a", null, "bar"),
  React.createElement("b")
);
*/

const element = {
  type: "div",
  key: null,
  ref: null,
  props: {
    id: "foo",
    children: [
      {
        type: "a",
        key: null,
        ref: null,
        props: { children: "bar" },
        _owner: null,
        _store: {}
      },
      { type: "b", key: null, ref: null, props: {}, _owner: null, _store: {} }
    ]
  },
  _owner: null,
  _store: {}
};

const container = document.getElementById("root");
ReactDOM.render(element, container);

この原因については、#1の方に書いた。

Objects are not valid as a React child (found: object with keys {type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.

今回はこのcreateElement関数の自作版を作成する。 参照したドキュメントを参考に(というか、そのまま写経)作成していくと、

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children
    }
  };
}

// ログを表示するためだけの関数(このブログに書き残したくするためにJSON.stringifyで文字列化している)
const displayLog = v => console.log(JSON.stringify(v))

const a = createElement("a", null, "bar");
const b = createElement("b");

console.log(createElement("div"));
displayLog(createElement("div"))
// {"type":"div","props":{"children":[]}} 

console.log(createElement("div", null, a));
displayLog(createElement("div", null, a))
// {"type":"div","props":{"children":[{"type":"a","props":{"children":["bar"]}}]}} 

console.log(createElement("div", null, a, b));
displayLog(createElement("div", null, a, b))
// {"type":"div","props":{"children":[{"type":"a","props":{"children":["bar"]}},{"type":"b","props":{"children":[]}}]}} 

Reactでは、childrenがない場合にプリミティブ値をラップしたり、空の配列を作成したりはしないよう。 ただ、参照元のコードでは、説明をわかりやすくするためにコードを単純化しているようなので、それに倣った実装を行う。 (といっても、ここでやっているのも、ほぼほぼ写経しているだけ)

ここではcreateElementに渡すchildrenがobject型でない場合は typeに TEXT_ELEMENTを設定したobjectを返すようにしている。

ここでコードのその返り値は下記のように更新される。

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    }
  };
}

const createTextElement = text => {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}

const displayLog = v => console.log(JSON.stringify(v))

const a = createElement("a", null, "bar");
const b = createElement("b");

console.log(createElement("div"));
displayLog(createElement("div"))
// {"type":"div","props":{"children":[]}} 

console.log(createElement("div", null, a));
displayLog(createElement("div", null, a))
// {"type":"div","props":{"children":[{"type":"a","props":{"children":[{"type":"TEXT_ELEMENT","props":{"nodeValue":"bar","children":[]}}]}}]}} 

console.log(createElement("div", null, a, b));
displayLog(createElement("div", null, a, b))
// {"type":"div","props":{"children":[{"type":"a","props":{"children":[{"type":"TEXT_ELEMENT","props":{"nodeValue":"bar","children":[]}}]}},{"type":"b","props":{"children":[]}}]}} 

childrenとしてテキストが渡された場合はtypeTEXT_ELEMENTが設定されたものが生成されている。

render関数を自作する

いよいよ次はrender関数の自作。

まずは既存のrender関数を使って描画を行ってみる。

import ReactDOM from 'react-dom'

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    }
  };
}

const createTextElement = text => {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}


const container = document.getElementById("root");
ReactDOM.render(createElement("h1", "hello"), container);

ただし、これはやはりまだこのエラーになる。 (上にも書いたが #1 を参照)

Objects are not valid as a React child (found: object with keys {type, props}). If you meant to render a collection of children, use an array instead.

次は render関数の自作に挑む。といっても写経だが。

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    }
  };
}

const createTextElement = text => {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}

const render = (element, container) => {
  const dom = element.type === "TEXT_ELEMENT"
    ? document.createTextNode("")
    : document.createElement(element.type)

  const isProperty = key => key !== "children"
  Object.keys(element.props)
    .filter(isProperty)
    .forEach(name => {
      dom[name] = element.props[name]
    })

  element.props.children.forEach(child =>
    render(child, dom)
  )

  container.appendChild(dom)
}

const element = createElement(
  "h1",
  null,
  "Hello"
)

const container = document.getElementById("root");
render(element, container);

実行するとHTMLの生成に成功し、下記のようなHTMLが吐き出される。

<div id="root">
  <h1>Hello</h1>
</div>

すこし生成するElementを変えてみる。

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    }
  };
}

const createTextElement = text => {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}

const render = (element, container) => {
  const dom = element.type === "TEXT_ELEMENT"
    ? document.createTextNode("")
    : document.createElement(element.type)

  const isProperty = key => key !== "children"
  Object.keys(element.props)
    .filter(isProperty)
    .forEach(name => {
      dom[name] = element.props[name]
    })

  element.props.children.forEach(child =>
    render(child, dom)
  )

  container.appendChild(dom)
}

const element = createElement(
  "div",
  { id: "foo" },
  createElement("h1", null, "hello"),
  createElement("p", null, createElement("b", null, "world"))
)

const container = document.getElementById("root");
render(element, container);

生成されるHTMLは下記の通り。

<div id="foo">
  <h1>hello</h1>
  <p><b>world</b></p>
</div>

今回はここで一旦区切る。

Reactを自作するための学習メモ #1

私は普段ソフトウェアエンジニアとして仕事をしているときが多い。

そして普段の業務でよく触るライブラリの一つに、Reactがある。

私は普段からReactをよく触っているくせに、そういえばこのReactの内部の仕組みを深堀りしたことがなかった。
(ザックリとだけ処理の概要を追っていたことはあるが)

というわけで、一度Reactを自身でも自作できないかと思い、その際に学習したことをこのブログに備忘録として残しておく。

なお、これは飾らない自分用の学習記録となるため、見づらい部分も多々ある。
(というかコードのフォーマットをしないでブログに載せているので、フォーマットがバラバラかもしれない)

もしある程度の学習の成果を残せたらQiitaやZennにしっかりとした形で残すかもしれないが、そうならなければこちらのブログに書くにとどまると思う。

なお、間違いなどありましたらコメントやTwitterに直接コメント頂けたらと思います。

参考にするドキュメントなど

なお、実際に作業に当たる前に予備知識として以下のドキュメント(その翻訳記事)を参照した。

というか学習の最初の方はこれらのページを参照に学習していくので、ただただ内容をさらっているだけのようなものとなるかと思う。

pomb.us

zenn.dev

Reactの基本的な挙動について

まずは下記のようなコードを書いてみる。

なお、ここについては環境構築などは一旦面倒なのでCodeSandboxで行っている。 React用のテンプレートを読み込んで、index.js内のコードを下記のように書き換えている。

import ReactDOM from "react-dom";

const element = <h1 title="foo">Hello</h1>
const container = document.getElementById("root")
ReactDOM.render(element, container)

これを実行されると、下記のようなHTMLが吐き出される。

<div id="root">
  <h1 title="foo">Hello</h1>
</div>

elementにはReact要素が定義されている(いわゆるJSXで書かれた部分) containerにはDOMから取得したノードがある。

実際にこのReactの処理結果を展開するさきのHTMLには下記の要素がある

<div id="root"></div>

ここの root を取得してその中にReactが展開する形となっている。

そのため、

import ReactDOM from "react-dom";

const element = <h1 title="foo">Hello</h1>
//const container = document.getElementById("root")
const container = document.getElementById("root2")
ReactDOM.render(element, container)

とすると、下記のようなエラーが発生する。

Target container is not a DOM element.

ターゲットとなるcontaiernはDOMである必要があるためだ。

さて、ここで今行われた処理はReactを用いた処理だが、これを生のJavaScriptに置き換えた場合どうなるだろう?

Reactの基本的な処理を生のJavaScriptに書き換えてみる

まずはJSXを利用している部分を生のJavaScriptに置き換えてみる。

JSXを利用するにはBabelなどのビルドツールによってJSに変換する必要があるため、このJSXをJSに置き換えるようにする

const element = <h1 title="foo">Hello</h1>

↑の部分だ。これを下記のようにする。

import React from "react"
import ReactDOM from 'react-dom'

const element = React.createElement(
  "h1",
  { title: "foo" },
  "Hello"
)
const container = document.getElementById("root")
ReactDOM.render(element, container)

勿論表示結果は変わらないし、HTMLの描画結果も下記のようにおなじになる。

<div id="root">
  <h1 title="foo">Hello</h1>
</div>

ちなみに、

const element = <h1 title="foo">Hello</h1>

でも

const element = React.createElement(
  "h1",
  { title: "foo" },
  "Hello"
)

でも、elementの中に入るものは変わらない。

ここには下記に記載されているようなReact要素が返されている。

これはconsole.log で中身を見てみると、JSONのような形をしている。
(ちなみに下記はJSON.stringify をした上で表示させたものをコピペして載せている)

{"type":"h1","key":null,"ref":null,"props":{"title":"foo","children":"Hello"},"_owner":null,"_store":{}} 

また下記のドキュメントには、React要素とはReact アプリケーションの最小単位の構成ブロックのことを指していると書かれている。

ja.reactjs.org

では、このJSON形式のオブジェクトをelement変数のかわりに扱えるかと思うのだが、そうはならない。

import ReactDOM from "react-dom";

/*
const element = React.createElement(
  "h1",
  { title: "foo" },
  "Hello"
)
*/

const element = {
  type: "h1",
  key: null,
  ref: null,
  props: { title: "foo", children: "Hello" },
  _owner: null,
  _store: {}
};

const container = document.getElementById("root");
ReactDOM.render(element, container);

このコードを実行すると、下記のようなエラーが発生する。

Objects are not valid as a React child (found: object with keys {type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.

エラーを見ると、childrenの要素をレンダリングするには配列を格納する必要があるらしく、このままではHTMLを描画できない。 単純にcreateElement関数の返り値と同じ値にするだけでは、ダメということだろうか?

render関数にはReact Elementを渡さなければならない?

実はここは最初よく分からないでいたのだが、どうやら Reactのコードを読んでみると、render関数の第一引数は React$Element<any>である必要があった。

ここは自分の憶測が入るが、おそらくここは単純なJavaScriptのObjectなどではなく、React Element(もしくはなんらかのElement)を渡さなければならないのではないだろうか。

私はさきほどJSON.stringifyをして単純に文字列化したものをObjectとして定義して入れていたが、それでは全く別物になっているためエラーとなっているのではないかと思われる。

実際のソースコードを下記に貼る。 これはreactソースコードをクローンしたあと、packages/react-dom/src/client/ReactDOMLegacy.jsのコード内にあった render関数の呼び出し箇所の記述だ。
なお、このときのプロジェクト全体でのコミットハッシュは ed6c091fe961a3b95e956ebcefe8f152177b1fb7 である。

export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {

ちなみにこのreaderに渡されたあとのコードだが、あとを辿っていくと、packages/react-reconciler/ReactFiberReconciler.new.jsupdateContainer 関数に行き着く。

さらに update.payload に格納され、 enqueueUpdate へ行くなどして(もう追いつけない...)、最終的にどこかで throwOnInvalidObjectType が呼ばれ、先ほどのエラーが発生したものと思われる。

ちなみにこの例外を投げる処理の前には isArrayなどのチェック関数があり、そちらは packages/shared 内に格納されていた。

おそらくそこのisArray関数あたりに引っかかったものと思われる。

React依存をなくした状態でHTMLを描画する

話は戻る。ここからは参照元のコードを再び参考にさせていただく。

下記のような処理を経て、HTMLを描画していくことで、完全にReact依存を捨てた状態となった。

ここで用いているelementの中身はさきほどエラーになった、単純化したJSONデータとなっている。

これをゴニョゴニョ(下記参照)と処理した上でHTMLに描画させるまでの処理を行っている。

const element = {"type":"h1","key":null,"ref":null,"props":{"title":"foo","children":"Hello"},"_owner":null,"_store":{}};

const node = document.createElement(element.type)
node["title"] = element.props.title

const text = document.createTextNode("")
text["nodeValue"] = element.props.children

console.log(text)
// Text {wholeText: "Hello", assignedSlot: null, splitText: ƒ splitText(), data: "Hello", length: 5…}

node.appendChild(text)
console.log(node)
// <h1 title="foo">Hello</h1>

const container = document.getElementById("root")

container.appendChild(node)
cconsole.log(container)
/*
<div id="root">
  <h1 title="foo">Hello</h1>
</div>
*/

いやー普段React触っているくせに全然内部のこと知らないなーと思った。

続きはまた後日やっていきます。

余談: React18ではrenderはLegacy root API的な立ち位置となり、createRootへの移行を勧められるようになる

Reactのソースコード読んでいたら発見しました。

最近Reactのことを追っていなかったのですが、まだまだ進化していきそうな感じですね。

【WordPressでGoogle AdSenseの審査に受からない人向け】Google AdSenseの審査に受かる方法と対策ツールについて

Google AdSenseの審査が厳しい問題について

最近ウェブを経由した集客支援の一環として、WordPressに関する相談を受ける時がある。

そして相談に乗っているときに、時折、話のついでとして、「Google AdSenseの審査に通らないのだけど、良い対策方法はある?」と聞かれる時がある。

このGoogle AdSenseの審査に受からない問題についてだが、ネット上でも予測変換に出てくるほどには事例の多いケースである。

下記のキャプチャはシークレットモードで Google AdSense 審査 で検索したときに出てくるサジェストである。

f:id:shinshin86:20210618084016p:plain

このようにGoogle AdSenseの審査というのはなかなかに厳しい。

一応対策方法というのは存在しており、相談に載っている方にはそれらの方法を伝えるようにしているが、結構審査基準が不透明、というか

  • 対策したのに審査通らない、
  • 判断基準満たしていないのに通った

みたいな気まぐれなケースも事例として割とあるので、70%ぐらいの自信で答えている。

そして、かくいう私も、最近自身で運営しているWordPressサイトでGoogle AdSenseの審査を受けてみたら見事に落ちた。
(ちなみにこちらのサイトは名義を分けているので、こちらには貼りませんが、あしからず)

WordPressで相談に乗っている人間のサイトが審査に落ちたらアカンでしょ」

そう思い、実際にWordPressにおけるGoogle AdSense対策の実証実験として、このサイトのGoogle AdSense対策を行って審査を通過させてみようと思い至った。

ちなみにこのはてなブログについてもGoogle AdSenseを利用しているが、こちらは審査は一発で通っている。

今にしても思えば、世間一般で言われているGoogle AdSenseに受かるための施策を全部行っていたわけではなく、ノリで審査依頼を出したような感じだったが、それでも通った。
(やはり審査基準は謎)

shinshin86.hateblo.jp

Google AdSenseの審査が通らない理由

Google AdSenseの審査基準についてはGoogle自身がガイドラインを出している。

support.google.com

support.google.com

support.google.com

基本的には上のページに書かれていることを実践すれば良いと思う。
(ただし、先ほども書いたように受かるとは限らない...)

Google AdSenseの審査前にチェックするリスト

一応Google AdSenseの審査前にチェックするリストを書き出しておくと、

  • プライバシーポリシーに関するページは存在するか?
  • 問い合わせフォームは存在しているか?
  • 不適切なコンテンツは存在しないか?
  • 中身のないページは存在しないか?
    • 例えばプログラムで自動生成した、テンプレートだけのサイトなどがあると、たぶん落ちる
  • 読んで価値のある文章が載っているか?
    • といっても、そんなに価値ない文章でも通ったとブログに書いている人もいるので、堅い基準ではなさそう...
  • 1記事の文字数はある程度の量を保っているか?

一応ここらへんの対応を行ってから審査に投げるようにはしておきたい。
(これで確実に受かるとは限らないが。 )

そして、ここの項目の中で対応が特に面倒くさいのが文字数関連だ。

文字数についても判断基準が確実というわけではなさそうだが、対応はしておいたほうが良さそうだろう。

あとはGoogle AdSenseは通っても、アフィリエイトを行うためにほかのASPの審査で引っかかるという可能性もある。

WordPressで公開済みの記事の文字数をチェックするツールを作った

前置きが長くなったが、そんなこんなで、WordPressで記事の文字数を一覧でチェックできるツールを作った。

wp-text-counter.com

サイトにアクセスして、フォームに自身のWordPressサイトのURLを入力して取得ボタンを押すことで調査可能だ。
(https://example.com といった形でサイトのトップのURLを入力してください)

このツールを使うことでWordPressにログインしなくとも、公開済みの記事の文字数を取得することができる。

サイトの細かな仕様や使い方についてはこちらを見てください。

WordPress文字数チェッカーについて

wp-text-counter.com

WordPress文字数チェッカーの使い方

wp-text-counter.com

WordPress文字数チェッカーの存在意義

現在のWordPressでは、このようなツールを使わなくとも、記事作成画面にあるアイコンをクリックすることで下記のように文字数などをチェックすることは可能だ。

f:id:shinshin86:20210618142129p:plain
画面上部のアイコンをクリックすると文字数の確認が可能

では、なぜこんなツールを作ったのか?ということについて少し書いていく。

すべての記事の文字数を確認できる

対象サイトのURLを入力するだけで、サイトにログインせずとも公開済みの記事の文字数を確認することができる。

これは自身のサイトでもそうだが、例えば他人のサイトの相談に乗るときなどにも重宝する。

いちいちログイン情報を預からなくても、その場でチェックできるからだ。

一覧結果にはページURLもついてくるので、その場で実際の記事を読むことも可能となっている。

ここらへんはウェブ集客のコンサルを普段からやっている人にも重宝するのではないかと思う。

私自身、とにかく気軽にすぐにチェックできるツールが欲しかった。

サイトにツールを入れなくて済む

現在のWordPressではデフォルトの状態で文字数をチェックできるようになっているので、そもそも不要だが、文字数をチェックできるプラグインなども存在する。

これらのツールを使えば文字数の一覧などが並び替えて表示もできるようなので便利そうだが、個人的にはなるべくWordPressに入れなくて済むプラグインは入れたくないタイプなので、外部ツールで簡単に済ませられるなら、それで済ませたかった。

競合サイトの文字数一覧なども簡単にチェックできる

あとはURLを入れれば確認が可能なので、例えばWordPressを用いている競合サイトの文字数なども併せてチェックできる。

例えば、競合サイトの情報を織り込むことで、クライアントへの説明資料などにも説得力をもたせられるし(実際、文字数はSEOを語る上で重要な要素だ)、色々と便利な使い方ができる。

こうやって振り返って見ると、個人でWordPressサイトを運営している人向けなのは当然のことながら、ウェブ集客・SEOコンサルなどを普段からやっている人の役にも立つかもしれないと思っている。

というか、自分自身がそういう用途で使うことを考えて作ったツールなので、たぶん役に立つと思う。

今後の課題

まだ作ったばかりなので、今後機能追加や改修は行っていきます。

気にある点があればTwitterまでご報告ください。

twitter.com

とりあえず今後追加する機能としては、一覧のデータのCSVエクスポート機能はほしいところ。

ちなみにしばらくアクセスされていないとサーバが眠りだすので、初回アクセス時だけ画面表示に時間がかかるかもしれません。

WordPress文字数チェッカーで用いている技術

ここからは完全に余談。

このWordPress文字数チェッカーだが、利用している技術はNode.jsで express + pug という、枯れている(?)技術を使っている。

ソフトウェア・エンジニアとしてはまだまだ未熟な自分だが、自分のスキルセットとして結果的に一番経験年数が長い言語となったJavaScript(Node.js)で構築している。

本当は学習がてらNuxtをガッツリ触ってみたかったので利用を検討したが、とりあえずはアイデアを形にするまでの時間を可能な限り短くしたかったので、これらの技術を用いている。
(React(Next.js)も検討したが、そもそもSPAである理由もないし、express使って普通にサーバで処理してデータ返すだけのシンプルな構成でええやん、となった)

まあ、そんなわけで、Node.js界隈の中でも枯れている技術を用いて作成している。

久しぶりにpugに触れたが、案の定細かいところ忘れていて、ちょくちょくググりながら作った。

ちなみにLighthouseのスコアはこんな感じ。

f:id:shinshin86:20210618091420p:plain

重たい素材も複雑な処理もないサイトなので、当然ではあるが、こんな感じでサクッと使えるので、ぜひ使ってみてください。

TrelloのBusiness Classの解約方法

いきなりTrelloでBusiness Classの無料トライアルが適用されていた

昨日ツイッターで呟いたのだが、こんなことがあった。
(下記のツイートを参照してみてください)

いきなりBusiness Classにようこそ的なメールが来ていて知らんがな!!となったのはインパクトがでかい。

ツイートにも書いていたとおり、無料のワークスペースはボード数が10枚に制限されているのだが、自分のワークスペースにはそれ以上のボードがあるので、まずはビジネスクラスの14日間無料トライアルを利用してみてねという意図でBusiness Classにあげたというようなメールが、同時刻に別メールできていたが、 Business Classにようこそ的なメール の中にそのことも書いておかないと、私みたいに何事か?と焦るユーザはいるのではないだろうか。

ちなみに、Trelloのドキュメントだと下記のページに書かれていた。

help.trello.com

TrelloのBusiness Classの解約方法

せっかくこんなメールをもらっておいて悪いのだが、私は今現在はTrelloを全く利用していないので、早速TrelloのBusiness Classを解約した。
(ちなみにたぶんそのままにしても私は請求情報をTrelloに登録していないし、無料トライアル期間が過ぎたら勝手に無料プランに戻ると思われる。 だが、あまりそこらへんは注意深く文章を読んでいないので、もし放っておかれる方は問題ないかをご自身で今一度確認することをおすすめする)

Trello側としては、こういうメールを送ることで再びTrelloを利用してもらいたいという意図があるのだろうが、余計なお世話という感じであると同時に、心地よく昼寝していたのに急に叩き起こされたときのような、まあ、そんな気分となった。

解約方法についてはtrelloのホーム画面の左下にある 設定 画面から行える。

設定をクリックすると下記のような画面になるので、請求タブをクリックする。

f:id:shinshin86:20210617081922p:plain
請求タブをクリックする

請求タブの画面を下にスクロールさせると、下記のような項目が出てくる。

f:id:shinshin86:20210616224138p:plain

ダウングレードすると不便よ的なメッセージが表示されるが、気にせず解約を行うを選択する。

最後にフィードバックを、というダイアログが出てきて、急に勝手にBusiness Classに上がってびっくりしたんですけど!と書こうと思ったが、うまい言い回しが思いつけず、いいえを選択して解約を実行

f:id:shinshin86:20210616224423p:plain
フィードバック、送ればよかったけど...うまい言葉が思いつかなかったのよ

画面が下記のように切り替われば解約は完了です。

f:id:shinshin86:20210616224515p:plain

以上。

Water.cssを使っていてレスポンシブデザインにならないときにチェックすること

Water.css、とても便利なので、あまりデザインの凝らないシンプルなWebアプリを作ろうというときには結構な頻度で手が伸びます。

watercss.kognise.dev

cssのことを気にせず、HTMLでページの構造を作っているだけで、それなりに整ったデザインになるのはとても助かります。

さて、今回、water.cssを適用しているのに画面上の表示がレスポンシブデザインにならないときがありました。

はて、なんでかな?と思い、調べてみると、下記の viewport に関する記述をHTMLファイルの <head></head> に挿入し忘れていただけでした。

<meta name="viewport" content="width=device-width, initial-scale=1.0">

なんてことない凡ミスなのですが、なんとなく将来同じミスをまたやりそうな気がしたので、備忘録として残すことにしました。

以上です。

音声認識した声を、ゆっくりの声に吹き替えるための Mac 用のソフトウェアを作った

音声認識した声を、ゆっくりの声に吹き替えるツール

音声認識した声を、ゆっくりの声に吹き替える yukkuri-hukikae-on-mac というツールを作りました。

github.com

詳細はこちらにも書いているので、ぜひ読んでみてもらえたらと思います。

free-sound.shinshin86.com

nexeを使ってうまくパッケージングができない

ここからはyukkuri-hukikae-on-mac を開発していたときにハマったポイントについての個人的メモ。

現状だと、ターミナル開いて yarn install && yarn startを打たなければ起動できないという、プログラマー以外の方には敷居の高い物となってしまっている。

本当はnexeを使ってNode.jsのアプリをパッケージングしようと思っていた。

github.com

nexeを用いてビルドして、シングルバイナリとして配布できればベストだったが、nexeでビルドを行おうとすると、下記のようなエラーとなってしまう。

github.com

原因の検討はついていて、aquestalk関連のライブラリを含めてビルドしようとすると、このエラーが出てしまうようだった。
(ライブラリを含めずにビルドを行うと、バイナリ生成まで問題なくできて、実行まで行ける。もちろん、ライブラリは含まれていないので、ゆっくり音声を生成する処理でエラーになるが)

どこで問題が起きているかの検討はついたのだが、そこをどう直せばうまくいくのかが分からず、一旦諦めてこのような形で公開することとした。

そのうちバイナリ化できる手立てが見つかったら、パッケージとして配布していけるように進めていけたらと思っている。

Macでゆっくりの声を使ってライブ配信できない現状を、このツールを公開することで少しずつ変えていけたらいいなと思っている。

需要はあると個人的には思っているけど、果たしてどうでしょうか?

Amazonアフィリエイト(アマゾン アソシエイト)におけるサイトの登録方法が簡単になっていた

最近、とあるサイトにAmazonアフィリエイトリンクを貼るための申請を行った。

Amazonアフィリエイトリンクを貼るためには、アマゾンアソシエイトのダッシュボードにある 問い合わせ からサイトを追加したい旨を書いたメッセージをカスタマーサポートに送る必要がある、というのが私の認識だったが、どうやらそのような手順は2020年の春頃から不要になっていたらしい。

ちなみに私はそのことを知らずにカスタマーサポートにメッセージを送っていたが、そのメッセージに対する返信の中でこのことを教えてもらった。

Amazonアフィリエイト(アマゾンアソシエイト)におけるサイトの追加方法 (2020年春以降)

では、どのようにサイトの追加を行うかというと、Amazonアソシエイトダッシュボードにログインした状態で下記のURLにアクセスして、 ウェブサイト情報の入力 のところに追加したいサイトのURLを入力して追加を行えば良い。たった、これだけだ。

https://affiliate.amazon.co.jp/home/account/profile/sitelist

ここでサイトの登録を行えば、すぐにAmazon アフィリエイトリンクを貼ることが可能だ。
(実際、私もすぐにリンクを貼ったが、問題はなさそうだ)

たった、これだけの手順でサイトの追加ができるようになっていたので、時間をかけることなく進められて嬉しい限りだった。

以上、ちょっとした備忘録であった。