Closure Compiler - best practice for JavaScript library projects? - javascript

I am trying to use Closure Compiler to minimize and validate my JavaScript library and I am struggling with one issue. I created a small project to highlight the problem. Here is the externs file with public API of my library. I defined it according to:
https://developers.google.com/closure/compiler/docs/externs-and-exports
/**
* #fileoverview Public API of my-lib.js
*
* #externs
*/
const myLib = {};
myLib.foo = function() {};
And here is the implementation:
// const myLib = {};
myLib.foo = function() {
console.log("foo");
};
So the problem is that if I uncomment the first line, I am getting this error:
my-lib.js:1:6: ERROR - [JSC_REDECLARED_VARIABLE_ERROR] Illegal redeclared variable: myLib
1| const myLib = {};
^^^^^^^^^^
If I don't, the output looks like this:
(function(){myLib.foo=function(){console.log("foo")};})()
Which is good, because myLib.foo didn't get renamed so the externs are working, but in the same time the myLib namespace has not been created.
What would be the best practice for solving such an issue, or if there is none, maybe there is some workaround instead?
I pushed this example to github:
https://github.com/morisil/closure-compiler-lib-example

Externs provide definitions for symbols that the compiler should consider to be already existing.
There are a couple of solutions:
/** #suppress {const,duplicate} */
const myLib = {}
or use a separate name internally and assign it in a way the compiler won't check:
globalThis['myLib'] = myInternalName;

Related

Sinon stub a function defined in the same file

I have code along the lines of:
// example.js
export function doSomething() {
if (!testForConditionA()) {
return;
}
performATask();
}
export function testForConditionA() {
// tests for something and returns true/false
// let's say this function hits a service or a database and can't be run in tests
...
}
export function performATask() {
...
}
// example.test.js
import * as example from 'example';
it('validates performATask() runs when testForConditionA() is true', () => {
const testForConditionAStub = sinon.stub(example, 'testForConditionA').returns(true);
const performATaskSpy = sinon.stub(example, 'performATask');
example.doSomething();
expect(performATaskSpy.called).to.equal(true);
});
(I know, this is a contrived example, but I tried to keep it short)
I haven't found a way to mock testForConditionA() using Sinon.
I know there are work arounds, like
A) place everything that's in example.js into a class, and then the functions of the class can be stubbed.
B) move testForConditionA() (and other dependencies) out of example.js into a new file, and then use proxyquire
C) inject the dependencies into doSomething()
However, none of these options are viable - I'm working in a large codebase, and many files would need a rewrite & overhaul. I've searched on this topic, and I see several other posts, like this Stubbing method in same file using Sinon, but outside of refactoring code into a separate class (or a factory as one person suggested), or refactoring into a separate file and using proxyquire, I haven't found a solution. I've used other testing & mocking libraries before in the past, so it's surprising that Sinon isn't able to do this. Or is it? Any suggestions on how to go about stubbing a function without refactoring the code it's trying to test?
This bit from a very related answer (mine), shows why it is not really that surprising:
ES modules are not mutable by default, which means Sinon can't do zilch.
The EcmaScript spec dictates this, so the only current way to mutate the exports is for the runtime to not adhere to the spec. This is essentially what Jest does: it provides its own runtime, translates the import calls into equivalent CJS calls (require) calls and provides its own require implementation in that runtime that hooks into the loading process. The resulting "module" usually has mutable exports that you can overwrite (i.e. stub).
Jest does not support native (as in no transpilation/modification of source) ESM either. Track issues 4842 and 9430 for how complex this (requires changes to Node).
So, no, Sinon cannot do this on its own. It is only a stubbing library. It does not touch the runtime or do anything magic, as it must work regardless of environment.
Now back to your original issue: testing your module. The only way I see this happening is through some sort of dependency injection mechanism (which you touch upon in alternative C). You obviously have some (internal/external) state your module depends on, so that means you need a way to change that state from the outside or inject a test double (what you are trying).
One easy way is just to create a setter strictly meant for testing:
function callNetworkService(...args){
// do something slow or brittle
}
let _doTestForConditionA = callNetworkService;
export function __setDoTestForConditionA(fn){
_doTestForConditionA = fn;
}
export function __reset(){
_doTestForConditionA = callNetworkService;
}
export function testForConditionA(...args) {
return _doTestForConditionA(...args);
}
You would then test your module simply like this:
afterEach(() => {
example.__reset();
});
test('that my module calls the outside and return X', async () => {
const fake = sinon.fake.resolves({result: 42});
example.__setDoTestForConditionA(fake);
const pendingPromise = example.doSomething();
expect(fake.called).to.equal(true);
expect((await pendingPromise).result).toEqual(42);
});
Yes, you do modify your SUT to allow testing, but I have never found that all that offensive. The technique works regardless of framework (Jasmine, Mocha, Jest) or runtime (browser, Node, JVM) and reads fine.
Optionally injected dependencies
You do mention injecting the dependencies into the function actually depending on them, and that has some issues that would propagate all over the codebase.
I would like to challenge that a bit by showing a technique I have used a bit in the past. See this comment (by me) on the Sinon issue tracker: https://github.com/sinonjs/sinon/issues/831#issuecomment-198081263
I use this example to show how you can inject stubs in a constructor that none of the usual consumers of this constructor needs to care about. Does require that you use some kind of Object to not add additional parameters, of course.
/**
* Request proxy to intercept and cache outgoing http requests
*
* #param {Number} opts.maxAgeInSeconds how long a cached response should be valid before being refreshed
* #param {Number} opts.maxStaleInSeconds how long we are willing to use a stale cache in case of failing service requests
* #param {boolean} opts.useInMemCache default is false
* #param {Object} opts.stubs for dependency injection in unit tests
* #constructor
*/
function RequestCacher (opts) {
opts = opts || {};
this.maxAge = opts.maxAgeInSeconds || 60 * 60;
this.maxStale = opts.maxStaleInSeconds || 0;
this.useInMemCache = !!opts.useInMemCache;
this.useBasicToken = !!opts.useBasicToken;
this.useBearerToken = !!opts.useBearerToken;
if (!opts.stubs) {
opts.stubs = {};
}
this._redisCache = opts.stubs.redisCache || require('./redis-cache');
this._externalRequest = opts.stubs.externalRequest || require('../request-helpers/external-request-handler');
this._memCache = opts.stubs.memCache || SimpleMemCache.getSharedInstance();
}
(see the issue tracker for expanded comments)
There is nothing forcing anyone to provide stubs, but a test can provide them to override how the dependencies work.

JSdoc for js closures

Using jsdoc version 3.6.2
I have a project with tons of jsdoc documentation written, but unfortunately jsdoc won't parse it into the format I'm expecting.
We have numerous js files that create objects and then use function closures to define methods on those objects. I would like jsdoc to pick up those methods and associate them with the given module.
/** #module #mymodule*/
var mymodule = {};
/** This module is my very special module */
(function() {
var started = false;
/**
* #memberof module:mymodule // I have tried this, #name, #lends, etc...
* Start the module
*/
mymodule.start = function() {
started = true;
};
})();
It seems no matter what I do, I can't get the start method to actually show up as a documented method of the mymodule module. If I use #alias on the method, then it shows up as a global on the jsdoc index page, but that doesn't make for a very readable index page if it just dumps 10k links.
Anyone know how I can get this method to show up on the mymodule.html page?

JSHint redefinition warning and adding more properties to object

I have object definition spanning multiple files and I use the following syntax to add more properties to namespace
var app = app || {};
// and then
app.namespace = {
...
}
But JSHint warns me with stuff like:
[L1:C5] W079: Redefinition of 'app'.
var app = app || {};
I'm not sure if this is really wrong as I've seen it used many times e.g. together with module pattern.
If that's ok, how can I globally supress that warning? I've found a way to supress given option for given file with
/* jshint: -W079 */
but is there a way to do it globally? Or is it considered bad practice?
use this:
window.app = window.app || {};
What you are trying is assigning the local variable app to the global variable app.

Mangle nested classes and variables with UglifyJS

I use UglifyJS to minify a concatenated set of files, which works fine but not good enough. The built lib uses namespaces, so classes, functions and constants are stored in a root namespace variable:
(function() {
var root = { api:{}, core:{}, names:{} };
/* util.js file */
root.names.SOME_LONG_NAMED_CONST='Angel';
/* Person.js file */
root.core.Person = function(name) { this.name = name };
/* API.js with the functions we want to expose */
root.api.perform = function(param_for_api) { /* do something */ }
window.lib_name.perform = root.api.perform;
})();
which is minified to the not-so-minimal version
(function(){var a={api:{},core:{},names:{}};a.names.SOME_LONG_NAMED_CONST="Angel",a.core.Person=function(a){this.name=a},a.api.perform=function(){},window.lib_name.perform=a.api.perform})();
I understand uglify probably thinks that root var is a data structure that must be kept as-is and can't be changed. Is there a way to let UglifyJS mangle the nested names in the root namespace?
When you minimize Javascript you can only change names of variables, the api, core and names are not variables but properties of an object. If these were changed by the minimizer, you would potentially get unexpected results. What if in your code you would call
root["api"].perform = function()...
or even something like
function doIt(section, method, argument) {
root[section][method](argument);
}
doIt('api','perform', 101);
All perfectly legal JS, but a minimizer could never figure out what's going on.
I have been trying to use --mangle-props of UglifyJS2 and can tell you: 'it makes a mess'.
As someone pointed out: 'Developer should decide what properties to mangle, not uglifyjs'
I am approaching the problem using this options:
--mangle-props
--mangle-regexp="/_$/"
The regex matches any property with a underscore at the end.
You asked to mangle nested names in the root namespace. So, your code:
(function() {
var root = { api:{}, core:{}, names:{} };
root.names.SOME_LONG_NAMED_CONST_='Angel';
root.core.Person_ = function(name) { this.name = name };
root.api.perform_ = function(param_for_api) { }
window.lib_name.perform = root.api.perform;
})();
Would result in this:
(function() {
var n = {
api: {},
core: {},
names: {}
};
n.names.a = "Angel";
n.core.b = function(n) {
this.name = n;
};
n.api.c = function(n) {};
window.lib_name.perform = n.api.c;
})();
Command:
uglifyjs --beautify --mangle --mangle-props --mangle-regex="/_$/" -- file.js
If you want to mangle first level of root namespace (api, core, names) just put a underscore on them (api_, core_, names_), you are in control ;)
Just a side note: when you are mangling properties usable by other js files, you should mangle all files together with the same command, so the same identifier will be used over all files.
Aside from #JanMisker 's point (which is completely valid), rewriting properties is unsafe because they can be exposed to code outside the scope of the minification.
Although the self executing function has a scope, and if the code is only
(function() {
var root = { api:{}, core:{}, names:{} };
root.names.SOME_LONG_NAMED_CONST='Angel';
alert(root.names.SOME_LONG_NAMED_CONST); // some code that does something
})();
It is true that outside of the function, there is no way to access the root object, so rewriting the property names is safe, and the following code would result in the same:
(function() {
var a = { b:{}, c:{}, d:{} };
a.d.e='Angel';
alert(a.d.e);
})();
But even if you are inside your private scope you can access, and more importantly assign to variables from an outer scope! Imagine this:
(function() {
var root = { api:{}, core:{}, names:{} };
root.api.perform = function(param_for_api) { /* do something */ }
window.lib_name = root.api;
})();
You are not only exposing a function but an object with a function on it. And the function will be visible from any place where window is visible.
So, for example writing the following in the javascript console would yield different results with and without minification:
window.lib_name.perform(asdf);
With minification you would have to write:
window.lib_name.f(asdf);
Or something similar.
Remember that there can always be code outside your minification.
It is not that crucial to have the absolute minimal JS, but if IT IS that crucial for some reason (for example: aliens abducted your stepdaughter, and the only way to have her back is to minify this below 100 characters or so), you can manually replace an undesirably long property name to a shorter one, just be sure that it will not be exposed anywhere, and isn't be accessed through associative array notation (root['api']).
as #Jan-Misker explained in his answer, property name mangling is NOT an good idea because it could potentially break your code.
However, you can workaround it by define the property names as local variables, and modify all .properties to [keys], to make smaller file size:
(function() {
var API = 'api';
var CORE = 'core';
var NAMES = 'names';
var SLNC = 'SOME_LONG_NAMED_CONST';
var root = {};
root[API]={};
root[CORE]={};
root[NAMES]={};
/* util.js file */
root[NAMES][SLNC] ='Angel';
/* Person.js file */
root[CORE].Person = function(name) { this.name = name };
/* API.js with the functions we want to expose */
root[API].perform = function(param_for_api) { /* do something */ }
window.lib_name.perform = root[API].perform;
})();
Because now all the properties became a local variable, uglify js will mangle/shorten the variable names and as consequence you overall file size reduced:
!function(){var a="api",b="core",c="names",d="SOME_LONG_NAMED_CONST",e={};e[a]={},e[b]={},e[c]={},e[c][d]="Angel",e[b].Person=function(a){this.name=a},e[a].perform=function(){},window.lib_name.perform=e[a].perform}();
However, reduced file size doesn't mean you will get shorter downloading time on real server, because usually our http transport is gzipped, most of the repetitions will be compressed by your http server and it does a better job than human.
The latest release of uglify (today) has object property mangling, see v2.4.18. It also supports reserved files for excluding both object properties and variables that you don't want mangled. Check it out.
Use the --mangle-props option and --reserved-file filename1.json filename2.json etc....

Using Google Closure Compiler can you exclude a section of source code from the compiled version?

I recently built a project using the Dojo toolkit and loved how you can mark a section of code to only be included in the compiled version based on an arbitrary conditional check. I used this to export private variables for unit testing or to throw errors vs. logging them. Here's an example of the Dojo format, I'd love to know if there are any special directives like this for the Google Closure Compiler.
window.module = (function(){
//private variable
var bar = {hidden:"secret"};
//>>excludeStart("DEBUG", true);
//export internal variables for unit testing
window.bar = bar;
//>>excludeEnd("DEBUG");
//return privileged methods
return {
foo: function(val){
bar.hidden = val;
}
};
})();
Edit
Closure the definitive guide mentions that you can extend the CommandLineRunner to add your own Checks and Optimizations that might be one way to do it. Plover looks promising as it supports custom-passes.
This simple test case worked. Compile with --define DEBUG=false
/**
* #define {boolean} DEBUG is provided as a convenience so that debugging code
* that should not be included in a production js_binary can be easily stripped
* by specifying --define DEBUG=false to the JSCompiler. For example, most
* toString() methods should be declared inside an "if (DEBUG)" conditional
* because they are generally used for debugging purposes and it is difficult
* for the JSCompiler to statically determine whether they are used.
*/
var DEBUG = true;
window['module'] = (function(){
//private variable
var bar = {hidden:"secret"};
if (DEBUG) {
//export internal variables for unit testing
window['bar'] = bar;
}
//return privileged methods
return {
foo: function(val){
bar.hidden = val;
}
};
})();
console.log(window['bar']);
module.foo("update");
console.log(window['bar']);
Closure Compiler supports "defines", like this:
/** #define {boolean} */
var CHANGABLE_ON_THE_COMMAND_LINE = false;

Categories

Resources