Closed minaEweida closed 3 years ago
Thanks @minaEweida we'll check.
Hi @arkivanov, if you are planning to do this, please make sure to mention that supported node.js version should be 12+ since MainScheduler
is using globalThis
which is supported for Node.js version 12.
NPM publishing plugin is released: https://gitlab.com/lt.petuska/npm-publish/-/releases/v0.1.1
Looks like a good article about publishing to npm: https://zellwk.com/blog/publish-to-npm/
Maybe anybody want to contribute? We could not find anyone with experience in both Kotlin Multiplatform and NPM at the same time. From our side we could setup everything needed (like registering with the NPM repository, etc.)
We managed to publish our lib on our internal NPM and as a workaround we published reaktive artifacts as well. I will try to do the same here.
It felt like a work around but I will contribute what I did and let's see if we can make it better.
NPM publishing would be very nice for my as it would enable its exploitation in my Kotlin MP project.
BTW I'm well aware of the issues related to publishing Kotlin MPP to NPM. I've implemented a simple plugin to simplify the process: https://github.com/gciatto/kt-npm-publish
It is not clear to me what's the limiting factor in this case, but I hope my plugin may help.
@gciatto-unibo The limiting factor here is the lack of expertise in this area. We will need to learn all the things first, hence the delay. As mentioned above we are happy to accept contributions for this task. And we will assist as much as we can.
I have some degree of expertise concerning how to publish Kotlin-JS stuff on NPM. Let me know if and how I can help.
@gciatto-unibo Perhaps we should first define all the required steps. Some steps my require infrastructure changes, code changes, repository settings changes (secrets), registering with third-party services, etc. Would you be able to list the steps? Then we could delegate the tasks. Or if you have another vision or questions, please let me know.
@gciatto-unibo (and other interested people) should we publish only the IR variant or both?
Also it seems that for the IR mode we need to specify js { binaries.library() }
, but it fails when BOTH
mode is used. We can use IR-only mode and enable binaries.library()
conditionally based on command line arguments. But may be there is a better way? Google and Slack are empty.
@arkivanov the JS API should be equal, so any of them is fine. I currently rely on the legacy backend, but it's because I'm lazy. If in IR mode all tests succeed, I'd suggest sticking to that.
@arkivanov Sorry for my late reply, I'll try to provide an overview of the steps below based on my experience. Everybody should feel free to correct me in case I'm wrong or imprecise---which is likely. Also, I'll assume knowledge about the JS and Node world, but please feel free to ask for details.
Essentially, assuming that you have a Kotlin MP project or a Kotlin JS project, there exist two Kotlin-to-JS workflows: one producing a single .js file for client-side usage, and the other producing Node projects, for server-side usage.
The former one can be summarised as follows:
Kotlin MPP/JS -[Kotlin compiler]-> JS sources -[Webpack]> Single .js file to be used as a static resource in web pages
The latter one can be summarised as follows:
Kotlin MPP/JS -[Kotlin compiler]-> Node Project containing JS sources and the `package.json` file
The two workflows are not mutually exclusive, and which one you'll use depend on how your Gradle build is configured.
My suggestion is to always use at least the Node option, as Webpack can be naturally applied to Node projects using the npm
command-line tool.
So, while Kotlin code transpiled into Node project for server-side usage can always be converted into a client-side JS script, Kotlin code transpiled into JS client-side script cannot be converted back into a Node project.
That said, I'll assume from now on that we can convert Kotlin MP/JS projects into Node projects.
Node projects can be naturally deployed on the NPM repository using the npm
command-line tool which should be included in any correctly configured Node environment.
This is as simple as running the npm publish
command into the root directory of the generated Node project.
This can either be performed by directly using npm
or via my aforementioned kt-npm-publish
plugin, in case you want to automate that via Gradle.
In both cases, a user must be registered on the NPM repository and they must have created a deploy token.
There are now 2 potential sources of troubles when deploying your JS-transpiled code on NPM:
Problem 1 arises because, when producing the Node project (and in particular the package.json
file), the Kotlin compiler does not references JS dependencies from NPM, but rather it leverages on a particular notation (which I do not fully understand) that only works locally (cf. https://discuss.kotlinlang.org/t/local-dependencies-in-package-json-of-js-mpp-projects/19260).
So, unless your project has no dependency (which is unlikely, as it shall at least depend on Kotlin's Standard Library for JS), the package.json
should be lifted (this is my own jargon) in such a way that dependencies only reference JS libraries that are or will be published on NPM.
Long story short, there is no automated way to do so (to the best of my knowledge) except for my kt-npm-publish
plugin.
Problem 2 only arises if you need/want to change the default project name generated by the Kotlin compiler.
This is far from trivial, especially in multi-projects builds, because it is NOT sufficient to edit the generated package.json
files to change the project names.
So for instance: if your root project is named foo
and it has two Kotlin MP/JS sub-projects named bar
and baz
respectively, then the Kotlin compiler will generate the foo-bar
and foo-baz
projects. Let's assume this is not fine for your, as, for instance, you may be willing to publish both projects under a common organization, say @acme
. This implies they should be renamed to @acme/foo-bar
and @acme/foo-baz
respectively. This implies their all occurrences of foo-*
in all package.json
files and in all .js
files should be updated accordingly...
The matter gets complicated if the two projects are inter-dependent.
So, again, long story short: no automate way to do so.
The only semi-automated way to do so I'm aware of is my kt-npm-publish
plugin.
@gciatto-unibo Thanks for the input!
Problem 1 Here are all Reaktive's JS target dependencies:
reaktive
, reaktive-annotations
and utils
modules depend only on stdlib
reaktive-testing
module depends on org.jetbrains.kotlin:kotlin-test-js
, but I'm not sure if this module has to be published at allreaktive-coroutines
module depends on stdlib
and org.jetbrains.kotlinx:kotlinx-coroutines-core-js
All other are either Java-specific or samples, so no need to publish them I guess.
Problem 2
I read that the name prefix can be changed by using the rootProject.name
setting in the settings.gradle
file. We don't have this setting set yet, but if it really works and will not affect anything else, then we can set it to something like badoo-reaktive
. In this case the name in the package.json
file should be like badoo-reaktive-reaktive
, badoo-reaktive-utils
, etc. May be we could use this as a workaround?
The issue with both IR and Legacy modes enabled
IR mode compiles just fine currently and all tests are green, and I also have feeling that it should be preferred. But as I mentioned in my previous message, I could not specify js { binaries.library() }
, as Gradle sync fails saying it is not allowed for BOTH
mode. Looks like we need a workaround here as well.
Registration with the NPM repository We will take of this step and provide updates in this issue.
PS
I managed to roughly publish Reaktive to local NPM repository by enabling only IR mode, specifying js { binaries.library() }
and running ./gradlew npmPublish
. Seems like it was successful, but maybe I missed something like Problem 1 or anything else.
Concerning reaktive
, reactive-annotation
, and utils
, I agree that they should be publishable with no major issues.
Concerning reactive-testing
... well it depends. Is that a library you use for testing in some Reaktive module or is it intended for letting people test their code when they use Reaktive? In the latter case, I believe reactive-testing
should be published on NPM as well.
Concerning reaktive-coroutines
, I bet it will be troublesome as kotlinx-coroutines-core-js
is not yet on NPM, to the best of my knowledge.
However, we may consider opening a ticket for that, and you may consider not publishing it for the moment.
That would imply discouraging the usage reaktive-coroutines
for Kotlin users willing to produce NodeJS projects.
(E.g. I could not be using it in my project until reaktive-coroutines
is on NPM)
In my experience, changing the rootProject
may affect the naming of some Maven artifacts.
So this may not be a trivial issue.
However, the real problem is different.
Your proposal is essentially "let's put a badoo-
prefix on all Reaktive-related packages published on NPM, to mimic Maven's groupId
s", isn't it?
If I correctly understand your proposal, my suggestion is to consider other options.
In that way, NPM packages will be just a bunch of unrelated JS packages which happen to have the same prefix in their name.
I believe that NPM's organization are the better option to mimic groupId
s.
But
rootProject.name
, andSo my suggestion is to reason about "how you want your JS-compiled libraries will be perceived by JS developers in the long term", and then stick to a decision.
Why can't you just use IR
instead of BOTH
?
Nice job! Now let's ensure the published library can work.
There are two tests you can manually perform:
could you please report the output of the following command
npm info <name of your Reaktive JS Package>
This should let us inspect the dependencies of your package, to ensure they reference NPM packages.
could you create a dummy Node project and use <name of your Reaktive JS Package>
as a dependency, then run npm install
and see if dependency resolution works?
Turned out we already have an account: https://www.npmjs.com/~bumble-core-services
@gciatto-unibo thanks again for your input.
Problem 1
reaktive-testing
is supposed to be used in Kotlin projects for testing purposes. So perhaps we can skip its publication for now.
reaktive-coroutines
is widely used as far as I know, e.g. for interop with Ktor. There is also the -nmtc
version of this module published from a separate branch (for Kotlin/Native multi-threaded coroutines). So it seems, even if we publish all modules without reaktive-coroutines
, it is still will be unusable for most of the devs, right? Or maybe devs could publish coroutines manually to their local npm repos, as they probably doing it with Reaktive currently?
Problem 2
Agree. I checked the account link above, we have company prefixes for all existing packages. So perhaps for Reaktive we should prepend badoo@/
, for consistency with the com.badoo
package name in this repository. Are there any pitfalls with this approach? How complex would be the effort?
IR/BOTH modes
We have BOTH
modes enabled because we should still have developers who can't migrate to IR mode. One of the reasons is that all dependencies should support IR mode (or BOTH
). Another reason could by possible compiler crashes (we had at least two blocking compiler bugs recently). So we should first find a way to specify js { binaries.library() }
, or maybe add workarounds to the publication tasks (e.g. enabled only IR mode when publishing to NPM).
PS concerns I will try local publications again this Friday (28/05). Thanks for providing the steps!
@gciatto-unibo I installed the utils
module locally.
I also created and installed a dummy Node project with Reaktive-utils
dependency.
Could you please try the same process with a module which depends on some other module? Say reaktive
instead of utils
@gciatto-unibo I have locally installed utils
, reaktive-annotations
and reaktive
modules. Here are outputs:
For some reason dependencies are not listed. Also I checked the content of the generated Reaktive-reaktive.js
file, looks like it has the utils
module bundled.
Ok these won't work, I guess: they lack a dependency from the kotlin
package on NPM.
I forgot to mention that, after compilation and before publishing, it is important to call the jsPublicPackageJson
task
Alright, I was looking in the ./<module>/build/productionLibrary/
folder. But looks like the correct one is ./build/js/packages/<module>/
. This is very confusing, since I was running a module's task and expected the output in the module's build
folder. So now after running :reaktive:jsPublicPackageJson
task, package.json
of the reaktive
module contains the following:
{
"name": "Reaktive-reaktive",
"version": "1.1.22",
"main": "kotlin/Reaktive-reaktive.js",
"devDependencies": {},
"dependencies": {
"Reaktive-utils": "1.1.22",
"Reaktive-reaktive-annotations": "1.1.22",
"kotlin": "file:/home/aivanov/dev/workspace/Reaktive/build/js/packages_imported/kotlin/1.5.0",
"kotlin-test-js-runner": "file:/home/aivanov/dev/workspace/Reaktive/build/js/packages_imported/kotlin-test-js-runner/1.5.0",
"kotlin-test": "file:/home/aivanov/dev/workspace/Reaktive/build/js/packages_imported/kotlin-test/1.5.0"
},
"peerDependencies": {},
"optionalDependencies": {},
"bundledDependencies": []
}
Looks like there is an issue with kotlin
dependency. Also not sure why there are test dependencies.
Test dependencies are for running tests in IntelliJ during development, I believe.
BTW I'm pretty sure they must be removed.
Conversely, the kotlin
dependency should be retained, and modified to refer to the official kotlin
package on NPM, i.e. https://www.npmjs.com/package/kotlin.
So your package.json
should only contain the external dependency:
"kotlin": "^KT_VERSION"
So is there any way how we could solve the issue?
Perhaps better to wait for an official solution.
I talked to @SebastianAigner and they suggested to also check the npm-publish plugin. Will check it in a few days.
Sorry for my late reply @arkivanov.
So is there any way how we could solve the issue?
Technically, the solution is to edit the package json manually. But one can always use Gradle to automate that.
Both my plugin and the npm-publish
support doing so programmatically, via a Gradle task.
I'd suggest to consider npm-publish
too: indeed we are considering to merge our efforts.
Ehy @arkivanov, any news?
@gciatto-unibo we are all on vacation this week, so I will check this next week. 😀
@gciatto-unibo So I tried the npm-publish
plugin and the results are kinda the same. The kotlin deps are bundled, but it is mentioned in the plugin's readme: ... as they already come with all kotlin dependencies bundled into js output file
.
What is more important, the Reaktive's own deps are also bundled, same as before without the npm-publish
plugin.
I checked the build/js/packages/Reaktive-reaktive/kotlin/Reaktive-reaktive.js
file - it contains the code from the utils
module
After publications, the output of npm info @com.badoo/reaktive
is:
aivanov@aivanov-XPS-17-9700:~/dev/workspace/nodehw$ npm info @com.badoo/reaktive
@com.badoo/reaktive@1.1.22 | Proprietary | deps: none | versions: 1
dist .tarball: http://127.0.0.1:4873/@com.badoo%2freaktive/-/reaktive-1.1.22.tgz .shasum: eaf0c4a5e8c759dfeda8b9fb40c39ee9733b5c85 .integrity: sha512-59DZWb2CVBLCbBi1HbrhKnsm2XXujwrw9y6m2IHgw+G9F2RrbrR6ft8T29urUBhOD5FVgOJMgcG1DFv590Iarw==
dist-tags: latest: 1.1.22
published 12 minutes ago
So the dependencies are not listed, which is weird.
3. Here is the content of the `build/js/packages/Reaktive-reaktive/package.json` file:
{ "name": "Reaktive-reaktive", "version": "1.1.22", "main": "kotlin/Reaktive-reaktive.js", "devDependencies": { "dukat": "0.5.8-rc.4", "source-map-support": "0.5.19" }, "dependencies": { "Reaktive-utils": "1.1.22", "Reaktive-reaktive-annotations": "1.1.22", "kotlin": "file:/home/aivanov/dev/workspace/Reaktive/build/js/packages_imported/kotlin/1.5.10", "kotlin-test-js-runner": "file:/home/aivanov/dev/workspace/Reaktive/build/js/packages_imported/kotlin-test-js-runner/1.5.10", "kotlin-test": "file:/home/aivanov/dev/workspace/Reaktive/build/js/packages_imported/kotlin-test/1.5.10" }, "peerDependencies": {}, "optionalDependencies": {}, "bundledDependencies": [] }
I think we will need a hand from someone experienced in Kotlin/JS/Node/NPM/etc.
The build/js/packages/Reaktive-reaktive/package.json
you showed in your last message is not adequate for publishing:
kotlin-test-js-runner
package, which is not even deployed on NPM.Try to manually edit the build/js/packages/Reaktive-reaktive/package.json
as follows, before publishing:
{
"name": "Reaktive-reaktive",
"version": "1.1.22",
"main": "kotlin/Reaktive-reaktive.js",
"devDependencies": {
"dukat": "0.5.8-rc.4",
"source-map-support": "0.5.19",
"kotlin-test": "1.5.10"
},
"dependencies": {
"Reaktive-utils": "1.1.22",
"Reaktive-reaktive-annotations": "1.1.22",
"kotlin": "1.5.10",
},
"peerDependencies": {},
"optionalDependencies": {},
"bundledDependencies": []
}
and the republish.
If this works, you must find a way to automatically generate a proper package.json
before publishing.
This can be achieved via gradle, using any plugin among mine and npm-publish
... but they must be configured accordingly.
@gciatto-unibo Thanks, I will try. What about the statement that Kotlin dependencies are bundled in IR mode?
@arkivanov, I sincerely do not recall where that statement is from.
What do you exactly mean by "bundled", in the first place?
In the following, I'll assume that a "bundled Kotlin dependency" is something related to the Kt Stdlib for JS that may or may not be included in the Reaktive-reaktive
JS package.
I find it very unlikely that the Reaktive-reaktive
JS package includes a bundled Kotlin dependency.
Think about it: it would be a waste of space for NPM packages to include bundled dependencies from other packages which are individually available on NPM.
Luckily for us, I argue there is a simple way to figure this out. There are 2 cases IMHO:
kotlin-XXX.js
file is included among the JS sources of the Reaktive-reaktive
package and referenced somewhere in the kotlin/Reaktive-reaktive.js
file (i.e. the entry point of your package)kotlin/Reaktive-reaktive.js
file itself.
A third option (which I believe is the only possible one) is that Kt stdlib is not bundled.Just to verify:
.js
files in the Reaktive-reaktive
root dirkotlin/Reaktive-reaktive.js
: if you find a require("kotlin")
statement somewhere, then the program is likely attempting to load a dependency which is expected to be declared in a package.json file and/or installed via NPM@gciatto-unibo The "bundled" statement comes from the npm-publish
plugin's readme. There is a comment for the bundleKotlinDependencies
configuration flag.
I checked the locally published reaktive
package on disk, it seems that it contains some code from the utils
module, and also from the stdlib. Moreover, it does not contain most of the reaktive
module's code, like map
, flatMap
are missing, etc. Perhaps they should be exported somehow? I have attached the package archive: reaktive.zip.
Also I found the following discussions which actually describe the issues we are having:
So overall there are at least two issues currently:
binaries.library()
if BOTH
mode is enabled - the Gradle configuration phase fails. It only works if the IR
mode is used, but I think we need to keep BOTH
for now.stdlib
, modules like utils
, etc@arkivanov can you put your experiments on a branch, so that I can reproduce the publishing process on my machine? Otherwise it is pretty has for me to provide informed suggestions
@gciatto-unibo I will push my changes to a branch this Friday (Jul 02). Thanks again!
@gciatto-unibo I have pushed the code to the npm-publish
branch:
npm-publish
plugin: dev.petuska:npm-publish:2.0.3
npm-publish
plugin for the following modules: utils
, reaktive-annotations
and reaktive
both
to ir
binaries.library()
for the js
targetJust in case, I used Verdaccio as local NPM registry and the following Gradle task: publishJsNpmPublicationToRepository
.
Thanks again!
OK try with the following build.gradle.kts
file for sub-project reaktive
:
(I had to switch to .kts
as I'm not comfortable with Groovy ^^")
plugins {
id("mpp-configuration")
id("publish-configuration")
id("detekt-configuration")
id("dev.petuska.npm.publish")
}
configuration {
enableLinuxArm32Hfp()
}
kotlin {
targets {
// fromPreset(presets.jvm, 'jvmCommon')
// fromPreset(presets.jvm, 'jvmJsCommon')
// fromPreset(presets.linuxX64, 'jvmNativeCommon')
// fromPreset(presets.linuxX64, 'nativeCommon')
// fromPreset(presets.linuxArm32Hfp, 'nativeCommon')
// fromPreset(presets.linuxX64, 'linuxCommon')
// fromPreset(presets.iosX64, 'darwinCommon')
}
sourceSets {
commonMain {
dependencies {
implementation(project(":utils"))
implementation(project(":reaktive-annotations"))
}
}
commonTest {
dependencies {
implementation(project(":reaktive-testing"))
}
}
}
}
npmPublishing {
repositories {
repository("verdaccio") {
registry = uri("http://localhost:4873")
authToken = "ayC+KcY0bEgHF/s8YMtlF76FbGgPmKR8AMauiMRoI/M="
}
}
organization = "com.badoo"
access = PUBLIC
publications {
val js by getting {
packageJson {
dependencies {
"@com.badoo/utils" to version // refers to the current project's version
"@com.badoo/reaktive-annotations" to version // refers to the current project's version
"kotlin" to "1.5.10" // idk how to refer to the currently used version of kotlin... yet literals should be avoided here
}
devDependencies {
"dukat" to "0.5.8-rc.4"
}
}
}
}
}
essentially what I do here is to configure npm-publish
to overwrite the dependencies of the generated package.json
.
I succeeded in publishing the correct package on Verdaccio in this way.
For a complete example to work, you should perform a similar configuration for sub-projects utils
and reaktive-annotations
.
After that, we must assess if import works... but I had not time to test that today
@gciatto-unibo Thanks for the results! I am wondering, what about the issue with the transitive dependencies being bundled into the reaktive
module? Should this approach help?
I checked the generated code. Dependencies are just declared in the package.json file, and they are dynamically loaded in by the Reaktive-reaktive.js script.
This is why I was suggesting to publish utils and reaktive-annotations on NPM as well
@gciatto-unibo I will double check with your approach. But when I was trying, I published both child modules first, and then published the reaktive
module. Then I checked the js file in the Verdaccio folder, it contained the code from utils. E.g. there were AtomicReference, Clock, etc. And at the same time it did not contain any code from the reaktive module itself. E.g. there were no flatMap, combineLatest operators, etc.
@gciatto-unibo I tried the approach suggested by you. The dependency declarations seems fine now. But I believe there is still the issue with the published js
files content. Please find the attached archive with the @com.badoo
folder from Verdaccio.
How I publish:
npm set registry http://127.0.0.1:4873
utils
and reaktive-annotations
modulesreaktive
moduleIssues:
Reaktive-utils.js
has just 72 lines of code, and does not contain anything useful. There is no AtomicReference
, Clock
, etc.Reaktive-reaktive.js
has just 1171 lines of code. It contains the code from the utils
module, e.g. there is AtomicReference
and Clock
. But it does not contain any operators, e.g. there is no flatMap
, combineLatest
, etc.Reaktive-
prefix? Or it does not matter?Thanks again for your help!
Hello @arkivanov, apologies for my disappearing: I'm working on my thesis and I'm really close to 0 free time.
So, after conducting some experiments to better understand the IR backend (cf. https://github.com/mpetuska/npm-publish/issues/21) I realised my previous suggestions was partially misleading. Essentially, I was basing my suggestions on the functioning of the legacy backend, while you're using the IR.
The legacy backend used to create an almost 1-to-1 correspondence among the Kt projects and the Node projects. The IR backend apparently performs some bundling by default. To the best of my understading, if you intend yo use the IR backend, you should:
binaries.executable()
instead of binaries.library()
@JsExport
annotation, to prevent those classes from being removed by the backend's dead code elimination featureThis should make the generated utils
project contain useful JS code, which reaktive
may reference and use.
Alternatively, you may switch to the legacy backend, where all symbols are exported by default and requires no experimental features.
You should use binaries.library()
still. Otherwise generated executable will also embed all your npm dependencies as well (as opposed to lifting them out via webpack's externals
)
@gciatto-unibo @mpetuska Big thanks for your inputs! I will try everything this Friday (30/07). The @JsExport
annotation should potentially fix the issue with missing public APIs in the published .js
files. However it's not clear how we can prevent things from dependent modules from being embedded into higher level modules? E.g. we have AtomicReference
defined in the utils
module, the reaktive
modules depends on utils
, the published .js
file for reaktive
module contains AtomicReference
. Should we prevent it at all?
Is there any plans to publish Reaktive to npm? Recently we used reaktive in our lib to support node.js apps so we are publishing Reaktive to our internal artifactory since Reaktive is not published