Wizard: add support of react js to the js template

This commit is contained in:
Ilya Kirillov
2020-01-22 16:29:17 +03:00
parent 351a037bcd
commit d330652eee
12 changed files with 255 additions and 21 deletions
@@ -0,0 +1,17 @@
import kotlinx.css.*
import styled.StyleSheet
object WelcomeStyles : StyleSheet("WelcomeStyles", isStatic = true) {
val textContainer by css {
padding(5.px)
backgroundColor = rgb(8, 97, 22)
color = rgb(56, 246, 137)
}
val textInput by css {
margin(vertical = 5.px)
fontSize = 14.px
}
}
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS Client</title>
</head>
<body>
<script src="${moduleName}.js"></script>
<div id="root"></div>
</body>
</html>
@@ -0,0 +1,15 @@
import react.dom.render
import kotlin.browser.document
import kotlin.browser.window
fun main() {
window.onload = {
render(document.getElementById("root")) {
child(Welcome::class) {
attrs {
name = "Kotlin/JS"
}
}
}
}
}
@@ -0,0 +1,59 @@
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import org.w3c.dom.HTMLInputElement
import react.RBuilder
import react.RComponent
import react.RProps
import react.RState
#if($useStyledComponents)
import styled.css
import styled.styledDiv
import styled.styledInput
#else
import react.dom.div
import react.dom.input
#end
interface WelcomeProps : RProps {
var name: String
}
data class WelcomeState(val name: String) : RState
class Welcome(props: WelcomeProps) : RComponent<WelcomeProps, WelcomeState>(props) {
init {
state = WelcomeState(props.name)
}
override fun RBuilder.render() {
#if($useStyledComponents)
styledDiv {
css {
+WelcomeStyles.textContainer
}
#else
div {
#end
+"Hello, ${state.name}"
}
#if($useStyledComponents)
styledInput {
css {
+WelcomeStyles.textInput
}
#else
input {
#end
attrs {
type = InputType.text
value = state.name
onChangeFunction = { event ->
setState(
WelcomeState(name = (event.target as HTMLInputElement).value)
)
}
}
}
}
}
@@ -6,6 +6,7 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.MavenPrinter
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
@@ -67,4 +68,10 @@ data class MultiplatformModuleIR(
)
}.listNl(needFirstIndent = false)
}
}
}
fun MultiplatformModuleIR.neighbourTargetModules() =
originalModule.parent
?.takeIf { it.kind == ModuleKind.multiplatform }
?.subModules
.orEmpty()
@@ -1,11 +1,8 @@
package org.jetbrains.kotlin.tools.projectWizard.plugins
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.Plugin
import org.jetbrains.kotlin.tools.projectWizard.core.TaskRunningContext
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidators
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.core.pathParser
import org.jetbrains.kotlin.tools.projectWizard.core.service.FileSystemWizardService
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.PomIR
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
@@ -39,9 +36,13 @@ class StructurePlugin(context: Context) : Plugin(context) {
}
}
val TaskRunningContext.projectPath
val ValuesReadingContext.projectPath
get() = StructurePlugin::projectPath.reference.settingValue
val ValuesReadingContext.projectName
get() = StructurePlugin::name.reference.settingValue
fun TaskRunningContext.pomIR() = PomIR(
artifactId = StructurePlugin::artifactId.reference.settingValue,
groupId = StructurePlugin::groupId.reference.settingValue,
@@ -15,6 +15,8 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ProjectKind
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.BuildFilePrinter
import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.printBuildFile
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectPath
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Repository
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.updateBuildFiles
@@ -44,7 +46,7 @@ abstract class BuildSystemPlugin(context: Context) : Plugin(context) {
val takeRepositoriesFromDependencies by pipelineTask(GenerationPhase.PROJECT_GENERATION) {
runBefore(BuildSystemPlugin::createModules)
runAfter(KotlinPlugin::createModules)
runAfter(TemplatesPlugin::postApplyTemplatesToModules)
withAction {
updateBuildFiles { buildFile ->
@@ -116,7 +116,7 @@ class ModulesToIRsConverter(
}
val moduleIr = SingleplatformModuleIR(
module.name,
if (modulePath == projectPath) projectName else module.name,
modulePath,
dependenciesIRs,
module.template,
@@ -8,6 +8,8 @@ import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectName
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectPath
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.updateBuildFiles
import org.jetbrains.kotlin.tools.projectWizard.templates.*
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.InterceptionPoint
@@ -111,7 +113,11 @@ class TemplatesPlugin(context: Context) : Plugin(context) {
): TaskResult<Unit> {
val template = module.template ?: return UNIT_SUCCESS
val settings = with(template) { settingsAsMap(module.originalModule) }
val allSettings = settings + interceptionPointSettings.mapKeys { it.key.name }
val allSettings: Map<String, Any> = mutableMapOf<String, Any>().apply {
putAll(settings)
putAll(interceptionPointSettings.mapKeys { it.key.name })
putAll(defaultSettings(module))
}
return with(template) { getFileTemplates(module) }.map { (fileTemplateDescriptor, filePath) ->
val path = generatePathForFileTemplate(module, filePath)
val fileTemplate = FileTemplate(
@@ -123,11 +129,16 @@ class TemplatesPlugin(context: Context) : Plugin(context) {
}.sequenceIgnore()
}
private fun TaskRunningContext.defaultSettings(moduleIR: ModuleIR) = mapOf(
"projectName" to projectName,
"moduleName" to moduleIR.name
)
private fun generatePathForFileTemplate(module: ModuleIR, filePath: FilePath) = when (module) {
is SingleplatformModuleIR -> {
when (filePath) {
is SrcFilePath -> SRC_DIR / filePath.sourcesetType.toString() / KOTLIN_DIR
is ResourcesFilePath -> RESOURCES_DIR / filePath.sourcesetType.toString()
is ResourcesFilePath -> SRC_DIR / filePath.sourcesetType.toString() / RESOURCES_DIR
}
}
@@ -39,6 +39,7 @@ data class BintrayRepository(val repository: String) : CustomMavenRepository {
object Repositories {
val KTOR_BINTRAY = BintrayRepository("kotlin/ktor")
val KOTLIN_JS_WRAPPERS_BINTRAY = BintrayRepository("kotlin/kotlin-js-wrappers")
val KOTLIN_EAP_BINTRAY = BintrayRepository("kotlin/kotlin-eap")
val KOTLIN_DEV_BINTRAY = BintrayRepository("kotlin/kotlin-dev")
}
@@ -1,12 +1,19 @@
package org.jetbrains.kotlin.tools.projectWizard.templates
import org.jetbrains.kotlin.tools.projectWizard.core.asPath
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import java.nio.file.Path
import java.nio.file.Paths
// Should be used to create any kind of files in the generated project
// Except build files as they will be generated using IR
data class FileTemplateDescriptor(val templateId: String, val relativePath: Path)
data class FileTemplateDescriptor(val templateId: String, val relativePath: Path) {
constructor(templateId: String) : this(
templateId,
Paths.get(templateId).fileName.toString().removeSuffix(".vm").asPath()
)
}
sealed class FilePath {
abstract val sourcesetType: SourcesetType
@@ -8,16 +8,21 @@ package org.jetbrains.kotlin.tools.projectWizard.templates
import org.jetbrains.kotlin.tools.projectWizard.core.TaskRunningContext
import org.jetbrains.kotlin.tools.projectWizard.core.asPath
import org.jetbrains.kotlin.tools.projectWizard.core.buildList
import org.jetbrains.kotlin.tools.projectWizard.core.entity.TemplateSetting
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.multiplatform.DefaultTargetConfigurationIR
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.library.NpmArtifact
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.JsBrowserTargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.DefaultRepository
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Repositories
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.transformers.interceptors.TemplateInterceptor
@@ -32,19 +37,62 @@ class SimpleJsClientTemplate : Template() {
override fun isApplicableTo(module: Module): Boolean =
module.configurator == JsBrowserTargetConfigurator
override fun TaskRunningContext.getRequiredLibraries(module: ModuleIR): List<DependencyIR> =
val renderEngine by enumSetting<RenderEngine>("Rendering engine", GenerationPhase.PROJECT_GENERATION) {
defaultValue = RenderEngine.REACT_WITH_STYLED
}
override val settings: List<TemplateSetting<*, *>> = listOf(renderEngine)
override fun TaskRunningContext.getRequiredLibraries(module: ModuleIR): List<DependencyIR> = withSettingsOf(module.originalModule) {
buildList {
+ArtifactBasedLibraryDependencyIR(
MavenArtifact(DefaultRepository.JCENTER, "org.jetbrains.kotlinx", "kotlinx-html-js"),
Version.fromString("0.6.12"),
DependencyType.MAIN
)
if (renderEngine.reference.settingValue != RenderEngine.KOTLINX_HTML) {
+Dependencies.KOTLIN_REACT
+Dependencies.KOTLIN_REACT_DOM
+Dependencies.NPM_REACT
+Dependencies.NPM_REACT_DOM
if (renderEngine.reference.settingValue == RenderEngine.REACT_WITH_STYLED) {
+Dependencies.NPM_REACT_IS
+Dependencies.KOTLIN_STYLED
+Dependencies.NPM_STYLED_COMPONENTS
+Dependencies.NPM_INLINE_STYLE_PREFIXER
}
}
}
}
override fun TaskRunningContext.getFileTemplates(module: ModuleIR): List<FileTemplateDescriptorWithPath> =
withSettingsOf(module.originalModule) {
buildList {
val hasKtorServNeighbourTarget = module.safeAs<MultiplatformModuleIR>()
?.neighbourTargetModules()
.orEmpty()
.any { module ->
module.template is KtorServerTemplate
}
if (!hasKtorServNeighbourTarget) {
+(FileTemplateDescriptor("$id/index.html.vm") asResourceOf SourcesetType.main)
}
if (renderEngine.reference.settingValue == RenderEngine.KOTLINX_HTML) {
+(FileTemplateDescriptor("$id/client.kt.vm") asSrcOf SourcesetType.main)
+(FileTemplateDescriptor("$id/TestClient.kt.vm", "TestClient.kt".asPath()) asSrcOf SourcesetType.test)
} else {
+(FileTemplateDescriptor("$id/reactClient.kt.vm", "client.kt".asPath()) asSrcOf SourcesetType.main)
+(FileTemplateDescriptor("$id/reactComponent.kt.vm", "welcome.kt".asPath()) asSrcOf SourcesetType.main)
}
if (renderEngine.reference.settingValue == RenderEngine.REACT_WITH_STYLED) {
+(FileTemplateDescriptor("$id/WelcomeStyles.kt.vm") asSrcOf SourcesetType.main)
}
}
}
override fun TaskRunningContext.getFileTemplates(module: ModuleIR): List<FileTemplateDescriptorWithPath> = buildList {
+(FileTemplateDescriptor("$id/client.kt.vm", "client.kt".asPath()) asSrcOf SourcesetType.main)
+(FileTemplateDescriptor("$id/TestClient.kt.vm", "TestClient.kt".asPath()) asSrcOf SourcesetType.test)
}
override fun createInterceptors(module: ModuleIR): List<TemplateInterceptor> = buildList {
+interceptTemplate(KtorServerTemplate()) {
@@ -78,7 +126,12 @@ class SimpleJsClientTemplate : Template() {
if (value.isNotEmpty()) return@interceptAtPoint value
buildList {
+value
+"""script(src = "/static/output.js") {}"""
+"""
div {
id = "root"
}
""".trimIndent()
+"""script(src = "/static/$JS_OUTPUT_FILE_NAME") {}"""
}
}
@@ -88,7 +141,7 @@ class SimpleJsClientTemplate : Template() {
target.safeAs<DefaultTargetConfigurationIR>()?.targetAccess?.type == ModuleSubType.jvm
} as? DefaultTargetConfigurationIR ?: return@transformBuildFile null
val jvmTargetName = jvmTarget.targetName
val webPackTaskName = "${jsSourcesetName}BrowserWebpack"
val webPackTaskName = "$jsSourcesetName$WEBPACK_TASK_SUFFIX"
val jvmJarTaskAccess = GradleByNameTaskAccessIR("${jvmTargetName}Jar", "Jar")
val jvmJarTaskConfiguration = run {
@@ -133,13 +186,12 @@ class SimpleJsClientTemplate : Template() {
}
}
override fun TaskRunningContext.getIrsToAddToBuildFile(module: ModuleIR): List<BuildSystemIR> = buildList {
+RepositoryIR(DefaultRepository.JCENTER)
if (module is MultiplatformModuleIR) {
+GradleImportIR("org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack")
val taskAccessIR = GradleByNameTaskAccessIR(
"${module.name}BrowserWebpack",
"${module.name}$WEBPACK_TASK_SUFFIX",
WEBPACK_TASK_CLASS
)
@@ -157,5 +209,56 @@ class SimpleJsClientTemplate : Template() {
companion object {
private const val JS_OUTPUT_FILE_NAME = "output.js"
private const val WEBPACK_TASK_CLASS = "KotlinWebpack"
private const val WEBPACK_TASK_SUFFIX = "BrowserProductionWebpack"
}
}
private object Dependencies {
val KOTLIN_REACT = ArtifactBasedLibraryDependencyIR(
MavenArtifact(Repositories.KOTLIN_JS_WRAPPERS_BINTRAY, "org.jetbrains", "kotlin-react"),
Version.fromString("16.9.0-pre.89-kotlin-1.3.60"),
DependencyType.MAIN
)
val KOTLIN_REACT_DOM = ArtifactBasedLibraryDependencyIR(
MavenArtifact(Repositories.KOTLIN_JS_WRAPPERS_BINTRAY, "org.jetbrains", "kotlin-react-dom"),
Version.fromString("16.9.0-pre.89-kotlin-1.3.60"),
DependencyType.MAIN
)
val KOTLIN_STYLED = ArtifactBasedLibraryDependencyIR(
MavenArtifact(Repositories.KOTLIN_JS_WRAPPERS_BINTRAY, "org.jetbrains", "kotlin-styled"),
Version.fromString("1.0.0-pre.89-kotlin-1.3.60"),
DependencyType.MAIN
)
val NPM_REACT = ArtifactBasedLibraryDependencyIR(
NpmArtifact("react"),
Version.fromString("16.12.0"),
DependencyType.MAIN
)
val NPM_REACT_DOM = ArtifactBasedLibraryDependencyIR(
NpmArtifact("react-dom"),
Version.fromString("16.12.0"),
DependencyType.MAIN
)
val NPM_REACT_IS = ArtifactBasedLibraryDependencyIR(
NpmArtifact("react-is"),
Version.fromString("16.12.0"),
DependencyType.MAIN
)
val NPM_STYLED_COMPONENTS = ArtifactBasedLibraryDependencyIR(
NpmArtifact("styled-components"),
Version.fromString("5.0.0"),
DependencyType.MAIN
)
val NPM_INLINE_STYLE_PREFIXER = ArtifactBasedLibraryDependencyIR(
NpmArtifact("inline-style-prefixer"),
Version.fromString("5.1.0"),
DependencyType.MAIN
)
}
enum class RenderEngine(override val text: String) : DisplayableSettingItem {
KOTLINX_HTML("Use statically typed Kotlinx.html DSL"),
REACT("Use Kotlin-wrapped React library"),
REACT_WITH_STYLED("Use Kotlin-wrapped React framework together with Styled Components"),
}
}