externs for jQuery Star Rating Plugin and Google Closure Compiler - jquery-plugins

I created an externs file to be able to compile the jQuery Star Rating Plugin fyneworks.com/jquery/star-rating/#tab-Testing with Google Closure Compiler's ADVANCED_OPTIMIZATIONS.
But, even though I reference the standard jQuery extern, the '$' is getting renamed which breaks the plugin.
Perhaps related: if I use the unmodified plugin, 'rating' also gets renamed. I can fix that part with:
$.fn['rating'] = function(opts) {
from google closure compile jQuery Plugin ... but that doesn't fix '$' (and it would be nice to use the unmodified plugin if possible).
from my attempt at an extern (which is probably wrong and/or incomplete):
// ??? for '$'
// this one does NOT prevent 'rating' from being renamed
function rating(arg1) {}
// the following seem to work: they prevent the functions from being renamed
rating.focus = function() {}
rating.blur = function() {}
rating.fill = function() {}
... etc.
command line (and rating.sh in the download):
java -jar ../compiler-latest/compiler.jar --formatting pretty_print --compilation_level ADVANCED_OPTIMIZATIONS --externs externs/jquery-1.7.js --externs externs/jquery.rating-extern.js --js original/jquery.rating.js --js_output_file jquery.rating.gcc.js
error messages:
Firefox:
$(".star1").rating is not a function
callback: function (value) {
jquery.ratingSampleCode.js (line 9)
Chrome:
Uncaught TypeError: Object [object Object] has no method 'rating'
jquery.ratingSampleCode.js:8
from my sample code:
$('.star1').rating({
callback: function (value) {
To test: http://prefabsoftware.com/test/rating-july15/
To download: prefabsoftware.com/test/rating-july15.zip
Some useful links: (which I'm not allowed to specify as markdown since I couldn't login with my old reputation points...)
Advanced Compilation and Externs: developers.google.com/closure/compiler/docs/api-tutorial3#externs
sample externs: contrib: code.google.com/p/closure-compiler/source/browse/#svn%2Ftrunk%2Fcontrib%2Fexterns) including jQuery itself, but not the rating plugin
more externs: code.google.com/p/closure-compiler/source/browse/#svn%2Ftrunk%2Fexterns
Is there a simple fix for the extern? Or a better solution?
Thanks!
Ok, this works for the externs file:
$.prototype.rating = function(arg1) {}
jQuery.prototype.rating = function(arg1) {}
$.prototype.rating.focus = function() {}
... etc.

From your description, you appear to be using an extern file improperly. An extern file for your plugin would allow other users to compile code referencing your plugin. It shouldn't be used to compile your actual plugin code at all. To compile your code, you would only need the jQuery extern file.
jQuery code styles have known issues with Closure-compiler. In particular, you would need to avoid the following:
Any use of the $ alias. Use the full jQuery namespace. The compiler doesn't handle aliased namespaces well.
The jQuery.fn alias. Instead use jQuery.prototype.
Use of the jQuery.extend method to add function prototypes or public methods. Instead, add them directly to the prototype. (example: jQuery.fn.extend({a: 'foo'}); would become jQuery.prototype.a = 'foo';);
With ADVANCED_OPTIMIZATIONS, keep in mind that you will still have to export or quote any public methods and prototypes. This may mean that SIMPLE_OPTIMIZATIONS turn out to be a better fit for your project.
For more information, see http://blogs.missouristate.edu/web/2011/02/14/jquery-plugins-and-closure-compiler/

Check out the latest externs: https://github.com/iplabs/closure-compiler/tree/master/contrib/externs

Related

Cypress command vs JS function

The Cypress documentation suggests that commands are the right way to reuse fragments of code, e.g.
Cypress.Commands.add("logout", () => {
cy.get("[data-cy=profile-picture]").click();
cy.contains("Logout").click();
});
cy.logout();
For simple cases like this, why would I use a command over a plain JS function (and all the nice IDE assistance that comes with it). What are the drawbacks of rewriting the above snippet as
export function logout(){
cy.get("[data-cy=profile-picture]").click();
cy.contains("Logout").click();
}
// and now somewhere in a test
logout();
Based on my experience with Cypress (one year project and several hundred test cases), I can say that a plan JS function is great for grouping cy commands.
From my point of view, a custom cy command may be really useful only if it is incorporated into the chain processing (utilizes the subject parameter or returns a Chainable to be used further in the chain). Otherwise, a plain JS function is preferable due to it simplicity and full IDE support (unless you're using an additional plugin).
If you for any reason need to do something inside the cypress loop, you can always wrap you code by cy.then() in a plain JS function:
function myFunction() {
cy.then(() => {
console.log(("I'm inside the Cypress event loop"))
})
}
Commands are for behavior that is needed across all tests. For example, cy.setup or cy.login. Otherwise, use functions.
See official docs: https://docs.cypress.io/api/cypress-api/custom-commands#1-Don-t-make-everything-a-custom-command

Laravel Webpack - Unwanted minification of top level variable

I have a variable in my main javascript file e.g. var example = {};.
After webpack has finished its job, I find that example is now referenced as t. This presents me a problem as I am using the variable across the web project. I bind functions onto objects for example:
var example = {};
example.initialise = function () {};
Finally at the bottom of a page I may invoke this section of script e.g:
<script>example.initialise()</script>
This way of writing javascript functions is not unusual...
This is obviously a huge pain in the ass as I have no control over the minification. Moreover, it appears that webpack doesn't figure out that example.initialise = function () {}; relates to its newly minified var example (becoming)--> var t. I.e. it doesn't become t.initialise = function {}; either.
What am I supposed to do here?
I've tried using rollup as well. The same kind of variable minification happens.
The thing is, this kind of minification/obfuscation is great, particularly on the inner workings of functions where there's little cause for concern over the parameter names. But not on the top level. I do not understand why this is happening, or how to prevent it.
Any ideas?
I assume that there are ways to set the configuration of webpack. E.g. inside webpack.config.js, but my perusing of the webpack docs gives me no easy understanding of what options I can use to resolve this, like preventing property minification in some way.
In laravel-elixir-webpack-official code you can see minify() is being applied here, minify() uses UglifyJS2 and mangling is on by default.
Mangling is an optimisation that reduces names of local variables and functions usually to single-letters (this explains your example object being renamed to t). See the doc here.
I don't see any way you can customize minify() behaviour in laravel-elixir-webpack, so for now you might have to monkey patch WebpackTask.prototype.gulpTask method before using the module (not an ideal solution). See the lines I am commenting out.
const WebpackTask = require('laravel-elixir-webpack-official/dist/WebpackTask').default;
WebpackTask.prototype.gulpTask = function () {
return (
gulp
.src(this.src.path)
.pipe(this.webpack())
.on('error', this.onError())
// .pipe(jsFiles)
// .pipe(this.minify())
// .on('error', this.onError())
// .pipe(jsFiles.restore)
.pipe(this.saveAs(gulp))
.pipe(this.onSuccess())
);
};
Turns out I have been silly. I've discovered that you can prevent top level properties from being minified by binding it to window... which in hindsight is something I've always known and was stupid not to have realised sooner. D'oh!
So all that needed to be done was to change all top-level properties like var example = {}; to something like window.app.example = {}; in which app is helping to namespace and prevent and override anything set by the language itself.

Typescript syntax - How to spy on an Ajax request?

I am trying to write a unit test where I want to verify that a ajax call has been made.
The code is simple :
it('test spycall',()=>{
spyOn($,"ajax");
//my method call which in turns use ajax
MyFunc();
expect($.ajax.calls.mostRecent().args[0]["url"].toEqual("myurl");
});
The error that I get :
Property 'calls' doesn't exist on type '{settings:jqueryAjaxSettings):jQueryXHR;(url:string, settings?:JQueryAjaxSettings}
$.ajax.calls, among others, is part of the Jasmine testing framework, not JQuery itself (As you know, Jasmine (or rather, Jasmine-Jquery, the plugin you're using) is adding certain debugging functions to JQuery's prototype in order to, well, be able to test ajax calls).
The bad part is that your .d.ts typescript definition file, the file that acts as an interface between typescript and pure JS libraries isn't aware of Jasmine's functions.
There are several ways you could approach fixing this, like
looking if someone has adjusted the JQuery .d.ts file for Jasmine's functions or
creating the new .d.ts file yourself by modifying the original one or, (what I would be doing)
overwriting the typescript definition by declaring $.ajax as any, or not including the typescript definition at all in your testing codebase and declaring $ as any.
There are 2 ways to get rid of the error:
// 1
expect(($.ajax as any).calls.mostRecent().args[0].url).toEqual("myurl");
// 2
let ajaxSpy = spyOn($,"ajax");
expect(ajaxSpy.calls.mostRecent().args[0].url).toEqual("myurl");
You can also use partial matching:
expect(($.ajax as any).calls.mostRecent().args).toEqual([
jasmine.objectContaining({url: "myurl"})
]);

ClojureScript optimized compilation requires goog.net.EventType declaration in externs file

The IFrameIo Clojure Library (https://closure-library.googlecode.com/git-history/docs/class_goog_net_IframeIo.html) can be used to facilitate file uploads. One of the more important pieces of the upload process is the event listener that dispatches the success callback once the file has successfully been uploaded:
(goog.events/listen (IframeIo.) (aget goog.net.EventType "SUCCESS") #(success-callback))
This works fine in a local development environment. However, as soon as the code is deployed to a production environment, the success-callback is no longer invoked, even if the upload is successful. After some investigation, it became clear that the compilation process was mangling the keys of the goog.net.EventType object:
Expected:
Object { SUCCESS: function() ..., ERROR: function() ...}
Actual:
Object { az: function() ..., of: function() ...}
The only way to resolve the issue was to create an externs file dependency that would prevent the object from being mangled:
in project.clj:
{:prod
{:compiler
{:optimizations :advanced
:pretty-print false
:externs ["path/to/googNet-EventType.js"]}}}
googNet-EventType.js:
var goog = {}
goog.net = {}
goog.net.EventType = {}
goog.net.EventType.SUCCESS
The compilation process now preserves the SUCCESS attribute of the goog.net.EventType object, thus successfully invoking the callback.
Why is it that a dependency from goog.net would mangle it's own objects?
You do not need externs for this case. Your interaction with Google Closure Library is the cause of the problem. Google Closure Library enums are also subject to minification:
(goog.events/listen (IframeIo.)
goog.net.EventType.SUCCESS #(success-callback))
Is what you want. I would personally use the ns form to alias goog.events to gevents and import EventType so I could write:
(gevents/listen (IframeIo.)
EventType.SUCCESS #(success-callback))
You really should never use aget to access an Object property when using property syntax will do. If for some reason you do need to look up a property by string use goog.object.get.

Cache won't work in Appcelerator

Titanium SDK version: 1.6.
iPhone SDK version: 4.2
I am trying out the cache snippet found on the Appcelerator forum but I get an error: [ERROR] Script Error = Can't find variable: utils at cache.js (line 9).
I put this one (http://pastie.org/1541768) in a file called cache.js and implemented the code from this one (http://pastie.org/pastes/1541787) in the calling script, but I get the error.
What is wrong? I copied the code exactly.
Your problems is whilst the first pastie defines utils.httpcache. The variable utils is not defined outside of this function closure (because it is not defined anywhere in global namespace). As below shows.
(function() {
utils.httpcache = {
};
})();
To make it all work in this instance add the following code to the top of your cache.js file.
var utils = {};
This declares the utils variable in global namespace. Then when the function closure is executed below it will add utils.httpcache to the utils object.
The problem is actually not specific to Appcelerator and is just a simple JavaScript bug. Checkout Douglas Crockfords book, JavaScript the Good Parts. Reading it will literally make you a more awesome JavaScript developer.
You can't use utils.httpcache.getFromCache(url) until you add this to your code:
var utils = {};
That's because how the author created his function, it's called JavaScript module pattern and it's generally used to structure the code.
I seem to lose this value "value.httpCacheExpire = expireTime;" when the code does the "Titanium.App.Properties.setString(key,JSON.stringify(value));" so when I get it back using the getString method, there's no longer the "value.httpCacheExpire.
Anyone else have this issue? Am I missing something to get this working?

Resources