Herokuのreview-appsはたとえHerokuを使っていなくても非常に便利なものですが、PR削除時にS3やElasticsearchなど外部にホストしているリソースを掃除する方法がありませんでした。
ところが、最近は pr-predestroy
hookが実装されたようで、外部リソースの掃除ができるようになったみたいですね。
ますます便利になりありがたい!
Herokuのreview-appsはたとえHerokuを使っていなくても非常に便利なものですが、PR削除時にS3やElasticsearchなど外部にホストしているリソースを掃除する方法がありませんでした。
ところが、最近は pr-predestroy
hookが実装されたようで、外部リソースの掃除ができるようになったみたいですね。
ますます便利になりありがたい!
一部のプロジェクトでmitamae (itamae on mruby) を使ってるんですが、自分が書いているときはともかく他人が書いているmiamaeでrecipeのロードエラーが発生すると、mruby-ioレベルでもmitamaeレベルでもファイル名を出力してくれなくてこれはデバッグできないぞという状態でした。
問題は2つのレイヤーであって、
低レイヤーのmruby-ioはopenは失敗時にファイ名名を出力すべきだし、miamaeレイヤーでもレシピの読み込みは非常に重要なのでもっと詳細にログに出してほしいところです。なおmitamaeレベルでのレシピ読み込みエラーはopen由来とも限らないので、どちらか一方ではなく両方のレイヤーで対応するのが一番いいと思っています。
というわけで両方なおして mitamae v1.4.5 にしてもらいました。
公式ドキュメントにありました。一言でまとめると、Android Oのpreviewが出た現在においても「Android N (API version 24)と同水準」となっています。
Use Java 8 language features | Android Studio
Android Studio 2.4 preview 4 (およびそれが要求するツールチェイン)の段階では、 desugar と呼ばれるツール(実体はAndroid Gradle PluginのTransform APIによるbytecode weaving tool)によって、一度javacでコンパイルしたバイトコードのJava8の言語機能(lambda, repating annotationsなど)をJava6水準のバイトコードに変換し、それをdxコマンドでdexにコンパイルするというプロセスを経るようです。このdesugarされたバイトコードは $subproject/build/intermediates/transforms/desugar/*
にクラスファイルとして残されるので、どのようにバイトコードが変更されたかは確認できます。
この変換されたバイトコードをみるかぎり、Android O相当のAndroid SDKによってサポートされた「Java8の言語機能」はretrolambdaとほぼ同じです。
件のドキュメントの表にもあるとおり、 “Java8 Language API” つまりJava8の標準ライブラリ(stream, optional, etc.)を使うには依然としてAPI level 24が必要で、状況としては一年前のAndorid Nの時点とほぼ変わりません。
つまり、Android Oのpreviewが出ている現在でも、Java8に関して去年からの唯一のアップデートは、 retrolambda相当のツールがAndroid SDKに同梱されるようになった というだけということになります *1。
Android Oで java.lang.invoke パッケージが追加されていたので、minSdkVersion = O ならもしかしたらinvokedynamicを使った本物のlambdaを使えるのではないかと思ったのですが、いまのところそのようにコンパイルされることは確認できませんでした。つまり、minSdkVersionの値に関わらず、desugarが必ず入るようです。このあたりはもしかしたら正規版までに変更があるかもしれませんが。
*1:去年の時点でβだったJackは正規版になるまえにdeprecatedになったのでノーカンで。
今更感ありますがReact Reduxを導入したの所感をメモしておきます。
これらを著者の有野さん よりご恵贈いただきました。ありがとうございます。
始めて知る内容も多かったのですが、既に知っていることでも著者の意見が反映されているのを読むと、いくつものモバイルOSを見てきたハッカーからみるとこう見えるのか!という新鮮な面白さがありました。
IとIIのテーマは独立しているので、どちらから読んでもいいと思います。
以下個人的に面白かった章をピックアップします。
public static void main(String[] args)
の役割
main()
があるのすら知りませんでしたなお『Androidを支える技術』レビュアーでありGoogleエンジニアでもあるomoさんのエッセイもすばらしいです。
MathJax というのはLaTeXなどで書いた数式を美しく描画するための処理系です。 なおこのエントリは MathJax v2.7.0 を対象にしています。
まず前提として、ほとんどのケースではMathJaxをconfigパラメータを与えてロードするだけで自動的にtypeset *1 が行われます。
この自動typesetは、HTML中のすべての要素に対してただ一度だけ行われるので、content editableでウェブエディタを実装しているケースやリアルタイムプレビューとの相性が非常に悪いのです。つまりcontent editable中のLaTeX記法は置き換えるべきではないのに置き換えてしまうし、プレビューが更新されてもtypesetされないのでこちらはLaTeX記法が処理されません。
そこで細かくMathJaxを制御する必要があるのですが、これがなかなか苦労したので、ハマったことや考えたことをここにメモしておきます。MathJaxは奥が深すぎて同じことをするのに何通りもの方法があり、ここに書いたものが唯一の方法ではありません。実際に実装するときは最新のドキュメントを参照することをおすすめします。
リファレンスはこちら。 ぐぐるとよく過去のバージョンのものが出てくることが多いです。何かを探すときは最新版をサイト内検索するのがいいでしょう。
http://docs.mathjax.org/en/latest/api/
MathJax-node があるので、SSR: Server Side Renderingもできるようです。
MathJaxの描画処理はかなり時間がかかり、またpreviewとして仮描画したものを出したりするため、数式の描画を表示するときにかなりガクガクします。
SSRするとこのガクガクがおそらくなくなるので、可能であればSSRしたいところです。しかしそのためには専用のnodejsサーバを立てなければならず、ほかの(たとえばReact SSR)レンダリングサーバに相乗りできるならいいですが、そうではなくMathJaxのためだけに新規にnodejsサーバを立てるのであれば大抵の場合オーバースペックでしょう。
今回はCSR: Client Side Renderingにしました。
MathJaxパッケージはnpmにもありますが、それをwebpackで固めるとextensionのロードが404になるという現象になり、結局解決できませんでした。
なのでcdnjsのようなpublic CDNを使うか、MathJax配布キットのとおりの構造でデプロイするかしないといけないようです。
MathJax 3.0でwebpack対応予定 ということなので、webpackに組み込みたいときは3.0を待つのがいいようです。
configに skipStartupTypeset: true
を渡します。
MathJax.Hub
を設定するためのドキュメントはこちら:
http://docs.mathjax.org/en/latest/options/hub.html
MathJaxは非同期でコンポーネントをロードするので、 MathJax
オブジェクトが存在するからといって使用可能とは限りません。
MathJax.isReady
は一つの指標としては使えますが、初期化の終了イベントをフックしたいところです。
初期化シーケンスは Startup Sequence にあり、それぞれのタイイングでコールバックを呼ぶのでイベントリスナを登録すればよさそうです。
このシーケンスの詳細は省きますが、End
が初期終了時に呼ばれるイベントです。
MathJax.Hub.Register.StartupHook('End', () => { // MathJax APIがすべて利用可能になった! });
MathJax.Hub.Typeset(target, callback)
を使うと、target elementを指定してtypesetを起動できます。
target
は HTML element またはその配列、あるいは null で、 nullの場合は document.body
全体が処理の対象になります。
MathJaxは処理をシーケンシャルにするために MathJax.Callback.Queue
というjob queueを持っているので、typesetの処理は実際にはこのようになります *2 。
MathJax.Callback.Queue(() => { MathJax.Hub.Typeset(elements, () => { console.log(`typeset is done for ${elements.length} elements.`); }); });
プロダクション環境ではエラーがあってもサービス提供者としては何もでないことのほうが多いでしょうが、開発中はエラーの詳細を知りたいというケースでは “Math Processing Error” イベントをフックすることでコンソールにエラーの詳細を出力できます。
MathJax.Hub.Register.MessageHook("Math Processing Error", (message) => { console.error(message); });
LaTeXをちゃんとレンダリングするのはかなり時間がかかるようで、デフォルトだと本描画のまえに数回プレビュー描画が入ります。
このpreviewは様々なレイヤでやっているらしく制御の仕方がはっきりしないのですが、 MathJax_Preview
CSS classが指定されているので、これに対してスタイルを当てると見た目はそれなりに制御できます。
というわけで全体像です。実際に運用しているコードそのものではありませんが、だいたいこんな感じです。 requestTypeset()
を呼ぶと (1) MathJaxがロードされていなければロードする (2) MathJaxが使えるようになるまで待ってtypesetを起動する (3) requestTypset()
は呼ぶたびにtypesetを起動する、ということを行うコードです。
const CONFIG = 'TeX-AMS-MML_HTMLorMML'; // 適当なconfig const DEV = (process.env.NODE_ENV != 'production'); let isLoading = false; function loadMathJax(onComplete) { const script = document.createElement('script'); // from https://cdnjs.com/libraries/mathjax script.src = `https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=${CONFIG}`; script.async = true; script.onload = () => { const MathJax = window.MathJax; console.log(`loaded MathJax/${MathJax.version} (${CONFIG})`); MathJax.Hub.Config({ messageStyle: DEV ? "normal" : "none", // 左下に出るメッセージボックスは開発時のみ有効にする skipStartupTypeset: true, tex2jax: { inlineMath: [ ['$','$'], ], displayMath: [ ['$$', '$$'], ], processEscapes: true, }, }); MathJax.Hub.Register.MessageHook("Math Processing Error", (message) => { console.error(message); }); MathJax.Hub.Register.StartupHook('End', onComplete); }; document.head.appendChild(script); } export function triggerTypeset() { const elements = [...document.querySelectorAll('.content')]; const MathJax = window.MathJax; console.assert(MathJax && MathJax.isReady); MathJax.Callback.Queue(() => { MathJax.Hub.Typeset(elements, () => { console.log(`typeset is done for ${elements.length} elements.`); }); }); } export function requestTypeset() { if (!document.querySelector('.content')) { return; } if (window.MathJax && window.MathJax.isReady) { triggerTypeset(); } else { if (isLoading) { return; } isLoading = true; loadMathJax(() => { triggerTypeset(); }); } }
追記:(2017/04/14): "foo" : "gfx/foo"
($github_user/$repository
)というフォーマットでうまくいきました。ε-(´∀`*)ホッ
追記(2017/04/13): 結局解決できてませんでした。つらい。
fooというnpm moduleにパッチを当てた gfx/foo
という版をどうしてもいま使いたい、というときに、package.jsonにはこんな感じにURLを直接書けるはずということになっているのですが
{ "dependencies": { "foo": "git+https://github.com/gfx/foo#branch", } }
これがどうもCircleCI上のyarnpkgだと刺さってしまって動きませんでした。yarnpkgのアップグレードなどでも解決せず。結局以下のように .git
suffixを付けると解決できました。
{ "dependencies": { "foo": "git+https://github.com/gfx/foo.git#branch", } }
CommonMarkというのはMarkdownの標準化を目指して2014年に立ち上がったプロジェクトです。
当時のニュース:
Standard Markdown Becomes Common Markdown then CommonMark (日本語版)
公式サイト:
あれから3年、とうとうGitHubがCommonMarkを採用したというアナウンスがありました。つまり、長らくMarkdownデファクトと考えられてきたGFM: GitHub Flavored Markdownは、いまやCommonMarkのスーパーセットなのです。
A formal spec for GitHub Flavored Markdown | GitHub Engineering (2017/03/14)
To ensure that the rendered Markdown in our website is fully compliant with the CommonMark spec, the new backend implementation for GFM parsing on GitHub is based on cmark,
実際には、このエントリにあるように、GitHubはオリジナルの参照実装であるcmarkをforkしたもの(cmark-gfm)を使っています。これは、GFMのいくつかの拡張について仕様が定まっていないためで、それらの議論が終わったらオリジナルのcmarkにマージするつもりだとのこと。GitHubはCommonMarkのコアメンバーなので、このあたりについては心配はいらなそうです。
https://github.com/github/cmark
拡張: https://github.com/github/cmark/tree/master/extensions
Ruby gemの CommonMarker も、実際に参照しているのは github/cmark で、いまはGitHub社のkivikakk氏がcommonmarkerとgithub/cmark両方をメンテしているようですね。
このGitHubのアナウンスをうけて、国内外のMarkdown採用サービスが CommonMark (あるいはその拡張のGFM)に移行してゆくと考えられます。
Kibela的にはCommonMarkの拡張構文への関心が高いところです。すでにPlantUMLとLaTeXという二種類の記法を描画するためにMarkdown構文を拡張しているからです。
CommonMarkの仕様や拡張については talk.commonmark.org というフォーラムがあるので、CommonMarkの将来についてはこのあたりを眺めるとよさそうですね。
いまReactのSSRのためにHypernova を使っていて、これはこれでちゃんと動くんですがいくつか不満があります。
さらに、JSでレンダリングしたいコンテンツは他にもあって、たとえば
なのでさしあたってこの三種類のコンテンツをレンダリングするためのNodeJSサーバがあるといいなと。
ここまでくるとNodeJSでアプリケーションサーバを書いたほうがいい気もしますが、まあそこは既存の資産があるのでステートレスなレンダリングだけでもまずはNodeJSでやれればなあと思いました。
というか、こうなるとHTTPサーバである必要もなさそう。
noindex
tagがあるにも関わらずインデックスされているケースを見かけたので調べてみたところ、googlebotに関してはどうやらそういう仕様があるようです。 noindex
tag は常にインデックスの削除を行うのだと思っていたのでちょっと驚きました。
メタタグを使用して検索インデックス登録をブロックする - Search Console ヘルプ
重要: noindex メタタグを有効にするには、robots.txt ファイルでページをブロックしないでください。ページが robots.txt ファイルでブロックされると、クローラは noindex タグを認識しません。そのため、たとえば他のページからリンクされていると、ページは検索結果に引き続き表示される可能性があります。
Block search indexing with meta tags - Search Console Help (同ページ英語版)
Important! For the noindex meta tag to be effective, the page must not be blocked by a robots.txt file. If the page is blocked by a robots.txt file, the crawler will never see the noindex tag, and the page can still appear in search results, for example if other pages link to it.
ちょっとこの説明だけだとよくわかりませんが、 noindex
を指定していても検索エンジンにインデックスされるケースがあるようです。
googlebotが「noindex
tagをみるとそのページをインデックスしない、既にインデックス済みの場合はインデックスを消す」というロジックだと仮定すると、たとえば次のようなケースではインデックスされるのかもしれません。
noindex
tagを指定しないままページを公開するnoindex
tagの指定とrobots.txtによる該当ページのブロックを 同時に 行うnoindex
tagをみてインデックスを削除する」という処理を行わないこれが正しいのだとすると、このgooglebotの注意書きのとおり、 robots.txt で該当ページのブロックを外せばインデックスが削除されるはずですね。goolebotがrobots.txt でブロックしているのを見つけたら、ブロックされているURLですでにあるインデックスをすべて削除すればいいような気もしますが、そうはなっていないようです。
ただ上記のgooglebotの注意書きの「たとえば他のページからリンクされていると、ページは検索結果に引き続き表示される可能性があります」はよくわかりません。最初からnoindex
tagとrobots.txtによるブロックしていて、かつ他のページからリンクされているときもインデックスされるかのように読めますが、この解釈が正しいのかどうかはわかりません。