diff options
Diffstat (limited to 'examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala')
-rw-r--r-- | examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala new file mode 100644 index 0000000..146b2b8 --- /dev/null +++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala @@ -0,0 +1,216 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ 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) + +} |