Repository: google/closure-compiler (java)

Issue:
Add a simple command-line option for the environment the code is going to run in
I think we've discussed this but I didn't see a bug filed. This would probably look something like --env=WEB or --env=NODEJS or maybe --env=MODERN_WEB (meaning, latest version or two of the major browsers) and you would automatically get the correct set of externs, instead of having to specify each externs file with the `--externs` flag.

This would also help a lot with making the compiler easier to use.

Pull request description:
Initial support for the --env flag
I expect this PR to have a rather large impact. Definitely should block this until after the next release.

* Builtin externs are now arranged by environment specific folders
* Adds a NODEJS environment
* Replaces the --use_only_custom_externs flag with the --env=CUSTOM argument
* esX.js externs are now included for every environment - including CUSTOM
* Makes it easy to add future environments

@nicks Would you like to take a look at the NODEJS part? I think the externs may need tweaking.

Fixes #966

Patch:
diff --git a/build.xml b/build.xml
index 556f0ab24..d472483dd 100644
--- a/build.xml
+++ b/build.xml
@@ -173,15 +173,16 @@
     <copy todir="${classes.dir}/com/google/javascript/jscomp/js">
       <fileset dir="${src.dir}/com/google/javascript/jscomp/js" />
     </copy>
+
+    <!-- Zip the externs and move them to the classes folder -->
+    <zip destfile="${classes.dir}/externs.zip" basedir="${externs.dir}" includes="**/*.js" />
   </target>
 
   <target name="jar"
           depends="compile"
           description="package compiler as an executable jar">
-    <zip destfile="${build.dir}/externs.zip" basedir="${externs.dir}" includes="*.js" />
     <jar destfile="${compiler-jarfile}" update="true">
       <fileset dir="${classes.dir}" />
-      <fileset dir="${build.dir}" includes="externs.zip" />
       <zipfileset src="${lib.dir}/args4j.jar"/>
       <zipfileset src="${lib.dir}/guava.jar"/>
       <zipfileset src="${lib.dir}/gson.jar"/>
@@ -341,7 +342,6 @@
   <target name="webservice-jar"
           description="package the compiler and Webservice classes"
           depends="webservice-compile, compile">
-   <zip destfile="${build.dir}/externs.zip" basedir="${externs.dir}" includes="*.js" />
    <jar destfile="${webservice-jarfile}" update="true">
      <zipgroupfileset dir="${lib.dir}" includes="*.jar"/>
      <fileset dir="${classes.dir}" />
@@ -379,7 +379,6 @@
   <target name="refasterjs-jar"
           description="Packages the compiler and RefasterJS classes"
           depends="refasterjs-compile, compile">
-   <zip destfile="${build.dir}/externs.zip" basedir="${externs.dir}" includes="*.js" />
    <jar destfile="${refasterjs-jarfile}" update="true">
      <zipgroupfileset dir="${lib.dir}" includes="*.jar"/>
      <fileset dir="${build.dir}" includes="externs.zip" />
diff --git a/externs/chrome.js b/externs/browser/chrome.js
similarity index 100%
rename from externs/chrome.js
rename to externs/browser/chrome.js
diff --git a/externs/deprecated.js b/externs/browser/deprecated.js
similarity index 100%
rename from externs/deprecated.js
rename to externs/browser/deprecated.js
diff --git a/externs/fetchapi.js b/externs/browser/fetchapi.js
similarity index 100%
rename from externs/fetchapi.js
rename to externs/browser/fetchapi.js
diff --git a/externs/fileapi.js b/externs/browser/fileapi.js
similarity index 100%
rename from externs/fileapi.js
rename to externs/browser/fileapi.js
diff --git a/externs/flash.js b/externs/browser/flash.js
similarity index 100%
rename from externs/flash.js
rename to externs/browser/flash.js
diff --git a/externs/gecko_css.js b/externs/browser/gecko_css.js
similarity index 100%
rename from externs/gecko_css.js
rename to externs/browser/gecko_css.js
diff --git a/externs/gecko_dom.js b/externs/browser/gecko_dom.js
similarity index 100%
rename from externs/gecko_dom.js
rename to externs/browser/gecko_dom.js
diff --git a/externs/gecko_event.js b/externs/browser/gecko_event.js
similarity index 100%
rename from externs/gecko_event.js
rename to externs/browser/gecko_event.js
diff --git a/externs/gecko_xml.js b/externs/browser/gecko_xml.js
similarity index 100%
rename from externs/gecko_xml.js
rename to externs/browser/gecko_xml.js
diff --git a/externs/google.js b/externs/browser/google.js
similarity index 100%
rename from externs/google.js
rename to externs/browser/google.js
diff --git a/externs/html5.js b/externs/browser/html5.js
similarity index 100%
rename from externs/html5.js
rename to externs/browser/html5.js
diff --git a/externs/ie_css.js b/externs/browser/ie_css.js
similarity index 100%
rename from externs/ie_css.js
rename to externs/browser/ie_css.js
diff --git a/externs/ie_dom.js b/externs/browser/ie_dom.js
similarity index 100%
rename from externs/ie_dom.js
rename to externs/browser/ie_dom.js
diff --git a/externs/ie_event.js b/externs/browser/ie_event.js
similarity index 100%
rename from externs/ie_event.js
rename to externs/browser/ie_event.js
diff --git a/externs/ie_vml.js b/externs/browser/ie_vml.js
similarity index 100%
rename from externs/ie_vml.js
rename to externs/browser/ie_vml.js
diff --git a/externs/intl.js b/externs/browser/intl.js
similarity index 100%
rename from externs/intl.js
rename to externs/browser/intl.js
diff --git a/externs/iphone.js b/externs/browser/iphone.js
similarity index 100%
rename from externs/iphone.js
rename to externs/browser/iphone.js
diff --git a/externs/mediasource.js b/externs/browser/mediasource.js
similarity index 100%
rename from externs/mediasource.js
rename to externs/browser/mediasource.js
diff --git a/externs/page_visibility.js b/externs/browser/page_visibility.js
similarity index 100%
rename from externs/page_visibility.js
rename to externs/browser/page_visibility.js
diff --git a/externs/streamsapi.js b/externs/browser/streamsapi.js
similarity index 100%
rename from externs/streamsapi.js
rename to externs/browser/streamsapi.js
diff --git a/externs/v8.js b/externs/browser/v8.js
similarity index 100%
rename from externs/v8.js
rename to externs/browser/v8.js
diff --git a/externs/w3c_anim_timing.js b/externs/browser/w3c_anim_timing.js
similarity index 100%
rename from externs/w3c_anim_timing.js
rename to externs/browser/w3c_anim_timing.js
diff --git a/externs/w3c_audio.js b/externs/browser/w3c_audio.js
similarity index 100%
rename from externs/w3c_audio.js
rename to externs/browser/w3c_audio.js
diff --git a/externs/w3c_batterystatus.js b/externs/browser/w3c_batterystatus.js
similarity index 100%
rename from externs/w3c_batterystatus.js
rename to externs/browser/w3c_batterystatus.js
diff --git a/externs/w3c_css.js b/externs/browser/w3c_css.js
similarity index 100%
rename from externs/w3c_css.js
rename to externs/browser/w3c_css.js
diff --git a/externs/w3c_css3d.js b/externs/browser/w3c_css3d.js
similarity index 100%
rename from externs/w3c_css3d.js
rename to externs/browser/w3c_css3d.js
diff --git a/externs/w3c_device_sensor_event.js b/externs/browser/w3c_device_sensor_event.js
similarity index 100%
rename from externs/w3c_device_sensor_event.js
rename to externs/browser/w3c_device_sensor_event.js
diff --git a/externs/w3c_dom1.js b/externs/browser/w3c_dom1.js
similarity index 100%
rename from externs/w3c_dom1.js
rename to externs/browser/w3c_dom1.js
diff --git a/externs/w3c_dom2.js b/externs/browser/w3c_dom2.js
similarity index 100%
rename from externs/w3c_dom2.js
rename to externs/browser/w3c_dom2.js
diff --git a/externs/w3c_dom3.js b/externs/browser/w3c_dom3.js
similarity index 100%
rename from externs/w3c_dom3.js
rename to externs/browser/w3c_dom3.js
diff --git a/externs/w3c_elementtraversal.js b/externs/browser/w3c_elementtraversal.js
similarity index 100%
rename from externs/w3c_elementtraversal.js
rename to externs/browser/w3c_elementtraversal.js
diff --git a/externs/w3c_encoding.js b/externs/browser/w3c_encoding.js
similarity index 100%
rename from externs/w3c_encoding.js
rename to externs/browser/w3c_encoding.js
diff --git a/externs/w3c_event.js b/externs/browser/w3c_event.js
similarity index 100%
rename from externs/w3c_event.js
rename to externs/browser/w3c_event.js
diff --git a/externs/w3c_event3.js b/externs/browser/w3c_event3.js
similarity index 100%
rename from externs/w3c_event3.js
rename to externs/browser/w3c_event3.js
diff --git a/externs/w3c_geolocation.js b/externs/browser/w3c_geolocation.js
similarity index 100%
rename from externs/w3c_geolocation.js
rename to externs/browser/w3c_geolocation.js
diff --git a/externs/w3c_indexeddb.js b/externs/browser/w3c_indexeddb.js
similarity index 100%
rename from externs/w3c_indexeddb.js
rename to externs/browser/w3c_indexeddb.js
diff --git a/externs/w3c_navigation_timing.js b/externs/browser/w3c_navigation_timing.js
similarity index 100%
rename from externs/w3c_navigation_timing.js
rename to externs/browser/w3c_navigation_timing.js
diff --git a/externs/w3c_range.js b/externs/browser/w3c_range.js
similarity index 100%
rename from externs/w3c_range.js
rename to externs/browser/w3c_range.js
diff --git a/externs/w3c_rtc.js b/externs/browser/w3c_rtc.js
similarity index 100%
rename from externs/w3c_rtc.js
rename to externs/browser/w3c_rtc.js
diff --git a/externs/w3c_selectors.js b/externs/browser/w3c_selectors.js
similarity index 100%
rename from externs/w3c_selectors.js
rename to externs/browser/w3c_selectors.js
diff --git a/externs/w3c_serviceworker.js b/externs/browser/w3c_serviceworker.js
similarity index 100%
rename from externs/w3c_serviceworker.js
rename to externs/browser/w3c_serviceworker.js
diff --git a/externs/w3c_webcrypto.js b/externs/browser/w3c_webcrypto.js
similarity index 100%
rename from externs/w3c_webcrypto.js
rename to externs/browser/w3c_webcrypto.js
diff --git a/externs/w3c_xml.js b/externs/browser/w3c_xml.js
similarity index 100%
rename from externs/w3c_xml.js
rename to externs/browser/w3c_xml.js
diff --git a/externs/webgl.js b/externs/browser/webgl.js
similarity index 100%
rename from externs/webgl.js
rename to externs/browser/webgl.js
diff --git a/externs/webkit_css.js b/externs/browser/webkit_css.js
similarity index 100%
rename from externs/webkit_css.js
rename to externs/browser/webkit_css.js
diff --git a/externs/webkit_dom.js b/externs/browser/webkit_dom.js
similarity index 100%
rename from externs/webkit_dom.js
rename to externs/browser/webkit_dom.js
diff --git a/externs/webkit_event.js b/externs/browser/webkit_event.js
similarity index 100%
rename from externs/webkit_event.js
rename to externs/browser/webkit_event.js
diff --git a/externs/webkit_notifications.js b/externs/browser/webkit_notifications.js
similarity index 100%
rename from externs/webkit_notifications.js
rename to externs/browser/webkit_notifications.js
diff --git a/externs/webstorage.js b/externs/browser/webstorage.js
similarity index 100%
rename from externs/webstorage.js
rename to externs/browser/webstorage.js
diff --git a/externs/window.js b/externs/browser/window.js
similarity index 100%
rename from externs/window.js
rename to externs/browser/window.js
diff --git a/src/com/google/javascript/jscomp/AbstractCommandLineRunner.java b/src/com/google/javascript/jscomp/AbstractCommandLineRunner.java
index 8b2fc0d7a..2cdb1e541 100644
--- a/src/com/google/javascript/jscomp/AbstractCommandLineRunner.java
+++ b/src/com/google/javascript/jscomp/AbstractCommandLineRunner.java
@@ -821,11 +821,11 @@ abstract class AbstractCommandLineRunner<A extends Compiler,
   protected int doRun() throws FlagUsageException, IOException {
     Compiler.setLoggingLevel(Level.parse(config.loggingLevel));
 
-    List<SourceFile> externs = createExterns();
-
     compiler = createCompiler();
     B options = createOptions();
 
+    List<SourceFile> externs = createExterns(options);
+
     List<JSModule> modules = null;
     Result result = null;
 
@@ -1086,8 +1086,8 @@ abstract class AbstractCommandLineRunner<A extends Compiler,
     return UTF_8;
   }
 
-  protected List<SourceFile> createExterns() throws FlagUsageException,
-      IOException {
+  protected List<SourceFile> createExterns(CompilerOptions options)
+      throws FlagUsageException, IOException {
     return isInTestMode() ? externsSupplierForTesting.get() :
         createExternInputs(config.externs);
   }
diff --git a/src/com/google/javascript/jscomp/CommandLineRunner.java b/src/com/google/javascript/jscomp/CommandLineRunner.java
index d6d15fc60..7bf5030d6 100644
--- a/src/com/google/javascript/jscomp/CommandLineRunner.java
+++ b/src/com/google/javascript/jscomp/CommandLineRunner.java
@@ -65,7 +65,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -380,12 +379,6 @@ public class CommandLineRunner extends
         "QUIET, DEFAULT, VERBOSE")
     private WarningLevel warningLevel = WarningLevel.DEFAULT;
 
-    @Option(name = "--use_only_custom_externs",
-        hidden = true,
-        handler = BooleanOptionHandler.class,
-        usage = "Specifies whether the default externs should be excluded")
-    private boolean useOnlyCustomExterns = false;
-
     @Option(name = "--debug",
         hidden = true,
         handler = BooleanOptionHandler.class,
@@ -589,6 +582,13 @@ public class CommandLineRunner extends
         usage = "A list of JS Conformance configurations in text protocol buffer format.")
     private List<String> conformanceConfigs = new ArrayList<>();
 
+    @Option(name = "--env",
+        hidden = true,
+        usage = "Determines the set of builtin externs to load. "
+            + "Options: BROWSER, CUSTOM. Defaults to BROWSER.")
+    private CompilerOptions.Environment environment =
+        CompilerOptions.Environment.BROWSER;
+
     @Argument
     private List<String> arguments = new ArrayList<>();
     private final CmdLineParser parser;
@@ -1128,6 +1128,8 @@ public class CommandLineRunner extends
       level.setDebugOptionsForCompilationLevel(options);
     }
 
+    options.setEnvironment(flags.environment);
+
     options.setChecksOnly(flags.checksOnly);
 
     if (flags.useTypesForOptimization) {
@@ -1191,15 +1193,15 @@ public class CommandLineRunner extends
   }
 
   @Override
-  protected List<SourceFile> createExterns() throws FlagUsageException,
-      IOException {
-    List<SourceFile> externs = super.createExterns();
-    if (flags.useOnlyCustomExterns || isInTestMode()) {
+  protected List<SourceFile> createExterns(CompilerOptions options)
+      throws FlagUsageException, IOException {
+    List<SourceFile> externs = super.createExterns(options);
+    if (isInTestMode()) {
       return externs;
     } else {
-      List<SourceFile> defaultExterns = getDefaultExterns();
-      defaultExterns.addAll(externs);
-      return defaultExterns;
+      List<SourceFile> builtinExterns = getBuiltinExterns(options);
+      builtinExterns.addAll(externs);
+      return builtinExterns;
     }
   }
 
@@ -1238,79 +1240,42 @@ public class CommandLineRunner extends
     return builder.build();
   }
 
-  // The externs expected in externs.zip, in sorted order.
-  private static final List<String> DEFAULT_EXTERNS_NAMES = ImmutableList.of(
-    // JS externs
-    "es3.js",
-    "es5.js",
-    "es6.js",
-    "es6_collections.js",
-    "intl.js",
-
-    // Event APIs
-    "w3c_event.js",
-    "w3c_event3.js",
-    "gecko_event.js",
-    "ie_event.js",
-    "webkit_event.js",
-    "w3c_device_sensor_event.js",
-
-    // DOM apis
-    "w3c_dom1.js",
-    "w3c_dom2.js",
-    "w3c_dom3.js",
-    "gecko_dom.js",
-    "ie_dom.js",
-    "webkit_dom.js",
-
-    // CSS apis
-    "w3c_css.js",
-    "gecko_css.js",
-    "ie_css.js",
-    "webkit_css.js",
-
-    // Top-level namespaces
-    "google.js",
-
-    "chrome.js",
-
-    "deprecated.js",
-    "fetchapi.js",
-    "fileapi.js",
-    "flash.js",
-    "gecko_xml.js",
-    "html5.js",
-    "ie_vml.js",
-    "iphone.js",
-    "mediasource.js",
-    "page_visibility.js",
-    "streamsapi.js",
-    "v8.js",
-    "webstorage.js",
-    "w3c_anim_timing.js",
-    "w3c_audio.js",
-    "w3c_batterystatus.js",
-    "w3c_encoding.js",
-    "w3c_css3d.js",
-    "w3c_elementtraversal.js",
-    "w3c_geolocation.js",
-    "w3c_indexeddb.js",
-    "w3c_navigation_timing.js",
-    "w3c_range.js",
-    "w3c_rtc.js",
-    "w3c_selectors.js",
-    "w3c_serviceworker.js",
-    "w3c_webcrypto.js",
-    "w3c_xml.js",
-    "window.js",
-    "webkit_notifications.js",
-    "webgl.js");
+  // The core language externs expected in externs.zip, in sorted order.
+  private static final List<String> BUILTIN_LANG_EXTERNS = ImmutableList.of(
+      "es3.js",
+      "es5.js",
+      "es6.js",
+      "es6_collections.js");
+
+  // Externs expected in externs.zip, in sorted order.
+  // Externs not included in this list will be added last
+  private static final List<String> BUILTIN_EXTERN_DEP_ORDER = ImmutableList.of(
+    //-- browser externs --
+    "browser/intl.js",
+    "browser/w3c_event.js",
+    "browser/w3c_event3.js",
+    "browser/gecko_event.js",
+    "browser/ie_event.js",
+    "browser/webkit_event.js",
+    "browser/w3c_device_sensor_event.js",
+    "browser/w3c_dom1.js",
+    "browser/w3c_dom2.js",
+    "browser/w3c_dom3.js",
+    "browser/gecko_dom.js",
+    "browser/ie_dom.js",
+    "browser/webkit_dom.js",
+    "browser/w3c_css.js",
+    "browser/gecko_css.js",
+    "browser/ie_css.js",
+    "browser/webkit_css.js"
+  );
 
   /**
    * @return a mutable list
    * @throws IOException
    */
-  public static List<SourceFile> getDefaultExterns() throws IOException {
+  public static List<SourceFile> getBuiltinExterns(CompilerOptions options)
+      throws IOException {
     InputStream input = CommandLineRunner.class.getResourceAsStream(
         "/externs.zip");
     if (input == null) {
@@ -1320,30 +1285,54 @@ public class CommandLineRunner extends
     Preconditions.checkNotNull(input);
 
     ZipInputStream zip = new ZipInputStream(input);
+    final String envPrefix = options.getEnvironment().toString().toLowerCase()
+        + "/";
+
     Map<String, SourceFile> externsMap = new HashMap<>();
     for (ZipEntry entry = null; (entry = zip.getNextEntry()) != null; ) {
-      BufferedInputStream entryStream = new BufferedInputStream(
-          ByteStreams.limit(zip, entry.getSize()));
-      externsMap.put(entry.getName(),
-          SourceFile.fromInputStream(
-              // Give the files an odd prefix, so that they do not conflict
-              // with the user's files.
-              "externs.zip//" + entry.getName(),
-              entryStream,
-              UTF_8));
+      // Only load externs in the root folder or in a subfolder matching
+      // the specified environment
+      if (entry.getName().indexOf("/") < 0 ||
+          (entry.getName().indexOf(envPrefix) == 0 &&
+              entry.getName().length() > envPrefix.length())) {
+        BufferedInputStream entryStream = new BufferedInputStream(
+            ByteStreams.limit(zip, entry.getSize()));
+        externsMap.put(entry.getName(),
+            SourceFile.fromInputStream(
+                // Give the files an odd prefix, so that they do not conflict
+                // with the user's files.
+                "externs.zip//" + entry.getName(),
+                entryStream,
+                UTF_8));
+      }
     }
 
-    Preconditions.checkState(
-        externsMap.keySet().equals(new HashSet<>(DEFAULT_EXTERNS_NAMES)),
-        "Externs zip must match our hard-coded list of externs.");
-
-    // Order matters, so the resources must be added to the result list
-    // in the expected order.
     List<SourceFile> externs = new ArrayList<>();
-    for (String key : DEFAULT_EXTERNS_NAMES) {
-      externs.add(externsMap.get(key));
+
+    // The externs for core JS objects are loaded in all environments.
+    for (String key : BUILTIN_LANG_EXTERNS) {
+      Preconditions.checkState(
+          externsMap.containsKey(key),
+          "Externs zip must contain " + key + ".");
+      externs.add(externsMap.remove(key));
+    }
+
+    // Order matters, so extern resources which have dependencies must be added
+    // to the result list in the expected order.
+    for (String key : BUILTIN_EXTERN_DEP_ORDER) {
+      if (key.indexOf(envPrefix) < 0) {
+        continue;
+      }
+
+      Preconditions.checkState(
+          externsMap.containsKey(key),
+          "Externs zip must contain " + key + " when environment is " +
+              options.getEnvironment().toString());
+      externs.add(externsMap.remove(key));
     }
 
+    externs.addAll(externsMap.values());
+
     return externs;
   }
 
diff --git a/src/com/google/javascript/jscomp/CompilerOptions.java b/src/com/google/javascript/jscomp/CompilerOptions.java
index cbdd3214c..d90142cbd 100644
--- a/src/com/google/javascript/jscomp/CompilerOptions.java
+++ b/src/com/google/javascript/jscomp/CompilerOptions.java
@@ -76,6 +76,11 @@ public class CompilerOptions implements Serializable {
    */
   private LanguageMode languageOut;
 
+  /**
+   * The builtin set of externs to be used
+   */
+  private Environment environment;
+
   /**
    * If true, don't transpile ES6 to ES3.
    *  WARNING: Enabling this option will likely cause the compiler to crash
@@ -961,6 +966,9 @@ public class CompilerOptions implements Serializable {
     languageIn = LanguageMode.ECMASCRIPT3;
     languageOut = LanguageMode.NO_TRANSPILE;
 
+    // Which environment to use
+    environment = Environment.BROWSER;
+
     // Language variation
     acceptTypeSyntax = false;
 
@@ -1647,6 +1655,17 @@ public class CompilerOptions implements Serializable {
     return languageOut;
   }
 
+  /**
+   * Set which set of builtin externs to use.
+   */
+  public void setEnvironment(Environment environment) {
+    this.environment = environment;
+  }
+
+  public Environment getEnvironment() {
+    return environment;
+  }
+
   /**
    * @return whether we are currently transpiling from ES6 to a lower version.
    */
@@ -2551,4 +2570,20 @@ public class CompilerOptions implements Serializable {
       }
     }
   }
+
+  /**
+   * An environment specifies the built-in externs that are loaded for a given
+   * compilation.
+   */
+  public static enum Environment {
+    /**
+     * Hand crafted externs that have traditionally been the default externs.
+     */
+    BROWSER,
+
+    /**
+     * Only language externs are loaded.
+     */
+    CUSTOM
+  }
 }
diff --git a/src/com/google/javascript/jscomp/ProcessCommonJSModules.java b/src/com/google/javascript/jscomp/ProcessCommonJSModules.java
index d0fe3cce1..491a6b1a8 100644
--- a/src/com/google/javascript/jscomp/ProcessCommonJSModules.java
+++ b/src/com/google/javascript/jscomp/ProcessCommonJSModules.java
@@ -244,14 +244,16 @@ public final class ProcessCommonJSModules implements CompilerPass {
     private void processExports(Node script, String moduleName) {
       if (hasOneTopLevelModuleExportAssign()) {
         // One top-level assign: transform to
-        // moduleName = rhs
+        // /** @const */ var moduleName = rhs
         Node ref = moduleExportRefs.get(0);
         Node newName = IR.name(moduleName);
         newName.putProp(Node.ORIGINALNAME_PROP, ref.getQualifiedName());
 
+        Node newVar = IR.var(newName)
+            .copyInformationFromForTree(ref.getParent());
         Node rhsValue = ref.getNext().detachFromParent();
-        Node newExprResult = IR.exprResult(IR.assign(newName, rhsValue)
-          .copyInformationFromForTree(ref.getParent()));
+        newVar.getFirstChild().addChildToFront(rhsValue);
+        newVar.setJSDocInfo(NodeUtil.createConstantJsDoc());
 
         // If the rValue is an object literal, check each property to see if
         // it's an alias, and if it is, copy the annotation over.
@@ -277,14 +279,20 @@ public final class ProcessCommonJSModules implements CompilerPass {
 
         Node assign = ref.getParent();
         Node exprResult = assign.getParent();
-        script.replaceChild(exprResult, newExprResult);
+        script.replaceChild(exprResult, newVar);
         return;
       }
 
       if (!hasExportLValues()) {
         // Transform to:
         //
+        // /** @const */ var moduleName = {};
         // moduleName.prop0 = 0; // etc.
+        //
+        // We consider the 0-ref case a special case of this.
+        Node newVar = injectExportsObject(script, moduleName);
+        newVar.setJSDocInfo(NodeUtil.createConstantJsDoc());
+
         for (Node ref : Iterables.concat(moduleExportRefs, exportRefs)) {
           Node newRef = IR.name(moduleName).copyInformationFrom(ref);
           newRef.putProp(Node.ORIGINALNAME_PROP, ref.getQualifiedName());
@@ -293,19 +301,23 @@ public final class ProcessCommonJSModules implements CompilerPass {
         return;
       }
 
+      // The general case:
+      // At the beginning, add the stanza:
+      // var moduleName = {}; var moduleName$$exports = moduleName;
       // Transform module.exports to moduleName
+      // Transform exports to moduleName$$exports
+      Node exportsNode = injectExportsObject(script, moduleName);
+
       for (Node ref : moduleExportRefs) {
         Node newRef = IR.name(moduleName).copyInformationFrom(ref);
         ref.getParent().replaceChild(ref, newRef);
       }
 
-      // Transform exports to exports$$moduleName and set to point
-      // to module namespace: exports$$moduleName = moduleName;
       if (!exportRefs.isEmpty()) {
         String aliasName = "exports$$" + moduleName;
         Node aliasNode = IR.var(IR.name(aliasName), IR.name(moduleName))
             .copyInformationFromForTree(script);
-        script.addChildToFront(aliasNode);
+        script.addChildAfter(aliasNode, exportsNode);
 
         for (Node ref : exportRefs) {
           ref.putProp(Node.ORIGINALNAME_PROP, ref.getString());
@@ -314,6 +326,17 @@ public final class ProcessCommonJSModules implements CompilerPass {
       }
     }
 
+    /**
+     * Creates an exports object for this module.
+     * var moduleName = {};
+     */
+    private Node injectExportsObject(Node script, String moduleName) {
+      Node varNode = IR.var(IR.name(moduleName), IR.objectlit())
+          .copyInformationFromForTree(script);
+      script.addChildToFront(varNode);
+      return varNode;
+    }
+
     /**
      * Recognize export pattern [1] (see above).
      */
diff --git a/src/com/google/javascript/jscomp/ant/CompileTask.java b/src/com/google/javascript/jscomp/ant/CompileTask.java
index 3c2a99dd3..7ec9e4117 100644
--- a/src/com/google/javascript/jscomp/ant/CompileTask.java
+++ b/src/com/google/javascript/jscomp/ant/CompileTask.java
@@ -76,7 +76,7 @@ public final class CompileTask
   private String encoding = "UTF-8";
   private String outputEncoding = "UTF-8";
   private CompilationLevel compilationLevel;
-  private boolean customExternsOnly;
+  private CompilerOptions.Environment environment;
   private boolean manageDependencies;
   private boolean prettyPrint;
   private boolean printInputDelimiter;
@@ -104,7 +104,7 @@ public final class CompileTask
     this.warningLevel = WarningLevel.DEFAULT;
     this.debugOptions = false;
     this.compilationLevel = CompilationLevel.SIMPLE_OPTIMIZATIONS;
-    this.customExternsOnly = false;
+    this.environment = CompilerOptions.Environment.BROWSER;
     this.manageDependencies = false;
     this.prettyPrint = false;
     this.printInputDelimiter = false;
@@ -172,6 +172,25 @@ public final class CompileTask
     }
   }
 
+  /**
+   * Set the environment which determines the builtin extern set.
+   * @param value The name of the environment.
+   *     (BROWSER, CUSTOM).
+   */
+  public void setEnvironment(String value) {
+    switch (value) {
+      case "BROWSER":
+        this.environment = CompilerOptions.Environment.BROWSER;
+        break;
+      case "CUSTOM":
+        this.environment = CompilerOptions.Environment.CUSTOM;
+        break;
+      default:
+        throw new BuildException(
+            "Unrecognized 'environment' option value (" + value + ")");
+    }
+  }
+
   /**
    * Enable debugging options.
    * @param value True if debug mode is enabled.
@@ -202,13 +221,6 @@ public final class CompileTask
     this.manageDependencies = value;
   }
 
-  /**
-   * Use only custom externs.
-   */
-  public void setCustomExternsOnly(boolean value) {
-    this.customExternsOnly = value;
-  }
-
   /**
    * Set output file.
    */
@@ -352,7 +364,7 @@ public final class CompileTask
     CompilerOptions options = createCompilerOptions();
     Compiler compiler = createCompiler(options);
 
-    List<SourceFile> externs = findExternFiles();
+    List<SourceFile> externs = findExternFiles(options);
     List<SourceFile> sources = findSourceFiles();
 
     if (isStale() || forceRecompile) {
@@ -421,6 +433,8 @@ public final class CompileTask
       this.compilationLevel.setDebugOptionsForCompilationLevel(options);
     }
 
+    options.setEnvironment(this.environment);
+
     options.setPrettyPrint(this.prettyPrint);
     options.setPrintInputDelimiter(this.printInputDelimiter);
     options.setPreferSingleQuotes(this.preferSingleQuotes);
@@ -597,11 +611,9 @@ public final class CompileTask
     return compiler;
   }
 
-  private List<SourceFile> findExternFiles() {
+  private List<SourceFile> findExternFiles(CompilerOptions options) {
     List<SourceFile> files = new LinkedList();
-    if (!this.customExternsOnly) {
-      files.addAll(getDefaultExterns());
-    }
+    files.addAll(getBuiltinExterns(options));
 
     for (FileList list : this.externFileLists) {
       files.addAll(findJavaScriptFiles(list));
@@ -648,9 +660,9 @@ public final class CompileTask
    *
    * Adapted from {@link CommandLineRunner}.
    */
-  private List<SourceFile> getDefaultExterns() {
+  private List<SourceFile> getBuiltinExterns(CompilerOptions options) {
     try {
-      return CommandLineRunner.getDefaultExterns();
+      return CommandLineRunner.getBuiltinExterns(options);
     } catch (IOException e) {
       throw new BuildException(e);
     }
diff --git a/src/com/google/javascript/refactoring/RefasterJs.java b/src/com/google/javascript/refactoring/RefasterJs.java
index dc161f1b5..06fb5d5cd 100644
--- a/src/com/google/javascript/refactoring/RefasterJs.java
+++ b/src/com/google/javascript/refactoring/RefasterJs.java
@@ -21,6 +21,7 @@ import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.javascript.jscomp.CommandLineRunner;
+import com.google.javascript.jscomp.CompilerOptions;
 import com.google.javascript.jscomp.ErrorManager;
 import com.google.javascript.jscomp.SourceFile;
 
@@ -67,9 +68,10 @@ final class RefasterJs {
       usage = "Location of the JS file to use as the RefasterJS template.")
   private String refasterJsTemplate = null;
 
-  @Option(name = "--include_default_externs",
-      usage = "Whether to include the standard JavaScript externs. Defaults to true.")
-  private boolean includeDefaultExterns = true;
+  @Option(name = "--env",
+      usage = "Which set of externs to include. Defaults to BROWSER.")
+  private CompilerOptions.Environment environment =
+      CompilerOptions.Environment.BROWSER;
 
   @Option(name = "--dry_run",
       usage = "Use this to display what changes would be made without applying the changes.")
@@ -108,9 +110,10 @@ final class RefasterJs {
 
     RefasterJsScanner scanner = new RefasterJsScanner();
     scanner.loadRefasterJsTemplate(refasterJsTemplate);
+    CompilerOptions options = new CompilerOptions();
+    options.setEnvironment(environment);
     RefactoringDriver driver = new RefactoringDriver.Builder(scanner)
-        .addExterns(includeDefaultExterns
-            ? CommandLineRunner.getDefaultExterns() : ImmutableList.<SourceFile>of())
+        .addExterns(CommandLineRunner.getBuiltinExterns(options))
         .addExternsFromFile(getExterns())
         .addInputsFromFile(fileInputs)
         .build();


Test patch:
diff --git a/test/com/google/javascript/jscomp/CommandLineRunnerTest.java b/test/com/google/javascript/jscomp/CommandLineRunnerTest.java
index 6793e0596..3100d7264 100644
--- a/test/com/google/javascript/jscomp/CommandLineRunnerTest.java
+++ b/test/com/google/javascript/jscomp/CommandLineRunnerTest.java
@@ -900,7 +900,7 @@ public final class CommandLineRunnerTest extends TestCase {
 
   public void testOnlyClosureDependenciesEmptyEntryPoints() throws Exception {
     // Prevents this from trying to load externs.zip
-    args.add("--use_only_custom_externs=true");
+    args.add("--env=CUSTOM");
 
     args.add("--only_closure_dependencies=true");
     try {
@@ -1007,7 +1007,7 @@ public final class CommandLineRunnerTest extends TestCase {
 
   public void testSourceMapLocationsTranslations3() {
     // Prevents this from trying to load externs.zip
-    args.add("--use_only_custom_externs=true");
+    args.add("--env=CUSTOM");
 
     args.add("--js_output_file");
     args.add("/path/to/out.js");
@@ -1280,7 +1280,7 @@ public final class CommandLineRunnerTest extends TestCase {
   }
 
   public void testNoSrCFilesWithManifest() throws IOException {
-    args.add("--use_only_custom_externs=true");
+    args.add("--env=CUSTOM");
     args.add("--output_manifest=test.MF");
     CommandLineRunner runner = createCommandLineRunner(new String[0]);
     String expectedMessage = "";
diff --git a/test/com/google/javascript/jscomp/ProcessCommonJSModulesTest.java b/test/com/google/javascript/jscomp/ProcessCommonJSModulesTest.java
index 7ec1936eb..e2475e16a 100644
--- a/test/com/google/javascript/jscomp/ProcessCommonJSModulesTest.java
+++ b/test/com/google/javascript/jscomp/ProcessCommonJSModulesTest.java
@@ -68,6 +68,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
     testModules(
         "var name = require('other');" + "name()",
         "goog.provide('module$test');"
+            + "var module$test = {};"
             + "goog.require('module$other');"
             + "var name$$module$test = module$other;"
             + "name$$module$test();");
@@ -79,6 +80,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
             SourceFile.fromCode(
                 "test/sub.js", "var name = require('mod/name');" + "(function() { name(); })();")),
         "goog.provide('module$test$sub');"
+            + "var module$test$sub = {};"
             + "goog.require('module$mod$name');"
             + "var name$$module$test$sub = module$mod$name;"
             + "(function() { name$$module$test$sub(); })();");
@@ -89,6 +91,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
     testModules(
         "var name = require('other');" + "exports.foo = 1;",
         "goog.provide('module$test');"
+            + "/** @const */ var module$test = {};"
             + "goog.require('module$other');"
             + "var name$$module$test = module$other;"
             + "module$test.foo = 1;");
@@ -97,7 +100,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
         "goog.provide('module$test');"
             + "goog.require('module$other');"
             + "var name$$module$test = module$other;"
-            + "module$test = function () {};");
+            + "/** @const */ var module$test = function () {};");
   }
 
   public void testPropertyExports() {
@@ -105,6 +108,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
     testModules(
         "exports.one = 1;" + "module.exports.obj = {};" + "module.exports.obj.two = 2;",
         "goog.provide('module$test');"
+            + "/** @const */ var module$test = {};"
             + "module$test.one = 1;"
             + "module$test.obj = {};"
             + "module$test.obj.two = 2;");
@@ -115,6 +119,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
     testModules(
         "exports.one = 1;" + "module.exports = {};",
         "goog.provide('module$test');"
+            + "var module$test = {};"
             + "var exports$$module$test = module$test;"
             + "exports$$module$test.one = 1;"
             + "module$test = {};");
@@ -125,6 +130,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
     testModules(
         "var a = 1, b = 2;" + "(function() { var a; b = 4})()",
         "goog.provide('module$test');"
+            + "var module$test = {};"
             + "var a$$module$test = 1, b$$module$test = 2;"
             + "(function() { var a; b$$module$test = 4})();");
   }
@@ -134,6 +140,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
     testModules(
         "var name = require('other'); exports.foo = 1;",
         "goog.provide('module$test_test');"
+            + "var module$test_test = {};"
             + "goog.require('module$other');"
             + "var name$$module$test_test = module$other;"
             + "module$test_test.foo = 1;");
@@ -144,6 +151,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
     testModules(
         "var name = require('../other'); exports.bar = 1;",
         "goog.provide('module$foo$index');"
+            + "var module$foo$index = {};"
             + "goog.require('module$other');"
             + "var name$$module$foo$index = module$other;"
             + "module$foo$index.bar = 1;");
@@ -153,7 +161,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
     setFilename("foo/bar");
     testModules(
         "var name = require('other');",
-        "goog.provide('module$foo$bar');"
+        "goog.provide('module$foo$bar'); var module$foo$bar = {};"
             + "goog.require('module$other');"
             + "var name$$module$foo$bar = module$other;");
     ProcessEs6ModulesTest.testModules(
@@ -162,6 +170,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
             SourceFile.fromCode("foo/name.js", ""),
             SourceFile.fromCode("foo/bar.js", "var name = require('./name');")),
         "goog.provide('module$foo$bar');"
+            + "var module$foo$bar = {};"
             + "goog.require('module$foo$name');"
             + "var name$$module$foo$bar = module$foo$name;");
   }
@@ -173,13 +182,13 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
         "module.exports = foo;",
         "goog.provide('module$test');" +
         "var foo$$module$test=function(module){module.exports={}};" +
-        "module$test=foo$$module$test");
+        "var module$test=foo$$module$test");
     testModules(
         "var foo = function () {var module = {};module.exports = {};};" +
         "module.exports = foo;",
         "goog.provide('module$test');" +
         "var foo$$module$test=function(){var module={};module.exports={}};" +
-        "module$test=foo$$module$test");
+        "var module$test=foo$$module$test");
     testModules(
         "var foo = function () {if (true) var module = {};" +
         "module.exports = {};};" +
@@ -187,7 +196,7 @@ public final class ProcessCommonJSModulesTest extends CompilerTestCase {
         "goog.provide('module$test');" +
         "var foo$$module$test=function(){if(true)var module={};" +
         "module.exports={}};" +
-        "module$test=foo$$module$test");
+        "var module$test=foo$$module$test");
   }
 
   public void testSortInputs() throws Exception {