When running the following code compiled with ADVANCED_OPTIMIZATIONS, I get an unexpected output (for me at least).
/**
* #typedef {{
* version: string,
* api_host: string
* }}
*/
var AppProps;
/**
*
* #param {AppProps} props
*/
window['app'] = function(props) {
props = props || {};
console.log("props.api_host = ", props.api_host);
console.log("props['api_host'] = ", props['api_host']);
};
Result:
props.api_host = undefined
props['api_host'] = http://localhost:8080
Is this expected behavior for compiled object literals? Should I always be using string accessors for Object properties (something I almost never do)?
Any insight to this behavior will be greatly appreciated.
Cheers and thank you!
PS: closure-compiler version is v20191111, also the compiler has 0 knowledge of the object literal being passed in, with the exception of the #typedef. Function invocation happens outside of the compiled code.
PPS: Here is the rewritten code, which explains the output, but not the reasoning.
console.log("props.api_host \x3d ",a.B);
console.log("props['api_host'] \x3d ",a.api_host)
I would not expect closure-compiler to rewrite the first statement as it has, given the provided #typedef. What am I doing wrong here?
After going through the docs and carefully considering my setup, I have found the errors of my ways, and a satisfactory explanation for the results.
Since everything within the compiled code, including my #typedef, is subject to rewriting, what I actually needed was an extern to describe the shape of this externally provided object.
externs.js
/**
* #interface
*/
function AppProps() {}
/**
* #type {string}
*/
AppProps.prototype.version;
/**
* #type {string}
*/
AppProps.prototype.api_host;
app.js
/**
*
* #param {AppProps} props
*/
window['app'] = function(props) {
props = props || {};
console.log("props.api_host = ", props.api_host);
console.log("props['api_host'] = ", props['api_host']);
};
The resulting output now works as expected.
Related
I'm trying to update Annotorious (https://annotorious.github.io/#) to the latest version of Closure / Javascript.
When I compile it with "SIMPLE" optimizations, dot-syntax functions seem to disappear when goog.events.listen callbacks are invoked. Here's an example:
Here's the "main":
goog.provide('annotorious.Annotorious');
...
/**
* The main entrypoint to the application. The Annotorious class is instantiated exactly once,
* and added to the global window object as 'window.anno'. It exposes the external JavaScript API
* and internally manages the 'modules'. (Each module is responsible for one particular media
* type - image, OpenLayers, etc.)
* #constructor
*/
annotorious.Annotorious = function() {
/** #private **/
this._isInitialized = false;
/** #private **/
this._modules = [ new annotorious.mediatypes.image.ImageModule() ];
...
In another file (they're all compiled together), we have this:
goog.provide('annotorious.Annotation');
goog.require('annotorious.shape');
/**
* A 'domain class' implementation of the external annotation interface.
* #param {string} src the source URL of the annotated object
* #param {string} text the annotation text
* #param {annotorious.shape.Shape} shape the annotated fragment shape
* #constructor
*/
annotorious.Annotation = function(src, text, shape) {
this.src = src;
this.text = text;
this.shapes = [ shape ];
this['context'] = document.URL; // Prevents dead code removal
}
So we start up the code, and at some point we end in the editor for the annotation, listening for a "Save" button:
goog.provide('annotorious.Editor');
....
/**
* Annotation edit form.
* #param {Object} annotator reference to the annotator
* #constructor
*/
annotorious.Editor = function(annotator) {
this.element = goog.soy.renderAsElement(annotorious.templates.editform);
....
/** #private **/
//this._btnSave = goog.dom.query('.annotorious-editor-button-save', this.element)[0];
this._btnSave = this.element.querySelector('.annotorious-editor-button-save');
...
goog.events.listen(this._btnSave, goog.events.EventType.CLICK, function(event) {
event.preventDefault();
var annotation = self.getAnnotation();
annotator.addAnnotation(annotation);
annotator.stopSelection();
if (self._original_annotation)
annotator.fireEvent(annotorious.events.EventType.ANNOTATION_UPDATED, annotation, annotator.getItem());
else
annotator.fireEvent(annotorious.events.EventType.ANNOTATION_CREATED, annotation, annotator.getItem());
self.close();
});
If I put a breakpoint at "goog.events.listen(this._btnSave...", and type "annotorious.Annotation", I get what I expect:
In fact, it has all kinds of methods:
Then I let the code go, and break in the listener (event.preventDefault();, etc, above):
Now, all the dot-syntax methods are gone.
This of course causes crashes later on.
All of these files are compiled together.
This happens for all callbacks - 'load' events, other ux callbacks, etc.
This must have worked in prior versions of Closure / JS.
What could be causing this?
Thanks!
Ok, I was able to resolve the problem by adding "--assume_function_wrapper" to the closure compiler command line.
Previously, I was compiling the package using Plovr. However, since Plovr hasn't been updated in a long time, I switched to using the closure compiler natively.
Apparently either Plovr provided '--assume_function_wrapper' internally or the version of the compiler Plovr used didn't break the functions as described.
As #john suggests above I'll bet '--assume_function_wrapper' keeps goog.provide from "destroying" namespaces by re-declaring them.
I have developed a native NodeJS module (using the NAN helpers). Now I am wondering what is the best way to document it.
The methods the module exports exist only in the C++ source code, however I wish to export Javascript documentation.
EDIT: I found another way that I think is better:
All JSDoc needs is to be able to attach the doclet to SOMETHING, so you can actually just do this (if you ignore JSHint warning about expecting an assignment or call instead of expression):
var nativeStuff = require('some/native/stuff');
/**
* My Cool Class
* #class
*/
nativeStuff.MyCoolClass;
/**
* My Cool Method
* #function
* #param {String} [name] The argument
*/
nativeStuff.MyCoolClass.prototype.CoolMethod;
/**
* Some Other Value
* #type {String}
*/
nativeStuff.someStringValue;
module.exports = nativeStuff;
This has the advantage of working with IDE's (at least WebStorm) and not requiring you to copy or proxy the object itself. Note though that you have to explicitly tell it what kind of field each entry is (with #function or #class or #type) because it can't infer it automatically otherwise.
Original Answer
There are a couple ways I know of, though I admit none seem particularly elegant.
In the small adapter file where you require() the native parts of the code and make it available as a module, you can assign each component individually and document them that way:
var nativeStuff = require('some/native/stuff');
// If your module only has one thing
/**
* My Cool Class
* #class MyCoolClass
*/
module.exports = nativeStuff;
// If it has several things
module.exports = {
/**
* My cool class
* #class
*/
MyCoolClass: nativeStuff.MyCoolClass,
/**
* My Other Cool Member
*/
OtherCoolMember: nativeStuff.OtherCoolMember
};
You could document members of the class this way too if you want to pick apart the class and re-assemble it, but it gets kinda unwieldy the deeper you go.
The other way I know of is to wrap every class method in native JS, which comes with a (almost negligible) performance hit:
var nativeStuff = require('some/native/stuff');
/**
* My Cool Class
*/
class MyCoolClass {
constructor() {
this._nativeObj = new nativeStuff.MyCoolClass();
}
/**
* Some Cool Method
* #param {String} [name] My name
*/
coolMethod(name) {
return this._nativeObj(name);
}
}
module.exports = MyCoolClass;
(Note this also works in older JS versions, but ES6 classes make stuff way easier to understand visually :)
You also can try using the #typedef directive to define things since a doclet about a typedef can be separate from the object it describes, but I don't think typedef's can document methods or classes in general, they're just for data objects.
I had to create a simple documentation generator to scan comments in source code in Javadoc-like style: https://github.com/aspectron/gendoc
Here is a part of such documentation, that looks like this:
/**
#module crypto
**/
/**
#class Hash
Hash generator
#function Hash(algorithm)
#param algorithm {String}
Constructor. Initiaize hash generator with specified algorithm, see #getHashes
Throws exception on unknown algorithm
**/
v8pp::class_<hash_generator> hash_class(bindings.isolate(), v8pp::v8_args_ctor);
hash_class
/**
#function reset([algorithm])
#param [algorithm] {String}
#return {Hash}
Reset hash generator to initial state optionaly changing generator algorithm.
Throws exception on unknown algorithm.
**/
.set("reset", &hash_generator::reset_v8)
...
Finally I opted for a not very elegant solution. I have created a separate JavaScript file which only has methods that are exported by my native API.
This file looks like this:
/** #namespace AwesomeLibrary
*
* #description API overview
*/
AwesomeLibrary = {
/**
* #param {string} param Input parameter
* #return combobulates {#link param}
*/
combobulate: function (param) {}
}
Then I am using JsDoc to generate the documentation of the project and passing this JavaScript file as an input rather than my native code. Finally I bundle the documentation with the binary distribution of my module.
This solution is not ideal as the documentation and the source code have to be maintained separately, however it has the advantage of zero overhead and a (rather) clean file. I have also disabled the source code generation in JsDoc as this would be obviously quite useless and only show the empty stubs.
I want to produce a warning for a number if a string is assigned to it. So, I thought typedef of Closure might do this for me. I tried the following -
var Widget = function()
{
/** #typedef {number} */
this.size = null;
};
new Widget().size = "kaboom!"
When I compile it using http://closure-compiler.appspot.com/home it doesn't throw a warning or error. What am I doing wrong? And/or what other tool should I be using?
Turn the optimization up to Advanced in the closure compiler service to catch these warnings. You still won't see any for your example (well, you will see some, but not what you are expecting), because typedefs are used to define custom types. Also, you need to annotate your constructor. Run the following example in advanced mode and you will see your warnings. Instead of making a typedef for a simple thing like number, I would just use #type, but this example is to show you the proper use of typedef.
/** #typedef {number} */
var customType;
/** #constructor */
var Widget = function()
{
/** #type {customType} */
this.size = null;
};
new Widget().size = "kaboom!"
I compile my source with closure compiler and when i call a function that got an event object from network the application throws an error in console.
The function called is:
/**
* #param {goog.events.Event} event Socket.io-Wrapper Event.
*/
de.my.app.admin.prototype.onSaved = function(event){
var category = event.data[0].category; //<-- here it throws the error because category get compiled.
var id = event.data[0].id;
var oldid = event.data[0].oldid;
[...]
}
the event object looks like this
{ data:{
0: {
category: 'someString',
id: 5,
oldid: -5
} }
[...someMoreValuesAddedBySocketIO...]
}
that is the behavior i expected.
now i add an externs declaration like this to my externs file but i didn't alter the type declaration of the #param at the function and the error disappears:
var xterns;
/**
* #typedef {{
* category : string,
* oldid : number,
* id : number
* }}
*/
xterns.NOTUSEDNAME;
/**
* #type {string}
*/
xterns.NOTUSEDNAME.prototype.category;
/**
* #type {number}
*/
xterns.NOTUSEDNAME.prototype.oldid;
/**
* #type {number}
*/
xterns.NOTUSEDNAME.prototype.id;
In short: I have a #param {goog.events.Event} event declaration and an extern for xterns.NOTUSEDNAME solves the compiler problems...
Can anyone explain why this happens?
This is a common misconception. Closure-compiler will not rename a property if any extern object contains a property of the same name. See the FAQ. If the type based optimizations are enabled, then this is no longer true and I would expect your code to break again.
To make this code type safe and compile without warnings, you would need to either:
Reference the data properties using quoted syntax event.data[0]['category']. Your properties will never be renamed by the compiler using this method (which is often used by JSON Data).
Extend the goog.events.Event type with a custom object that defines the data object as a strongly typed array.
Example:
/**
* #constructor
* #extends {goog.events.Event}
*/
de.my.app.AdminEvent = function() {};
goog.inherits(de.my.app.AdminEvent, goog.events.Event);
/** #type {Array.<{category:string, id:number, oldid:number}>} */
de.my.app.AdminEvent.prototype.data = [];
Depending on your exact situation, an interface might be a better option.
I use a external library that uses requirejs I don't know
how its work but FileError is in the global scope in other browser
or in FF8 but in FF 14/15 says FileError not defined.
define(function (require, exports, module) {
"use strict";
var Async = require("utils/Async");
var NativeFileSystem = {
/**
* LOT OF CODE HERE
* ...
* ...
* ...
*/
/** class: FileError
*
* Implementation of HTML file API error code return class. Note that we don't
* actually define the error codes here--we rely on the browser's built-in FileError
* class's constants. In other words, external clients of this API should always
* use FileError.<constant-name>, not NativeFileSystem.FileError.<constant-name>.
*
* #constructor
* #param {number} code The error code to return with this FileError. Must be
* one of the codes defined in the FileError class.
*/
NativeFileSystem.FileError = function (code) {
this.code = code || 0;
};
/**
*THIS FIX THE PROBLEM BUT IT A HACK
*window.FileError = NativeFileSystem.FileError;
*/
// Define public API
exports.NativeFileSystem = NativeFileSystem;
});
Of course if I add window.FileError = NativeFileSystem.FileError; after the
function definition its work fine. But I don't want hack the library
The full source of the file is here
Brackets relies on the FileError class only for the error code constants.
The FileAPI is still a draft spec http://www.w3.org/TR/FileAPI/ and it appears they have changed FileError to DOMError in the time since we originally wrote NativeFileSystem. We could remove this dependency and define our own equivalent FileError constants to remove the dependency. We were attempting to model our API after the FileAPI, but it's been a moving target.