Help me understand externs effects?

83 views
Skip to first unread message

Thomas Heller

unread,
Apr 7, 2017, 5:14:24 AM4/7/17
to Closure Compiler Discuss
Hello,

I'm trying to grasp the way externs are handled in Closure. If I have a test.js file with just

document.createElement("foo");

and run that through advanced optimizations with the default externs I get an empty file which is what I would expect.

If I add

var React = {};
React.createElement = function(x) {};

as additional externs the above code is no longer removed. Hmm?

I don't get why these extra externs break the well annotated document Object.

Is there any setting I'm supposed to use to let it remove the code once again?

It is rather hard finding documentation about the actual internals and digging through the code just confused me more. The type checker cares very much for correctly typed externs but everything else does not it seems?

Regards,
/thomas


Dimitris Vardoulakis

unread,
Apr 7, 2017, 12:04:14 PM4/7/17
to Closure Compiler Discuss
I'm assuming here that you are using advanced optimizations.

When you add your own createElement, the call in the program may be to the new external function, which may have side effects, so the compiler won't remove it. The compiler optimizations distinguish properties only by name, so all createElement are not distinguished from each other. If you add the flag,
--use_types_for_optimization
and also declare the type of React, then the two properties will be distinguished.

I think even without types you may get the removal here if you add /** @nosideeffects */ to the definition of React.createElement.

Thomas Heller

unread,
Apr 7, 2017, 3:11:53 PM4/7/17
to Closure Compiler Discuss
I'm actually using the Compiler directly via Clojure but I just verified that the behaviour is the same when using just the compiler via CLI.

ext.js:
goog.provide("test.ext");

document.createElement("foo");
React.createElement("foo");


externs.js
var React = {};
React.createElement = function(tag) {};


When I run with
java -jar closure-compiler-v20170218.jar --js ext.js --compilation_level=ADVANCED --externs externs.js

I get
document.createElement("foo");React.createElement("foo");

When I run without the externs I get nothing.
java -jar closure-compiler-v20170218.jar --js ext.js --compilation_level=ADVANCED --use_types_for_optimization

What is confusing me is why the document.createElement call is also kept although the correct type should be known?
Also why React.createElement is just removed although its type should not be known at all. The type checker doesn't even complain.

I surely must be missing something? I tried adding a bunch of type annotations to the externs but that had no effect at all.

/thomas

Dimitris Vardoulakis

unread,
Apr 7, 2017, 3:21:39 PM4/7/17
to Closure Compiler Discuss

Chad Killingsworth

unread,
Apr 7, 2017, 7:06:45 PM4/7/17
to Closure Compiler Discuss
Type based optimizations are enabled by default in the external distributions.

This problem has bugged me for a long time. I believe the solution is to treat external namespaces as distinct types automatically.

Dimitris Vardoulakis

unread,
Apr 11, 2017, 12:51:07 PM4/11/17
to Closure Compiler Discuss
Thomas, I will submit a fix to turn on the check for undeclared variables in advanced mode by default. To turn it on manually, you do --jscomp_error=checkVars.

Thomas Heller

unread,
Apr 11, 2017, 3:01:38 PM4/11/17
to Closure Compiler Discuss
I'm still confused why document.createElement is affected by the React.createElement externs though?

Shouldn't the compiler find the correct .createElement for document and still remove it?

Chad Killingsworth

unread,
Apr 11, 2017, 3:23:17 PM4/11/17
to Closure Compiler Discuss
Because of how your externs are defined (and this is very non-obvious).

Something like this should work:

/** @interface */
var ReactType = function() {};

/** @param {string} tagName */
ReactType.prototype.createElement = function(tagName) {};

/** @type {!ReactType} */
var React;

Chad Killingsworth

Thomas Heller

unread,
Apr 11, 2017, 4:17:28 PM4/11/17
to Closure Compiler Discuss
It doesn't.

I actually tried a few variant like yours (with or without @const for React as well), nothing seems to have an effect.

You don't even need to define the React const

/** @interface */
var ReactType = function() {};
ReactType.prototype.createElement = function() {};

Once the createElement is defined the document.createElement is not removed. Granted its a contrived example that probably doesn't have real-world impact but still very confusing.

I don't know enough about the externs path (yet) to figure this out on my own.
Reply all
Reply to author
Forward
0 new messages