【JavaScript】関数式、無名関数、即時関数について

クロージャーについても理解が深まったので、即時関数についてもさくっと分かるかなと思いましたが、どうしても引っかかる...いや念のためまとめました。

function で始まる文は、関数名がないと文法エラーになる

無名関数をそのまま書くとエラーになるということです。

function() {
  // 何らかの処理
}

これだとエラーになります。
function で始まると関数宣言になるので、関数に名前を付けなければならないそうです。

たしかに名前がついていないと呼び出せませんね。

関数式の場合は変数宣言から始まり、変数に関数を代入しているので問題ありません。

なので無名関数の使い方は変数に代入するか、定義してすぐに実行するか(=即時関数)です。

const a = function() {
  // 何らかの処理
};

// 実行
a();

即時関数は「無名関数」+「実行」

カッコの書き方を忘れてしまいがちな即時関数ですが、2つの () の意味がわかれば思い出して書けそうです。

即時関数の使い方と構造について | JavaScript中級編 - ウェブプログラミングポータル

『function(){}』を『()』で包んでしまえば中身に影響を与えることなく『無名関数』が定義できるということです。

そして関数を実行させるための『()』をつければ『即時関数』となります。

エラーにならないように、即時関数では無名関数を () で囲っているのです。

上の記事では、例で () の代わりに + を最初につけてエラーを回避しています。
ただ + では別の問題が発生するので () がいいのですね。

(function() {
  // 何らかの処理
})();

// このように()で囲ってもOK
(function() {
  // 何らかの処理
}());

これは関数を定義と同時に実行していることになります。
一度きりの実行なので名前をつける必要もありません。

即時関数とクロージャ

これはクロージャーを学んだときの書き方を思い出せば良さそうです。
前回と同じように図にしてみました。

returnされた変数xは初期値の0のままなのに対し、クロージャーの中の変数xはinner関数を実行する度に1ずつ増えます。
0(xの初期値) + 1(x++の部分) + 3(y) = 4
1(x) + 1(x++) + 3(y) = 5
:
です。

即時関数ではないクロージャーでは、同じ機能を持ったクロージャーを複数作れますが、即時関数は1つのみ。
今のところ大きな違いはそれぐらいかなと思います。
(学習して新しく発見したら追記します。。)

関数式と即時関数

即時関数は return を使う、というイメージを持っていますが、関数を返しているのは普通の関数式と同じです。

// 関数式(無名関数)を変数に代入
const a = function() {
  let x = 'kansushiki';
  console.log(x + ' is called');
};
a();    //  kansushiki is called

// 即時関数を変数に代入
const b = (function() {
  let y = 'sokuji'; 
  function fn() {
    console.log(y + ' is called');
  }
  // returnの中身(関数fn)が変数bに代入される
  return {
    fn
  }
})();
b.fn();    //  sokuji is called

違うところは、やはりクロージャーがあるかどうかの違いだと思います。
実際に書いているうちに使い分けができるようになるといいです。

文と式について

関数式は最後に ; を付けるのが正しい書き方です。
ということを少し掘り下げてみると、文と式について知ることができました。

文と式 · JavaScript Primer #jsprimer

※だいぶ端折っているのでこちらの記事をご覧ください。


変数に代入できるもの。数値、演算子を使ったもの、関数式(無名関数)関数の実行(fn()


最後に ; を付けて処理に区切りをつける。関数宣言(名前がある)、if文、for文
※ブロック {} の最後には ; は不要。

☆式文:文になった式のこと。関数の実行(fn();

では const a = 1;let b; などは文なのでしょうか?

本で確認すると「const文」、「let文」と書かれているので文のようです。

なので関数の定義を変数に代入している処理は、const a = 1; などと同じく「文」なので、最後に ; が付きます。
const a = function() {}; などです。

ちなみにChatGPT に const x =1; は文なのかどうか聞いてみると、

「const x = 1; は厳密には 文(statement)ではなく 宣言(declaration)です。JavaScriptにおいて、宣言は文の一部です。」

と返ってきました。文ではないが文の一部...なぞなぞ...?

さいごに

少し知ることができた文と式。今後の役に立てばいいです...奥が深い。

関数関連ではこの即時関数とは別に「コールバック関数」があります。
それが理解できれば関数まわりは理解できそうです。 あと this

おしまい。

【JavaScript】クロージャーについて(スコープとレキシカルスコープ)

前回に引き続き、勢いのあるうちにまとめようと思いました。

調べてみると、クロージャーとは「状態」、「記法」、「仕組み」、「関数」といろいろありました。

そして分かりにくいもののようです。。

クロージャーはスコープ、主にレキシカルスコープと出てくることが多いようなので、レキシカルスコープについて先に調べます。

レキシカルスコープとは

実はレキシカルスコープはJavaScriptスコープの種類としては、あまり紹介されていません。

まずはスコープについて調べました。

スコープとは

一番理解できた記事です。
JavaScriptのスコープ総まとめ | 第1回 スコープの種類とその基本

JavaScriptにおけるスコープとは、変数がどの場所から参照できるのかを定義する概念です。言い換えれば、変数の有効範囲ということです。同じスコープ上にある変数にはアクセスできますが、スコープが違えば、別々のスコープにある変数にはお互いにアクセスすることができません。

ここで関数の参照範囲についてはいわないのかな?と思いました。

関数とスコープ · JavaScript Primer #jsprimer

スコープとは変数の名前や関数などの参照できる範囲を決めるものです。 スコープの中で定義された変数はスコープの内側でのみ参照でき、スコープの外側からは参照できません。

おそらく関数についてもスコープという呼び出せる参照範囲があるのだと思います。

「スコープの中で定義された変数は...」とあるので、イメージとしては、

  1. コードを解析して、上記のスコープが形成される
  2. 変数・関数の定義をする
    1. を記述した場所によって、その変数・関数の参照範囲(1. で作られたスコープ)が決まる。

ということなのかなと思いました。

なので解析時、もとからある(以下で説明している)「スコープ」と、定義時に決まる変数・関数の「スコープ」、2つの意味で「スコープ」が登場していると考えました。

「スコープ」に慣れれば違和感はないのでしょうが、最初は(今も?)言葉尻が気になって悶々としていました。
...が、たくさんスコープのことを考えていたら馴染んできました。

スコープは大きく分けて2種類、細かく分けると5種類あるそうです(ローカルスコープを除いて5種類)。

そして、上記の5種類に入っていないレキシカルスコープは、そのまま「範囲」を指す言葉ではなさそうです。。

ここでグローバル・関数・ブロックスコープの特徴をまとめます。

グローバルスコープ

グローバルスコープで定義すると、どこからでも参照できる。
letconstデベロッパーツールで見るとスクリプトスコープになるが、var, function は定義時にウィンドウオブジェクトに格納され、デベロッパーツールではグローバルスコープになる。

// 定義:グローバルスコープ
var a = 'global_var';
let b  = 'global_let';

function fn() {
    console.log('fn is called');
}

debugger;

debugger; の行で処理を止めてブレークポイントとします。 この時点で 変数a, b、関数fn のスコープを確認してみます。

たしかに確認できました。
ブレークポイントを使う機会がやってきました。

ここで分かることは、どこで実行するかによって参照できる変数・関数が決まっている、ということです。
なので「各変数・関数のスコープ外で実行しているときはその変数・関数を参照できない」と言えます。

ローカルスコープ

範囲が限定されているスコープ。デベロッパーツールでも「Local」と表示されます。 関数スコープとブロックスコープのどちらかです。

関数スコープ

function(){ }{ } の中。関数スコープで定義された変数・関数は、同じ関数の中からしか参照できない。
さらに入れ子になっている関数から呼び出すことができる。

function fn() {
    // 定義:関数の中
    function fnInner() {
        console.log('fnInner is called');
    }
    fnInner();     // fnInner is called

    function fnInner2() {
        //参照:他の関数の中
        fnInner();
    }
    fnAInner2();     // fnInner is called
}
// 参照:グローバルスコープ
fnInner();    // Uncaught ReferenceError: fnInner is not defined

ここで関数の例えはわかりにくいかもしれませんが...
fn関数の中で定義したfnInner関数は、fn関数の中が参照範囲(スコープ)になる。その外側からはfnInner関数を参照できない。
fnInner2関数の内側もfnInner関数のスコープ内なので、fnInner関数を参照できる。

こんな感じです。

変数や、関数の引数(仮引数)も同じ関数の内側からのみ参照できます。

ブロックスコープ

{ } の中。オブジェクトやif文、for文などもブロックスコープになります。

{
    // 定義:ブロックスコープ
    var v = 'block_var';
    let l  = 'block_let';
    const c  = 'block_const';

    function fn() {
        console.log('fn is called');
    }

}

// 参照:グローバルスコープ
console.log(v);   // block_var
console.log(l);   // Uncaught ReferenceError: fnInner is not defined
console.log(c);   // Uncaught ReferenceError: fnInner is not defined

fn();   // fn is called

'block_var'と'fn is called' が表示できるのは、変数v関数fnのスコープがグローバルスコープになっている、つまりブロックスコープを作っていないということです。

var と関数の宣言はブロックスコープを無視する、は覚えておこうと思います。

スコープの特性

これらのスコープの種類を踏まえて、JavaScriptのスコープには以下の特性があります。

同一スコープ内では同じ変数名の宣言はできない

varfunction の宣言は例外的にできるそうです。

まずやらないですが、letvar を混ぜた場合でも宣言はできません。

let a = 1;
var a = 2;  // Uncaught SyntaxError: Identifier 'a' has already been declared

エラーの翻訳は「構文エラー: 識別子 'a' は既に宣言されています。」でした。

スコープが異なれば同じ名前の変数が宣言できる

同一スコープ内ではだめ、ということはスコープが異なればOKです。
以下はグローバルスコープ、ブロックスコープ、さらにその中のブロックスコープ、それぞれで同じ変数名(x)で宣言しています。

let x = 1;
{
  let x = 2;
  console.log(x);  // 2

  {
    let x = 3;
    console.log(x);  // 3
  }
}

console.log(x);  // 1

同じ変数名でもスコープごとに別々の変数として扱われます。

注意したいのは以下のような場合です。

let x = 1;
{
  console.log(x);    // エラー(Uncaught ReferenceError: Cannot access 'x' before initialization)

  let x = 2;
}

console.log(x) の値は現在のスコープの中、let x = 2 だから2かとおもいきや、エラーになります。

これはブロックスコープの中で変数の巻き上げ(ホイスティング)が起こるためです。
let の変数宣言の場合は何も代入されないので、このように「初期化前のため参照できません」と言われます。

ホイスティングについてはこちらでまとめました。

宣言はできるだけスコープの最初にしましょう!

変数の参照は同じスコープ→外側 の順

この参照が書かれている現在のスコープから外側のスコープへ定義されている変数・関数をたどっていくことをスコープチェーンと言います。

let x = 1;
{
  let x = 2;
  {
    console.log(x);  // 2
  }
}

{
  {
    console.log(x);  // 1
  }
}

上の例のように、参照しているスコープで変数の定義がされていない場合は、一つ外側のスコープに探しに行きます。それでもなければさらに一つ外...となります。

ここでひとつ脱線ですが、定義(宣言)ではなく代入を考えてみます。

let x = 1;
{
  console.log(x);  // 1
  x = 2;
  
  {
    console.log(x);  // 2
    x = 3;
  }
  console.log(x);  // 3
}

console.log(x);  // 3

この場合は、定義は1行目でのみで行われていて、グローバル変数なので、変数xはブロックスコープからも参照できます。 つまり同一の変数に順に代入しているだけなので、上記のように出力されます。

同じ名前の変数をいろいろなところで宣言するケースもあまりないと思いますが、混乱したときに振り返られるようにしました。

「宣言(宣言したスコープはどこか)、代入・参照(宣言したスコープ内かどうか)」を注意して考えて行こうと思います。

レキシカルスコープとは静的に決まるスコープ

ようやくレキシカルスコープに戻ってきました(本題はクロージャー)。

lexical scope(字句の範囲)」と、翻訳されました。あとは「lexical=語彙的」などです。
(どうしても「歴史カル」と頭で変換されてしまう日本語脳を持っているのは私ぐらいでしょうか。。)

レキシカルスコープは、定義側というより参照側目線の印象を持ちました。

関数内の、変数が参照するスコープは、関数の定義時に決定するというものです。

let x = 'global';

function A() {
  console.log(x);    // 参照先がグローバルスコープで固定
}
A();    // global

function B() {
  let x = 'fnB';
  console.log(x);    // 参照先が現在の関数スコープで固定
  
  A();    // 関数A内のxの参照先は固定されている→global
}
B();    // 関数A内のxの参照先は固定されている→fnB

// 再代入してみると...?
x = 'global2';
A();    // global2

関数A内で参照している変数xはグローバル変数のx、関数B内で参照している変数xは1行上、同じ関数スコープ内のローカル変数のxというのはすぐ分かると思います。

問題は、関数B内で参照している関数A、から参照している変数xです。

関数A内での変数xのスコープは定義時に決まるため、この場合グローバルスコープにある変数が参照されます。

はじめx = 'global' までが固定なのかと思ったのですが、試しに x = 'global2' と再代入して関数Aを実行してみたところ、'global2' が出力されました。

固定されるのはあくまでスコープであることがわかりました。

このレキシカルスコープは、JavaScript以外にもいくつかの言語で使われているそうです。
反対にダイナミックスコープ(動的スコープ)を使う言語もあります。
これはざっくりいうとレキシカルと反対です。参照元の位置でその都度スコープが変わります。

ここまででレキシカルスコープが理解できました。
お次はついにクロージャーについてです。

クロージャーとは

いくつか記事を読んでなんとなくわかってきました。
そのうちの個人的に特に分かりやすかった記事を載せます。

【JavaScriptの基礎】レキシカルスコープとクロージャを理解する | WEMO

関数内部で定義されたローカルな関数がその親関数の実行によって外部スコープへと持ち出される時、その瞬間の環境を記憶したクロージャという特殊なオブジェクトへと変化する。

関数とスコープ · JavaScript Primer #jsprimer

クロージャーとは「外側のスコープにある変数への参照を保持できる」という関数が持つ性質のことです。

[JavaScript] 猿でもわかるクロージャ超入門 2 関数の中の関数 · DQNEO日記

クロージャとは関数である。

イメージ図

クロージャーの例としてよく書かれているコードから、クロージャーを示してみます。

ここでは関数を代入した変数のことを「クロージャー」とします。関数ですね。

代入した関数では、return でその「中の関数」を返しているのがポイントだと思いました。

「中の関数」では、外部で宣言した変数への参照を保持することができます。

なので、繰り返しクロージャーの関数を実行すると(calc())、この上↑の場合は、x が1ずつ増えます。

あるいは、異なる変数に親関数の戻り値を代入すると、それは別々のオブジェクトになります。

それが親関数が関数ファクトリー(Factory)言われる所以だそうです。

その他に覚えておきたいこと

本当は触れておきたかったメモリについても少しメモします。

ガベージコレクション

ガベージコレクションとは、どこからも参照されなくなったデータを不要なデータと判断して自動的にメモリ上から解放する仕組みのことです。

言語によっては「メモリの解放」方法は異なるようです。JSはガベージコレクションという、自動的にデータを削除する仕組みが備わっています。

下記のようなコードでは、関数内の変数は、実行完了時に参照(データ)を破棄されます。
実行の都度、新しい変数xを保管しては破棄しています。

function printX() {
  const x = "X";
  console.log(x);
}

printX();  // X xを定義、そして破棄
printX();  // X xを定義、そして破棄 

一方で、クロージャーの部分で登場した変数xは破棄されることなく保持されます。 内部関数(inner)から参照されているからです。

クロージャーは安全・便利!と多用していると、メモリの問題もあるというお話でした。

さいごに

クロージャーまで書くことができてよかったです。
(※適切な言葉と表現で書けたかどうかは怪しい)

まだ少しパターンの違うコードが出てきたら固まりそうだなと思いますが、結局は見て書いて慣れることかなと思いました。

最後に触れた「メモリ」について書くことが、 前の記事からの最終目標なのですが、果たして。。!?

おしまい。

【JavaScript】変数の宣言・初期化・参照について

前回の投稿から1年以上経ってしまいました。

現在UdemyのとあるJavaScriptの講座を見ています。 分かったつもりで進めてもこの先さらにつまずくだろうなと思ったので、忘れがちな部分をまとめました。

変数の宣言・初期化

変数の宣言・初期化・そして代入というのはメモリ上ではどういう状態?という疑問が出発点です。
そこにホイスティングが加わるとややこしいなと思ったので、以下検証しました。

let, const 宣言

変数宣言のキーワードである letconst には、変数が呼び出せる範囲、スコープがあります。

JavaScript講座第2章 基本文法 | DevDojo

letconst の特性です。

let

  • ブロックスコープ: let で宣言された変数はブロックスコープを持ちます。これは、{} で囲まれた範囲内でのみその変数が利用可能であるということを意味します。
  • letで宣言された変数も巻き上げられますが、初期化前のアクセスはエラーとなります。

const

  • ブロックスコープ: constもブロックスコープを持ちます。
  • 再代入不可: 一度値が代入されると、その後の再代入はできません。
  • オブジェクトのプロパティの変更は許可されます。

ここからは const よりもややこしい let について考えます。
var は非推奨で使わないので、思い切って割愛します。

先に変数を呼び出す

宣言や初期化をしていない変数(一部は変数ともいえないただの文字)を真っ先に呼び出すと下記のようになりました。

※この図ともう一つ下の図は、検証外のパターンをコメントアウトしつつまとめたものです。一気にこのように書いてもエラーが出てきません。

どれもエラーですが、宣言をする・しないでエラーの内容が異なることが分かりました。

変数の宣言をしていない

宣言キーワードがないとき「is not defined」という状態になります。

console.log(a);  // エラー
a;

このときのエラーは
「Uncaught ReferenceError: a is not defined」
(参照エラー:aは定義されていません)

「is not defined = 未定義」と訳すとundefinedと混同してしまうので書き分けたいなと思ったのですが、
この時点では letなどの宣言キーワードを使うのか、function の関数宣言を使うのか、aが何者なのかもはっきりしていないので、そういう意味では「未定義」でいいのかなと思いました。

変数の宣言をしている

一方で宣言キーワードがある場合は、変数にアクセスできない、というエラーが出ます。

console.log(d);  // エラー
let d = 1;

このときのエラーは
「Uncaught ReferenceError: Cannot access 'd' before initialization」
(参照エラー:初期化前に変数dにアクセスできません)

「'd'」とクォーテーションで囲まれているあたりが、「変数」と宣言はされている扱いになっているように思いました。

よって変数にアクセス(呼び出し)前に宣言と初期化が必要なのかなと思いました。

後で変数(あるいは文字)を呼び出す

今度は先に変数宣言をしたり、初期値を代入したり、しなかったりしてみました。

変数を宣言していない、初期値を代入していない

謎の「e」を呼び出すのは確かにエラーがでると予想できます。
しかも他と違い、エラーの位置が変数(もどき)を記述した行にありました。

e;      // エラー
console.log(e);

エラーは
「Uncaught ReferenceError: e is not defined」
(参照エラー:eは定義されていません)

先に変数を呼び出したときのab もそうですが、代入もされていないただの文字では、出てきただけでエラーになるのは納得できました。

変数を宣言していない、初期値を代入している

宣言していないにも関わらず初期値が代入されているという状態です。

f = 1;    
console.log(f);  // 1

[変数] 変数の基本|業務ができる中級者になるためのJavaScript入門(文法編)

JavaScriptは明示的に宣言をしなくても、初めて値が代入されるタイミングで暗黙的に変数を用意してくれます。宣言なしで作成された変数はすべてグローバル変数となる ので、宣言の省略は避けるべきです。

値が代入されているので、上記でいう f は変数と判断されるのだろうと思います。
宣言キーワードがないとブロックスコープが効かずグローバル変数になってしまうので、宣言キーワードを付けて初期値を代入しましょうとういうことですね。

変数を宣言している、初期値を代入していない

undefinedが代入されます。

let g;    
console.log(g);  // undefined

上記と同じ記事に書かれていたletについて、

初期値を指定しないで宣言できますが、その場合はデフォルト値として、undefined(未定義) という値が割り当てられます。またconstと同じく、同じ変数名での宣言はできません。なお、letは何かの略ではなく、数学で利用される「仮に~とする」という英単語です。

宣言した変数には初期値 undefined が代入されているということですね。

変数を宣言している、初期値を代入している

この書き方が最も平和です。

let h = 1;    
console.log(h);  // 1

ちなみに const の場合は、宣言と初期値の代入を同時に行わないとエラーになります。
↓宣言のみの場合

const gg;      // エラー
console.log(gg);

「Uncaught SyntaxError: Missing initializer in const declaration」
(構文エラー:const宣言に初期値がありません)

というエラーが出ます。

ホイスティングを考える

ここでホイスティングについて調べました。

ホイスティング(宣言の巻き上げ)
コンテキスト内で宣言した変数や関数の定義をコード実行前にメモリに配置すること。

console.log(d);  // エラー
let d = 1;

↑こちらはホイスティングが起こり、コード実行より先に let d と変数dの宣言がされます。が、コードで代入部分が来るまでは初期値が代入されないためアクセスできず(呼び出せない)エラーになります。

let g;    
console.log(g);  // undefined

↑ホイスティングが起こっている状況はこのコードと似ていますが、この場合は undefined割り当られるのです。

この2つが混乱していました。

これでブロックスコープとホイスティングが絡んだ変数宣言の問題が解けそうです。

let のホイスティングは無い?

let - JavaScript | MDN

var と let の違いは、後者が宣言文に到達した後でしかアクセスできないという点があります(一時的なデッドゾーンを参照)。このため、let 宣言は一般に巻き上げされないと見なされています。

宣言だけ行われて、初期化されず(undefinedが代入されない)参照エラーになることから、ホイスティングではないということでしょうか。

一時的なデッドゾーン(TDZ)というのがあるのですね。
「letにはホイスティングがないという見方もある」と覚えておきます。。

関数の宣言

関数についても少し書きます。

function 宣言 - JavaScript | MDN

関数宣言は宣言より先に実行のコードがあっても、ホイスティングにより宣言部分が巻き上げられて、実行のコードで実行します。
関数式は巻き上げられないことだけ注意すれば、すごくシンプルです。

メモリと変数宣言、代入、参照あたりについて

変数に代入されている値がどのように保存されているのか、図を作って整理します。

こちらの記事を参考にさせていただいて図を書きました。

[変数] 変数とメモリ|業務ができる中級者になるためのJavaScript入門(文法編)

※この図のような挙動はブラウザなどによって異なるそうです。

図の一番下の abc = def; は、値をコピーして他の記憶領域を確保しているのだと思います。
参照先をコピーするのはオブジェクトの場合だと他で学んだので、参考記事はブラウザなどの環境による違いなのでしょうか。。(私が正しく解釈できていないだけかも)

変数とは別の記憶領域の番地を保管した記憶領域のことで、変数名とは記憶領域に設定されたエイリアス(別名)のようなものです。

変数を使うことで、同じ値でも違った用途がある複数の値を扱うことができるのですね。
変数名と変数に代入した値、それぞれ記憶領域があるというイメージは持っておきたいです。

変数に値を格納することを代入する(assignment) と言い、初めて行う代入は特に初期化(initialization) といいます。

(中略)

変数に何も代入しなくても未定義であることを示す値となるundefinedが自動で格納されます。つまり正確には空の変数というものは存在しません。

ここで let のホイスティングを思い出すと、変数宣言だけされて初期化されない状態は何なのだろうと思いました。

ホイスティングは「コンテキスト内で宣言した変数や関数の定義をコード実行前にメモリに配置すること。」とあるので、変数名だけメモリに配置されているのかと推測しましたが、。。どうなのでしょう、調べきれませんでした。

本当は関数の宣言や関数式について図が作れたら良かったのですが、またの機会にします。

さいごに

Udemyの動画でスコープやコンテキスト、クロージャーという言葉が出てくると苦手意識が出てくるようになったので、早めに克服したいです。他の言語でも使われる言葉(多分)ですし。。

おしまい。

iOS Safari(おそらく14系)のページ表示不具合と対応まとめ

2022年6月にIEのサポートが終了しましたが、Safariで想定外の表示になっていることがあります。

iPhoneSafari(おそらくバージョンは14)で表示が変、というお声を頂いたことをきっかけに、色々調べました。

対応表を書いてくださった記事がありましたので載せさせていただきます。

iOSのバージョンと Safariバージョンの対応表(2022/10/6 現在 - Qiita

※記事タイトルの「系」の使い方。Safariのバージョン14.0〜14.1.2のことをまとめて言いたいのですが、あっているといいな...。

☆以降の「Safari」はiPhoneのSafari14系で起こったことと想定して下さい。

CSSグラデーションで transparent を使うと黒くなる

対応策:transparent の代わりに rgba() を使って色指定まで行う

linear-gradient() 関数を使って背景色にグラデーションをかけることがありますが、「透明」を指定する際に transparent を使うと、Safariでは黒くなってしまいます。

例)赤から透明になるグラデーション

div {
  background: linear-gradient(#e66465, transparent);
}

Safariではグラデーションが上から下にかけて
赤→黒→透明
になります。

Appleデバイスでtransparentのグラデーションが黒くなる現象 | 無題のブログ

W3Cの仕様書だと transparent は rgba(0, 0, 0, 0) のショートハンドと定義さております。

rgba(0, 0, 0, 0) は「透明な黒」です。
ransparentの実態が分かり目からうろこでした。

以下のように書くと、Safariでも黒が入らず単色の透明グラデーションになりました。

div {
  background: llinear-gradient(#e66465, rgba(230, 100, 101, 0));
}

#e66465 という16進数(HEX:Hexadecimal)のカラーコードをRGBに変換して、さらにアルファ(a)を0、すなわち透明にした数値を rgba() 関数に設定します。

上のコードはHEXとRGB両方使われていて気持ち悪いので
llinear-gradient(rgba(230, 100, 101, 1), rgba(230, 100, 101, 0)) がいいと思います。

そこで今度はなぜChromeなどのブラウザでは rgba(0, 0, 0, 0) を使っても黒くならないのか気になりました。

記事には

グラデーションの計算は乗算済みアルファ

と、Chromeなどでのブラウザではアルファは「乗算済み」のため透明になるようです。
が、参考記事の参考記事を見ても「乗算済みアルファ」が分からなかったので、そういうことなのかとぼんやり理解することにします。

なのでChromeなどではアルファ(透明度)の値が0であれば、何色の透明であってもその色が間に表示されることはなく、透明グラデーションが表示されます。

色を変化させつつ、だんだん透明にするグラデーションを作る場合は難しいなと思いました。

Photoshopのグラデーションのように、色と透明度の軸が別々の書き方がもしかしたらあるのかもしれません。

CSS グラデーション ジェネレーターを見た限りではできなさそうでした。

フォトショのようにグラデーションを描画したい時のイメージです。

水色からピンクへ、次第に透明になるグラデーションです(不透明度0%→100%)。
CSSの場合は、水色とピンクの中間にポイントを取って、中間色・中間の透明度を指定すると、それっぽくなりました。
透明になるピンクのポイントは「ピンクの透明」でなくても透明であれば何色でもよい、というのが変な感じです。

transparentは極力使わないほうが無難ですね。

Webpは表示されない

対応策:webpが表示されない場合の代替画像の記述をしておく

Webpは「ウェッピー」と呼びます。
心の中で1回「ウェブピー」と言っています。

Webp非対応は2022年11月現在IEぐらいなので、もっと積極的に使っていきたいです。
(今調べるとSafari on iOSの14以降も対応しているみたいですが、非表示だったのは何だったのだろう...)

もう悩まない。Mac版safariも対応したのでWebP始めました | ホームページ制作 | 茨城県水戸市 | 株式会社グレイズ

保険としてjpeg画像が表示されるようにpictureタグとsourceタグを使って書きます。

<picture>
 <source srcset="img/webp-image.webp" type="image/webp">
 <img src="img/jpeg-image.jpg" alt="イメージ">
</picture>

Photoshopではプラグインを使ってWebpが書き出せるのですね!
早く標準で書き出せるようになってほしいです。

cssプロパティ aspect-ratio が使えない

対応策:従来の方法(padding-topなど)

縦横比を指定できる便利な aspect-ratio ですが、使えるはのSafari15からだそうです。

"aspect-ratio" | Can I use... Support tables for HTML5, CSS3, etc

表示が崩れてしまった方にアップデートしてくださいって言いたい...!

aspect-ratio については以前調べました。こちらです。

***

ここからはiOSであった問題と対策です。

番外:スクロール禁止に使っていた preventDefault() が理想通りに動かず

iOS対策のためにグローバルメニューを開いたときにページスクロールを止めるために入れていました。 しかしメニュー内のスクロールもできなくなってしまうので使うのを止めました。

iOS でページ全体はスクロールを無効にし、個別の要素(textarea など)では有効にする方法

window.addEventListener('touchmove', function(event) {
    event.preventDefault();
});

これだとすべてのスクロールイベントを発生させないように指定してしまいます。

preventDefault() のそもそもを下記で学んでから、上記記事のコードを解読して、組み込んでみたいと思います。

JavaScriptのpreventDefault()って難しくない?preventDefault()を使うための前提知識 - Qiita

番外:アドレスバーなどで height: 100vh だとはみ出てしまう問題

iOSでも100vhをいい具合に調整して画面の高さいっぱいに要素を表示させる

Javascriptで高さを取得して、cssに変数を設定するという技です。

const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);

ウィンドウサイズの高さ1vhあたりのpxを変数vhに格納して、setProperty()
--vh: ${vh}pxcssにセットしています。

100vhというのはiPhone Safariだけではなく色々なブラウザでもはみ出そうなので使っていきたいです!

さいごに

バージョンアップの中でも、大幅に変更のある壁が存在するのだなぁとしみじみ思いました。

バージョン違いで使える・使えないはあるあるなので、バージョンを意識しながら新しい記事を見るようにします!

おしまい。

鍵認証でSSH、SFTP接続をしてみる

これまではサーバーとのファイル転送接続は、パスワード認証のSFTPほぼ一択だったので、念願(?)の鍵を使った接続方法を試してみました。

ファイル転送プロトコルについては、こちらの記事で調べました。

鍵認証でのSSH接続の手順

昨年契約して以来、全く使っていないエックスサーバーがあるので、そちらで試してみることにしました。
(その時の模様をまとめた記事です)

エックスサーバーにSSHの接続方法が載っているので下記を見ながら進めていきます。

SSH設定 | レンタルサーバーならエックスサーバー

【初心者向け】エックスサーバーでSSH接続する手順と設定まとめ | ゆるグラミング!!

1. エックスサーバーでSSHを有効化する

サーバーパネルにログインします。
下記のように「状態:ON」となっていればOKです。

2. サーバーに公開鍵の登録を行う

いよいよ登場しました公開鍵!

お客様にて生成の公開鍵を登録していただく方法と、サーバー上で公開鍵認証用鍵ペアを生成し秘密鍵をダウンロードしていただく方法があります

何らかの方法で生成した公開鍵をすでに持っていれば前者(登録)なのだと思います。

自分のPCで鍵ペアを作る→登録、とする事もできるようですが、なにせ初めてなのでエックスサーバーで公開鍵認証用鍵ペアを生成します!

後述する参考サイトに書かれていた

公開鍵と秘密鍵の仕組みを一言で言うと錠前(公開鍵)と鍵(秘密鍵)のようなものです。

サーバーに錠前(公開鍵)を設置することで、さらにセキュリティが強化されて第3者が盗聴や不正侵入するリスクを減らすことができます。

そして、鍵(秘密鍵)は自分のパソコンだけに持っておくことで、サーバーに設置した錠前(公開鍵)を開けてサーバーへ接続できるのは自分だけとなるわけです。

ここでは鍵・錠前(南京錠)の関係をイメージしてみます。

この「パスフレーズ」というのは?

パスフレーズSSH接続の時に使用するパスワードです。 接続時に入力する必要があるので、忘れないよう注意してください。

パスワード認証でつかうものとは別のパスワードですね。大事なものでした。

またこの画面には下記のように注釈がありました。

本機能のご利用により、サーバー上の公開鍵が更新されます。 本機能を利用して鍵ペアを作成すると、秘密鍵のダウンロードが始まるとともに、サーバー上の公開鍵が更新されます。

「生成する」ボタンを押すと秘密鍵がダウンロードされました。

秘密鍵.key が拡張子のファイル。Skypeのアイコンで、押したらSkypeが起動してしまったので慌てて閉じました。

ファイルの情報を確認すると「種類:Keynote書類」とありました。

PC(Mac)に入っているソフトが反応しただけなのかなと思います(調べてもよく分からず)。

秘密鍵を任意の場所に保存すればいいのかと思いきや、

鍵がダウンロードされたら、自分のPCの「.sshディレクトリに移動させましょう!

置く場所が決まっているようです。
ひとまず置いてみます。

3. SSHで繋ぐ

コマンドラインsshコマンドを使ってサーバーに繋いでみます。

$ ssh -i サーバーID.key サーバーID@サーバーID.xsrv.jp -p 10022

サーバーIDはエックスサーバーの「サービス一覧 > Xserver レンタルサーバー」から確認できました。

.sshディレクトリでコマンドを打つと、

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for 'キー名.key' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "キー名.key": bad permissions
サーバーID@サーバーID.xsrv.jp: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

アットマークがものすごく怖い!

どうやら秘密鍵ファイルの権限が緩くて警告を受けているようです。パーミッション644から所有者のみ読み書きができる600へ変更します。

chmod コマンドを使います。(おそらくchange modeの略)

chmod 600 キー名.key 

パーミッションの変更をして再度 ssh コマンドで接続すると、例のパスフレーズを聞かれました。

すると [サーバーID@sv*** ~]$ と表示されていました。サーバーに接続できました♪

これでパスワード認証ではない、鍵認証でのSSH接続ができました!

ペアになっている、サーバーに置く公開鍵とローカルPCに置く秘密鍵
割とすんなりとできた印象です。

鍵認証でのSFTP接続手順

こちらを参考にしました。

FileZillaでエックスサーバーにSFTP接続で安全にファイル転送する方法

「鍵認証でのSSH設定」は、SFTP接続でも必要なことなので、サーバーのコンパネでSSH設定をONにして、公開鍵・秘密鍵を作成します。

私はFileZillaを使っています。設定をしていきます。

FileZillaの設定

FileZilla秘密鍵の登録をします。
「鍵認証でのSSH接続の手順」で鍵ファイルを置いた.sshディレクトリは隠しフォルダのせいか、FileZillaからでは秘密鍵ファイル選択の際に見つかりません。
一時的に移しても、.sshに戻したらパスが変わって繋がらなくなってしまいます。

致し方なく保存場所を変えてやってみたところ秘密鍵を登録できました。

秘密鍵のファイル形式がFileZillaには合わないようで、変換されました。

「[編集]→[設定]→[SFTP]」から秘密鍵を登録する方法と、接続先の設定画面で秘密鍵を登録する方法と2通りあるようです。
前者の方がFileZilla全体の登録されている鍵が把握できていいのかなと思いますが、どのサーバーとの鍵なのは記載がありませんでした。

ちなみに、sshで繋いだときもポート番号は10022
セキュリティ対策のため、エックスサーバーがsshのポート番号をデフォルトの22から変更しているようです。

接続時にはパスフレーズも聞かれます。

このような画面も出ます。

「常にこのホストを信用し〜」にチェックを入れてOK。無事繋がりました!

さいごに

プロトコルを調べた際に大混乱したので、実際には「習うより慣れろ」かなぁといいう印象です。

秘密鍵は大事にして、鍵認証の接続に慣れて、何かでつまづいたときに復習したいと思います。

おしまい。

ファイル転送プロトコル関連(FTP、SFTP、SSL/TLS、SSHなど)

なんとなくな知識で普段サーバーと繋いでいるので、プロトコルについて調べました。

ファイル転送プロトコル - FTP、FTPS、SFTP、SCP

こちらをはじめに見ました。

ファイル転送プロトコルについて(FTP、FTPS、SFTP、SCP)

ファイル転送プロトコルは主に以下の4つがあります。

  • FTP:「File Transfer Protocol」
    暗号化されていない
  • FTPS:「File Transfer Protocol over SSL/TLS
    FTPSSL/TLSで暗号化されている
  • SFTP:「SSH File Transfer Protocol」
    SSHで暗号化されている
  • SCP:「Secure Copy Protocol」
    SSHで暗号化されていてSFTPよりも通信速度が早い

FTPについて

ファイル転送プロトコルといえば、のFTPについて調べました。

FTPとは? ファイル転送の仕組みとデメリット、FTPSとSFTPの違いについて | 物理サーバ愛が止まらないホスティング事業者のブログ | ベアメタルブログ

TCP/IP - FTPとは

コネクション

コネクションとは、サーバーとクライアントの間で確立される通信経路のことを指します。FTPの通信においては、「制御用コネクション」(コントロールコネクション)と、「データ転送用コネクション」(データコネクション)の2つのコネクションを確立する必要があります。

表にするとこうなります。

コネクション FTPサーバのポート番号 特徴
コントロールコネクション 21 ファイルの転送方法などの命令と応答など
データコネクション アクティブモード:20、パッシブモード:サーバーが指定したポート 転送されるデータの送受信

VSCodeFTPを使う際、ポート番号を20にしたら繋がりませんでした。
コントロールコネクションの21にしたら繋がりました。

これは通信で最初に使われるコントロールコネクションのポート番号を指定するものと思われます。

ちなみに後述するFTPSもこの2つのコネクションを確立して通信します。

一方でSFTPは1つのコネクションを使い、またFTPとは根本的に通信方式が異なるそうです。
「SFTP」という名前なのに誤解されそうな名前...。

アクティブモード・パッシブモード

さらにアクティブモード・パッシブモードについて調べました。
モードの違いについては、データコネクションFTPクライアントFTPサーバ、どちらから接続要求を行うかによって決まります。

モード 接続要求の開始 特徴
アクティブモード サーバー ファイアーウォールの設定などでうまく接続できない場合がある
パッシブモード クライアント データコネクションのサーバーのポート番号がぎりぎりで決まる

それぞれのモードの通信の流れは、参考記事の図を見ると頭に入ります。

なお、データ転送モード(ASCIIモード・バイナリモード)については割愛します!

SCPについて

一番言葉で聞き馴染みのないSCPについて調べました。

「SFTP?FTPS?なにが違う?」 https://academy.gmocloud.com/keywords/20170308/4155

SFTPとの違いはファイルの転送が再開できないことや、SFTPより通信が高速であると言われています。また、フォルダの転送はできませんが、フォルダをzipやtar形式で圧縮して転送することは可能です。

SCPもSFTPもSSHを使っているプロトコル

SCPは、ファイル管理や削除もできないそうです。

一度に大きな容量をアップロード・ダウンロードする際などに、通信が切れてファイルの転送が途中で止まってしまうことがあります。
SFTPの場合は、通信再開時に転送できなかった分を続きから転送することができます。
SCPの「ファイルの転送が再開できない」ということは、途中まで転送できたのに、通信再開時に転送できた分を含めてまたイチから送ることになります。
そしてまた通信が切れてしまったら...と思うと永遠に全てを転送することはできないように思えてしまいます。

一長一短ですね。

以前 scp コマンドの代わりに rsync コマンドを教えてもらった事がありました。

scp の代わりとしての rsync コマンドとそのオプション – ラボラジアン

【rsync】差分同期の仕組みとオプション, ログの見方(フォーマット) | SEの道標

rsync(アールシンク)「remote synchronization」だそうです。

rsyncSSHを使うこともできるそうですが、SCP同様、コピー、同期の意味合いが強い印象です。

rsyncプロトコルなのかどうかも、rsyncを使った繋ぎ方によって変わるようです。
後述するSSHのように、時にアプリケーションだったりします。

プロトコルと言われているものもいろいろあるのですね。

転送はするけれども保存はしないプロトコル - HTTP、HTTPS

ダウンロードで,FTPとHTTPの違いは何? | 日経クロステック(xTECH)

HTTPはファイル転送は行っているものの、目的が「転送」ではなく「配布」であるとありました。

  • HTTP:「HyperText Transfer Protocol」
    ウェブサーバーとブラウザで通信する
  • HTTPS:「HyperText Transfer Protocol over SSL/TLS
    SSL/TLSで暗号化されているところをHTTPで通信する

HTTPは,Webページのコンテンツをサーバーから読み出すのに使われる。Webブラウザは,WebサーバーにあるHTMLファイルや画像ファイルなどをダウンロードし,パソコンのハードディスクやメモリーに一時的にためたものを表示している。

ソフトウエアなどをHTTPでダウンロードする場合で違うのは,Webブラウザで表示するか表示せずに保存するかというユーザーからの見え方だけ。しくみはまったく同じだ。

ウェブページの画像を保存する場合は、表示のために「一時的にためたもの」を保存用としてさらに端末の別の場所に保存している、ということなのかなと思いました。

ブラウザに表示なしで、ボタンを押してすぐにPDFをダウンロードしたりする場合も、例えば

<a href="./file/sample.pdf" download="sample.pdf">ダウンロード</a>

このようなテキストリンクがあったとしたら、リンクのパスは相対パスでも https:// から始まるので、HTTP(S)を使っているのでしょう。

HTTP、HTTPSはファイル転送プロトコルではあるけれども、目的が他のプロトコルと異なるため、少し立ち位置が違うみたいです。

暗号化に関わるプロトコル - SSL/TLSSSH

次によく混乱するSSL/TLSSSHについて調べました。

どちらも暗号化した通信をするプロトコルですが、ざっくりした2つの違いは

  • SSL/TLS: 不特定多数のサイトへアクセスする
  • SSH:特定のサーバーへアクセスする、遠隔操作する

です。

5分で読める!SSHとSSLとは?それぞれの違いを分かりやすく解説 - Pimlusメディア

言い換えると、ご自身のコンピュータから不特定多数のサーバー、すなわちサイトへアクセスする場合に利用されるのがSSLであり、一方、ご自身のコンピュータから特定のサーバーへアクセスし、遠隔操作する際に利用されるのがSSHです。

SSL/TLS

SSL(Secure Sockets Layer)とTLS(Transport Layer Security)をまとめて表記した形が「SSL/TLS」です。
2022年現在多く使われているのはTLSの方のようです(SSLは安全ではないそうです)。

SSL/TLSはそれぞれ同じ枠組みの中にあり、SSLがバージョンアップを重ねてTLSに至ったものの、SSLが一般化したことから現在ではSSL/TLSと表記されることが多くなっています。

とのことなので、断りがない場合は以降SSLTLSを同じと捉えて「SSL」と書きます。

暗号化通信であるSSLには、「公開鍵暗号」や「共通鍵暗号」が使われています。
ハイブリッド暗号方式

SSLの流れとしては、
電子署名秘密鍵・公開鍵、CSRなどを使って、サーバー側から「認証局お墨付き、ドメインを保証するサーバー証明書」を送ってもらい、 最初は端末のルート証明書と検証して通信先のドメインを確認します。

そこからやっとSSLの暗号化通信がスタートします。

HTTPSの場合は、SSLで暗号化された通信路をHTTPでやり取りするイメージです。

その後は、共通鍵を使って通信するデータを暗号化して送信、受け取り側は共通鍵を使って復号してやり取りします。

SSLって何?意味や仕組みをわかりやすく解説! | さくらのSSL

ここで少し気になったルート証明書について調べてみました。

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

↑すごく分かりやすくて感動しました。

サーバー証明書/中間CA証明書/ルート証明書の違いとは? | さくらのSSL

さくらサーバーのコラムには

ルート証明書は端末やWebブラウザなどにあらかじめ組み込まれており、Windows Update等で更新されます。

とあります。

詳しくはまたの機会に調べます。

FTPSについて

FTPSもSSLが使われていますが、ファイル転送で「不特定多数への」というのはよろしくない気がします。

FTPSの接続方法について調べたところ、FTPを使うのでサーバー情報やユーザー名・パスワードが必要で、不特定多数ではない事がわかりました。
そりゃそうだ。
この言葉に囚われすぎていたようです。

FTPSの接続方式にはexplicit(明示的な)方式implicit(暗黙的な)方式があり、
「implicit」は

httpsで利用されているTLSに近い。

とあります。

【vsftpd】FTPをSSL/TLSで暗号化する設定 - Qiita

FTPSではFTP同様2つのコネクション ー ポートを使う点などから、セキュリティ面ではSFTPの方が優れていると言われます。

SSH

SSH(Secure Shell)です。

暗号によって保護された通信を通じて遠隔操作を行う1アプリケーション、またはプロトコルを指します。

SSHプロトコルの1つですが、ファイル転送プロトコルではありません。

コマンドラインssh コマンドでサーバーにアクセスすると、たしかに遠隔操作という感じがしました。

gitでpullやpushをする時にSSHを使う方法もありますが、その場合もファイル「転送」が目的ではないようです。

HTTPSと同じようなものかもしれませんが(機能はあるけどその仲間には入らない、みたいな)...すごく気になりますがここでは割愛します。

SSHでサーバーと繋ぐ方法は「パスワード認証」と「公開鍵認証」があります。

現在は公開鍵認証が推奨されています。
しかし下記の記事を見ると、一概に「パスワード認証は危ない」「公開鍵認証がいい」とは言えないようです。

SSHの公開鍵認証における良くある誤解の話 - Qiita

SSHの通信の流れについても、こちらの記事の図を見て覚えた方が良さそうです。

FTPではつながるのにSFTPではつながらない原因

ファイル転送のFTP設定の際、プロトコルを「SFTP」に切り替えるとつながらない、ということがありました。

ずっと原因が分からず、そういうものなのだろう、と思っていました。

いろいろ見てみると、サーバー会社、契約プランによって可能な接続方法は異なるということが分かりました。

本当に“そういうもの”でした...。

エックスサーバーではSFTPのパスワード認証ではつながらない(2022年6月現在)のでFTPでつないでいました。

今思うととても危険なことをしていました!

そこで鍵認証でSSH、SFTP接続を試してみます!

☆続きはこちらの記事に書きました。

暗号化についてもう少し理解を試みる

できればもう少し調べて暗号化について知っておきたいものです。

暗号化のタイミング・対象

一口に「暗号化」と言っても何を暗号化するのか、という対象があります。
ぱっと調べて出てきたのは以下の2つです。

通信の暗号化:SSL

「HTTPの教科書」という本には暗号化について以下のように書かれています。

1つは通信を暗号化する方法です。

SSL等によって安全な通信路を確立してから、その通信路を使ってHTTPの通信を行います。

コンテンツの暗号化:盗聴を守ることはできても、改ざんやなりすましの危険性がある

もう1つは、通信しているコンテンツの内容自体を暗号化してしまうことです。

コンテンツの暗号化を有効にするためには、クライアントとサーバーがコンテンツの暗号化や復号の仕組みを持っていることが前提となりますので、ユーザーが普段使うブラウザとWebサーバーという図式では利用することは難しいでしょう。

SSL通信のところで「データの暗号化・復号」が出てきましたが、もしかしてSSLもコンテンツの暗号化をやっているということなのでしょうか??

え、HTTPSの転送なのにファイルも暗号化するんですか???

ファイル(データ)を暗号化しないで通信するHTTPSの方法もあるのですね。
転送」という言葉が引っかかります。

暗号化の方法

2つの暗号方式が出てくるのでまとめました。

共通鍵暗号方式

またの名を秘密鍵暗号方式。 通信し合う者同士で同じ鍵を使い、そのため第三者に鍵が渡ると危険なので、「共通」「秘密」という名前がついている。

公開鍵暗号方式

通信相手に渡す公開鍵と、自分だけが持ち、人に知られてはいけない秘密鍵を使った暗号方式。
公開鍵と秘密鍵はペアになっている。

RSA(Rivest Shamir Adleman)」も「公開鍵暗号方式」で使われる暗号化の1つだそうです。

どちらにも「秘密」が出てきますね。軽く混乱します。

公開鍵暗号に関して誤解がいろいろとあるようです。

公開鍵暗号に関するQ&A - Qiita

【分かった部分】

公開鍵暗号の一分類である「電子署名

電子署名」は、現実の「印鑑」での喩えに非常にマッチしている。
→ 「はんこ」が秘密鍵、「印影のサンプル」が公開鍵、また「印影のサンプル」の載った「印鑑登録証明」が公開鍵証明書に相当

イメージです。

印影のサンプルというところが一筋縄ではいかないですね。
分かっていないかも。

さいごに

今回はどんどん沼にハマってしまいました。

いろいろな記事を読んでいくと、正しいと思っていたことがひっくり返されたり、何度もスタート地点に立ち返ったり。
一体何を学んだのか。

書く側としても、記事のほとんどが正しくないことを書いているかもしれないという恐ろしさを感じました。

難しいです><

お気をつけください。

おしまい。

Hugoのコードブロックのシンタックスハイライトをカスタマイズする

Hugoのカスタマイズをしています。テーマは「hugo-clarity」です。

記事の本文はマークダウンで書きますが、コードを書く部分をいじりたくなりました。

直したいところ

優先順で挙げると

  1. 文字の色を変えたい
  2. 言語表記の色を変えたい
  3. 行数表示をデフォルトにしたい
  4. 折りたたみの表示を変えたい
  5. ツールチップを日本語にしたい
  6. 「...」に「もっと見る」を入れる

です。

好きな配色の文字だと気分も上がります♪

2.と5. は何とかなるのではないかと楽観的に考えています。

用語のおさらい

そもそもの用語を調べました。

コードブロック(Code block)とは

引用元はこちらです。

コードブロックとはプログラミング言語におけるコードのまとまりのことを指します。

記事で見かけるコードのかたまりを指す言葉かと思いきや、広い意味で使われているのですね。

シンタックスハイライト(Syntax highlight)とは

引用元はこちらです。

シンタックスハイライトとは、テキストエディタなどの文字表示に関する機能の一つで、あらかじめ指定された文中の特定の記号やキーワードなどを他とは異なる色で表示すること。

VS Codeでもシンタックスハイライトを設定したことがあったので、認識はあっていたようです。

以前Hugoでもシンタックスハイライトを設定できるライブラリを教えていただいたことがあるのですが、覚えていない...。

文字の色を変える

まずは一番変えたい文字の色表示(シンタックスハイライト)について方法を調べました。

シンタックスハイライトのライブラリ(シンタックスハイライター)

やはりライブラリなるものがありました。

  • Rouge:Qiitaはこれ
  • highlight.js:対応している言語がたくさん
  • Prism:Rubyでできている
  • syntaxhighlighter:白とグレーのしましまの行
  • code-prettify:Google
  • Pygments:Pythonでできている

もっとありました。

赤い画面のhighlight.js!見たことがありました。思い出しました。

ただ、現在のHugoはChromaというライブラリが使われているそうです。

Pygmentsを参考に、Goでできています。

上記の変えたいことを実現できるか分かりませんが、ここは標準で付いているChromaを使おうと思います。

Chromaを使う

以下記事を元に設定していきます。

hugo 単体でシンタックスハイライト適用 - チラシのすきま

If you run with markup.highlight.noClasses=false in your site config, you need a style sheet.

ドキュメントにもこのように書かれていたので、カスタマイズにはcssファイルが必要なようです。

スタイルはこちらから選び、monokaiにしました。

デフォルトはテーマでオリジナルのシンタックスハイライトが設定されているようです。すでに_syntax.sassファイルがあり、「monokai」とは違う配色でした。

コマンドラインに以下のように打ちました。
なお、hugo server でサーバー起動中ではできませんでした。1度停止させます。

hugo gen chromastyles --style=monokai > syntax.css

すると「monokai」のスタイルが入ったsyntax.cssファイルがルートディレクトリにできるので、然るべき場所に移して、読み込むようにします。

テーマ「hugo-clarity」では、/static/css ディレクトリを作り、中にsyntax.cssファイルを配置しました。
ファイルの読み込みはstaticディレクトリ以下が読み込めるように設定されているようです。

README.md には

However, sometimes you may need to load additional style or script files. In such cases, you can add custom .css and .js files by listing them in the config.toml file (see the snippet below). Similar to images, these paths should be relative to the static directory.

とありました。
翻訳は、

しかし、時には追加のスタイルファイルやスクリプトファイルをロードする必要があるかもしれません。そのような場合には、カスタムファイルである .css.jsconfig.toml ファイルに記述して追加します (以下のスニペットを参照してください)。 画像と同様に、これらのパスは static ディレクトリからの相対パスである必要があります。

でした。

スニペットというのは、その下に書かれていた

[params]
...
customCSS = ["css/custom.css"] # Include custom CSS files
customJS = ["js/custom.js"] # Include custom JS files
...

のことのようです。

はじめconfig.tomlにカスタムcssの設定を書かずに
/layouts/partials/head.html ファイルに読み込みについて記述してみたところ、うまくいきました。

<link rel="stylesheet" type="text/css" href="/css/syntax.css">

このあとにconfig.tomlで設定できることを知りました。

どちらでも同じ結果になりますが、config.tomlに書く方がhead.htmlファイルを触らずに済むのでいいなと思いました。

ちなみに複数ファイルを読ませたい場合は

customCSS = ["css/custom1.css", "css/custom2.css"] 

と書くとできました。

Chromaではうまく色分けしてくれない

これで晴れて「monokai」のsyntax.css ファイルを読めるようになったのですが、肝心のシンタクスハイライトに難点が。

試しにphpのコードを入れてみると、<?php ?> の部分がエラー扱いされ、エラーの配色で表示されてしまいました。

これは...嫌です。

言語によって綺麗に色分けされるものと、ところどころうまく色分けされないものとあるのかもしれません。

私の書き方に問題があるのでしょうか(それもありそう)。

ライブラリを変えて解決できるのであればそうしたいです。

ということで、次は highlight.js を試してみます。

highlight.js を使う

以下記事を参考にさせていただきました。

シンタックス・ハイライト・ライブラリ「highlight.js」の使い方 – ラボラジアン

ファイルをダウンロードしてサーバに上げて読み込む方法で行います。

記事にある highlight.pack.js はダウンロードファイルにはなかったので、highlight.min.js を使ってみます(おそらくバージョン違いによるものかと思います)。

highlight.js のテーマにも「monokai」はありましたが、テーマの数が豊富なのであえて別の「Base16/Onedark」にしてみます。

scriptタグの書き方

2つのファイル

  • onedark.min.css
  • highlight.min.js

の読み込みに加えて以下のコードを書く必要があります。

<script>hljs.highlightAll();</script>

highlight.min.js ファイルを読み込んだ後に書きたいのですが、/layouts/partials/head.html ファイルの </footer> の下に書くのでは highlight.min.js のになってしまいます。

これは1行だけどもファイルとして読み込ませるしかなさそうです。

customJS = ["js/highlight.min.js", "js/highlight_script.js"]

として、highlight_script.js ファイルに hljs.highlightAll(); を入れました。

これでうまくhighlight.jsが適用されました!

この時点での表示はこちらです。

なぜか行数が出るようになりました。そして非表示にできなくなりました。

どうやら

  • 指定した言語をサポートしている → 行数が出る(非表示にできない)、
    さらに言語によって色が違ったり、行数を囲むタグも異なる(行数がドラッグ対象になったりならなかったり)
  • 言語未指定 → 行数の表示・非表示切り替えができる(デフォルトは非表示)

しかも、コードブロックの書き方によっても挙動が異なるようです。
☆これらは ''' で囲って書く方法で調べています。

highlight.jsの導入方法に問題がありそうです。

今度はこちらを参考に進めてみます。

hugoでシンタックスハイライトに対応する

記事に忠実に今回はCDNを使います。

行数表示をする

highlight.jsから行番号を表示させるライブラリも見つけたのでこちらも入れてみました。

highlight.jsに行番号を追加する方法 - kotonoha

すると...

言語間で表示が安定しない

スクショを撮るのを忘れてしまいましたが、行番号が表示されたりされなかったり、二重で表示されたり、
コードブロックの機能(コピーなど)ボタンも出てこなくなったり、
さらにはダークモードからライトモードへの切り替えもできなくなったり...うまくいきませんでした。

おそらくconfig.tomlファイルの [markup.highlight] の設定がよろしくないのかなと思います。

Configure Markup | Hugo

こちらを見ながら設定項目を翻訳してtrueにしたりfalseにしたり...いろいろいじってみましたが、Hugoに翻弄されている感がありました。

ここまでの挙動を見ていると、
Chromaとhighlight.jsが両方動いてしまっているために、表示がおかしなことになっているのではないかと思いました。

Chromaをオフにする方法はあるのでしょうか。

...これ以上は技術的に突き詰められなさそうなので、Chromaに戻して調整していきます!

☆Chromaでのphpのエラー表示の問題は、WordPressテンプレートみたいなhtmlとphpが混在しているコードではだめ、ということが判りました。

Chromaで再度調整する

ここまで悪戦苦闘して、いろいろと諦めることにしました。

結局スタイルは、ページの色味に合わせてsolarized-dark256にしました。よいです。

デフォルトで行数表示させたい

themesにあるcode.jsをコピペして、編集したcode.jsを読ませるようにしました。 とある関数の判定で false だったところを true にしました。

問題なく動いています。が、ベストな書き方かどうかは分かりません。

折りたたみの表示を変えたい

こちらもcode.jsを書き換えました。
行数表示のように簡単にはいかず、試行錯誤しました。
(コメントもたくさん書き込んで...このままコミットします)

関数名をわかりやすくするのは大事だなと改めて思いました。

その他の修正

このように変えました。

言語表記の色を変えたい(その他コードブロックの表示調整) → _custom.sass
ツールチップを日本語にしたい → code.js
「...」に「もっと見る」を入れる → 変更するのをやめました

結果、満足のいくコードブロックになりました!

長かった...

Hugoで使われているパーサー

せっかくなのでGoldmarkとBlackfridayを調べました。

Goldmark・・・markdown(マークダウン)をHTMLに変換するパーサー
Blackfriday・・・HugoでGoldmarkの前に使われていたパーサー

だそうです。

以前もマークダウンについて調べていたことを思い出して記事を確認したところ、
Blackfridayについて調べていました。すっかり忘れています。

パーサー」について調べたことも以前あったような気がします。
なんとなくの理解でここは良しとします。

さいごに

今回はてなブログでやっとコードブロックを書くときに言語指定をしました。

色が付くと見栄えがいいですね。

また、最近ではHugoで調べ物をすると、記事がたくさん出てくるようになりました。

2年前はもっと少ない印象でした。

とてもありがたいです。

私はいつになったらブログを移行できるのでしょうか。

おしまい。