Super Dev Mode: support standalone apps without precompile
Standalone, static apps hosted entirely on the Super Dev Mode
code server didn't work with -noprecompile because we didn't
copy the public resources to the stub CompileDir and because
binding properties weren't computed.
It now copies the files and generates binding properties.
Change-Id: Ib01b3783aef18e0041370067bd056f5c50339d61
Review-Link: https://gwt-review.googlesource.com/#/c/9137/
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
index 08171ca..0a84805 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
@@ -285,5 +285,4 @@
File prefix = new File(getOutputDir().getExtraDir(), getOutputModuleName());
return new File(prefix, path);
}
-
}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
index 14f547e..7bd1d72 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
@@ -19,6 +19,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.impl.PropertiesUtil;
import com.google.gwt.core.linker.CrossSiteIframeLinker;
import com.google.gwt.core.linker.IFrameLinker;
import com.google.gwt.dev.Compiler;
@@ -46,6 +47,7 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
@@ -183,38 +185,84 @@
CompilerOptions loadOptions = new CompilerOptionsImpl(compileDir, inputModuleName, options);
compilerContext = compilerContextBuilder.options(loadOptions).unitCache(
Compiler.getOrCreateUnitCache(logger, loadOptions)).build();
+
+ // Loads and parses all the Java files in the GWT application using the JDT.
+ // (This is warmup to make compiling faster later; we stop at this point to avoid
+ // needing to know the binding properties.)
module.getCompilationState(compileLogger, compilerContext);
- String newModuleName = module.getName(); // includes any rename.
- outputModuleName.set(newModuleName);
+ setUpCompileDir(compileDir, module, compileLogger);
+ outputModuleName.set(module.getName());
lastBuild.set(compileDir);
- try {
- // Prepare directory.
- File outputDir = new File(
- compileDir.getWarDir().getCanonicalPath() + "/" + getOutputModuleName());
- if (!outputDir.exists()) {
- if (!outputDir.mkdir()) {
- compileLogger.log(TreeLogger.Type.WARN, "cannot create directory: " + outputDir);
- }
- }
- // Creates a "module_name.nocache.js" that just forces a recompile.
- String moduleScript = PageUtil.loadResource(Recompiler.class, "nomodule.nocache.js");
- moduleScript = moduleScript.replace("__MODULE_NAME__", getOutputModuleName());
- PageUtil.writeFile(outputDir.getCanonicalPath() + "/" + getOutputModuleName() + ".nocache.js",
- moduleScript);
-
- } catch (IOException e) {
- compileLogger.log(TreeLogger.Type.ERROR, "Error creating uncompiled module.", e);
- }
long elapsedTime = System.currentTimeMillis() - startTime;
compileLogger.log(TreeLogger.Type.INFO, "Module setup completed in " + elapsedTime + " ms");
return new Result(compileDir, null);
}
+ /**
+ * Prepares a stub compile directory.
+ * It will include all "public" resources and a nocache.js file that invokes the compiler.
+ */
+ private static void setUpCompileDir(CompileDir compileDir, ModuleDef module,
+ TreeLogger compileLogger) throws UnableToCompleteException {
+ try {
+ String outputModuleName = module.getName();
+
+ // Create the directory.
+ File outputDir = new File(
+ compileDir.getWarDir().getCanonicalPath() + "/" + outputModuleName);
+ if (!outputDir.exists()) {
+ if (!outputDir.mkdir()) {
+ compileLogger.log(Type.WARN, "cannot create directory: " + outputDir);
+ }
+ }
+
+ // Copy the public resources to the output.
+ ResourceOracleImpl publicResources = module.getPublicResourceOracle();
+ for (String pathName : publicResources.getPathNames()) {
+ File file = new File(outputDir, pathName);
+ File parent = file.getParentFile();
+ if (!parent.isDirectory() && !parent.mkdirs()) {
+ compileLogger.log(Type.ERROR, "cannot create directory: " + parent);
+ throw new UnableToCompleteException();
+ }
+ Files.copy(publicResources.getResourceAsStream(pathName), file.toPath());
+ }
+
+ // Create a "module_name.nocache.js" that calculates the permutation and forces a recompile.
+ String stubJs = generateStub(module, compileLogger);
+ PageUtil.writeFile(outputDir.getCanonicalPath() + "/" + outputModuleName + ".nocache.js",
+ stubJs);
+
+ } catch (IOException e) {
+ compileLogger.log(Type.ERROR, "Error creating stub compile directory.", e);
+ UnableToCompleteException wrapped = new UnableToCompleteException();
+ wrapped.initCause(e);
+ throw wrapped;
+ }
+ }
+
+ /**
+ * Generates the nocache.js file to use when precompile is not on.
+ */
+ private static String generateStub(ModuleDef module, TreeLogger compileLogger)
+ throws IOException, UnableToCompleteException {
+
+ String outputModuleName = module.getName();
+
+ String stub = PageUtil.loadResource(Recompiler.class, "nomodule.nocache.js");
+
+ return "(function() {\n"
+ + " var moduleName = '" + outputModuleName + "';\n"
+ + PropertiesUtil.generatePropertiesSnippet(module, compileLogger)
+ + stub
+ + "})();\n";
+ }
+
private boolean doCompile(TreeLogger compileLogger, CompileDir compileDir, Job job)
throws UnableToCompleteException {
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/computeScriptBase.js b/dev/codeserver/java/com/google/gwt/dev/codeserver/computeScriptBase.js
index 7b30dea..04fd716 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/computeScriptBase.js
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/computeScriptBase.js
@@ -25,6 +25,8 @@
* script tag with an absolute URL to head. (However, it's also okay for an html
* file included in the GWT compiler's output to load the nocache.js file using
* a relative URL.)
+ *
+ * As a side effect, sets superdevmode on in the __gwt_activeModules registry.
*/
function computeScriptBase() {
// TODO(skybrian) This approach won't work for workers.
@@ -48,5 +50,6 @@
}
}
- $wnd.alert('Unable to load Super Dev Mode version of ' + __MODULE_NAME__ + ".");
+ $wnd.alert('Unable to load Super Dev Mode version of __MODULE_NAME__.');
+ return null;
}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/dev_mode_on.js b/dev/codeserver/java/com/google/gwt/dev/codeserver/dev_mode_on.js
index c8f7124..cf434a3 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/dev_mode_on.js
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/dev_mode_on.js
@@ -546,9 +546,16 @@
if (module_name && !error) {
// The user clicked a "Compile" bookmarklet and we believe it will
// succeed.
- var active_modules = window.__gwt_activeModules;
- compile(module_name, params.server_url,
- active_modules[module_name].bindings);
+
+ // Use the binding properties provided in params if available.
+ // (This happens if we're being called from a stub rather than a bookmarklet.)
+ var getPropMap = params.getPropMap;
+ if (!getPropMap) {
+ // Probably a regular compile, so check in the page.
+ var active_modules = window.__gwt_activeModules;
+ getPropMap = active_modules[module_name].bindings;
+ }
+ compile(module_name, params.server_url, getPropMap);
} else {
// The user clicked the "Dev Mode On" bookmarklet or something is wrong.
showModuleDialog(params.server_url);
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/nomodule.nocache.js b/dev/codeserver/java/com/google/gwt/dev/codeserver/nomodule.nocache.js
index 0b5e8e0..419105c 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/nomodule.nocache.js
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/nomodule.nocache.js
@@ -15,33 +15,142 @@
*/
/**
- * @fileoverview Stub for non-compiled modules.
+ * @fileoverview A stub used in place of nocache.js files for modules that haven't been
+ * compiled yet.
*
- * This script forces the proper reload + compilation in Super Dev Mode.
+ * When the stub is loaded, it finds the appropriate code server and requests that it should
+ * recompile one module and reload the page.
+ *
+ * <p>It assumes the following variables are predefined:
+ * <ul>
+ * <li>moduleName is the name of the module to compile. (Output module name.)
+ * <li>providers is a map from binding properties (strings) to no-argument functions that
+ * compute the value of the binding variable for that property (string).
+ * <li>values is a map from a binding property name (string) to the set of possible
+ * values for that binding property (represented as a map with string keys).
+ * </ul>
+ *
+ * <p>This script should be wrapped in a function so that the variables defined here aren't global.
*/
-(function() {
- var moduleName = '__MODULE_NAME__'; // Replaced by actual module name.
+// These variables are used by property providers.
+// (We also use them below.)
+var $wnd = window;
+var $doc = document;
- // Active Super Dev Mode is assumed.
- var key = '__gwtDevModeHook:' + moduleName;
- if (!window.sessionStorage[key]) {
- alert('Unable to load Super Dev Mode version of ' + moduleName + '.');
- return;
+/**
+ * The URL used previously to load this module or null if Super Dev Mode is not on for this module.
+ */
+var previousUrl = $wnd.sessionStorage.getItem('__gwtDevModeHook:' + moduleName);
+
+/**
+ * The URL of the front page of the code server to use to compile the module.
+ */
+var codeServerUrl = (function () {
+
+ /**
+ * Converts a relative URL to absolute and returns the front page (ending with a slash).
+ */
+ function getFrontPage(url) {
+ var a = $doc.createElement('a');
+ a.href = url;
+ return a.protocol + '//' + a.host + '/';
}
- var scriptLocation = window.sessionStorage[key];
- // Get the Super Dev Mode Server URL: use the HTML a.href parsing.
- var a = document.createElement('a');
- a.href = scriptLocation;
- var devServerUrl = a.protocol + '//' + a.host;
+ /**
+ * Returns true if string 's' has the given suffix.
+ */
+ function hasSuffix(s, suffix) {
+ var startPos = s.length - suffix.length;
+ return s.indexOf(suffix, startPos) == startPos;
+ }
- // Load the bookmarklet.
- window.__gwt_bookmarklet_params = {
- 'server_url' : devServerUrl + '/',
- 'module_name': moduleName
+ /**
+ * Returns true if the given URL ends with {moduleName}.nocache.js,
+ * ignoring any query string or hash.
+ */
+ function isModuleNoCacheJs(url) {
+ // Remove trailing query string and/or fragment
+ url = url.split("?")[0].split("#")[0];
+ return hasSuffix(url, '/' + moduleName + '.nocache.js');
+ }
+
+ // If Super Dev Mode is already running for this module, use the same code server again.
+ if (previousUrl) {
+ return getFrontPage(previousUrl);
+ }
+
+ // Since Super Dev Mode is not on, an HTML page somewhere is directly including
+ // this nocache.js file from the code server.
+ //
+ // Some people run codeserver behind a proxy, for example to enable HTTPS support.
+ // So, we should figure out this script's URL from the client's point of view,
+ // similar to how computeScriptBase() does it but simpler. We do this by searching
+ // all script tags for the URL.
+ var scriptTagsToSearch = $doc.getElementsByTagName('script');
+ for (var i = 0; ; i++) {
+ var tag = scriptTagsToSearch[i];
+ if (!tag) {
+ break; // end of list; not found
+ }
+ if (tag && isModuleNoCacheJs(tag.src)) {
+ return getFrontPage(tag.src);
+ }
+ }
+
+ throw "unable to find the script tag that includes " + moduleName + ".nocache.js";
+}());
+
+var activeModules = $wnd.__gwt_activeModules = $wnd.__gwt_activeModules || {};
+if (!activeModules[moduleName]) {
+ // dev_mode_on.js checks for this.
+ activeModules[moduleName] = {canRedirect: true};
+}
+
+/**
+ * A function returning a map from binding property names to values.
+ */
+var getPropMap = (function() {
+
+ var module = activeModules[moduleName];
+ if (module.bindings) {
+ // The module is already running, so use the same bindings.
+ // (In this case, a dev mode hook probably redirected to this script.)
+ return module.bindings;
+ }
+
+ /**
+ * Returns the value of the given binding property name.
+ */
+ function computePropValue(propName) {
+ var val = providers[propName]();
+ // sanity check
+ var allowedValuesMap = values[propName];
+ if (val in allowedValuesMap) {
+ return val;
+ } else {
+ console.log("provider for " + propName + " returned unexpected value: '" + val + "'");
+ throw "can't compute binding property value for " + propName;
+ }
+ }
+
+ return function () {
+ var result = {};
+ for (var key in values) {
+ if (values.hasOwnProperty(key)) {
+ result[key] = computePropValue(key);
+ }
+ }
+ return result;
};
- var script = document.createElement('script');
- script.src = devServerUrl + '/dev_mode_on.js';
- document.getElementsByTagName('head')[0].appendChild(script);
-})();
+}());
+
+// Load the bookmarklet.
+window.__gwt_bookmarklet_params = {
+ 'server_url' : codeServerUrl,
+ 'module_name': moduleName,
+ 'getPropMap': getPropMap
+};
+var script = document.createElement('script');
+script.src = codeServerUrl + 'dev_mode_on.js';
+document.getElementsByTagName('head')[0].appendChild(script);
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
index 4134f74..1f3937e 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
@@ -22,6 +22,8 @@
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.ConfigurationProperty;
import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.jjs.JsOutputOption;
import java.util.Map.Entry;
import java.util.SortedSet;
@@ -59,17 +61,57 @@
// Add property providers
startPos = selectionScript.indexOf("// __PROPERTIES_END__");
- if (startPos != -1) {
- for (SelectionProperty p : context.getProperties()) {
- String text = generatePropertyProvider(logger, p,
- context.getConfigurationProperties());
- selectionScript.insert(startPos, text);
- startPos += text.length();
- }
+ if (startPos == -1) {
+ return selectionScript;
}
+ selectionScript.insert(startPos, generatePropertyProviders(logger, context));
return selectionScript;
}
+ /**
+ * Returns JavaScript that declares and initializes the "providers" and "values" variables.
+ *
+ * <p>Requires $doc and $wnd variables to be defined. (And possibly others; this is unclear.)
+ *
+ * <p>Provides "providers" and "values" variables.</p>.
+ *
+ * <ul>
+ * <li>"providers" is a mapping from each binding property (string) to a no-argument function
+ * that determines its value.</li>
+ * <li>"values" is a mapping from each binding property (string) to the set of allowed values
+ * (represented as a mapping with the value as key).
+ * </li>
+ * </ul>
+ */
+ public static String generatePropertiesSnippet(ModuleDef module, TreeLogger compileLogger)
+ throws UnableToCompleteException {
+
+ // TODO: PropertyProviderGenerator should specify the JavaScript environment that a
+ // property provider can assume so the caller of this function knows what it should provide.
+
+ LinkerContext linkerContext = new StandardLinkerContext(compileLogger, module,
+ module.getPublicResourceOracle(), JsOutputOption.PRETTY);
+
+ String initProvidersJs =
+ generatePropertyProviders(compileLogger, linkerContext);
+
+ return "var providers = {};\nvar values = {};\n" + initProvidersJs + "\n";
+ }
+
+ /**
+ * Generates JavaScript to create a property provider for each non-derived binding property
+ * in the given linker. The JavaScript mutates the "providers" and "values" variables which
+ * must already exist.
+ */
+ private static String generatePropertyProviders(TreeLogger logger, LinkerContext context)
+ throws UnableToCompleteException {
+ StringBuilder out = new StringBuilder();
+ for (SelectionProperty p : context.getProperties()) {
+ out.append(generatePropertyProvider(logger, p, context.getConfigurationProperties()));
+ }
+ return out.toString();
+ }
+
private static String generatePropertyProvider(TreeLogger logger,
SelectionProperty prop, SortedSet<ConfigurationProperty> configProps)
throws UnableToCompleteException {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 7e5f9bd..b33af9a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -31,7 +31,7 @@
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.Script;
import com.google.gwt.dev.jjs.InternalCompilerException;
-import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.jjs.JsOutputOption;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.js.JsLiteralInterner;
import com.google.gwt.dev.js.JsNamer.IllegalNameException;
@@ -117,7 +117,7 @@
private final SortedSet<ConfigurationProperty> configurationProperties;
- private final JJSOptions jjsOptions;
+ private final JsOutputOption outputOption;
private final List<Class<? extends Linker>> linkerClasses;
private Linker[] linkers;
@@ -133,7 +133,8 @@
private final SortedSet<SelectionProperty> selectionProperties;
public StandardLinkerContext(TreeLogger logger, ModuleDef module,
- ResourceOracle publicResourceOracle, JJSOptions jjsOptions) throws UnableToCompleteException {
+ ResourceOracle publicResourceOracle, JsOutputOption outputOption)
+ throws UnableToCompleteException {
logger = logger.branch(TreeLogger.DEBUG,
"Constructing StandardLinkerContext", null);
@@ -141,7 +142,7 @@
this.moduleName = module.getName();
this.moduleLastModified = module.lastModified();
this.publicResourceOracle = publicResourceOracle;
- this.jjsOptions = jjsOptions;
+ this.outputOption = outputOption;
// Sort the linkers into the order they should actually run.
linkerClasses = new ArrayList<Class<? extends Linker>>();
@@ -415,7 +416,7 @@
@Override
public boolean isOutputCompact() {
- return jjsOptions.getOutput().shouldMinimize();
+ return outputOption.shouldMinimize();
}
@Override
@@ -449,7 +450,7 @@
JsUnusedFunctionRemover.exec(jsProgram);
try {
- switch (jjsOptions.getOutput()) {
+ switch (outputOption) {
case OBFUSCATED:
/*
* We can't apply the regular JsLiteralInterner to the JsProgram that
@@ -478,8 +479,7 @@
throw new UnableToCompleteException();
}
- DefaultTextOutput out = new DefaultTextOutput(
- jjsOptions.getOutput().shouldMinimize());
+ DefaultTextOutput out = new DefaultTextOutput(outputOption.shouldMinimize());
JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
v.accept(jsProgram);
return out.toString();
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index d78c778..72e7d28 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -982,7 +982,7 @@
// Create a new active linker stack for the fresh link.
StandardLinkerContext linkerStack = new StandardLinkerContext(
- linkLogger, module, compilerContext.getPublicResourceOracle(), options);
+ linkLogger, module, compilerContext.getPublicResourceOracle(), options.getOutput());
ArtifactSet artifacts = linkerStack.getArtifactsForPublicResources(logger, module);
artifacts = linkerStack.invokeLegacyLinkers(linkLogger, artifacts);
artifacts = linkerStack.invokeFinalLink(linkLogger, artifacts);
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index 77ecb88..c2ebbb7 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -177,7 +177,8 @@
LinkOptions linkOptions)
throws UnableToCompleteException, IOException {
StandardLinkerContext linkerContext =
- new StandardLinkerContext(logger, module, publicResourceOracle, precompileOptions);
+ new StandardLinkerContext(logger, module, publicResourceOracle,
+ precompileOptions.getOutput());
ArtifactSet artifacts = doSimulatedShardingLink(
logger, module, linkerContext, generatedArtifacts, permutations, resultFiles, libraries);
@@ -205,7 +206,7 @@
JarOutputStream jar = new JarOutputStream(new FileOutputStream(jarFile));
StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
- module, publicResourceOracle, precompileOptions);
+ module, publicResourceOracle, precompileOptions.getOutput());
StandardCompilationResult compilation = new StandardCompilationResult(
permResult);
@@ -714,7 +715,7 @@
TreeLogger branch = logger.branch(TreeLogger.INFO, "Linking module "
+ module.getName());
StandardLinkerContext linkerContext = new StandardLinkerContext(branch,
- module, publicResourceOracle, precompileOptions);
+ module, publicResourceOracle, precompileOptions.getOutput());
try {
ArtifactSet artifacts = scanCompilePermResults(logger, resultFiles);
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index 5d3d3ab..88fa711 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -438,7 +438,7 @@
compilerContext = compilerContextBuilder.module(module).build();
StandardLinkerContext linkerContext = new StandardLinkerContext(
- TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(), options);
+ TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(), options.getOutput());
boolean generateOnShards = true;
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java b/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java
index e71165c..2d16854 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java
@@ -200,7 +200,7 @@
ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, compilerContext, moduleName);
compilerContext = compilerContextBuilder.module(module).build();
StandardLinkerContext linkerContext = new StandardLinkerContext(
- TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(), options);
+ TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(), options.getOutput());
if (!linkerContext.allLinkersAreShardable()) {
logger.log(TreeLogger.ERROR,
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 51c22da..2eada41 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -35,6 +35,7 @@
import com.google.gwt.dev.javac.CompilationProblemReporter;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.jjs.JsOutputOption;
import com.google.gwt.dev.shell.CheckForUpdates;
import com.google.gwt.dev.shell.jetty.JettyLauncher;
import com.google.gwt.dev.util.arg.ArgHandlerCompilePerFile;
@@ -1126,7 +1127,8 @@
try {
Linker l = module.getActivePrimaryLinker().newInstance();
StandardLinkerContext context = new StandardLinkerContext(
- getTopLogger(), module, compilerContext.getPublicResourceOracle(), null);
+ getTopLogger(), module, compilerContext.getPublicResourceOracle(),
+ JsOutputOption.PRETTY);
if (!l.supportsDevModeInJunit(context)) {
if (module.getLinker("std") != null) {
// TODO: unfortunately, this could be race condition between dev/prod