Google Closure Library と Closure Compiler を思い出しながら ES6 を書く
Google Closure Tools
https://developers.google.com/closure/
https://developers.google.com/closure/compiler/
https://developers.google.com/closure/library/
8年ぐらい前に話題になったやつ。
Closure Library は YUIとかExtJSとか、JavaScriptでGUIアプリを作るためのライブラリ。
Closure Compiler はES6をトランスパイルするのにも使えるようになった。未使用関数を削除したりインライン展開したりもするのでトランスパイルより名前の通りコンパイルが近いイメージ。
今でも淡々と更新されていて、特に宣伝はされていないので流行っていない。8年ぐらい前も流行るところまでいけてなかった気がする。
フレームワークを作るためのライブラリ集という感じで、UIですぐ使える便利機能というものは無い。特にUI系ライブラリのデザインは当初のものと同じなので、そのまま使うと8年前のGoogle系サービスの管理画面のようなデザインになる。
5年ぐらい触ってなかったのでリハビリする。
ES6で書くのはChrome拡張以外では初めてなのでそれも込みで。
アコーディオンコンポーネントを作る
jQueryのslideToggle
のようなもの。
goog.ui.Component
を継承して書く。
goog.module('app.anim.Accordion');
const Component = goog.require('goog.ui.Component');
const EventHandler = goog.require('goog.events.EventHandler');
const EventType = goog.require('goog.events.EventType');
const AnimEvent = goog.require('goog.fx.Animation.EventType');
const PredefinedEffect = goog.require('goog.fx.dom.PredefinedEffect');
const style = goog.require('goog.style');
const log = goog.require('app.log');
const CSS_CLASS_BODY = goog.getCssName('js-accordion-body');
class Accordion extends Component
{
/**
* @param {Element} inputEl hidden switch input element
* @param {number} time mili sec
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
*/
constructor(inputEl, time, opt_domHelper){
log('Accordion#constructor');
super(opt_domHelper);
/**
* radio or checkbox
* @type {Element}
* @private
*/
this.inputEl_ = inputEl;
/**
* duration
* @type {number}
* @private
*/
this.time_ = time;
/**
* animation logic
* @type {AccordionAnim}
* @private
*/
this.anim_ = null;
}
/**
* TODO: 動作未確認
* @override
*/
createDom(){
this.setElementInternal(
this.getDomHelper().createDom(
goog.dom.TagName.DIV, CSS_CLASS_BODY));
}
update(e){
if (this.anim_)
this.anim_.dispose();
const el = this.getElement();
this.anim_ = new AccordionAnim(el, this.time_, this.inputEl_.checked);
this.anim_.play();
}
/**
* @override
*/
enterDocument(){
log('Accordion#enterDocument');
super.enterDocument();
this.getHandler().listen(this.inputEl_, EventType.CHANGE, this.update);
}
/**
* @override
*/
exitDocument(){
log('Accordion#exitDocument');
this.getHandler().unlisten(this.inputEl_, EventType.CHANGE, this.update);
if (this.anim_)
this.anim_.dispose();
super.exitDocument();
}
/**
* @override
*/
disposeInternal(){
log('Accordion#disposeInternal');
this.inputEl_ = null;
this.anim_ = null;
super.disposeInternal();
}
}
初期からあまり変わっていないgoog.ui.Component
をextends
で継承しても特に問題なさそう。prototypeベースの親クラスにもgoog.base
の代わりにsuper
で動く。
重いと嫌なので終了処理は細かく書いた。この辺の解説記事はほぼ無いと言っても過言ではない。
goog.ui.Componentの継承で気をつけるべきこと一覧
goog.ui.Componentのはぐれかた
昔の記事で、この二つぐらい。
まあ構造はずっと変わっていないようなので昔の本を読むのが良いのかもしれない。
このコードは上記リンク先と違って出力したHTMLに後からdecorate
で対応させる用。HTMLが無い状態でDOM生成にも対応させるなら他のコントロール系コンポーネントのようにコンストラクタでinputEl
じゃなくて何らかのコントローラークラスを受け取るのが良さそう。今回は使わないので非対応。
アニメーションクラス。
/**
* @extends {goog.fx.dom.PredefinedEffect};
*/
class AccordionAnim extends PredefinedEffect {
/**
* @param {Element} el content element
* @param {number} time mili sec
* @param {boolean} toOpen if true then open animation
*/
constructor(el, time, toOpen){
log('AccordionAnim#constructor');
const start = toOpen ?
0 : goog.style.getSize(el).height;
const end = toOpen ?
goog.style.getSize(el).height : 0;
goog.style.setStyle(el, 'display', 'block');
goog.style.setStyle(el, 'height', start + 'px');
super(el, [start], [end], time);
this.el_ = el;
this.eh_ = new EventHandler(this);
this.eh_.listenOnce(this, AnimEvent.END, function(e){
if (!toOpen)
goog.style.setStyle(el, 'display', '');
goog.style.setStyle(el, 'height', '');
});
}
/**
* @override
*/
updateStyle(){
const height = this.coords[0];
goog.style.setStyle(this.el_, 'height', height + 'px');
}
/**
* @override
*/
disposeInternal(){
log('AccordionAnim#disposeInternal');
this.eh_.dispose();
super.disposeInternal();
}
}
exports = Accordion;
/** @const {string} */
exports.CSS_CLASS_BODY = CSS_CLASS_BODY;
ここでしか使わないヘルパー的な処理なので同じファイルの下側に追記した。
DOMいじってる感がすごくある。でもES6でJavaっぽくなって、Closure Libraryの設計自体が元々Javaっぽいから、これはこれで書きやすくなったのではないかな?
このクラスを使うコードはこんな感じ。
// ... snip ...
const dom = goog.require('goog.dom');
const CSS_CLASS_AREA = goog.getCssName('js-accordion-area');
const CSS_CLASS_INPUT = goog.getCssName('js-accordion-input');
class App {
// ... snip ...
decorateAccordion(el){
this.accs_ = [];
goog.array.forEach(dom.getElementsByClass(CSS_CLASS_AREA, el), function(a){
const inputEl = dom.getElementByClass(CSS_CLASS_INPUT, a);
const bodyEl = dom.getElementByClass(Accordion.CSS_CLASS_BODY, a);
if (!inputEl || !bodyEl)
return;
const acc = new Accordion(inputEl, 500);
acc.decorate(bodyEl);
this.accs_.push(acc);
}, this);
}
}
ES6が使えると言ってもArray.from
とかmap
とかを直接使うのは危険。ユーティリティ系関数を使うとブラウザサポートに合わせて切り替えるコードが出力されるのでそっちを使う。
元コードはかなり冗長だけれど、コンパイル後は
var c = Ua("w-x-y", b || a.a);
b = Ua("w-x-z", b || a.a);
c && b && (c = new Z(c,this.l),
Wb(c, b),
this.c.push(c))
のように展開される。これは最後の呼び出し部分。
goog.getCssName
が展開され、その定数もインライン展開されている。
ここのCSSは
.js-accordion-area
{
margin: 0;
}
.js-accordion-input
{
display: none;
}
.js-accordion-input:not(:checked) ~ .js-accordion-body
{
display: none;
}
.js-accordion-body
{
overflow: hidden;
}
このような、よくあるJavaScript無しで表示/非表示を切り替えるCSS。
jQueryなら3行くらいで書けるものがこんなに長大になった。大抵のUI系ライブラリは用意されている機能を使って楽して早く作りたい人向け、Closure Libraryは自前実装したい人向けというか。
一応似たようなコンポーネントでgoog.ui.Zippy
というクラスはあるんだけれど、これはなんか動きが違う。
HTMLは
<div class="@css('css-accordion-area')">
<input type="checkbox" id="ac1" value="1" class="@css('js-accordion-input')">
<label for="ac1" class="@css('title')">タイトル1</label>
<div class="@css('js-accordion-body')">
コンテンツ1
</div>
</div>
<div class="@css('css-accordion-area')">
<input type="checkbox" id="ac2" value="1" class="@css('js-accordion-input')">
<label for="ac2" class="@css('title')">タイトル2</label>
<div class="@css('js-accordion-body')">
コンテンツ2
</div>
</div>
こんな感じ。
@css
はサーバー側の関数呼び出しでClosure Stylesheetsの短縮形を出力している。使わずに普通のテキストでも良い。その場合はgoog.getCssName
も使わない。
JavaScript無しだと単なる表示/非表示切り替えのところを、スライドアニメーションを後から追加する形になる。
メモ
基本的に goog.provide
を goog.module
と exports
に、goog.require
はそのまま使える。
モジュールからスクリプトを呼び出す場合は普通にgoog.require
で良い。
スクリプトからモジュールを呼び出すにはgoog.scope
の中でgoog.module.get
が必要。
https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide
closure-compiler.jar のオプションはかなりややこしい。これは試すしかない。
モジュールはthis
がwindow
ではないが、Closure Tools はグローバル変数を結構使う。
モジュールじゃない設定系のファイルを作って読み込んだり、Closure Stylesheetsで出力したrenaming_map.js
を読み込むためにgoog.provide('renamingcss')
を追記したりした。グローバルの処理は全部実行してくれると思っていたんだけど、ES6と組み合わせるとrequire
しないと使えない?この辺の情報は全然分からなくて、推理力が試される。
あと素っ気ないAPIドキュメントがあるだけでマニュアルのようなものは無いので、困った時はソースコードを読むのが結局は早い。コード量は膨大だが昔ながらのJavaScriptで1ファイルが短いのですぐ読める。
Author And Source
この問題について(Google Closure Library と Closure Compiler を思い出しながら ES6 を書く), 我々は、より多くの情報をここで見つけました https://qiita.com/nishimura/items/103ed6d2a0b7865a9bb9著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .