Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ lazy val `sbt-converter` = project
name := "sbt-converter",
scalaVersion := scala212, // Always use Scala 2.12 for this project
crossScalaVersions := Seq(scala212), // For now, keep SBT 1.x only
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.21.1"),
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.1"),
sbtPlugin := true,
// set up 'scripted; sbt plugin for testing sbt plugins
Expand Down
4 changes: 2 additions & 2 deletions docs/conversion-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ project.settings(

### `stIncludeDev`

By default the plugin only includes dependencies defined with `npmDependencies`.
If you set this to true `npmDevDependencies` will also be included.
By default the plugin only includes dependencies defined with `stNpmDependencies`.
If you set this to true `stNpmDevDependencies` will also be included.

Note that dependencies from `Test` scope are included as well.
The resulting jars are all added to the `Compile` classpath.
Expand Down
7 changes: 3 additions & 4 deletions docs/library-developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ project.enablePlugins(ScalablyTypedConverterGenSourcePlugin)

```scala
project.settings(
Compile / npmDependencies ++= Seq(
Compile / stNpmDependencies ++= Seq(
"react-router-dom" -> "5.1.2",
"@types/react-router-dom" -> "5.1.2"
)
Expand Down Expand Up @@ -82,10 +82,9 @@ project.settings(

The plugin checks for updated npm dependencies on each compile, and yarn responds much faster than npm.

Configure scalajs-bundler like this:
```scala
project.settings(
useYarn := true
stUseYarn := true
)
```
Yarn will need to be present on your system for this to work. You should also check in `yarn.lock`.
Expand Down Expand Up @@ -158,7 +157,7 @@ The names you supply should be exactly as they appear in the generated scala cod
```scala
project.settings(
/* setup libraries */
Compile / npmDependencies ++= Seq(
Compile / stNpmDependencies ++= Seq(
"moment" -> "2.24.0",
"react-big-calendar" -> "0.22",
"@types/react-big-calendar" -> "0.22.3"
Expand Down
4 changes: 2 additions & 2 deletions docs/plugin-no-bundler.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
id: plugin-no-bundler
title: SBT plugin without scalajs-bundler
title: SBT plugin with externally managed npm packages
---

- [I want to use scalajs-bundler (recommended)](plugin.md)
- [I want the plugin to manage npm packages for me (recommended)](plugin.md)
- [I want to use mill](https://github.com/lolgab/mill-scalablytyped)
- [I can't/won't use sbt nor mill](cli.md)

Expand Down
17 changes: 7 additions & 10 deletions docs/plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ id: plugin
title: SBT plugin
---

The plugin uses [scalajs-bundler](https://scalacenter.github.io/scalajs-bundler/)
to download NPM packages, and translates typescript type definitions into Scala.js jars before your project compiles.
The plugin downloads NPM packages itself (running `npm install`/`yarn install`), and translates
typescript type definitions into Scala.js jars before your project compiles.

- [I can't/won't use scalajs-bundler](plugin-no-bundler.md)
- [I want to manage npm/node_modules myself](plugin-no-bundler.md)
- [I want to use mill](https://github.com/lolgab/mill-scalablytyped)
- [I can't/won't use sbt](cli.md)

Expand All @@ -21,8 +21,6 @@ Then check out the demo projects:

## Notes

- Scalajs-bundler has some functionality where it picks up `npmDependencies` from your classpath.
This plugin generates parts of the classpath, so to avoid a circular dependency that functionality is disabled.
- You're only supposed to use the plugin once in your build.
If you really want more than one conversion make sure to [shade](conversion-options.md#stoutputpackage) them into different packages

Expand All @@ -49,7 +47,7 @@ project.enablePlugins(ScalablyTypedConverterPlugin)

```scala
project.settings(
Compile / npmDependencies ++= Seq(
Compile / stNpmDependencies ++= Seq(
"react-router-dom" -> "5.1.2",
"@types/react-router-dom" -> "5.1.2"
)
Expand Down Expand Up @@ -89,11 +87,10 @@ project.settings(
## Use yarn instead of npm

The plugin checks for updated npm dependencies on each compile, and yarn responds much faster than npm.

Configure scalajs-bundler like this:

```scala
project.settings(
useYarn := true
stUseYarn := true
)
```
Yarn will need to be present on your system for this to work. You should also check in `yarn.lock`.
Expand All @@ -106,7 +103,7 @@ The plugin taps into the `allDependencies` task in sbt, and this has some conseq

Whenever the task is evaluated, typically through a `compile` or an import into an IDE, the plugin

- Runs a customized version of `installNpmDependencies` from scalajs-bundler, changed to avoid touching the classpath.
- Writes a `package.json` from `stNpmDependencies`/`stNpmDevDependencies`/`stNpmResolutions` and runs `npm install` (or `yarn install`) if it changed

- Computes a digest from the resulting `package.json` file and of the configuration of the plugin

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.scalablytyped.converter.internal

import _root_.io.circe.{Json => CJson}
import org.scalablytyped.converter.plugin.ScalablyTypedPluginBase.autoImport._
import sbt.Keys._
import sbt._

/** Writes a minimal package.json from the st{Npm,NpmDev,NpmResolutions} settings and runs `npm install`
* (or `yarn install`), independent of any third-party scalajs-bundler-style plugin.
*/
object NpmInstall {

/** Where package.json/node_modules end up. Pure computation, doesn't trigger installation. */
def targetDir(crossTarget: File): File = crossTarget / "scalablytyped-npm"

val task: Def.Initialize[Task[File]] = Def.task {
val log = streams.value.log
val targetDir = os.Path(NpmInstall.targetDir((Compile / crossTarget).value))
os.makeDir.all(targetDir)

val dependencies = (Compile / stNpmDependencies).value
val devDependencies = {
val declared = (Compile / stNpmDevDependencies).value
if ((dependencies ++ declared).exists { case (lib, _) => lib == "typescript" }) declared
else declared :+ ("typescript" -> stTypescriptVersion.value)
}
val resolutions = (Compile / stNpmResolutions).value
val useYarn = stUseYarn.value

val packageJsonContent = packageJson(dependencies, devDependencies, resolutions)
val packageJsonFile = targetDir / "package.json"
val hashFile = targetDir / ".stNpmInstall.hash"
val nodeModules = targetDir / "node_modules"

val newHash = Digest.of(IArray(packageJsonContent)).hexString
val upToDate =
os.exists(nodeModules) && os.exists(hashFile) && os.read(hashFile) == newHash

if (!upToDate) {
os.write.over(packageJsonFile, packageJsonContent)

val command = if (useYarn) "yarn" else "npm"
log.info(s"Running `$command install` in $targetDir")

val result = os
.proc(command, "install")
.call(
cwd = targetDir,
stdout = os.Inherit,
stderr = os.Inherit,
check = false,
)
if (result.exitCode != 0)
sys.error(s"`$command install` failed with exit code ${result.exitCode}")

os.write.over(hashFile, newHash)
}

targetDir.toIO
}

private def packageJson(
dependencies: Seq[(String, String)],
devDependencies: Seq[(String, String)],
resolutions: Map[String, String],
): String = {
def obj(entries: Iterable[(String, String)]): CJson =
CJson.fromFields(entries.map { case (k, v) => k -> CJson.fromString(v) })

CJson
.obj(
"name" -> CJson.fromString("scalablytyped-npm-install"),
"version" -> CJson.fromString("0.0.0"),
"private" -> CJson.fromBoolean(true),
"dependencies" -> obj(dependencies),
"devDependencies" -> obj(devDependencies),
"resolutions" -> obj(resolutions),
)
.spaces2
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package org.scalablytyped.converter.internal

import org.scalablytyped.converter.internal.ts.TsIdentLibrary
import org.scalablytyped.converter.plugin.ScalablyTypedPluginBase.autoImport.stIncludeDev
import org.scalablytyped.converter.plugin.ScalablyTypedPluginBase.autoImport._
import sbt.{Compile, Def, Test}
import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport._

import scala.collection.immutable.SortedMap

Expand All @@ -15,10 +14,10 @@ object WantedLibs {
def maybeAdd(values: Seq[(String, String)], include: Boolean): Unit =
if (include) values.foreach { case (k, v) => b += ((TsIdentLibrary(k), v)) }

maybeAdd((Compile / npmDependencies).value, include = true)
maybeAdd((Compile / npmDevDependencies).value, include = stIncludeDev.value)
maybeAdd((Test / npmDependencies).value, include = true)
maybeAdd((Test / npmDevDependencies).value, include = stIncludeDev.value)
maybeAdd((Compile / stNpmDependencies).value, include = true)
maybeAdd((Compile / stNpmDevDependencies).value, include = stIncludeDev.value)
maybeAdd((Test / stNpmDependencies).value, include = true)
maybeAdd((Test / stNpmDevDependencies).value, include = stIncludeDev.value)

b.result()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import org.scalablytyped.converter.internal.*
import org.scalablytyped.converter.internal.orphanCodecs.{FileDecoder, FileEncoder}
import org.scalablytyped.converter.internal.scalajs.{Name, QualifiedName}
import org.scalablytyped.converter.internal.ts.TsIdentLibrary
import org.scalajs.sbtplugin.ScalaJSPlugin
import os.Path
import sbt.*
import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin

import scala.util.Try

object ScalablyTypedConverterGenSourcePlugin extends AutoPlugin {
override def requires = ScalablyTypedPluginBase && ScalaJSBundlerPlugin
override def requires = ScalablyTypedPluginBase && ScalaJSPlugin

object autoImport extends GenSourceKeys {
type SourceGenMode = plugin.SourceGenMode
Expand All @@ -25,7 +25,6 @@ object ScalablyTypedConverterGenSourcePlugin extends AutoPlugin {
override lazy val projectSettings: scala.Seq[Def.Setting[_]] = {
import ScalablyTypedPluginBase.autoImport.*
import autoImport.*
import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport.*

Seq(
stSourceGenMode := SourceGenMode.ResourceGenerator,
Expand All @@ -41,8 +40,7 @@ object ScalablyTypedConverterGenSourcePlugin extends AutoPlugin {
},
stMinimize := Selection.None,
stMinimizeKeep := Nil,
ScalaJsBundlerHack.adaptScalaJSBundlerPackageJson,
ScalaJsBundlerHack.adaptNpmInstallJSResources,
stNpmInstall := NpmInstall.task.value,
stImportSources := {
val stLogger = WrapSbtLogger.task.value
val conversion = stConversionOptions.value
Expand All @@ -55,7 +53,7 @@ object ScalablyTypedConverterGenSourcePlugin extends AutoPlugin {
.fromTraversable(stMinimizeKeep.value)
.map(str => QualifiedName(conversion.outputPackage +: QualifiedName(str).parts))

(Compile / npmUpdate).value
(Compile / stNpmInstall).value

val (toDir, overrideTargetFolder) = stSourceGenMode.value match {
case SourceGenMode.ResourceGenerator =>
Expand All @@ -66,7 +64,7 @@ object ScalablyTypedConverterGenSourcePlugin extends AutoPlugin {
})
}

val nodeModulesDir = os.Path((Compile / npmUpdate / Keys.crossTarget).value / "node_modules")
val nodeModulesDir = os.Path(NpmInstall.targetDir((Compile / Keys.crossTarget).value)) / "node_modules"
val globalCacheDir = (Global / stDir).value
val cachedInputs = os.Path(Keys.streams.value.cacheDirectory / "input.json")
val cachedOutputs = os.Path(Keys.streams.value.cacheDirectory / "output.json")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,30 @@ import _root_.io.circe.syntax.*
import com.olvind.logging.LogLevel
import org.scalablytyped.converter.internal.*
import org.scalablytyped.converter.internal.RunCache.Present
import org.scalajs.sbtplugin.ScalaJSPlugin
import sbt.*
import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin

import scala.concurrent.ExecutionContext

object ScalablyTypedConverterPlugin extends AutoPlugin {
override def requires = ScalablyTypedPluginBase && ScalaJSBundlerPlugin
override def requires = ScalablyTypedPluginBase && ScalaJSPlugin

private[plugin] val stInternalZincCompiler = taskKey[ZincCompiler]("Hijack compiler settings")

object autoImport extends ConverterKeys

import ScalablyTypedPluginBase.autoImport.*
import autoImport.*
import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport.*

private[plugin] val stImportTask = Def.taskDyn[ImportTypings.InOut] {
val cacheDir = os.Path((Global / stDir).value)
val stLogger = WrapSbtLogger.task.value
val conversion = stConversionOptions.value
val publishLocalFolder = Utils.IvyLocal.value
val fromFolder = InFolder(os.Path((Compile / npmUpdate / Keys.crossTarget).value / "node_modules"))
val targetFolder = os.Path(Keys.streams.value.cacheDirectory) / "sources"
val fromFolder = InFolder(
os.Path(NpmInstall.targetDir((Compile / Keys.crossTarget).value)) / "node_modules",
)
val targetFolder = os.Path(Keys.streams.value.cacheDirectory) / "sources"

val input = ImportTypings.Input(
converterVersion = BuildInfo.version,
Expand All @@ -54,7 +55,7 @@ object ScalablyTypedConverterPlugin extends AutoPlugin {
Def.task((input, output))
case Present.No =>
val t = Def.task {
(Compile / npmInstallDependencies).value
(Compile / stNpmInstall).value

ImportTypings(
input = input,
Expand Down Expand Up @@ -88,7 +89,7 @@ object ScalablyTypedConverterPlugin extends AutoPlugin {
Keys.allDependencies ++= stImport.value._2.moduleIds.toSeq,
stImport := stImportTask.value,
stInternalZincCompiler := ZincCompiler.task.value,
ScalaJsBundlerHack.adaptScalaJSBundlerPackageJson,
stNpmInstall := NpmInstall.task.value,
stPublishCache := RunCache.publishCacheTask(stImport).value,
)
}
Loading
Loading