kintone 開発で Closure Compiler の ADVANCED を使うための externs

みなさまこんにちは！
先日自宅のPCが2台立て続けに沈黙しまして、いっそ新しいノートPC買ってやれということで、逆にほくほくつやつやな気持ちを満喫しております。新しいPCはアガりますよね。
こいつを持っていろいろ参加したいｗ

さて、kintone Advent Calendar 2018 14日めです！
[2017年のAdvent Carendar](https://qiita.com/advent-calendar/2017/kintone)ではお遊び的な感じで、[kintoneカスタマイズで画像を使う方法。そしてマインスイーパ。](https://qiita.com/spica/items/341ee21222f20799d082)とか書きました。（これ今やってみたらバグありますね……デモは直しましたがアプリのほうはそのままです。あとで直しときます。すいません）

今年はClosure Compilerをがっつり使うためのexternsということで、書いていこうと思います。
ほんとはマジカルドロップとか作ってkintoneカスタマイズで動かしてみようかと思ったんですが、節子……それkintoneネタやない、ただのJavascriptや、と突っ込まれそうだったので踏みとどまりましたｗ

#Closure Compiler について
[Closure Compiler](https://developers.google.com/closure/compiler/)をいちおう説明させていただくと、Googleが提供しているJavascriptコードの最適化ツールです。難読化とか言われていたりもしますが、それは副次的な効果でしてminifyを行うツールです。
[サイボウズエンジニアのブログ](https://blog.cybozu.io/entry/502)でも触れてますね。こちらは2012年とちょっと古い記事ですが。

#3つの最適化レベル
このツールには3つのレベル。最適化モードというべきものがあります。

* WHITESPACE_ONLY
* SIMPLE_OPTIMIZATIONS
* ADVANCED_OPTIMIZATIONS

です。
百聞は一見にしかずで、テスト用のコードを用意しました。単に「fuga」と2回出力するだけのJavascriptです。

``` javascript
var obj = { hoge: "fuga" };

function output (_obj){
  console.log( _obj.hoge );
  console.log( _obj["hoge"] ); //プロパティの参照方法が一意じゃない状態
}

//ここで出力
output(obj);
```
Closure Compiler をお試しできるサービスがあるので、実際に試してみてください。
[Closure Compiler UI](https://closure-compiler.appspot.com/home#code%3D%252F%252F%2520%253D%253DClosureCompiler%253D%253D%250A%252F%252F%2520%2540compilation_level%2520SIMPLE_OPTIMIZATIONS%250A%252F%252F%2520%2540output_file_name%2520default.js%250A%252F%252F%2520%2540formatting%2520pretty_print%250A%252F%252F%2520%253D%253D%252FClosureCompiler%253D%253D%250A%250A%252F%252F%2520ADD%2520YOUR%2520CODE%2520HERE%250A%250Avar%2520obj%2520%253D%2520%257B%2520hoge%253A%2520%2522fuga%2522%2520%257D%253B%250A%250Afunction%2520output%2520(_obj)%257B%250A%2520%2520console.log(%2520_obj.hoge%2520)%253B%250A%2520%2520console.log(%2520_obj%255B%2522hoge%2522%255D%2520)%253B%2520%252F%252F%25E3%2583%2597%25E3%2583%25AD%25E3%2583%2591%25E3%2583%2586%25E3%2582%25A3%25E3%2581%25AE%25E5%258F%2582%25E7%2585%25A7%25E6%2596%25B9%25E6%25B3%2595%25E3%2581%258C%25E4%25B8%2580%25E6%2584%258F%25E3%2581%2598%25E3%2582%2583%25E3%2581%25AA%25E3%2581%2584%25E7%258A%25B6%25E6%2585%258B%250A%257D%250A%250A%252F%252F%25E3%2581%2593%25E3%2581%2593%25E3%2581%25A7%25E5%2587%25BA%25E5%258A%259B%250Aoutput(obj)%253B)（サンプルのコードが入力された状態で開きます）

__"Whitespace only"、"Simple"、"Advanced"__ を選択できますのでそれぞれのレベルで __Complie__ を実行してみると最適化結果の違いがざっくりわかります。__Pretty print__ モードにしてありますのでインデントと改行を除去していないのでわかりやすいかと思います。

で、各レベルの最適化内容はこんな感じ。

__WHITESPACE_ONLY__
コメント、スペースや改行、不要な括弧やセミコロンなどを除去します。

__SIMPLE_OPTIMIZATIONS__
WHITESPACE_ONLYに加えて、ローカル変数やfunctionのパラメータが短い名前に短縮されます。このレベルがClosure Comiplerのデフォルトです。

__ADVANCED_OPTIMIZATIONS__
より積極的に、極端に最適化圧縮を行います。
一度もコールされないfunctionなど動作に影響しない部分はカットされるし、functionである必要がないものは中身が展開されたりします。静的なパラメータ以外はほぼ全て名前が短縮されます。

#ADVANCEDレベルで気をつけること
ここからようやく本題でございます。kintoneの話に絡めていきますよ。

やはりClosure CompilerはADVANCEDレベルを使ってこそだと思うのですが、それなりに壁があるのですね。
ADVANCEDレベルで最適化すると変数名やfunction名やオブジェクトプロパティ名が短縮されます。
ということは、グローバルに公開したいライブラリを__作る時__、ライブラリを__使う時__にもひと工夫必要というわけです。もちろんkintoneオブジェクトのいろいろなプロパティを使う時もです。

__kintone.events.on__ とかなにもしないで最適化したら __kintone.a.b__ とかに変わっちゃいますからね。
__window["kintone"]["events"]["on"]__ と書けば最適化しても大丈夫ですがそれはさすがにダサい。エレガントじゃないです。

短縮したくない名前をClosure Compilerに教える手段が必要。それが __externs__ というわけです。
短縮したくない名前を列挙して定義しておけばADVANCEDレベルで最適化を行っても元の名前が維持されるのです。素晴らしい。

#externsです
そしてその肝心のexterns定義ファイルですが、きっとどこかにkintoneのオフィシャル的なexternsが公開されているに違いない……と思いつつ探しても見つかりません。仕方ないので潔く諦めて[API仕様](https://developer.cybozu.io/hc/ja/articles/360000313406)とかいろいろ見つつ書いたものを公開します、というわけです。
実際に開発で使っているものなので、間違ってるとか足りない箇所とかあったら困るのですが、なにぶん私が書いたやつです。もしかしたらそういうこともあるかもしれません。使用には十分ご注意を。

あとfunctionの@paramタグなんかはほとんど書いてないのでタイプセーフのチェックに使うには足りない感じです。このへんの手抜きも……とりあえずご容赦ください m(__)m

---

というわけではい、これだドン。長いから折りたたみにしました。
<details><summary>__kintoneのexterns__</summary><div>

```javascript:kintone.externs

/** @type {object} */
window.kintone = {}

/** @type {function} */
window.kintone.getRequestToken = function(){};
/** @type {function} */
window.kintone.getLoginUser = function(){};
/** @type {function} */
window.kintone.getUiVersion = function(){};

/** @type {object} */
window.kintone.app ={};
/** @type {function} */
window.kintone.app.getFieldElements = function(){};
/** @type {function} */
window.kintone.app.getHeaderMenuSpaceElement = function(){};
/** @type {function} */
window.kintone.app.getHeaderSpaceElement = function(){};
/** @type {function} */
window.kintone.app.getId = function(){};
/** @type {function} */
window.kintone.app.getLookupTargetAppId = function(){};
/** @type {function} */
window.kintone.app.getQuery = function(){};
/** @type {function} */
window.kintone.app.getQueryCondition = function(){};
/** @type {function} */
window.kintone.app.getRelatedRecordsTargetAppId = function(){};
/** @type {object} */
window.kintone.app.record = {};
/** @type {function} */
window.kintone.app.record.getSpaceElement = function(){};
/** @type {function} */
window.kintone.app.record.getId = function(){};
/** @type {function} */
window.kintone.app.record.get = function(){};
/** @type {function} */
window.kintone.app.record.set = function(){};
/** @type {function} */
window.kintone.app.record.getFieldElement = function(){};
/** @type {function} */
window.kintone.app.record.getHeaderMenuSpaceElement = function(){};
/** @type {function} */
window.kintone.app.record.setFieldShown = function(){};
/** @type {function} */
window.kintone.app.record.setGroupFieldOpen = function(){};

/** @type {object} */
window.kintone.mobile = {};
/** @type {object} */
window.kintone.mobile.app = {};
/** @type {function} */
window.kintone.mobile.app.getHeaderSpaceElement = function(){};
/** @type {object} */
window.kintone.mobile.record = {};
/** @type {function} */
window.kintone.mobile.record.get = function(){};
/** @type {function} */
window.kintone.mobile.record.set = function(){};
/** @type {function} */
window.kintone.mobile.record.setFieldShown = function(){};
/** @type {function} */
window.kintone.mobile.record.setGroupFieldOpen = function(){};

/** @type {object} */
window.kintone.oauth = {};
/** @type {function} */
window.kintone.oauth.clearAccessToken = function(){};
/** @type {function} */
window.kintone.oauth.hasAccessToken = function(){};
/** @type {function} */
window.kintone.oauth.proxy = function(){};
/** @type {function} */
window.kintone.oauth.redirectToAuthenticate = function(){};

/** @type {object} */
window.kintone.plugin = {};
/** @type {object} */
window.kintone.plugin.app = {};
/** @type {object} */
window.kintone.plugin.app.getConfig = function(){};
/** @type {object} */
window.kintone.plugin.app.setConfig = function(){};
/** @type {object} */
window.kintone.plugin.app.getProxyConfig = function(){};
/** @type {object} */
window.kintone.plugin.app.setProxyConfig = function(){};
/** @type {object} */
window.kintone.plugin.app.proxy = function(){};
/** @type {object} */
window.kintone.proxy = function(){};

/** @type {string} */
window.kintone.$PLUGIN_ID;





// -----------------------------------------------------------------
// 共通管理情報
// -----------------------------------------------------------------
/**
* ユーザー情報
* kintone.api('/k/v1/users', "GET") などで 取得。
* @typedef {object}
*/
var protoUserInfo = {
  /** @type {string} */
  code: "",
  /** @type {string} */
  name: "",
  /** @type {string} */
  email: "",
  /** @type {string} */
  givenName: "",
  /** @type {string} */
  givenNameReading: "",
  /** @type {string} */
  surName: "",
  /** @type {string} */
  surNameReading: "",
  /** @type {string} */
  description: "",
  /** @type {Date} */
  birthDate: null,
  /** @type {string} */
  callto: "",
  /** @type {string} */
  ctime: "",
  /** @type {Array.<string>} */
  customItemValues: [],
  /** @type {string} */
  employeeNumber: "",
  /** @type {string} */
  extensionNumber: "",
  /** @type {string} */
  id: "",
  /** @type {Date} */
  joinDate: null,
  /** @type {string} */
  localName: "",
  /** @type {string} */
  localNameLocale: "",
  /** @type {string} */
  locale: "",
  /** @type {string} */
  mobilePhone: "",
  /** @type {string} */
  mtime: "",
  /** @type {string} */
  phone: "",
  /** @type {string} */
  primaryOrganization: "",
  /** @type {string} */
  sortOrder: null,
  /** @type {string} */
  timezone: "",
  /** @type {string} */
  url: "",
  /** @type {string} */
  valid: true
};
/**
* 組織情報のプロパティ
* kintone.api('/k/v1/organizations', "GET", ...) などで 取得。
* @typedef {object}
*/
var protoOrgInfo = {
  /** @type {string} */
  id: "",
  /** @type {string} */
  code: "",
  /** @type {string} */
  description: "",
  /** @type {string} */
  name: "",
  /**
   * 上位組織コード名
   * @type {string}
   */
  parentCode: "",
  /** @type {string} */
  localName: "",
  /** @type {string} */
  localNameLocale: "ja"
};
/**
* グループ情報のプロパティ
* kintone.api('/v1/groups', "GET", ...) などで 取得。
* @typedef {object}
*/
var protoGroupInfo = {
  /** @type {string} */
  id: "",
  /** @type {string} */
  code: "",
  /** @type {string} */
  description: "",
  /** @type {string} */
  name: ""
};


// -----------------------------------------------------------------
// 共通データ型
// -----------------------------------------------------------------
/**
 * レコード - フィールドvalue - ユーザー・組織・グループを表す値
 * @typedef {object}
 */
var protoNamedValue = {
    /** @type {string} */
    code: "",
    /** @type {string} */
    name: ""
};


// -----------------------------------------------------------------
// アプリ情報
// -----------------------------------------------------------------
/**
 * ラジオボタン等の選択肢の設定
 * @typedef {object}
 */
var protoOptionNode = {
  /** @type {string} */
  label:"",
  /** @type {string} */
  index:""
}
/**
 * 選択肢のユーザー・グループ・組織を表すデータ
 * @typedef {object}
 */
var protoEntityNode = {
    /** @type {string} */
    code:"",
    /** @type {string} */
    type:""
}
/**
 * ルックアップフィールドの「ほかのフィールドのコピー」の設定を表す配列要素
 * @typedef {object}
 */
var protoLookupFieldMappingNode = {
    /** @type {string} */
    field: "",
    /** @type {string} */
    relatedField: ""
};
/**
 * フィールド一覧 - フィールド詳細情報
 * @typedef {object}
 */
var protoFieldInfo = {
    /** @type {string} */
    revision: "",
    /** @type {string} */
    label: "",
    /** @type {string} */
    code: "",
    /** @type {string} */
    type: "",
    /** @type {boolean} */
    noLabel: false,
    /** @type {boolean} */
    required: true,
    /** @type {boolean} */
    unique: false,
    /** @type {string} */
    maxValue: "",
    /** @type {string} */
    minValue: "",
    /** @type {string} */
    maxLength: "",
    /** @type {string} */
    minLength: "",
    /**
     * 初期値
     * @typedef {(string|Array.<string>)}
     */
    defaultValue: [],
    /** @type {boolean} */
    defaultNowValue: true,
    /**
     * ラジオボタンフィールド等の選択肢
     * @type {Object.<string, protoOptionNode>}
     */
    options: {},
    /**
     * ラジボタン等で選択肢が表示される向き
     * @type {string}
     */
    align: "",
    /** @type {string} */
    expression: "",
    /** @type {boolean} */
    hideExpression: false,
    /** @type {boolean} */
    digit: false,
    /** @type {string} */
    thumbnailSize: "",
    /** @type {string} */
    protocol: "",
    /** @type {string} */
    format: "",
    /** @type {string} */
    displayScale: "",
    /** @type {string} */
    unit: "",
    /** @type {string} */
    unitPosition: "",
    /** @type {Array.<protoEntityNode>} */
    entities: [],
    /**
     * 関連レコード一覧
     * @type {object}
     */
    referenceTable: {
        /** @type {object} */
        relatedApp: {
            /** @type {string} */
            app: "",
            /** @type {string} */
            code: ""
        },
        /** @type {object} */
        condition:{
            /** @type {string} */
            field: "",
            /** @type {string} */
            relatedField: ""
        },
        /** @type {string} */
        filterCond: "",
        /** @type {Array.<string>} */
        displayFields: [],
        /** @type {string} */
        sort: "",
        /** @type {string} */
        size: "",
    },
    /** @type {object} */
    lookup: {
        /** @type {object} */
        relatedApp: {
            /** @type {string} */
            app: "",
            /** @type {string} */
            code: ""
        },
        /** @type {string} */
        relatedKeyField: "",
        /** @type {Array.<protoLookupFieldMappingNode>} */
        fieldMappings: [],
        /** @type {Array.<string>} */
        lookupPickerFields: [],
        /** @type {string} */
        filterCond: "",
        /** @type {string} */
        sort: ""
    },
    /** @type {boolean} */
    openGroup: true,
    /**
     * テーブル内のフィールドを表すオブジェクト
     * @type {{Object.<string, protoFieldInfo>}}
     */
    fields: {},
    /** @type {boolean} */
    enabled: true
};
/**
 * フィールド一覧
 * kintone.api('/k/v1/app/form/fields', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoFieldInfoList = {
  /** @type {string} */
  revision: "",
  /** @type {{Object.<string, protoFieldInfo>}} */
  properties: {},
};



/**
* レイアウト情報 - フィールド
* @typedef {object}
*/
var protoLayoutFieldInfo = {
  /** @type {string} */
  type: "",
  /** @type {string} */
  code: "",
  /** @type {string} */
  label: "",
  /** @type {string} */
  elementId: "",
  /** @type {object} */
  size: {
    /** @type {string} */
    width: "",
    /** @type {string} */
    height: "",
    /** @type {string} */
    innerHeight: ""
  }
};

/**
* レイアウト情報 - フォームの行ごとのレイアウトを表す配列の要素
* @typedef {object}
*/
var protoLayoutRowInfo = {
  /** @type {string} */
  type: "",
  /** @type {string} */
  code: "",
  /** @type {Array.<protoLayoutFieldInfo>} */
  fields: [],
  /** @type {Array.<protoLayoutRowInfo>} */
  layout: []
};

/**
* レイアウト情報
* kintone.api('/k/v1/app/form/layout', "GET", ...) 等で取得される
* @typedef {object}
*/
var protoLayoutInfo = {
  /** @type {string} */
  revision: "",
  /** @type {Array.<protoLayoutRowInfo>} */
  layout: []
};

/**
 * フォームの設計情報
 * kintone.api('/k/v1/form', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoFormDesignField = {
    /** @type {string} */
    label: "",
    /** @type {string} */
    code: "",
    /** @type {string} */
    elementId: "",
    /** @type {string} */
    type: "",
    /** @type {boolean} */
    noLabel: false,
    /** @type {boolean} */
    required: false,
    /** @type {boolean} */
    unique: false,
    /** @type {number|string|null} */
    maxValue: 0,
    /** @type {number|string|null} */
    minValue: 0,
    /** @type {number|string|null} */
    maxLength: 0,
    /** @type {number|string|null} */
    minLength: 0,
    /** @type {string|{Array.<string>}} */
    defaultValue: "",
    /** @type {string} */
    defaultExpression: "",
    /** @type {Array.<string>} */
    options: [],
    /** @type {string} */
    expression: "",
    /** @type {string} */
    digit: "",
    /** @type {string} */
    protocol: "",
    /** @type {string} */
    format: "",
    /** @type {string} */
    displayScale: "",
    /** @type {string} */
    hideExpression: "",
    /** @type {string} */
    relatedApp: "",
    /** @type {Array.<protoFormDesignField>} */
    fields: [],
    /** @type {string} */
    unit: "",
    /** @type {string} */
    unitPosition: ""
};

/**
 * 一覧の設定
 * kintone.api('/k/v1/app/views', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoViews = {
    /** @type {string} */
    type: "",
    /** @type {string} */
    builtinType: "",
    /** @type {string} */
    name: "",
    /** @type {string} */
    id: "",
    /** @type {Array.<string>} */
    fields: [],
    /** @type {string} */
    date: "",
    /** @type {string} */
    title: "",
    /** @type {string} */
    html: "",
    /** @type {boolean} */
    pager: true,
    /** @type {string} */
    device: "",
    /** @type {string} */
    filterCond: "",
    /** @type {string} */
    sort: "",
    /** @type {string} */
    index: "",
};

/**
 * アプリ情報
 * kintone.api('/k/v1/app', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoApp = {
    /** @type {string} */
    appId: "",
    /** @type {string} */
    code: "",
    /** @type {string} */
    name: "",
    /** @type {string} */
    description: "",
    /** @type {string} */
    spaceId: "",
    /** @type {string} */
    threadId: "",
    /** @type {string} */
    createdAt: "",
    /** @type {protoNamedValue} */
    creator: {},
    /** @type {string} */
    modifiedAt: "",
    /** @type {protoNamedValue} */
    modifier: {}
};

/**
 * 一般設定
 * kintone.api('/k/v1/app/settings', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoCommonSettings = {
    revision: "",
    name: "",
    description: "",
    icon: {
        type: "",
        key: "",
        file: {
            name: "",
            contentType: "",
            size: "",
            fileKey: ""
        }
    },
    theme: "",

};

/**
 * プロセス管理の設定 - ステータス - ステータスの作業者のユーザー情報を表すオブジェクト
 * @typedef {object}
 */
var protoProcessStatesAssigneeEntity = {
    /** @type {object} */
    entity: {
        /** @type {string} */
        type: "",
        /** @type {string} */
        code: ""
    },
    /** @type {boolean} */
    includeSubs: false
};
/**
 * プロセス管理の設定 - ステータス
 * @typedef {object}
 */
var protoProcessStates = {
    /** @type {string} */
    name: "",
    /** @type {string} */
    index: "",
    /** @type {object} */
    assignee: {
        /** @type {string} */
        type: "",
        /** @type {Array.<protoProcessStatesAssigneeEntity>} */
        entities: []
    },

};
/**
 * プロセス管理の設定 - アクションの情報
 * @typedef {object}
 */
var protoProcessAction = {
    /** @type {string} */
    name: "",
    /** @type {string} */
    from: "",
    /** @type {string} */
    to: "",
    /** @type {string} */
    filterCond: ""
}
/**
 * プロセス管理の設定
 * kintone.api('/k/v1/app/states', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoProcess = {
    /** @type {string} */
    revision: "",
    /** @type {boolean} */
    enable: true,
    /** @type {Object.<string, protoProcessStates>} */
    states: {},
    /** @type {Array.<protoProcessAction>} */
    actions: []
};

/**
 * Javascript/CSS カスタマイズの設定
 * kintone.api('/k/v1/app/customize', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoCustomizeFile = {
    /** @type {string} */
    type: "",
    /** @type {string} */
    url: "",
    /** @type {object} */
    file: {
        /** @type {string} */
        contentType: "",
        /** @type {string} */
        fileKey: "",
        /** @type {string} */
        name: "",
        /** @type {string} */
        size: ""
    }
};
/**
 * Javascript/CSS カスタマイズの設定
 * kintone.api('/k/v1/app/customize', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoCustomize = {
    /** @type {string} */
    revision: "",
    /** @type {string} */
    scope: "",
    /** @type {object} */
    desktop: {
        /** @type {Array.<protoCustomizeFile>} */
        js: [],
        /** @type {Array.<protoCustomizeFile>} */
        css: []
    },
    /** @type {object} */
    mobile: {
        /** @type {Array.<protoCustomizeFile>} */
        js: [],
    }
};

/**
 * アプリ・レコード・フィルドのアクセス権の設定の対象を表すオブジェクト
 */
var protoAclRightEntity = {
    /** @type {object} */
    entity: {
        /** @type {string} */
        type: "",
        /** @type {string} */
        code: "",
        /** @type {boolean} */
        includeSubs: false
    },
    /** @type {boolean} */
    includeSubs: false,
    /** @type {boolean} */
    appEditable: false,
    /** @type {boolean} */
    recordViewable: true,
    /** @type {boolean} */
    recordAddable: true,
    /** @type {boolean} */
    recordEditable: true,
    /** @type {boolean} */
    recordDeletable: false,
    /** @type {boolean} */
    recordImportable: false,
    /** @type {boolean} */
    recordExportable: false,

    /** @type {boolean} */
    viewable: true,
    /** @type {boolean} */
    editable: true,
    /** @type {boolean} */
    deletable: true,
    /** @type {boolean} */
    includeSubs: false,

    /** @type {string} */
    accessibility: "",
};
/**
 * レコード・フィールドのアクセス権 - レコードの条件とアクセス権の設定対象を表す配列
 */
var protoAclSpecifyRights = {
    /** @type {string} */
    filterCond: "",
    /** @type {string} */
    code: "",
    /** @type {Array.<protoAclRightEntity>} */
    entities: []
};

/**
 * 実行ユーザーのレコードのアクセス権
 * kintone.api('/k/v1/records/acl/evaluate', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoAclUserFieldRights = {
    /** @type {boolean} */
    viewable: true,
    /** @type {boolean} */
    editable: true
}
/**
 * 実行ユーザーのレコードのアクセス権
 * kintone.api('/k/v1/records/acl/evaluate', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoAclUserRights = {
    /** @type {string} */
    id:"",
    /** @type {object} */
    record: {
        /** @type {boolean} */
        viewable: true,
        /** @type {boolean} */
        editable: true,
        /** @type {boolean} */
        deletable: true
    },
    /** @type {Object.<string, protoAclUserFieldRights>} */
    fields: {}
};
/**
 * アプリ・レコード・実行ユーザーのレコード のアクセス権
 * kintone.api('/k/v1/app/acl', "GET", ...) 等で取得される
 * kintone.api('/k/v1/record/acl', "GET", ...) 等で取得される
 * kintone.api('/k/v1/field/acl', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoAcl = {
    /** @type {string} */
    revision: "",
    /** @type {{Array.<protoAclRightEntity>}|{Array.<protoAclSpecifyRights>|{Array.<protoAclUserRights>}}} */
    rights: [],
};

/**
 * アプリ設定の反映状況
 * kintone.api('/k/v1/preview/app/deploy', "GET", ...) 等で取得される
 * @typedef {object}
 */
var protoDeploy = {
    /** @type {string} */
    app: "",
    /** @type {string} */
    status: ""
};

/**
* REST API 取得データ
* @typedef {object}
*/
var protoRestApiResponse = {
    /** @type {string} */
    revision: "",
    /** @type {{Object.<string, protoGroupInfo|protoOrgInfo|protoUserInfo|protoFieldInfo|protoLayoutInfo>}|{Array.<protoFormDesignField>}} */
    properties: {},
    /** @type {Object.<string, protoViews>} */
    views: {},
    /** @type {{Array.<protoApp>}|{Array.<protoDeploy>}} */
    apps: []
};






// -----------------------------------------------------------------
// フィールドvalueの定義
// -----------------------------------------------------------------
/**
 * レコード - テーブルフィールド行に配置されたフィールドのデータ
 * @typedef {object}
 */
var protoRecordFieldTableRowValue = {
    /** @type {string} */
    type: "",
    /** @type {string} */
    error: "",
    /** @type {boolean} */
    lookup: false,
    /**
     * @type {string|Array.<string>|protoNamedValue|Array.<protoNamedValue>}
     */
    value: ""
}
/**
 * レコード - テーブルフィールド行
 * @typedef {object}
 */
var protoRecordFieldTableRow = {
    /** @type {string} */
    id: "",
    /** @type {Object.<string, protoRecordFieldTableRowValue>} */
    value: {}
};
/**
 * レコードのフィールドデータ
 * @typedef {object}
 */
var protoRecordField = {
    /** @type {string} */
    type: "",
    /** @type {string} */
    error: "",
    /** @type {boolean} */
    lookup: false,
    /**
     * @type {string|Array.<string>|protoNamedValue|Array.<protoNamedValue>|Array.<protoRecordFieldTableRow>}
     */
    value: ""
};

/**
 * レコード1件
 * @typedef {object}
 */
var protoRecord = {
    /** @type {Object.<string, protoRecordField>} */
    record: {
        /** @type {protoRecordField} */
        $id: {},
        /** @type {protoRecordField} */
        $revision: {}
    }
};






// -----------------------------------------------------------------
// イベント情報
// -----------------------------------------------------------------
/**
* フィールド編集時のオブジェクト型
* @typedef {object}
*/
var protoChangeEventProperties = {
  /** @type {protoRecordField} */
  field:{},
  /** @type {protoRecordFieldTableRow} */
  row:{}
};
/**
 * レコード詳細画面
 * @typedef {object}
 */
var protoEventProperties = {
    /** @type {number} */
    appId: 0,
    /** @type {number} */
    recordId: 0,
    /** @type {protoRecord} */
    record: {},
    /**
     * 実行したアクション
     * @type {object}
     */
    action: {},
    /**
     * 変更前のステータス
     * @type {object}
     */
    status: {},
    /**
     * 変更後のステータス
     * @type {object}
     */
    nextStatus: {},
    /** @type {boolean} */
    reuse: false,
    /** @type {protoChangeEventProperties} */
    changes: {},
    /** @type {string} */
    viewType: "",
    /** @type {number} */
    viewId: 0,
    /** @type {string} */
    viewName: "",
    /** @type {{Array.<protoRecord>}|{Object.<string, protoRecord>}} */
    records: {},
    /** @type {number} */
    offset: 0,
    /** @type {number} */
    size: 0,
    /** @type {string} */
    date: "",
    /** @type {string} */
    error: "",
    /** @type {string} */
    type: ""
};

/**
* @typedef {object}
*/
var protoApiError = {
  /** @type {string} */
  code: "",
  /** @type {object} */
  errors: {
    /** @type {object} */
    app: {
        /** @type {Array.<string>} */
        messages: []
    }
  },
  /** @type {string} */
  id: "",
  /** @type {string} */
  message: ""
};

/**
* @type {function}
* @param {string} url
* @param {string} method
* @param {object} body
* @param {function({protoRestApiResponse|protoApp|protoCommonSettings|protoProcess|protoCustomize|protoAcl}):{}} success
* @param {function(protoApiError):{}} error
*/
window.kintone.api = function(url, method, body, success, error){}

/**
* @type {function}
* @constructor
*/
window.kintone.Promise = function(resolve, reject){}
/** @type {object} */
window.kintone.Promise.prototype = {
  /**
   * @type {function}
   * @param success {function({protoRestApiResponse|protoApp|protoCommonSettings|protoProcess|protoCustomize|protoAcl}):{}} succcess
   * @param error {function(protoApiError):{}} error
   */
  then: function(success, error){},
  /**
   * @type {function}
   * @param error {function(protoApiError):{}} error
   */
  catch: function(error){},
  /**
   * @type {function}
   * @param {function} callback
   */
  finally: function(callback){}
};
/** @type {function} */
window.kintone.Promise.all = function(){}
/** @type {function} */
window.kintone.Promise.race = function(){}
/** @type {function} */
window.kintone.Promise.reject = function(){}
/** @type {function} */
window.kintone.Promise.resolve = function(){}

/** @type {object} */
window.kintone.events = {};
/**
* @type {function}
* @param {string|Array.<string>} event
* @param {function(protoEventProperties):{}} handler
*/
window.kintone.events.on = function(event, handler){};
/**
* @type {function}
* @param {string|Array.<string>} event
* @param {function(protoEventProperties):{}} handler
*/
window.kintone.events.off = function(event, handler){};
```
</div></details>

---

実際にClosure Compilerを使う方法は……それはそれでまたボリューム感のある話題なので割愛します。
・[Closure Compiler チュートリアル](https://developers.google.com/closure/compiler/)
・[Closure Compilerを使う！](https://www37.atwiki.jp/aias-closurecompiler/pages/16.html)
とかご参考ください。JAVAの実行環境を用意して compiler.jar を使うのがやはり一般的ですかね。

本題は以上です！

#宣伝
そしてそしてちょっと宣伝させてくださいまし。
Spicaでは[__機能拡張スタンダードAll-In__](https://spica.tokyo/bluelaunch)というプラグインを販売しております。

いま決裁サービスが停止しててトライアルができるのみですが……orz　そのせいで代替システムができるまでトライアル期間を延長してますので微妙にお得です。

kintoneの「ちょっとここが足りないな」というのを全部入れてしまえ、という完成する日が来なさそうなプロジェクトとなっています。全部乗せなら機能間の細かい連携もできる競合もないじゃないか、という利点もあります。
__自動計算__とか__条件書式__とか__フィールド非表示やら編集不可__やら__ルックアップのあれこれ__とかをはじめ、レコードの__一括更新__とか__かんたん検索ボックス__とか__PDF出力__とか他にもいろいろ節操無い感じでモリモリ機能を追加しております。Compilerにかける前のナマのコード量が3万行を超えたくらいのボリューム感のプラグインです。ぜひ一度お試しを。

ほんとに売る気あるのかってくらいwebサイトがしょっぱいですが……最初って大事ですねｗ　もっと頑張る。
そんな感じでーす！