Skip to content

Commit f80641f

Browse files
committed
Merge pull request scala-js#1879 from nicolasstucki/append-prepend-to-emmited-js
Fix scala-js#1845: Add scalaJSOutputWrapper sbt setting.
2 parents 5ac6389 + bf62f4c commit f80641f

File tree

7 files changed

+460
-51
lines changed

7 files changed

+460
-51
lines changed

project/BinaryIncompatibilities.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ object BinaryIncompatibilities {
2828
"org.scalajs.core.tools.optimizer.Analyzer#ClassInfo.isClass"),
2929
ProblemFilters.exclude[MissingMethodProblem](
3030
"org.scalajs.core.tools.optimizer.Analyzer#ClassInfo.isHijackedClass"),
31+
ProblemFilters.exclude[IncompatibleMethTypeProblem](
32+
"org.scalajs.core.tools.optimizer.ScalaJSClosureOptimizer.org$scalajs$core$tools$optimizer$ScalaJSClosureOptimizer$$writeResult"),
3133

3234
ProblemFilters.exclude[MissingMethodProblem](
3335
"org.scalajs.core.tools.javascript.JSDesugaring#JSDesugar.org$scalajs$core$tools$javascript$JSDesugaring$JSDesugar$$implicitOutputMode"),

sbt-plugin-test/build.sbt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ lazy val root = project.in(file(".")).
2323
lazy val noDOM = project.settings(baseSettings: _*).
2424
enablePlugins(ScalaJSPlugin).
2525
settings(
26-
name := "Scala.js sbt test w/o DOM"
26+
name := "Scala.js sbt test w/o DOM",
27+
scalaJSOutputWrapper := (
28+
"// Scala.js - noDOM sbt test\n//\n// Compiled with Scala.js\n",
29+
"// End of Scala.js generated script")
2730
)
2831

2932
lazy val withDOM = project.settings(baseSettings: _*).
@@ -32,8 +35,10 @@ lazy val withDOM = project.settings(baseSettings: _*).
3235
name := "Scala.js sbt test w/ DOM",
3336
jsDependencies ++= Seq(
3437
RuntimeDOM,
35-
"org.webjars" % "jquery" % "1.10.2" / "jquery.js"
36-
)
38+
"org.webjars" % "jquery" % "1.10.2" / "jquery.js"),
39+
scalaJSOutputWrapper := (
40+
"// Scala.js - withDOM sbt test\n//\n// Compiled with Scala.js\n",
41+
"// End of Scala.js generated script")
3742
)
3843

3944
lazy val jetty9 = project.settings(baseSettings: _*).

sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPlugin.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ object ScalaJSPlugin extends AutoPlugin {
154154
val emitSourceMaps = SettingKey[Boolean]("emitSourceMaps",
155155
"Whether package and optimize stages should emit source maps at all", BPlusSetting)
156156

157+
val scalaJSOutputWrapper = TaskKey[(String, String)]("scalaJSOutputWrapper",
158+
"Custom wrapper for the generated .js files. Formatted as tuple (header, footer).", BPlusTask)
159+
157160
val jsDependencies = SettingKey[Seq[AbstractJSDep]]("jsDependencies",
158161
"JavaScript libraries this project depends upon. Also used to depend on the DOM.", APlusSetting)
159162

sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -275,15 +275,15 @@ object ScalaJSPluginInternal {
275275
import ScalaJSOptimizer._
276276
val outCP = (scalaJSOptimizer in fastOptJS).value.optimizeCP(
277277
(scalaJSPreLinkClasspath in fastOptJS).value,
278-
Config(
279-
output = AtomicWritableFileVirtualJSFile(output),
280-
cache = Some(taskCache),
281-
wantSourceMap = (emitSourceMaps in fastOptJS).value,
282-
relativizeSourceMapBase = relSourceMapBase,
283-
bypassLinkingErrors = opts.bypassLinkingErrors,
284-
checkIR = opts.checkScalaJSIR,
285-
disableOptimizer = opts.disableOptimizer,
286-
batchMode = opts.batchMode),
278+
Config(AtomicWritableFileVirtualJSFile(output))
279+
.withCache(Some(taskCache))
280+
.withWantSourceMap((emitSourceMaps in fastOptJS).value)
281+
.withRelativizeSourceMapBase(relSourceMapBase)
282+
.withBypassLinkingErrors(opts.bypassLinkingErrors)
283+
.withCheckIR(opts.checkScalaJSIR)
284+
.withDisableOptimizer(opts.disableOptimizer)
285+
.withBatchMode(opts.batchMode)
286+
.withCustomOutputWrapper(scalaJSOutputWrapper.value),
287287
s.log)
288288

289289
Attributed.blank(output).put(scalaJSCompleteClasspath, outCP)
@@ -319,16 +319,16 @@ object ScalaJSPluginInternal {
319319
val outCP = new ScalaJSClosureOptimizer().optimizeCP(
320320
(scalaJSOptimizer in fullOptJS).value,
321321
(scalaJSPreLinkClasspath in fullOptJS).value,
322-
Config(
323-
output = AtomicWritableFileVirtualJSFile(output),
324-
cache = Some(taskCache),
325-
wantSourceMap = (emitSourceMaps in fullOptJS).value,
326-
relativizeSourceMapBase = relSourceMapBase,
327-
bypassLinkingErrors = opts.bypassLinkingErrors,
328-
checkIR = opts.checkScalaJSIR,
329-
disableOptimizer = opts.disableOptimizer,
330-
batchMode = opts.batchMode,
331-
prettyPrint = opts.prettyPrintFullOptJS),
322+
Config(AtomicWritableFileVirtualJSFile(output))
323+
.withCache(Some(taskCache))
324+
.withWantSourceMap((emitSourceMaps in fullOptJS).value)
325+
.withRelativizeSourceMapBase(relSourceMapBase)
326+
.withBypassLinkingErrors(opts.bypassLinkingErrors)
327+
.withCheckIR(opts.checkScalaJSIR)
328+
.withDisableOptimizer(opts.disableOptimizer)
329+
.withBatchMode(opts.batchMode)
330+
.withCustomOutputWrapper(scalaJSOutputWrapper.value)
331+
.withPrettyPrint(opts.prettyPrintFullOptJS),
332332
s.log)
333333

334334
Attributed.blank(output).put(scalaJSCompleteClasspath, outCP)
@@ -625,6 +625,8 @@ object ScalaJSPluginInternal {
625625

626626
emitSourceMaps := true,
627627

628+
scalaJSOutputWrapper := ("", ""),
629+
628630
scalaJSOptimizerOptions := OptimizerOptions(),
629631

630632
jsDependencies := Seq(),

tools/jvm/src/main/scala/org/scalajs/core/tools/optimizer/ScalaJSClosureOptimizer.scala

Lines changed: 220 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,28 +85,39 @@ class ScalaJSClosureOptimizer {
8585
}
8686

8787
logTime(logger, "Closure: Write result") {
88-
writeResult(result, compiler, cfg.output)
88+
writeResult(result, compiler, cfg)
8989
}
9090
}
9191

9292
private def writeResult(result: Result, compiler: ClosureCompiler,
93-
output: WritableVirtualJSFile): Unit = {
93+
cfg: Config): Unit = {
94+
val output = cfg.output
9495

95-
val outputContent = if (result.errors.nonEmpty) ""
96-
else "(function(){'use strict';" + compiler.toSource + "}).call(this);\n"
96+
def withNewLine(str: String): String = if (str == "") "" else str + "\n"
97+
98+
val (header0, footer0) = cfg.customOutputWrapper
99+
val header = withNewLine(header0) + "(function(){'use strict';\n"
100+
val footer = "}).call(this);\n" + withNewLine(footer0)
101+
102+
val outputContent =
103+
if (result.errors.nonEmpty) "// errors while producing source\n"
104+
else compiler.toSource + "\n"
97105

98106
val sourceMap = Option(compiler.getSourceMap())
99107

100108
// Write optimized code
101109
val w = output.contentWriter
102110
try {
111+
w.write(header)
103112
w.write(outputContent)
113+
w.write(footer)
104114
if (sourceMap.isDefined)
105115
w.write("//# sourceMappingURL=" + output.name + ".map\n")
106116
} finally w.close()
107117

108118
// Write source map (if available)
109119
sourceMap.foreach { sm =>
120+
sm.setWrapperPrefix(header)
110121
val w = output.sourceMapWriter
111122
try sm.appendTo(w, output.name)
112123
finally w.close()
@@ -151,34 +162,225 @@ object ScalaJSClosureOptimizer {
151162
}
152163

153164
/** Configuration for the output of the Scala.js Closure optimizer */
154-
final case class Config(
165+
final class Config private[Config] (
155166
/** Writer for the output */
156-
output: WritableVirtualJSFile,
167+
val output: WritableVirtualJSFile,
157168
/** Cache file */
158-
cache: Option[WritableVirtualTextFile] = None,
169+
val cache: Option[WritableVirtualTextFile],
159170
/** Whether to only warn if the linker has errors. Implicitly true, if
160-
* noWarnMissing is nonEmpty
161-
*/
162-
bypassLinkingErrors: Boolean = false,
171+
* noWarnMissing is nonEmpty */
172+
val bypassLinkingErrors: Boolean,
163173
/** If true, performs expensive checks of the IR for the used parts. */
164-
checkIR: Boolean = false,
174+
val checkIR: Boolean,
165175
/** If true, the optimizer removes trees that have not been used in the
166176
* last run from the cache. Otherwise, all trees that has been used once,
167177
* are kept in memory. */
168-
unCache: Boolean = true,
178+
val unCache: Boolean,
169179
/** If true, no optimizations are performed */
170-
disableOptimizer: Boolean = false,
180+
val disableOptimizer: Boolean,
171181
/** If true, nothing is performed incrementally */
172-
batchMode: Boolean = false,
182+
val batchMode: Boolean,
173183
/** Ask to produce source map for the output */
174-
wantSourceMap: Boolean = false,
184+
val wantSourceMap: Boolean,
175185
/** Pretty-print the output. */
176-
prettyPrint: Boolean = false,
186+
val prettyPrint: Boolean,
177187
/** Base path to relativize paths in the source map */
178-
relativizeSourceMapBase: Option[URI] = None,
188+
val relativizeSourceMapBase: Option[URI],
179189
/** Elements we won't warn even if they don't exist */
180-
noWarnMissing: Seq[ScalaJSOptimizer.NoWarnMissing] = Nil
190+
val noWarnMissing: Seq[ScalaJSOptimizer.NoWarnMissing],
191+
/** Custom js code that wraps the output */
192+
val customOutputWrapper: (String, String)
181193
) extends OptimizerConfig with ScalaJSOptimizer.OptimizerConfig
194+
/* for binary compatibility */ with Product with Serializable with Equals {
195+
196+
/* NOTE: This class was previously a case class and hence many useless
197+
* methods were implemented for binary compatibility :(
198+
*/
199+
200+
// For binary compatibility
201+
@deprecated("Use Config(output) and .withXYZ() methods", "0.6.5")
202+
def this(
203+
output: WritableVirtualJSFile,
204+
cache: Option[WritableVirtualTextFile] = None,
205+
bypassLinkingErrors: Boolean = false,
206+
checkIR: Boolean = false,
207+
unCache: Boolean = true,
208+
disableOptimizer: Boolean = false,
209+
batchMode: Boolean = false,
210+
wantSourceMap: Boolean = false,
211+
prettyPrint: Boolean = false,
212+
relativizeSourceMapBase: Option[URI] = None,
213+
noWarnMissing: Seq[ScalaJSOptimizer.NoWarnMissing] = Nil) = {
214+
215+
this(
216+
output = output,
217+
cache = cache,
218+
bypassLinkingErrors = bypassLinkingErrors,
219+
checkIR = checkIR,
220+
unCache = unCache,
221+
disableOptimizer = disableOptimizer,
222+
batchMode = batchMode,
223+
wantSourceMap = wantSourceMap,
224+
prettyPrint = prettyPrint,
225+
relativizeSourceMapBase = relativizeSourceMapBase,
226+
noWarnMissing = noWarnMissing,
227+
customOutputWrapper = ("", ""))
228+
}
229+
230+
def withCache(cache: Option[WritableVirtualTextFile]): Config =
231+
copyWith(cache = cache)
232+
233+
def withBypassLinkingErrors(bypassLinkingErrors: Boolean): Config =
234+
copyWith(bypassLinkingErrors = bypassLinkingErrors)
235+
236+
def withCheckIR(checkIR: Boolean): Config =
237+
copyWith(checkIR = checkIR)
238+
239+
def withUnCache(unCache: Boolean): Config =
240+
copyWith(unCache = unCache)
241+
242+
def withDisableOptimizer(disableOptimizer: Boolean): Config =
243+
copyWith(disableOptimizer = disableOptimizer)
244+
245+
def withBatchMode(batchMode: Boolean): Config =
246+
copyWith(batchMode = batchMode)
247+
248+
def withWantSourceMap(wantSourceMap: Boolean): Config =
249+
copyWith(wantSourceMap = wantSourceMap)
250+
251+
def withPrettyPrint(prettyPrint: Boolean): Config =
252+
copyWith(prettyPrint = prettyPrint)
253+
254+
def withRelativizeSourceMapBase(relativizeSourceMapBase: Option[URI]): Config =
255+
copyWith(relativizeSourceMapBase = relativizeSourceMapBase)
256+
257+
def withNoWarnMissing(noWarnMissing: Seq[ScalaJSOptimizer.NoWarnMissing]): Config =
258+
copyWith(noWarnMissing = noWarnMissing)
259+
260+
def withCustomOutputWrapper(customOutputWrapper: (String, String)): Config =
261+
copyWith(customOutputWrapper = customOutputWrapper)
262+
263+
@deprecated("Not a case class anymore", "0.6.5")
264+
def copy(
265+
output: WritableVirtualJSFile = this.output,
266+
cache: Option[WritableVirtualTextFile] = this.cache,
267+
bypassLinkingErrors: Boolean = this.bypassLinkingErrors,
268+
checkIR: Boolean = this.checkIR,
269+
unCache: Boolean = this.unCache,
270+
disableOptimizer: Boolean = this.disableOptimizer,
271+
batchMode: Boolean = this.batchMode,
272+
wantSourceMap: Boolean = this.wantSourceMap,
273+
prettyPrint: Boolean = this.prettyPrint,
274+
relativizeSourceMapBase: Option[URI] = this.relativizeSourceMapBase,
275+
noWarnMissing: Seq[ScalaJSOptimizer.NoWarnMissing] = this.noWarnMissing): Config = {
276+
277+
copyWith(output, cache, bypassLinkingErrors, checkIR, unCache,
278+
disableOptimizer, batchMode, wantSourceMap, prettyPrint,
279+
relativizeSourceMapBase, noWarnMissing)
280+
}
281+
282+
private def copyWith(
283+
output: WritableVirtualJSFile = this.output,
284+
cache: Option[WritableVirtualTextFile] = this.cache,
285+
bypassLinkingErrors: Boolean = this.bypassLinkingErrors,
286+
checkIR: Boolean = this.checkIR,
287+
unCache: Boolean = this.unCache,
288+
disableOptimizer: Boolean = this.disableOptimizer,
289+
batchMode: Boolean = this.batchMode,
290+
wantSourceMap: Boolean = this.wantSourceMap,
291+
prettyPrint: Boolean = this.prettyPrint,
292+
relativizeSourceMapBase: Option[URI] = this.relativizeSourceMapBase,
293+
noWarnMissing: Seq[ScalaJSOptimizer.NoWarnMissing] = this.noWarnMissing,
294+
customOutputWrapper: (String, String) = this.customOutputWrapper): Config = {
295+
296+
new Config(output, cache, bypassLinkingErrors, checkIR, unCache,
297+
disableOptimizer, batchMode, wantSourceMap, prettyPrint,
298+
relativizeSourceMapBase, noWarnMissing, customOutputWrapper)
299+
}
300+
301+
// For binary compatibility
302+
@deprecated("Not a case class anymore", "0.6.5")
303+
def canEqual(that: Any): Boolean = true
304+
305+
// For binary compatibility
306+
@deprecated("Not a case class anymore", "0.6.5")
307+
def productArity: Int = productArray.length
308+
309+
// For binary compatibility
310+
@deprecated("Not a case class anymore", "0.6.5")
311+
def productElement(n: Int): Any = productArray(n)
312+
313+
private def productArray: Array[Any] = {
314+
Array[Any](output, cache, bypassLinkingErrors, checkIR, unCache,
315+
disableOptimizer, batchMode, wantSourceMap, prettyPrint,
316+
relativizeSourceMapBase, noWarnMissing, customOutputWrapper)
317+
}
318+
319+
// For binary compatibility
320+
override def equals(other: Any): Boolean = super.equals(other)
321+
322+
// For binary compatibility
323+
override def hashCode(): Int = super.hashCode()
324+
325+
// For binary compatibility
326+
override def toString(): String =
327+
productArray.mkString("Config(", ", ", ")")
328+
}
329+
330+
object Config extends runtime.AbstractFunction11[WritableVirtualJSFile,
331+
Option[WritableVirtualTextFile], Boolean, Boolean, Boolean,
332+
Boolean, Boolean, Boolean, Boolean, Option[URI],
333+
Seq[ScalaJSOptimizer.NoWarnMissing], Config] {
334+
335+
def apply(output: WritableVirtualJSFile): Config = {
336+
new Config(
337+
output = output,
338+
cache = None,
339+
bypassLinkingErrors = false,
340+
checkIR = false,
341+
unCache = true,
342+
disableOptimizer = false,
343+
batchMode = false,
344+
wantSourceMap = false,
345+
prettyPrint = false,
346+
relativizeSourceMapBase = None,
347+
noWarnMissing = Nil,
348+
customOutputWrapper = ("", ""))
349+
}
350+
351+
// For binary compatibility
352+
@deprecated("Use Config(output) and .withXYZ() methods", "0.6.5")
353+
def apply(
354+
output: WritableVirtualJSFile,
355+
cache: Option[WritableVirtualTextFile] = None,
356+
bypassLinkingErrors: Boolean = false,
357+
checkIR: Boolean = false,
358+
unCache: Boolean = true,
359+
disableOptimizer: Boolean = false,
360+
batchMode: Boolean = false,
361+
wantSourceMap: Boolean = false,
362+
prettyPrint: Boolean = false,
363+
relativizeSourceMapBase: Option[URI] = None,
364+
noWarnMissing: Seq[ScalaJSOptimizer.NoWarnMissing] = Nil): Config = {
365+
366+
new Config(output, cache, bypassLinkingErrors, checkIR, unCache,
367+
disableOptimizer, batchMode, wantSourceMap, prettyPrint,
368+
relativizeSourceMapBase, noWarnMissing, customOutputWrapper = ("", ""))
369+
}
370+
371+
// For binary compatibility
372+
@deprecated("Not a case class anymore", "0.6.5")
373+
def unapply(config: Config): Option[(WritableVirtualJSFile,
374+
Option[WritableVirtualTextFile], Boolean, Boolean, Boolean,
375+
Boolean, Boolean, Boolean, Boolean, Option[URI],
376+
Seq[ScalaJSOptimizer.NoWarnMissing])] = {
377+
378+
Some((config.output, config.cache, config.bypassLinkingErrors,
379+
config.checkIR, config.unCache, config.disableOptimizer,
380+
config.batchMode, config.wantSourceMap, config.prettyPrint,
381+
config.relativizeSourceMapBase, config.noWarnMissing))
382+
}
383+
}
182384

183385
/** Minimal set of externs to compile Scala.js-emitted code with Closure. */
184386
val ScalaJSExterns = """

0 commit comments

Comments
 (0)