Annotation

Closure Compiler 的檢查、編譯皆基於 JSDoc Annotation,因此撰寫清楚的 Annotation 才能達到最有效的應用。

  1. Closure Compiler 並不完全符合 JSDoc 的規範,請參考 Google 的 Type 說明Google 的 Annotation 說明

Data Type

Feature
  1. 變數, 屬性, 參數, 回傳須以註記以下 Annotation 協助 Closure Compiler 進行檢查; DATA_TYPE 為其類型
    • @type {DATA_TYPE}: 變數, 屬性類型為 DATA_TYPE
    • @const {DATA_TYPE}: 變數, 屬性為 const; 類型為 DATA_TYPE
    • @param {DATA_TYPE}: 參數類型為 DATA_TYPE
    • @returns {DATA_TYPE}: 回傳類型為 DATA_TYPE
    • @enum {DATA_TYPE}: 列舉的值為 DATA_TYPE
Setup
  1. Primitive Type: 為小寫單字 - null, undefined, boolean, number, string
  2. Instance Type: 為字首大寫單字 - Object, Array, Function, Type
    • Object: 內含任意屬性的 Object
    • Object.<DATA_TYPE1, DATA_TYPE2>: key 值類型為 DATA_TYPE1, value 值類型為 DATA_TYPE2 的 Object
    • {key1:DATA_TYPE1}: 內含屬性 key1 的 Object, key1 值為 DATA_TYPE1 類型
    • Array: 內含任意類型的 Array
    • Array.<DATA_TYPE>: 指定類型的 Array
    • Function: 任意參數, 回傳的 Function
    • function(DATA_TYPE1):DATA_TYPE2: 需要 1 個 DATA_TYPE1類型的參數, 且回傳 DATA_TYPE2 的 Instance
    • function(...DATA_TYPE): 能夠傳入任意個 DATA_TYPE 類型的參數
  3. 若可接受複數種類型, 則以 | 分隔兩個 DATA_TYPE
    • @type {DATA_TYPE=}: 等同於 @type {DATA_TYPE|undefined}
      • @typedef@record 不支援此種縮寫
    • @type {?DATA_TYPE}: 等同於 @type {DATA_TYPE|null}
      • Primitive Type 不須另外註記 null 類型
      • @typedef@record 不支援此種縮寫
  4. 若 DATA_TYPE 為較為複雜的 Object, 可以 @typedef@record 定義
    • @typedef: 以 Annotation 及變數宣告定義內含特定屬性的 Object, 可將變數作為 DATA_TYPE 填入 Annotation 使用 - @typedef 無法跨檔案
    • @record: 以 function 及 prototype 定義內含特定屬性的 Object, 可將 function 作為 DATA_TYPE 填入 Annotation 使用
  5. function 中可不填寫的參數需註記為包含 undefined 的複數類型
範例
/** @enum {number} */
const Gender = {
	Female: 0,
	Male: 1,
	None: 3
}

/**
 * @typedef {{firstName:string, lastName:(string|undefined)}}
 */
let Name;

/**
 * @record
 */
function Persona () {}
/** @type {Name} */
Persona.prototype.name;
/** @type {Gender|undefined} */
Persona.prototype.gender;
/** @type {function():string} */
Persona.prototype.hello;

/**
 * @param {Persona} persona
 */
function sayHello (persona) {
	console.log(persona.name.firstName + ": " + persona.hello());
}

let john = {
	name: {
		firstName: "John"
	},
	gender: Gender.Male, //此處不能直接填寫數字 1
	hello: function () {return "您好!";}
}

sayHello(john)

Function

Setup
  1. 須以 @param {DATA_TYPE} NAME 註明參數的類型及名稱, 若無參數怎可省略
    • 若為選填, 則在 DATA_TYPE 需註記可為 undefined: @param {DATA_TYPE|undefined} NAME, @param {DATA_TYPE?} NAME
  2. 須以 @returns {TYPE} 註明回傳的類型, 若無回傳怎可省略
  3. 若為內部使用的 function 需註記 @protected@private

Type (Class)

Setup
  1. 作為 Type 使用的 function 需要註記 @constructor
    • ES6 class 寫法不需要添加此註記
  2. 需要註記 @returns {TYPE}, TYPE 為自身的名稱
    • ES6 class 寫法不需要添加此註記
  3. 若要繼承某 Type, 則需註記 @extends {TYPE}, TYPE 為繼承對象的名稱
    • ES6 class 寫法不需要添加此註記
    • Type 僅能繼承 Type
  4. 若覆寫 Parent Type 的 function, 需註記 @override
  5. constructor 不需註記 @protected@private
範例:
//以下為 ES5 的範例
/**
 * @constructor
 * @param {string} id
 * @param {string=} styleClass
 * @returns {PHComponent}
 */
function PHComponent (id, styleClass) {
	this.id = id;
	this.styleClass = styleClass;
	this.htmlElement = null;
}
PHComponent.prototype.init = function () {
	this.htmlElement = this.doInit();
};
/**
 * @protected
 * @returns {Element}
 */
PHComponent.prototype.doInit = function () {};

/**
 * @constructor
 * @extends {PHComponent}
 * @param {string} id
 * @param {string=} styleClass
 * @returns {PHButton}
 */
function PHButton (id, styleClass) {
	PHComponent.call(this, id, styleClass);
}
/** @override */
PHButton.prototype.doInit = function () {
	return document.createElement('button');
};

Protocol (Interface)

Feature
  1. 應用同個 Protocol, 編譯是會被視為同個 Type
  2. Protocol 所具備的屬性, function API, 皆須被定義在 prototype
  3. prototype 上的屬性皆需註記 @type {DATA_TYPE}, 且屬性不能被實作(賦值)
  4. Protocol.prototype 上的每個屬性皆需被套用的 Type 於 constructor 中賦值
    • constructor 中可以先賦值為 null, 此時屬性的 DATA_TYPE 尚不會被 Closure Compiler 檢查
Setup
  1. 作為 Protocol 使用的 function 需註記 @interface
  2. 套用 Protocol 的 Type 需註記 @implements {PROTOCOL}, PROTOCOL 為 Protocol 的名稱
  3. Protocol 僅能繼承 Protocol, 需註記 @extends {PROTOCOL}
範例:
//以下為 ES5 的範例
/**
 * @interface
 */
function PHComponent () {}
/** @type {string} */
PHComponent.prototype.id;
/** @type {string|undefined} */
PHComponent.prototype.styleClass;
/** @type {Element} */
PHComponent.prototype.htmlElement;
/** @type {Function} */
PHComponent.prototype.init;

/**
 * @constructor
 * @implements {PHComponent}
 * @param {string} id
 * @param {string=} styleClass
 * @returns {PHButton}
 */
function PHButton (id, styleClass) {
	this.id = id;
	this.styleClass = styleClass;
	this.htmlElement = null;
}
/** @override */
PHButton.prototype.init = function () {
	this.htmlElement = document.createElement('button');
};

輸出 Symbols, 避免 Closure Compiler 更動變量/屬性名稱

  • 註記 @export, 並將 base.js 列為 --js 的第一個檔案
  • 使用指令 —-generate_exports true--export_local_property_definitions true

Annotation 順序

/**
 * @override
 * @private ----------------> @protected / @private
 * @constructor ------------> @type / @constructor / @interface
 * @extends {TYPE} ---------> @extends / @implements
 * @param {DATA_TYPE} NAME
 * @returns {TYPE}
 * @export
 */