at backyard

Color my life with the chaos of trouble.

cssフレームワークのBULMAで text-align:center を実現したい場合は "has-text-centered"を使う

BULMAを使っていて text-align:center はどうやるのか、一瞬迷った。

ドキュメント上で cetner または text-align などと検索しても出てこないので、ないのかなと一瞬思ったがそんなことはなく、探した方が悪かっただけだ。

BULMAでtext-align:centerを実現する場合は"has-text-centered"を使う

該当するドキュメントは下記となる。

https://bulma.io/documentation/helpers/typography-helpers/#alignment

実際にHTMLで記述する場合は

<div class="has-text-centered">hogehoge</div>

と指定することで中央に配置される。

余談: Bootstrapでtext-align:centerを実現する場合は "text-center"

ちなみにBootstrapで同じことをやりたい場合は text-center となる。

<div class="text-center">hogehoge</div>

参照するドキュメントは下記

https://getbootstrap.jp/docs/5.0/utilities/text/#text-alignment

YouTube Premiumを3ヶ月無料で試せるキャンペーンの正体について調べてみた

YouTube Premiumをまだ試していない人は1ヶ月無料でYouTube Premiumを試すことができる。

だが、YouTube Premium で検索すると、検索結果に3ヶ月無料でという文字が踊る。
(下記の画像を参照)

f:id:shinshin86:20210724234320p:plain
どうせなら三ヶ月無料で利用したいところだ

条件を満たしているなら3ヶ月無料で利用できるキャンペーンを用いたほうが当然お得である。

そこで今回はYouTube Premiumを3ヶ月無料で試せるキャンペーンについて調べてみた。

ちなみに私は既にYouTube Premiumであり、無料特典は1ヶ月無料で行っているので、今回紹介しているものを試すすべはない。

「ここで書いてあったから試してみたのにできなかったじゃないか!」

という苦情には答えられないので、試す際は一応ご自身でも調べてから実行してみてほしい。
(キャンペーン対象期間が過ぎていることも考えられるので)

ちなみにこのキャンペーンを行っているのは、Google Asia Pacific Pte. Limited で、名前から察するにアジアを中心に展開しているキャンペーンなのではないだろうかと予測する(完全に自身の勝手な予想です)

目次

Google Oneを利用しているならYouTube Premiumを3ヶ月無料で試せる

まず上の検索結果に登場してきたキャンペーンだが、これはGoogle One を利用しているユーザに対して実行されるキャンペーンのようだ。
(既に検索結果に書いてあるが)

YouTube Premium を 3 か月間無料でお楽しみいただけます | YouTube

ただGoogle Oneを利用しているユーザに対して、どうやれば3ヶ月無料で試せるようになるのかが分からない。

上のキャンペーンページからの誘導も特にないので、これは完全に置いてけぼりを食らうパターンではないだろうか。

Googleは一見便利そうなキャンペーンを行っていても、それを実現させるまでの道のりが非常に分かりにくいというパターンが割とよくある印象。
もし私が村上春樹の小説の主人公なら、ここは「やれやれ」とつぶやくところだろう。

やれやれ

Chromebookを利用しているユーザならYouTube Premiumを3ヶ月無料で試せる

上の特典内容と若干似ているが、Chromebookを利用しているユーザならYouTube Premiumを3ヶ月無料で試せるようだ。

こちらキャンペーンページが用意されており、YouTube Premiumの特典についても下記のページからたどり着けるようになっている。

Chromebook の特典 - Google Chromebooks

試しに私がMacBookからアクセスしてみたところ、下記のような画面になったので、後はきみの目で確かめてみてくれ!

f:id:shinshin86:20210724235339p:plain

Y!mobileの回線を利用しているユーザならYouTube Premiumを3ヶ月無料で試せる

次はGoogle以外が主催しているキャンペーン。Y!mobile

Y!mobileの回線を利用しているユーザがそのSIMカードを挿入したiPhoneスマートフォンまたはタブレットから YouTube Premium に申し込むと、3ヵ月無料で利用できるようだ。

詳細は下記のキャンペーンページを確認してほしい。

YouTube Premium 3ヵ月無料キャンペーン|Y!mobile - 格安SIM・スマホはワイモバイルで

ソフトバンクユーザならYouTube Premiumを3ヶ月無料で試せる

こちらはソフトバンクユーザ向けのキャンペーン。

上のY!mobileと同じく、ソフトバンクの回線を利用しているユーザがそのSIMを設定した iPhoneiPadGoogle Pixel、その他Android搭載のスマートフォンまたはタブレットから YouTube Premium に申し込むと、3ヵ月無料で利用できるらしい。

YouTube Premium 3ヵ月無料キャンペーン | スマートフォン・携帯電話 | ソフトバンク

auを利用しているユーザならYouTube Premiumを3ヶ月無料で試せる

auからの加入でも同じように3ヶ月無料キャンペーンが試せる。

YouTube Premiumのご紹介 | エンタメ | au

Pokémon GOのトレーナーならYouTube Premiumを3ヶ月無料で試せる

いくつか通信業者経由でのキャンペーンを紹介してきたが、次はPokémon GO。

Pokémon GOのトレーナーならYouTube Premiumを3ヶ月無料で試せるらしい。

詳細は下記のページを見てみてほしい。

トレーナーの皆さん:YouTube Premium 3ヶ月分をプレゼント! – Pokémon GO

というわけで、YouTube Premiumを3ヶ月無料で試せるキャンペーンについて興味本位で調べてみた。

私自身は既にYouTube Premiumを利用しているため、これらのお得なキャンペーンの恩恵にあずかることはできないが、もしまだYouTube Premiumを利用していない方でこれらの条件にあう方は試してみてほしい。

また、他にもこんなキャンペーンがあるよ、というのがあればコメントください。

Googleが検索結果にAMP表示を出すことをやめたことについて。また、Habanero Beeの今後の対応について。

モバイルの検索結果からAMP対応を示す雷マークが消えた

9to5Googleの下記の記事によると、Googleの検索結果からAMPに対応していることを表す雷マークを削除したようだ。

9to5google.com

確かに実際に検索してみると、雷マークが表示されなくなっている。
(自身のサイトを例に取り恐縮だが、下記の自身のサイトはAMP対応している。以前だったらAMP対応済みであることを示す雷マークが表示されていたが、現在は既に検索結果から消えている。)

f:id:shinshin86:20210722074815p:plain
以前であれば表示されていた雷マークが消えている

※なお、上のキャプチャはChromeのモバイル表示でスクショしているが、iPhoneSafariから検索しても同様の表示となっていることを確認している

ちなみにAMPのアイコンは消えているものの、検索結果からアクセスするとAMPページにはアクセスされる。
ここの挙動については変更はされていない。

なお、Googleはこの変更について4月に発表していた。
すでにインターネットのメディアで取り上げられていたのを見て、知っている方も多いだろう。

AMP対応する意味はなくなったのか?

さて、このようにGoogleの検索結果からはAMP対応かどうかは分からないような変更が加わったが、ではそもそもAMP対応することの意味はなくなったと言えるだろうか、というと、

まだ、AMP対応する意味はある(と思いたい)。

そもそもの話だが、AMP対応自体の根本的な意義としては、ウェブのベストプラクティスをサイト側に行わせるための施策である。
よってAMPに対応しているということは、必然的にパフォーマンスの良い使用感に優れたサイトであることは間違いない。
(もちろん代償としてはJavaScriptを利用したリッチな表現ができなかったり、それなりに大きい)

そのためAMPに対応すること自体の意味はなくなっていないと私は考えている。

ただ、検索結果のAMPアイコンなどは多少は、AMP非対応のサイトとの(見た目的な意味での)差別化要因となっていたので、これが今後クリック率にどう影響を与えるかはチェックしておいたほうが良いと思われる。

私の個人的な意見としては、AMPにきっちり対応しなくとも、ある程度パフォーマンスに優れたサイトを構築できれば、それでいいのかも知れないと考えている。
(AMP対応することによる制約の大きさによる苦労は、Habanero Beeの開発を通じてそれなりに味わった)

Habanero Beeの今後の対応方針

f:id:shinshin86:20210722080053p:plain
AMPに対応した静的サイトを素早くデプロイできるオープンソースプロジェクト、Habanero Bee

最後に私が開発しているHabanero Beeについての今後の対応方針について述べていく。

※Habanero Beeとはなんぞや?という方は下記の記事を読んでください。

zenn.dev

現状では、AMP対応のサイトを構築できるというメリットの打ち出しをやめる気はない。

上でも書いたとおり、AMPに対応できるということは必然的にある程度のパフォーマンスを確保できることと同義だからだ。

そのため、現状開発の方針を変える気はないが、引き続きAMPに関する話題は追っていく予定である。

AMP対応することの大変さ

Habanero BeeはAMP対応を打ち出しているが、やはりというか、実際に開発してみて思い知ったのはAMP対応することによる制約の大きさだ。

リッチな表現ができないぐらいであれば許容できる範囲だったが、Google AdSenseや新しいGoogle Analytics(GA4)に対応していないなど、Googleが関わっているプロジェクトなのにも関わらずAMPに関する対応は明らかに不足している。
(おそらく中の人も大変なのだろうとは思う)

zenn.dev

これまではAMP対応することによるメリットを天秤にかけた上でこれらのことには目をつむってきたが、今後AMP対応に関するメリットよりもこれらの不満のほうが大きくなればHabanero Beeの方針を変更するかも知れない。
(もしくは似たようなプロジェクトを別に作成するかも知れない。どちらがいいのだろう?AMP対応がさくっと行えることは一つのアドバンテージであると思うが、それが未来永劫続いていくわけではないだろう)

というわけで、今のところHabanero Beeの方針を変更する予定はないが、今後のAMPに関する状況、またモバイル検索結果に関する何かしらの変更があれば、都度都度考えては行くと思う。

BootstrapのBorder spinner (ローディングスピナー) が動かないときにチェックすること(Bootstrap v4)

f:id:shinshin86:20210720074601p:plain

Bootstrap(v4)の下記のページを参考にしてBorder spinnerを表示させようとした際、そのままコードをコピペしただけでは動かなかった。

getbootstrap.com

本来ここに表示されているコードをそのままコピペするだけで動くはずだが、コピペしてもローディングスピナーは表示されない。

はて?と疑問に思ったが、読み込んでいるBootstrapのversionが古かったのが原因のようだ。

下記は読み込んでいるBootstrapのバージョンを修正した際のdiff。

diff --git a/public/index.html b/public/index.html
--- a/public/index.html
+++ b/public/index.html
@@ -26,8 +26,8 @@
     -->
     <link
       rel="stylesheet"
-      href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
-      integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
+      href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
+      integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
       crossorigin="anonymous"
     />
     <script

このようにv4の最新版( こちら )のversionのBootstrapを読み込むようにすることでローディングスピナーが表示されるようになった。

娘の記録(育児日記メモ)

最近の娘のことを簡潔に書いていく。

数年後、この文章を読んで、「こんなこともあったな〜」と思い返せるような未来の自身に当てた育児日記的な役割を果たすことになる。

保育園に通い出した

今年度から娘は保育園に通いだしている。
最初の方は保育園に送りに行くと泣き出すこともあったものの、すぐに慣れて、今では送っていくと保育園の先生にじゃれついたり、おもちゃを持って遊びだしたりして楽しそうだ。

保育園に通いだしてから体調を崩すようになった。
これは 育児あるある のようで、保育園で他の園児から菌をもらったりするなどして、すぐに体調を崩してしまうようだ。
現にいまもRSウィルスというのが流行っており、半数近くの園児たちが休んでいる。
(幸いにも娘は大丈夫なようだが)

なお、娘が体調を崩すようになったのにつられて、我々も体調を崩すようになった。

娘が鼻水と咳を出すようになると、数日後には私も同じ症状が出る、といった形で、明らかにうつされている形だ。

こういう親御さんは多いらしい。なるほど、子供の症状は親にもきっちり伝染るのだ。

娘は頑固&破天荒。そしておしゃべり

これは生まれた直後から病院の方々にも言われていたことだが、娘は意思がはっきりしている子供で、「自分が一度決めたことはやり抜くまでやめないし、なにか言われても頑なに意思を変えることは絶対にしない性格」である。

それは今も健在で、超がつくほど頑固である。
こういうのは大変な反面、まあ頼もしいというか、将来起業家にでもなりそうな雰囲気を感じるが、まあこういうのはどこにでもある『親バカ思考』なのだろうなと妻と笑うことがある。

あと遊んでいたぬいぐるみに対していきなり冷徹な攻撃を食らわしたり、少しサイコパスっている一面も見せているのが、まあ大丈夫だろう。

そして元気いっぱいな破天荒ぶりも見せる。

娘は基本的に自分で何もかもしたいタイプのようで、外に行くときも抱っこをするとキレる。
自分で歩くのは問題ないが、危なっかしく階段を登り始めたり、いきなり走り出して転んで泣く、という行為を3秒ぐらいの低スパンで実現させるなど、「目が離せない行動」が多い。

さすがに道路付近では手は絶対に離さないが、まあ異常に行動的な側面を持っており、目が離せないことが多い。

そして娘はかなりおしゃべりだ。

起きているあいだじゅう、ずっとおしゃべりしながら何かをしている。

体は小さいのに、よくスタミナが尽きないな〜といった感じである。

これは保育園の先生からもよく言われることで、

「〇〇ちゃんはずっとおしゃべりしていますね〜」

とよく言われる。

日中、そんな感じでずっとフルスロットルで生きているため、眠りだすと、もう深く眠る。

夜泣きをすることが殆どない。

そういう点で我々夫婦は恵まれているのかも知れない。

少し前にもツイートしたが、そう言えば最近は様々な言葉を発するようになった。

これからいろいろな言葉を発していくようになるだろう。

(そう言えば少し前に急に私のことを「先生」と呼び始めて何事かと思った)

そんなわけで娘の記録は以上となる。

健やかにこのまま育っていってくれ。

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

Reactを自作するための学習の記録の続き

この続き

shinshin86.hateblo.jp

参照ドキュメント

現在ここに記述しているコードは、下記のドキュメントのほぼ写経となっています。

pomb.us

zenn.dev

関数コンポーネントに対応する

現在の実装では関数コンポーネントに対応していない。

例えば下記のようなコードを書いた場合、ブラウザアクセス時にエラーが発生する。

import { createElement, render } from "./Didact";

/** @jsxRuntime classic */
/** @jsx createElement */
const App = props => (
  <div>
    <h1>Hi {props.name}</h1>
    <ul>
      <li>aaa</li>
      <li>bbb</li>
      <li>ccc</li>
    </ul>
  </div>
) 

const element = <App name="foo" />

const container = document.getElementById("root")

render(element, container)

エラーの内容はこんなやつ

Uncaught DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('props => Object(_Didact__WEBPACK_IMPORTED_MODULE_0__["createElement"])("div", {
  __self: undefined,
・
・
・

関数コンポーネントは、クラスコンポーネントとは下記のような異なる点があるため、現在の実装では正常に処理が行えない。

  • 関数コンポーネントからのファイバーにはDOMノードが存在しない
  • 子要素は、propsから直接取得するのではなく、関数を実行することから取得できる

そのため、 performUnitOfWork関数でファイバーの型が関数かどうかを確認して、処理を振り分けるようにする必要がある。

以前と同じ処理は、updateHostComponentで実行するようにし、関数コンポーネントの場合は、 updateFunctionComponent で処理を実行するようにコードを追加していくことにする。
(参照元のドキュメントに倣っています)

渡されたコンポーネントが関数だった場合、 updateFunctionComponent では関数を実行して子を取得するようにしていく。 子要素を取得できれば、差分検出の処理などは同じように機能するため、以後の処理に変更を加える必要はない。

ただし、commitWork関数については変更の必要がある。
関数コンポーネントの場合、DOMノードがないfiberであるため、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 isNew = (prev, next) => key => prev[key] !== next[key]
const isGone = (prev, next) => key => !(key in next)
const isEvent = key => key.startsWith("on")
const isProperty = key => key !== "children" && !isEvent(key)

const createDom = fiber => {
    const dom = fiber.type === "TEXT_ELEMENT"
        ? document.createTextNode("")
        : document.createElement(fiber.type)

    updateDom(dom, {}, fiber.props)

    return dom
}

const updateDom = (dom, prevProps, nextProps) => {
    // Remove or modify event listeners
    Object.keys(prevProps)
        .filter(isEvent)
        .filter(
            key =>
                !(key in nextProps) ||
                isNew(prevProps, nextProps)(key)
        )
        .forEach(name => {
            const eventType = name
                .toLowerCase()
                .substring(2)
            dom.removeEventListener(
                eventType,
                prevProps[name]
            )
        })

    // Delete old properties
    Object.keys(prevProps)
        .filter(isProperty)
        .filter(isGone(prevProps, nextProps))
        .forEach(name => {
            dom[name] = ""
        })

    // Set a new or changed property
    Object.keys(nextProps)
        .filter(isProperty)
        .filter(isNew(prevProps, nextProps))
        .forEach(name => {
            dom[name] = nextProps[name]
        })

    // Add a new event listener
    Object.keys(nextProps)
        .filter(isEvent)
        .filter(isNew(prevProps, nextProps))
        .forEach(name => {
            const eventType = name
                .toLowerCase()
                .substring(2)
            dom.addEventListener(
                eventType,
                nextProps[name]
            )
        })
}

const render = (element, container) => {
    wipRoot = {
        dom: container,
        props: {
            children: [element]
        },
        alternate: currentRoot,
    }

    deletions = []

    nextUnitOfWork = wipRoot
}

let wipRoot = null
let currentRoot = null
let nextUnitOfWork = null;
let deletions = null

const commitRoot = () => {
    commitWork(wipRoot.child)
    currentRoot = wipRoot
    wipRoot = null
}

const commitWork = fiber => {
    if (!fiber) {
        return
    }

    // DOMノードを持つファイバーが見つかるまでファイバーツリーを上に移動
    let domParentFiber = fiber.parent
    while (!domParentFiber.dom) { domParentFiber = domParentFiber.parent }
    const domParent = domParentFiber.dom


    if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
        domParent.appendChild(fiber.dom)
    } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
        updateDom(fiber.dom, fiber.alternate.props, fiber.props)
    } else if (fiber.effectTag === "DELETION") {
        commitDeletion(fiber, domParent) // ノードを削除するときは、DOMノードを持つ子が見つかるまで探索を続行
    }

    commitWork(fiber.child)
    commitWork(fiber.sibling)
}

const commitDeletion = (fiber, domParent) => {
    if (fiber.dom) {
        domParent.removeChild(fiber.dom)
    } else {
        commitDeletion(fiber.child, domParent)
    }
}

const workLoop = deadline => {
    let shouldYield = false

    while (nextUnitOfWork && !shouldYield) {
        nextUnitOfWork = performUnitOfWork(
            nextUnitOfWork
        )

        shouldYield = deadline.timeRemaining() < 1
    }

    if (!nextUnitOfWork && wipRoot) {
        commitRoot()
    }

    requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

const performUnitOfWork = fiber => {
    // 関数かどうかを確認
    const isFunctionComponent = fiber.type instanceof Function

    if (isFunctionComponent) {
        updateFunctionComponent(fiber)
    } else {
        updateHostComponent(fiber)
    }


    if (fiber.child) {
        return fiber.child
    }

    let nextFiber = fiber

    while (nextFiber) {
        if (nextFiber.sibling) {
            return nextFiber.sibling
        }

        nextFiber = nextFiber.parent
    }
}

const updateFunctionComponent = fiber => {
    const children = [fiber.type(fiber.props)]

    reconcileChildren(fiber, children)
}

const updateHostComponent = fiber => {
    if (!fiber.dom) {
        fiber.dom = createDom(fiber)
    }

    reconcileChildren(fiber, fiber.props.children)
}

const reconcileChildren = (wipFiber, elements) => {
    let index = 0
    let oldFiber = wipFiber.alternate && wipFiber.alternate.child
    let prevSibling = null

    while (index < elements.length || oldFiber != null) {
        const element = elements[index]
        let newFiber = null

        const sameType = oldFiber && element && element.type === oldFiber.type

        // If the old fiber and the new element are of the same type, keep the DOM node and update it with the new props
        if (sameType) {
            // Update process for nodes
            newFiber = {
                type: oldFiber.type,
                props: element.props,
                dom: oldFiber.dom,
                parent: wipFiber,
                alternate: oldFiber,
                effectTag: "UPDATE",
            }
        }

        // If the type is different and there is a new element, a new DOM node needs to be created
        if (element && !sameType) {
            // Adding a node
            newFiber = {
                type: element.type,
                props: element.props,
                dom: null,
                parent: wipFiber,
                alternate: null,
                effectTag: "PLACEMENT",
            }
        }

        // If different types and old fibers are present, delete the old node
        if (oldFiber && !sameType) {
            // Deletion process of old fiber nodes
            oldFiber.effectTag = "DELETION"
            deletions.push(oldFiber)
        }

        if (oldFiber) {
            oldFiber = oldFiber.sibling
        }

        if (index === 0) {
            wipFiber.child = newFiber
        } else {
            prevSibling.sibling = newFiber
        }

        prevSibling = newFiber
        index++
    }
}

export {
    createElement,
    render
}

useState(hooks)に対応させる

関数コンポーネントの対応の次は useStateにも対応させる。

クリックするたびに、stateのカウンター値が1つ増えるような、サンプルとしてよくある例にコードを変更する。

index.jsを下記のように変更して、これが動くようにDidact.jsを修正していく。

// index.js
import { createElement, render, useState } from "./Didact";

/** @jsxRuntime classic */
/** @jsx createElement */
const App = () => {
  const [ count, setCount ] = useState(1)

  return (
    <h1 onClick={() => setCount(count => count + 1)}>
      Count: {count}
    </h1>
  )
}

const element = <App name="foo" />

const container = document.getElementById("root")

render(element, container)

関数コンポーネントuseState を呼び出すとき、既に古いフックがあるかどうかを確認し、 hookIndexを用いてwipFiber.alternate をチェックするようにする。
もし古いフックがある場合は、stateを古いフックから新しいフックにコピーする。そうでない場合は初期化。

新しいフックをファイバーに追加する際は、 hookIndexを1つインクリメントしてstateを返すようにする。

また useState はstateを更新する関数を返す必要があるため(ここでは setCount 関数)、アクションを受け取る setState関数を定義する。

そのアクションをフックに追加したキューにpushする。

そして、作業のループが新しいレンダリングフェーズを開始できるように、新しい wipRootを次の作業単位として設定する。

ここらへんのコードは下記のように実装。
(何度もしつこいですが、参照元の写経です)

const useState = initial => {
    const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex]
    const hook = {
        state: oldHook ? oldHook.state : initial,
        queue: []
    }

    const actions = oldHook ? oldHook.queue : [];
    actions.forEach((action) => {
        hook.state = action(hook.state);
    });

    const setState = action => {
        hook.queue.push(action)
        wipRoot = {
            dom: currentRoot.dom,
            props: currentRoot.props,
            alternate: currentRoot,
          }
        nextUnitOfWork = wipRoot
        deletions = []
    }


    wipFiber.hook.push(hook)
    hookIndex++
    return [hook.state, setState]
}

・
・
・

export {
    createElement,
    render,
    useState
}

最後に

ひとまずドキュメントを写経しながらという形でReact自作に向けた学習を進めてきた。

が、一旦この学習はここで中断する。

他にやりたいタスクが残っているのと、Reactを1から自作するというのはなかなか時間がかかりそうだということがわかり、それに対して今は時間を確保できなそうだからだ。

あと、Fiber周りの仕組みなどがまだまだ理解が追いついていないので、ここについてはもう少し復習しても良さそうに思える。

実際のReactのコードも参照しつつ進めていたが、React本家のコードを細かく追っていくのもなかなかに骨が折れる作業だということも分かった。

ただ、Reactがどういう仕組で実際にHTMLを生成していくのか、差分検出はどのような考え方で実行されているのか?など今までほぼほぼブラックボックスとなっていた中身が想像できるようになったのは、今後もReactを触っていく上では大きいことのように思う。

また時間を見つけてReact自作に向けた学習は進めていく予定。

なお、ここまでのコードは下記に載せている。
後々またこのリポジトリにコードを追加していくかもしれないので、コミットログと併せてご参照ください。
(この時点での最新のコードはコミットハッシュが 23bc60265d2a1813f1751708536d4b4d9298eecc となっています)

github.com

MDN Web Docsへのコントリビュート手順(備忘録)

f:id:shinshin86:20210715224844p:plain

これはMDN Web Docsへのコントリビュートを実際に行った際の手順などをメモした備忘録となる。

目次

アカウントを作成(これは不要かもしれない)

MDN Web Docsにアクセスすると、画面右上にあるSigninからアカウントを作成できる。
これが必須なのかはわからないが、とりあえずアカウントは作成してみた。
GitHub連携かGoogle連携か選べたので、GitHub連携で作成してみた。

ただ、その後の作業手順を考えると、別にコントリビュートをするのにアカウントは作らなくても良かったのではないかとも思ったりした。
(コントリビューション自体はGitHubアカウントがあればできそうだったので)

コントリビューションに関するMDNのドキュメント

コントリビューションに関するMDNのドキュメントは下記に記載されている。

まずはこちらを読んだ。

content/README.md at main · mdn/content · GitHub

MDNへコントリビューションする際はGitHubアカウントが必須

修正作業はGitHub経由で行われるのでアカウントを持っていない方はアカウントを作成するところから始める必要がある。

MDN の構成について

MDNのドキュメントとGitHubで格納されている実際のファイルの関係については下記に記載がある。

content/README.md at main · mdn/content · GitHub

英語以外のドキュメントにコントリビューションしたいとき

ちなみに上記リポジトリは英語のドキュメントのみを扱っている。

他の言語のドキュメントについてコントリビューションを行いたい場合は、下記のリポジトリを見る必要がある。

GitHub - mdn/translated-content: All translated MDN content in raw form

なお、現在は下記の言語しかコントリビューションを受け付けていないようだ。
これはアクティブなメンテナンスチームの言語のみを表しているとのこと。

  • fr
  • ja
  • ko
  • pt-BR
  • ru
  • zh (zh-CN and zh-TW)

修正対象のファイルの探し方

上の MDN の構成について でも書いたが、URLを見ながら実際に修正対象となるファイルを探していくことになる。

MDN Web Docsへコントリビューションする際に事前に知っておくべき情報は、これで以上となる。

ここからは実際にリポジトリで修正作業を行った際、修正内容をローカルで確認する方法について記載していく。

ローカルサーバで修正したドキュメント内容を確認する方法

1. まずは下記のリポジトリをどちらもcloneする

下記がMDN Web Docsの本体(英語のみ)

github.com

下記がMDN Web Docsの多言語に関するファイルが格納されている。

github.com

2. content repoのセットアップ

cloneした2つのリポジトリのうち、contentのルートで下記のコマンドを打って依存パッケージをインストールする
(ちなみに Node.jsのインストールと、 yarnのインストールは事前に済ませておくこと)

yarn install

3. 環境変数を追加

cloneしていたもう一つのリポジトリ translated-contentfiles のパスを CONTENT_TRANSLATED_ROOT にセットした状態で yarn startを行う

実際に打つ際のコマンドは下記。

CONTENT_TRANSLATED_ROOT=/path/to/translated-content/files yarn start

もしくは export環境変数を設定してからでも良い。

export CONTENT_TRANSLATED_ROOT=/path/to/translated-content/files
yarn start

すると、ローカルサーバが立ち上がるので、 localhost:5000 にアクセスして確認が可能。
(アクセスした画面から指定のページに遷移し、 Change Language から指定の言語に変更できる)

環境変数追加の別の方法

上のやり方以外にも .env ファイルを用いて設定する方法もある。 こちらであれば以降はyarn startだけでOKとなる。

echo CONTENT_TRANSLATED_ROOT=/path/to/translated-content/files >> .env

# .envに設定情報は記憶されるので、あとは "yarn start"でOK
yarn start

ローカルでの確認方法については以上である。

実際にコントリビューションしてみた

今回修正しようと思ったファイルはこちらである。

https://developer.mozilla.org/ja/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning

日本語の誤字を見つけたので、その修正PRを作成することにした。

まずは上にも書いたtranslated-contentリポジトリを自身のGitHubアカウントにforkする

github.com

次は人によるかもしれないが、私はローカルに一度cloneしてから修正PRを作成、

上で書いたローカルサーバを立ち上げる方法で修正内容を確認したりしながら作業を実施。

実際に作成したPRが下記となる。

github.com

修正で凡ミスをしてしまっており恥ずかしい。 またPRを見てもらうと分かる通り、日本人の方に対応していただき、コミュニケーションは日本語で行うことができた。
(まだ一度PRを作っただけのため、毎回、日本語で行えるかどうかまでは分からない)

またPRのやり取りを見てもらうと分かる通り、実は日本語のドキュメントには修正必要な箇所や、不要なfontタグがソース内に散りばめられており(自動生成した際の痕跡とか?)、修正が必要な箇所は結構ありそうでした。

なお、日本語のドキュメントを修正する際は下記のガイドラインにも目を通しておくと良さそうだ。

github.com

というわけで、もしこの記事がMDNへコントリビューションしようか迷っている方の背中を押すきっかけになれば幸いです。

ちなみにMDNのPRを作成すると、最初に push した分の修正に対してPreview URLが発行されるので、そちらで一度修正内容を確認してみるのも良さそうです。
(自分はそこで確認するのも漏れていたので、これは自分に対する戒めでもあります。)