/* __ *\ ** ________ ___ / / ___ __ ____ Scala.js tools ** ** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** ** /____/\___/_/ |_/____/_/ | |__/ /____/ ** ** |/____/ ** \* */ package scala.scalajs.tools.optimizer import scala.scalajs.tools.sem.Semantics import scala.scalajs.tools.classpath._ import scala.scalajs.tools.logging._ import scala.scalajs.tools.io._ import scala.scalajs.tools.corelib.CoreJSLibs import com.google.javascript.jscomp.{ SourceFile => ClosureSource, Compiler => ClosureCompiler, CompilerOptions => ClosureOptions, _ } import scala.collection.JavaConverters._ import scala.collection.immutable.{Seq, Traversable} import java.net.URI /** Scala.js Closure optimizer: does advanced optimizations with Closure. */ class ScalaJSClosureOptimizer(semantics: Semantics) { import ScalaJSClosureOptimizer._ private def toClosureSource(file: VirtualJSFile) = ClosureSource.fromReader(file.toURI.toString(), file.reader) private def toClosureInput(file: VirtualJSFile) = new CompilerInput(toClosureSource(file)) /** Fully optimizes an [[IRClasspath]] by asking the ScalaJSOptimizer to * emit a closure AST and then compiling this AST directly */ def optimizeCP(optimizer: ScalaJSOptimizer, inputs: Inputs[ScalaJSOptimizer.Inputs[IRClasspath]], outCfg: OutputConfig, logger: Logger): LinkedClasspath = { val cp = inputs.input.input CacheUtils.cached(cp.version, outCfg.output, outCfg.cache) { logger.info(s"Full Optimizing ${outCfg.output.path}") val irFastOptInput = inputs.input.copy(input = inputs.input.input.scalaJSIR) val irFullOptInput = inputs.copy(input = irFastOptInput) optimizeIR(optimizer, irFullOptInput, outCfg, logger) } new LinkedClasspath(cp.jsLibs, outCfg.output, cp.requiresDOM, cp.version) } def optimizeIR(optimizer: ScalaJSOptimizer, inputs: Inputs[ScalaJSOptimizer.Inputs[Traversable[VirtualScalaJSIRFile]]], outCfg: OutputConfig, logger: Logger): Unit = { // Build Closure IR via ScalaJSOptimizer val builder = new ClosureAstBuilder(outCfg.relativizeSourceMapBase) optimizer.optimizeIR(inputs.input, outCfg, builder, logger) // Build a Closure JSModule which includes the core libs val module = new JSModule("Scala.js") for (lib <- CoreJSLibs.libs(semantics)) module.add(toClosureInput(lib)) val ast = builder.closureAST module.add(new CompilerInput(ast, ast.getInputId(), false)) for (export <- inputs.additionalExports) module.add(toClosureInput(export)) // Compile the module val closureExterns = (ScalaJSExternsFile +: inputs.additionalExterns).map(toClosureSource) val options = closureOptions(outCfg) val compiler = closureCompiler(logger) val result = GenIncOptimizer.logTime(logger, "Closure Compiler pass") { compiler.compileModules( closureExterns.asJava, List(module).asJava, options) } GenIncOptimizer.logTime(logger, "Write Closure result") { writeResult(result, compiler, outCfg.output) } } private def writeResult(result: Result, compiler: ClosureCompiler, output: WritableVirtualJSFile): Unit = { val outputContent = if (result.errors.nonEmpty) "" else "(function(){'use strict';" + compiler.toSource + "}).call(this);\n" val sourceMap = Option(compiler.getSourceMap()) // Write optimized code val w = output.contentWriter try { w.write(outputContent) if (sourceMap.isDefined) w.write("//# sourceMappingURL=" + output.name + ".map\n") } finally w.close() // Write source map (if available) sourceMap.foreach { sm => val w = output.sourceMapWriter try sm.appendTo(w, output.name) finally w.close() } } private def closureOptions(optConfig: OptimizerConfig, noSourceMap: Boolean = false) = { val options = new ClosureOptions options.prettyPrint = optConfig.prettyPrint CompilationLevel.ADVANCED_OPTIMIZATIONS.setOptionsForCompilationLevel(options) options.setLanguageIn(ClosureOptions.LanguageMode.ECMASCRIPT5) options.setCheckGlobalThisLevel(CheckLevel.OFF) if (!noSourceMap && optConfig.wantSourceMap) { options.setSourceMapOutputPath(optConfig.output.name + ".map") options.setSourceMapDetailLevel(SourceMap.DetailLevel.ALL) } options } private def closureCompiler(logger: Logger) = { val compiler = new ClosureCompiler compiler.setErrorManager(new LoggerErrorManager(logger)) compiler } } object ScalaJSClosureOptimizer { /** Inputs of the Scala.js Closure optimizer. */ final case class Inputs[T]( /** Input to optimize (classpath or file-list) */ input: T, /** Additional externs files to be given to Closure. */ additionalExterns: Seq[VirtualJSFile] = Nil, /** Additional exports to be given to Closure. * These files are just appended to the classpath, given to Closure, * but not used in the Scala.js optimizer pass when direct optimizing */ additionalExports: Seq[VirtualJSFile] = Nil ) /** Configuration the closure part of the optimizer needs. * See [[OutputConfig]] for a description of the fields. */ trait OptimizerConfig { val output: WritableVirtualJSFile val cache: Option[WritableVirtualTextFile] val wantSourceMap: Boolean val prettyPrint: Boolean val relativizeSourceMapBase: Option[URI] } /** Configuration for the output of the Scala.js Closure optimizer */ final case class OutputConfig( /** Writer for the output */ output: WritableVirtualJSFile, /** Cache file */ cache: Option[WritableVirtualTextFile] = None, /** If true, performs expensive checks of the IR for the used parts. */ checkIR: Boolean = false, /** If true, the optimizer removes trees that have not been used in the * last run from the cache. Otherwise, all trees that has been used once, * are kept in memory. */ unCache: Boolean = true, /** If true, no optimizations are performed */ disableOptimizer: Boolean = false, /** If true, nothing is performed incrementally */ batchMode: Boolean = false, /** Ask to produce source map for the output */ wantSourceMap: Boolean = false, /** Pretty-print the output. */ prettyPrint: Boolean = false, /** Base path to relativize paths in the source map */ relativizeSourceMapBase: Option[URI] = None ) extends OptimizerConfig with ScalaJSOptimizer.OptimizerConfig /** Minimal set of externs to compile Scala.js-emitted code with Closure. */ val ScalaJSExterns = """ /** @constructor */ function Object() {} Object.protoype.toString = function() {}; /** @constructor */ function Array() {} Array.prototype.length = 0; /** @constructor */ function Function() {} Function.prototype.constructor = function() {}; Function.prototype.call = function() {}; Function.prototype.apply = function() {}; var global = {}; var __ScalaJSEnv = {}; """ val ScalaJSExternsFile = new MemVirtualJSFile("ScalaJSExterns.js"). withContent(ScalaJSExterns) }