Closure Library & HTML5

自己紹介

  • 伊藤千光(いとう ちひろ)
    • @webos_goodies
    • http://webos-goodies.jp
  • フリーで Web 開発やってます
  • Gadgets API Expert
  • Closure Library 本書きました→

Agenda

Closure Library について

Closure Libraryの特徴

どうでもいいよね

Closure Library のアイデンティティ

JavaScript開発のGoogle Way

Google Way (1) ソースファイルの依存性管理を自動化

// myapp.js
goog.provide('myapp.MyClass');
goog.require('goog.ui.DatePicker');
// index.html
<script>goog.require('myapp.MyClass');</script>

Google Way (2) 最適化はコンパイラに任せる

var CONSTANT = 'Hello ';
function hello(name) {
  return CONSTANT + name;
}
function bye(name) {
  return 'Bye ' + name;
}
alert(hello('world!'));

Closure Compiler でコンパイル

alert("Hello world");

Google Way (3) 変数には型がある

/** @param {string} name Your name. */
function hello(name) {
  alert('Hello ' + name);
}

hello(['world!']); // 警告 : 引数の型が違う
hello();           // 警告 : 引数は省略不可

Google Way (4) クラスベースのオブジェクト指向

/** @constructor */
function BaseClass() {};

/**
 * @param {string} msg A greeting message. */
BaseClass.prototype.greeting = function(msg) { alert(msg); };

/**
 * @constructor
 * @extends {BaseClass}*/
function HelloClass() {};
goog.inherits(HelloClass, BaseClass);

/** @inheritDoc */
HelloClass.prototype.greeting = function(msg) {
  goog.base(this, 'greeting', 'Hello ' + name);
};

/** @type {BaseClass} */
var instance = new HelloClass();

instance.greeting('world!');
instance.undefinedMethod();  // 警告

Google Way (5) ログをとる

goog.provide('myapp.MyClass');
goog.require('goog.debug.Logger');

/** @constructor */
myapp.MyClass = function() {}

/** @type {goog.debug.Logger} */
myapp.MyClass.logger = goog.debug.Logger.getLogger('myapp.MyClass');
myapp.MyClass.logger.setLevel(goog.debug.Logger.Level.ALL);

myapp.MyClass.foo = function() {
  myapp.MyClass.logger.debug('foo is called');
}

Google Way (6) 例外は逃さない

goog.require('goog.debug.ErrorHandler');
goog.require('goog.debug.entryPointRegistry');

var eh = new goog.debug.ErrorHandler(function(e) {
  logger.warning('Exception :', e);
});

goog.debug.entryPointRegistry.monitorAll(eh);

// setTimeout, setInterval での例外も補足するには以下を実行
eh.protectWindowSetTimeout();
eh.protectWindowSetInterval();

Google Way (7) テストする

Google Way まとめ

Closure Library 入門

準備

HTML の作成

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>サンプル</title>
  <link rel="stylesheet" type="text/css" href="editor.css">
</head>
<body>
  <div id="frame">
    <div id="toolbar"></div>
    <div id="editor"></div>
  </div>
  <script src="closure-library/closure/goog/base.js"></script>
  <script src="deps.js"></script>
  <script>
    goog.require('editor.App');
  </script>
</body>
</html>

JavaScript ファイルの作成

goog.provide('editor.App');
goog.require('goog.editor.Field');
goog.require('goog.editor.plugins.BasicTextFormatter');
goog.require('goog.ui.editor.ToolbarController');
goog.require('goog.ui.editor.DefaultToolbar');

goog.scope(function() {
  var Field      = goog.editor.Field;
  var Toolbar    = goog.ui.editor.DefaultToolbar;
  var Controller = goog.ui.editor.ToolbarController;

  /** @constructor */
  editor.App = function() {
    var editor     = new Field('editor', document);
    var toolbarEl  = goog.dom.getElement('toolbar');
    var toolbar    = Toolbar.makeDefaultToolbar(toolbarEl);
    var controller = new Controller(editor, toolbar);

    editor.registerPlugin(new goog.editor.plugins.BasicTextFormatter());
    editor.makeEditable();
  };
});

new editor.App();

CSS ファイルの作成

@import "closure-library/closure/goog/css/common.css";
@import "closure-library/closure/goog/css/colormenubutton.css";
@import "closure-library/closure/goog/css/palette.css";
@import "closure-library/closure/goog/css/colorpalette.css";
@import "closure-library/closure/goog/css/editortoolbar.css";
@import "closure-library/closure/goog/css/button.css";
@import "closure-library/closure/goog/css/menu.css";
@import "closure-library/closure/goog/css/menuitem.css";
@import "closure-library/closure/goog/css/toolbar.css";

#frame {
  border: solid 1px black;
}

#editor {
  display:block; width:100%; height:400px;
}

依存関係定義ファイルの生成

python closure-library/closure/bin/build/depswriter.py
  --root_with_prefix="scripts ../../../scripts"
  --output_file=deps.js

deps.js に以下の内容が生成される

// This file was autogenerated by closure-library/closure/bin/build/depswriter.py.
// Please do not edit.
goog.addDependency(
  '../../../scripts/app.js',
  ['editor.App'],
  ['goog.editor.Field',
   'goog.editor.plugins.BasicTextFormatter',
   'goog.ui.editor.DefaultToolbar',
   'goog.ui.editor.ToolbarController']);

これでひとまず動きます

スクリプトを単一ファイルにビルドする

python closure-library/closure/bin/build/closurebuilder.py
  --root=closure-library --root=scripts
  -n editor.App -o compiled --output_file=compiled.js
  -c /path/to/compiler.jar
  -f "--compilation_level=ADVANCED_OPTIMIZATIONS"
  -f "--define=goog.DEBUG=false"
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>サンプル</title>
  <link rel="stylesheet" type="text/css" href="editor.css">
</head>
<body>
  <div id="frame">
    <div id="toolbar"></div>
    <div id="editor"></div>
  </div>
  <script src="compiled.js"></script>
</body>
</html>

Closure Library の HTML5 関連機能

Closure Library の HTML5 への姿勢

goog.storage

goog.require('goog.storage.Storage');
goog.require('goog.storage.mechanism.mechanismfactory');

var mechanism = goog.storage.mechanism.mechanismFactory.create('namespace');
if(!mechanism) {
  var storage = new goog.storage.Storage(mechanism);

  storage.set('item1', 'Hello world!');
  storage.set('item2', [0, 1, 2]);
  storage.set('item3', { 'a':'A', 'b':'B' });

  var a = storage.get('item1'); // a == 'Hello world!'
  var b = storage.get('item2'); // b == [0, 1, 2]
  var c = storage.get('item3'); // c == { 'a':'A', 'b':'B' }

  storage.remove('item1');
  storage.remove('item2');
  storage.remove('item3');
}

goog.fs

goog.require('goog.fs');

goog.fs.getPersistent(10*1024).addCallback(function(fs) {
  return fs.getRoot().getFile(
    'test.txt', goog.fs.DirectoryEntry.Behavior.CREATE);
}).addCallback(function(entry) {
  return entry.createWriter();
}).addCallback(function(writer) {
  writer.write(goog.fs.getBlob('Hello world!'));
});

goog.net.xpc.CrossPageChannel

goog.net.xpc.CrossPageChannel (親フレーム)

goog.require('goog.net.xpc.CrossPageChannel');
goog.require('goog.events');

goog.scope(function() {
  var xpc = goog.net.xpc;

  var cfg = {};
  cfg[xpc.CfgField.LOCAL_RELAY_URI] = 'http://example1.com/relay.html';
  cfg[xpc.CfgField.PEER_RELAY_URI]  = 'http://example2.com/relay.html';
  cfg[xpc.CfgField.LOCAL_POLL_URI]  = 'http://example1.com/blank.html';
  cfg[xpc.CfgField.PEER_POLL_URI]   = 'http://example2.com/blank.html';
  cfg[xpc.CfgField.PEER_URI]        = 'http://example2.com/index.html';

  var channel = new xpc.CrossPageChannel(cfg);
  channel.createPeerIframe(document.body);
  channel.registerService('message', messageHandler);

  channel.connect(function() {
    channel.send('message', 'Hello');
  });

  function messageHandler(payload) {
    console.log(payload);
  }
});

goog.net.xpc.CrossPageChannel (子フレーム)

goog.require('goog.net.xpc.CrossPageChannel');
goog.require('goog.events');
goog.require('goog.Uri');
goog.require('goog.json');

goog.scope(function() {
  var xpc = goog.net.xpc;

  var cfg = goog.json.parse(
    (new goog.Uri(window.location.href)).getParameterValue('xpc'));
  // cfgの内容は検証するべき

  var channel = new xpc.CrossPageChannel(cfg);
  channel.registerService('message', messageHandler);

  channel.connect(function() {});

  function messageHandler(payload) {
    console.log(payload);
    channel.send('message', 'World!');
  }
});

デモ

goog.evets.FileDropHandler

ローカルファイルのドラッグ&ドロップをサポート

goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.FileDropHandler');

var handler = new goog.events.FileDropHandler(
  goog.dom.getElement('droptarget'), true);

goog.events.listen(
  handler, goog.events.FileDropHandler.EventType.DROP, onFileDrop);

function onFileDrop(e) {
  // FileList オブジェクトを取得
  var files = e.getBrowserEvent().dataTransfer.files;
};

(サンプル)

デモ

goog.net.WebSocket

goog.require('goog.events');
goog.require('goog.net.WebSocket');

goog.scope(function() {
  var events    = goog.events;
  var WebSocket = goog.net.WebSocket;

  var ws = new WebSocket();
  events.listen(ws, WebSocket.EventType.OPENED,  onOpen);
  events.listen(ws, WebSocket.EventType.MESSAGE, onMessage);

  try {
    ws.open('ws://example.com/websocket');
  } catch(e) {
    alert('WebSocketをサポートしていません');
  }

  function onOpen(e) {
    e.target.send('hello');
  };

  function onMessage(e) {
    console.log(e.message);
  };
});

goog.net.WebSocket の利点

goog.require('goog.debug.ErrorHandler');

// WebSocketのイベントハンドラ内で発生した例外は、
// すべてこの関数に転送される
var errHdl = new goog.debug.ErrorHandler(function(e) {
  console.log(e);
});

goog.net.WebSocket.protectEntryPoints(errHdl);

Web Worker 内で Closure Library を使う

// base.js があるディレクトリの相対パス
CLOSURE_BASE_PATH = 'closure-library/closure/goog/';

// bootstrap/webworkers.js, base.js の順に読み込む
importScripts(
  CLOSURE_BASE_PATH + 'bootstrap/webworkers.js',
  CLOSURE_BASE_PATH + 'base.js');

// goog.require() で必要なスクリプトを読み込む
goog.require('goog.array');
goog.require('goog.json');
//...

参考

Closure Tools

Closure Library
http://code.google.com/intl/ja/closure/library/

Closure Compiler
http://code.google.com/intl/ja/closure/compiler/

Closure Template
http://code.google.com/intl/ja/closure/templates/

Closure Linter
http://code.google.com/intl/ja/closure/utilities/

Google JavaScript Style Guide
http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml

サードパーティのツール

plovr
http://plovr.com/

Closure Script
http://www.closure-script.com/

ClosureBuilder
http://pypi.python.org/pypi/ClosureBuilder/

node-goog
https://github.com/hsch/node-goog

Closure Kitchen
http://closure-kitchen.appspot.com/

ご清聴ありがとうございました!