Utilizing the Module Pattern in conjunction with Closure Compiler's ADVANCED_OPTIMIZATIONS

Although similar questions have been asked previously, I am interested in current best practices as methodologies evolve rapidly. Just two days ago, Chad Killingsworth commented on an accepted answer from three years ago, stating that the @expose annotation is now deprecated.

I am currently implementing the module pattern. A working example can be found on this JSFIDDLE link below:

/** @const */
var MATHCALCS = (function () {
    'use strict';

    var MY = {};

    /**
     * @constructor
     * @param {!Object} obj
     * @expose
     */
    MY.ModuleStruct = function (obj) {
        /** @expose */
        this.color = (obj.color !== undefined) ? obj.color : null;
        /** @expose */
        this.size = (obj.size !== undefined) ? obj.size : null;
    };

    /**
     * @expose
     */
    MY.ModuleStruct.prototype.clone = function () {
        return new MY.ModuleStruct({
            "color": this.color,
                "size": this.size
        });
    };


    MY.moduleProperty = 1;

    /**
     * @type {function(!Array<number>)}
     * @expose
     */
    MY.moduleMethod = function (a) {
        var i, x = 0;
        for (i = 0; i < a.length; i += 1) {
            x = x + a[i];
        }
        return x;
    };

    return MY;

}());

window["MATHCALCS"] = MATHCALCS;

With the usage of @expose annotation, the code above can be effectively minified using Closure in advanced mode and the subsequent calls are functional (minified example):

// call a public method
alert(MATHCALCS.moduleMethod([1, 2, 3]));

// allocate a new structure
var ms = new MATHCALCS.ModuleStruct({
    "color": "red",
        "size": "small"
});
alert(ms.color + '\t' + ms.size);

// clone a second instance
var ms2 = ms.clone();
alert(ms2.color + '\t' + ms2.size);
alert(ms !== ms2); // cloned objs are not equal

// and directly update the properties of the object
ms2.color = "white";
ms2.size = "large";
alert(ms2.color + '\t' + ms2.size);

If feasible, I would like to transition the approximately 10,000 lines of code to utilize the @export annotation without deviating from the module pattern. However, upon replacing @expose with @export, Closure throws the following error:

ERROR - @export only applies to symbols/properties defined in the global scope.

Q: Is it achievable, and if so, how should the aforementioned code be annotated for compatibility with ADVANCED_OPTIMIZATIONS?

While I understand I can use notation such as:

MY["ModuleStruct"] = MY.ModuleStruct;
MY["ModuleStruct"]["prototype"]["clone"] = MY.ModuleStruct.prototype.clone;

manually exporting object properties this way may become burdensome. Additionally, JSLint raises concerns about unconventional assignments, therefore utilizing JSDocs annotations seems to be a preferable approach.

Answer №1

To address the concern raised by @ChadKillingsworth, a temporary solution is provided below to allow the usage of @export with minimal adjustments to your existing code:

/** @const */
var MATHCALCS = {};

goog.scope(function () {
    'use strict';

    var MY = MATHCALCS;

    /**
     * @constructor
     * @param {!Object} obj
     * @export
     */
    MY.ModuleStruct = function (obj) {
        this.color = (obj.color !== undefined) ? obj.color : null;
        this.size = (obj.size !== undefined) ? obj.size : null;
    };

    /**
     * @export
     */
    MY.ModuleStruct.prototype.clone = function () {
        return new MY.ModuleStruct({
            "color": this.color,
                "size": this.size
        });
    };


    MY.moduleProperty = 1;

    /**
     * @type {function(!Array<number>)}
     * @export
     */
    MY.moduleMethod = function (a) {
        var i, x = 0;
        for (i = 0; i < a.length; i += 1) {
            x = x + a[i];
        }
        return x;
    };

});

The key modifications are as follows:

  • Replace the @expose tags with @export.
  • Introduce an empty MATHCALCS object outside the module wrapper function and set the MY alias to it.
  • Instead of immediate execution of the module wrapper function (IIFE), utilize goog.scope(). This allows aliasing within scope functions, informing the compiler that the exported symbols are defined on the global MATHCALCS object, preventing error occurrences related to ("@export only applies to symbols/properties defined in the global scope").
  • Omit unnecessary elements such as:
    • The @export tags on this.color and this.size
    • return MY;
    • window["MATHCALCS"] = MATHCALCS;

Upon compilation using this command:

java -jar compiler.jar \
    --js closure/goog/base.js \
    --js mathcalcs.js \
    --js_output_file mathcalcs.min.js \
    --compilation_level ADVANCED_OPTIMIZATIONS \
    --generate_exports \
    --formatting PRETTY_PRINT \
    --output_wrapper '(function() {%output%}).call(window);'

The result will be:

(function() {var f = this;
function g(a, d) {
  var b = a.split("."), c = f;
  b[0] in c || !c.execScript || c.execScript("var " + b[0]);
  for (var e;b.length && (e = b.shift());) {
    b.length || void 0 === d ? c[e] ? c = c[e] : c = c[e] = {} : c[e] = d;
  }
}
;function h(a) {
  this.color = void 0 !== a.color ? a.color : null;
  this.size = void 0 !== a.size ? a.size : null;
}
g("MATHCALCS.ModuleStruct", h);
h.prototype.clone = function() {
  return new h({color:this.color, size:this.size});
};
h.prototype.clone = h.prototype.clone;
g("MATHCALCS.moduleMethod", function(a) {
  var d, b = 0;
  for (d = 0;d < a.length;d += 1) {
    b += a[d];
  }
  return b;
});
}).call(window);

The function g() mirrors the compiled version of goog.exportSymbol() – refer to the official @export documentation for further insights.

Note: To run the uncompiled code successfully, loading the Closure Library or defining goog.scope() manually is necessary:

var goog = {};
goog.scope = function(fn) {
    fn();
};

A modified version of your JSFiddle incorporating these updates can be found here.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Can TypeScript automatically deduce keys from a changing object structure?

My goal here is to implement intellisense/autocomplete for an object created from an array, similar to an Action Creator for Redux. The array consists of strings (string[]) that can be transformed into an object with a specific shape { [string]: string }. ...

What is the best way to add data to my collection using the main.js file on the server side in a Meteor application

I am a beginner with Meteor and trying to customize the tutorial codes. I have a code that listens for packets on my server-side main.js. Now, I need to store the data printed on the console into my database collection. import { Meteor } from 'meteor/ ...

The website is failing to extend and reveal content that is being concealed by jQuery

I'm currently diving into the world of Javascript and jQuery, attempting to create a functionality where upon clicking the submit button, the website dynamically expands to display search information. Although the feature is still in progress, I am ut ...

Issue with hamburger menu or modal not functioning properly in Rails 4 with Bootstrap 4 alpha3 and JS

I am encountering an issue while implementing the hamburger menu feature in rails 4 with bootstrap 4. Although the menu is displayed, the dropdown section is not functioning correctly. Despite referring to other resources, I have been unable to resolve thi ...

Would it be possible to use a script to convert the y-axis values of the chart into Arabic numerals?

Currently, I am working on creating line and pie charts, but I need the Y-axis to display Arabic language. $('button').on('click', function(e) { $('#pie-chart3').empty(); var type = $(this).d ...

Trim the name property of an object within an object's map function if it exceeds a certain length

Imagine having an object structured like this: var fullObj = { prop1: "myProp1", subobject: { Obj1_id: { id: "Obj3_id", name: "./", otherProperties:... }, Obj2_id: { id: "Obj2_id&q ...

What are the problems with Internet Explorer in Selenium WebDriver?

Currently dealing with a mouse-over issue in IE while using webdriver code. The functionality works smoothly in Chrome and Firefox, however, the problem arises only in IE. How can I resolve this? Initially focusing on an element and then clicking on a li ...

What is the best way to create a brand new item using these characteristics?

Here is the object I'm working with: const sampleData = [ { main: 7, second: 2 otherData: 'some string' } ] I want to create a new object only containing specific properties, like this: const newDataObject = { 7: { ...

A guide on updating the color of a Button component (Material UI) when dynamically disabling it

In a React table, I have a button that is disabled based on the value in an adjacent column. For example, if the value in the adjacent column is "Claimed", the button is disabled. However, if the value is "Failed" or blank, the button can be clicked. Curre ...

Encountering a compilation issue while building a Next.js application with next-pwa integration

I've encountered an issue while trying to run my local Next.js project (v 12.2.2). The dev script isn't behaving as expected even after installing all necessary dependencies. I'm struggling to identify the root cause of this problem. Upon r ...

What is the optimal placement for the business logic in a React application with Redux - action creators or reducers?

Although there may be similar questions out there, none of them quite address my specific query. That's why I've decided to initiate a new discussion. Currently, I am working on creating a basic basket component for an e-commerce application.... ...

Using Javascript's Speech Recognition to activate a button

I am new to using JavaScript Speech Recognition and decided to work with the Annyang library. My goal is to automatically trigger the "show date" button when the user says 'hello', without actually clicking the button. However, I've been fac ...

What causes the off-canvas menu to displace my fixed div when it is opened?

Using the Pushy off-canvas menu from GitHub for my website has been great, but it's causing some trouble with my fixed header. When I scroll down the page, the header sticks perfectly to the top, but once I open the off-canvas menu, the header disappe ...

What is the best way to create a universal root component in next.js for sending requests, no matter the page URL?

Currently, my goal is to send a request to the server whenever the page is refreshed or opened in a new tab. For instance, after hitting F5, I want to trigger a request. However, I do not want to make a request after every routing event. Essentially, upon ...

React: Render function did not return any content. This typically indicates that a return statement is missing

Can anyone help me troubleshoot this error? Error: CountryList(...): No render was found. This typically indicates a missing return statement. Alternatively, to display nothing, return null index.js import React from 'react'; import ReactDOM f ...

Storing the initial toggle state in localStorage with React hooks is a helpful way to save user

I'm currently working on persisting the state of a toggle in local storage, as an example of maintaining a modal or a similar element even after a page refresh. Most of my implementation is functional - upon the initial page load, the boolean value i ...

Comparing Node.js and Node on Ubuntu 12.04

After following the instructions on this website, I successfully installed nodejs on my ubuntu system. However, when I type node --version in the terminal, I get an error message saying: -bash: /usr/sbin/node: No such file or directory Oddly enough, ...

Echarts: Implementing a Custom Function Triggered by Title Click Event

I recently created a bar graph using Echart JS, but I'm struggling to customize the click event on the title bar. I attempted to use triggerEvent, but it only seems to work on statistics rather than the title itself. JSFiddle var myChart = echarts.i ...

Altering the hue of a picture

Looking to alter the color of an image on a webpage, specifically a carport. It's crucial that the texture and shadows remain consistent. Swapping out images with different colors would require an excessive amount of images, so I'm seeking advice ...

Struggling to verify sessionStorage token and update the route - facing challenges in ReactJS

I am attempting to verify the sessionStorage key, and if it is valid, I want to skip displaying the register page and instead redirect to the dashboard. However, I am encountering an issue where it does not redirect or replace the path, and continues to sh ...