简体   繁体   中英

Typescript and Google Closure

I use Typescript command (tsc) to create a single Javascript file containing all plateform classes.

tsc "./Main.ts" -out "./script/myProject_debug.js" --declarations

Then, I want to obfuscate this file with Google Closure (compiler.jar) like this :

java -jar ./compiler/compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js "./script/myProject_debug.js" > "./script/myProject.js".

But when I execute the resulting obfuscated/optimized code, I got this following error : Uncaught TypeError: Cannot read property 'prototype' of undefined

Which matches the following non-obfuscated JS code (generated by tsc command) :

var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
}

This part is used to translate the "extends" Typescript keyword and the equivalent of b is undefined.

Is anyone got similar error or/and get a solution to be able to obfuscate with Google Closure with a Typescript compiled file?

I tried with uglifyjs command and the output file works perfectly, but I want total obfuscation (classes, args, variables, methods, etc). Also, the extra optimization provided by Google Closure would be welcome.

Thanks you!

The definition of __extends has an issue which is most likely causing the error you see.

var __extends = this.__extends || function (d, b) { ... };

The this.__extends reference is meant to be the same thing as window.__extends , however Closure-compiler does not know (or ever even tries) to realize that the reference to this in the global context is in fact the window object. Compiled with --warning_level=VERBOSE the compiler will emit the warning:

Dangerous use of the global this object at line 1 character 16
var __extends = this.__extends || function (d, b) {
                ^

In addition, the this.__extends is a reference to an external/undefined property and the compiler is also warning about that on VERBOSE level.

I've modified and annotated the definition to compile without warnings using the Closure-compiler Service UI :

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @warning_level VERBOSE
// @output_file_name default.js
// @formatting pretty_print
// ==/ClosureCompiler==

var __extends = window['__extends'] || function (d, b) {
  /** @constructor */
  function __() { this.constructor = d; }
  __.prototype = b.prototype;
  d.prototype = new __();
}

/**
 * @constructor
 * @extends {String}
 */
function foo2() {this.foo = 'bar'; }
__extends(foo2, String);

var bar2 = new foo2;
alert(bar2.toLowerCase);

A JSFiddle of the modified and compiled code

Ok I found the problem.

As I said earlier, b is undefined in :

var __extends = this.__extends || function (d, b) {
   function __() { this.constructor = d; }
   __.prototype = b.prototype;
   d.prototype = new __();
}

When typescript "compile" into javascript, if you got one namespace by project but that you write all classes related to this namespace in separated files, Typescript do the following in the final generated js file :

var namespace;
(function (namespace) {

    var Class1 = (function (dependency) {
        [...]
        return Class1;
    })(namespace.dependency);

    namespace.Class1 = Class1;
})(namespace || (namespace= {}));

var namespace;
(function (namespace) {

    var Class2 = (function (dependency) {
        [...]
        return Class2;
    })(namespace.dependency);

    namespace.Class2 = Class2;
})(namespace || (namespace= {}));

var namespace;
(function (namespace) {

    var Main = (function (dependency) {
        [...]
        return Main;
    })(namespace.Class2);

    namespace.Main = Main;
})(namespace || (namespace= {}));

I don't know exactly how its works but somewhere google-closure-compiler removed some classes even if there is no problem with this code and JS can handle it. So some dependencies was missing and b was undefined.

So I found that if you declare your namespace like the following, you will not encounter the error anymore (closure will keep all your classes in the final obfuscated js file as long as the "Main" class is used or that you keep a reference of your namespace in the global window object) :

var namespace;
(function (namespace) {

    var Class1 = (function (dependency) {
        [...]
        return Class1;
    })(namespace.dependency);

    namespace.Class1= Class1;

    var Class2 = (function (dependency) {
        [...]
        return Class2;
    })(namespace.dependency);

    namespace.Class2= Class2;

    var Main = (function (dependency) {
        [...]
        return Main;
    })(namespace.Class2);

    namespace.Main = Main;
})(namespace || (namespace= {}));

I think I will open an issue on typescriptlang.org. It's optimizing the generated file size by the way.

Thank you for your answers!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM