/** * Writes out all of the module files. This method is only applicable when modules are used. This * is expected to be used only with the build command. * * @throws IOException */ public void writeCompiledCodeToFiles( final Function<String, String> moduleNameToUri, String sourceMapPath) throws IOException { if (modules == null) { throw new IllegalStateException("This compilation does not use modules"); } ModuleConfig moduleConfig = config.getModuleConfig(); Map<String, File> moduleToOutputPath = moduleConfig.getModuleToOutputPath(); final Map<String, String> moduleNameToFingerprint = Maps.newHashMap(); final boolean isDebugMode = false; for (JSModule module : modules) { String moduleName = module.getName(); File outputFile = moduleToOutputPath.get(moduleName); com.google.common.io.Files.createParentDirs(outputFile); // Reset the source map if it is not going to be reset later in this // loop // when the source map is written to disk. final boolean resetSourceMap = (sourceMapPath == null); String moduleCode = getCodeForModule(moduleName, isDebugMode, moduleNameToUri, resetSourceMap); // Fingerprint the file, if appropriate. if (config.shouldFingerprintJsFiles()) { String fileName = outputFile.getName(); String fingerprint = Md5Util.hashJs(moduleCode); moduleNameToFingerprint.put(moduleName, fingerprint); fileName = insertFingerprintIntoName(fileName, fingerprint); outputFile = new File(outputFile.getParentFile(), fileName); } Files.write(moduleCode, outputFile); // It turns out that the SourceMap will not be populated until after // the // Compiler's internal representation has been output as source // code, so // it should only be written out to a file after the compiled code // has // been generated. if (sourceMapPath != null) { Writer writer = Streams.createFileWriter(sourceMapPath + "_" + moduleName, config); // This is safe because getCodeForModule() was just called, // which has // the side-effect of calling compiler.toSource(module). SourceMap sourceMap = compiler.getSourceMap(); sourceMap.appendTo(writer, moduleName); sourceMap.reset(); Closeables.close(writer, false); } } if (moduleConfig.excludeModuleInfoFromRootModule()) { File outputFile = moduleConfig.getModuleInfoPath(); com.google.common.io.Files.createParentDirs(outputFile); final Function<String, String> fingerprintedModuleNameToUri = new Function<String, String>() { @Override public String apply(String moduleName) { String uri = moduleNameToUri.apply(moduleName); String fingerprint = moduleNameToFingerprint.get(moduleName); if (fingerprint != null) { uri = insertFingerprintIntoName(uri, fingerprint); } return uri; } }; Writer writer = Streams.createFileWriter(outputFile, config); appendRootModuleInfo(writer, isDebugMode, fingerprintedModuleNameToUri); Closeables.close(writer, false); } }
/** * This method always calls compiler.toSource(module), which means that it can be followed by a * call to compiler.getSourceMap().appendTo(writer, module), though * compiler.getSourceMap().reset() should be called immediately after when that is the case. * * <p>This method is sychronized in order to fix * http://code.google.com/p/plovr/issues/detail?id=31. The problem is that if two modules are * requested at the same time, it is often the case that compiler.toSource(module) is executing * for the second module while the source map is still being modified for the first module, * causing an IllegalStateException. Empirically, synchronizing this method appears to fix things, * though a more provably correct (and minimal) solution should be sought. */ private synchronized String getCodeForModule( String moduleName, boolean isDebugMode, Function<String, String> moduleNameToUri, boolean resetSourceMap) { Preconditions.checkState(hasResult(), "Code has not been compiled yet"); Preconditions.checkState(modules != null, "This compilation does not use modules"); StringBuilder builder = new StringBuilder(); ModuleConfig moduleConfig = config.getModuleConfig(); String rootModule = moduleConfig.getRootModule(); boolean isRootModule = rootModule.equals(moduleName); if (isRootModule) { // For the root module, prepend the following global variables: // // PLOVR_MODULE_INFO // PLOVR_MODULE_URIS // PLOVR_MODULE_USE_DEBUG_MODE // // Because the standard way to read these variables in the // application is: // // moduleLoader.setDebugMode(!!goog.global['PLOVR_MODULE_USE_DEBUG_MODE']); // moduleManager.setLoader(moduleLoader); // moduleManager.setAllModuleInfo(goog.global['PLOVR_MODULE_INFO']); // moduleManager.setModuleUris(goog.global['PLOVR_MODULE_URIS']); // // It is important that the PLOVR variables are guaranteed to be // global, // which (as much as it pains me) is why "var" is omitted. if (!moduleConfig.excludeModuleInfoFromRootModule()) { try { appendRootModuleInfo(builder, isDebugMode, moduleNameToUri); } catch (IOException e) { // This should not occur because data is being appended to // an // in-memory StringBuilder rather than a file. throw new RuntimeException(e); } } } int lineOffset = 0; JSModule module = nameToModule.get(moduleName); StringBuffer prependBuffer = new StringBuffer(); if (isRootModule) { lineOffset++; // one line for the function wrapper for (File prepend : config.getPrependInputs()) { try { String s = Files.toString(prepend); if (s != null) { String[] lines = s.split("\n"); for (String line : lines) { prependBuffer.append(line); prependBuffer.append("\n"); lineOffset++; } } } catch (IOException e) { e.printStackTrace(); } } } compiler.setPrependedLineOffset(lineOffset); String moduleCode = compiler.toSource(module); boolean hasGlobalScopeName = !Strings.isNullOrEmpty(config.getGlobalScopeName()) && config.getCompilationMode() != CompilationMode.WHITESPACE; // Optionally wrap the module in an anonymous function, with the // requisite wrapper to make it work. if (hasGlobalScopeName) { if (isRootModule) { builder.append(prependBuffer.toString()); // Initialize the global scope in the root module. builder.append(config.getGlobalScopeName()); builder.append("={};"); } builder.append("(function("); builder.append(Config.GLOBAL_SCOPE_NAME); // Including a newline makes the offset into the source map easier to calculate. builder.append("){\n"); } builder.append(moduleCode); if (hasGlobalScopeName) { builder.append("})("); builder.append(config.getGlobalScopeName()); builder.append(");"); } if (resetSourceMap) { SourceMap sourceMap = compiler.getSourceMap(); if (sourceMap != null) sourceMap.reset(); } // http://code.google.com/p/closure-library/issues/detail?id=196 // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/ // non-root modules are loaded with eval, give it a sourceURL for better // debugging if (!isRootModule) { builder.append("\n//@ sourceURL=" + moduleNameToUri.apply(moduleName)); } return builder.toString(); }