Skip to content

Commit

Permalink
Merge pull request #696 from icerockdev/#669-js-test-compilation
Browse files Browse the repository at this point in the history
#669 js test compilation
  • Loading branch information
Alex009 authored May 15, 2024
2 parents af2a266 + 24f8e50 commit 4070eec
Show file tree
Hide file tree
Showing 50 changed files with 304 additions and 122 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ implement all your UI in Kotlin with Jetpack Compose and MOKO resources.
## Requirements

- Gradle version 7.5+
- Kotlin 1.9.20+
- Android Gradle Plugin 7.4.2+
- Android API 16+
- iOS version 11.0+
- Compose Multiplatform 1.6.0+

## Installation

Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
kotlinVersion = "1.9.10"
kotlinVersion = "1.9.20"
androidGradleVersion = "8.1.4"
androidSdkCommonVersion = "31.1.2"

Expand All @@ -14,7 +14,7 @@ androidAppCompatVersion = "1.6.1"
composeUiVersion = "1.5.1"

# jetbrains compose
composeJetbrainsVersion = "1.5.1"
composeJetbrainsVersion = "1.6.0"

# jvm
apacheCommonsTextVersion = "1.10.0"
Expand Down
2 changes: 1 addition & 1 deletion gradle/moko.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
resourcesVersion = "0.24.0-beta-3"
resourcesVersion = "0.24.0-beta-4"

[libraries]
resources = { module = "dev.icerock.moko:resources", version.ref = "resourcesVersion" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ import dev.icerock.moko.resources.desc.StringDesc
@Composable
actual fun StringDesc.localized(): String {
return produceState("", this) {
value = localized()
value = toLocalizedString()
}.value
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ actual fun stringResource(resource: PluralsResource, quantity: Int, vararg args:
@Composable
private fun localized(stringDesc: StringDesc): String {
return produceState(initialValue = "", stringDesc) {
value = stringDesc.localized()
value = stringDesc.toLocalizedString()
}.value
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import dev.icerock.gradle.generator.platform.apple.setupFatFrameworkTasks
import dev.icerock.gradle.generator.platform.apple.setupFrameworkResources
import dev.icerock.gradle.generator.platform.apple.setupTestsResources
import dev.icerock.gradle.generator.platform.js.setupJsKLibResources
import dev.icerock.gradle.generator.platform.js.setupJsResources
import dev.icerock.gradle.generator.platform.js.setupJsResourcesWithLinkTask
import dev.icerock.gradle.tasks.GenerateMultiplatformResourcesTask
import dev.icerock.gradle.utils.kotlinSourceSetsObservable
import org.gradle.api.Plugin
Expand All @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
Expand Down Expand Up @@ -89,6 +90,10 @@ open class MultiplatformResourcesPlugin : Plugin<Project> {
setupTestsResources(target = target)
}

if (target is KotlinJsIrTarget) {
setupJsResourcesWithLinkTask(target = target, project = project)
}

target.compilations.configureEach { compilation ->
compilation.kotlinSourceSetsObservable.forAll { sourceSet: KotlinSourceSet ->
val genTaskProvider: TaskProvider<GenerateMultiplatformResourcesTask> =
Expand Down Expand Up @@ -119,15 +124,12 @@ open class MultiplatformResourcesPlugin : Plugin<Project> {

compilation.compileTaskProvider.configure { compileTask: KotlinCompilationTask<*> ->
compileTask.dependsOn(genTaskProvider)
}

if (target is KotlinJsIrTarget) {
compilation.compileTaskProvider.configure { compileTask ->
compileTask as Kotlin2JsCompile

if (compileTask is Kotlin2JsCompile) {
setupJsResources(
compileTask = compileTask,
resourcesGenerationDir = genTaskProvider.flatMap {
it.outputResourcesDir.asFile
},
projectDir = project.provider { project.projectDir }
)
setupJsKLibResources(
compileTask = compileTask,
resourcesGenerationDir = genTaskProvider.flatMap {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl
import java.io.File

internal class CopyResourcesToExecutableAction(
private val resourcesGeneratedDir: Provider<File>,
private val outputDir: Provider<File>,
private val projectDir: Provider<File>
) : Action<Kotlin2JsCompile> {
override fun execute(task: Kotlin2JsCompile) {
val resourceDir = resourcesGeneratedDir.get()
val resourceDir = outputDir.get()

task.klibs.forEach { dependency ->
copyResourcesFromLibraries(
Expand All @@ -28,17 +28,15 @@ internal class CopyResourcesToExecutableAction(
)
}

generateWebpackConfig(resourceDir)
generateWebpackConfig()
generateKarmaConfig()
}

private fun generateWebpackConfig(resourcesOutput: File) {
private fun generateWebpackConfig() {
val webpackDir = File(projectDir.get(), "webpack.config.d")
webpackDir.mkdirs()

val webpackConfig = File(webpackDir, "moko-resources-generated.js")
val webpackResourcesDir: String = resourcesOutput.absolutePath
.replace("\\", "\\\\")

webpackConfig.writeText(
// language=js
Expand All @@ -48,16 +46,14 @@ internal class CopyResourcesToExecutableAction(
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const mokoResourcePath = path.resolve("$webpackResourcesDir");
config.module.rules.push(
{
test: /\.(.*)/,
resource: [
path.resolve(mokoResourcePath, "assets"),
path.resolve(mokoResourcePath, "files"),
path.resolve(mokoResourcePath, "images"),
path.resolve(mokoResourcePath, "localization"),
path.resolve(__dirname, "kotlin/assets"),
path.resolve(__dirname, "kotlin/files"),
path.resolve(__dirname, "kotlin/images"),
path.resolve(__dirname, "kotlin/localization"),
],
type: 'asset/resource'
}
Expand All @@ -68,7 +64,7 @@ internal class CopyResourcesToExecutableAction(
{
test: /\.css${'$'}/,
resource: [
path.resolve(mokoResourcePath, "fonts"),
path.resolve(__dirname, "kotlin/fonts"),
],
use: ['style-loader', 'css-loader']
}
Expand All @@ -78,13 +74,11 @@ internal class CopyResourcesToExecutableAction(
{
test: /\.(otf|ttf)?${'$'}/,
resource: [
path.resolve(mokoResourcePath, "fonts"),
path.resolve(__dirname, "kotlin/fonts"),
],
type: 'asset/resource',
}
)
config.resolve.modules.push(mokoResourcePath);
})(config);
""".trimIndent()
)
Expand Down Expand Up @@ -129,13 +123,16 @@ internal class CopyResourcesToExecutableAction(
outputDir: File,
logger: Logger
) {
if (inputFile.extension != "klib") return
if (inputFile.exists().not()) return

logger.info("copy resources from $inputFile into $outputDir")
val klibKonan = org.jetbrains.kotlin.konan.file.File(inputFile.path)
val klib = KotlinLibraryLayoutImpl(klib = klibKonan, component = "default")
val layout: KotlinLibraryLayout = klib.extractingToTemp
val layout: KotlinLibraryLayout = if (klib.isZipped) {
klib.extractingToTemp
} else {
klib
}

try {
File(layout.resourcesDir.path, "moko-resources-js").copyRecursively(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,51 @@ package dev.icerock.gradle.generator.platform.js
import dev.icerock.gradle.actions.js.CopyResourcesToExecutableAction
import dev.icerock.gradle.actions.js.CopyResourcesToKLibAction
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.targets.js.ir.JsIrBinary
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrCompilation
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import java.io.File

internal fun setupJsKLibResources(
compileTask: Kotlin2JsCompile,
resourcesGenerationDir: Provider<File>
resourcesGenerationDir: Provider<File>,
) {
val copyResourcesToKLibAction = CopyResourcesToKLibAction(resourcesGenerationDir)
@Suppress("UNCHECKED_CAST")
compileTask.doLast(copyResourcesToKLibAction as Action<in Task>)
}

internal fun setupJsResources(
compileTask: Kotlin2JsCompile,
resourcesGenerationDir: Provider<File>,
projectDir: Provider<File>
internal fun setupJsExecutableResources(
linkTask: KotlinJsIrLink,
projectDir: Provider<File>,
) {
val copyResourcesAction = CopyResourcesToExecutableAction(
resourcesGeneratedDir = resourcesGenerationDir,
outputDir = linkTask.destinationDirectory.asFile,
projectDir = projectDir
)

@Suppress("UNCHECKED_CAST")
compileTask.doLast(copyResourcesAction as Action<in Task>)
linkTask.doLast(copyResourcesAction as Action<in Task>)
}

internal fun setupJsResourcesWithLinkTask(
target: KotlinJsIrTarget,
project: Project,
) {
target.compilations.withType<KotlinJsIrCompilation>().configureEach { compilation ->
compilation.binaries.withType<JsIrBinary>().configureEach { binary: JsIrBinary ->
binary.linkTask.configure { linkTask ->
setupJsExecutableResources(
linkTask = linkTask,
projectDir = project.provider { project.projectDir }
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class JsAssetResourceGenerator(
val filePath: String = File(ASSETS_DIR, metadata.pathRelativeToBase.path).path
.replace("\\", "/")

val requireDeclaration = """require("$filePath")"""
val requireDeclaration = """require("./$filePath")"""
return CodeBlock.of(
"AssetResource(originalPath = js(%S) as String, rawPath = %S)",
requireDeclaration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal class JsFileResourceGenerator(
override fun imports(): List<ClassName> = emptyList()

override fun generateInitializer(metadata: FileMetadata): CodeBlock {
val requireDeclaration = """require("$FILES_DIR/${metadata.filePath.name}")"""
val requireDeclaration = """require("./$FILES_DIR/${metadata.filePath.name}")"""
return CodeBlock.of(
"FileResource(fileUrl = js(%S) as String)",
requireDeclaration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class JsFontResourceGenerator(
override fun imports(): List<ClassName> = emptyList()

override fun generateInitializer(metadata: FontMetadata): CodeBlock {
val requireDeclaration = """require("$FONTS_DIR/${metadata.filePath.name}")"""
val requireDeclaration = """require("./$FONTS_DIR/${metadata.filePath.name}")"""
return CodeBlock.of(
"FontResource(fileUrl = js(%S) as String, fontFamily = %S)",
requireDeclaration,
Expand All @@ -36,6 +36,8 @@ internal class JsFontResourceGenerator(
}

override fun generateResourceFiles(data: List<FontMetadata>) {
if (data.isEmpty()) return

val fontsDir = File(resourcesGenerationDir, FONTS_DIR)
fontsDir.mkdirs()

Expand Down Expand Up @@ -93,7 +95,7 @@ internal class JsFontResourceGenerator(
val addFontsFun: FunSpec = FunSpec.builder("addFontsToPage")
.addCode(
"js(%S)",
"""require("$FONTS_DIR/$cssDeclarationsFileName")"""
"""require("./$FONTS_DIR/$cssDeclarationsFileName")"""
).build()
builder.addFunction(addFontsFun)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ internal class JsImageResourceGenerator(
}
}

val requireDeclaration: String = """require("$IMAGES_DIR/$fileName")"""
val darkRequireDeclaration: String = """require("$IMAGES_DIR/$darkFileName")"""
val requireDeclaration: String = """require("./$IMAGES_DIR/$fileName")"""
val darkRequireDeclaration: String = """require("./$IMAGES_DIR/$darkFileName")"""

return if (darkFileName != null) {
CodeBlock.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal class JsPluralResourceGenerator(
builder.addSuperinterface(Constants.Js.loaderHolderName)

builder.addJsFallbackProperty(
fallbackFilePath = LOCALIZATION_DIR + "/" + getFileNameForLanguage(LanguageType.Base)
fallbackFilePath = "./" + LOCALIZATION_DIR + "/" + getFileNameForLanguage(LanguageType.Base)
)
builder.addJsSupportedLocalesProperty(
bcpLangToPath = metadata.asSequence()
Expand All @@ -69,7 +69,7 @@ internal class JsPluralResourceGenerator(
LanguageType.fromLanguage(locale)
}.filterIsInstance<LanguageType.Locale>().map { language ->
val fileName: String = getFileNameForLanguage(language)
language.toBcpString() to "$LOCALIZATION_DIR/$fileName"
language.toBcpString() to "./$LOCALIZATION_DIR/$fileName"
}.toList()
)
builder.addJsContainerStringsLoaderProperty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal class JsStringResourceGenerator(
builder.addSuperinterface(Constants.Js.loaderHolderName)

builder.addJsFallbackProperty(
fallbackFilePath = LOCALIZATION_DIR + "/" + getFileNameForLanguage(LanguageType.Base)
fallbackFilePath = "./" + LOCALIZATION_DIR + "/" + getFileNameForLanguage(LanguageType.Base)
)
builder.addJsSupportedLocalesProperty(
bcpLangToPath = metadata.asSequence()
Expand All @@ -69,7 +69,7 @@ internal class JsStringResourceGenerator(
LanguageType.fromLanguage(locale)
}.filterIsInstance<LanguageType.Locale>().map { language ->
val fileName: String = getFileNameForLanguage(language)
language.toBcpString() to "$LOCALIZATION_DIR/$fileName"
language.toBcpString() to "./$LOCALIZATION_DIR/$fileName"
}.toList()
)
builder.addJsContainerStringsLoaderProperty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package dev.icerock.gradle.utils

import dev.icerock.gradle.metadata.resource.ImageMetadata.Appearance
import kotlinx.serialization.json.JsonPrimitive
import org.apache.commons.text.StringEscapeUtils
import java.util.Locale

Expand Down Expand Up @@ -49,25 +50,24 @@ internal val String.flatName: String
get() = this.remove('.')

internal fun String.convertXmlStringToAndroidLocalization(): String {
return convertXmlStringToLocalization().let {
return StringEscapeUtils.unescapeXml(this).let {
// Add mirroring for newline, without this android does flat string line
it.replace("\n", "\\n")
}.let {
StringEscapeUtils.escapeXml11(it)
}
}

internal fun String.convertXmlStringToLocalization(): String {
return StringEscapeUtils.unescapeXml(this).let {
StringEscapeUtils.escapeEcmaScript(it)
}
}
val jsonPrimitive = JsonPrimitive(it)
// Usage of inner encode mechanism of Koltinx.Serialization
val stringValue: String = jsonPrimitive.toString()

internal val String.appearance: Appearance
get() {
return if (withoutScale.endsWith(suffix = Appearance.DARK.suffix, ignoreCase = true)) {
Appearance.DARK
} else {
Appearance.LIGHT
}
// Remove symbol ["] from start and end of string
stringValue.substring(1, stringValue.length - 1)
}
}

internal val String.withoutAppearance: String
get() {
Expand Down
Loading

0 comments on commit 4070eec

Please sign in to comment.