Hugoでサイトを作り直します

すっかりご無沙汰になってしまったHugoのサイト作り、テーマを新しいものにして作り直そうと思います。

今回はこちらのテーマにしました。

f:id:yokoyoko_115:20210321005234p:plain

去年の記事を見ながらhttp://localhost:1313/で表示できるところまで進めました。

前回作ったサイトへのアクセスもhttp://localhost:1313/ですが、サイトディレクトリの中まで移動して hugo server でhugoサーバを起動させることで切り替えています。

前回の反省点

途中で作ることをやめてしまった要因は、完璧を目指してしまったからだと思います。

Hugoを理解してから記事移行しよう、など順番を決めて自らハードルを上げてしまっていました。

今回はできるところからやっていきます!

静的サイトジェネレーターのホスティングサービスを利用する

ブログのカスタマイズも行わずに、ダウンロードしたテーマをそのまま公開します。

HugoのホスティングサービスならNetlifyが良いそうなので使ってみます。

自作のサイトを公開するならサーバーを契約してドメインを作って、とお金がかかると思っていましたが無料で作れてしまうんですね!

ゆくゆくは自分のドメインでサイトを公開したいですが、ひとまず。

参考にしたい記事

いろいろと網羅されていて今後お世話になりそうです。

Hugo で静的なサイト・ブログを構築しよう - Qiita

Netlifyの使い方が書かれています。

Netlifyで静的サイトのホスティングをする - Qiita

こちらに倣ってNetlifyのアカウント登録をしました。

「Deploy site」をクリック!

Netlifyでデプロイできず

それらしきリンクを押したらこう出てきました。

f:id:yokoyoko_115:20210322010916p:plain

「 "page not found" support guide 」のページを見てみても当然のことながら英語。

さっそく挫けそうですが、同じ境遇の方はきっといると思うので日本語で調べます。

Hugoのテーマ保存方法を振り返る

GitHubのHugoテーマのリポジトリからコピーしてくるところで、

git submodule を使いましょう。

とよく見かけるので、どういうことか調べました。

今回のテーマの「Getting up and running」の方法で“recommended”と書かれていたのがgit submodule だったのでこちらを行っていました。
git clone とどう違うのでしょうか。

こちらは今後の課題として置いておいて...。

どうにもこうにも、Netlifyの英文をいくら(翻訳して)読んでも分からなかったので、サイトを一回削除して作り直しました。

Netlifyの画面

f:id:yokoyoko_115:20210326005927p:plain
1回目のときはBuild commandが空だったような。いけるかもしれない

またもや失敗。Deploy logを読んでみる

ダメもとで logを読んでみました。

ERROR 2021/03/25 15:45:25 HUGO-CLARITY theme does not support Hugo version 0.54.0. Minimum version required is 0.63.0

訳すと

ERROR 2021/03/25 15:45:25 HUGO-CLARITYテーマはHugoバージョン0.54.0をサポートしていません。最低限必要なバージョンは0.63.0です

Hugoのバージョンをアップデートしていなかったことが原因かもしれません。
Hugoを初めてインストールしたのは去年で、それきり触っていませんでした。

ローカル環境でHugoのサイトは見れましたが、デプロイ時には問題なのかもしれません。

Hugoをアップグレードする

brew upgrade hugo 

でアップデートできました。
すると、

Upgrading hugo 0.65.3 -> 0.82.0

と出てきました。

ローカルのhugoのバージョンはもとから0.63.0以上だったみたいです。
(ローカルでサイトは見れていたので当たり前といえばそうですね)

ということは、NetlifyでのHugoのバージョンを上げる必要がありそうです。

NetlifyのHugoのバージョンを設定する

Hugo 0.20以降はバージョンを設定することになるみたいです。

方法は

  • NetlifyのBuild & deploy > Environment で環境変数を設定する
  • config.toml ファイルに設定する

2つあります。

今はNetlifyへ設定する方法で進めます。

Environmentへの行き方
Sitesからサイトを選択 > 上のメニューやサイトの概要部分のSite settings > 左のメニューにBuild & deploy があります。

f:id:yokoyoko_115:20210327002923p:plain

バージョンはローカルと揃えました。

さぁうまくデプロイできるでしょうか!?

cssが読まれていない。

残念でした。

cssなどのパスや記事などへのリンクもhttps://example.com/になっていていました。
リンク先は

f:id:yokoyoko_115:20210327003746p:plain

と出てきました。

翻訳にかけると

例示ドメイン このドメインは、文書の中で例示的に使用するためのものです。このドメインは、事前の調整や許可を得ることなく、文献に使用することができます。

???

「example」の説明な気がします。

もしやと思い、config.toml ファイルの設定を見直しました。

すると

baseurl = "https://example.com/"

とありました。ここを変えれば良さそうです。
こちらもNetlifyから設定してみます。

Environmentで設定してみる

しかし、Environmentにbaseurlを設定しても変わりませんでした。

ビルドコマンドを設定してみる

そこで、ビルドコマンドを hugo -b $DEPLOY_URL にすると良い、という情報を見つけたのでやってみました。

Netlifyのプレビュー用URLに合わせてHugoのbaseURLを変更する · Issue #11 · kai2nenobu/www · GitHub

Hugoは -b, --baseURL オプションで config.toml の baesURL を上書きできる。なのでNetlifyのビルドコマンドにそれを指定してあげればいい。

フォーラムの例では netlify.toml にコマンドを指定しているが、NetlifyのWebでもできる。具体的には Settings > Build & deplooy > Continuous Deployment > Build settings の Build command に hugo -b $DEPLOY_URL を指定すればよい。

$DEPLOY_URLにはプレビュー用のURL?が入っているのでしょうか。

すると、トップページは相変わらず崩れたままで、リンク先の記事のページは、URLの頭に英数字がたくさん並んでいました。これがプレビュー用URLっぽいです!

目指している解決法とは少し違うみたいです。

config.tomlを修正する

はじめからこの方法を取ればよかったのですが、Gitが謎の状態になっていたのであまり触りたくありませんでした。遅かれ早かれ触るのですが。

Gitの謎現象は、きっとsubmoduleを使っている影響だと思います。 (リポジトリ?が2つ並んでいる)

baseurl = "https://example.com/" の部分を修正して、あたり障りなくコミットしてプッシュしました。

ようやく表示できました!

初心者には全然簡単ではありませんでした!

今度はプレビュー画面にcssが効かなくなってしまいました。

submoduleで作られたリポジトリは、今後どういう扱いをしていけばいいのかもわかりません。

ただ更新の仕方は、GitHubにプッシュ→Netlifyでビルド&デプロイ ということは分かりました。

少しずつ調べながらHugoサイトをカスタマイズしていこうと思います!

おしまい。

gulp導入の覚え書き(後編)- gulpとnpmの使い方

gulpを利用した当初は、なすがままに設定していただきました。
コマンドもほぼはじめましてだったので、黒い画面に呪文...と、何が起きているのか理解に苦しみました。

現在理解している範囲でnpm、gulpまわりで集めた情報をまとめます。

大まかな流れ

1. homebrewをインストール  
 ↓  
2. anyenvをインストール  
 ↓  
3. nodenvをインストール  
 ↓  
4. Node.js(npm)をインストール  
 ↓  
5. gulpをインストール・・・後編ここから  
 ↓  
6. gulpをつかう  
 ↓  
7. npmをつかう

前編の続きです。
gulpのインストールからnpmの使い方までまとめます。

gulpとは

gulpはNode.jsをベースとしたビルドシステムヘルパーです。Gruntと似た目的を持って作られたツールで、gulpを使えばさまざまな作業を自動化することができます。一番の特徴はファイルの処理をストリームで行う「ストリーミングビルドシステム」です。この特徴によって複雑なタスクも細かくカスタマイズして書くことができます。

(一部抜粋)

現場で使えるgulp入門 | 第1回 gulpとは何か | CodeGrid

ビルドシステムヘルパー...。
ストリーミングビルドシステム...。
まずはざっくり理解できればと思います。

gulpはNode.jsのライブラリのうちの一つであることは覚えておきます。

☆以下「ライブラリ」だったり「パッケージ」だったり、「モジュール」だったり用語が出てきますが、厳密な違いが分かっていないのでほぼ同じ認識で進めます。
(パッケージ > ライブラリ > モジュール の順の大きさだと思っています)

5. gulpをインストールする

ようやくgulpをインストールする準備ができました。
まずはNode.jsインストール時に一緒にインストールされたnpmの設定です。

プロジェクトのディレクトリに移動して行います。

npmの初期化

Node.jsのパッケージを使うにあたり最初に行います。
下記コマンドでpackage.jsonというパッケージ管理ファイルを作ります。

npm init

対話形式でpackage.jsonの設定値を入力していきますが、この時点で必ず必要ではないのでreturn(enter)キーで進めて大丈夫です。
最後にこれらの設定値でよいかyes/noで聞かれます。

詳細はこちらを参照しましょう。

初期化処理を行う!npm initの使い方【初心者向け】 | TechAcademyマガジン

また、npm init -ynpm init -yes とオプションを付けたコマンドを打つことで、対話などを省略することができます。

中には npm init --yes と、ハイフン(ダッシュ)が2つのコマンドも見掛けました。
これはLinuxの中で「UNIXスタイル」や「GNUスタイル」などのスタイルの違いから来ているそうです。複数のオプションをつけるときの書き方も異なるので注意です。

Linux引数(オプション)のハイフン-と--の違い - Qiita

なお、npm(Node Package Manager)については今のところNode.jsのパッケージ管理ツールである、とだけ認識して進めます。

package.json 中身の主な設定

"name":パッケージ名
"version":最初は"1.0.0"。パッケージの内容を変えるときに変更する
"author":作った人の名前
"dependencies":npmでインストールしたパッケージ(モジュール)
"scripts": script コマンド のプロパティ。キーがイベント、そして値がコマンド??

npmからglupをインストールする

npmコマンドでNode.jsのパッケージであるgulpをインストールします。

npm install gulp 

glupのバージョン確認

gulpのインストールが終わったら、

gulp -v

と、バージョンを確認しましょう。

npmでのパッケージのインストールについて

引っかかった部分や、覚えておきたい内容をまとめました。

パッケージのインストール

方法はオプションによって異なります。

npm install パッケージ名 -P

npm install パッケージ名 --save-prod
npm install パッケージ名 --save
npm install パッケージ名(オプションなし)と同じです。
package.jsondependencies フィールドに追加されます。

npm install パッケージ名 -D 

npm install パッケージ名 --save-dev と同じです。
package.jsondevDependencies フィールドに追加されます。

npm install -g パッケージ名

パッケージのグローバルインストールです。
パッケージのグローバルのインストールはお薦めされていません。

package.jsonに書かれているパッケージをインストールする

npm install

同じプロジェクトを複数人で開発する時に使うと便利なコマンドです。
Gitでpackage.jsonを管理しておくと、複数人の環境を npm install で揃えることができます。

上記のようにオプションなしではpackage.jsonのdependenciesとdevDependencies両方に書かれているパッケージをインストールします(npm install --production でdependenciesのパッケージのみインストール)。

dependenciesとdevDependenciesの違い

「dependencies = 依存(複数)」、「development = 開発」です。

ここではパッケージを開発用公開用に分けて考えます。

いつもプロジェクトで作るパッケージは開発用です。npm init でpackage.jsonを作ってパッケージを設定します。

これとは別に、公開用のパッケージ、すなわちインストールされるパッケージを作る場合はdependenciesとdevDependenciesを使い分ける必要があります。

【いまさらですが】package.jsonのdependenciesとdevDependencies - Qiita

パッケージを使いたい人がnpm install パッケージ名としたときにはdependenciesに書かれているパッケージのみがインストールされます。

公開パッケージを作る、というのは中々ないかなと思います。
開発のためのパッケージの使い方をどうすればいいか考えたところ、

npmのpackage.jsonと依存関係を理解しよう! - bagelee(ベーグリー)

全てdependenciesに追加してしまってもいいのですが、これらの違いはNode.jsサーバーとして動かすときやパッケージを公開するときに影響してきます。普段から、プログラムの動作には必要なく開発やテストのときのみに使うパッケージはdevDependenciesとして追加するように意識するといいでしょう。

開発やテストの時に使うパッケージとプログラムの動作に使うパッケージが異なる、というイメージがあまり持てず、そのパッケージに依存しているパッケージは全部必要に思えてしまうのですが、そういう場面に直面しなと実感できなさそうです。

なにはともあれ、開発だけのパッケージの場合はdevDependenciesに入るようにインストールする習慣をつけようと思います!

パッケージの開発、インストールまとめ

開発パッケージの場合 インストールされるパッケージの場合
パッケージに対して使うコマンド npm install npm install パッケージ名
コマンドのはたらき package.jsonに書かれているパッケージをインストールする
(他の人とパッケージの内容を共有できる)
パッケージをインストールする
インストール先のpackage.jsonへの追加(dependencies/devDependenciesはオプションで選べる)
dependenciesの
パッケージ
devDependenciesの
パッケージ

--productionをつけるとインストールしない

gulpのインストールはグローバルなのかローカルなのか、両方なのか問題

2021年現在ではローカルのみにインストールするのが主流のようです。

グローバルインストールでは、パスが通っている状態なので、gulp とコマンドを打つと、グローバルなgulpの実行になります。

なのでローカルのみのインストールでは不便なのかというと、npm-scriptnpx(後述)を使えば簡単にローカルのgulpを実行することができます。

問題の「両方」については、

gulpのインストールは通常ですとローカルとグローバルの両方にインストールする必要があります。グローバルにインストールされたgulpは、ローカルにインストールしたgulpを実行するのが役割です。

とのことで、仕組みは以下のように、

gulpのアプローチ "なぜグローバルとローカルにインストールが必要なのか" | じまぐてっく

グローバルなgulpのコマンドを叩くことで、ローカルなgulpを実行することができるのだと解釈しました。

現在のgulpのバージョン(4.0.2)では同じコードを見つけられなかったのですが、仕組みは同じではと思いました。

☆ローカルのみにgulpをインストールした環境で以下進めています

package-lock.jsonとは

まずは「ロックファイル」について調べました。

ロックファイル (lock file)
https://wa3.i-3-i.info/word12436.html

排他制御というのをするファイルなのですね。トイレの例を覚えよう。

他の言語などの場面では .lock という拡張子のロックファイルがあったりするみたいです。

そろそろlockファイルを理解するための最初のページ【composer.lock/package-lock.json】 - Qiita

ロックファイルの働きが、実際の作業の流れで見ていくとわかりやすかったです。

パッケージの公開も、こんなに簡単にできてしまうとは驚きでした。

こちらでも簡単にまとめられています。

【Node.js】package.jsonとpackage-lock.jsonについて簡単にまとめる - Qiita

買うものリストと領収書!

これらをまとめると

  • モジュールをインストール、またはアップデートする時、package.jsonにインストールするモジュールとバージョン(キャレット^やチルダ~付き)が書き込まれる→node_modulesフォルダにモジュールが入る→package-lock.jsonにインストールされたモジュールとバージョンが書き込まれる
  • package-lock.jsonはnode_modulesの中身やpackage.jsonによって自動で作成・変更される
  • npm install をする時、package-lock.jsonがあると、package-lock.jsonに書かれているバージョンのパッケージがインストールされる
  • npm install をする時、package-lock.jsonがないと、package.jsonに書かれているパッケージの新しいバージョンがインストールされる(package.json^2.0.0 と書かれていたら、2.5.0がインストールされることもある)

大体あっていると思います。

package-lock.jsonもGitで管理しましょう!

6. gulpをつかう

gulpfile.jsを作る

制作に便利なタスク(処理)をgulpfile.jsに書いて、コマンドで実行します。

2021年3月現在はgulp4が最新なので、その書き方で覚えていきます。

主に以下の記事を見ながら書きました。

Gulp4の変更点と新しい書き方 - Qiita

gulp4の設定方法 - SassやAutoprefixer、ejs、画像の圧縮などを自動化する | 夢みるゴリラ

調べるとgulp3を使っている人向けの記事ががほとんどなので、3と4の違いも分かります。同時に混乱します。

とても簡単にgulp(gulp4)の書き方をまとめると

  • task() を使わずに、タスクの関数宣言をする(無名関数を変数に入れたり、関数名を付けたり)
  • タスクの関数の中は大体 return ではじまる
  • exports.gulpタスク名 = タスク名(変数、関数) でgulpコマンドにタスクを設定する
  • gulpタスクの実行コマンドは gulp gulpタスク名(※gulpのローカルインストールでは実行できない)
  • exports.defalut とgulpタスク名を defalut にすると、実行時に gulp default と打たなくてよくなる(gulp でdefalutタスクが実行される)

という感じです。 肝心のタスクの中身は 心のタスクク中身はgulpのpipe()series()parallel()メソッドを使って、 処理をつなげたり、並行処理したりします。

何とかコードは読めはするものの、自力では書けないのでもう少しがんばりたいです。

***

ひとつ、冒頭のrequireの書き方で気になった部分があったので調べました。

const { src, dest, watch, lastRun, parallel, series } = require("gulp");

分割代入によりオブジェクトの特定のプロパティだけを単独変数に取得する (Object destructuring) | まくまくJavaScriptノート

これはES2015から使える分割代入というのですね。

gulpの中身はオブジェクトで、

const gulp = {
  'src': 'srcValue',
  'dest': 'destValue',
  'watch': watchVvalue'
}

などとなっているのかな、ーと思いましたが、ざっと見る限りそうでもありませんでした。
安直でした(笑)
実際どうrequireされているのか不明です。

const { src, ... のように書くと、gulpメソッドをつかう時に gulp.src ではなく src で済む、というものでした。

gulpコマンドでgulpタスクを実行する

ここではnpmの働きと分けるためにgulpコマンドから実行することを考えます。

先ほど「gulpタスクの実行コマンドは gulp gulpタスク名(※gulpのローカルインストールでは実行できない)」
と書きました。

gulpをローカルのみでインストールしている場合は簡単なコマンドではうまくいかないのです。

たとえば

gulpfile.js(一部)

// Sassファイルを監視し、変更があったらSassを変換するタスク
const watchSassFiles = () => watch('./scss/**/*.scss', compileSass);

// タスクの宣言
exports.watch = watchSassFiles;

と書いた場合、gulpfile.jsがある階層で
gulp watch とコマンドを叩けば実行できるかというとできません。

「command not found: gulp」(gulpコマンドが見つかりません)と返ってきます。
ローカルなgulpにはパスが通っていないのです。

なので./node_modules/.bin/gulp watch と叩けば実行できます。
これは毎回実行するのが大変です。

そこで便利なのがnpmです!

7. npmをつかう

npm コマンドからgulpのタスクを実行すると、コマンドもシンプルです。
また npx コマンドも使えるのでそれぞれ見ていきます。

npm-script

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

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

gulpfile.jsのたとえを受けてpackage.jsonが次のように書かれているとします。

package.json(一部)

 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "sass": "gulp sass",
   "start": "gulp watch"
}

echo \"Error: no test specified\" && exit 1シェルスクリプト(コマンド)で、これを test という別のコマンド(スクリプト名)に置き換えています。

実行するコマンドは

npm run test
npm run sass
npm start

になります。
npm runrunを付けます。

なおスクリプト名がstart、testの場合は run は不要です。
(↑は npm test でもOK)

runにまつわるお話も見つけました。

`npm run` は正式なコマンドじゃなかった件(NPMおれおれAdvent Calendar 2019 – 01日目) | Ginpen.com

gulpに関わらず、いろいろとコマンドを設定して使いやすくできるようです!

npx

npxはnpmのバージョン5.2.0以降に入っています。

npm 5.2.0の新機能! 「npx」でローカルパッケージを手軽に実行しよう - Qiita

ローカルにインストールしたnpmパッケージを、npxコマンドだけで実行できるようになります。

なので上記のnpmコマンドの代わりにnpxを使うと、

npx gulp sass
npx gulp watch

で実行できます。

こちらもgulp以外にもインストールしたパッケージを実行する時に、手軽にできて便利です。

さいごに

gulpとnpmのことは、ずっとまとめようと思って温め続けていました。
ようやく書けました。スッキリ!
(後々見返して意味が分かるのかは怪しい)

存在知りたての頃よりだいぶ分かってきたと思います。

私の場合はnpmの入り口がgulpでしたが、他の用途や機能の異なるパッケージも使っていけばもっと体系的に分かってくるのだろうと思います。

おしまい!

gulp導入の覚え書き(前編)- Node.jsのインストールまで

gulpを使おうとなった時に以前行った手順は記憶の彼方なので、これまでのメモをまとめました。
(特にNode.jsまでは一度入れてしまえば中々入れ直すこともないと思うので忘れてしまいます)

☆長くなるので前・後編に分けました。後編はこちら

大まかな流れ

以下に沿って進めます。

1. homebrewをインストール  
 ↓  
2. anyenvをインストール  
 ↓  
3. nodenvをインストール  
 ↓  
4. Node.js(npm)をインストール  ・・・前編ここまで
 ↓  
5. gulpをインストール

1. homebrewをインストールする

homebrewとは

App Storeで扱っていない、開発をするためのソフトウェアをインストールする際に依存関係を管理してくれるツールです。

一言でいうと、macOSのパッケージ管理ツールです。
以前調べました(記事)。

homebrew=自家醸造=ユーザー自らがビルドする」でしたね!

日本語のhomebrew公式ページ
「これをmacOSのターミナルまたはLinuxのシェルプロンプトに貼り付けて下さい。」という部分を、コピペでコマンドラインで実行します。

これでbrewコマンドが使えるようになります。

【参考】homebrewについて

【簡単】MacにHomebrewをインストールする方法と基本的な使い方|新卒エンジニアの開発日記

2. anyenvをインストールする

anyenvとは

pyenvやrbenv、phpenv、nodenvなど「〜env」と名のついた、プログラミング言語バージョン管理ツールを一元管理するツールです。
各環境(仮想環境とか、プロジェクト)ごとに各言語のどのバージョンを使っているかを管理してくれます(と解釈しました)。

anyenvのメリットです。

  • 現在のバージョンを何に設定しているか、どのバージョンが入ってるかまとめて確認できる
  • 〜env系を簡単にインストールできる
  • 新しいプログラミング言語を追加しても.bash_profileや.zprofileなど(bashzshといったシェルの設定ファイル。パスを通す時に記述する)に書き込まずに(汚さなくて)済む
  • VM (VirtualMaschine)での開発でプロジェクトを切り替えるとき、いちいち VM を立ち上げ直す手間がない
  • プロジェクトごとにバージョンを切り替えて開発できる

VMのことなど、一部まだよくわかっていないですが、便利ということは分かりました。

anyenvのインストール方法は2つあります。 ※以下シェルがzshの書き方です。

方法1:brewコマンドを使う

brew install anyenv
echo 'eval "$(anyenv init -)"' >> ~/.zshrc
exec $SHELL -l

2行目は anyenv init - を実行する処理を echo>> を使って .zshrc に記述します。

3行目でシェルを再起動させます。

シェルを再起動させる簡単な方法 - Qiita

exec $SHELL -l とは以下の意味だそうです。

環境変数 $SHELL をログインシェルから起動させることを exec するという意味となり、ターミナルが再起動できるようになります。

まだ理解するには早かったみたいです...。

方法2:gitコマンドを使う

GitHubにあるリポジトリをクローンしてインストールします。

git clone https://github.com/riywo/anyenv ~/.anyenv
echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(anyenv init -)"' >> ~/.zshrc
exec $SHELL -l

GitHubに説明がありました(英語です)。

GitHub - anyenv/anyenv: All in one for **env

【参考】anyenvについて

anyenv + macOS環境構築 - Qiita

3. nodenvをインストール する

anyenvをインストールして使えるようになったanyenvコマンドから「〜env」をインストールできるようになります。

nodenvとは

プロジェクトごとにNode.jsのバージョンを自動で切り替えてくれるツールです。

コマンドラインでnodenvをインストールします。

anyenv install nodenv

exec $SHELL -l

以降のNode.jsのインストール手順もこちらに書かれています!

Node.jsのバージョンを自動で切り替えられるnodenvが超便利 - Qiita

コマンドの使い方が丁寧に書かれています!

anyenvとnodenvでNode.jsのバージョン管理をしよう! - to-R Media

4. Node.js(npm)をインストール する

npmはNode.jsをインストールすると、もれなくインストールされます。

まずグローバルでNode.jsのインストール、バージョンの設定をします。

グローバルでNode.jsをインストールする

Node.jsはバージョンを指定してインストールします。必要なバージョンをグローバルでインストールして、全体、各プロジェクトでバージョン指定を行って使います。

インストールできるバージョンの確認

ndenv install --list

バージョンがずらっと出てくるので、安定した最新のバージョンを選びます。
新規プロジェクトの場合は最新のものでOKです。
数字だけのもののうち、一番数が大きいものが最新です。

☆ここではv13.5.0をインストールしてみます。

インストール

nodenv install 13.5.0

グローバルでNode.jsのバージョン指定をする

インストールしただけでは使えないのでバージョンの指定をします。グローバルのバージョンがデフォルトになります。

グローバルでのNode.jsのバージョン指定

nodenv global 13.5.0

プロジェクト(ローカル)ごとにNode.jsのバージョン指定をする

バージョン指定をするファイルは .node-versionです。.node-versionファイルは直接作成する方法と、コマンドで作成する方法があります。

.node-versionファイルの中身

13.5.0

ローカルのNode.jsのバージョン指定

nodenv local 13.5.0

上記のコマンドで.node-versionファイルが作られます。
プロジェクト内に .node-versionファイルがない状態、すなわちバージョンを指定しない場合はグローバルで指定されているバージョンが使われます。

インストールしただけでは使えない、バージョン指定が必要ということなので、グローバルかプロジェクト、どちらかでバージョン指定が必要であることがわかりました。

バージョンの確認

node -v

または

node --version

ちなみにanyenvでは「〜env」ごとにインストールされている言語のバージョン(ここではNode.js)のリストと、指定されているバージョンが分かります。

anyenv versions

続きは後編で

後編ではgulpのインストールとnpmの使い方についてまとめます!

レスポンシブな画像の表示を考える

投稿するコンテンツの場合、アップする画像サイズを統一させられないのが常です。
いろいろな縦横比の画像を画面いっぱいに表示させる方法を模索しています。

wordpressなどでは、リサイズさせた画像を表示させたりするので、あまり頻繁ではありませんが、たまに詰まるときがあります。

ポイントは

  • レスポンシブ(拡大縮小しても縦横比は同じ)
  • 表示枠の中央に表示
  • 表示枠いっぱいに画像を表示する(余白が出ない)

これら3つをおさえることです。

現時点でこれがベスト!という書き方をメモします。

検証の条件

サンプル画像はこちらです。

f:id:yokoyoko_115:20210306003406j:plainf:id:yokoyoko_115:20210306003439j:plain

中央がわかりやすいと思ったものにしました。
(縦は微妙かもしれない)

これらはそれぞれ
427x640px
640x427px

のサイズで約長辺3:短辺2の比です。

※横長画像はコンテンツ幅に合わせて小さくなっています。
2つの画像の比があっていないように一見見えますが同じです!(錯覚?)

これらを、幅4:縦3の枠にいっぱいに表示させることを目指します。

上記の3つのポイントを見ていきます。

htmlはこのように書いて検証しています。

html

<p class="frame">
<img class="frame_img" src="https://drive.google.com/uc?export=view&id=18asPUoAeh3QanjaSkyp_q-ETAAvc74fr" alt="縦長写真の猫">
</p>
<p class="frame">
<img class="frame_img" src="https://drive.google.com/uc?export=view&id=1_DRrKwnmc1SzcdYMp992SoCr2-EkhKCb" alt="横長写真の猫">
</p>

レスポンシブな枠の高さの作り方

レスポンシブの際にいつも悩む高さの指定ですが、height または、padding-top で考えます。

heightの場合

height で%を使うと、親要素の高さから値を取るのであまり使うことはないと思います。

一方でvwという、ビューポートの横幅を基準とした単位では、階層が深い要素だと指定が難しいです(要素のvwの数値を出す計算が大変)。

padding-topの場合

padding では%は、要素の幅から計算します。
縦:横 = 1:2 の場合は、padding-topは幅100%の半分の50%になります。

padding-topの出し方
縦 / 横 x 100%

ですね。

なのでとても使えます!

疑似要素を使うといい点

padding-top を使うときは疑似要素を使いましょう、と調べると出てきます。
ですが、例えば幅4:縦3の枠で、表示枠が width: 80% という指定だった場合

.frame {
  width: 80%;
  padding-top: 60%;    /* ←3 / 4 x 80% */
}

と、わざわざ疑似要素を作らなくても枠が作れるのです。

padding-top で高さを作る場合は、どちらにしろ表示枠の中の子要素には position: absolute を指定することになるので、必ずしも疑似要素が必要、というわけではありません。

ではなぜ疑似要素を作ることを勧められているのかというと

  • padding-top の計算がしやすい(縦 / 横 x 100% と、100%で計算できる)

ぐらいしか思い浮かびませんでした。
もっと複雑に要素を含ませたい場合に擬似要素の方が都合が良いのかもしれません。

☆ちなみに疑似要素(::before, ::after)はインライン要素なので、この場合 display: block を入れます。

画像を中央に配置する方法

中央というとまたまた position: absolute を使うか、または flexboxを使うかが考えられます。

position: absoluteの場合

.position_frame {
  position: relative;
  background-color: #000;
  padding-top: 15%;
  width: 20%;
  overflow: hidden;
}

.position_img {
  position: absolute;
  top: 0;
  bottom: 0;
  left:0;
  right: 0;
  margin: auto;
  width: auto;
  height: 100%;
}

position: absolute を使った中央配置の書き方はtop: 50%; left:50; transform: translate(-50%, -50%); transform: translateY(-50%); もありますが、私は↑の書き方のほうが好きです。 (transformを使わなくてはいけないケースがあったような)

flexboxの場合

.flex_frame {
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #000;
  width: 40vw;
  height: 30vw;   /* ←3 / 4 x 40vw */
  overflow: hidden;
}

.flex_img {
  width: auto;
  height: 100%;
}

paddingの部分にflexitemを置くことはできないので、flexboxでは height を使うことになります。
しかし使いづらい height を指定してまでflexboxにする意味はあまりなさそうです。

表示枠いっぱいはobject-fit: cover

imgタグを width: auto; height: 100%; とすると、画像の比率は保たれるものの、縦長画像の場合は高さが足りないことがあります。 width: 100%; height: auto; も同様です。

imgタグを使わずに枠の背景画像として background-size: cover を指定する方法もありますが、それができれば苦労しない、ということでここでは置いといて。
それ以外にある方法は、object-fit プロパティををつかう方法しかないと思います。

object-fit:cover で枠いっぱいに広げてくれます。
中央に配置する記述も不要!枠の大きさを決めるだけで良いなんて素敵です。
画像もwidth: 100%; height: 100%; でも歪む心配はありません。

こちらに詳しく書かれています!

1行追加でOK!CSSだけで画像をトリミングできる「object-fit」プロパティー | Webクリエイターボックス

object-fit プロパティは2021年3月現在ではIE以外で有効です。

Can I use... Support tables for HTML5, CSS3, etc

もう使ってもいいと思いますが、IE対応までしたいという場合は、潔くプラグイン入れたいところです。

IE11での画像のobject-fitの使用について – 東京のホームページ制作 / WEB制作会社 BRISK@22年新卒採用中

さらに前回の記事で書いたwidthとheight属性をimg要素に書けば完璧!

まとめ - 枠いっぱいのレスポンシブな画像表示の書き方

CodePenでの検証はこちらです。

See the Pen 画像のレスポンシブ表示検証 by yokoyoko (@yokoyoko_code) on CodePen.

うまくいかないときは

実際に私はうまくいきませんでした!
コードを同じようにコピーしてみても枠の高さがおかしい。
よくよく見ると親要素にdisplay: flexがかかっていました!

フレックスアイテム(flexboxの子要素)の高さはデフォルトで親要素いっぱいに広がります。なのでいくら子要素に高さを指定しても効かないようです。

そこで画像の枠である子要素に align-self: flex-start を設定することにより、高さが子要素の高さになります。

ちなみに

CodePenで画像を使っているものを見かけるのですが、方法がわかりませんでした。
調べて無料会員が画像を使う方法を会得しました!
個人でサーバーを持っていなかったらこの手があります。

CodePenの無料会員で画像を使うには | こんぷれ

最後に

自分の中ではベストな書き方がまとまった!とすがすがしさを感じていますが、人様が作ったサイトを見ることも大事ですね。
もっといい書き方があると思います。
研究しなくてはですね。

おしまい!

読み込む画像の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が分かるようになりたい...とじりじりしていますが、 少しずつ前へ進みたいと思います!