sass-loaderとdart-sassにまつわるfibersの話

tl;dr

用語について

現在npmで sass として公開されているパッケージはdart製のsassとなっています。旧来使われていたnode製のsassは node-sass として公開されています。

ここでは区別を分かりやすくするため、dart製のsassを dart-sass、 node製のsassを node-sass と呼び分けています。

本文

そもそもsass-loaderで何を設定しているのでしょうか。sass-loaderでdart-sassを利用する際はだいたい下記のように設定しているかと思います。

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        // ...
        use: [
          `// ...
          {
            loader: `sass-loader`,
            options: {
              implementation: require('sass'), // ここで `dart-sass` を読み込んでいる
              sassOptions: {
                fiber: require(`fibers`), // 大体の人がなんとなく一緒に読み込んでいる
              },
            },
          },
        ],
      },
    ],
  },
  // ...
};

まず implementation プロパティについてはsass-loaderのドキュメント に書いてあるとおりですが簡単にまとめると

  • implementation を設定しない場合、 node-sassとdart-sassの両方インストールされている場合、自動的に後者が読み込まれる
  • implementation を設定することで明示することができる

の2点かとおもいます。この「両方がインストールされている場合」というのは、プロジェクト内の package.jsondevDependencies などに追加されている場合以外にも他のパッケージの依存に入っている場合も含まれるので、基本的には明示したほうが良いでしょう。

では、なぜ dart-sass を利用する場合に fiber プロパティにfibersパッケージを指定しているのでしょうか。

node-sassにもdart-sassにも render()renderSync() というAPIがあります。これはどちらもscssからcssへコンパイルするための関数ですが、前者が非同期に実行され、後者は同期的に実行されます。sass-loaderでは render() が利用され非同期的にscssファイルがコンパイルされることになります。

しかし、 dart-sassでは非同期にに実行する render() が非同期実行のオーバーヘッドのため renderSync() より2倍近く遅くなってしまいます。これを避けるために、利用されるのがfibresです。

fibersはもともとnode.jsに非同期に関数を実行する仕組み(Promiseやasnyc/await)がなかった頃に開発されたものです。同期関数を非同期で実行することができる Promise に相当する機能があります。(他にもGeneratorに相当する機能もありますがここでは割愛します。)

速度の早い renderSync() 関数を非同期でするために、このfibersを利用する設定が前述した webpack.config.js です。ただしfibersは作者により使用を避けるべきとされており、node 16への対応もされていません。

まとめ

つまり記事の冒頭でまとめたように、 node 16 を利用するのであればfibersを利用することはできず、コンパイル速度の低下を受け入れざるを得ません。sass-loaderではdart-sassの場合に renderSync() を利用するIssueが立っているのでもしかするとsass-loader側で対応がなされるかもしれません。

nvmとかnodenvとかanyenvをやめてasdfにした

これまでnode環境はnvmで、他はpyenvやgoenvを利用していたのですが、nvmが .node-version ファイルに対応していませんでした。(.nvmrc で指定することはできませす。)
そこでいい加減nodenvに移行したのですが、せっかくなのでどうせなら新しい物をを使おうと思いasdfに移行しました。

anyenvの問題点ははただの「**envをインストールソフト」であり、nodenvやpyenvなどそれぞれの利用法を知っている必要があります。
asdfは実行環境ごとのプラグインを介して直接バージョンを管理するため
どの実行環境でも同様に扱うことができます。
ある日突然「PHPの5.3のバイナリが欲しい…」となっても使い方を迷う必要がありません。

例としてnodejsであれば

asdf install nodejs 16.1.0

Pythonであれば

asdf install python 3.9.5

でインストールが可能です。

また、nodejsであれば lts-fermium のようにタグで指定することも可能です。
自分はグローバルにインストールするものは「一番新しいLTSの最新バージョン」を利用することにしているので、非常に便利です。(nodenvではこれができませんでした。)
また、.asdfrclegacy_version_file = yes を追加することで .node-version なども自動的に読み込み、ディレクトリによって自動的にバージョンを切り替えることもできます。

注意点としては、ビルドする関係上、使用するプラグイン(実行環境)によって依存するライブラリがあるため、そのインストールは別途必要になります。

anyenvより圧倒的に使いやすいので、anyenvやその他のバージョン管理ツールを利用している場合は、asdfへの移行を強くおすすめします。

@nuxtjs/apolloでレスポンスが空だったら404を出す

Vue ApolloがNuxtで利用可能になる @nuxtjs/apollo を使うときレスポンスが空だったら404を出したい場合があります。

下記のようにSmart Queryを使うと簡単にサーバーサイドとクライアントサイドでfetchする機能を書くことができます。

// pages/blog/_id.vue
import GetPosts from '~/apollo/GetPost.gql';

export default {
  name: 'BlogIndex',
  apollo: {
    posts: {
      query: GetPost,
      variables () {
        return {
          pageId: 123,
        };
      },
    },
  },
}

しかしSmartQueryはレスポンスのHTTPステータスコードを設定できないという問題があります。
その場合は素直に asyncData() を利用しましょう

// pages/blog/_id.vue
import GetPost from '~/apollo/GetPost.gql';

export default {
  name: 'BlogPost',
  async asyncData ({ error, app, params }) {
    const response = await app.apolloProvider.defaultClient.query({
      query: GetPost,
      variables: { pageId: params.id },
    });
    if (!response.data.post) {
      error({ statusCode: 404, message: 'Not Found' });
    }
    return { post: response.data.post };
  },
  data () {
    return {
      post: {},
    };
  },
};

ただそうなると @nuxtjs/apollo を使う意義も薄くなってしまいますね。
vue-apolloの開発もあまり活発でないので何か別の手段を使ったほうがいいんでしょうか…。

HTMLの文字コードをどうするべきか、あるいはHTMLとは何かという話

HTML文書は文字エンコーディングUTF-8でなければなりませんという記事があり、混乱があるようなのでHTMLについてHTML5とHTML Living Standard(以下HTML LSと省略)について、そしてHTMLファイルの文字コードをどうするかについて、まとめておきます。

TL;DR

  • HTMLファイルの文字コードはHTML Living Standardに従ってUTF-8にする
  • 古いSJISやEUC-JPのHTMLファイルをUTF-8に変換する必要はない

What is "HTML" ?

一般にHTMLと呼ばれる規格には複数あります。

  • HTML4.01を含むそれ以前のHTML (W3C)
  • XHTML1.1 (W3C)
  • HTML5.1 (W3C)
  • HTML Living Standard (WHATWG)

まず一旦古い話は置いておいて、HTML5とHTML LSについて考えることにします。 以下「HTML5」は特筆なき場合「W3Cの勧告したHTML5.1」の意味です。また、HTML5の日本語訳へのリンク先は5.0の文書ですのでご注意下さい。

HTML5(W3C) とHTML LS(WHATWG)の違い

HTML5とHTML LSは基本的には違いはありません。
1.2 Is this HTML5? - HTML Living Standard1.2 これはHTML5か? ― HTML Living Standard 日本語訳
にあるようにWHATWGの策定したHTML LSに基いてW3CがHTML5として勧告を出しています。

そもそもWHATWGはW3CがHTMLをアップデートしないのに業を煮やしたAppleやMozilla、Operaによって設立されました。それを後にW3Cが取り込んだのが規格としてのHTML5となります。
経緯は1.6 歴史 ― HTML Living Standardを参照するのが良いでしょう。

W3CとWHATWGのどちらに従うべきか

ブラウザの実装による、と言ってしまえばそれまでですが、基本的にHTML Living Standardを守れば良いです。ブラウザの実装もこれに則っています。またW3CではHTML LSに基いているだけで、全く同一ではありません。また、標準化のタイミングも異なります。

  1. HTML LSで標準化済みで、HTML5で勧告済み(多くの基本的な内容)
  2. HTML LSで標準化済みだが、W3Cでは未勧告(最新の内容)
  3. HTML LSで標準化済みだが、W3Cでは違いがある(一部の内容)

という3つのパターンがありえます。

3.については1.12 About this document - HTML5.1に列挙されています。

やっと文字コードの話

再掲になりますがHTML文書は文字エンコーディングUTF-8でなければなりませんの文字エンコーディングの話は

  • WHATWGでHTML Living Standardの話
  • W3Cでは未勧告(まだissueが切られただけ)

となります。
つまり、HTML4.01やXHTMLは関係がないので、古いサイトの文字コードをUTF-8に変換する必要はありません。
BOMについては、UTF-8についてはBOMをつけることが許可されているので、つけても良いですが、つけなくて良いでしょう。<meta charset="utf-8">のようにmetaタグで文字コードを指定することも禁止されていません。(が、当然ファイルそのものの文字コードと一致する必要があります。)

逆に他のSJISやUTF-16などの文字コードが利用できないかについては、HTML LSでは

the actual character encoding used to encode the document must be UTF-8.

4.2.5.5 Specifying the document's character encoding - HTML Living Standard

とあり、一方HTML5の現状では

Authors should use UTF-8. Conformance checkers may advise authors against using legacy encodings.
(中略)
Authors must not use encodings that are not defined in the WHATWG Encoding standard. Additionally, authors should not use ISO-2022-JP.

4.2.5.5 Specifying the document's character encoding - HTML5.1

と微妙に差異があります。が、これに逆らってUTF-8以外を選択するメリットは殆どないでしょう。

おわりに

Web開発者であれば、WHATWGとW3Cの文章に目を通すようにしましょう。

movファイルを手軽にgifに変換するシェル関数

パッと画面上の動きなどをSlackなどで共有したい場合に、mov形式の動画からGIFを生成するのは何かと面倒ですし、ファイルサイズも大きくなりがちです。かといってAppStoreから余計なフリーソフトなどを入れたくないですよね。そこでシェル関数という形でまとめてみました。

1. FFmpegとgifsicleをインストール

brew install ffmpeg
brew install gifsicle

などする。FFmpegは言わずと知れた動画や画像を変換するためのソフトウェアで、gifsicleはアニメーションGIF化をいい感じにやってくれるやつです。

2. 下記のシェルスクリプトを.zshrcなどにコピペする

function mov2gif() {
	mov=$1;
	if [[ -z $2 ]]; then
		width=300
	else
		width=$2
	fi
	if [[ -z $3 ]]; then
		rate=15
	else
		rate=$3
	fi
	gif=`basename $mov`".gif"
	ffmpeg -i $mov -vf scale=$width:-1 -pix_fmt rgb24 -r $rate -f gif - | gifsicle --optimize=3 --delay=3 > $gif
}

3. movファイルを変換する

mov2gif hoge.mov #オプション指定なしだと横幅300px・FPS15のGIFを出力
mov2gif hoge.mov 500 30 #と指定すれば横幅500px・FPS30で出力

アニメーションGIFのファイルサイズはFPSに大きく依存しますが、動きをよく見せたい場合もあるので、FPSを指定できるようにしてあります。
またGIF画像の高さを指定できないのは、殆どの場合キャプチャした動画は正方形に近く、サッと共有したい場合にはだいたいの画像の大きさが指定できれば十分という理由からです。

出力例

こんな感じのGIFファイルが生成できます。(横幅500pxで出力)

HHVMではv3.19以降でないと定数に配列を使えない

HHVMはPHPとある程度互換性がありますが、完全互換ではありません。

const ARR = ['foo', 'bar'];
var_dump(ARR);

このような定数に配列を代入すると、Fatal errorを吐いてしまいます。これはクラス定数も同じです。
HHVMを3.19以降では機能がサポートされているので利用可能です。

PhpStormでnode製サイトのプレビューをする

node製サイトを開発時はwebpackを利用していればwebpack-dev-serverなどを利用するのが常ですが、プロダクションビルド後のファイルで確認したい場合もあります。

そういうときにわざわざサーバーにアップロードしたり、MAMPなどを利用するのは面倒です。ですが、PhpStormであればビルドも含めてショートカット1発でサーバーを起動することができます!

今日はその設定をしていきましょう。設定は1分で終わります。

Step.1 サーバー設定

「Run」->「Edit Configurations」から「Run/Dubug Configurations」のウィンドウを表示し下記に設定します。

  • Name: Preview server(好きな名前を設定してください)
  • Single instance only: チェックを入れる(重複起動を許可しないかのチェックです)
  • Host: localhost
  • Port: 3001
  • Document Root: ドキュメントルートへのパス

HostとPortは他のアプリケーションが利用中だと起動できないので、webpack-dev-serverやMAMPなどを利用している場合は被らないように設定するのが吉です。

Step.2 npmスクリプトの起動設定

次に「Before launch: Acvtive tool window」で「+」をクリックして「Run npm script」をクリックします。

すると「NPM Script」のウィンドウが開くので下記に設定します。

  • Command: run
  • Scripts: build(など自分でpackage.jsonに設定したスクリプト名)

Step.3 起動

あとは^ Rまたは「Run」->「Run 'Preview server'(上記のName:で設定した名前)」をクリックすれば指定したHostとPortでサーバーが起動します。

PhpStormなどJetBrains製のIDEはWeb関係の技術と親和性が高いので、Web開発者は必携ですね。

PHPでMarkdownをいい感じにやる

Markdown方言

Markdownの方言は
Markdown
CommonMark
Github flavored Markdown
Markdown Extra
だいたいこの4つから選ぶことになると思います。

CommonMark

  • Markdown系のスタンダード(になるかもしれない)
  • 現在も仕様策定中で2017年内にv1.0.0がリリース予定
  • 公式のPHPの実装がある

Markdown

  • 本来の「Markdown」といった場合はこのMarkdown
  • 機能は少ない
  • 実装はいくつかある

Github flavored Markdown

  • Githubで利用できるMarkdown
  • URLの自動リンクやテーブルが利用可能
  • 行末にスペース2つを入れずに、改行するだけで<br>が生成されるのが特徴
  • PHPでの実装は単独では見つからない

Markdown Extra

  • テーブルに加え定義リスト(まさにこのリストの部分)が利用可能
  • 多機能で実装も多い
  • PHPで実装されている

ライブラリ

今回はcebe/markdownを選びました。

cebe/markdownの特徴はMarkdownGithub flavored MarkdownMarkdown Extraの3つが利用可能ですが、Markdown Extraについては定義リストなど実装されていない文法があるようです。
ただし、拡張が簡単なので、今回はこれを利用します。

実装

まずはcomposerでインストールします。

composer require cebe/markdown

下記のコードで簡単にHTMLに変換することができます。

$markdown_string = '# Hello, Markdown';
$converter = new \cebe\markdown\MarkdownExtra();
echo $converter->parse($markdown_string);
<h1># Hello, Markdown</h1>

ただ、Markdown Extraは改行のみだと<br>が生成されず、行末にスペースを2つ入れる必要があるので、拡張してみましょう。

class MyMarkdown extends \cebe\markdown\MarkdownExtra{
	//同ライブラリのGithub flavored用の実装と同一
	protected function renderText($text){
		if ($this->enableNewlines) {
			$br = $this->html5 ? "<br>\n" : "<br />\n";
			return strtr($text[1], ["  \n" => $br, "\n" => $br]);
		} else {
			return parent::renderText($text);
		}
	}
}

$markdown_string = '# Hello, Markdown';
$converter = new MyMarkdown();
echo $converter->parse($markdown_string);

簡単ですね。これをうまく利用するとWordPressでも実装することができます。
WordPressへの組み込みはまたいずれ…。