※スマホ対応はしてません。

カテゴリー: JavaScript

Chrome、Edge、Firefox互換のブラウザー拡張を作るポリフィル。

カテゴリー: JavaScript

最近思うところあってブラウザー拡張を作って公開しました。(Macが対応していない絵文字を使っているので、変に見えます。いずれどうにかする。) 仕事中についSNS見ちゃうのを止めるやつです。

スイッチオンにしてTwitterとかを開くと止められます。

作ったものについてはそのうち記事に書きたいと思ってます。あとブラウザー拡張の作り方についてもちゃんとした形にしたいなと思って準備中。

それはそれとして、作成中に得た知見のひとつ、互換性についてです。

先にまとめ

  • ポリフィル使えばすぐコード共通化できるしPromise化する
  • 共通化したコードはFirefoxに寄る
  • Edgeはもうひと手間
  • async/await はいいぞ

互換性

前提として、ブラウザー搭載のAPIは以下のような感じ。

  • Chromeは chrome オブジェクト以下にAPIを持つ
  • Firefoxは chrome 、Edgeは browser にChrome互換APIを持つ
  • Chrome互換APIは、コールバックは引数に与える

Firefoxは互換APIに加えて別にAPIがあります。

  • Firefoxは browser にChromeのAPIと同機能だが別I/FのAPIを持つ
  • Firefox独自APIはPromise化されている
  • async/await を利用できる
  • 後述のポリフィルで、ChromeとEdgeでもいける(いえーい)

つまりどのブラウザーでもだいたい同じ機能がそろってるけど、インターフェイスがちょっと違うよと。表にまとめるとこんな感じ。

ブラウザー chrome browser ポリフィル
Chrome ◎ ✘ 〇
Edge ✘ △ 〇
Firefox 〇 ◎

Promiseだと嬉しいって話

「ちょっと違うインターフェイス」の差はPromise化されているかどうかです。なので共通化する場合は、全部Promise化するか、逆に全部Promiseを剥がすかのどちらかになるわけですが、前者Promise化する方をお勧めします。というわけでその理由なんですけれども。

コールバック関数方式

まずこちらの例。Chrome互換APIで、保存した情報を持ってくるやつです。

chrome.storage.local.get(["item1", "item2"], (result) => {
  console.log('# result', result.item1, result.item2);
});

まあこれはこれで問題ないんだけど。

Promise方式

一方FirefoxがChrome互換APIとは別に独自に持っている browser 系APIだと、これがPromiseになる。(「にもなる」の方が正しいかも。)

browser.storage.local.get(["item1", "item2"]).then((result) => {
  console.log('# result', result.item1, result.item2);
});

これだけだと、まああんまり変わらないように見える。

いちおうメリットとして、Promiseを使うと非同期処理を連結してもコードのインデントが深くならないというものががあります。が、そんなものより、ES2017で導入された async/await を利用できる面が大きい。

async/await

前述のPromiseを使ったコードは、( async な関数の中で)以下のように書ける。

const result = await browser.storage.local.get(["item1", "item2"]);
console.log('# result', result.item1, result.item2);

まっすぐ書けるのでたいそう見やすい。

どうすか

Promiseの方が良くないすか?

APIをPromise化するライブラリ

というわけでPromise化したくなりましたか? なりましたね? なったので、これ↓を導入して実現します。

残念ながらライブラリのファイルが単体で公開されていないようです。ビルドシステムを導入していない場合でも、一度npmでインストールしてからファイルをコピーしてきます。

$ npm install webextension-polyfill
$ cp node_modules/webextension-polyfill/dist/browser-polyfill.min.js .

でもって読み込むようにする。

<script src="/browser-polyfill.min.js"></script>
<script src="/popup.js"></script>

これでChromeでも browser を使って、 async/await 記法でさらさら書けるようになります。やったー!

Edge対応

最終的にできあがったのはこれ↓

ライブラリを有効化する

件のライブラリは現状ではEdgeに対応しておらず、検討中みたい。

詳細省略するけど、ライブラリ読み込み前にこれ↓を実行したらEdgeもうまいこといった。

// ※別途に `cloneDeep()` 的なものをご用意ください
"use strict";

// "SCRIPT5045: Assignment to read-only properties is not allowed in strict mode" を避ける
try {
  if (window.browser) {
    browser.storage.local.get = browser.storage.local.get;
  }
} catch (error) {
  // 上書きできるようにする
  window.chrome = cloneDeep(window.browser);

  // Chrome偽装
  window.browser = undefined;
}

openOptionsPage()

他に、自分の見つけた範囲だと chrome.runtime.openOptionsPage() が実装されていないようなので、自前でpolyfillを実装した。他にもそういうのありそう。

window.chrome.runtime.openOptionsPage = () => {
  const { options_page } = browser.runtime.getManifest();
  browser.tabs.create({
    url: `/${options_page}`,
  });
};

manifest.json の互換性

今回特につまづかなかったけど、いちおうブラウザーによって必須だったり名前が違ったりするところがある。

一覧

options_ui

Chromeでは chrome_style: true が推奨。Edgeでは options_page になる。

{
…
  "options_page": "options_ui/index.html",
  "options_ui": {
    "page": "options_ui/index.html",
    "chrome_style": true
  }
}

あとFirefoxでも browser_style: true が推奨だけど、初期値がそうなっているので記述しなくてもいい。

APIの互換性

ライブラリがあるとは言ってももともと実装されていないものはされていないのであきらめる。

MDNの互換性の表を見るのが良さそう。

例えば先ほどの runtime.openOptions() は、やっぱり対応してなかった。

その他

  • Firefoxの chrome オブジェクトについての記述がMDNで見当たらなかった
  • Firefoxの browser でもChrome互換APIと同様にコールバックを引数に与えられるっぽい。でもMDNでの記述は見当たらなかった
  • Edgeは全く関係ない chrome オブジェクトを持っているっぽいけど詳細不明
  • ブラウザー拡張の標準化がW3Cで進行中 → Browser Extensions
  • 標準化されたAPIはPromise化されている(つまりFirefox風)
  • Chromeは標準APIに寄せる気がないらしい[要出典]
  • EdgeはChromeだけを見ているらしい[要出典]
  • Edge拡張を作るのは簡単だけど、公開するの難しそうでつらい

おしまい

async/await いいわー。

更新履歴

2018-04-24

  • Edge対応コードの "use strict" がなんか抜けてたのを修正
  • Edge対応コード完成版のリンクを追加
  • APIの互換性について追加

Nuxt.js他で時計アプリ (PWA) を作ってみたよ。

カテゴリー: JavaScript

ええいや時計って……という感じはあるんだけど、とにかくNuxt.jsを使ってみたかったのです。あと自分では使う。

できあがったもの

アナログ時計が左に、デジタル時計が右に。あとポモドーロタイマー付き。

使った技術

ライブラリ、フレームワーク

ツール

ウェブ標準API

サービス

技術系感想

Nuxt.js

この記事の影響。

なんか久しぶりに「動くものがさくさく作れる」という快感を味わうことができたフレームワーク。Ruby on Railsにこなれてきた頃の快感を思い出しました。

vue-cli使って入れたんだけど、最初からディレクトリとか開発用サーバーとか、各種ビルドとか即時部分更新 (HMR) とか、追加のあれこれなしに書くだけですぐ動く。黒魔術的な感じなのもRailsっぽい。いや別にRailsと比べるものじゃないんだけど。

今回は静的HTML (SSR) でやったけど、そのうち動的なやつもやってみたい。Firebaseでホストすればできると聞いたような気がする? 未調査。別に # なURLでも構わないけども。

vue-cilで入れるやつのコマンド(公式ドキュメントより):

$ npx vue-cli init nuxt-community/starter-template my-project

Nuxt + PWA

こいつを yarn add するだけでわりとすぐ動いた。お手軽便利。これを導入するだけで用が済んでしまったので、ServiceWorkerも自前では書いてないです。

ドキュメントはあんまり親切じゃない感じがする。まだ使い方とかがあんまりわかってない。

Visual Studio Code

ずっとVimを使ってきたんだけど(最近はNeoVim)、ちょっと人に教えるのにこれはあれだよなーと思って「普通のエディター」として使い始めてみました。あらでも良いじゃない。

スクリーンショット。公式サイトから。

後発なうえに開発体制が盤石なだけあって、さすがに出来が良い。各種OSに対応している点、インストールしてすぐ使える点、プラグインが豊富かつ導入や管理も楽な点、日本語対応している点から、今後は人様にお勧めしていこうかなと思っております。

最初に導入してほしいプラグイン:

  • Code Spell Checker
  • EditorConfig
  • ESLint(JS系のひと向け)

ただ、スクロールがなかなかちょっと。これはCodeじゃなくてたぶんElectron for Windowsの問題で、Slack.exeなんかでも起こる。どうも onscroll で何かやってるとうまくスクロールが動かなくなるみたい。詳細未調査だけど、たしか自分が作ったアプリだとそんな感じだった。まあだいたいの人はMacだから問題ないね……。

あとVimプラグインはキー操作が衝突するんで、案の定アレでした。

CSS Grid

何度か使ったことあったかな。気がつけば一通りのブラウザサポートが揃っていたので。

CSS Gridは全てのモダンブラウザで動作する。画像はgrid-templateプロパティの場合。

今回複雑なレイアウトをしてはいないけれども、別にそんじょそこらの細かいところでも割と気軽に使えるので、今後はがしがし使って行きたい。

今回使ったのは右側半分。”T”の字に区切ってます。

GitHub Pages + Cloudflare

今回は全部静的ファイルなので、GitHub Pagesで公開してCloudflareでhttps化。

これも簡単にできて楽だったので、今後は同じようにサブドメインにどんどん生やしていく感じで色々作っていこうかなと思いました。

作業内容はこれ↓。

yarn

普段はnpmの方を使っているんだけど、今回はなんとなくこっちに。インストールが早いとかは聞いてるけれども、うーん特に不満はないけど利点を感じるほどでもなかった。npmも今はロックファイルあるし、普通にインストールしたら記録されるしなあ。

アプリの紹介

時計です。

ポモドーロのタイマー付きです。

おしまい

楽しかった。

inputイベントでフォーム入力値をリアルタイム取得できるよ。(あとjQuery例。)

カテゴリー: JavaScript

input イベントを使います。

時間差なしで入力値が取れる。わあかんたん。

jQueryを使った例。

change イベントは入力を終えてフォーカスを外した際に発火するのに対して、 input イベントは入力の度に発火します。

対象要素

  • <input>
  • <textarea>
  • <select> … IE、Edgeでだめ
  • contenteditable 属性付きの要素 … IEでだめ

<select> は change イベントを使います。この場合は変更したらフォーカス残したままでも、すぐ発火する。(よね? しない環境ある?)

contenteditable は、逆に change イベントが発火しない。

チェックボックスとかはだめ

MDNによると、歴史的経緯により <input type="checkbox"> とかは click イベントを使う必要があるそうです。

あ、Firefoxだけ動くみたい。今後動くの増えるかなあ。

動作はIE 9+

基本機能は一般的なブラウザ各種と、IEは9+で利用可能です。

ただし一部機能が使えなかったりするみたいです。まあ普通に <input type="text"> で使う分には、特に。

昔話

昔はこの input イベントがなかったので、 setTimeout() を使って自前で監視してました。それ用のJavaScriptライブラリも多数あったと思う。

今はもう、そんな苦労する必要はないのだ……。

参考

Vue.jsで文字列が空のとき “Mismatching childNodes vs. VNodes” になった。

カテゴリー: JavaScript

Vue.jsで、こんなエラー(というか警告)が出た。

console.error()で &quot:[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content."

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.

再現コード

はこんな感じ。

<template>
  <section>
    <p><img />{{text}}</p>
  </section>
</template>

<script>
export default {
  data () {
    return {
      text: '',
    }
  },
}
</script>

たぶん、 <p> の直下には「 <img> 」と「 {{text}} から成るテキストノード」の二つがあるはずなんだけど、文字列が空だとそこにテキストノードが用意されないので、なんか予定と違うぞーと怒ってる感じだろうか。

解決策

とにかくテキストノードが存在していれば良いので、空白文字とか置いておこう。

text: '' を text: ' ' にするとか、同様に <template> の方にスペース追加するとか。

余白が嫌なら幅なしのやつ &zwnj; にしよう。

<template>
  <section>
    <p><img />&zwnj;{{text}}</p>
  </section>
</template>

とっぴんぱらりのぷう。

ES6とES2015に違いはないです。(現代的JavaScriptおれおれアドベントカレンダー2017 – 24日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 24日目

昔はES5とかES6とか言ってたはずなのに気付いたらES2015とか呼ばれるようになってて、なんかマリオが64になって急に数字が増えたなあみたいなのに近い感覚だったんですが、そんなことありませんでしたかそうですか。

概要

  • ES6とES2015は同じもの
  • わかりやすいからES2015と呼ぼうぜ
  • ES2015以降は毎年更新
  • 正式名称は “ECMA-262 edition 6” とかそういうの

ES vs JS

JavaScript (JS) はプログラミング言語ですが、その大本の仕様となるのがECMAScript (ES) です。JSはESの一種であり、ESの仕様で定められたものはJSで動きます。(あるいは、動くことが期待される。)

JSは各ブラウザが環境を用意していますが、ESの方はEcma Internationalという団体の中のTC39という委員会が仕様策定を行っています。

ベンダーはESの仕様通りにJSの動作を実装することになります。ES側は、逆にベンダーの提案と先行実装から仕様をまとめてる感じです。(たぶん。ここらへん怪しい。) 二つ以上の実装が存在するまで正式な仕様にはなりません。

ECMAScriptの仕様

ESも、具体的には “ECMA-262” という名前の仕様です。

Ecma International

色々と仕様策定を行ってる団体みたいです。ES以外にもC#とか。

E-C-M-A

前はECMA = European Computer Manufacturers Association(欧州電子計算機工業会)という団体でしたが、なんか国際化した現状に合わせて1994年に名前を変えたそうです。Windows 95より前だ。

なので “ecma” という英単語が存在するわけではないですが、かといって現在は略語というわけでもありません。

ES6 vs ES2015

この二者は同じものを指します。一般には後者の呼び方が推奨されます。

ES6

本来はESの第6版のことです。仕様の正式名称としては “ECMA-262” で、その “Edition 6” と。 class やアロー関数 => 等、「いまどき」の機能が多数追加されました。

前身のES5が2009年の発表で、2011年のES5.1を挟んで久しぶりに、かつ大きく更新されました。その発表年が、2015年です。

ES2015

というわけで、ES6こと “ECMA-262 Edition 6” のことです。

ウェブ界隈の進みが早いのでもっと細かく、毎年仕様を策定していこうという話になりました。それなら連番はわかりづらいよね、と。(ES7, ES8, ES9, ES10, …) そこで連番よりも発行年で、ES6ならES2015と呼ぶことが推奨されるようになりました。

ES2016, ES2017

それぞれ公開されています。

ES2015に比べて影が薄いけど、都度新機能なりが追加されます。

年で呼ぶことを推奨

ESの5、5.1、6版の仕様策定を引っ張ったAllen Wirfs-Brock氏のブログから。

So, why the year-based designation? The 6th edition of ECMA-262 took a long time to develop, arguably 15 years. As ES6 was approaching publication, TC39 (the Technical Committee within Ecma International that develops the ECMAScript specifications) already knew that it wanted to change its process in a way that enabled yearly maintenance updates. That meant a new edition of ECMA-262 every year with a new edition number. After a few years we would be talking about ES6, ES7, ES8, ES9, ES10, ES11, etc. Those numbers quickly loose any context for people who aren’t deeply involved in the standards development process. Who would know if the current standard ES7, or ES8, or ES9? Was some feature introduced in ES6 or ES7? TC39 couldn’t eliminate the actual edition numbers (standards organizations love their document numbers) but it could change the document title. We decide that TC39 would incorporate the year of release into the documents title and to encourage people to use the year when referring to a specific edition. So, the “newest version of JavaScript” is ECMA-262, Edition 8 and its title is ECMAScript 2017 Language Specification. Some people still refer to it as ES8, but the preferred shorthand name is ECMAScript 2017 or just ES2017.

では、何故年ごとの呼称になるのでしょうか。 ECMA-262の第6版は長い時間がかかりました。 15年ですよ。 ES6の公開が近づいていますが、その作業工程を変更し毎年更新することが望まれていると、TC39 (Ecma International内のECMAScript仕様策定の専門委員会)はわかっていました。 そう、毎年新しいECMA-262、そして新しい版番号です。 数年後、私たちは ES6, ES7, ES8, ES9, ES10, ES11, etc. について会話することになります。 標準化作業に明るくない方々にとって、これらの番号はすぐわけがわからないものになってしまうでしょう。 現在の標準がES7なのかES8なのか、それともES9なのか、誰が知っているというのでしょうか。 ある機能が追加されたのはES6? それともES7?  TC39は実際の版番号を消し去ることはできませんでしたが(標準化組織は文書番号が好きなのです)、文書のタイトルを変えることはできました。 TC39は発行年を文書タイトルへ組み込み、また特定の版へ言及する場合はこの年を使うことを推奨することにしました。 ですので、「JavaScript最新版」はECMA-262の第8版であり、そのタイトルはECMAScript 2017 Language Specificationとなります。 (訳注: 2017/08/31当時)  これをES8と呼ぶ人もいますが、簡略化する場合はECMAScript 2017、あるいはただES2017とするのが良いでしょう。

(訳注: “edition number” を日本語で「版次」というらしいんだけど、あんまり一般的じゃなさそうなので「版番号」としました。いやまあおれが知らないだけかもしらんけど。)

そもそもESではなくJSと呼んだ方が

JSはESではありますが、本当にES自体についての文脈でなければJSの名前で呼んだ方が良いだろう、との提言もしておいでです。(前項引用箇所の次の段落。) 同感です。

「現代的なJavaScript」という呼び方

版番号が関係する場合でも、単純に新旧で分けるなら、ES2015以前を「古いJS (legacy JavaScript) 」、以後を「現代的JS (modern JavaScript) 」と呼びましょう、と。

ES2015は大きな変更でしたから、そこで分けるのは妥当だと思います。

まだIE 11(2013年リリース)とか対応しなきゃとかってのはあると思うんだけど、そこはBabelを使う等して、できるだけ現代的な書き方でやっていきたいっすねー。便利だもの。

その他

他のもさー

ついでにIE 9とかIE 11とかじゃなくて、IE2009とかIE2013とか呼びたくない?

あとAndroid 4.4じゃなくてAndroid 2013とか。iOSは……まあいいか。いいか?

おしまい

というわけで「現代的JavaScriptおれおれAdvent Calendar」全24回でした。ここまでお付き合い頂きありがとうございました。

良いお年を!

(でもいくつか触れ損ねた話題もあるので、もうちょっとだけ続くんじゃ。既存記事も少し書き足したりします。)

参考