2016-04-29
Re: Template Strings (の使い方)
ES6のテンプレート文字列/リテラルは、 ` `
このようにバックティックで囲んだ高性能文字列のことなんだけど、具体的にどう便利かという解説があまり無いので、返信がてら実際の利用例書いた。via id:a-kuma3 k you!
ヒアドキュメントとか置換とか色々出来る。おおむね簡単なコードなのでわかりやすい、はず。
TL;DR
実はちょっと工夫すれば昔々から似たようなことが出来てた。長すぎて別記事調整中なので上げたらリンク貼ります。では庶民のコードをどうぞ。
WYSIWYG感
Template Stringsのおかげでヒアドキュメント可能になったので、CSS書くのが格段に楽になった。
addStyle()
は GM_addStyle()
のshimで、CSS足すやつ。 ぼくの定番コードで素のJavaScriptで書いてる。実際の中身はリンク先見て下さい。
jQueryよく知らないけど jQuery(document.head).append(`<style type="text/css">/*CSS*/</style>`)
とか読みかえれば良い。イマイチ感あるけど上手いやり方あるのかな。
(() => { 'use strict'; //... addStyle(` .flip-list-title-header { width: 50% !important; padding-left: initial !important; } .flip-entry-info { width: 50% !important; } .flip-entry-title { display: initial !important; } `); })();no title
長いCSSの文字列を普通のCSSファイルみたいに素直に書けるのでコードが整う。素晴らしい。
ES5以前
さて今まで見た目や可読性どうやってたかというと、こういうコード達見たことあると思う。CSSに限らず画像データとかに多い。
一時的にArrayを作り ,
で改行した後 join('/* お好み */')
で連結。
var style = [ '.klass {', 'font-size: huge;', // 'rule...', '}' ].join('\n'); style; /* <- ".klass { font-size: huge; }" */
+
で連結するパターン。画像データとかの長大文字列によく使われてる。
var img = 'dataURIスキームの画像ですよ' + 'どうしても長くなるので連結する' + 'まだまだ延々と数十行続くよ' /*+ '...'*/ +'やっと終わりですよ'; img; /* "dataURIスキームの画像ですよどうしても長くなるので連結するまだまだ延々と数十行続くよやっと終わりですよ" */
バックスラッシュでエスケープも出来るけど少数派。特徴としてインデントも文字列に含まれる点がある。改行を除けば一番ヒアドキュメントに近い感じ。
var escaped = '超絶長い文字列だよ\ インデントも\ ちゃんと入るよ\ マジで長いんだよ\ 途中改行入れられないよ'; escaped; /* "超絶長い文字列だよ インデントも ちゃんと入るよマジで長いんだよ途中改行入れられないよ" */
基本ですが、改行したい場合は \n
を使い、 \
出したい場合は \\
とエスケープが必要。
var linebreaked = 'この後改行ですよ\n\ \n\ \\ でごちゃごちゃしてしまう'; linebreaked; /* "この後改行ですよ \ でごちゃごちゃしてしまう" */
\
の嵐に耐えられるなら良いでしょう。
こういったソース整形用タイプは大抵置き換えて問題ない。
素直さ
先の例はCSS足すだけなので気楽に使ってるけど、ヒアドキュメントなのでソースの見た目のまま出力されるのに注意。
(() => { return `Linebreakable HardTab Half Width Space x 8 `; })(); /* <- Linebreakable HardTab Half Width Space x 8 */
このように文字列がソースの形のまま出る。
スペース・タブのインデントや改行入れたくない場合は整形してはいけない。
(() => { return `Linebreakable trim HardTab trim Half Width Space`; })(); /* <- Linebreakable trim HardTab trim Half Width Space */
何とも見た目が気持ち悪いのと、コーディングルール的にどうしましょうか、というのがヒアドキュメント系永遠の課題でしょう。先に上げた例の、シリアライズした画像の文字列クソ長いけどどう書くか、みたいな感じがまさにそう。
一応メリット取りつつ変形させるのさっと書いてみた。手抜きなのでもう少しスマートになると思います。
(() => { const heredoc = `Linebreakable HardTab Half Width Space x 8 `; return [ 'インデント消去改行保持:' + heredoc.replace(/^\s+/gm, ''), '1スペース連結:' + heredoc.replace(/^\s+/gm, '').trim().replace(/\n/gm, ' '), 'カンマ連結:' + heredoc.replace(/^\s+/gm, '').trim().replace(/\n/gm, ','), '文字のみ連結:' + heredoc.replace(/^\s+/gm, '').trim().replace(/\n/gm, '') ].join('\n----\n'); })(); /* <- インデント消去改行保持:Linebreakable HardTab Half Width Space x 8 ---- 1スペース連結:Linebreakable HardTab Half Width Space x 8 ---- カンマ連結:Linebreakable,HardTab,Half Width Space x 8 ---- 文字のみ連結:LinebreakableHardTabHalf Width Space x 8 */
ここまでするなら普通の文字列かArray連結でいいんじゃないの、という感じもあるけど、長くても書きやすいので今後増えてきそうな気がしてる。メソッドチェーンはお好みなので、 ``.replace(/pattern/, (m, c) => {/* 一気に変換 */})
とかもいいと思います。
実際使うときはこんな感じでしょう。カンマ連結のやつ、Array使う方法にちょっと変えてみた例。最後のカンマ区切り処理を .join(',')
ではなく + ''
で代替してる。
`Linebreakable HardTab Half Width Space x 8 `.split('\n').map(s => s.trim()).filter(s => !!s) + ''; //.replace(/^\s+/gm, '').trim().replace(/\n/gm, ','); /* <- Linebreakable,HardTab,Half Width Space x 8 */
配列最後尾に空の値が入るので .filter()
必要。もう少し短く出来るけど、
`Linebreakable HardTab Half Width Space x 8 `.split('\n').map(s => s.trim()).slice(0 ,-1) + ''; //`.split('\n').map(s => s.trim()).filter(s => !!s) + '';
わかりやすさイマイチですね。後述するタグ付きテンプレートのハンドラ作っておくと便利に使えそう。
小さいテンプレートたち
素朴さ重要。Template Literals 、 `${expression}`
と書くと、フレームワーク(FW)のテンプレートエンジンみたいに置換された文字列になる。これがテンプレートリテラル。置換部分は変数や関数、プロパティの処理結果が勝手に入ってくれる。
これが一番画期的なところで、あまり着目されないけどDOMで絶大な効果を発揮する。
といった多大なメリットがあり、冗長なコードが大分短く出来る。後述になるけどFirefoxの人はより幸せ度が高まるでしょう。
よくあるタイトルとURLを改行付きで取得するやつ。Bookmarkletとかショートさが要求されるシーンに最高。
// e.g. GitHub insane CSP constraint to Bookmarklet in Firefox, // use w/ Scratchpad or Console in Devtools panel `${document.title} ${location.href}` /* <- put title and URL w/ linebreak https://gist.github.com/noromanba/b9a3d4108d4f4d4055375856740db6bb */no title
ただ、サイトとブラウザ次第では、 javascript:/*...*/
の形であってもBookmarklet使えません。あまり知られてないけど、Content Security Policy(CSP)にはクロスブラウザ問題がある。上記コメントのようにGitHubとFirefoxの組み合わせが代表的なところです。
よってこういう場面ではローカルにjsファイルを置いておき、
もしくはコンソールにぶち込む、というのがWebバトルには必須と言えるでしょう。Markdownのリンクとかもこんだけ短いワンライナーで済みます。
`[${document.title}](${location.href})`; /* <- [put Markdown link syntax](https://gist.github.com/noromanba/2d79d49b4467f2e693cc5f2bcef29399) */no title
一々 '
で囲って +
する必要ない幸せ。素晴らしい。同じことES5以前だとこうです。
'[' + document.title + ']' + '(' + location.href + ')';
つらい。もう戻る気はない。
もう少しES6してまともに使ってる例。 C:。ミ タコいストリーミングサイトの情報補うやつ。
長いのでリンク先でどうぞ。
CSP vs Bookmarklet
CSPとブックマークレットの問題は id:mono0x さんが詳しいので以下がオススメ。
GitHubのCSP/CORSはやりすぎだと思う。Firefox側のCSP解釈に難があるのか、GitHubがヘッダ分けてるのか。前者だと思いますが。
タグ付きテンプレート
Tagged Templates という機能で、テンプレートに関数名のタグが付けられる。今の所庶民にはあまり縁が無さそう。
handler`${expression}`
と任意の関数名を前に書くと、 handler
関数がテンプレートに対して処理した結果が得られる。
handler
は自前で書く必要がある。引数は以下のように、ただの文字列部分の配列と、 ${expression}
を可変長な引数でとる形で処理する。
function handler(strings, ...expression) { // hard work //... return 'handled string'; } // or const handler = (strings, ...expression) => { // hard work //... return 'handled string'; };
大体どこのサイトも同じような説明なので、GoogleがEarly 2015に出したコード貼ります。
// Contextual auto-escaping qsa`.${className}`; safehtml`<a href="${url}?q=${query}" onclick="alert('${message}')" style="color: ${color}">${message}</a>`; // Localization and formatting l10n`Hello ${name}; you are visitor number ${visitor}:n! You have ${money}:c in your account!` // Embedded HTML/XML jsx`<a href="${url}">${text}</a>` // becomes React.DOM.a({ href: url }, text) // DSLs for code execution var childProcess = sh`ps ax | grep ${pid}`;Getting Literal With ES6 Template Strings | Web | Google Developers
これ何が便利かというと、FWがこういうタグ用のフィルタ関数を用意しておけば、ユーザは楽々エスケープとか多言語対応とか出来ますね、という妄想。リンク先見ると、 html()
をこんな感じでさらっと使ってるけど、
//... var username = "Domenic Denicola"; var tag = "& is a fun tag"; console.log(html`<b>${username} says</b>: "${tag}"`); //=> <b>Domenic Denicola says</b>: "& is a fun tag"Getting Literal With ES6 Template Strings | Web | Google Developers
エスケープするために地獄のような関数をシコシコ書いている。庶民にはつらい。あるいはコメントにあるように、自前でDSLとかFW書くような人には非常に便利そう。・・という位です今のところ。
標準でタグ用の関数が色々用意されると便利で面白いと思う。
String.raw
との併用
これは具体例あるのでよかった。jsの正規表現リテラルは / /
と、スラッシュでしか定義できないので、URLのスラッシュエスケープが特にダルい。さらに RegExp
で文字列コンストラクタ/ファクトリした場合、かなり複雑な2重エスケープする必要があるので避けるべきとされてる。ぼくは覚えられない。
それを解消した a-kuma3 さんの面白いコード。ライトな人には一見だとわかりづらいので、コメント消して引用注釈を † で入れてある。なお String.raw
は、タグの為のメソッドと言って過言でないと思う。
var s = "http://q.hatena.ne.jp/"; var re; // † / / な普通の正規表現リテラル // - スラッシュを \/ とエスケープする必要あり re = /https?:\/\/\S+(\.\S+)*\//; console.log(re.test(s)); // true // <snip> // † RegExp("") な通常文字列コンストラクタ // - スラッシュは / とそのまま書ける // - バックスラッシュは \\ とエスケープ必要 re = new RegExp("https?://\\S+(\\.\\S+)*/"); console.log(re.test(s)); // true // † RegExp(``) テンプレート文字列で文字列コンストラクタ // - 通常文字列コンストラクタ同様、 \\ とエスケープ必要 // - RegExp(`https?://\\S+(\\.\\S+)*/`) で true re = new RegExp(`https?://\S+(\.\S+)*/`); console.log(re.test(s)); // false // † String.raw のおかげで余計なエスケープ要らない、便利! re = new RegExp(String.raw`https?://\S+(\.\S+)*/`); console.log(re.test(s)); // trueTemplate Strings - おまえ、うまそうだな
ぼくは滅多なことが無い限り極力文字列コンストラクタ/ファクトリは使わないようにしてるのだけど、最後のコードは良さそう。String.raw``
パターン、わかりづらいけど String.raw
が特殊なタグでハンドラ、なはず。
Tagged Templates 詳細
役立ちそうなやつ厳選したので貼っておく。この2つだけでいい。ES6 全般で有名なやつです。
後者の 2ality は面白いハックが載ってる。両者とも実装内部の話が載ってるのでいい感じ。
近況
ぼくは昔から黙々とコード書いてコードでやり取りする、というのが活動の主体なのでGist更新が多いです。フィード拾っとくと何かと便利かもしれません。2012年頃から長い間隠されてたatomタグが最近復活してますが、一応貼っておきます。.atom
足すだけなんですけど。
このブログよりは更新頻度多いです。ただ、何故かGistのアクティビティが出ない。相当昔のリニューアル時にバグってから直ってないので、今のところGist追う目的のFollowは機能しません。過去のスクリプト更新とかもしょっちゅうしてるので、Recently updated見ると便利でしょう。
欠点としては、他の方がStarやコメント付けても更新扱いで上がってくる事です。この点通知に変えるとか、何とかして欲しい。
Hatena::Letも大分使ってましたが、残念ながら今後上げる予定は今の所無いです。滅茶苦茶不便で断腸の思いですが、更新もしてない。今度理由書きます。とはいえパブリックな物だけで208個もあるので参考にはなると思います。ユーザページから参照して下さい。
あるいはポータルサイト覗くほうが便利かもしれません。
js無いと動かないのが気に喰わないので、GitHub Pages お手軽に作れるやつとかに移行したい。
Hubサイト探してます
CodeRepos とか Userscripts.org とか Hatena::Let みたいな 黙々素朴Hubサイト欲しい。切実。いいのあったら教えて下さい。
P.S.
http://a-kuma3.hatenablog.com/entry/javascript_template_strings
日付無しなページ作れるの良いですね。AboutMe これで作っても良いかも。
〆
超絶 TL;DR