Google Closure presents bugs

EDIT

The lesson learned with @Alex is that you should never place function declarations in the block area. Not that I intended to do this, but if you slip away it can cause big problems.


I have a script file that seems to be improperly compressed using Google Closure. When I run my application with the source code, everything works fine. But when I try to compress it using Google Closure, some errors appear.

I DO NOT use the advanced version; I use default base mode

Obviously, I can't expect anyone to debug a compressed file, but I hope someone can look at the uncompressed code and let me know if I somehow do something insanely stupid to trick Closure.

Some notes about minified code:

Closing the attachment is BEFramework.prototype.hstreamLoad and BEFramework.prototype.hstreamEvalJson and seems to completely remove the helper functions getDeleteValue , getValueToDisplay , getDisplayForLabel and possibly others.

The uncompressed file is below.

This code can be manually compiled by closing here , which should reproduce the symptoms described above.

 (function() { var $ = jQuery; // Load and display the messages ("healthstream") for a given module. // This requires that the module HTML have specific features, see // dashboard.htm and contactsManager/details/default.htm for examples. // This also requires that the `request` support `pageIndex` and `pageSize`, // so we can handle paging. // // Args: `options` An options object with these keys: // `channelId` The channel ID of the module (for transmitRequest) // `translationId` Optional alternate ID for translation (if not given, // `channelId` is used). // `action` The action (for transmitRequest) // - Must support `pageIndex` and `pageSize` // `request` The request (for transmitRequest) // - Must include `pageIndex` and `pageSize` // `complete` Optional callback triggered when the load is complete. // `showOptions` Optional callback if an options menu is supported // by the calling module. Receives a raw event instance // and the item on which the options were triggered: // function showOptions(event, item) // `context` Optional context (`this` value) for the call to // `complete` and/or `showOptions` BEFramework.prototype.hstreamLoad = hstreamLoad; function hstreamLoad(options) { var inst = this; var channelId, translationId, action, request, complete, showOptions, context, pageIndex, pageCount, pageSize, pageCount, btnPrevious, btnNext, dataShownFlags; // Get our arguments (with defaults) channelId = options.channelId; translationId = options.translationId || options.channelId; action = options.action; request = $.extend({}, options.request); // Create a *copy*, because we modify it when doing paging complete = options.complete; if (typeof complete !== "function") { complete = undefined; } showOptions = options.showOptions; if (typeof showOptions !== "function") { showOptions = undefined; } context = options.context; // (undefined will automatically become the global object) // Grab the initial pageIndex and pageSize pageIndex = request.pageIndex || 1; pageSize = request.pageSize || 100; // Disable the button and show "searching" label $('#healthStreamSearchButton') .button("disable") .button("option", "label", BETranslate(translationId, 'HealthStreamSearching')); // Hook up the buttons; be a bit paranoid that they've been hooked before and clear previous handlers btnPrevious = $('#healthStreamPagePrevious'); btnNext = $('#healthStreamPageNext'); btnPrevious.hide().unbind("click.paging").bind("click.paging", goToPreviousPage); btnNext.hide().unbind("click.paging").bind("click.paging", goToNextPage); // Do it doLoad(); // === Support functions // Trigger a load request function doLoad() { request.pageIndex = pageIndex; request.pageSize = pageSize; inst._transport.transmitRequest(channelId, action, request, hstreamLoaded); } // Hndle the load response function hstreamLoaded(objResponse) { var healthStream = objResponse.items; var total = objResponse.total; var tbody = $('#healthStreamList'); // Need to make this update optional $('#pageHeaderName').html(BETranslate(translationId, 'HeaderActivity') + ' (' + String(total) + ')'); $('#healthStreamSearchButton') .button("enable") .button("option", "label", BETranslate(translationId, 'HealthStreamSearch')); tbody.empty(); btnPrevious.hide(); btnNext.hide(); if (healthStream.length > 0) { pageCount = Math.ceil(total / pageSize); if (pageCount > 1) { if (pageIndex > 1) { btnPrevious.show(); } if (pageIndex < pageCount) { btnNext.show(); } } var item; var tr; var tdMain; var daysHash = {}; var creationDate; var key; var today = new Date(); var yesterday = new Date(); var msg; yesterday.setDate(yesterday.getDate() - 1); dataShownFlags = {}; for (var x = 0; x < healthStream.length; x++) { item = healthStream[x]; msg = inst.hstreamEvalJson(item); if (msg.length > 0) { creationDate = new Date(item.CreationDate); key = [creationDate.getYear(), creationDate.getMonth(), creationDate.getDate()].join('-'); if (!daysHash[key]) { if (isDateEqual(creationDate, today)) { addRowHeader(tbody, BETranslate(inst._channelId, 'HSToday')); } else if (isDateEqual(creationDate, yesterday)) { addRowHeader(tbody, BETranslate(inst._channelId, 'HSYesterday')); } else { addRowHeader(tbody, creationDate.toString('MM/dd/yyyy')); } daysHash[key] = true; } tr = $( "<tr>" + "<td class='date' style='white-space:nowrap;'>" + new Date(item.CreationDate).toString('h:mm tt') + "</td>" + "<td class='main'><span class='name'>" + msg + "</span>" + "</tr>" ); tbody.append(tr); if (showOptions) { tr.find("td.main").prepend($("<em rel='opt'>&nbsp;</em>").click(makeShowOptionsHandler(item))); } } } // If any of the templates created links with a `data` attribute, hook them up $('#healthStreamList a[data]').click(showTitle).each(function (index) { this.id = 'data' + index; }); } else { tbody.html('<tr><td colspan="2">' + BETranslate(inst._channelId, 'HSNoActivity') + '</td></tr>'); } // Trigger completion callback if (complete) { complete.call(context, objResponse); } } function makeShowOptionsHandler(item) { // Our event comes to us from jQuery, but we pass on the raw // event to the callback return function (event) { showOptions.call(context, event.originalEvent || event, item); }; } function addRowHeader(listRef, name) { listRef.append( "<tr>" + "<td colspan='2' class='divider'>" + name + "</td>" + "</tr>" ); } function showTitle(event) { $.stopEvent(event); var link = this; var $link = $(this); var href = $link.attr("href"); // We want the attribute, not the property (the property is usually expanded) var hrefTitle = $link.attr('hreftitle') || BETranslate(inst._channelId, 'HSMoreInfo'); var data = $link.attr('data') || ""; var linkId = link.id; if (!dataShownFlags[linkId]) { dataShownFlags[linkId] = true; if (data) { var div = $( "<div class='data'>" + "<span data-linkId='" + linkId + "' class='close'>x</span>" + "<table><thead></thead></table>" + "</div>" ); $link.parent().append(div); var thead = div.find("thead"); var arr = data.split('~'); var splitEntry; for (var x = 0; x < arr.length; x++) { splitEntry = arr[x].split('|'); if (splitEntry[0] === 'Changed length') { splitEntry[1] = splitEntry[1].replace(/\d+/g, BEFramework.prettyTime); } if (splitEntry.length > 1 && splitEntry[1].length > 0) { thead.append( "<tr>" + "<td class='hslabel'>" + splitEntry[0] + ":</td>" + "<td>" + splitEntry[1] + "</td>" + "</tr>" ); } } div.find("span:first").click(hideTitle); if (href && href !== "#") { $("<a target='_blank'>" + hrefTitle + "</a>").attr("href", href).appendTo(div); } } } } function hideTitle(event) { var $this = $(this), linkId = $this.attr("data-linkId"); delete dataShownFlags[linkId]; $this.parent().remove(); return false; } function goToPreviousPage(event) { --pageIndex; doLoad(); return false; } function goToNextPage(event) { ++pageIndex; doLoad(); return false; } } var ___x = false; var __i = 0; BEFramework.prototype.hstreamEvalJson = hstreamEvalJson; function hstreamEvalJson(item) { var inst = this; if (item.Action === 'saveinsurance' && !___x && __i != 0){ var start = +new Date(); __i = 1; } var userId = inst._BEUser ? inst._BEUser.getId() : -1; var json = eval('(' + item.JSON + ')'); var key = 'HS' + item.Module + '_' + item.Action; var msg = BETranslate(inst._channelId, key); var fromIsMe = item.CreatedByContactId == userId; var toIsMe = item.ContactId == userId; var fromString = (fromIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYou') + '</strong>' : '<a class="vcard" contactId="' + item.CreatedByContactId + '">' + item.CreatedByName + '</a>'; var toString = (toIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYour') + '</strong>' : '<a class="vcard" contactId="' + item.ContactId + '">' + item.ContactName + '</a>'; var fromString2 = (fromIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYour').toLowerCase() + '</strong>' : '<a class="vcard" contactId="' + item.CreatedByContactId + '">' + item.CreatedByName + '</a>'; var toString2 = (toIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYou').toLowerCase() + '</strong>' : '<a class="vcard" contactId="' + item.ContactId + '">' + item.ContactName + '</a>'; var subFormat, subProps; var configObject = (BEFramework.healthStreamConfig[item.Module] && BEFramework.healthStreamConfig[item.Module][item.Action]) || {}; var standardCase = configObject.standardCase; var suppress = configObject.suppress || []; var propertiesInOrder = configObject.displayOrder || []; if (msg.indexOf('not found in module') != -1) { try { switch (item.Module) { case 'contacts': if (item.Action == 'setpermission' || item.Action == 'deleterelationship' || item.Action == 'addinvite') { msg = BETranslate(inst._channelId, key + json.type.toString()); } break; case 'tasks': if (item.Action == 'savetask') { msg = BETranslate(inst._channelId, key + json.type.toString()); } break; default: msg = ''; } } catch (ex) { msg = ''; } } for (var prop in json) { if (typeof (json[prop]) == 'object') { if (prop === 'changes' || prop === 'deleted'){ subProps = json[prop]; for (var propName in subProps) { if (indexInArrayCI(propName, propertiesInOrder) === -1 && indexInArrayCI(propName, suppress) === -1){ propertiesInOrder.push(propName); } } } if (prop == 'changes') { var changes = ''; var changeFrom = BETranslate(inst._channelId, 'HSChangedFrom'); var changeTo = BETranslate(inst._channelId, 'HSChangedTo'); for (var i = 0; i < propertiesInOrder.length; i++) { var subprop = propertiesInOrder[i]; if (getObjectValCI(subProps, subprop) == null) continue; var subSplit = stripHtml(getObjectValCI(subProps, subprop)).split('|'); if (subSplit.length === 1) { subFormat = BETranslate(inst._channelId, 'HS' + item.Module + '_changes_' + subprop); if (subFormat.indexOf('not found in module') < 0) { changes += $.sandr(subFormat, '#{value}', subSplit[0]); } else { changes += "*|" + subprop + " " + subSplit[0] + "~"; } } else { var fromValue = stripHtml(subSplit[0]); var toValue = stripHtml(subSplit[1]); var packetInfo = processChangedValues(subprop, fromValue, toValue); if (packetInfo.skip) continue; changes = changes + changeFrom + packetInfo.display + '|' + packetInfo.fromValue + '<b>' + changeTo + '</b>' + packetInfo.toValue + '~'; } } msg = $.sandr(msg, '#{' + prop + '}', changes); } else if (prop == 'deleted') { var deleted = ''; for (var i = 0; i < propertiesInOrder.length; i++) { var subprop = propertiesInOrder[i]; var currentValue = getObjectValCI(subProps, subprop); if (currentValue == null || currentValue.toString().length === 0) continue; deleted = deleted + getDisplayForLabel(subprop) + '|' + getDeleteValue(subprop, currentValue) + '~'; } msg = $.sandr(msg, '#{' + prop + '}', deleted); } } else { msg = $.sandr(msg, '#{' + prop + '}', $.sandr(json[prop], '"', ' ')); } function processChangedValues(label, fromValue, toValue){ var typeFormat = (getObjectValCI(configObject, label) || {}).type; var result = {}; if (typeFormat === 'date'){ var d1 = new Date(fromValue); var d2 = new Date(toValue); if (isDateEqual(d1, d2)) result.skip = true; } result.fromValue = getValueToDisplay(fromValue, typeFormat); result.toValue = getValueToDisplay(toValue, typeFormat); result.display = getDisplayForLabel(label) return result; } function getDeleteValue(label, value){ var typeFormat = (getObjectValCI(configObject, label) || {}).type; return getValueToDisplay(value, typeFormat); } function getValueToDisplay(rawValue, typeFormat){ if (typeFormat === 'date'){ var d = new Date(rawValue); return isNaN(d.getTime()) ? rawValue : d.toString('MM/dd/yyyy'); } else if (typeof typeFormat === 'function') { return typeFormat(rawValue) } else { return rawValue; } } function getDisplayForLabel(label){ var fixCaseOfProperty = standardCase === '*' || indexInArrayCI(label, standardCase) > -1; var rawConfigForLabel = getObjectValCI(configObject, label) || {}; return (rawConfigForLabel && rawConfigForLabel.display) || (fixCaseOfProperty ? fixCase(label) : null) || label; } } msg = $.sandr(msg, '#{contactId}', item.ContactId); msg = $.sandr(msg, '#{from}', fromString); msg = $.sandr(msg, '#{to}', toString); msg = $.sandr(msg, '#{from2}', fromString2); msg = $.sandr(msg, '#{to2}', toString2); msg = $.sandr(msg, '#{recordId}', item.RecordId); msg = msg.replace(/#{[\S]*}/g, ''); if (item.Action === 'saveinsurance' && !___x && __i == 1){ var end = +new Date(); ___x = true; //alert(end - start); } if (item.Action === 'saveinsurance') __i++; if (msg.indexOf('not found in module') == -1) { return msg; } else { return ''; } } function stripHtml(html) { var tmp = document.createElement('DIV'); tmp.innerHTML = html; return tmp.textContent || tmp.innerText; } function isDateEqual(date1, date2) { if (date1.getDate() === date2.getDate() && date1.getMonth() === date2.getMonth() && date1.getYear() === date2.getYear()) { return true; } else { return false; } } function getObjectValCI(obj, key){ for (var k in obj){ if (k.toLowerCase() === key.toLowerCase()){ return obj[k]; } } } function indexInArrayCI(item, arr){ if (!$.isArray(arr)) arr = []; var target = item.toString().toLowerCase(); for (var i = 0; i < arr.length; i++){ if (target === arr[i].toLowerCase()) return i; } return -1; } function fixCase(str){ return str.replace(/[az][AZ]/g, function(match) { return match.charAt(0) + ' ' + match.charAt(1).toLowerCase(); }).toLowerCase() .replace(/\sid\s/g, ' ID ') .replace(/\sid$/g, ' ID') .replace(/^id$/g, 'ID'); } })(); 
+4
source share
2 answers

Well, too many mistakes to think about. First of all, I don’t understand if you want static references or instantiated values. You are not using jsDoc tags or anything like that. The compiler works best only with the corresponding jsDoc tag. You logic is very strange and poorly formulated. Alternation prototypes, etc., Everything happens in IIFE (immediately called function expression). Do your functions collide? Are they constructors? Are we human or are we a dancer?

IIFE is executed before the DOMContentLoaded event is fired by the browser. The most you can do is jQuery IIFE equivalent $(function() {})(); which associates this with a DOMReady or DOMContentLoaded callback. You define built-in functions inside blocks, which is not even in ECMA.

Although most script engines support function declarations within blocks, they are not part of ECMAScript (see ECMA-262 , clauses 13 and 14). Worse implementations are incompatible with each other and with future EcmaScript proposals. ECMAScript only allows function declarations in the list of root script or function statements. Instead, use a variable initialized with a function expression to define a function within a block.

 var myFunctionName = function (params) {}; 

You also do not have enough half-column loads. Automatically inserting a semicolon insert into your JS interpretation is not entirely flawless, so get out of it.

Reliance on implicit insertion can cause subtle, hard-to-debug problems. Do not do this. You are better than that.

There are a couple of places where missing semicolons are especially dangerous:

 // 1. MyClass.prototype.myMethod = function() { return 42; } // No semicolon here. (function() { // Some initialization code wrapped in a function to create a scope for locals. })(); var x = { 'i': 1, 'j': 2 } // No semicolon here. // 2. Trying to do one thing on Internet Explorer and another on Firefox. // I know you'd never write code like this, but throw me a bone. [normalVersion, ffVersion][isFF](); var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here. // 3. conditional execution a la bash -1 == resultOfOperation() || die(); 

So what is going on?

JavaScript error - the return 42 function is returned first with the second function as a parameter, then the number 42 is “called”, which leads to an error. Most likely, you will get “no such property in error undefined” at runtime because it is trying to call x[ffVersion][isIE]() . die is called if resultOfOperation() NaN and THINGS_TO_EAT get the result of die() . Why?

JavaScript requires instructions to end with a semicolon, unless it thinks it can safely infer their existence. In each of these examples, a function declaration or a literal of an object or array is used inside the statement. Closing brackets are not enough to signal completion of an instruction. Javascript never ends a statement if the next token is an infix or bracket operator.

It really surprised people, so make sure your assignments end with a semicolon.

+1
source

When you use the closure compiler, you give up control of your code. It will do all kinds of tricks and potentially delete unused code.

It seems that your functions are not deleted, but renamed.

For example, your call getDeleteValue ...

 getDeleteValue(subprop, currentValue) 

Now...

 l(g,r) 

Since getDeleteValue not exported, Closure renamed it.

Working with the Closure compiler requires a bit of refinement and a bit of documentation processing until you know how it works.

+2
source

Source: https://habr.com/ru/post/1443289/


All Articles