Multi Vitamin & Mineral

Multi Vitamin & Mineral です。プログラムに関することを書いております。

【TIPS】CSS のみで開閉するメニューを作る

f:id:hiranoon:20210409161544p:plain

モバイル用ページのヘッダーによくあるハンバーガーメニュー。 です。クリックするとメニューが表示するヤツです。この開閉を JavaScript を使わずに、CSS だけで実現したいと思いまして、その TIPS の紹介です。

JavaScript を使った方が簡単かも知れませんが、使いたくない場面もあろうかと。

結果

パターン1

「Result」の下にある (ハンバーガーメニュー)ボタンをクリックすると、メニューボタン下にぶら下がるようにメニューが開きます。メニューボタンは で切り替わるようにしています。

See the Pen mobile-sidemenu-button-03-design by hiranoon (@hiranoon) on CodePen.

パターン2

ほぼ同じではありますが、 ボタンをクリックすると、画面左半分にメニューが開くパターンも用意しました。メニュー以外の領域(右半分の半透明のグレーの部分)をクリックすると閉じます。

See the Pen mobile-sidemenu-area-03-design by hiranoon (@hiranoon) on CodePen.

仕組みと実装方法(パターン1)

まずはパターン1の実装方法を紹介し、その中で仕組を説明していきます。

見えないチェックボックスの利用

JavaScript を使わない仕組みのミソは、見えないチェックボックスです。チェックの ON/OFF を 開/閉 に利用します。

ベースの HTML・CSS

ベースとなる HTML・CSS は以下のようになります。

See the Pen mobile-sidemenu-button-01-base by hiranoon (@hiranoon) on CodePen.

チェックボックスは後ほど CSS で非表示にします。

openclose というラベルがあります。これはチェックボックスと連動します。(仮でこのような文言にしていますが、後ほど  に置き換えます。)

背景オレンジ色の部分がメニューです。ここはチェック ON で表示、チェック OFF で非表示になるようにします。

ベースの HTML・CSS
ベースの HTML・CSS

チェックボックスとボタンの連動

先程の HTML の時点で実現できていますが、チェックボックスとラベル(openclose)の連動は HTML のみで行います。

<input type="checkbox" id="check" />
<label for="check" id="open">open</label>
<label for="check" id="close">close</label>

チェックボックスの id (ここでは check とした)を、ラベルの for に書けば連動します。

チェック状態による表示・非表示

この状態の HTML に CSS のみで表示・非表示機能を組み込んだのが以下になります。

See the Pen mobile-sidemenu-button-02-action by hiranoon (@hiranoon) on CodePen.

一般兄弟結合子

CSS のセレクタをつなげるモノに「一般兄弟結合子」というものがあります。 ~ を使います。

例。

#target ~ p {
  color: red;
}

こう書いたら、

  • #target :
    • id="target" より
  • ~ :
    • 後ろにあり、
    • 同じ親要素を持つ、
  • p:
    • <p> タグは、
  • color: red; :
    • 赤文字になる。

という意味になります。

以下のような HTML があれば、 <div id="target">起点</div> より後ろが赤文字です。また、同じ親要素を持つ必要があります。前にあったり別階層は対象になりません。(ちなみに、兄弟(= 同一階層)の子は対象になります。)

<!-- ◯は赤文字。✕は赤文字じゃない。 -->
<div>
  <p>✕(前なので)</p>
  <div id="target">起点</div>
  <p>◯(兄弟は対象である)</p>
  <div>
    <p>◯(兄弟の子も対象になる)</p>
  </div>
</div>
<p>✕(別階層なので)</p>

後ろは対象で前は非対象なので、兄貴はダメで、弟はOKって考えればよいです。 CSS 的には「一般兄弟」は弟のことを指します。兄弟と言いつつ兄は要らんかったんです。

MDN のリンクも貼っておきます。

developer.mozilla.org

チェック状態と関連付ける

わかりやすいよう、一旦メニュー部分(リンク1、リンク2、リンク3の部分)を抜き出します。 CSS は以下のようになっています。

#menu {
  display: none; /* チェックがない場合は非表示 */
}
#check:checked ~ #menu {
  display: block; /* チェックがある場合は表示 */
}

HTML は以下。

  <input type="checkbox" id="check" />
  <!-- 中略 -->
  <ul id="menu">
    <li>リンク1</li>
    <li>リンク2</li>
    <li>リンク3</li>
  </ul>

CSS の2つ目に書いた #check:checked ~ #menu に注目してください。

  • #check:checked (前半部分)は、
    • チェックが付いている状態<input type="checkbox" id="check" /> を指します。(#check<input type="checkbox" id="check" /> で、 :checked はチェックが付いている状態を指す疑似セレクタ。)
  • #check:checked ~ #menu は、
    • チェックが付いた <input type="checkbox" id="check" />後ろにある <ul id="menu">...</ul> を指します。

つまり、「チェックが付いた」を実現するために …:checked を使い、「チェックが付いた AND その後ろ」を実現するために …:checked ~ … を使ったということです。これでチェック状態と後ろの要素が関連付きました

チェック状態と関連付けたい HTML 要素は <label ...>open</label><label ...>close</label><ul id="menu">...</ul> の3つあります。

いずれの HTML 要素もチェックボックスの後ろ置けば :checked ~ ... で指定できますので、「一般兄弟結合子」の ~ は都合が良かったのです。

チェック状態と関連付け
チェック状態と関連付け

2つのボタンと1つのメニューのそれぞれに対し、チェックがない場合とチェックがある場合の表示・非表示の設定を CSS で定義すれば OK です。

最後に、チェックボックス自身は #check { display: none; } で常に非表示にしておきます。

デザインの調整

あとは CSS をいじってデザインを調整するとよいかと思います。デザインは本題ではないですが、まあ、こんな感じになりますよという例として簡単に調整したものが以下です。(最初に貼り付けたものと同じです。)

See the Pen mobile-sidemenu-button-03-design by hiranoon (@hiranoon) on CodePen.

開閉のボタンの は CSS で書きました。 FontAwesome のようなウェブフォントを使ってもいいと思います。

実装方法(パターン2)

次に、画面左半分にメニューが開くパターンです。ちょっと CSS の書き方が違うだけで仕組みは同じです。

ベースの HTML・CSS

パターン2は以下に示すベースの CSS の書き方が肝です。それ以外はパターン1との違いはありません。

ということで、ベースは以下です。

See the Pen mobile-sidemenu-area-01-base by hiranoon (@hiranoon) on CodePen.

閉じるボタンの <label ... id="close"></label> と、メニューの <ul id="menu">...</ul> は、どちらも画面全体に表示させています。

#close {
  /* 画面左上に配置 */
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  /* 画面全体 */
  width: 100%;
  height: 100%;
  /* menu より下 */
  z-index: 99;
  /* ...中略... */
}
#menu {
  /* 画面左上に配置 */
  position: fixed;
  top: 0;
  left: 0;
  /* 画面左4割分 */
  width: 40%;
  height: 100%;
  /* close より上 */
  z-index: 100;
  /* ...中略... */
}
  • position: fixed; top: 0; left: 0; で HTMLタグを書いている位置は無視して画面の左上を基点とします。
  • widthheight をパーセント定義することで、画面全体に描画しています。
  • z-index を定義して、閉じるボタン( label )を下に、メニュー( ul )を上にします。(数字の 99 と 100 は適当です。1 と 2 でも OK です。)
  • 閉じるボタン( label )はインライン要素なので幅と高さを持ちません。 display: block; で無理やりブロック要素( divul などと同じ)扱いにし、幅と高さを持たせます。

パターン2の肝はこの閉じるボタンとメニューの配置です。それ以外はパターン1と同じです。

チェックボックスとボタンの連動

動きを付けたものが以下になります。

See the Pen mobile-sidemenu-area-02-action by hiranoon (@hiranoon) on CodePen.

これはパターン1と同じことを行っているだけですので説明は割愛します。

デザインの調整

パターン1と同様、ちょっとそれっぽくしました。デザインは本題ではありませんので参考までということで。

See the Pen mobile-sidemenu-area-03-design by hiranoon (@hiranoon) on CodePen.

あとがき

まあ正直、 JavaScript を使った実装の方が素直だとは思います。チェックボックスを連動させるため、 HTML 要素を兄弟となるようムリクリ配置した感もなきにしもあらず、ですし。

当ブログのモバイル用にメニューを追加したのですが、その際に JavaScript を追加したくなかったんですね。なので、この TIPS を利用しました。

ブログやネットショップのような既存のシステムに、あとから自作パーツを追加するとか、そのシステムが JavaScript の追加ができないとか、そういう場面で使えるでしょうか。

ああ、そうそう、 openclose って2つのボタンを用意しなくても OK です。説明の都合上2つにしましたが、ボタンは1つで中身を切り替えるような CSS にしても問題なし。です。

「カスタムデータ属性(data-*)」と「script タグへの JSON 埋め込み」のススメ【JavaScript で扱うデータを HTML に埋める方法2選】

f:id:hiranoon:20210305000546p:plain

ブラウザに表示されているデータは HTML 上にあり、 JavaScript から取得できるます。これはいいんです。が、ブラウザに表示されないが、JavaScript で扱いたいデータがあった場合は HTML のどこに書くのがよいのでしょう? というのが今回のテーマです。

方法はいくつかあるのですが、私が良さそうと思った2つの方法を書いています。

結論

前者は有名だと思っていますが、後者はあまり見かけない話かなと思います。前者を知っている人は後者だけでも見てってもらえればと思います。

今回取り上げる問題

問題が分かりにくいかと思うので、具体例をあげます。分かる人はどうぞ飛ばして(→次へ)。

表示データ=取得したいデータ(問題のないパターン)

まずは特に問題のない通常のパターンから。

以下のような HTML があるとします。

<div>しょうゆ:税抜<span id="price">200</span></div>

JavaScript で税込価格を計算します。

const price = document.getElementById('price').textContent; // "200" を取得
console.log('税込' + Math.round(price * 1.1) + '円'); // "price * 1.1" で税込価格
// => "税込220円"

document.getElementById('price').textContent で目的の 200 を取得できました。完。( ちなみに、 Math.round(...) は小数点以下の丸めです。)

表示データ≠取得したいデータ(問題のあるパターン)

ですが、取得したいデータが表示されていない場合もあるでしょう。

<div>しょうゆ:お値段秘密! カートに追加してからのお楽しみ!</div>

んなサイトあるかい! って感じの例ですが、要するに表示しないけれども裏でデータを持っておきたい場合です。この場合のデータは「税抜価格」の 200 です。この 200 を HTML のどこかに埋めておいて、 JavaScript から取得したい!

その1 : カスタムデータ属性(data-*)の利用(対応要素がある場合)

今回の解決策の1つ目。まずは条件として対応するタグ(要素)がある場合です。

この場合は HTML の仕様上用意されている「カスタムデータ属性( data-* )」を使うのがスジでしょう。

カスタムデータ属性(data-*)と利用例

以下、「カスタムデータ属性( data-* )」の例です。

<div id="item" 
  data-price="200"
  data-longname="本仕込み丸大豆醤油"
  data-shortname="しょうゆ">
しょうゆ:お値段秘密! カートに追加してからのお楽しみ!
</div>

「カスタムデータ属性( data-* )」は、「特定の要素(今回の例では <div id="item"> )に関連付いたデータ」を定義できるのが特徴です。

「属性」というくらいなので、 idclass のようにタグの中に プロパティ名="プロパティ値" の形式で埋め込めます。ルールは先頭を data- にすることで、その後ろは任意です。必要なデータを自由に好きなだけ追加できます

JavaScript からのアクセス

JavaScript からは [要素].dataset.[data-以下の部分] でアクセスできる仕組みになっています。

const item = document.getElementById('item'); // <div> を取得
console.log('税込' + Math.round(item.dataset.price * 1.1) + '円');
// => "税込220円"

item.dataset がカスタムデータ属性を指しています。 item.dataset.price でカスタムデータ属性の中のひとつの data-price へアクセスしています。

このように、標準の JavaScript でアクセスできる専用の方法(というか専用のプロパティ( dataset ))が用意されているというわけです。

場面にもよりますが、 <span id="price">200</span>円 のように頑張って表示データと関連させるよりずっと扱いやすいと思います。

jQuery からのアクセス

jQuery を利用していた場合は以下の方法を使いましょう。

const item = $("#item"); // <div> を取得
console.log('税込' + Math.round(item.data('price') * 1.1) + '円');
// => "税込220円"

jQuery からは [要素].data('[data-以下の部分]') でアクセスできる仕組みになっています。

カスタムデータ属性( data-* )のまとめ

「カスタムデータ属性( data-* )」は、

  • 特定の要素に関連付いたデータを定義できます。
  • 必要なデータを自由に好きなだけ追加できます
  • ルール
    • 先頭は必ず data- になります。
    • data- 以下の部分は任意ですので好きな名前を使えます。
  • アクセス方法
    • JavaScript からは [要素].dataset.[data-以下の部分] でアクセスできます
    • jQuery からは [要素].data('[data-以下の部分]') でアクセスできます

(参考)MDN の解説ページ

MDN の解説ページも一応掲載しておきます。

developer.mozilla.org

↑こちらは「カスタムデータ属性( data-* )」の利用例。当該記事以外の情報としては、 CSS からのアクセスについても触れられています

ただ、 CSS からは通常の属性としてアクセスするだけです。「カスタムデータ属性( data-* )」に特化した話ではありませんし、また、 CSS から利用する頻度は少なそうなので当該記事には書きませんでした。

developer.mozilla.org

↑こちらは「カスタムデータ属性( data-* )」の技術仕様。詳しく知っておきたい人向け。

その2 : script タグへの JSON 埋め込み(対応要素がない場合)

今回の解決策の2つ目。

条件として対応するタグ(要素)がない場合です。タグ(要素)がないのでその1の「カスタムデータ属性( data-* )」が使いたくても使えないパターンです。

script タグに JSON を埋め込む利用例

「script タグに JSON を埋め込めばいいじゃん」というタイトル通りの方法です。

こんな感じで使います。

<script id="user-info" type="application/json">
{
  "username": "hogehoge",
  "premiumUser": true
}
</script>

対応するタグがないので、<script> タグを作ります。 <script> タグはブラウザでは表示されません。 type="application/json" として、 JSON データであることを明示するとよいでしょう。(これはなくても動きますが)

JavaScript からは、 id で取得します。

const userInfo = JSON.parse(document.getElementById('user-info').text);
if (userInfo.premiumUser) {
  console.log(userInfo.username + 'はプレミアムユーザーです!');
}

id で取得した要素の中身を JSON.parse(...) でオブジェクトに変換しています。 userInfo はオブジェクトです。

オブジェクトの要素は . でアクセスできるので、 userInfo.premiumUsertrue が、 userInfo.username"hogehoge" が取得できます。

(参考)MDN の解説ページ

MDN には、「こういう使い方もできるよ」という例として掲載されていました。

developer.mozilla.org

<script>: スクリプト要素」の解説ページなのですが、正直こういうページを敢えて見ることはほぼないので、今回の情報は見つけにくかったですね。地味な場所に書かれていましたが、中々よい方法だと思いましたので拡散したいです。

(おまけ)避けたいパターン

ここからはおまけです。

私が様々なプロジェクトで見てきた実際のコードで、避けてもらいたいと思ったパターンです。これらを使うなら、先に紹介した2つの方法のどちらかを選択いただきたいと思ってます。

input タグの hidden を使う

こういうやつです。

<input type="hidden" id="price" value="200" />

<input type="hidden"> はブラウザ上で表示されないので、これを活用しようという方法です。

冷静に考えると(冷静にならなくとも)、通常 <input type="hidden"><form> の中に入れて、サーバー側にデータを送信する(submit する)用途で使うものです。サーバー側にも送信するならいいんですが、送信しないのにこれを使うのは違うかな、と思います。

サーバーサイドエンジニアが使いがちな方法で、何度も見てきました。ブラウザに見せないデータを <input type="hidden"> で送信することはよくあるので、それをフロントにも転用させてしまったパターンです。まあ問題なく動いちゃうんですけどね。

display: none で見えない意味無しタグを用意する

ちょっとタイトル分かりにくいですが、こんな感じ。

<div id="price">200</div>
<style>
#price {
  display: none;
}
</style>

<div> などの「ブラウザに表示される意味を持つタグ」を使い、 CSS の display: none; により「ムリヤリ非表示にしてやろう」という作戦です。

<input type="hidden"> よりも気持ちは分かる、が、本来はブラウザに表示するタグを別用途で使ってるんですよね。今回あげた2つの方法の方が、本来の用途に従っているという意味でスッキリ解決できるかと思います。

JavaScript のコードに埋め込む

ゴクマレに見かけます。レアです。

<div>しょうゆ:税抜<%= getPrice() %></div> <!-- Java コード getPrice() を実行 -->
<script>
const price = <%= getPrice() %>; // Java コード getPrice() を実行
console.log('税込' + Math.round(price * 1.1) + '円');
</script>

注目は3行目の const price = <%= getPrice() %>; の部分です。 Java の JSP を例にしていますが、 PHP でも Ruby でもありえる話です。

サーバーサイド側の言語にて、 HTML に値を埋め込むことはあります。通常は <div>しょうゆ:<%= getPrice() %>円</div> と使います。( getPrice( ) は別途作成している前提。)ここまではOK。

JavaScript から HTML の値を取得する方法が分からなくて(or 思いつかなくて)、 JavaScript のソースコード中に、 const price = <%= getPrice() %>; のようにして変数や定数に直接値を埋め込んだのでしょう。 JavaScript を HTML (JSP) と別ファイルにできなくなりますし、結構キッツイです。

これは、いろいろな手法を知らないため、知恵を絞って考えた方法なのだと思います。(そこは偉い!)先人がすでに考えた方法がありますので、それを知っていただいて、データは HTML に埋め込み JavaScript はそれを扱うっていう原則に従ってコードを書けるようになるといいかなと思います。いや、初心者のころって知識は断片的になっちゃうんで気持ちは分かるんですよね。知ったらこんな方法使わんわーってなると思いますので、知識を仕入れてもらえればと思います。

あとがき

私、 <script type="application/json"> の中に JSON データを定義する方法、知らなかったんですね。知って「便利じゃん!」と思って、知らせたく、拡散したくて、そのために記事を書きました。

尚、こちらのはてなブログ用のパーツでも利用しています。パーツ用の設定値を埋め込む方法に JSON を利用しました。

multimineral-tech.com

当該記事では、2つの方法チョイスして書きましたが、他によりいい方法があったならすみません。。。(教えていただきたいです。。。)

【はてなブログ用】カテゴリーを階層化するブログパーツを作りました【多段階層OK】【脱jQuery】

f:id:hiranoon:20210301205552p:plain

2021/03/22 : スクリプトを修正しました。内容は変更履歴をご参照ください。

サイドバーのカテゴリー部分を階層化するパーツを作りました。

はてなブログ用のカテゴリーパーツは既に何人かの方が実装されていましたが、オリジナルな実装を作成しました。多段階層が可能であることと、親カテゴリーはあってもなくてもOKなところが特徴です。

できること

ザックリと以下のことができます。(詳しい仕様は後半にまとめました。)

  • サイドバーのカテゴリーを階層表示できます。
  • カテゴリー名の先頭の「▶」or「▼」をクリックすると開閉します。(月別アーカイブと同じです。(※1))
  • 階層は1段目、2段目、3段目、4段目、、、と、何段でも作成することができます。

※1 : 選ぶテーマによっては月別アーカイブのマークは「▶」と「▼」以外になることがあります。

親カテゴリーのリンクなし

見た方が早いと思うのでアニメにて。例として3段表示しています。こんな感じで動きます。

親カテゴリーのリンクなし
親カテゴリーのリンクなし

親カテゴリーのリンクあり

親にあたるカテゴリーをリンクとして機能させることが可能です。差が分かりにくいかも知れませんが、親カテゴリーにマウスカーソルをあわせると、指表示になり下線が表示されるのが分かると思います。また、含まれる記事の数も表示されています。

親カテゴリーのリンクなし
親カテゴリーのリンクあり

(※通常のカテゴリー同様、記事一覧へのリンクとなります。)

(※記事の数の表示ははてなブログの機能です。)

(※はてなブログの仕様上、設定&メンテナンスが面倒&大変になります。)

パーツの設置方法

大きく以下の2つの対応が必要になります。

  1. 記事に付けているカテゴリー名を変更する。
  2. パーツ(JavaScript と CSS)自体を設置する。

カテゴリーの設定変更

設定済みのカテゴリーを変更します。この作業は親カテゴリーのリンクを作るかどうかにより作業内容が異なります

「カテゴリー名」と「見出し」の変更

管理画面から「カテゴリー」をクリックします。

存在するカテゴリーの一覧が表示されますのですべてのカテゴリーの「カテゴリー名」と「見出し」を変更します。

以下に画像を掲載します。この画像内の赤丸の数字の②~⑤をすべてのカテゴリーに対して繰り返します。

「カテゴリー名」と「見出し」の変更
「カテゴリー名」と「見出し」の変更

編集する「カテゴリー名」と「見出し」は以下のようにします。

  • 「カテゴリー名」: __ で階層を区切った名称に変更します。(画像の③)
    • __アンダースコア2つです。(日本語キーボードの場合は半角英数入力で「Shift + ろ」を2回押します。)
    • 例) プログラミング__言語__Java
  • 「見出し」: 最下層のカテゴリー名にしてください。(画像の④)
    • 例)Java

既存の記事のカテゴリーは以上の作業で変更が完了しました。

以降、新規の記事を作成する場合には、記事作成画面の右側にあるカテゴリーの選択から、上記対応後のカテゴリーを設定すればOKです。新しいカテゴリーを作る場合も今までのルールに従って作ればOKです。

新規の記事を作成
新規の記事を作成

親カテゴリーをリンクさせる必要がなければ、カテゴリーの編集は完了です。パーツの設置方法に進んでください。

(Advance)記事ごとに親カテゴリーを追加

親カテゴリーをリンクさせたい場合はこちらの対応も行ってください。

管理画面から「記事の管理」をクリックします。

各記事をクリックし、カテゴリーを変更します。

元々のカテゴリーが プログラミング__言語__Java であった場合、 プログラミングプログラミング__言語 を「新しいカテゴリー」として追加します。複数カテゴリーに所属していたらその分の親カテゴリーを作成します。

以下の画像の例では、3つの親にあたるカテゴリーを追加しています。追加さえしていればOKです。追加順序はパーツの挙動に影響はしません。(はてなブログの機能である、パンくずリストには一番上のカテゴリーが利用されます。)

記事ごとに親カテゴリーを追加
記事ごとに親カテゴリーを追加

その上で、管理画面から「カテゴリー」をクリックし、「見出し」名を変更します。(前の「カテゴリー名」と「見出し」の変更の項で行った内容と同じです。)(尚、1階層目は「カテゴリー名=見出し」になりますので対応不要です。)

親カテゴリーのリンクありの「カテゴリー名」と「見出し」の変更
親カテゴリーのリンクありの「カテゴリー名」と「見出し」の変更

パーツの設置

パーツの設定のための変更箇所は2ヶ所です。

  1. 「デザイン > レンチマーク > フッタ」への追記
  2. 「デザイン > レンチマーク > デザインCSS」への追記

JavaScript の設置

管理画面から以下のようにたどります。

「デザイン > レンチマーク > フッタ」

この中のテキストエリアをクリックします。

フッタの変更
フッタの変更

ここに以下の内容を追記します。(上書きしないよう気をつけてください。)

CSS の設置

管理画面から以下のようにたどります。

「デザイン > レンチマーク > デザインCSS」

この中のテキストエリアをクリックします。

デザインCSSの変更
デザインCSSの変更

ここに以下の内容を追記します。(上書きしないよう気をつけてください。)

パーツの設定変更

基本的にはこの作業は必要ありません。

以下の3点、変更したい場合はこちらの作業を行ってください。

  1. カテゴリーの区切り文字を、 __ 以外に変更する
  2. 初期表示時のカテゴリー階層が、 開いた状態になるよう変更する
  3. カテゴリーの先頭のマークを、 「▶」、「▼」、「・」以外に変更する

設定値の変更

JavaScript の設置の項にあるスクリプト中には設定値を埋めていました。以下の「ココ」と書いている部分です。

<script id="category-settings" type="application/json">
  {
    "delimiter": "__",      // ←ココ
    "initialState": "close" // ←ココ
  }
</script>
<script>/* 中略 */</script>

この設定値は以下のようになっています。

プロパティ 設定値 説明 ※参考:未設定の場合
delimiter 任意の文字 カテゴリー階層の「区切り文字」を指定できます。 __
initialState close or open 初期表示時にカテゴリーの開閉状態を指定できます。 close

(補足 : ※ 最初の script タグである <script id="category-settings" type="application/json">{...}</script> の部分自体がなかったら「※参考:未設定の場合」に記載した設定値として動きます。)

カテゴリーの区切り文字の変更

__ 以外への変更が可能です。他の区切り文字にしたい場合は任意の文字に変更してください。

これは、すでに __ 以外の文字で区切りを入れていた場合を意図して入れた設定です。他のはてなブログ用のパーツで - (ハイフン)を使う仕様になっていたモノがありました(※1)。こういったパーツから乗り換えた場合(※2)のコストを下げるために設定変更が可能な仕組みにしました。

※1 : 私も利用させていただいておりました。大変お世話になりました。。。

※2 : 親カテゴリーの設定が必須であったりなかったり、多段階層が可能であったりなかったり、ヤリタイコトができない場合の乗り換えを意図しています。

初期表示時の開閉状態の変更

初期表示時に開いた状態にしたい場合、 open に変更してください。

この設定がどちらが良いかはブログごとに異なるかと思っています。階層の深さや、作ったカテゴリーの数によって、初期表示時の開閉状態を検討いただけると良いかと思います。

尚、「1段目だけ開いて2段目以降は閉じる」みたいな階層ごとの設定変更には対応できません。また特定のカテゴリーだけ開く対応もできません。(多分、あまり需要がないだろうと思って組み込んでいません。)

カテゴリー名の先頭のマークを変更

CSS の設置の項にある CSS 中の値を変えてください。以下の「ココ」と書いている部分です。

/* ...前略... */
.hatena-module-category label.category-toggle-checkbox-label::before {
  content: "▶"; /* ココ */
  cursor: pointer;
}
.hatena-module-category
  input.category-toggle-checkbox:checked
  + .category-toggle-checkbox-label::before {
  content: "▼"; /* ココ */
}
.hatena-module-category label.category-not-to-toggle::before {
  content: "・"; /* ココ */
}
/* ...後略... */

試しに、前から順に "◎""◯""" としてみたら以下のようになりました。

カテゴリー名の先頭のマークを変更
カテゴリー名の先頭のマークを変更

文字で変更しても良いでしょうし、CSS で図形っぽいものを描画しても良いでしょうし、画像を利用しても良いでしょうし、ということで、利用されるブログのデザインに合うものに変更していただけると良いと思います。

仕様

この項では、今までも触れてきた機能仕様についてまとめておきます。

できること

はてなブログが持つデフォルトのカテゴリー機能に付加される機能の列挙です。

  • サイドバーのカテゴリーが区切り文字(__)ごとに階層表示されます。
    • 階層の数に制限はありません。(区切り文字はいくつ入れてもOK)
    • 区切り文字は設定で __ 以外に変更可能です。
  • 子を持つカテゴリー(以降、親カテゴリー)の先頭には「▶」or「▼」のマークが表示されます。子を持たないカテゴリーの先頭には「・」のマークが表示されます。
    • この先頭のマークは変更可能です。
  • 親カテゴリーの先頭の「▶」or「▼」をクリックすると開閉し、配下のカテゴリーの表示・非表示が切り替わります。
    • 初期表示時は閉じた状態になっています。
    • 設定で初期表示時に開いた状態に変更可能です。
  • 親カテゴリーを作成&設定していると親カテゴリーもリンクとして働きます。(親カテゴリーの作成&設定はしてもしなくてもどちらでもOKです。)
    • 例として 親__子__孫 というカテゴリーがあるとします。このとき、 親__子 というカテゴリーも作成&設定していると、 親__子 もリンクとして働き、カテゴリーの記事一覧が表示されます。
    • 親__子 というカテゴリーがなかった場合は、リンクとして働かないだけで動作に問題は生じません。

できないこと

行わないことを列挙しました。おそらく需要が少ないだろうなと思って実装していません。

  • 初期表示時の開閉状態を、階層やカテゴリーごとの個別設定はできません。
    • 1階層目だけ開いていて、2階層目以降は閉じている」ような階層ごとの開閉状態設定はできません。
    • 特定のカテゴリーだけ開閉設定を行うことはできません。
  • 記事先頭の「パンくずリスト」は階層表示を行いません。
    • 「パンくずリスト」は、はてなブログの機能です。「デザイン > レンチマーク > 記事」から利用する設定に変更できます。
    • 親__子__孫 であれば、「トップ > 孫 > 記事名」と表示されます。「トップ > 親 > 子 > 孫 > 記事名」とはなりません。
  • スマートフォン用の実装はありません。
    • レスポンシブにしている前提で実装されています。レスポンシブについてはこの記事の内容のみで行けると思います。
    • multimineral-tech.com

余談

不具合について

不具合等のご指摘がございましたら当該記事のコメント欄に記載いただければと思います。

デザインの変更について

CSS の設置の項にある CSS は適宜変更や追加などしてデザイン変更を行ってください。

特に、利用されているテーマによっては変なデザインで表示されてしまう懸念があります。こちらに関しては各ブログごとでご対応いただきたいと思います。

まあ、デザインの変更を行う方は、特に言われなくとも CSS をいじるような方だと思います。ですので、特に言及しなくていいとも思いましたが、一応書いておきます。

コダワリのポイント

コダワリ、といいますか、今回のパーツ作成に取り組んだキッカケやヤリタイコトです。今回のパーツを使うかどうかの判断基準に使っていただけるかも知れませんので、記載しておきます。

  1. jQuery を使用しない
  2. 区切り文字は - (ハイフン)以外にする
  3. 親カテゴリーはつくらなくても良い
  4. カテゴリーの順序は適当でOK
  5. 開閉は CSS で実現する

外部スクリプトは不使用にした

上記の 1. の jQuery を利用しないのは時流でもあり、すでに読み込まれている jQuery が多くて(元々のブログ機能やテーマなどで...)さらにグチャリそうなのを避けたかったからです。外部スクリプトは使わないで済むならそれにコシタコトハナイでしょう。

区切り文字を変更可能にした

区切り文字の 2. について。私のブログのカテゴリーで Material-UI というのを作ったんです。そしたら MaterialUI に区切られてしまって。。。というのがキッカケです。ヘンテコな文字列をデフォルトにして、変更可能にしておけば困らないだろうと思った訳です。

親カテゴリーはあってもなくてもOKにした

親カテゴリーの 3. について。やってみると分かりますが、親カテゴリーを作るのはすげー大変なんです。記事一つひとつに親カテゴリーも忘れずに付けるって、作業自体も大変だし、付け忘れも発生します。そして気づけない。

ただし、親カテゴリーがあると、その親カテゴリーで記事の一覧を表示できます。この表示はしたい人もいそうだなと思って、親カテゴリーがあってもヨシナに動くようにしました。

カテゴリーの順序は適当でOKにした

カテゴリーの順序の 4. について。これも記事ごとの設定が大変だと思ったので、順序はテキトーでも動くようにしました。

開閉は CSS で実装した

開閉の CSS 実装の 5. について。他のパーツでは開閉のイベントを JavaScript で実装しているモノしか見かけませんでした。 CSS で実装するとコードが煩雑にならずに済むのでそうしてみました。

こういう CSS で実現する系の話は良くある話ですね。今回やりたかったことも下記の記事を代表として、ググるとたくさんヒットしました。

maku77.github.io

実は、見えない <input type="checkbox"> タグを使っているんですよね。コードはスッキリするんですけど、見えないチェックボックスが潜んでいるというのも変な話で、分かりにくくなる原因でもあり諸刃の剣です。

メリデメは色々あろうと思いますが、今回は CSS で実現する方を採用したというお話でした。

あとがき

コダワリのポイント」として書きましたが、それを実現したパーツが欲しくてオジリナルな実装を書いてみました。

minify やら何やらやりたかったので、開発環境を作って、コードは GitHub に置くという流れになりました。そんななんで意外と時間がかかりましたね。コードは GitHub のを直接貼り付けてみました。ちなみに GitHub のリポジトリは以下です

github.com

変更履歴

変更日 変更箇所 変更内容
2021/03/22 JavaScript の設置 カテゴリー名の先頭(左側)に表示されるはずのマークが後ろ側(右側)に表示されてしまう問題を修正。

inliner で JavaScript も CSS も画像も1つのHTMLファイルにできるって、マ?

f:id:hiranoon:20210223025906p:plain

inliner」というツールの紹介です。 HTML が参照している JavaScript、 CSS 、画像などの外部ファイル、それらをまとめて1つの HTML ファイルにしてくれるツールです。

このツール、ご存知でしたか? 私は全然知らんかったです。

  • ニッチなツールだけれども、
  • ヤリタイコトとマッチしていたら確実に役に立つ、
  • ちと古いのがタマニキズ、

そんなツールでした。使い方次第で便利に活用できそうなので紹介したいと思った次第です。

inliner とは?

inliner ができることは?

公式サイトの「What it does」にはこのように書かれています。(私的翻訳。)

github.com

  • CSS、 JavaScript、 画像、 動画、 CSS 内で使われている画像、これらを取得してくるよ。
  • それらを1つの HTML ファイルにまとめあげるよ。

ここには書かれていませんでしたが、対象は、ウェブサイトでも HTML ファイルでもどちらでもOKです。

そのまとめ上げる中で、ついでに以下も実施してくれるそうですね。

  • JavaScript は minify するよ。( uglify-js を使います)
  • CSS 内の白は取り除くよ。
  • 画像や動画は Base64 でエンコードするよ。( つまり文字列化するということ。)

どう使うか?

基本的にはコマンドラインで使うようです。 Windows であれば GitBash などから $ inliner http://remysharp.com みたいな感じで起動できます。

また、 new Inliner('http://remysharp.com', function (error, html) {...}); みたいなスクリプトを書く使い方も可能です。(本記事では取り上げません。。。)

ということで、詳細は後ほど。

ちと古いツールか?

2021年2月現在、最終更新日は「2017年4月5日」となっています。主要なファイルの更新日時は4、5年前。うーん、ちと古いですかね。ソースコードを見ても var が使われていて、 ES5 のようですし、最新の感はありませんね。

とはいえ、今現在使ってみた変換結果に問題がなければ使っても良いかと思っています。基本的にローカルPCで使うと思いますので。

inliner を使ってみよう

inliner は npm の利用を前提としているので、Node.js の実行環境は必須になります。 Node.js の環境を用意した上で、 GitBash などのコマンドラインから動かします。

inliner の実行環境の用意

前述の通り、 npm が必要になります。この記事では、 Windows10 で、 GitBash を使った例で説明しています。(もちろん cmd.exe や PowerShell からも動かせます。その方法もこの記事とほぼ同じになります。)

前提環境

前提となる環境です。今回の記事の内容は以下のバージョンで行っています。

ツール バージョン
Windows 10
Node.js v14.15.5
npm 6.14.11
git (git bash を追加います) 2.27.0.windows.1

これだけじゃ困る人は以下の記事で。

multimineral-tech.com

inliner のインストール

ローカルPCにインストールする場合

ローカルPC にインストールする場合は以下。 -g オプションを付けます。公式サイトではこちらを紹介していますね。(私はやってませんが。)

$ npm install -g inliner

この後は $ inliner ... で使えます。

プロジェクト内だけで使う場

プロジェクト内だけで使う場合のお話です。

以降、ゴニョゴニョ書きますが、

  • npm install --save-dev inliner でインストールして、
  • $ ./node_modules/.bin/inliner ... で使えるよ、

ってだけの話です。それを補足含めて書いています。

ということで、インストールは以下。( $ npm init -y は任意で。)

$ npm init -y
$ npm install --save-dev inliner

npm init -y は、実行したディレクトリ内で npm が使える状態を作るコマンドです。( npm init だけだと色々聞かれます。回答するのが面倒なのでこの例で -y オプションを付けています。すると全部 Yes と回答したのと同じ結果になります。)(当然ですが、すでに npm を使っている場合はこのコマンドは不要です。)

次に npm install --save-dev inliner です。 --save-dev オプションで、このプロジェクト内のコマンドからのみ実行できるようにしています(ビルドした本番モジュールには含めない)。

インストールの結果、 node_modules フォルダに inliner がダウンロードされます。加えて package.json の内容が以下のように変更されます。

{
  // ...中略...
  "devDependencies": {
    "inliner": "^1.13.1"
  }
}

インストールした inliner 本体は ./node_modules/.bin/ に入るので $ ./node_modules/.bin/inliner ... で使えるようになります。

package.json の中に npm scripts として書く場合は ./node_modules/.bin/ は不要です。( ./node_modules/.bin/ は npm scripts のスコープに含まれるのです。)

{
  // ...中略...
  "scripts": {
    "build": "inliner src.html > dist.html",
    // ...中略...
  },
  // ...中略...
}

inliner の実行

では、使ってみましょう。

※ 前述したとおり、「プロジェクト内だけで使う場合」のコマンドは $ ./node_modules/.bin/inliner ... とする必要があります。ですが、以降では $ inliner ... で統一して記載していますのでご注意ください。

ウェブサイトの HTML を取得する

標準出力される

まずは(公式の最初にあったような)悪い例から。うちのサイトの記事を取得してみましょう。

$ inliner https://multimineral-tech.com/entry/2021/02/19/024215

サンプルをそのまま実行するとコンソールが。。。
サンプルをそのまま実行するとコンソールが。。。

うーん、これはヒドイ。。。

しばらくの間、コンソールが文字だらけに。コマンドの実行結果は標準出力されるんですね。まあそうっすよね。それが自然だ。

ファイルに書き出す

というわけで、 > article.html を追加してみました。

$ inliner https://multimineral-tech.com/entry/2021/02/19/024215 > article.html
‣ 404 on https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fh%2Fhiranoon%2F20210210%2F20210210232018.png
‣ 404 on https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fh%2Fhiranoon%2F20210210%2F20210210225851.png
‣ 404 on https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fh%2Fhiranoon%2F20201225%2F20201225191231.png
‣ 404 on https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fh%2Fhiranoon%2F20201216%2F20201216194527.png
‣ 404 on https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fh%2Fhiranoon%2F20210115%2F20210115195805.png
97% remaining: js(3)
Last job: 404 on https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fh%2Fhiranoon%2F20210115%2F20210115195805.png
Time: 9s 929.738ms

これで article.html が生成されました。

ブラウザで表示してみる

では、 article.html をブラウザでそのまま開いてみましょう。

inliner の変換結果
inliner の変換結果

お!? いい感じでは?

実際のサイトは下記のような表示になります。

実際のウェブサイトの表示
実際のウェブサイトの表示

ほぼ同じでは?

うちのサイト、更新日は JavaScript で表示しているのですが、上手く動いています。 CSS も画像もバッチリ表示されていい感じです。

ソーシャルボタンがちょっと違っています。まあこの部分は iframe を使っていたりと脂っこい部分なので致し方なしとしましょう。

いずれにせよ、ブラウザでサイトを保存した場合はここまでキレイに表示できませんね。

inliner の変換結果の確認

取得できた article.html の中から部分的にピックアップして見ていきましょう。

以降、「★変換前:」は元のページの HTML の抜粋で、「★変換後:」は article.html の抜粋になります。また、コードが長い場合はどちらも「中略」として記載しています。

JavaScript の変換結果

★変換前:

<script type="text/javascript" src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', function () {
    var menuBtn = $('#menu-btn');
    var menuContent = $('#menu-content');
    /*中略*/
  });
</script>

★変換後:

<script type="text/javascript" src="https://code.jquery.com/jquery-1.9.1.min.js"></script> <script>document.addEventListener("DOMContentLoaded",function(){var n=$("#menu-btn"),e=$("#menu-content");/*中略*/});</script>

2つ目の <script> タグの JavaScript のコードがコンパクトになっています。 minify が効いていますね。

一方で、1つ目の <script> タグで行っている外部ファイルの jQuery の読み込み部分はそのままです。

-m オプションで外部 JavaScript が取り込める

1つの HTML ファイルに外部 JavaScript ファイルもまとめるには -m オプションを付けると良いです。

$ inliner -m https://multimineral-tech.com/entry/2021/02/19/024215 > article-m.html

★変換後( -m オプション付き):

<script type="text/javascript">/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license
//@ sourceMappingURL=jquery.min.map
*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){/*中略*/)(window);</script> <script>document.addEventListener("DOMContentLoaded",function(){var n=$("#menu-btn"),e=$("#menu-content");/*中略*/});</script>

上記の通り jQuery も読み込んでまとめることができました。

CSS の変換結果

★変換前:

<link rel="stylesheet" type="text/css" href="https://cdn.blog.st-hatena.com/css/blog.css?version=4c5b1e236bf5f5214756766d711e87cb228f40db&amp;env=production"/>

★変換後:

<style>.clearfix{display:block;*zoom:1}.clearfix:after{display:block;visibility:hidden;font-size:0;height:0;clear:both;content:"."}.inline-block{display:inline-block;*display:inline;*zoom:1}...中略...</style>

読み込まれて、かつ、コンパクトになっています。 JavaScript と違ってオプションの -m なしで展開されました。

画像の変換結果

★変換前:

<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hiranoon/20210219/20210219022720.png" alt="f:id:hiranoon:20210219022720p:plain" title="" class="hatena-fotolife" itemprop="image">

★変換後:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABLAAAAJ2CAYAAABPQHtc...中略..." alt="f:id:hiranoon:20210219022720p:plain" title class="hatena-fotolife" itemprop="image">

画像が文字列データに圧縮されました。この方法で、画像をファイルにせずに HTML ファイルに埋め込んでいるんですね。(ちなみに、この部分はブログのアイキャッチ画像だったのですが、100万文字くらいに変換されました。長げぇ。)

変換まとめ

CSS、画像は、素直に1つの HTML ファイルにまとめることができました。

JavaScript の外部ファイルは -m を付ける必要がありました。用途にもよりますが、この点が注意ですね。

(おまけ)公式サイトの example を見る

利用方法は公式サイトの usage.txt にかかれていました。

github.com

何とも分かりにくいところにあるな。。。(って、 $ inliner --help で表示されるものと同じみたいなんで分かりにくくても問題ないんですけどね。)先程の -m のようなオプションについても説明が書かれています

さて、ここには example は4つ掲載されています。それらを順々に見ていたいと思います。(後半2つは特にマニアックなので飛ばしてOKです。)

ウェブサイトを取得する

$ inliner https://twitter.com > twitter.html

これは先程試したコマンドとほぼ同等ですね。ウェブサイトを1つの HTML ファイルに変換してくれます。

圧縮しないオプションを付ける

$ inliner -ni local-file.html > local-file.min.html

ローカルにある local-file.html というファイルを local-file.min.html に変換する例です。変換元はウェブサイトでも HTML ファイルでもどちらでもOKです。

ついている -ni オプションは以下のような意味になります。

  • -n (= --nocompress) は CSS と HTML を圧縮しないオプションです。

    • これを付けるとスペースや改行はそのままになります。
    • 逆にこれを付けないと、スペースや改行を除去してくれます。
  • -i (= --noimages) は画像を文字列化しないオプションです。

    • これを付けると <img src="https://..."> のようなタグになります。
    • 逆にこれを付けないと <img src="data:image/png;base64,iVBORw0KGg..."> のように画像を文字列にしてくれます。

文字コードを指定する

$ inliner -e windows-1253 http://foofootos.gr > foofootos-utf8.html

文字コードを指定したパターンです。

ヘッダーを指定する

$ inliner -H 'User-Agent: Inliner Custom' https://httpbin.org/headers

HTTPヘッダを付与してアクセスすることができます。 curl みたいに使えるとのことです。

※これはちょっと話がそれますが、、、 https://httpbin.org は「HTTPリクエストとレスポンスを返してくれるサービス」です。その中で更に https://httpbin.org/headers とするとヘッダ情報だけ返してくれます。実際にやってみると以下のような結果が返ってきます。

{ "headers": { "Host": "httpbin.org", "User-Agent": "Inliner Custom" } } 

httpbin.org さんが「こういうHTTPヘッダを受け付けたぜ」と教えてくれる訳です。HTTPヘッダとして付与した 'User-Agent: Inliner Custom' が有効であったと分かりました。

(おまけ)私的活用方法

はてなブログを運用している中で、デザインのカスタマイズや JavaScript を使ったパーツを作ったりしております。はてなブログは、管理画面にて HTML (JavaScript も含む) や CSS を追加できる仕組みになっています。

その作業の中で、以下の2点を実現したかったです。

  1. デザインの確認のため、ローカルPCにブラウザで表示確認ができる HTML が欲しい。
  2. JavaScript を、はてなブログの管理画面にそのまま貼り付けられる形式にしたい。

デザイン確認用の HTML の取得

これはまさに今回の例で実行したそのものです。inliner で取得するとブラウザで直接確認できる1つの HTML ファイルが手に入ります。

HTML にそのまま貼り付けられる JavaScript の取得

ちょっとヤヤコシイですが、、、うちのブログのサイドバーのカテゴリは、 JavaScript コードを独自で作っています。一部抜粋すると以下。

// (1) src/category.js
document.addEventListener('DOMContentLoaded', function () {
  // 中略
  const rebuildSidebarCategory = (settings) => {
    // 中略
  };
  // 中略
});

これはそのまま貼り付けられないので、以下の形式に変換したいんです。1. ES5 へ変換して、 2. minify して、3. HTMLの <script> タグで囲んだ形式です。

<!-- (3) dist/category.html -->
<script>"use strict";document.addEventListener("DOMContentLoaded",function(){var ...中略...</script>

やりたいことの 3.にある <script> タグで囲むってのが意外と厄介でして、これに inliner が使えます。ついでに 2.の minify も実施してくれます。(1. の ES5 への変換は Babel を使っています。)

(1) src/category.js (先に掲載)と (2) src/category.html (以下)の2ファイルを用意します。

<!-- (2) src/category.html -->
<script src="src/category.js"></script>

この状態で inliner を使います。

$ inliner src/category.html > dist/category.html

(2) src/category.html 内にある (1) src/category.js を読み込み、 minify した結果を (3) dist/category.html に出力してくれます。これで望みのモノが得られました!

・・・と、かなりマニアックな話にはなりましたが、うちではこのような活用をしています。お役立ち情報じゃなかったかも知れませんが、タイトルに(おまけ)と付けたので許してください。

あとがき

inliner 、ちょっと古めのツールで、ちょっとニッチな感じもしますが、使い所があれば便利に活用できそうです。私ははてなブログのカスタマイズというニッチな用途で大変助かりました。

ES6 を IE11 でも動くよう変換したい。そうだ、 Babel を使おう!

f:id:hiranoon:20210219022720p:plain

IE11 でも動く JavaScript に変換したい! ES2015(=ES6) 以降の JavaScript を変換して ES5 にダウングレードする方法です。この記事で利用しているのは Babel のバージョン7です。

なお、本記事ではあえて Babel 以外のツールは含めないようにしています。

前提(ヤリタイコト)

この記事では以下を前提としています。

  • IE11 でも動く JavaScript (ES5)に変換したい
  • 複数ファイルを一括で変換したい
  • できる限り簡単な環境で実施したい(余計なことをしない)
  • (おまけ)JavaScript はコンパクトにしたい(コメントを消して minify したい)

こういう変換は Babel がいっちゃん有名だろうということで Babel を使います。

この記事のコンセプトは「余計なこと」は避けたいので、 Webpack で1つのファイルにまとめたりとか、 React も変換しちゃえとか、他のツールとの組み合わせは言及しません。

Babel とは

公式サイトはこちら。

babeljs.io

その公式は「What is Babel?」というページで、 Babel は、

  • ES2015 (= ES6) 以降の新しいバージョンの JavaScript コードを、
  • 古いブラウザや環境で動く下位互換バージョンの JavaSdript コード に、
  • 変換するツール

と言っています。(私的翻訳)

「新しいバージョンの JavaScript でバリバリコーディングをして → Babel で変換して → 大体のブラウザで動く JavaScript を得る」のが目的のツールと言って良いでしょう。(その「大体のブラウザ」というのは、2021年現在では IE11 が対象になると思います。)

ブラウザで変換する(お試し利用)

Babel はブラウザだけで変換することができます。

公式サイトのヘッダーにある「Try it out」に行ってみましょう。

左サイドバーの「TARGETS」の欄に ie 11 を入力します。(このサイドバーの設定が分かりにくい!)

エディタ部の左側に以下のコードを入力するします。

const printMsg = (msg) => console.log(`print ${msg}`);

すると、エディタ部の右側に変換後のコードが表示されます。

"use strict";

var printMsg = function (msg) {
  console.log("print ".concat(msg));
};

const 、アロー関数( (xxx) => {...} )、テンプレート構文( text ${expression} )が変換されていますね。

これで事足りる人は以上で終了でOK。

とはいえ「Try it out」(お試し)ですし、毎回ブラウザに入力するのも面倒なのでローカルPCに環境を作ることにしましょう。

Babel を使おう

Babel は npm の利用を前提としているので、Node.js の実行環境は必須になります。 Node.js の環境を用意した上で、コマンドラインから動かします。

Babel の実行環境の用意

前述の通り、 npm が必要になります。この記事では、 Windows10 で、 VSCode と GitBash を使った例で説明しています。

前提環境

前提となる環境です。今回の記事の内容は以下のバージョンで行っています。

ツール バージョン
Windows 10
Visual Studio Code (VSCode) 1.53.2
Node.js v14.15.5
npm 6.14.11
git (git bash を使います) 2.27.0.windows.1

これだけじゃ困る人は以下の記事で。

multimineral-tech.com

フォルダの用意

以下のようなフォルダ構成を作りたいと思います。

./exec-babel
├─dist
└─src     

任意の場所に任意のフォルダ(ここでは exec-babel とします)を作り、 VSCode から開きます。

そしたら、 VSCode のターミナル(Ctrl + @ で表示される)から以下を入力します。

$ mkdir src
$ mkdir dist

npm の利用開始

npm が使える環境にしましょう。以下のコマンドを打ちます。( npm init だけだと色々聞かれます。回答するのが面倒なのでこの例で -y オプションを付けています。すると全部 Yes と回答したのと同じ結果になります。)

$ npm init -y

package.json ファイルが生成されます。

Babel のインストール

以下のコマンドで Babel をインストールします。

$ npm install --save-dev @babel/core @babel/cli @babel/preset-env

node_modules というフォルダが生成され、この中に Babel がダウンロードされています。

package.json の内容が以下のように変更されています。 devDependencies: の項目にインストールされたモジュールの情報が追加されています。

{
  // ...中略...
  "devDependencies": {
    "@babel/cli": "^7.12.16",
    "@babel/core": "^7.12.16",
    "@babel/preset-env": "^7.12.16"
  }
}

インストールされたのは以下の3つでした。

ツール 説明
@babel/core Babel の本体。これだけだと動かない。
@babel/cli コマンドラインから Babel を操作するツール。
@babel/preset-env 変換内容のプリセットです。

@babel/preset-env はプリセット? ちょっと分かりづらいですね。コレは後ほど簡単に説明しますが一旦次に進みます。

Babel の設定ファイルの作成

Babel の設定ファイルである babel.config.json を用意しましょう。変換したい内容を指定します。

$ touch babel.config.json

中身は「IE11対応にして!」とだけ書いておきます。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "ie": "11"
        }
      }
    ]
}

以上で Babel を実行する準備は完了です。

Babel の実行

それでは適当なファイルを用意して実際に変換してみます。

変換元ファイルの用意

変換元のファイルを用意します。(ここでは以下のファイルをサンプルとして用意しますが、もちろん自分で作成したファイルでOKです。)

$ touch src/printlog.js

src/printlog.js の中身は以下のようにしておきます。

// コンソールにログを出力する関数です。
const printMsg = (msg) => console.log(`print ${msg}`);

Babel で変換を行う(ファイル単位)

以下のコマンドで変換を実行します。(インストールした Babel 本体は ./node_modules/.bin/ に入ってます。)

$ ./node_modules/.bin/babel src/printlog.js --out-file dist/printlog.js

dist/printlog.js というファイルが生成されました。

"use strict";

// コンソールにログを出力する関数です。
var printMsg = function printMsg(msg) {
  return console.log("print ".concat(msg));
};

Babel で変換を行う(フォルダ単位)

以下のコマンドでも結果は同じになります。

$ ./node_modules/.bin/babel src --out-dir dist

今回は 1ファイルだけでしたが、フォルダ内に複数のファイルがあれば、一括で変換してくれます。

前回と今回ではオプションが違っています。 --out-dir でフォルダを指定した場合、変換後のファイル名は変換元と同一ファイル名になります。 --out-file であれば違うファイル名に指定できます。

  • --out-file : ファイル単位で変換
    • 変換元と変換先のファイル名を指定する(別フォルダの同一ファイル名でもOK。)
  • --out-dir : フォルダ単位で変換
    • 変換元と変換先のフォルダを指定する
    • 変換元と変換先は同じファイル名になる

こんな感じ。

npm scripts を使う

./node_modules/.bin/babel と長いコマンドを打つのは面倒です。通常は npm scripts を使うでしょう。

package.json"scripts": 内に build というタスクを追加すべく "build": "babel src --out-dir dist", の行を追記します。

{
  // ...中略...
  "scripts": {
    "build": "babel src --out-dir dist",
    // ...中略...
  },
  // ...中略...
  "devDependencies": {
    "@babel/cli": "^7.12.16",
    "@babel/core": "^7.12.16",
    "@babel/preset-env": "^7.12.16"
  }
}

package.json 内では ./node_modules/.bin/ の部分が不要になります。(npm scripts 内では ./node_modules/.bin/ がスコープに含まれるのだ。)

実行するときは以下のコマンドになります。

$ npm run build

(おまけ)minify とコメント削除もしたい

IE11 利用可能バージョンにするのに加えて、 minify とコメント削除も行ってみたいと思います。

Babel でプラスアルファを行いたい場合の一例として、参考にしていただければと思います。

minify の実施

インストールと設定変更

babel-preset-minify というツールがあったのでコレを使います。まずはインストールから。

$ npm install --save-dev babel-preset-minify

設定ファイルである babel.config.json を変更します。

["@babel/preset-env"...] の並びに "minify" を追加しました。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "ie": "11"
        }
      }
    ],
    "minify"
  ]
}
実行と結果

以下のコマンドで実行します。

$ npm run build

dist/printlog.js が以下のように生成されました。

"use strict";// コンソールにログを出力する関数です。
var printMsg=function(a){return console.log("print ".concat(a))};

不要なスペース・改行が削除され、変数名も簡素化されています。

コメント削除の実施

設定変更

コメント削除は Babel の機能に搭載されていますので他のツールのインストールは不要です。設定ファイルである babel.config.json の変更のみ行います。

"presets": の並びに "comments": false を追加しました。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "ie": "11"
        }
      }
    ],
    "minify"
  ],
  "comments": false
}
実行と結果

以下のコマンドで実行します。

$ npm run build

dist/printlog.js が以下のように生成されました。

"use strict";var printMsg=function(a){return console.log("print ".concat(a))};

コメントが削除されました。

補足と注意点

@babel/preset-env とは?

「Babel の実行環境の用意」の項では、 @babel/preset-env がちょっと分かりづらいかな? と書きました。それについてです。

@babel/preset-env は、 IE11 などの環境毎に変換すべき内容を提供してくれるモノです。

例えば、 IE11 で動かすための JavaScript が欲しければ、

「IE11 が対応したい(目的)。使える JavaScript のバージョンを調べよう。ええと、 ES5 だと分かったな。だから ES5 に変換すると指定してやるか、、、(手段)」

なんて調べた上で、設定ファイル内に ES5 と指定する必要がありそうです。そんな気がします。ですが、 @babel/preset-env さんがいれば、

「IE11 で動くよう、ヨシナに変換しといて!(目的&手段)」

とだけ指定すればOKになります。

IE11 だけならそこまで大変でもないんでしょうね。でも、環境が変わりゆく中で適切な変換内容を調べるのは結構骨が折れるモノです。ですので、「目的」だけ伝えればOKっていうのは中々便利なものです。 @babel/preset-env はそういうツールです。

Babel のバージョンについて

Babel の最新バージョンは、 2021年2月現在では「7」です。「Babel 7」では @babel/core@babel/cli@babel/preset-env をインストールして使いました。

Babel の旧バージョンはちょっと違っているんですよね。 babel-clbabel-preset-env のようなモジュール名になっています。先頭に @babel/ がついていないものは古いです。もし、 @babel/ がついていないモジュールを使っている解説記事があれば「古い内容かも知れない!?」と疑ってください。

ですが、今回使った babel-preset-minify のような @babel/ 化していないモジュールもありますのでご注意を。( minify を( Webpack ではなく) Babel で行う需要は低いんでしょうかね。だから Babel 7 化(?)されずに取り残されているのかな?)

あとがき

現時点で Babel だけを使う簡単な記事があってもいいかな、と思って書き始めました。

そう思った理由は以下。

  • 公式サイトを見るのがスジなのでしょう。が、「ちょっと変換したいだけ」という場合にはノイズが多すぎて(公式だからそりゃそうだ)読むのに時間が掛かる。
  • 公式サイトは英語なので読むのに時間がかかる。(英語力の問題。。。)
  • ネットで調べた「簡単な記事」のほとんどが Babel 6 以前の古い情報だった。
  • 「簡単な記事」では Babel だけでなく Webpack を使っているパターンが紹介されがちであった。

本格的に使い始める場合は、当該記事のような簡単な内容を知った上で、公式サイトを参照されると良いかと思います。

babeljs.io

今回は IE11 対応の JavaScript を生成することを目的としました。ですが、そろそろ IE11 も切り捨てていい頃かも知れないと思っています。 Microsoft 365 の IE11 サポートは、2021年8月17日 まで。本家でもそんな感じですからね。

最後に、「Babel って、普通は Webpack と一緒に使うよな」とは思っています。 Babel で変換したファイルをさらに1つのファイルにまとめたい場面の方が多いでしょう。今回の記事では、1つのファイルにまとめないニッチな需要を満たせたのなら幸いです。

nodist の利用を諦めた! そしてアンインストールが難しい!

ここ数年 nodist という Windows 用の Node.js のバージョンを管理できるツールを使っていました。大変お世話になっておりました。が、本日をもって利用を諦めました。

nodist のアンインストールはいくつか注意点があるなぁと思い、この記事を書きました。通常のアンインストールだけでは削除されないファイルがあり、そいつが悪さをすることがありました。ので、その共有の記事になります。

結論

先にまとめを。以下を順に実施すれば万事OKでした。

  1. 設定 > アプリ> Nodist から「アンインストール」します。
  2. /c/Program Files (x86)/Nodist を削除します。
  3. /c/Users/(ユーザ名)/.npmrc を削除します。
  4. /c/Users/(ユーザ名)/AppData/Roaming/npm_cache を削除します。

これだけで事足りる人は以上でOK。以降に詳細を続けます。

nodist について(蛇足その1)

蛇足その1。

こちらの記事で紹介しています。こういう物です。

multimineral-tech.com

が、 2019年に久々の BugFix がなされたまま。もう役割は終えたのでしょうか。

諦めた理由(蛇足その2)

蛇足その2。

キッカケは、コマンド npx create-react-app ... が使えなかったことです。

$ npx create-react-app hogehoge --template typescript
...中略...
npm ERR!     C:\Users\(ユーザ名)\AppData\Roaming\npm-cache\_logs\yyyy-MM-ddTHH:mm:ss.SSSZ-debug.log
Package install failed, see above.

ログに出力された C:\Users\(ユーザ名)\AppData\Roaming\npm-cache\_logs\yyyy-MM-ddTHH:mm:ss.SSSZ-debug.log の中は以下の通りです。 error cb.apply is not a function というエラーが出力されます。

...前略...
16 error cb.apply is not a function
...後略...

この解消は意外と根深いことが分かってきました。そして nodist の利用をやめることが解消の早道と分かったところで利用を諦めました。

※ 万一 error cb.apply is not a function でググって当該ページに来た方で、 nodist を使っていない方は下記の記事をご参照してください。。。

multimineral-tech.com

nodist のアンインストール方法

本題です。

通常のアンインストールだけでは削除されないファイルがあり、困ったことにそいつが悪さをします。それが何でどう削除するかを記載します。

  1. 設定 > アプリ > Nodist から「アンインストール」します。
  2. /c/Program Files (x86)/Nodist を削除します。
  3. /c/Users/(ユーザ名)/.npmrc を削除します。
  4. /c/Users/(ユーザ名)/AppData/Roaming/npm_cache を削除します。

.npmrc が結構キッツイ感じがしました。はい。理由は以下に。

通常のアンインストール

スタートメニュー(Windows左下のやつ) > 設定(歯車アイコン) > アプリ

とたどります。「アプリと機能」から Nodist を探してアンインストールします。

ま、普通ですね。スクショを撮るまでもなかったでしょうに(一応撮った)。

通常のアンインストール
通常のアンインストール

Program Files の Nodist フォルダの削除

/c/Program Files (x86)/Nodist が残っています。今後利用されても都合が悪いので削除しておきましょう。

Program Files の Nodist フォルダの削除
Program Files の Nodist フォルダの削除

.npmrc を削除

/c/Users/(ユーザ名)/.npmrc を削除します。

このファイルが残っていると結構な害悪になります。ですので手動で必ず削除します。

.npmrc ファイルとは?

これは npm コマンドの設定の一つで、ユーザー別の設定ファイルになります。 Nodist だろうがそうじゃなかろうが、 npm コマンドを実行したときはこの設定を見に行きます。

Nodist では、アンインストール時にこのファイルを消さない場合があるようです。(条件は不明。。。そこまでは調べてないっす。)

.npmrc が害悪な理由

残っていた /c/Users/(ユーザ名)/.npmrc の中身は以下のようになっています。

prefix=C:\Program Files (x86)\Nodist\bin

npm コマンドを使った時の設定ファイルで、グローバルにインストールするときの場所の指定になります。 Nodist というキーワードが残っていますね。

例として、 npm コマンドを打つと、以下のようなエラーが発生します。ディレクトリがないぞって言われます。

$ npm -v
Error: EPERM: operation not permitted, mkdir 'C:\Program Files (x86)\Nodist'
6.14.11

前項では「Program Files の Nodist フォルダを消そう」と書きました。Nodist フォルダを消してないと、エラーが発生せずに Nodist のフォルダに npm のグローバルモジュールがインストールされ続けます。(Nodist をアンインストールしたのに!?)

これ、しばらく気づかなくないですか? 私は Nodist フォルダを消したから気づけましたが、これは厳しいですね。。。

npm_cache の削除

/c/Users/(ユーザ名)/AppData/Roaming/npm_cache を削除します。

Nodist 時代に作られたキャッシュですので、今後の軋轢が生まれないよう消しておきましょう。

node のインストール

nodist 亡き後、何をインストールすべきか。色々あるようですが、素直に Node.js を Windows10 に直付け(普通にインストール)しました。

※ WSL2 もあるし、色々工夫されている方もおられますね。が、もうサードパーティの管理ツールはしばらくはいいかな、と。。。ガチガチにやる必要があれば docker 使ったり検討しようかな、と。

ということで、公式サイトからダウンロードしてインストールするだけです。

nodejs.org

特に何もなければ LTS (Long Term Support) とある方を選びましょう。(「最新版」と書いてある方は安定はしていないよバージョンだと思って良いです。)私が実施したタイミングでは「14.15.5 LTS 推奨版」でした。

インストールが終わったら gitbash を起動してバージョン確認をしました。

$ node -v
v14.15.5
$ npm -v
6.14.11
$ npx -v
6.14.11

これで完了。

【TIPS】Windows の「npm」で「error cb.apply is not a function」を解消した話(nodist の利用は諦めた!)

error cb.apply is not a function というエラーに悩まされていたのでその共有です。 Windows 10 で Node.js をインストールしている前提のお話になりますのでご了承ください。

調べた中で出てきいくつかの解消方法をかき集めてまとめたので、どれかにヒットすれば幸いです。

事象

npm コマンドや npx コマンドを実行したときに以下のようなエラーが出力されました。

# ...前略...
npm ERR!     C:\Users\(ユーザ名)\AppData\Roaming\npm-cache\_logs\yyyy-MM-ddTHH:mm:ss.SSSZ-debug.log
Package install failed, see above.

ログに出力された C:\Users\(ユーザ名)\AppData\Roaming\npm-cache\_logs\yyyy-MM-ddTHH:mm:ss.SSSZ-debug.log の中は以下の通りです。 error cb.apply is not a function というエラーが出力されます。

cb.apply エラー
cb.apply エラー

...前略...
16 error cb.apply is not a function
...後略...

調べてみると、解決されたパターンはいくつかあるようでした。環境にもよるでしょうから当然でしょう。以降、複数人の方がコレで解消したと言っている方法と、私が解消した方法をまとめて記載します。

対処方法

上から順に、影響が少なく簡単かと思います。一つずつ試してみて、解消すれば終了ラッキーという感じで進めるとよいんじゃないかと思います。

  1. npm cache clean --force でキャッシュを削除
  2. /AppData/Roamingnpmnpm_cache を削除
  3. Node.js の再インストール
  4. /graceful-fs/polyfills.js の特定の箇所のコメントアウトする。
  5. nodist の利用をやめる(nodist を使っていた場合)

1 2 3 は、下記のサイトの情報を参考にしました。

github.com

4 は、下記のサイトの情報を参考にしました。

flaviocopes.com

5 は、 nodist を使っている場合の対応です。

キャッシュの削除

以下のコマンドを実行します。

$ npm cache clean --force

コレで解決しなければ次へ。

/AppData/Roaming 配下の削除

キャッシュ等々が直接残っているフォルダを削除します。

  • /C/Users/(ユーザ名)/AppData/Roaming/npm_cache
  • /C/Users/(ユーザ名)/AppData/Roaming/npm

「別のプログラムがこのフォルダーまたはファイルを開いているので、操作を完了できません」と出で削除が出来ない場合は「リソースモニター」で握っているアプリを見つけると吉です。下記のサイトが参考になりました。

qiita.com

私の場合、 VSCode が握っていたのでこれを閉じたら上手くいきました。

コレで解決しなければ次へ。

Node.js の再インストール

スタートメニュー(Windows左下のやつ) > 設定(歯車アイコン) > アプリ > Node から「アンインストール」します。それから再度インストールします。

コレで解決しなければ次へ。

/graceful-fs/polyfills.js のコメントアウト

/graceful-fs/polyfills.js の特定の箇所のコメントアウトする方法です。

下記のサイトが参考になります。

flaviocopes.com

かなりアグレッシブな方法になると思います。 error cb.apply is not a function が発生したまさにその箇所を直接修正しちゃえってことですね。

管理が大変なのであまりオススメではないですが、これで助かる人もいらっしゃるでしょう。

コレで解決しなければ次へ。

nodist の利用をやめる

nodist の利用をやめます。代わりに公式サイトで提供されている Windows 版を直接インストールする等、他の方法を利用します。私はこれで解消しました。

nodist のアンインストールは各種フォルダやファイルが完全削除されないので注意です。 Windows で「nodist」を検索し、残っているものを削除するのが良いかも知れません。

nodist のアンインストールとその後のインストールについては下記の記事にまとめました。

multimineral-tech.com

別の事象ではありますが、下記のように nodist に悩まされている人は結構いらっしゃるようです。もう nodist を使う時代は終わったのでしょう。。。そんな更新もされてないですし。。。

qiita.com

あとがき

ということで、私自信がヒットしていない方法もありますが(スミマセン)、ヒットした方法+周辺情報ということでまとめました。私があちこち行ったり来たりするのが大変だったのでまとまった記事が一つくらいあってもよかろう、というのが作った理由。どれかが当たれば幸いです。