Archivo de la categoría: Javascript

Notes on using JSDuck for javascript API documentation

I’ve been using JSDuck to generate log4js-ext documentation, and I’m quite happy with it.

It allowed me to generate the log4js-ext API documentation in a day, and I had to learn how to use it and decide some baseline practices in that time.

These notes are relative to JSDuck 3.11.0, and include some practices, doubts and precautions I consider relevant.

BTW, the list of tags JSDuck supports is here.

Important practices -at least to me!

  • Data types: this is key, because javascript is not type safe and sometimes the type is not evident

    • Always specify the type for parameters, return values and properties.
      This is usually done writing {xxx} somewhere, where xxx is the type (String, Array, Sm.log.Logger,etc.).
    • If something can have several types, use this notation: {String/Sm.log.Logger}, with ‘/’ separating several alternative types.
    • If you want to refer to the special arguments variable, you can define its type as {Arguments}.
    • Always add @returns {void} to functions that returns nothing. Else, you never know whether there is no type because the function returns nothing, or you forgot to specify it.

  • Statics:

    • You must always add the @static tag by hand, even though you are commenting something in statics: else, the static member will appear as an instance member.
    • You must specify @property explicitly for statics, or they will appear as a global, instead of appearing as part of a class. Bug?

  • If you are using the config thing in ExtJs based code:

    • You must explicitly add the @cfg tag, or else the item will be signaled as a property, not a config.
    • To get getter/setter doc you must add the @accessor explicitly.
      Unfortunately, if a config entry is not marked with @cfg, there is no documentation generated for getters/setters.
    • If you have an item for which you don’t want a setter, create documentation for the setter and make it private.
      As explained below, you can create documentation for an item even if you don’t declare it explicitly.
    • Mark the config as required if needed: you do this by writing something like @cfg (required)

  • Always add the @readonly tag where appropriate. It is very important because this tells you not to juggle with something.

  • Always specify the @protected and @private tags. Because in javascript everything is public, your only way to know about this is the documentation.

  • Always specify the @abstract tag where appropriate. This will help implementors of derived clases quite a lot.

  • In a @returns tag, do not start comment in one line and continue in another, or it will not be considered part of the return comment. You can start the comment in the second line, placing the type in the first one.

  • To hide a class to documenation, make it @private.

  • In javascript you can always add a new property or function as you see fit, but, how do you document something you add in the middle of some function?
    You can document things that are not declared explicitly like this:

       /**
         * @cfg {String} name (required)
         * A very importand config option that must be specified.
         */
    

    I have to confess that it feels a bit strange to have comments for a property that is not there, and that I feel better if I provide the property itself, if at all possible. But I have to consider what’s worse, a ‘lonely’ comment or a comment with a property initialized to a fake and posibly wrong value.

Doubts

There are some things I’m not sure how to handle:

  • I’m not sure about how to specify an enumerated type (WARNING, INFO, …).
    Maybe the best way is to create a class just for that enumeration, and refer to that class as the type.
    I think this is good style, too, so everything should be ok with this approach.
  • I’m not sure about how to specify optional arguments: for example, in log4js-ext a logger’s logging methods can receive from 1 to n arguments, with very different purpose. I have not decided on a way to document that, beyond providing a link to examples.
  • I miss an additional visibility qualifier, to designate those methods visible only inside a subsystem (package, library, a group of classes…).
    Right now I just mark these as private, and add an extra ‘Package’ comment there (which is not visible in the API documentation, unfortunately).
  • I’m torn about when to make something private, and when not to document it.
    This being javascript, I feel that documenting a private method to the public is almost like telling them that they might use it if they are careful enough, and that to make it really private I should just avoid documenting it.

Huh?

And there are some things that make me wonder if I’m missing something…

  • The @template tag, does it really make sense to use it? Its meaning, to me, is equivalent to implying that a certain function is overridable, but, is not that the case for functions,
    by default?

Checking everything is ok

Once you’ve generated API doc, you’ll want to:

  • Take a look at warnings -though JSDuck generates a lot.
  • Look for the ‘unknown type’ string in JSDuck output!
  • Take a look at the API doc page with all your classes, and look for spurious globals.
  • In some cases, I’ve got warnings about missing images…that were there.
    When checking warnings, make sure that they are for real.
  • One of these days I will create doc that includes the ExtJs doc itself, so that the user can check whatever he wants *and* I do not get a thousand ‘class not found’ messages when generating the doc.

Minificación + compresión = 90% de ahorro en espacio

Log4js-ext cuenta con una docena de clases javascript, algunos “mods” a clases de ejemplo o de terceros, media docena de archivos de tests para JsTestDriver (también javascript), y varios archivos .css, con lo que la librería se va a casi dos docenas de archivos.

Hace unos días decidí publicar la versión 0.9 de log4js-ext. Una vez que uno hace una librería pública empieza a pensar en la conveniencia de pulirla.

Parece importante consolidar tanto los archivos javascript como los .css en un par de archivos, para facilitar su uso. Y minificar esos archivos para optimizar el rendimiento.

Consolidar y minificar javascript

La verdad, ExtJs, librería sobre la que funciona log4js-ext, proporciona una herramienta para consolidar/minificar javascript, pero la documentación es floja, y me ha parecido relativamente complicada.

Entre otras cosas, hay que generar un archivo de configuración que debe listar explícitamente todos los archivos javascript, nada de ‘añádeme todos los *.js en el directorio xyz’.

El problema no es que esto sea incómodo, ¡que lo es!, sino que fácilmente termina por generar defectos: añades un archivo de nada con una docena de constantes y se te olvida incluirlo en el archivo de configuración de la herramienta. Siendo javascript como es de permisivo, ni lo notas hasta varios días más tarde, cuando ya la has liado.

O sea, que al final he pasado de la herramienta de la gente de ExtJs/Sencha y he usado ant más YUI compressor.

Porque a) ant me es familiar, b) ya lo uso en ese proyecto para otras tareas (cuantas menos herramientas, mejor) y c) ant tiene unos mecanismo de inclusión/exclusión de archivos muy potentes, que me permiten incluir en la versión minificada de la librería justo los .js que quiero, excluyendo los de test, ejemplos, etc.

Aquí va la parte de la tarea que genera tanto un .js para desarrollo (para depurar) como un .js minificado (para producción): el primero lo monto a base de concatenar todos los .js, y el segundo usando el YUI Compressor sobre dicho archivo.

<target name="generate.packed-files" 
               description="Generate a single minified .js + a single .css">
   <property name="pack.outputdir" value="${basedir}/workdir/packed-files"/>
   <property name="yui.jar" 
       value="/devenv/lib/java/yuicompressor-2.4.7/yuicompressor-2.4.7.jar"/>

   <delete dir="${pack.outputdir}"/>
   <mkdir dir="${pack.outputdir}"/>

   <!-- Concatenate all .js files -->
   <concat destfile="${pack.outputdir}/${ant.project.name}-all-dev.js" 
         fixlastline="true">
     <fileset dir="${basedir}/WebContent/sm">
       <include name="**/*.js"/>
       <exclude name="**/*Test.js"/>
       <exclude name="**/test/*.js"/>
       <exclude name="**/examples/*.js"/>
     </fileset>

     <fileset dir="${basedir}/WebContent/smthirdparty" >
       <include name="**/*.js"/>
       <exclude name="**/*Test.js"/>
       <exclude name="**/test/*.js"/>
       <exclude name="**/examples/*.js"/>
     </fileset>     
   </concat>
   <echo message=
         "Generated unique dev time file ${pack.outputdir}/${ant.project.name}-all-dev.js"/>

   <!-- Compress resulting .js file -->
   <!-- Use YUI minifier -->
   <exec executable="cmd">
     <arg line="/c java -jar ${yui.jar} ${pack.outputdir}/${ant.project.name}-all-dev.js -o ${pack.outputdir}/${ant.project.name}-all.js"/>
   </exec>
   <echo message="Generated unique minified js file ${pack.outputdir}/${ant.project.name}-all-dev.js"/>
   
   ...

Nótese que concatenar a saco funciona porque el código .js que escribo permite concatenar archivos .js sin problemas: esto no es una limitación impuesta para poder generar un archivo único (eso sería mala cosa), sino que sale de forma natural al aplicar una serie de buenas prácticas que me parece importante aplicar en todo el código javascript en modo “no hola mundo”.

Ejecutando este script genero tanto la versión de desarrollo de la librería (log4js-ext-dev.js) como la versión de producción (log4js-ext.js): la primera me ocupa 96.208 bytes, y la segunda 31.427 bytes.

Por cierto, usando Tomcat como servidor y activando la compresión gzip, el tamaño del archivo final enviado al navegador pasa a ser de unos 9.300 bytes, es decir, un ahorro de espacio con respecto al tamaño original del código de ¡más del 90%!

Consolidar y minificar los .css en un único archivo

Otra cuestión a resolver es la de consolidar los diferentes .css en un único archivo, cómo no también minificado, para lo que uso juicer , una herramienta escrita en Ruby -llamándolo desde ant. Los resultados son también espectaculares.

ExtJs: avoid using the id property -unless you have to

In ExtJs you find youself locating components by identifier all the time, as in

Ext.getCmp( 'myToolbar' );

instead of referencing them like this:

myWindow.myToolbar;

A main cause for this is the fact that in ExtJs you work with config objects all the time, and the real objects that will be created based on that config are not there yet. You have to use some kind of identifier to refer to them.

We often use the id property as the identifier for objects because it is natural to relate id with identifier.

But sometimes using id for this purpose is a bad thing, because the id has a larger meaning. It assigned as the id for the dom object corresponding to this element, or at least as the base for those ids.

This means that the id must be globally unique. Therefore, you’ll get a collision if two different panels have a status bar that happen to have the same id (not uncommon for a large application).

What you want in most cases is for an identifier to be unique in a certain context, not globally unique. You want to have a unique identifier for a button in a window, but don’t care if you have two buttons with the same identifier in different windows, right?

I recommend you use itemId instead of id as the default way to identify ExtJs components. Use id if you are going to perform dom related tasks.

To locate an element by itemId you can use the ComponentQuery , as well as the up, down and child methods in components/containers.

Whereas you used to write

Ext.getCmp( 'myToolbar' );

now you will be best served by something like

myWindow.down( '#myToolbar' );

which will look for a component with the myToolbar itemId somewhere below the myWindow container.