読み込む画像のCLSと遅延読み込みについて

Googleの、Search Consoleの「ウェブに関する主な指標」で不良のURLがありました。
引っかかっていたのは「CLS に関する問題: 0.25 超(モバイル)」です。

他の指標としてはLCP(Largest Contentful Paint: コンテンツの初回ペイント)やFID(First Input Delay: 初回入力遅延)がありますが、今回問題のCLSを中心に画像読み込みについて調べました。

Cumulative Layout Shift (CLS)

累積レイアウト変更。
ページを読み込んだときに、後から画像や動画が読み込まれて、その下の画像やテキストががくんと下がるものです。 Search Consoleでは値が大きいほど良くないとされ、0.25を超えると「不良」と言われます。

テキストよりも画像や動画の方が容量が大きいため、読み込みが遅れて一瞬レイアウトが崩れてしまいます。

解決法

画像が読み込まれる前に、あらかじめ画像の表示領域を確保しておくというものです。

方法1:imgタグに widthheight 属性を設定する

先にhtmlの widthheight の値を読んで領域確保→画像を読み込む、という動きなのでしょう。
しかしレスポンシブなページに固定値は書けない...その場合は元画像のサイズを入れておく、という記事も見かけました。

☆ちなみにwidthheight 属性の値はピクセル(「px」不要)または%で指定します。

方法2:cssでレスポンシブな箱を用意する

img 要素をさらに divp 要素で囲います。
レスポンシブな背景画像を配置するように、親要素(divp)に 画像の縦横比に合う padding-topを、さらに position: relative。子要素(img)には position: absolute をかけます。
個人的には position: absolute を乱用するのは気が進まないのですが、用法用量を正しく守れば問題ないでしょうか。

他にいいcssの書き方がないか探していきたいです。

widthとheightを指定すると画像の表示は早くなる?

という記事を見かけたのですが、それが上記のようなcssでもいいのか、htmlで書かなくては意味がないのか、そもそも表示速度に関係がないのか、はっきり分かりませんでした。

ここで累積レイアウト変更の問題は一旦解決なのですが、画像の最適化を考える上では大事な「画像の遅延読み込み」について調べました。

画像の遅延読み込み

ページ読み込み時に一気に画像を読み込まず、スクロールで表示領域に入ったときに読み込むものです。

実装方法

方法1:プラグインを使う

以前の記事でちらっと書いた「Lozad.js」でも実装できるみたいです。
他には「lazysizes」や「yall.js」などがあります。
遅延読み込みの仕組みはプラグインごとで異なりますので、使う前に知っておきたいです。

方法2: Chromeloading 属性を入れる

Chromeがネイティブlazy-loadをサポート、JSなしで画像を遅延読み込み可能に – 海外SEO情報ブログ

Chromeでは img 要素にloading 属性を入れるだけで遅延読み込みになるようになっています。

3種類の値を loading 属性に設定できます。

● loading="lazy": lazy は lazy-load を必ず適用させます。Viewport(スクリーンに見えるエリア)に画像が近づくと画像を読み込みます
● loading="eager": eager は lazy-load を適用せずに、ページのロードと同時にコンテンツを読み込みます
● loading="auto": auto は lazy-load するかどうかの判断をブラウザに任せます。
loading 属性の値が設定されていないとき (loading 属性だけのとき) は、loading="auto" が適用されます。

他のブラウザも対応できるようになってほしいところです。

WordPressの5.5場合、遅延読み込みが標準

htmlの widthheight 属性がついている画像には自動的に loading="lazy" が入る仕様になったそうです。

画像の遅延読み込みが標準機能に【WordPress5.5の新機能】

たしかにWordPressのページのimgタグを見てみると入っている...widthheight 属性には元画像のサイズが入っています。

話は戻りますが、これでレイアウトシフトが起こっていなければhtmlでimg要素にwidthheight 属性を入れた方がいいことになります。
(ざっと検証した感じでは、がくんとなっていないようです...!)

その他WordPressが自動で入れてくれる srcsetsizes 属性などもちゃんと理解していないので、いつか知りたいです。
WordPressが生成したコードをもとにあれこれ勉強するのもいいかと思います。

読み込み速度と見た目を考慮した遅延読み込み

人がいいなと思うページを検索上位に上げたい、というのがGoogleの目指すところらしいです。
見出しに挙げた、早く、かつ自然なページ表示が閲覧者の理想だと思いますが、それに近づけるための方法を調べました。

方法1:画像が読み込まれる前にダミー画像を用意しておく

ダミー画像だったり、プレースホルダー画像と呼ばれています。 真っ白なところに突然画像が表示されるよりは、見た目がユーザビリティにもよいということで、先に容量の小さい画像を表示させておく方法です。

オリジナル画像を小さくして引き伸ばしてぼかした画像をはじめに表示させ、オリジナル画像読み込みのタイミングで差し替えます。

フェードしながら画像を遅延読み込みする機能を実装する方法 | 株式会社LYZON

方法2:ダミー画像なし

プログレッシブJPEGでWEBパフォーマンスを向上させるアイデア

1つのオリジナル画像を段階的に読み込むということで無駄は少なく感じますが、難しそう。

ベースラインJPEGプログレッシブJPEG

JPEGには2つのモードがあります。

通常がベースラインで保存されています。Photoshopで保存時にプログレッシブで保存できます。

f:id:yokoyoko_115:20210105130650p:plain

たしかにありますね。

プログレッシブモードの方が、ベースラインよりも容量が若干小さいようです。
表示のされ方も違うので、2つのJPEG画像の表示を見ると「ああ見たことあるー!」と思いました。

最後に

ページ表示の速度改善はアプローチが様々で、Search ConsoleやPageSpeed Insights の言われるがままにあれもこれもと改善を試みると終りが見えません。

ただ、最適な書き方を知っておくのは大事だと思うので、Googleの示す数字に固執せずに、気になったら都度少し対策、を心がけたいと思います。

Interseprtion Observerを解読する

ちゃんと理解していないのに使ってしまいました...Interseprtion Observer。

このまま年を越すわけにはいかないと思ったので、2020年の力試しとしてまとめます。

2020年最後の記事です。

Interseprtion Observerとは

とてもわかりやすく、詳しく書かれているので、こちらを見て分かる方は十分です。

JSでのスクロール連動エフェクトにはIntersection Observerが便利 - ICS MEDIA

サンプルコードと動きの概要

上記記事から引用させていただきます。
文字通り「解読」なのでこの詳しい記事をさらに細かく見ていきます!

JavaScript

// 今回の交差を監視する要素
const boxes = document.querySelectorAll(".box");

const options = {
  root: null, // 今回はビューポートをルート要素とする
  rootMargin: "-50% 0px", // ビューポートの中心を判定基準にする
  threshold: 0 // 閾値は0
};
const observer = new IntersectionObserver(doWhenIntersect, options);
// それぞれのboxを監視する
boxes.forEach(box => {
  observer.observe(box);
});

/**
 * 交差したときに呼び出す関数
 * @param entries
 */
function doWhenIntersect(entries) {
  // 交差検知をしたもののなかで、isIntersectingがtrueのDOMを色を変える関数に渡す
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      activateIndex(entry.target);
    }
  });
}

/**
 * 目次の色を変える関数
 * @param element
 */
function activateIndex(element) {
  // すでにアクティブになっている目次を選択
  const currentActiveIndex = document.querySelector("#indexList .active");
  // すでにアクティブになっているものが0個の時(=null)以外は、activeクラスを除去
  if (currentActiveIndex !== null) {
    currentActiveIndex.classList.remove("active");
  }
  // 引数で渡されたDOMが飛び先のaタグを選択し、activeクラスを付与
  const newActiveIndex = document.querySelector(`a[href='#${element.id}']`);
  newActiveIndex.classList.add("active");
}

これはスクロールに合わせて目次の見出しに背景色がつくといったものです。
簡単にHTML+CSSの様子を表すと、こうなります。

f:id:yokoyoko_115:20201223005340j:plain

白い部分がビューポート(ディスプレイ)です。
#indexList というところが目次です。

.box を監視して、ビューポートに表示されるとその .box に紐付いた目次の見出しに.active のクラスが付きます。

解読

まとまりごとに見ていきます。

// 今回の交差を監視する要素
const boxes = document.querySelectorAll(".box");

const options = {
  root: null, // 今回はビューポートをルート要素とする
  rootMargin: "-50% 0px", // ビューポートの中心を判定基準にする
  threshold: 0 // 閾値は0
};

監視要素の.boxboxes に入れる。
querySelectorAll メソッドは指定したセレクタのHTML要素(NodeList)を取得します。
boxesを console.log(boxes) とすると、

NodeList(4) [div.box, div.box, div.box, div.box]

などと出てきます。

そして options というオブジェクトを作る。
{} で囲まれているのでオブジェクトです。

ここまでは大丈夫です。
次のまとまりを見ます。

const observer = new IntersectionObserver(doWhenIntersect, options);

new でこれも IntersectionObserverのオブジェクト、observer というインスタンスを作っています。

ここでIntersectionObserverのオブジェクトって何?となりました。

IntersectionObserverはコンストラクター

調べていくと、「IntersectionObserverはコンストラクター」だそうです。

コンストラクター(関数)
new式を使用して新規オブジェクト(インスタンス)を作成する関数。

そういう関数があらかじめ用意されているようです。
これがいわゆる「API」ということでしょうか!

Web API の紹介 - ウェブ開発を学ぶ | MDN

クライアントサイドJavaScriptとはブラウザで動くJavaScriptのことですね。

そのうちのAPIは以下の2つに分けられるとあります。

  • ブラウザ API
     ┗ ブラウザに組み込まれているもの。 ←IntersectionObserverはこっち?
  • サードパーティ API
     ┗ デフォルトではブラウザに組込まれていない。Twitter APIなど

Web APIとしてこちらにもIntersectionObserverが載っていました。

Web API | MDN

Web API は通常 JavaScript とともに使用されますが、常にそうとは限りません。

意味深ですね。他の言語でもWeb API が使われることがあるみたいです。

IntersectionObserver

IntersectionObserver - Web API | MDN

Intersection Observer API - Web API | MDN

苦手意識のあるAPI、また少し分かってきた気がします。
ひとくくりにするにはその種類が多いですね。

IntersectionObserver() の中身を見てみると、doWhenIntersect がコールバック関数で、options が監視範囲の設定値です。

コールバック関数の中身はそのさらに下に書かれているので今は置いておいて...。

// それぞれのboxを監視する
boxes.forEach(box => {
  observer.observe(box);
});

監視対象の .box 要素をループ処理しています。

ここでforEachについて調べました。

JavaScriptでforEachメソッドを使う方法【初心者向け】 | TechAcademyマガジン

forEachはArrayオブジェクトに実装されたメソッド

配列もオブジェクトなのでメソッドがあります。書き方は下記です。

配列.forEach( コールバック関数による処理 )

ここでもコールバック関数!

box => { observer.observe(box); }

この部分、アロー関数で書かれていますが、

function(box) {
  observer.observe(box);
}

と同じです。box.box の各要素です。

observeメソッド

このはたらきは

IntersectionObserver が対象の要素を監視するよう命じます。

だそうです。

observer.observe(box); は、observerというオブジェクトに対してobserveメソッドをあてています。名前が一緒でややこしいですね。
引数に対象の要素を入れています。

つまりは「IntersectionObserver が各 .box 要素を監視する」ということですね。

/**
 * 交差したときに呼び出す関数
 * @param entries
 */
function doWhenIntersect(entries) {
  // 交差検知をしたもののなかで、isIntersectingがtrueのDOMを色を変える関数に渡す
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      activateIndex(entry.target);
    }
  });
}

ここで IntersectionObserver() のコールバック関数が出てきました。
そしてまたforEach。
なぜforEachを使っているのかは、参考本ページに書かれていました。

doWhenIntersect(entries) と引数を設けています。
ここには、doWhenIntersect が発動するタイミングで、監視している要素が入ってくるのだと思います。

コールバックは、入力引数として交差したすべての閾値を示すIntersectionObserverEntry オブジェクトの配列を、また参照としてIntersectionObserver オブジェクト自身を受け取ります。

ちょっと違いました^^;
IntersectionObserverEntry オブジェクトがよく分かりません。

閾値のリスト内のそれぞれの項目は、通過した閾値を説明するIntersectionObserverEntry オブジェクトです。つまり、それぞれの項目は指定された要素がルート要素とどれだけ交差したのか、要素が交差したと言えるのかどうか、推移が発生した方向を示します。

console.log(entries) とすると、閾値に指定している(今回はthreshold: 0)、交差のタイミングで表示されました。

分かったような気がします。大事なオブジェクト...!

isIntersectingプロパティ

監視対象が見えるようになったか、見えなくなったかを判断します。
entry.isIntersectingtrue なら見えるようになった
false なら見えなくなった

です。
(見えている状態が継続されている場合はtrueなのでしょうか)

要素が見え始めた場合に activateIndex という関数に entry.target という引数を渡しています。

targetプロパティ

先程の console.log(entries) のtargetプロパティを見てみると div.box とありました。

このあたりの細かい説明はあまり見つからず...「targetプロパティ」は広くいろいろなところで同じようにセレクタを値に持っているのかもしれませんね。

文章にすると「閾値の交差時に監視対象の要素が見えたら、次の関数を実行する」でしょうか。

/**
 * 目次の色を変える関数
 * @param element
 */
function activateIndex(element) {
  // すでにアクティブになっている目次を選択
  const currentActiveIndex = document.querySelector("#indexList .active");
  // すでにアクティブになっているものが0個の時(=null)以外は、activeクラスを除去
  if (currentActiveIndex !== null) {
    currentActiveIndex.classList.remove("active");
  }
  // 引数で渡されたDOMが飛び先のaタグを選択し、activeクラスを付与
  const newActiveIndex = document.querySelector(`a[href='#${element.id}']`);
  newActiveIndex.classList.add("active");
}

これは読めました。
目次に対して、先にactiveクラスを外してから、該当の要素にactiveクラスを付けています。

以上です!

Lozad.jsについて

こちらのプラグインの中でInterseprtion Observerが使われているということなので調べました。

IntersectionObserveの遅延読み込みライブラリのLozad.jsを紹介する - Qiita

使いやすそうですね。Interseprtion Observer を理解した上で使うならいいと思いました。

最後に

JavaScriptはまだまだ1文ずつ分解して読んでいかないと分からないところがあります。
謎のプロパティ、引数が出てくると心が折れそうになりますが、調べれば正体が分かったので、根気よく解読していこうと思います。

アロー関数とコールバック関数に慣れなくては。

***

2020年に始めたこのブログも何とか1年続けられました。奇跡!
後半息切れしましたが、2021年もマイペースに書いていきます!

JavaScriptの歴史をまとめてみる

まだまだJavaScriptの基本やNode.jsについて抑えておこう!ということで、こちらの動画を見てJavaScriptの歴史を自分なりにまとめてみました。

【モダンJavaScript #2】歴史から学ぶJavaScript前編。ECMAScript、CommonJS、モジュール、名前空間を知ろう【フロントエンドエンジニア講座】 - YouTube

【モダンJavaScript #3】歴史から学ぶJavaScript後編① Node.jsとパッケージ管理システムnpm【フロントエンドエンジニア講座】 - YouTube

【モダンJavaScript #4】歴史から学ぶJavaScript後編② IIFE・AMDモジュールとブラウザ向けパッケージ管理システムBower 【フロントエンドエンジニア講座】 - YouTube

【モダンJavaScript #5】歴史から学ぶJavaScript完結編 バンドル・コンパイルを知る。Browserify, webpack, Babel【フロントエンドエンジニア講座】 - YouTube

素晴らしい動画をありがとうございます!!

☆私の解釈が間違っている可能性もあるので、正しく知るには動画を見ることをおすすめします!

また最近でこんな記事も見つけました。

プログラミング初心者のための JavaScript と Node.js の歴史、それを踏まえた勉強方法

用語など所々難しく感じましたが、JavaScriptは色々な人に使われていて、そのためちゃんと新しく正しい記事を探すべきであることが分かりました。

ざっくりしたJavaScriptの歴史の流れ

動画では4回に分けられていたので同じく分けました。

  1. JavaScriptの誕生
  2. Node.jsの誕生
  3. ブラウザ側でのモジュールの模索
  4. ブラウザJSの進化

サーバーとブラウザ、それぞれでJavaScriptが使われていくようになるのですが、ブラウザのJSは、しばらく使いづらい状況が続いていました。
そこでコードを事前に変換する という発想の転換によって、とても便利に使いやすくなりました。

さらに大事なこととして以下が挙げられていました。

・ JavaScriptの歴史のお話では「ブラウザとモジュール」というのが大きなテーマ
・ プログラミング用語はコンテキスト(背景)、言語、人、ツールによって違う意味で使われる。

用語の定義については、以前引っかかった「コンパイル」もこの類ですね。 今回は「モジュール」と「パッケージ」の違いでもやもやする予感です。

以下長くなりますが文字に起こしました。

1. JavaScriptの誕生

Natscapeのブラウザ(Netscape Navigator)で使われたのが始まり。 もともとは「LiveScript」。
当時注目されていたJavaにあやかって「JavaScript」に名称変更した。

IEでも搭載される

それぞれのブラウザに合わせたJavascriptを作らなくてはならず大変。
統一させたい、と考える。

ECMAScript(エクマスクリプト)の誕生

JavascriptECMA International(標準化団体)に登録された。仕様を一つに統合した。

☆ES(←きっと「ECMAScript」の略)は毎年リリースされる。現在はES[20xx]という表記。

ECMAScript
JavaScriptの中核となる言語仕様のこと。
いろいろな実行環境(サーバー:Node.js、ブラウザ間)でも共通の動作のみが定義されているJavaScript

☆といってもそのまま使えるわけではなく、環境によってはコンパイルしたりする必要がある(後述)。

不人気の後、大人気!

JS起因のクラッシュ、悪用ウィルス、不快なアニメーションなども多く、機能の使用をオフにする人もいた 。

  • GoogleMapsがAjaxを利用 :非同期通信で画面が一瞬白くなったりしない
  • jQueryの誕生:ブラウザ間の差異を吸収できて便利

これらの影響で人気になる。

Ajax(Asynchronous JavaScript + XML)について
初心者目線でAjaxの説明 - Qiita

サーバーサイドのJavascript

サーバーサイドでもJavascriptを作ろう、ということで「ServerJS」というプロジェクトが立ち上がる。

たくさんのAPIを作る必要がある

「CommonJS」へ名称変更(プロジェクト名の変更)
たくさんのAPI仕様が作られていく

API
Application Programming Interface
「アプリケーション、ソフトウェア」と「プラグラム」をつなぐもの

JavascriptAPIを作る」とはどういうことなのかなぁと引っかかりましたが、先のAjaxの参考ページにもあったように、Ajaxの機能を支えるXMLHttpRequestDOMAPIということなので、同じようなものを作ろうとしていたのだろうと考えました。
インターフェースなのでJSと外との窓口を増やす、といったイメージでしょうか。

モジュールAPI
CommonJSで作られたAPIのうちの1つ。モジュールを使う。

モジュール
ただのファイル。1つのスクリプトは1つのモジュール。
exportとimportを使用して機能をやり取り、あるモジュールの関数を別のモジュールから呼び出したりすることができる。
例)変数をexportして、別ファイルでimport(require)して使うことができる

Javascriptの課題

名前空間が一つしかない

変数や関数の命名が被ると上書きしてしまう。バグが発生しやすい。
例)あるjsファイルを3つに分けてhtmlファイルでa、b、cと順に読む

//a.js
var foo = 123;

//b.js
var foo = "foo";

//c.js
foo * 10; // NaN  ←変数fooの値は"foo"に上書きされている

let を使っても上書きされます。
const を使って同じ変数名にすると、この場合エラーになります。

サーバーではモジュールを使って解決。ブラウザでも使えるようにしたい。

Javascriptのモジュールは1ファイル単位でスコープ(“よそはよそ、うちはうち”)がかかっている。ファイル内の変数や関数は外部に影響しない 。

依存関係がある

読み込みの順番がある。
例)jQuery→jQueryUIの順で読み込まないとバグが発生する

2. Node.jsの誕生

モジュールの仕組みを持っている。
最初はCommonJSのモジュールAPIに準拠している(requireとexportをつかったもの。言い方:「CommonJS形式のモジュール」)

CommonJS(プロジェクト)は機能しなくなり、 Node.jsが独自の進化を遂げる。現在はモジュールの仕組みもCommonJSとは違うものがある。

Javascriptは複数の形式(仕様)のモジュールがある。

npmの誕生

モジュールのおかげで名前空間の問題が解決し、機能を細分化することができた。
すると、機能を他のプロジェクトでも使い回すようなる(共有)。
(他のサーバーサイド言語ではすでに実現されている)

npm誕生

npm(Node Package Manager)
Node.jsのパッケージ管理システム

パッケージ
package.jsonで記述されたファイルやディレクトリのこと。
共有したい機能をまとめた1つの単位。1ファイルの場合もあれば、ディレクトリの場合もある。

パッケージ管理システム
パッケージのバージョンを管理したり、共有するためのシステム。

はたらき

  1. リポジトリの購読
    ローカル環境にインストールしたパッケージを更新できる。パッケージを検索できる
  2. パッケージのインストール、削除
    パッケージを指定してローカルにインストールできる。削除もできる
  3. 依存関係の解決
    パッケージに必要な別なパッケージを自動的にインストールや更新することでがきる
  4. 設定管理
    設定を書くことで、1.2.3を自動で行える。毎回手動でパッケージを入れたりする必要がなくなり、チームでの環境を簡単に揃えたりすることができる

☆パッケージの依存関係を視覚化されているサイト
http://npm.anvaka.com/#/

☆npmのサイトの、パッケージのメニュー「Dependencies」では依存関係にあるパッケージが載っている。(Dev Dependenciesはまた別)

☆なるべく依存関係の少ないパッケージを選ぶと良い。

サーバーサイドJSの準備が整う

名前空間 → モジュールで解決
依存関係の問題 → npmで解決
有名企業も採用するように(UberNetflixなども!)

3. ブラウザ側でのモジュールの模索

モジュールっぽい仕組みはサーバーサイドJavascriptよりも前にあった。4パターン。

IIFE(即時実行関数式)形式

(function() {
  var foo = "foo";
})();
foo; // foo is not defined  即時関数の外ではfooは未定義

関数を使って、擬似的にプライベートな空間を作り上げている。

それだけでは不十分 。 言語仕様としてのモジュールではないので、名前空間の問題も完全に解決されていない。

AMD & Require JS誕生

AMD:モジュールの仕様(形式)
ブラウザ環境での実行を考慮し、依存関係の解決及び遅延ロードに対応した仕様

Require JS:AMDを実装しているもの

ブラウザでもモジュールが実現。依存関係も解決することができる。

AMD形式

define(["moduleA", "moduleB"], function(fnA, fnB) {
  // returnしたものがpublic(外部に出す)
  // returnしなければprivate
  return function() {};
});

☆moduleAとmoduleBにjQuery、jQueryUIを入れて開発、モジュールを作る、ということもできる。

次に欲しいのはブラウザ向けパッケージ管理システム

Bower誕生

Bower(バウアー)
クライアントサイド(ブラウザ)開発向けのパッケージ管理システム。
npmと似たようなことができる 。 ほとんどのBowerのパッケージはIIFEモジュールか、AMDモジュールを利用している。

ブラウザJavascripの落とし穴

AMD形式のモジュールは、サーバー側との互換性がない。構文がCommonJS形式と比較して冗長。メンテナンスも大変。

Bowerは、パッケージの依存関係の定義をユーザーが手動で定義する必要がある。 同一ページ内にある同じパッケージの異なるバージョンをサポートしていない 。 例)jQueryの1系と2系を同一ページで一緒に使えない

モジュールまとめ

  • IIFE形式(欠点あり)
  • AMD形式(欠点あり)
  • CommonJS形式 (Node.jsで成功)

UMD形式もある(パッケージを外部に公開するときの設定の話)

ブラウザJS、考え方を変える

CommonJS形式で書かれたものを事前にブラウザ形式に変換する
書いたコード(開発時のコード)と(ブラウザで)動くコードが違う。

事前にブラウザ向けに変換する方法

Bundle
Bundleを使うと...

  1. 開発時はCommonJSモジュールで開発
  2. モジュールの依存関係を解決して1ファイルに変換(バンドル) → jsファイルになる
  3. 2でできたファイルをscriptタグで読み込む

☆CJS(CommonJS)モジュールだけではなく、CSSや画像やいろいろなものをBundleすることができる。

☆設定によって複数ファイルにバンドルすることができる。

Browserify(ブラウザリファイ)
CommonJS形式に書かれたものをブラウザ向けにバンドルするツール。 CJS形式モジュールをバンドルする。

Browserifyを使うと、ブラウザでもNode.js同様requireを使うコードが使用できる
CommonJS形式のモジュールが使えるようになる。

npmのパッケージがブラウザ向けに移植される

ブラウザでもnpmが主流に!

もともとnpmはCommonJS形式で書かれたパッケージが多く、ブラウザでは使えなかったが、Browserifyでブラウザ向けに変換できることで、ブラウザでもnpmが使われるようになった。

webpack
新たなバンドルツール(「モジュールバンドラー」と呼ばれる)。
Browserifyよりも高機能(chunk(チャンク)という単位で区切ることで最初のロード時間を短縮できる)。
主にJavascript向けだが、対応するローダーがあればHMTML、CSS、画像などのフロントエンドのアセット("もの")もバンドルすることができる。

☆バンドル前のファイルはブラウザで読み込めない拡張子のものがある(.sass、.cjs、.hbs)

☆バンドル前のファイルは依存関係にあるが、バンドル後のファイルは依存関係がない

ES Modules(ESM)の誕生

言語仕様としてのモジュールが欲しい。
今まで出てきたモジュールは、自分たちでどうにかして作ったモジュールっぽい仕組み(独自仕様)だった。

CJSではrequireを使っていたところ、ES Modulesではimport構文を使う(ES2015)

webpackがES Modulesに対応(ネイティブサポート)

ES Modulesはまだほとんどのブラウザでサポートされていなかった。モジュールバンドラーはまだ必要。
でもES Modulesで書きたい。

☆「ネイティブサポート」とは、webpackはローダーがなくてもES Modulesを読み込めるようになった、ということ

☆後に「Parcel(パーセル)」というモジュールバンドラーも出てくるが"webpack1強"に変わりはない

☆現在のES Modulesサポート状況
webpackを通さなくても、IE以外のモダンブラウザとNode.jsのVersion12系以上では標準で使用可能

Compile
Compileを使うと...

  1. 開発時はブラウザでは動かないけど、開発に便利な機能を使ってコードが書ける
  2. 書いたコードをブラウザで動くように変換する(コンパイル
  3. 変換したコードをscriptタグで読み込む

バンドルは依存関係の解決に焦点を当てていたが、コンパイルは純粋に便利な機能をJSに変換する。

ES2015のたくさんの仕様

モジュール、let、const、class、Promise、アロー関数、分割代入、スプレット構文、テンプレート文字列などなど。
IEではすぐには使えないが、コンパイルで解決!

Babel(6to5 ←最初の名称)誕生

Babel
ES2015などで書かれたコードを従来の環境でも動くように古いJavascriptに変換するコンパイラ

☆似た言葉で「トランスパイラ」があるが、Babelのサイトには「コンパイラ」とある。

便利なパッケージが流行る

パッケージ

  • React(.jsx)
  • Vue(.vue)
  • TypeScript(.ts)

これらはコンパイルしてjsファイルにして使う。

パラダイムシフトが起こった

事前に変換というパラダイムシフト(考え方が大きく変わること)が起こり、現在は段々とjQueryが使われなくなっってきている。

最後に

後半駆け足になってしまいましたが、字が多すぎ。図を入れたかった...。

ここまでまとめると、モジュールの進化がすごいことが分かりました。

モジュールとパッケージの関係も、モジュール(機能)がセットになったものがパッケージかな、と少し分かりました。

といっても、未だ「ブラウザでパッケージを使う」機会に遭遇していないので、ピンときていません。
Node.js同様、コマンドを叩いたりするのでしょうか。

早くReactが分かるようになりたい...とじりじりしていますが、 少しずつ前へ進みたいと思います!

【css】line-heightの使い方

テキストのすき間問題。見て見ぬふりしてきたシリーズの1つです。

vertical-align も気になっているのですが、今回は line-height を見つめ直す機会があったのでおさらいします。

まずこちらの記事を読んで「なるほど」と理解してから考えます。

【CSS】line-heightで行間を調整する方法:おすすめの値は?

覚えておきたいこと

line-height の単位は付けないほうが使いやすい

emや%よりも「単位なし指定」がオススメです。なぜかというとemや%だと親要素で計算された行高が、子要素にそのまま引き継がれてしまうからです。つまり、親要素のフォントサイズが40px、子要素のフォントサイズが20pxの場合にも、子要素には親要素と同じ行間が適用されてしまうのです。 一方で単位なし指定の場合、子要素のフォントサイズに応じて行高を計算し直してくれます。

てっきり単位なしは em% と同じ意味だと思っていました(この2つは同じ、だと思います)。

今までのコード書き直したい...。

line-height はblock要素に効く

これはinline-block でもダメでした。 今回検証したところ、span要素には効きませんでした。
確かに文章の途中で行間が変わる、というのは考えにくい事象ですね。

レイアウトしたもの

こんな感じの、1行目が大きい文字で、2行目が小さい文字のレイアウトです。
いろいろ書き方はあると思いますが、今回はp要素の中にspan要素で小さい字を入れて改行させました。

f:id:yokoyoko_115:20201119001000j:plain

この図が今回のまとめです。

当初大きい文字(1行目)と小さい文字(2行目)の行間がものすごく空いてしまい、line-height: 1em だ!line-height: 0だ!とあれこれやっていましたが、改めてちゃんと調べたらスッキリしました。

おまけ

ちなみに、line-height: 0 ですと要素の高さが0になってしまうようです。

テキストをドラッグして選択したときの高さというのは要素の高さとはまた少し違い、line-heightの高さなのかな?と思いました(line-height: 0 は最低限1文字の高さの範囲で選択できます)。
そもそもインライン要素の高さとは...。 このあたりややこしそうです。

See the Pen line-height検証 by yokoyoko (@yokoyoko_code) on CodePen.

正規表現チェックツールの使い方

ごく稀に正規表現を書くことがあり、その度に正規表現の書き方を調べています。 時間のないときはお手上げです...。

そんな時にこちらのツールを教えていただきました!

正規表現チェッカー | WEB ARCH LABO

次回以降、こちらのツールの使い方を思い出せるようにメモします。

正規表現チェッカーの画面の使い方

正規表現もよく分かっていないと、この画面だけを見ても使い方が分かりませんでした。

使い方
① 渾身の正規表現を「正規表現」の欄に入力する
② ①にマッチするであろう文字列を「検証対象文字列」の欄に入力する(改行して何パターンも文字列を検証できる)
③ マッチすると「結果」欄に②の文字列が赤く表示される

例)
f:id:yokoyoko_115:20201024005909p:plain

こんな感じで使いました。 「正規表現」の欄に右の正規表現の表を見ながら試行錯誤して入力して、ひたすらチェックするといった使い方になりそうです。

またこのサイトでは「HTMLエスケープ」という、HTMLエスケープされた文字列を返してくれるツールもあります。便利です。

正規表現とは

そもそもですが調べてみました。

サルにもわかる正規表現入門

端的に言えば、「いくつかの文字列を一つの形式で表現するための表現方法」です。

なるほど。

^$ などの特殊文字正規表現ではメタ文字というそうです。

正規化とは

こちらもよく聞く単語だったので調べました。

https://wa3.i-3-i.info/word11632.html

データとかを使いやすいように整理したり変形したりすることです。

正規表現と関係があるかと思いましたがそうでもないようです。
(同じ「正規」と付くだけあってそこまで遠くはなさそうですが...)

.htaccessでリダイレクトの方法

ちなみに今回は.htaccessでリダイレクトの設定をしました。

これもメモ程度にざっくり書くと、

RewriteRule サイト内の対象ページ リダイレクト先 [R=301,L]

です。正規表現を使って書くことができます。

頭に RewriteRule が付き、最後は [R=301,L] などで終ります。

また、「対象ページ」に書いた (.+) など () で囲った部分は、「リダイレクト先」に$1$2$3 と書いて渡すことができます。

例)

RewriteRule ^blog/(.+)cat/(.*)$ https://%{HTTP_HOST}/blog/cat/$2 [R=301,L]

これで
https://example.com/blog/postcat/hoge

https://example.com/blog/tcat/hoge
リダイレクトします。
$2 には (.*) が渡ります。

.htaccessの書き方は勉強しよう!

と、自分に言い聞かせています。

.htaccessではいろいろなことができますが、1歩間違えればサイトにアクセスすらできない、恐れ多い印象を持っています。
難しく感じてしまうのは正規表現をつかった処理が出てくるから、というのもあると思います。
少しずつ理解していきたいです。

正規表現がある程度スラスラ書けるようになると、いろいろな場面で役立ちそうなので、 こういったツールを使って正規表現に慣れていきたいです。

コマンドが見つかりません(command not found)の対処法

npmのプログラム(パッケージ)をインストールしても、コマンドを叩いてうまくいかないことがあります。

うまくいかなかったケース

私がハマってしまったのは、ProgateのNode.jsの「Expressのアプリケーションを作成しよう」というコラムを実践していたときです。

コラムはこちら

Node.jsの新規アプリケーションを作ろう! | プログラミングの入門なら基礎から学べるProgate[プロゲート]

npmのパッケージ、「nodemon」をインストールしても nodemon app.js でサーバー起動ができないというものでした。

インストール
・グローバル

npm install -g nodemon

・ローカル

npm install nodemon

ローカルやグローバルにインストールしたり、1回アンインストールして再度インストールしてみてもだめ。

実行

nodemon app.js

とすると、
zsh: command not found: nodemon と返ってきます。

☆後ほどグローバルでインストールして実行したところ、うまくいきました。スペルミスしていたのか、何だったのでしょう...。
めでたしめでたしですが、ここはローカルにインストールした場合で進めます。

環境

  • シェルはzsh
  • nodenvからnodeとnpmはインストール済み

原因と対処法

原因はPATHが通っていないケースが大半なのではないでしょうか。

実際、パスを付けてnodemon という実行ファイルを直接叩けば実行されました。

node_modulesフォルダのあるところまで移動して

./node_modules/.bin/nodemon app.js

です。
(ローカルにインストールしたnodemonの実行です)

毎回ここまで打つのは大変なので、その他の対処法を考えてみました。

対処法その1:PATHの設定をする

PATHの設定をすれば nodemon コマンド一つで実行できます。

Macで「zsh: command not found」が出る原因と対処法

環境変数のPATHには、実行したいプログラムのあるフォルダのパスを複数登録することができるので、そこに追加する、といったものです。

今回ローカルにインストールしたプログラム(nodemon)なので、できれば汎用的なパスだけの登録にしておきたいです。

env コマンドで環境変数(PATHなど)を確認できるということは覚えておきたいです。

以下はこちらを参考にしながらまとめていきます。
どうもありがとうございます。

知らないのは損!npmに同梱されているnpxがすごい便利なコマンドだった | Developers.IO

対処法その2:npm binを使って直接叩く

npm binはカレントプロジェクトのローカルパッケージPATHを返してくれるコマンドです。

実際にプロジェクトのディレクトリ内で npm bin と叩くと、ルートディレクトリから node_module/.bin までのパスが返ってきました。

$(npm bin)/nodemon app.js

でうまくいきました。

$() の書き方は初めて知りました。

Linuxでのドル記号「$(...)」の意味と使い方 | UX MILK

対処法3:package.jsonにscriptを追加する

nodemon のコマンドをpackage.jsonのscriptに登録して使う方法です。

npmのrun-scriptというしくみを利用します。
こちら↓には npm-script とあります。
「npmのrun-script=npm-script」なのでしょうか。

Node.jsユーザーなら押さえておきたいnpm-scriptsのタスク実行方法まとめ - ICS MEDIA

npm-scriptsとは、package.jsonファイルに記述可能なシェルスクリプトエイリアスです。エイリアスとはコマンド名を別のコマンド名に置き換えることです。

gulpを使わなくても、scriptフィールドでもりもりタスクを設定できることが分かりました。

package.json

 "scripts": {
    "nodemon": "nodemon"
  },

ターミナル

npm run nodemon app.js

で動きました。

また
package.json

 "scripts": {
    "nodemon": "nodemon app.js"
  },

ターミナル

npm run nodemon

でもOKです。

毎回サーバーを起動させるので、できる限りコマンドを短くしたい、となれば
package.json

 "scripts": {
    "start": "nodemon app.js"
  },

ターミナル

npm start

でしょうか。

ところでなぜコマンド名だけのシェルスクリプトの登録でうまく実行できるのでしょうか。

scriptsフィールドではローカルインストールしたパッケージのコマンドを直接実行できるテクニックを使用したものです。

npmのパッケージの構成ファイルであるpackage.jsonに書いたものなので、そう言われたらそうですね。

対処法4:npxをつかう

npx とは

npxはnpmパッケージを簡単に実行できるコマンドです。具体的には次のようなことができます。

  • run-scriptを使用せずにローカルインストールしたコマンドを実行する
  • グローバルインストールせずに一度だけコマンドを実行する
  • GitHubやGistで公開されているスクリプトを実行する

他の記事でも出てきた、遊び心いっぱいのパッケージで私も試してみました。

npx yosay Hello!

f:id:yokoyoko_115:20201017011821p:plain

npxでは、事前インストールせずに一度だけコマンド実行でき、実行後には削除されます。

便利便利と書かれているので便利なのでしょうが、残念ながらまだ実感できず...。パッケージをお手軽に使いたくなる日が来ますように。

気になったこと

ここまで書いていて追加で気になったことをまとめました。

パッケージとモジュールの違いについて

未だに「パッケージ」と「モジュール」の差がよくわかっていません。同じだと思っていました。

どうやら違うようなので、こちらの記事を見て自分の言葉に替えてみました。

【npm】パッケージとモジュールの違いって何? - Qiita

パッケージ

パッケージとはpackage.jsonによって記述されるファイルもしくはディレクトリのことを指します。

なので、package.jsonがある、node_moduleディレクトリ内のディレクトリたちは各パッケージなのだと思います。

モジュール

モジュールとは、Node.jsの require 機能によって呼び出すことができる node_modules 内のファイルまたはディレクトリのことを指します。

この「Node.jsの require 機能によって呼び出す」というのは、今のところgulpfile.jsで見たことがあります。

モジュールとして require 機能によって読み込まれる為には、次のいずれかである必要があります。

  • package.json を含むフォルダーであり、mainフィールドを含むファイルであること。
  • index.js というファイルが入っているフォルダであること。
  • JavaScript ファイルであること。

package.jsonname フィールドはパッケージ名、 main フィールドはメインエントリー(?)といいjsファイルやディレクトリ名が入るようです。

package.jsonの内容をまとめてみました - Qiita

これらからnodemonはパッケージでありモジュールであることが分かりました。

パッケージを特にrequire 機能によって読み込まれるものをモジュールという
考え方でいいかと思いました。

自分たちもパッケージを作っているんだ、という意識を持ちました。
いろいろなパッケージを組み合わせてオリジナルのパッケージを作っているって改めてすごい。

パッケージにはコマンドを提供されているものとされていないものがある

コマンドが提供されている場合、node_modules/.bin/ 配下にコマンドが配置されます。

このことを覚えておけば、のちのち腑に落ちそうです。

実行ファイルに拡張子がついていないのは「コマンド」として打ちやすいからなのでしょうか...。中身はjsファイルですね。 そもそも .bin とは?ってなりますが、別の機会に調べます。

最後に

今までこのような「ローカルでインストールしたのにコマンドが見つからない」、といった問題にぶつかったことないのはなぜなのだろう、と考えたところ、直接コマンドを叩く機会がなかったからだと気づきました。

Node.jsのrequireでインストールしたモジュールを読み込んで、処理のコマンドをpackage.jsonでnpm-scriptとして実行していたからです。

npm-scriptで gulp sass と何の疑問を持たずに書いてきましたが、 ここにきて gulp コマンドって何でしょうか...気になります。

なにはともあれNode.jsの理解が深まってよかったです。

これまでNode.jsは単にgulpを使うために必要なものものぐらいにしか思っていなかったので、Progateのスライドも勉強になりました。

もう少し詳しく知りたいです!

おしまい。

PHPのメール送信を考える(前編)

先日WordPressプラグイン「ContactForm7 」と「WP Mail SMTP」を使ったメールフォームを作りました。
「WP Mail SMTP」には送信元アドレスやメーラーSMTP ホストなどを設定できますが、このプラグインを使わなくても「ContactForm7 」でメールフォームを作って送信できたはず、一体何が起こっているのだろう...と気になったので調べました。

メールは奥が深い

初心者(?)がイチから調べるにはあまりにも必要な知識が多すぎる! 気になることが後から後から出てきていつも以上に収拾がつかなさそうなので、ひとまずメール送信機能を作ってみます。

環境

  • MacMAMP(ローカル環境)でメールフォームを作る
  • SMTPサーバーはMAMP内に入っていれば(?)それを使って、なければGmailを使う
  • Gmailが送信側で絡むかもしれないので、送信先アドレスはyahooにする

↑書いていることも正しいのか今の時点では怪しいですが、これでスタートします。

フォームの作り方

まずはプラグインなしのPHPでメールフォームの作り方を学びます。

お問い合わせフォームを作る | GRAYCODE PHPプログラミング

きっとこちらが一般的な作り方なのだろうと信じます。

要点は

  • form要素をaction="" と空の値の属性を入れることで、1つのPHPファイル(index.php)で入力画面、確認画面、送信完了画面を用意する
  • 入力・確認はそれぞれPOST通信でデータを送る。
  • 確認・送信ボタンにそれぞれname属性を付け、その値($_POST['name名'])で入力・確認・送信完了画面の判別をする
  • メール送信は mb_send_mail関数を使う

です。

今回は入力画面、確認画面や見た目は考えず、送信だけできるページを作ります。

index.phpの一部

<?php if(empty($_POST["btn_submit"])): ?>
  <h1>ボタンを押すと送信</h1>
  <form method="post" action="">
      <input type="submit" name="btn_submit" value="送信">
  </form>
<?php else: ?>
  <h1>送信!</h1>
  <?php mySendMail(); ?>
<?php endif; ?>

<?php
function mySendMail() {
  // 変数の設定
  $to = "xxx@yahoo.co.jp";
  $subject = "メール送信のテスト";
  $text = "メール本文です。";

  // メール送信
  mb_send_mail($to, $subject, $text);
}
?>

f:id:yokoyoko_115:20200916132118p:plain
表側はとてもかんたんなつくり

送信してみる

送信先のメールアドレスが間違っていないか、いつも以上に確認して送信ボタンを押すと...

なんと送受信ができた!ダメ元だったのに。

yahooのメールアドレス宛に送信すると迷惑メール扱いすらされず受信箱に入っていました。
gmailのメールアドレスでは迷惑メールになっていました)

index.php では設定していなかった送信者情報がメールに入っていたので、送信時にどこかで設定された値が使われたと考えられます。

mb_send_mail関数について

mb_send_mail関数は、日本語などマルチバイトの文字をメール送信する時に使う関数で、中で mail関数を呼んでいます。

mb_send_mail( $to, $subject, $text, $header, $parameter);

mb_send_mail関数の引数 $header (省略できる)には、メールヘッダーに追加する送信者情報を入れる部分があります

送ったメールがスパム判定(迷惑メール)されないためのヘッダー設定 | GRAYCODE PHPプログラミング

送信者情報(抜粋)

・Content-Type – メール形式 ・Return-Path – 送信先メールアドレスが受け取り不可の場合に、エラー通知のいくメールアドレス
・From – 送信者の名前(または組織名)とメールアドレス
・Sender – 送信者の名前(または組織名)とメールアドレス
・Reply-To – 受け取った人に表示される返信の宛先
・Organization – 送信者名(または組織名)
・X-Sender – 送信者のメールアドレス
・X-Priority – メールの重要度を表す

これらの情報は、改行コードを入れながら一つの文字列にします。

MAMPのどこかで送信者情報が設定されているはずなので調べました。

PHPのメール送信に関する設定値

MAMPのスタートページから「TOOLS > PHOINFO」のページを見るとメール送信に関連する設定がありました。

Core(一部抜粋) PHP Version 7.4.2

Directive Local Value Master Value
sendmail_from no value no value
sendmail_path /usr/sbin/sendmail -t -i /usr/sbin/sendmail -t -i
SMTP localhost localhost
smtp_port 25 25

このページは phpinfo() を実行した結果を表示させたページです。
phpinfo() では、php.ini(拡張子の読みは「イニ」が多そう)ファイルなどで設定した、PHPの設定値を見ることができます。
設定値の変更はphp.iniだけではなく、Apache側からや.htaccessからもできます。優先順位があるそうです。

ちなみに読み込んでいるphp.iniの場所は、phpinfoの「Loaded Configuration File」にあります。

phpinfo関数による設定内容の確認 | PHPインストールと初期設定

php.iniファイルの確認と修正 | XAMPPの使い方

sendmailとは

phpinfoにもあった sendmail についてです。
mb_send_mail関数では内部でsendmailコマンドが実行されます。
sendmail」というのはメール転送エージェント(MTA)というメール配信用プログラムです。「postfix」というのも同じくMTAです。

メール転送エージェント:
https://wa3.i-3-i.info/word11134.html

sendmail コマンドの実行は、phpinfoにある「sendmail_path」の値を見て行われるのだと思います。

途中結果

ということは、今のところ

SMTPlocalhost ←ローカル環境にSMTPサーバがある?
SMTPのポート番号:25
送信者情報:sendmail の設定値?

と推測しました。

このsendmailpostfixの設定、何やら難しそうです...。

次回でsendmailについて調べられたらいいと思います。

挫折せずに後編を書けるといいです...!