diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt index 4e37cdb9287..5817d8af865 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildReportsIT.kt @@ -92,7 +92,7 @@ class BuildReportsIT : KGPBaseTest() { } } - @DisplayName("") + @DisplayName("smoke") @GradleTest fun testSingleBuildMetricsFileSmoke(gradleVersion: GradleVersion) { project("simpleProject", gradleVersion) { @@ -109,4 +109,4 @@ class BuildReportsIT : KGPBaseTest() { } } } -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsConfigurationCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsConfigurationCacheIT.kt index 9b894d8ffae..cfb3a03642c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsConfigurationCacheIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsConfigurationCacheIT.kt @@ -8,8 +8,10 @@ package org.jetbrains.kotlin.gradle import org.gradle.api.logging.configuration.WarningMode import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType +import org.jetbrains.kotlin.gradle.targets.js.NpmVersions import org.jetbrains.kotlin.gradle.testbase.* import org.junit.jupiter.api.DisplayName +import javax.inject.Inject abstract class AbstractJsConfigurationCacheIT(protected val irBackend: Boolean) : KGPBaseTest() { private val defaultJsOptions = BuildOptions.JsOptions( @@ -93,4 +95,4 @@ abstract class AbstractJsConfigurationCacheIT(protected val irBackend: Boolean) class JsConfigurationCacheIT : AbstractJsConfigurationCacheIT(irBackend = false) @JsGradlePluginTests -class JsIrConfigurationCacheIT : AbstractJsConfigurationCacheIT(irBackend = true) \ No newline at end of file +class JsIrConfigurationCacheIT : AbstractJsConfigurationCacheIT(irBackend = true) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt index 43d5a301dcd..e8debe2bb3d 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt @@ -74,7 +74,12 @@ fun KGPBaseTest.project( localRepoDir?.let { testProject.configureLocalRepository(localRepoDir) } if (buildJdk != null) testProject.setupNonDefaultJdk(buildJdk) - testProject.test() + runCatching { + testProject.test() + }.onFailure { + // A convenient place to place a breakpoint to be able to inspect project output files + throw it + } return testProject } @@ -411,6 +416,7 @@ internal fun Path.addPluginManagementToSettings() { it } } + Files.exists(settingsGradleKts) -> settingsGradleKts.modify { if (!it.contains("pluginManagement {")) { """ @@ -422,6 +428,7 @@ internal fun Path.addPluginManagementToSettings() { it } } + else -> settingsGradle.toFile().writeText(DEFAULT_GROOVY_SETTINGS_FILE) } @@ -519,6 +526,7 @@ internal fun Path.enableCacheRedirector() { """.trimIndent() ) } + "build.gradle.kts" -> { it.appendText( """ diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-library-ir/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-library-ir/build.gradle.kts index e906841a706..4b844bba0bf 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-library-ir/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-library-ir/build.gradle.kts @@ -12,7 +12,12 @@ kotlin { js(IR) { browser { commonWebpackConfig { - cssSupport.enabled = true + cssSupport { + enabled.set(true) + } + scssSupport { + enabled.set(true) + } } } } @@ -20,4 +25,4 @@ kotlin { dependencies { "jsMainImplementation"(npm("decamelize", "4.0.0", generateExternals = true)) -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-browser-project/app/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-browser-project/app/build.gradle.kts index 6c91a98edc0..ed3c9edf7b3 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-browser-project/app/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-browser-project/app/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.targets.js.NpmVersions +import javax.inject.Inject + plugins { kotlin("js") } @@ -9,18 +12,35 @@ dependencies { testImplementation(kotlin("test-js")) } +abstract class CustomWebpackRule +@javax.inject.Inject +constructor(name: String) : org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackRule(name) { + init { + test.set("none") + } + override fun loaders() = listOf() +} + kotlin { target { browser { webpackTask { - cssSupport.enabled = true + cssSupport { + enabled.set(true) + } + scssSupport { + enabled.set(true) + } + rules { + rule("custom") + } } testTask { useKarma { useChromeHeadless() } enabled = false // Task is disabled because it requires browser to be installed. That may be a problem on CI. - // Disabled but configured task allows us to check at least a part of configuration cache correctness. + // Disabled but configured task allows us to check at least a part of configuration cache correctness. } } binaries.executable() @@ -31,4 +51,4 @@ kotlin { } } } -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen/build.gradle.kts index b0f468181b6..4eb27629335 100644 --- a/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen/build.gradle.kts @@ -19,4 +19,4 @@ listOf(generateNpmVersions).forEach { "org.jetbrains.kotlin.generators.gradle.targets.js.outputSourceRoot", project(":kotlin-gradle-plugin").projectDir.resolve("src/common/kotlin").absolutePath ) -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/targets/js/NpmPackages.kt b/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/targets/js/NpmPackages.kt index e25d53de696..3961349dc8c 100644 --- a/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/targets/js/NpmPackages.kt +++ b/libraries/tools/kotlin-gradle-plugin-npm-versions-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/targets/js/NpmPackages.kt @@ -19,6 +19,8 @@ val npmPackages = listOf( NpmPackage("source-map-support"), NpmPackage("css-loader"), NpmPackage("style-loader"), + NpmPackage("sass-loader"), + NpmPackage("sass"), NpmPackage("to-string-loader"), NpmPackage("mini-css-extract-plugin"), NpmPackage("mocha"), @@ -40,4 +42,4 @@ data class NpmPackage( val name: String, val version: String? = null, val displayName: String = name -) \ No newline at end of file +) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/NpmVersions.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/NpmVersions.kt index 2302f97c032..85e26658e63 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/NpmVersions.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/NpmVersions.kt @@ -25,6 +25,8 @@ class NpmVersions : Serializable { val sourceMapSupport = NpmPackageVersion("source-map-support", "0.5.21") val cssLoader = NpmPackageVersion("css-loader", "6.7.1") val styleLoader = NpmPackageVersion("style-loader", "3.3.1") + val sassLoader = NpmPackageVersion("sass-loader", "13.0.0") + val sass = NpmPackageVersion("sass", "1.52.3") val toStringLoader = NpmPackageVersion("to-string-loader", "1.2.0") val miniCssExtractPlugin = NpmPackageVersion("mini-css-extract-plugin", "2.6.1") val mocha = NpmPackageVersion("mocha", "10.0.0") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinWebpackRulesContainer.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinWebpackRulesContainer.kt new file mode 100644 index 00000000000..2b728c2e20f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinWebpackRulesContainer.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.dsl + +import org.gradle.api.Action +import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer +import org.gradle.api.model.ObjectFactory +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackRule + +class KotlinWebpackRulesContainer( + container: ExtensiblePolymorphicDomainObjectContainer, + private val objectFactory: ObjectFactory, +) : ExtensiblePolymorphicDomainObjectContainer by container { + inline fun rule(name: String, config: Action = Action {}) { + rule(T::class.java, name, config) + } + + fun rule(type: Class, name: String, config: Action = Action {}) { + add(objectFactory.newInstance(type, name).also(config::execute)) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/WebpackRulesDsl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/WebpackRulesDsl.kt new file mode 100644 index 00000000000..42ddb1f7561 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/WebpackRulesDsl.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.dsl + +import org.gradle.api.Action +import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer +import org.gradle.api.model.ObjectFactory +import org.gradle.api.tasks.Nested +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssRule +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackRule +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackScssRule +import org.jetbrains.kotlin.gradle.utils.newInstance + +interface WebpackRulesDsl { + @get:Nested + val rules: KotlinWebpackRulesContainer + + fun rules(action: Action) { + action.execute(rules) + } + + fun cssSupport(action: Action) { + val rule = rules.maybeCreate("css", KotlinWebpackCssRule::class.java) + action.execute(rule) + } + + fun scssSupport(action: Action) { + val rule = rules.maybeCreate("scss", KotlinWebpackScssRule::class.java) + action.execute(rule) + } + + companion object { + private inline fun ObjectFactory.bindTo( + container: ExtensiblePolymorphicDomainObjectContainer + ) = container.registerFactory(T::class.java) { newInstance(it) } + + fun ObjectFactory.webpackRulesContainer(): KotlinWebpackRulesContainer { + val delegate = polymorphicDomainObjectContainer(KotlinWebpackRule::class.java).also { + bindTo(it) + bindTo(it) + } + return KotlinWebpackRulesContainer(delegate, this) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt index e108c476665..24accd8ffad 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecuti import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.* +import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl.Companion.webpackRulesContainer import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject @@ -40,11 +41,11 @@ class KotlinKarma( @Transient override val compilation: KotlinJsCompilation, private val services: () -> ServiceRegistry, private val basePath: String -) : - KotlinJsTestFramework { +) : KotlinJsTestFramework { @Transient private val project: Project = compilation.target.project private val npmProject = compilation.npmProject + @Transient private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) private val nodeRootPackageDir by lazy { nodeJs.rootPackageDir } @@ -80,7 +81,8 @@ class KotlinKarma( export = false, progressReporter = true, progressReporterPathFilter = nodeRootPackageDir.absolutePath, - webpackMajorVersion = webpackMajorVersion + webpackMajorVersion = webpackMajorVersion, + rules = project.objects.webpackRulesContainer(), ) init { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt index 52832ae5937..551b34ec6a9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt @@ -29,6 +29,9 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.distsDirectory import org.jetbrains.kotlin.gradle.report.BuildMetricsReporterService import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency +import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWebpackRulesContainer +import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl +import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl.Companion.webpackRulesContainer import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject @@ -47,7 +50,7 @@ constructor( @Transient override val compilation: KotlinJsCompilation, objects: ObjectFactory -) : DefaultTask(), RequiresNpmDependencies { +) : DefaultTask(), RequiresNpmDependencies, WebpackRulesDsl { @Transient private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) private val versions = nodeJs.versions @@ -62,6 +65,9 @@ constructor( open val fileResolver: FileResolver get() = injected + override val rules: KotlinWebpackRulesContainer = + project.objects.webpackRulesContainer() + @get:Inject open val execHandleFactory: ExecHandleFactory get() = injected @@ -204,9 +210,6 @@ constructor( @Input var sourceMaps: Boolean = true - @Nested - val cssSupport: KotlinWebpackCssSupport = KotlinWebpackCssSupport() - @Input @Optional var devServer: KotlinWebpackConfig.DevServer? = null @@ -219,7 +222,9 @@ constructor( var generateConfigOnly: Boolean = false @Nested - val synthConfig = KotlinWebpackConfig() + val synthConfig = KotlinWebpackConfig( + rules = project.objects.webpackRulesContainer(), + ) @Input val webpackMajorVersion = PropertiesProvider(project).webpackMajorVersion @@ -249,7 +254,7 @@ constructor( outputFileName = outputFileName, configDirectory = configDirectory, bundleAnalyzerReportDir = if (!forNpmDependencies && report) reportDir else null, - cssSupport = cssSupport, + rules = rules, devServer = devServer, devtool = devtool, sourceMaps = sourceMaps, diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt index a15109383b6..d1437e0093b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt @@ -8,6 +8,8 @@ package org.jetbrains.kotlin.gradle.targets.js.webpack import com.google.gson.GsonBuilder +import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer +import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested @@ -15,10 +17,9 @@ import org.gradle.api.tasks.Optional import org.jetbrains.kotlin.gradle.targets.js.NpmVersions import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.appendConfigsFromDir +import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWebpackRulesContainer +import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl import org.jetbrains.kotlin.gradle.targets.js.jsQuoted -import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssMode.EXTRACT -import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssMode.IMPORT -import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssMode.INLINE import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackMajorVersion.Companion.choose import org.jetbrains.kotlin.gradle.utils.appendLine import java.io.File @@ -51,7 +52,7 @@ data class KotlinWebpackConfig( @Input var experiments: MutableSet = mutableSetOf(), @Nested - var cssSupport: KotlinWebpackCssSupport = KotlinWebpackCssSupport(), + override val rules: KotlinWebpackRulesContainer, @Input @Optional var devtool: String? = WebpackDevtool.EVAL_SOURCE_MAP, @@ -70,7 +71,7 @@ data class KotlinWebpackConfig( var resolveFromModulesFirst: Boolean = false, @Input val webpackMajorVersion: WebpackMajorVersion = WebpackMajorVersion.V5 -) { +) : WebpackRulesDsl { @get:Input @get:Optional val entryInput: String? @@ -135,18 +136,11 @@ data class KotlinWebpackConfig( ) } - if (!cssSupport.enabled || cssSupport.rules.isEmpty()) return@also - - it.add(versions.cssLoader) - cssSupport.rules.forEach { rule -> - when (rule.mode) { - EXTRACT -> it.add(versions.miniCssExtractPlugin) - INLINE -> it.add(versions.styleLoader) - IMPORT -> it.add(versions.toStringLoader) - else -> cssError() + rules.forEach { rule -> + if (rule.active) { + it.addAll(rule.dependencies(versions)) } } - } enum class Mode(val code: String) { @@ -215,7 +209,11 @@ data class KotlinWebpackConfig( appendDevServer() appendReport() appendProgressReporter() - appendCssSupport() + rules.forEach { rule -> + if (rule.active) { + with(rule) { appendToWebpackConfig() } + } + } appendErrorPlugin() appendFromConfigDir() appendEvaluatedFileReport() @@ -363,109 +361,6 @@ data class KotlinWebpackConfig( ) } - private fun Appendable.appendCssSupport() { - if (!cssSupport.enabled || cssSupport.rules.isEmpty()) - return - - appendLine( - """ - // css settings - ;(function(config) { - """.trimIndent() - ) - - val extractedCss = - """ - | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); - | use.unshift({ - | loader: MiniCssExtractPlugin.loader, - | options: {} - | }) - | config.plugins.push(new MiniCssExtractPlugin()) - """.trimMargin() - - val inlinedCss = - """ - | use.unshift({ - | loader: 'style-loader', - | options: {} - | }) - | - """.trimMargin() - - val importedCss = - """ - | use.unshift({ - | loader: 'to-string-loader', - | options: {} - | }) - | - """.trimMargin() - - cssSupport.rules.forEach { rule -> - appendLine( - """ - | ;(function(config) { - """.trimMargin() - ) - appendLine( - """ - | const use = [ - | { - | loader: 'css-loader', - | options: {}, - | } - | ] - """.trimMargin() - ) - - when (rule.mode) { - EXTRACT -> appendLine(extractedCss) - INLINE -> appendLine(inlinedCss) - IMPORT -> appendLine(importedCss) - else -> cssError() - } - - val excluded = rule.exclude.let { - if (it.isNotEmpty()) { - "[${it.joinToString()}]" - } else null - } - - val included = rule.include.let { - if (it.isNotEmpty()) { - "[${it.joinToString()}]" - } else null - } - - appendLine( - """ - | config.module.rules.push({ - | test: /\.css${'$'}/, - | use: use, - | ${excluded?.let { "exclude: $it," } ?: ""} - | ${included?.let { "include: $it" } ?: ""} - | }) - - """.trimMargin() - ) - - appendLine( - """ - | })(config); - - """.trimMargin() - ) - } - - appendLine( - """ - })(config); - - """.trimIndent() - ) - } - private fun Appendable.appendErrorPlugin() { //language=ES6 appendLine( @@ -525,18 +420,6 @@ data class KotlinWebpackConfig( ) } - private fun cssError() { - throw IllegalStateException( - """ - Possible values for cssSupport.mode: - - EXTRACT - - INLINE - - IMPORT - """.trimIndent() - ) - } - - private fun json(obj: Any) = StringWriter().also { GsonBuilder().setPrettyPrinting().create().toJson(obj, it) }.toString() diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackCssRule.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackCssRule.kt new file mode 100644 index 00000000000..5a854c03ec9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackCssRule.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.webpack + +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.jetbrains.kotlin.gradle.targets.js.NpmVersions +import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssMode.EXTRACT +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssMode.IMPORT +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssMode.INLINE +import javax.inject.Inject + +@Deprecated( + message = "Renamed to KotlinWebpackCssRule", replaceWith = ReplaceWith( + expression = "KotlinWebpackCssRule", + imports = arrayOf("org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssRule") + ) +) +typealias KotlinWebpackCssSupport = KotlinWebpackCssRule + +@Suppress("LeakingThis") +abstract class KotlinWebpackCssRule @Inject constructor(name: String) : KotlinWebpackRule(name) { + @get:Input + abstract val mode: Property + + init { + mode.convention(INLINE) + test.convention("/\\.css\$/") + } + + override fun validate(): Boolean { + if (mode.get() !in arrayOf(EXTRACT, INLINE, IMPORT)) { + error( + """ + Possible values for cssSupport.mode: + - EXTRACT + - INLINE + - IMPORT + """.trimIndent() + ) + } + return true + } + + override fun dependencies(versions: NpmVersions): Collection { + return mutableListOf().apply { + add(versions.cssLoader) + when (mode.get()) { + EXTRACT -> add(versions.miniCssExtractPlugin) + INLINE -> add(versions.styleLoader) + IMPORT -> add(versions.toStringLoader) + } + } + } + + override fun loaders(): List = when (mode.get()) { + EXTRACT -> listOf( + Loader( + loader = "MiniCssExtractPlugin.loader", + prerequisites = listOf( + "const MiniCssExtractPlugin = require('mini-css-extract-plugin');", + "config.plugins.push(new MiniCssExtractPlugin())" + ) + ), + ) + + INLINE -> listOf(Loader(loader = "'style-loader'")) + IMPORT -> listOf(Loader(loader = "'to-string-loader'")) + else -> listOf() + } + Loader(loader = "'css-loader'") +} + +object KotlinWebpackCssMode { + const val EXTRACT = "extract" + const val INLINE = "inline" + const val IMPORT = "import" +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackCssSupport.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackCssSupport.kt deleted file mode 100644 index 2a8c3d62157..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackCssSupport.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.gradle.targets.js.webpack - -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Nested -import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackCssMode.INLINE - -data class KotlinWebpackCssSupport( - @Input - var enabled: Boolean = false, - - @Nested - var rules: MutableList = mutableListOf( - KotlinWebpackCssRule() - ) -) { - @get:Internal - var mode: String - get() = - rules - .singleOrNull() - ?.mode ?: singleRuleError() - set(value) { - rules.singleOrNull() - ?.let { - it.mode = value - } ?: singleRuleError() - } - - private fun singleRuleError(): Nothing { - error("CSS mode shortcut can be applied only with one css rule") - } -} - -data class KotlinWebpackCssRule( - @Input - var mode: String = INLINE, - - @Input - var include: MutableList = mutableListOf(), - - @Input - var exclude: MutableList = mutableListOf() -) - -object KotlinWebpackCssMode { - const val EXTRACT = "extract" - const val INLINE = "inline" - const val IMPORT = "import" -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRule.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRule.kt new file mode 100644 index 00000000000..792a6fda702 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRule.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.webpack + +import com.google.gson.GsonBuilder +import org.gradle.api.Named +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.jetbrains.kotlin.gradle.targets.js.NpmVersions +import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency +import org.jetbrains.kotlin.gradle.utils.appendLine +import java.io.StringWriter +import javax.inject.Inject + +@Suppress("LeakingThis") +abstract class KotlinWebpackRule @Inject constructor(private val name: String) : Named { + @get:Input + abstract val enabled: Property + + /** + * Raw rule `test` field value. Needs to be wrapped in quotes when using string notation. + */ + @get:Input + abstract val test: Property + + @get:Input + abstract val include: ListProperty + + @get:Input + abstract val exclude: ListProperty + + @get:Input + protected open val description: String + get() = (this::class.simpleName?.removeSuffix("_Decorated") ?: "KotlinWebpackRule") + "[${getName()}]" + + init { + enabled.convention(false) + } + + /** + * Validates the rule state just before it getting applied. + * Returning false will skip the rule silently. To terminate the build instead, throw an error. + */ + open fun validate(): Boolean = true + + /** + * Provides a list of required npm dependencies for the rule to function. + */ + open fun dependencies(versions: NpmVersions): Collection = listOf() + + /** + * Provides a loaders sequence to apply to the rule. + */ + protected abstract fun loaders(): List + + @get:Internal + internal val active: Boolean get() = enabled.get() && validate() + internal fun Appendable.appendToWebpackConfig() { + appendLine( + """ + // $description + ;(function(config) { + """.trimIndent() + ) + val loaders = loaders() + loaders.flatMap(Loader::prerequisites).forEach(::appendLine) + val use = loaders.joinToString( + separator = ",", + prefix = "[", + postfix = "]", + ) { + """ + { + loader: ${it.loader}, + options: ${json(it.options)} + } + """.trimIndent() + } + appendLine( + """ + const use = $use + """.trimIndent() + ) + + val excluded = exclude.get().takeIf(List<*>::isNotEmpty) + ?.joinToString(separator = ",", prefix = "[", postfix = "]") ?: "undefined" + val included = include.get().takeIf(List<*>::isNotEmpty) + ?.joinToString(separator = ",", prefix = "[", postfix = "]") ?: "undefined" + appendLine( + """ + config.module.rules.push({ + test: ${test.get()}, + use: use, + exclude: $excluded, + include: $included, + }) + """.trimIndent() + ) + appendLine( + """ + })(config); + + """.trimIndent() + ) + } + + protected fun json(obj: Any) = StringWriter().also { + GsonBuilder().setPrettyPrinting().create().toJson(obj, it) + }.toString() + + @Internal + override fun getName(): String = name + override fun equals(other: Any?): Boolean = other is KotlinWebpackRule && getName() == other.getName() + override fun hashCode(): Int = getName().hashCode() + + data class Loader( + /** + * Raw `loader` field value. Needs to be wrapped in quotes if using string notation. + */ + val loader: String, + /** + * Loader options map if any. Will be converted to json object via Gson. + */ + val options: Map = mapOf(), + /** + * Any prerequisite code to be added before building the loader object. + */ + val prerequisites: List = listOf(), + ) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackScssRule.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackScssRule.kt new file mode 100644 index 00000000000..82c42f1a51a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackScssRule.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.webpack + +import org.jetbrains.kotlin.gradle.targets.js.NpmVersions +import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency +import javax.inject.Inject + +@Suppress("LeakingThis") +abstract class KotlinWebpackScssRule @Inject constructor(name: String) : KotlinWebpackCssRule(name) { + init { + test.convention("/\\.(scss|sass)\$/") + } + + override fun dependencies(versions: NpmVersions): Collection { + return super.dependencies(versions) + versions.sass + versions.sassLoader + } + + override fun loaders(): List { + return super.loaders() + Loader(loader = "'sass-loader'") + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/objectFactoryExt.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/objectFactoryExt.kt index bff161dc7fb..ac43dbe3918 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/objectFactoryExt.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/objectFactoryExt.kt @@ -9,4 +9,7 @@ import org.gradle.api.Named import org.gradle.api.model.ObjectFactory internal inline fun ObjectFactory.named(name: String): T = - named(T::class.java, name) \ No newline at end of file + named(T::class.java, name) + +internal inline fun ObjectFactory.newInstance(vararg args: Any?): T = + newInstance(T::class.java, *args)