summaryrefslogtreecommitdiff
path: root/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala
diff options
context:
space:
mode:
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.scala216
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)
+
+}