Starting point: Many js files are successfully compiled (no warning/error) by Closure Compiler (ADVANCED_OPTIMIZATIONS level) in a single library file.
In these js files:
goog.require and goog.provide are used to import/export things between them
/** #export */ is used in front of whatever (const/function/Class/var) is required outside the library.
Some HTML files include the library and some non compiled js accessing successfully to all whatever defined in this library.
What I want: move to es6 module syntax
What I did for each js file:
goog.require replaced by import with the list of Class, function from another js file
goog.provide removed and export added in front of each Class, function etc. required by another js file
Try 1: no change for the /** #export */ each time whatever is required outside the library.
Try 2: all /** #export */ whatever replaced by goog.exportSymbol('whatever', whatever)
This is sucessfully compiled (no warning/error, still with ADVANCED_OPTIMIZATIONS level).
The problem: now, for the same HTML files, all the whatever defined in the library are seen "undefined" by the browser. Indeed, when I type Object.keys( window ) in the console, I can see all the symbol names changed by the compiler (aa, ba, ca etc.) but none of my exported symbol whatever.
Example: demoVisitors is a const array defined in the library and required outside.
Before in the library file, I could see ... w("demoVisitors",[Oa,La,Ma,Na]); ... and the content was properly visible in the HTML page. After the es6 module change, I can see ... H("demoVisitors$$module$filemane",Oa); ... (filename being the file name in which demoVisitors is defined) for try 1 and H("demoVisitors",[Na,Ka,La,Ma]); for try 2. demoVisitors is undefined in the browser for the same page.
After further investigations, I found the solution.
Although loaded in the browser without any error in the console (except undefined whatever of course), my library was not executed. I simply moved the closure library ahead of the file stack to be compiled and my library was then properly executed by the browser with my symbols properly exported. See below for more details.
The 3 ways to export symbols are working in compiled es6 modules: /** #export */ whatever, goog.exportSymbol('whatever', whatever), window['whatever'] = whatever. The first 2 being a handy way for the third one (for root symbols).
Nevertheless /** #export */ myClass generates an unfriendly unobfuscated names like myClass$$module$source-filename-with-path.
To get the unobfuscated name myClass, avoid a goog function within my code and enable neatly both compiled/uncompiled mode, I removed the /** #export */ and add unobfuscateSymbol('myClass', myClass) after class myClass { ... }. It's my "own" function directly inspired from exportSymbol function defined in the closure library. This is only required for root symbols like classes, you can keep /** #export */ for all the symbols (properties, functions etc.) defined in the class.
Here is the source code:
export function unobfuscateSymbol(publicPath, object, objectToExportTo = window) {
// Same code can be used compiled and not compiled so unobfuscation only in case of obfuscation
if (unobfuscateSymbol.name !== 'unobfuscateSymbol') {
const /** Array<string> */ parts = publicPath.split('.');
let /** Object */ objToExportTo = objectToExportTo;
let /** string */ part;
const /** number */ nbOfParts = parts.length;
for (let /** number */ i = 0; i < nbOfParts; i += 1) {
part = parts[i];
if ((i === (nbOfParts - 1)) && object) {
objToExportTo[part] = object;
} else if (objectToExportTo[part] && objectToExportTo[part] !== Object.prototype[part]) {
objToExportTo = objectToExportTo[part];
} else {
objToExportTo[part] = {};
objToExportTo = objToExportTo[part];
}
}
}
}
How I identified the issue in details:
To understand the export issue, I tried to put a breakpoint in the exportSymbol function defined in the closure library while loading my HTML test page in the browser: no break...
I double checked this execution issue by adding a console.log("my library is being executed"): I was able to see the message in the goog.require/goog.provide version of my library but not in the es6 import/export version. Without execution, of course, no symbol exported.
I wrapped my library with a IIFE. Closure compiler argument: --output_wrapper "(function(){%output%})()" and an execution error message in my library appeared in the browser console. I discovered that goog.global, the base namespace for the Closure library, was undefined at the break time.
I moved the closure library ahead of the file stack to be compiled. Closure compiler arguments: –js closure-lib-path/base.js –js myfile1.js –js myfile2.js … to make sure compiled goog stuff is before the first exportSymbol call generated by the first /** #export */ compiled.
Execution and exports were OK then in the browser. I just added the unobfuscateSymbol function as described above to get the same friendly exported names as for the goog.require/goog.provide version of my library.
Related
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?
Showing type information in vscode for Typescript files is pretty simply via the /// <reference path ... comment. However, I have the case where I use exported functions from one js file in another. The exported functions have been annotated to contain type information. Here's an example (lib/index.js):
/**
* Put the executing thread to sleep for the given amount of milliseconds.
* #param {number} milliseconds
*/
exports.sleep = function (milliseconds) {
var end = new Date().getTime() + milliseconds;
while (new Date().getTime() < end) { }
}
In my main.js file I do:
var lib = require("lib");
lib.sleep(500);
Unfortunately, vscode does not provide the call signature of that sleep function nor can I use cmd/ctrl+click to navigate to it.
I also created a typings file and referenced that in main.js, which works almost like I want, except that it doesn't take me to the original source, but instead to the typings file when I cmd/ctrl+click on the function name.
Is it possible and how to let vscode recognize exports in other JS files and provide call information and code navigation for them?
Change require path to ./lib
var lib = require("./lib");
lib.sleep(500);
I have a large (~15k LoC) JS app (namely a NetSuite app) written in old-style all-global way. App consists of 26 files and dependencies between them are totally unclear.
The goal is to gracefully refactor the app to smaller modules. By gracefully I mean not breaking\locking the app for long time, but doing refactoring in smaller chunks, while after completing each chunk app remains usable.
An idea I have here is to concat all the JS files we have now into single-file bundle. After that some code could be extracted into modules. And the legacy code could start importing it. The modules & imports should be transpiled with webpack\whatever, while legacy code remains all-globals style. Finally all this is packed into single JS file and deployed.
My questions are
is there a better approach maybe? This sounds like a typical problem
are there any tools available to support my approach?
I gave webpack a try and I haven't managed to get what I want out of it. The export-loader and resolve-loader are no options because of amount of methods\vars that needs to be imported\exported.
Examples
Now code looks like
function someGlobalFunction() {
...
}
var myVar = 'something';
// and other 15k lines in 26 files like this
What I would ideally like to achieve is
function define(...) { /* function to define a module */ }
function require(moduleName) { /* function to import a module */ }
// block with my refactored out module definitions
define('module1', function () {
// extracted modularised code goes here
});
define('module2', function () {
// extracted modularised code goes here
});
// further down goes legacy code, which can import new modules
var myModule = require('myNewModule');
function myGlobalLegacyFunction() {
// use myModule
}
I'm following an approach similar to that outlined here: https://zirho.github.io/2016/08/13/webpack-to-legacy/
In brief:
Assuming that you can configure webpack to turn something like
export function myFunction(){...}
into a file bundle.js that a browser understands. In webpack's entry point, you can import everything from your module, and assign it to the window object:
// using namespace import to get all exported things from the file
import * as Utils from './utils'
// injecting every function exported from utils.js into global scope(window)
Object.assign(window, Utils).
Then, in your html, make sure to include the webpack output before the existing code:
<script type="text/javascript" src="bundle.js"></script>
<script type="text/javascript" src="legacy.js"></script>
Your IDE should be able to help identify clients of a method as you bring them into a module. As you move a function from legacy.js to myNiceModule.js, check to see if it still has clients that are aware of it globally - if it doesn't, then it doesn't need to be globally available.
No good answer here so far, and it would be great if the person asking the question would come back. I will pose a challenging answer saying that it cannot be done.
All module techniques end up breaking the sequential nature of execution of scripts in the document header.
All dynamically added scripts are loaded in parallel and they do not wait for one another. Since you can be certain that almost all such horrible legacy javascript code is dependent on the sequential execution, where the second script can depend on the first previous one, as soon as you load those dynamically, it can break.
If you use some module approach (either ES7 2018 modules or require.js or you own) you need to execute the code that depends on the loading having occurred in a call-back or Promise/then function block. This destroys the implicit global context, so all these spaghetti coils of global functions and var's we find in legacy javascript code files will not be defined in the global scope any more.
I have determined that only two tricks could allow a smooth transition:
Either some way to pause continuation of a script block until the import Promise is resolved.
const promise = require("dep1.js", "dep2.js", "dep3.js");
await promise;
// legacy stuff follows
or some way to revert the scope of a block inside a function explicitly into the global scope.
with(window) {
function foo() { return 123; }
var bar = 543;
}
But neither wish was granted by the javascript fairy.
In fact, I read that even the await keyword essentially just packs the rest of the statements into function to call when promise is resolved:
async function() {
... aaa makes promise ...
await promise;
... bbb ...
}
is just, I suppose, no different from
async function() {
... aaa makes promise ...
promise.then(r => {
... bbb ...
});
}
So this means, the only way to fix this is by putting legacy javascript statically in the head/script elements, and slowly moving things into modules, but continue to load them statically.
I am tinkering with my own module style:
(function(scope = {}) {
var v1 = ...;
function fn1() { ... }
var v2 = ...;
function fn2() { ... }
return ['v1', 'fn1', 'v2', 'fn2']
.reduce((r, n) => {
r[n] = eval(n);
return r;
}, scope);
})(window)
by calling this "module" function with the window object, the exported items would be put on there just as legacy code would do.
I gleaned a lot of this by using knockout.js and working with the source readable file that has everything together but in such module function calls, ultimately all features are on the "ko" object.
I hate using frameworks and "compilation" so generating the sequence of HTML tags to load them in the correct order by the topologically sorted dependency tree, while I could write myself such a thing quickly, I won't do this, because I do not want to have any "compilation" step, not even my own.
UPDATE: https://stackoverflow.com/a/33670019/7666635 gives the idea that we can just Object.assign(window, module) which is somewhat similar to my trick passing the window object into the "module" function.
I'm trying to use Closure Compiler and Closure Library.
When I use the library everything is ok, I'm including "base.js" in my simulation and it works with all my javascript files.
The problem is present when I "compilate" my application: In the output file I've got a reference to a closure library'sfunction "goog.inherits".
From what I've read, it's not necessary to include "base.js" in production. I'm working on a library, so I don't want to force users to have a reference to the Closure Library.
How can I do?
Here is my code:
NM.ObjectEvent = function( type )
{
goog.base(this);
}
goog.inherits( NM.ObjectEvent, NM.Event );
And the script look like that:
java -jar compiler.jar --compilation_level SIMPLE_OPTIMIZATIONS --js_output_file myLib.js `find ../src/ -name '*.js'`
What you have heard does not apply to SIMPLE_OPTIMIZATIONS. With ADVANCED_OPTIMIZATIONS everything unused in base.js is removed, with SIMPLE_OPTIMIZATIONS only function local optimizations are performed and unused methods are not removed.
Regardless of the mode, if you use goog.inherits it will remain in some form. Something needs to do the work that goog.inherits does to setup the prototype chain.
Like John said, if you have references to goog.base and goog.inherits, you're referencing the library. Fortunately, you can emulate those functions... Something like this should work...
NM.ObjectEvent = function( type )
{
NM.Event.call(this, type);
}
(function(){
var temp = function(){};
temp.prototype = NM.Event.prototype;
NM.ObjectEvent.prototype = new temp();
}();
If you're using goog.base elsewhere (for example, to call superclass methods), then you'll need to do more work, but the above should suffice if you're only using base and inherits where shown in your original post.
I'm looking for a way to strip out debug code from functions so I can add test hooks to closures. I've read
Google Closure Compiler advanced: remove code blocks at compile time and tested out removing debug code with the following:
/** #define {boolean} */
var DEBUG = true;
if (DEBUG) {
console.log('remove me');
}
Simple optimisation with --define='DEBUG=false' reduces this to var DEBUG=!1;. The same applies for this:
/** #const */
var DEBUG = false;
if (DEBUG) {
console.log('remove me');
}
Where I run into trouble is using this convention inside a function:
/** #const */
var DEBUG = false;
function logMe() {
if (DEBUG) {
console.log('remove me');
}
}
This reduces to the following:
var DEBUG=!1;function logMe(){DEBUG&&console.log("remove me")};
I would expect it to reduce further to:
var DEBUG=!1;function logMe(){};
Is there a reason this is not working as expected? I'm really just looking for a clean way to strip debug code and am not ready to take the plunge into advanced optimizations.
Update
Per #John's answer, I implemented my own compiler and have found that the following configuration will remove if (DEBUG) {} from inside and outside the code for the case of a #define:
CompilerOptions options = new CompilerOptions();
CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);
//options.setInlineConstantVars(true);
options.setInlineVariables(CompilerOptions.Reach.ALL);
options.setDefineToBooleanLiteral("DEBUG", false);
This works well enough for a single file with the following limitations:
This requires var DEBUG to be defined in each file, which is bad practice.
When combining multiple files, you can only have a single var DEBUG or the compiler can't optimize around it. This could be avoided by compiling each file individually and merging them.
Because the value is defined at the beginning of the file, there's no flexibility to receive the value beforehand.
I've toyed with the idea of removing all var DEBUG definitions from the files and injecting it into the source or extern before execution, but I've run into two issues:
Defining it in extern appears to do nothing.
Undefined DEBUG in the uncompiled code throws a reference error in the browser.
The ideal option would be to test window.DEBUG, which does not throw a reference error. Unfortunately, while injecting /** #const */ var window = {}; /** #const */ window.DEBUG = false; works at the top level, reducing if (window.DEBUG) {}, the optimization is actually reverted if placed in a function.
Unless another compiler option works the only option that would really make sense is to go with window.DEBUG and before compilation inject /** #const */ var DEBUG = false; and to a global replace of /\bwindow.DEBUG\b/ with DEBUG. Is there a better way?
Use #define annotation:
#define {boolean}
DEBUG = true;
And compile with option
--define="DEBUG=false"
A custom build of the compiler would allow you to do this. You basically want to "inline constant variables":
options.setInlineConstantVars(true);
You could add it here, in applySafeCompilationOptions:
http://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/javascript/jscomp/CompilationLevel.java?r=706
Or you could use the Java API and add the option (without modifying the compiler's code). Michael Bolin given an example of how to do this here:
http://blog.bolinfest.com/2009/11/calling-closure-compiler-from-java.html
This is an old answer, but I found a way that's not mentioned here.
(function(){
var DEBUG = true;
if (DEBUG) {
if (something === "wrong") {
console.warn("Stop! Hammer time!");
}
else if (something === "as expected") {
console.log("All good :-)");
}
}
foo();
})();
With ADVANCED_OPTIMIZATIONS this compiles to this:
"wrong" === something ?
console.warn("Stop! Hammer time!") :
"as expected" === something && console.log("All good :-)");
foo();
In our build script we can rewrite the DEBUG line to set it to false, which would then yield this output.
foo();
The reason this happens is Closure will remove unreachable code. By creating the closure, and defining a local variable, Closure can see that we can't do something like window.DEBUG === true, so the code is guaranteed to never be run.
Your DEBUG variable is currently global. GCC will not remove or rename global variables in simple optimization mode, so they'll remain available to any code in other scripts that might possibly want to access them. Try enclosing your code into anonymous function.
The way i solved the problem of "removing debug functions from closure compiled javascript using SIMPLE_OPTIMIZATION" was by combining a similar method as #John proposes as well as using some of #Brian Nichols update. I could only get the compiler to remove the lines by placing this is the global scope of my main js file and doing a custom compile (using multiple .js files this still removed them)
/** #const
* #type {boolean}
*/
var DEBUG = false;
//and used this format for my debug function
DEBUG && myLog('foo');
and then compiling the closure-compiler java with ant to include this option options.setInlineVariables(CompilerOptions.Reach.ALL);
under the applySafeCompilationOptions function in the CompilationLevel.java file as #john suggests. This worked for me and didnt break my codebase as ADVANCED did...
Remove var DEBUG = true; from your code and convert all your conditions that check if (DEBUG) to if (goog.DEBUG). Modify your compiler option to read --define goog.DEBUG=false. The goog variable is built into the Closure Library API to provide options and flags for the compiler.