Skip to content

Commit

Permalink
Refactor: Switch over to Scala DOM types code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
raquo committed Dec 3, 2022
1 parent 6db62d4 commit 4057b51
Show file tree
Hide file tree
Showing 46 changed files with 1,160 additions and 583 deletions.
8 changes: 7 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ ThisBuild / scalaVersion := Versions.Scala_2_13

ThisBuild / crossScalaVersions := Seq(Versions.Scala_2_12, Versions.Scala_2_13, Versions.Scala_3)

lazy val precompile = taskKey[Unit]("runs Laminar-specific pre-compile tasks")

precompile := DomDefsGenerator.cachedGenerate()

(Compile / compile) := ((Compile / compile) dependsOn precompile).value

lazy val websiteJS = project
.in(file("websiteJS"))
.settings(
Expand Down Expand Up @@ -51,7 +57,7 @@ lazy val laminar = project.in(file("."))
.settings(
libraryDependencies ++= Seq(
"com.raquo" %%% "airstream" % Versions.Airstream,
"com.raquo" %%% "domtypes" % Versions.ScalaDomTypes,
// "com.raquo" %%% "domtypes" % Versions.ScalaDomTypes, #Note this is a compile-time dependency. See `project/build.sbt`
"com.raquo" %%% "ew" % Versions.Ew,
"com.raquo" %%% "domtestutils" % Versions.ScalaDomTestUtils % Test,
"org.scalatest" %%% "scalatest" % Versions.ScalaTest % Test,
Expand Down
374 changes: 374 additions & 0 deletions project/DomDefsGenerator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
import com.raquo.domtypes.codegen.DefType.LazyVal
import com.raquo.domtypes.codegen.{CanonicalCache, CanonicalDefGroups, CanonicalGenerator, CodeFormatting, SourceRepr}
import com.raquo.domtypes.common.{HtmlTagType, SvgTagType}
import com.raquo.domtypes.defs.styles.StyleTraits

object DomDefsGenerator {

private object generator extends CanonicalGenerator(
baseOutputDirectoryPath = "src/main/scala/com/raquo/laminar",
basePackagePath = "com.raquo.laminar",
standardTraitCommentLines = List(
"#NOTE: GENERATED CODE",
s" - This file is generated at compile time from the data in Scala DOM Types",
" - See `project/DomDefsGenerator.scala` for code generation params",
" - Contribute to https://github.com/raquo/scala-dom-types to add missing tags / attrs / props / etc.",
),
format = CodeFormatting()
) {

override def settersPackagePath: String = basePackagePath + ".modifiers.KeySetter"

override def scalaJsElementTypeParam: String = "Ref"
}

private val cache = new CanonicalCache("project")

def cachedGenerate(): Unit = {
cache.triggerIfCacheKeyUpdated(
metaProject.BuildInfo.scalaDomTypesVersion,
forceOnEverySnapshot = false
)(_ => generate())
}

def generate(): Unit = {
val defGroups = new CanonicalDefGroups()

// -- HTML tags --

{
val traitName = "HtmlTags"

val fileContent = generator.generateTagsTrait(
tagType = HtmlTagType,
defGroups = defGroups.htmlTagsDefGroups,
printDefGroupComments = true,
traitCommentLines = Nil,
traitName = traitName,
keyKind = "HtmlTag",
baseImplDefComments = List(
"Create HTML tag",
"",
"Note: this simply creates an instance of HtmlTag.",
" - This does not create the element (to do that, call .apply() on the returned tag instance)",
" - This does not register this tag name as a custom element",
" - See https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements",
"",
"@param tagName - e.g. \"div\" or \"mwc-input\"",
"@tparam Ref - type of elements with this tag, e.g. dom.html.Input for \"input\" tag"
),
keyImplName = "htmlTag",
defType = LazyVal
)

generator.writeToFile(
packagePath = generator.tagDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}

// -- SVG tags --

{
val traitName = "SvgTags"

val fileContent = generator.generateTagsTrait(
tagType = SvgTagType,
defGroups = defGroups.svgTagsDefGroups,
printDefGroupComments = false,
traitCommentLines = Nil,
traitName = traitName,
keyKind = "SvgTag",
baseImplDefComments = List(
"Create SVG tag",
"",
"Note: this simply creates an instance of HtmlTag.",
" - This does not create the element (to do that, call .apply() on the returned tag instance)",
"",
"@param tagName - e.g. \"circle\"",
"",
"@tparam Ref - type of elements with this tag, e.g. dom.svg.Circle for \"circle\" tag"
),
keyImplName = "svgTag",
defType = LazyVal
)

generator.writeToFile(
packagePath = generator.tagDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}

// -- HTML attributes --

{
val traitName = "HtmlAttrs"

val fileContent = generator.generateAttrsTrait(
defGroups = defGroups.htmlAttrDefGroups,
printDefGroupComments = false,
traitCommentLines = Nil,
traitName = traitName,
keyKind = "HtmlAttr",
implNameSuffix = "HtmlAttr",
baseImplDefComments = List(
"Create HTML attribute (Note: for SVG attrs, use L.svg.svgAttr)",
"",
"@param key - name of the attribute, e.g. \"value\"",
"@param codec - used to encode V into String, e.g. StringAsIsCodec",
"",
"@tparam V - value type for this attr in Scala",
),
baseImplName = "htmlAttr",
namespaceImports = Nil,
namespaceImpl = _ => ???,
transformAttrDomName = identity,
defType = LazyVal
)

generator.writeToFile(
packagePath = generator.attrDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}

// -- SVG attributes --

{
val traitName = "SvgAttrs"

val fileContent = generator.generateAttrsTrait(
defGroups = defGroups.svgAttrDefGroups,
printDefGroupComments = false,
traitName = traitName,
traitCommentLines = Nil,
keyKind = "SvgAttr",
baseImplDefComments = List(
"Create SVG attribute (Note: for HTML attrs, use L.htmlAttr)",
"",
"@param key - name of the attribute, e.g. \"value\"",
"@param codec - used to encode V into String, e.g. StringAsIsCodec",
"",
"@tparam V - value type for this attr in Scala",
),
implNameSuffix = "SvgAttr",
baseImplName = "svgAttr",
namespaceImports = Nil,
namespaceImpl = SourceRepr(_),
transformAttrDomName = identity,
defType = LazyVal
)

generator.writeToFile(
packagePath = generator.attrDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}

// -- ARIA attributes --

{
val traitName = "AriaAttrs"

def transformAttrDomName(ariaAttrName: String): String = {
if (ariaAttrName.startsWith("aria-")) {
ariaAttrName.substring(5)
} else {
throw new Exception(s"Aria attribute does not start with `aria-`: $ariaAttrName")
}
}

val fileContent = generator.generateAttrsTrait(
defGroups = defGroups.ariaAttrDefGroups,
printDefGroupComments = false,
traitName = traitName,
traitCommentLines = Nil,
keyKind = "AriaAttr",
implNameSuffix = "AriaAttr",
baseImplDefComments = List(
"Create ARIA attribute (Note: for HTML attrs, use L.htmlAttr)",
"",
"@param key - suffix of the attribute, without \"aria-\" prefix, e.g. \"labelledby\"",
"@param codec - used to encode V into String, e.g. StringAsIsCodec",
"",
"@tparam V - value type for this attr in Scala",
),
baseImplName = "ariaAttr",
namespaceImports = Nil,
namespaceImpl = _ => ???,
transformAttrDomName = transformAttrDomName,
defType = LazyVal
)

generator.writeToFile(
packagePath = generator.attrDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}

// -- HTML props --

{
val traitName = "HtmlProps"

val fileContent = generator.generatePropsTrait(
defGroups = defGroups.propDefGroups,
printDefGroupComments = true,
traitCommentLines = Nil,
traitName = traitName,
keyKind = "HtmlProp",
implNameSuffix = "Prop",
baseImplDefComments = List(
"Create custom HTML element property",
"",
"@param key - name of the prop in JS, e.g. \"value\"",
"@param codec - used to encode V into DomV, e.g. StringAsIsCodec,",
"",
"@tparam V - value type for this prop in Scala",
"@tparam DomV - value type for this prop in the underlying JS DOM.",
),
baseImplName = "htmlProp",
defType = LazyVal
)

generator.writeToFile(
packagePath = generator.propDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}

// -- Event props --

{
val baseTraitName = "GlobalEventProps"

val subTraits = List(
"WindowEventProps" -> defGroups.windowEventPropDefGroups,
"DocumentEventProps" -> defGroups.documentEventPropDefGroups
)

{
val fileContent = generator.generateEventPropsTrait(
defSources = defGroups.globalEventPropDefGroups,
printDefGroupComments = true,
traitCommentLines = Nil,
traitName = baseTraitName,
traitExtends = Nil,
traitThisType = None,
baseImplDefComments = List(
"Create custom event property",
"",
"@param key - event type in JS, e.g. \"click\"",
"",
"@tparam Ev - event type in JS, e.g. dom.MouseEvent",
),
outputBaseImpl = true,
keyKind = "EventProp",
keyImplName = "eventProp",
defType = LazyVal
)

generator.writeToFile(
packagePath = generator.eventPropDefsPackagePath,
fileName = baseTraitName,
fileContent = fileContent
)
}

subTraits.foreach { case (traitName, eventPropsDefGroups) =>
val fileContent = generator.generateEventPropsTrait(
defSources = eventPropsDefGroups,
printDefGroupComments = true,
traitCommentLines = List(eventPropsDefGroups.head._1),
traitName = traitName,
traitExtends = Nil,
traitThisType = Some(baseTraitName),
baseImplDefComments = Nil,
outputBaseImpl = false,
keyKind = "EventProp",
keyImplName = "eventProp",
defType = LazyVal
)

generator.writeToFile(
packagePath = generator.eventPropDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}
}

// -- Style props --

{
val traitName = "StyleProps"

val fileContent = generator.generateStylePropsTrait(
defSources = defGroups.stylePropDefGroups,
printDefGroupComments = true,
traitCommentLines = Nil,
traitName = traitName,
keyKind = "StyleProp",
keyKindAlias = "StyleProp",
setterType = "StyleSetter",
setterTypeAlias = "SS",
derivedKeyKind = "DerivedStyleProp",
derivedKeyKindAlias = "DSP",
baseImplDefComments = List(
"Create custom CSS property",
"",
"@param key - name of CSS property, e.g. \"font-weight\"",
"",
"@tparam V - type of values recognized by JS for this property, e.g. Int",
" Note: String is always allowed regardless of the type you put here.",
" If unsure, use String type as V.",
),
baseImplName = "styleProp",
defType = LazyVal,
lengthUnitsNumType = "Int",
outputUnitTraits = true
)

generator.writeToFile(
packagePath = generator.stylePropDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}

// -- Style keyword traits

{
StyleTraits.defs.foreach { styleTrait =>
val fileContent = generator.generateStyleKeywordsTrait(
defSources = styleTrait.keywordDefGroups,
printDefGroupComments = styleTrait.keywordDefGroups.length > 1,
traitCommentLines = Nil,
traitName = styleTrait.scalaName,
extendsTraits = styleTrait.extendsTraits,
extendsUnitTraits = styleTrait.extendsUnits,
propKind = "StyleProp",
keywordType = "StyleSetter",
derivedKeyKind = "DerivedStyleProp",
lengthUnitsNumType = "Int",
defType = LazyVal,
outputUnitTypes = true,
allowSuperCallInOverride = false // can't access lazy val from `super`
)

generator.writeToFile(
packagePath = generator.styleTraitsPackagePath(),
fileName = styleTrait.scalaName,
fileContent = fileContent
)
}
}
}

}

0 comments on commit 4057b51

Please sign in to comment.