『超速! Webページ速度改善ガイド』を読んだ

超速! Webページ速度改善ガイド ── 使いやすさは「速さ」から始まる (WEB+DB PRESS plus)

ここ最近、仕事でWebサイトのパフォーマンス改善をしているので、最新のベストプラクティスを押さえるために読みました。

パフォーマンス改善については、『ハイパフォーマンスWebサイト』という古典的名著があり、ここ最近の本としては『Webフロントエンド ハイパフォーマンスチューニング』などがあります。

パフォーマンス改善には、計測(ボトルネックの特定)=>改善というステップが存在します。本書は、計測方法や改善の方策が、具体的にわかりやすく解説されているのが特徴です。また、章立てが 基礎知識 => 調査と改善 で統一されているため、効率よく知識を身につけることができます。

Chrome DevTools はパフォーマンスの改善には欠かせないツールですが、高機能なぶんどのような機能があるか把握するのが大変です。本書では、ネットワーク・スクリプティング・ペインティング・メモリ等、様々なシーンでDevToolsを使って計測をする方法が解説されています。

また、改善方法も、小さなサンプルコードが載っているため、すぐに導入しやすいです。

Webサイトのパフォーマンスに悩んでいる人が、最初に読む本としてオススメ。


『React入門』を読んだ

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

『React入門』は、JavaScriptの定番ライブラリ、React.jsの入門書です。

Reactの入門書は他にもありますが、本書が特に優れているのは、実務で使う上で必要なポイントが書かれている点です。

Reactはそれほど機能の多くないビューライブラリで、Reactと他のライブラリを適宜組み合わせることで、リッチなユーザインターフェースを実現します。

しかし、従来の入門書の多くは、React本体の入門で終わっています。ある程度の規模になると必要になる状態管理ライブラリや、リッチなインタフェースを実現するためのUIライブラリ等、Reactは本体以外にも学ぶべきことが色々あります。

本書では、他の入門書で扱っているようなReact本体の入門は前半3分の1くらいで、その後はReduxやUIライブラリ、ユニットテスト、作ったアプリケーションの公開方法(GitHub PagesとFirebase)からサーバサイドレンダリングまで、より実践的なトピックを手厚く紹介しています。

Vue.jsやAngular等、モダンなJavaScriptフレームワークの経験者であれば、本書でいきなりReactに入門できると思います。そうでない人は、もう1冊他の入門書を挟んでから本書を読んだ方が良いかもしれません。

Webフロントエンド技術を扱った本の宿命として、本書もそれほど賞味期限は長くないので、早めに読むのがオススメです。

Gutenberg コードリーディングメモ

Gutenberg本体のコードを読む上で、とっかかりになりそうな部分をメモ。

registerBlockType()

https://github.com/WordPress/gutenberg/blob/master/blocks/api/registration.js#L51

色々バリデーションが書いてあって、最終的には

return blocks[ name ] = settings;

でローカル変数 blocks に第1引数(name)をキー、第2引数(settings)を値として追加する。

setAttributes()

BlockListBlock というブロックで定義されている。

https://github.com/WordPress/gutenberg/blob/master/editor/components/block-list/block.js#L184-L203

setAttributes( attributes ) {
  const { block, onChange } = this.props;
  const type = getBlockType( block.name );
  onChange( block.uid, attributes );

  const metaAttributes = reduce( attributes, ( result, value, key ) => {
    if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) {
      result[ type.attributes[ key ].meta ] = value;
    }

    return result;
  }, {} );

  if ( size( metaAttributes ) ) {
    this.props.onMetaChange( {
      ...this.props.meta,
      ...metaAttributes,
    } );
  }
}

ここではBlockListBlockがpropsに持っているonChange()メソッドが重要。

https://github.com/WordPress/gutenberg/blob/master/editor/components/block-list/block.js#L465

onChange( uid, attributes ) {
  dispatch( updateBlockAttributes( uid, attributes ) );
},

onChangeはStoreをdispatchする。属性の設定処理の本体はreducerにある。

https://github.com/WordPress/gutenberg/blob/master/editor/store/reducer.js#L129

case 'UPDATE_BLOCK_ATTRIBUTES':
  // Ignore updates if block isn't known
  if ( ! state[ action.uid ] ) {
    return state;
  }

  // Consider as updates only changed values
  const nextAttributes = reduce( action.attributes, ( result, value, key ) => {
    if ( value !== result[ key ] ) {
      // Avoid mutating original block by creating shallow clone
      if ( result === state[ action.uid ].attributes ) {
        result = { ...result };
      }

      result[ key ] = value;
    }

    return result;
  }, state[ action.uid ].attributes );

  // Skip update if nothing has been changed. The reference will
  // match the original block if `reduce` had no changed values.
  if ( nextAttributes === state[ action.uid ].attributes ) {
    return state;
  }

  // Otherwise merge attributes into state
  return {
    ...state,
    [ action.uid ]: {
      ...state[ action.uid ],
      attributes: nextAttributes,
    },
};

Gutenbergのコードを読むなら、ある程度Flux(およびその実装であるRedux)については理解しておく必要がある。

Gutenberg Handbook 読書メモ (3) Reference

↓の続き。今回はリファレンスをざっくり。

Gutenberg Handbook 読書メモ (1)
Gutenberg Handbook 読書メモ (2)

Attributes

https://wordpress.org/gutenberg/handbook/reference/attributes/

ソース(source)

属性のsourceはブロックの属性の値の取得方法を定義する。

それぞれのsourceは、第1引数にセレクタを取る(オプショナル)。セレクタが指定されると、sourceは、ブロック内で対応する要素を取得する。

内部では、sourcehpqライブラリのスーパーセットとなっている。

attribute

attributeを使うと、マークアップから属性を取得できる。

以下の例では、imgタグからsrc属性を抜き出している。

{
    url: {
        source: 'attribute',
        selector: 'img',
        attribute: 'src',
    }
}
// { "url": "https://lorempixel.com/1200/800/" }

text

textを使うと、マークアップの内側のテキストを取得できる。

{
    content: {
        source: 'text',
        selector: 'figcaption',
    }
}
// { "content": "The inner text of the figcaption element" }

html

htmlを使うとマークアップの内側のHTMLを取得できる。

{
    content: {
        source: 'html',
        selector: 'figcaption',
    }
}
// { "content": "The inner text of the <strong>figcaption</strong> element" }

children

childrenを使うとマッチした要素の子ノードを取得できる。Editableコンポーネントとの組み合わせでよく使われる。

{
    content: {
        source: 'children',
        selector: 'p'
    }
}
// {
//   "content": [
//     "Vestibulum eu ",
//     { "type": "strong", "children": "tortor" },
//     " vel urna."
//   ]
// }

query

queryを使うと、複雑なセレクタでマークアップから値を取得できる。

以下では、それぞれのimg要素からurlalt属性を抜き出している。

{
    images: {
        source: 'query'
        selector: 'img',
        query: {
            url: { source: 'attribute', attribute: 'src' },
            alt: { source: 'attribute', attribute: 'alt' },
        }
    }
}
// {
//   "images": [
//     { "url": "https://lorempixel.com/1200/800/", "alt": "large image" },
//     { "url": "https://lorempixel.com/50/50/", "alt": "small image" }
//   ]
// }

Meta

記事のメタデータ(post meta)から属性を取得することもできる。

attributes: {
    author: {
        type: 'string',
        source: 'meta',
        meta: 'author'
    },
},

このようにすると、ブロックからメタ属性に対して読み書きできるようになる。

edit( { attributes, setAttributes } ) {
    function onChange( event ) {
        setAttributes( { author: event.target.value } );
    }

    return <input value={ attributes.author } onChange={ onChange } />;
},

検討事項

デフォルトでは、メタフィールドは記事のオブジェクトのメタデータからは除外されている。この制限は、フィールドを明示的に可視化することで回避可能である。

function gutenberg_my_block_init() {
    register_meta( 'post', 'author', array(
        'show_in_rest' => true,
    ) );
}
add_action( 'init', 'gutenberg_my_block_init' );

さらに、WordPressは以下のようなデフォルト設定になっている点に注意が必要。

  • メタデータをユニークなものとしては扱わず、値の配列を返す
  • データを文字列として扱う

どちらの振る舞いも望ましくないなら、register_meta関数の呼び出しを以下のようにすることで、補うことができる。

function gutenberg_my_block_init() {
    register_meta( 'post', 'author_count', array(
        'show_in_rest' => true,
        'single' => true,
        'type' => 'integer',
    ) );
}
add_action( 'init', 'gutenberg_my_block_init' );

最後に、属性に値を設定する際はデータの型に注意が必要である。

function onChange( event ) {
    props.setAttributes( { authorCount: Number( event.target.value ) } );
}

Blocks support by themes

https://wordpress.org/gutenberg/handbook/reference/theme-support/

基本的に、ブロックのスタイルのカスタマイズはテーマ側のCSSで行うことができる。しかし、一部の機能はテーマ側での明示的な有効化(オプトイン)が必要になる。

function mytheme_setup_theme_supported_features() {
    add_theme_support( 'gutenberg', array(
        'wide-images' => true,
    ) );
}

add_action( 'after_setup_theme', 'mytheme_setup_theme_supported_features' );

Meta Boxes

https://wordpress.org/gutenberg/handbook/reference/meta-box/

※色々書いているけど一旦飛ばす

Glossary

https://wordpress.org/gutenberg/handbook/reference/glossary/

用語集

  • 属性ソース(Attribute sources): ブロックの属性の形を記述するオブジェクト
  • 属性(Attributes): ブロックの現在の状態を表現するオブジェクト
  • ブロック(Block): マークアップの単位を表す抽象的な用語。ブロックは組み合わせて使用され、Webページのコンテンツやレイアウトを構成する。
  • ブロックの名前(Block name): ブロック型のユニークな識別子
  • ブロック型(Block type): ブロックの振る舞いの定義
  • 動的ブロック(Dynamic block): 保存時にコンテンツが確定せず、記事が表示されるタイミングで描画されるブロック
  • Editable: リッチエディタ機能を提供するコンポーネント
  • Inspector: ブロック設定用のUIコンポーネント
  • 記事の設定(Post settings): 記事編集画面のサイドバーのこと
  • シリアライゼーション(Serialization): 記事の保存時にブロックの属性オブジェクトをHTMLマークアップに変換するプロセス
  • 静的ブロック(Static Block): 記事の保存時に内容が確定するブロック
  • TinyMCE: リッチエディタ(WYSIWYGエディタ)
  • ツールバー(Toolbar): ブロックの上に表示される一連のボタンのこと

Design Priciples

https://wordpress.org/gutenberg/handbook/reference/design-principles/

抽象的な話なので割愛

History

https://wordpress.org/gutenberg/handbook/reference/history/

内容がないので割愛

Coding Guidelines

https://wordpress.org/gutenberg/handbook/reference/coding-guidelines/

Gutenbergのコードに関することなので割愛。

Testing Overview

https://wordpress.org/gutenberg/handbook/reference/testing-overview/

JSはnpm testでテストが実行できる。

Jestのスナップショットテストを使っていて、スナップショットテストを使う際のベストプラクティス等について言及してある。

FAQ

https://wordpress.org/gutenberg/handbook/reference/faq/

いずれ公式で訳されるはず。

Gutenberg Handbook 読書メモ (2) Block Tutorial

Gutenberg Handbook 読書メモ (1) の続き。

Block

https://wordpress.org/gutenberg/handbook/blocks/

新しいブロック型を作るためのチュートリアル。

このチュートリアルのコードは下記GitHubリポジトリから入手できる。
https://github.com/WordPress/gutenberg-examples

なお、本記事のサンプルコードはESNextとJSXを使用しているが、Gutenbergのブロック機能はES5でも利用できる。

Writing Your First Block Type

https://wordpress.org/gutenberg/handbook/blocks/writing-your-first-block-type/

はじめに、固定されたメッセージを表示するブロック型を作成する。静的なコンテンツを含むブロックはJavaScriptでregisterBlockType関数を使うだけで実装できる。この関数はブロックの青写真となり、エディタ上での表示、編集・保存時の動作等を定義する。

ブロックのスクリプトを追加(enque)する

ブロック型はJavaScriptで実装されるが、WordPressのenqueue_block_editor_assetsアクションを使ってスクリプトがエディタに読み込まれるようにする必要がある。このアクションはwp_enqueue_scriptsと似ているが、エディタのスクリプトとスタイルに限定される点に違いがある。

<?php

function gutenberg_boilerplate_enqueue_block_editor_assets() {
    wp_enqueue_script(
        'gutenberg-boilerplate-es5-step01',
        plugins_url( 'step-01/block.js', __FILE__ ),
        array( 'wp-blocks', 'wp-element' )
    );
}
add_action( 'enqueue_block_editor_assets', 'gutenberg_boilerplate_enqueue_block_editor_assets' );

特に注意が必要なのは以下のように設定していること。

  • wp-blocks は、ブロック型の登録と関連する関数の読み込みを行う
  • wp-element はWordPressのElement抽象化機能を使用する

ブロックの登録

スクリプトが登録されたので、次にブロックを実装する。

const { registerBlockType } = wp.blocks;
const blockStyle = { backgroundColor: '#900', color: '#fff', padding: '20px' };

registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-01', {
    title: 'Hello World (Step 1)',

    icon: 'universal-access-alt',

    category: 'layout',

    edit() {
        return <p style={ blockStyle }>Hello editor.</p>;
    },

    save() {
        return <p style={ blockStyle }>Hello saved content.</p>;
    },
} );

ブロックを登録すると、エディタのinserterのダイアログで利用できるようになる。inserterではtitle, icon, categoryが利用される。アイコンは組み込みのアイコンから選ぶか、独自のsvg要素を指定する。

ブロック名は nemaspace/block-name という構造である必要がある。

editsave関数は、エディタ画面でのレンダリングと保存後のレンダリングを定義する。

Applying Styles From a Stylesheet

https://wordpress.org/gutenberg/handbook/blocks/applying-styles-with-stylesheets/

↑のサンプルではインラインでスタイルを定義しているが、外部のスタイルシートにスタイルを定義して、これを読み込む方法もある。

エディタは、それぞれのブロック型に対して、独自のクラス名を自動的に生成する。このクラス名には、editsave関数のオブジェクト引数からアクセスできる。

const { registerBlockType } = wp.blocks;

registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-02', {
    title: 'Hello World (Step 2)',

    icon: 'universal-access-alt',

    category: 'layout',

    edit( { className } ) {
        return <p className={ className }>Hello editor.</p>;
    },

    save( { className } ) {
        return <p className={ className }>Hello saved content.</p>;
    },
} );

クラス名は、ブロック名を元に生成される。

/* 対応するブロックは gutenberg-boilerplate-esnext/hello-world-step-02 */
.wp-block-gutenberg-boilerplate-es5-hello-world-step-02 {
    color: green;
    background: #cfc;
    border: 2px solid #9c9;
    padding: 20px;
}

エディタでのみ使用するブロックのアセットを登録(enque)する

enqueue_block_editor_assetsアクションを使うことでエディタでのみ使用するスタイルを登録できる。

<?php

function gutenberg_boilerplate_enqueue_block_editor_assets() {
    wp_enqueue_script(
        'gutenberg-boilerplate-es5-step02',
        plugins_url( 'step-02/block.js', __FILE__ ),
        array( 'wp-blocks', 'wp-element' )
    );
    wp_enqueue_style(
        'gutenberg-boilerplate-es5-step02-editor',
        plugins_url( 'step-02/editor.css', __FILE__ ),
        array( 'wp-edit-blocks' ),
        filemtime( plugin_dir_path( __FILE__ ) . 'step-02/editor.css' )
    );
}
add_action( 'enqueue_block_editor_assets', 'gutenberg_boilerplate_enqueue_block_editor_assets' );

エディタとフロントエンドで使用するアセットを登録(enque)する

ブロックのスクリプトはエディタでのみ必要なものだが、スタイルはサイトの表側とエディタの両方で読み込みたいこともある。

enqueue_block_editor_assetsアクションはエディタの読み込み時にしかトリガーされない。サイトの表側でも読み込まれるようにするには、enqueue_block_assetsアクションを使うのが良い。

<?php

function gutenberg_boilerplate_es5_enqueue_common_assets() {
    wp_enqueue_style(
        'gutenberg-boilerplate-es5-step02',
        plugins_url( 'step-02/style.css', __FILE__ ),
        array( 'wp-blocks' ),
        filemtime( plugin_dir_path( __FILE__ ) . 'step-02/style.css' )
    );
}
add_action( 'enqueue_block_assets', 'gutenberg_boilerplate_es5_enqueue_common_assets' );

JavaScriptをサイトの表側に限定したいなら、enqueue_block_assetsアクションを使った上で、! is_admin()の場合のみ読み込むようにすれば良い。

Introducing Attributes and Editable Fields

https://wordpress.org/gutenberg/handbook/blocks/introducing-attributes-and-editable-fields/

属性(attributes)

ブロックの出力は属性(attributes)の値によって決まる。

const { registerBlockType, Editable, source } = wp.blocks;

registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-03', {
    title: 'Hello World (Step 3)',

    icon: 'universal-access-alt',

    category: 'layout',

    attributes: {
        content: {
            type: 'array',
            source: 'children',
            selector: 'p',
        },
    },

    edit( { attributes, className, focus, setAttributes, setFocus } ) {
        const { content } = attributes;

        function onChangeContent( newContent ) {
            setAttributes( { content: newContent } );
        }

        return (
            <Editable
                tagName="p"
                className={ className }
                onChange={ onChangeContent }
                value={ content }
                focus={ focus }
                onFocus={ setFocus }
            />
        );
    },

    save( { attributes, className } ) {
        const { content } = attributes;

        return <p className={ className }>{ content }</p>;
    },
} );

新しいブロック型を登録する際は、attributesプロパティによってeditsave関数で受け取りたいオブジェクトの形を指定できる。それぞれの値はsource function(???)であり、ブロックをマークアップするための値を取得する。

上記サンプルコードでは、contentの値を子供のp要素から取得している。

コンポーネントとEditableコンポーネント

ここまで、createElement関数(またはJSX)を使ってDOMノードを作成してきた。これらの振る舞いは、「コンポーネント」に閉じ込めることもできる。このような抽象化を行うことで、処理の共通化や複雑さの隠蔽といった恩恵を得ることができる。ブロックを作成する際に利用できるコンポーネントがいくつも用意されている。サンプルコードではEditableコンポーネントを使用している。

Editableコンポーネントはtextarea要素の機能強化版である。太字、イタリック体、ハイパーリンク等の機能を持っている(Editableコンポーネントは、現行のビジュアルエディタと同様、TinyMCEを使って実装されている)。

Block Controls: Toolbars and Inspector

https://wordpress.org/gutenberg/handbook/blocks/block-controls-toolbars-and-inspector/

ブロックのカスタマイズをシンプルにして、ユーザに一貫した体験を提供するため、エディタのプレビュー機能の提供を助ける組み込みのUIパターンが用意されている。wp.blocksグローバル変数は、エディタのインタフェースとして利用できる、いくつかの共通コンポーネントを含んでいる。

ツールバー

ユーザがブロックを選ぶと、その上にツールバーが現れ、その上にいくつものボタンが表示される。これらのブロック用コントロールは、選択された要素がEditableコンポーネントである場合などに自動的に表示される。

ツールバーをカスタマイズして、特定のブロック専用のコントロールを読み込むこともできる。ブロック型のedit関数の戻り値がBlockControls要素を含んでいれば、これらのコントロールがブロックのツールバーに表示される。

const {
    registerBlockType,
    Editable,
    BlockControls,
    AlignmentToolbar,
    source
} = wp.blocks;

registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-04', {
    title: 'Hello World (Step 4)',

    icon: 'universal-access-alt',

    category: 'layout',

    attributes: {
        content: {
            type: 'array',
            source: 'children',
            selector: 'p',
        },
    },

    edit( { attributes, className, focus, setAttributes, setFocus } ) {
        const { content, alignment } = attributes;

        function onChangeContent( newContent ) {
            setAttributes( { content: newContent } );
        }

        function onChangeAlignment( newAlignment ) {
            setAttributes( { alignment: newAlignment } );
        }

        return [
            !! focus && (
                <BlockControls key="controls">
                    <AlignmentToolbar
                        value={ alignment }
                        onChange={ onChangeAlignment }
                    />
                </BlockControls>
            ),
            <Editable
                key="editable"
                tagName="p"
                className={ className }
                style={ { textAlign: alignment } }
                onChange={ onChangeContent }
                value={ content }
                focus={ focus }
                onFocus={ setFocus }
            />
        ];
    },

    save( { attributes, className } ) {
        const { content } = attributes;

        return <p className={ className }>{ content }</p>;
    },
} );

ブロックが選択されている場合のみBlockControlsを読み込むよう注意が必要である。↑ではfocus変数の値を見ることで、BlockControlsの条件レンダリングを実現している。

Inspector

フォームのフィールドのためにより広い編集エリアがほしい場合、inspectorを使うことができる。InspectorControlsを読み込んで、edit関数で表示すればよい。