I'm currently trying to get a simple example running in which jQuery can be used by kotlin code compiled to JS with the help of gradle. So far I pieced together the following elements which should be enough according to older descriptions using the kotlin.js.externals:kotlin-js-jquery:3.2.0-0 package by adding it like this to the build.gradle.kts of the project:
repositories {
...
mavenCentral()
maven(url = uri("https://kotlin.bintray.com/js-externals"))
}
dependencies {
implementation("kotlin.js.externals:kotlin-js-jquery:3.2.0-0")
}
After adding the package I can sucessfully import the corresponding kotlin package in a simple Kotlin file like this:
import js.externals.jquery.jQuery
fun main() {
console.log(jQuery("div"))
}
But this sadly fails if executed with the following stack trace in the JS console:
Uncaught TypeError: $module$jquery is not a function
at main (simple.kt?9d2a:4)
at Object.eval (yet_another_one.js:14)
at eval (yet_another_one.js:5)
at eval (yet_another_one.js:8)
at Object../kotlin-dce-dev/yet_another_one.js (yet_another_one.js:515)
at __webpack_require__ (yet_another_one.js:30)
at Object.0 (yet_another_one.js:527)
at __webpack_require__ (yet_another_one.js:30)
at yet_another_one.js:94
at yet_another_one.js:97
After this I checked the packages webpack used for the bundling and I found out that as soon as the kotlin.js.externals:kotlin-js-jquery:3.2.0-0 package is used the following jquery.js file gets used instead of the one with the actual code:
(function (root, factory) {
if (typeof define === 'function' && define.amd)
define(['exports', 'kotlin'], factory);
else if (typeof exports === 'object')
factory(module.exports, require('kotlin'));
else {
if (typeof kotlin === 'undefined') {
throw new Error("Error loading module 'jquery'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'jquery'.");
}
root.jquery = factory(typeof jquery === 'undefined' ? {} : jquery, kotlin);
}
}(this, function (_, Kotlin) {
'use strict';
Kotlin.defineModule('jquery', _);
return _;
}));
//# sourceMappingURL=jquery.js.map
As this looks a bit strange to me as the jquery code seems not to be found anywhere I also tried adding it as a NPM dependency to the build.gradle.kts like this:
dependencies {
...
implementation(npm("jquery", "3.6.0"))
}
But doing so didn't change a thing. Webpack still uses the jquery.js file shown above (and maybe rightfully so), but it also still doesn't work.
I would appreciate any help to get this working as I already invested a lot of time in it and I'm currently kinda hopeless :(.
Thanks a lot!
To create a simple example, start a multiplatform project from the IntelliJ IDEA:
Select: File -> New Project
In the new window on the left hand select: Kotlin
In the main pane select the project template "Full-stack Web Application"
Use Build-System "Gradle Kotlin" to have best compatibility.
After this, Dukat is installed to generate your externals for jQuery:
Simply go to build.kts (your gradle build file) and under dependencies of jsMain add:
val jsMain by getting {
dependencies {
/// ... ommitted pre-installed directives
implementation(npm("#types/jquery","3.5.1", generateExternals = true))
implementation(npm("jquery","3.5.1"))
}
}
After this you can simply access jquery using jQuery as a static import:
import jQuery
And use it like a kotlin library: val xhr = jQuery.get("http://example.com")
Related
Modules generally start something like this
(function(root, factory)
{
/* globals define */
if (typeof define === 'function' && define.amd)
{
// AMD. Register as an anonymous module.
define([], factory);
}
else if (typeof module === 'object' && typeof exports !== 'undefined')
{
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
}
else
{
// Browser globals (root is window)
root.Papa = factory();
}
I'm trying to implement require to handle node style CommonJS-like modules.
Find the package folder, parse package.json to learn the entrypoint and dependencies, recursively descent with a shared cache to load dependencies... that stuff works.
But having loaded the script for a module, I'm having trouble executing it in such a way as to have it populate module.exports
This will all end up running on Jint (a JS interpreter) so node isn't supplying the usual furniture, I have to build it all myself. There's no step-debug with Jint so I'm using node from VS Code and faking Jint like this.
import * as fs from "fs";
var code = fs.readFileSync("node_modules/papaparse/papaparse.js").toString();
let x = 3;
console.log(eval("x*x"))
let result = eval(`
let module = { exports: "dunno" };
${code}
console.log(module.exports);
`);
This is in a file test.js and package.json nominates this file as main and specifies a type of module. It launches, reads the module code, creates module and runs the code, which promptly complains that it Cannot set properties of undefined (setting 'Papa').
Looking at the snippet above, that tells us it's executing the last else clause, which means it's not seeing module. I thought it might be some sort of scope thing for eval which is where this came from
let x = 3;
console.log(eval("x*x"))
but that duly writes 9 to the console so the question is why module isn't in scope.
This is one of those JavaScript closure weirdnesses; can anyone guide me on how to provide the module object so that the second clause will take effect populating module.exports with the result of factory()?
I know I said it's running in the absence of Node, but I'm debugging it using Node mostly because that's what you get when you launch a js file in VS Code. As already mentioned the production target is Jint. The debug environment is as close an approximation as I can manage, and I'm refraining from using facilities that won't be available in production.
In the debug environment I've created a package.json file that governs whether it's treated as CommonJS or ES6. Experiments show that the production environment behaves like more ES6 than commonjs. The trouble is that most packages expect that they will run either in a browser or in node. They don't consider the possibility of another headless environment. The code above decides it's browser hosted and tries to to install Papa in a DOM that isn't there.
The solution is to wrap the module like so (I'm constructing a string in C#).
string wrapped =
#"(function () {
var module = { exports: { } }; " +
jsSource + #"
var strays = Object.keys(this).filter(itemName => itemName !== 'module');
if (strays.length === 1)
module.exports = this[strays[0]]
else
strays.forEach(itemName => module.exports[itemName] = this[itemName]);
return module.exports;
}).apply({});";
Wrapping it in an anonymous function and using apply to set this for the call allows the "DOM write" to succeed, and a little follow-up code looks for these strays, gathering them up into module exports before returning to normal operation.
I followed this tutorial to create a kotlin->js project: https://kotlinlang.org/docs/tutorials/javascript/getting-started-gradle/getting-started-with-gradle.html
Next, I followed these instructions to use coroutines in my code: https://github.com/kotlin/kotlinx.coroutines/blob/master/README.md#using-in-your-projects
Everything was fine to far, no errors marked in the code and I could build my js application without any error messages. However, my js scripts are not running in the browser and I get the above mentioned error message in the browser console.
Does any of you here know what I missed or might have configured wrong?
Here's my build.gradle
group 'de.berlin'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.3.31'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin2js'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version"
compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M1"
compile("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.0-M1")
compile "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.0-M1"
}
compileKotlin2Js.kotlinOptions.sourceMap = true
compileKotlin2Js.kotlinOptions.outputFile = "${projectDir}/web/js/myApp.js"
compileKotlin2Js.kotlinOptions.suppressWarnings = false
compileKotlin2Js.kotlinOptions.verbose = true
build.doLast {
configurations.compile.each { File file ->
copy {
includeEmptyDirs = false
from zipTree(file.absolutePath)
into "${projectDir}/web/js/lib"
include { fileTreeElement ->
def path = fileTreeElement.path
path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
}
}
}
copy {
includeEmptyDirs = false
from "${buildDir}/resources/main"
into "${projectDir}/web"
}
}
clean.doFirst {
delete "${projectDir}/web"
}
Everything compiles without any error message but I get the following error message in the browser console:
""Its dependency 'kotlinx-coroutines-core' was not found. Please, check whether 'kotlinx-coroutines-core' is loaded prior to '(projectname)'."
A more detailled inspection shows that /web/js/lib only contains kotlin.js, shouldn't kotlinx-coroutines-core be also there because it's part of the depencency block and schould be copied in the build.doLast-step?
I also noticed that the comiled js file contains the following:
if (typeof kotlin === 'undefined') {
throw new Error("Error loading module 'myApp'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'myApp'.");
}
if (typeof this['kotlinx-coroutines-core'] === 'undefined') {
throw new Error("Error loading module 'myApp'. Its dependency 'kotlinx-coroutines-core' was not found. Please, check whether 'kotlinx-coroutines-core' is loaded prior to 'myApp'.");
}
Why is it this['kotlinx-coroutines-core'] and not kotlinx-coroutines-core (like in the line above)?
Why is it this['kotlinx-coroutines-core'] and not kotlinx-coroutines-core (like in the line above)?
Because "kotlinx-coroutines-core" is not a valid JS identifier.
"this['kotlinx-coroutines-core']" is testing whether the module "kotlinx-coroutines-core" has been loaded or not.
if it is undefined, then it means you have not 'loaded' the module prior to the code being executed.
depending how you load JS modules, you will need to "require(....)" or have an html script reference to the "kotlinx-coroutines-core" module
It has been more than a year and a half since the thread was opened but I answer in case someone has the same problem in the future.
You have to download "kotlinx-coroutines-core" from npm. Coroutines JS - install
So I have been searching all over the internet to try to find a solution to this problem but I cannot find a solution that works. I'm currently using the latest version of Gulp and Browserify to bundle up JS for the website I'm working on. We previously would concatenate all the JS files together, but I'm moving to a module setup now.
The problem I am running into is duplicating certain dependencies, in this example, I'll focus on jQuery (v2.1.4). Here is my setup:
main.js (Loaded on every page)
window.jQuery = window.$ = require('jquery');
window.Vue = require('vue');
require('jquery-validation');
// More JS that loads on all pages
page.js (Each page has it's own js file for scripts relating to that page)
require('remodal'); // This requires jQuery
// Rest of the JS for this page...
The problem I am running into is that now jQuery is in both javascript bundles. With Browserify, I marked jQuery as "external" for page-speicific.js which removed jQuery from the script, but I get an error Uncaught Error: Cannot find module 'jquery' and I cannot seem to find a solution to this.
If I "exclude" jQuery with Browserify, or if I put a try block around the require('remodal'), I end up with Uncaught TypeError: $(...).remodal is not a function instead. I'm guessing since the module remodal requires jQuery and it's not loaded there, it's not seeing it's set to the window and that's why execution fails?
Well, found the answer to my question. Guess a night of rest was all I needed to be able to think clearer to search for an answer.
I checked out browserify-shim (and browserify-global-shim) at some point, but found that it would only shim top-level dependencies. If jQuery was a dependency of a dependency, this would not work. Well, once I found the answer linked below, I discovered that theres an undocumented (at least, I never found it) { global: true } you can set to have the shim propagate to all dependencies.
var b = browserify();
var globalShim = require('browserify-global-shim').configure({
'jquery': '$'
});
b.transform({ global: true }, globalShim);
After running gulp, all of my page-specific scripts now referenced jQuery as a window variable.
!(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], function($) {
return factory(root, $);
});
} else if (typeof exports === 'object') {
factory(root, (window.$)); // <----------------- :D
} else {
factory(root, root.jQuery || root.Zepto);
}
})(this, function(global, $) {
Source: Shimming dependencies of dependencies with browserify-shim
I'm using the expect.js library with my mocha unit tests. Currently, I'm requiring the library on the first line of each file, like this:
var expect = require('expect.js');
describe('something', function () {
it('should pass', function () {
expect(true).to.be(true); // works
});
});
If possible, I'd like to remove the boilerplate require code from the first line of each file, and have my unit tests magically know about expect. I thought I might be able to do this using the mocha.opts file:
--require ./node_modules/expect.js/index.js
But now I get the following error when running my test:
ReferenceError: expect is not defined
This seems to make sense - how can it know that the reference to expect in my tests refers to what is exported by the expect.js library?
The expect library is definitely getting loaded, as if I change the path to something non-existent then mocha says:
"Error: Cannot find module './does-not-exist.js'"
Is there any way to accomplish what I want? I'm running my tests from a gulp task if perhaps that could help.
You are requiring the module properly but as you figured out, the symbols that the module export won't automatically find themselves into the global space. You can remedy this with your own helper module.
Create test/helper.js:
var expect = require("expect.js")
global.expect = expect;
and set your test/mocha.opts to:
--require test/helper
While Louis's answer is spot on, in the end I solved this with a different approach by using karma and the karma-chai plugin:
Install:
npm install karma-chai --save-dev
Configure:
karma.set({
frameworks: ['mocha', 'chai']
// ...
});
Use:
describe('something', function () {
it('should pass', function () {
expect(true).to.be(true); // works
});
});
Thanks to Louis answer and a bit of fiddling around I sorted out my test environment references using mocha.opts. Here is the complete setup.
My project is a legacy JavaScript application with a lot of "plain" js files which I wish to reference both in an html file using script tags and using require for unit testing with mocha.
I am not certain that this is good practice but I am used to Mocha for unit testing in node project and was eager to use the same tool with minimal adaptation.
I found that exporting is easy:
class Foo{...}
class Bar{...}
if (typeof module !== 'undefined') module.exports = { Foo, Bar };
or
class Buzz{...}
if (typeof module !== 'undefined') module.exports = Buzz;
However, trying to use require in all the files was an issue as the browser would complain about variables being already declared even when enclosed in an if block such as:
if (typeof require !== 'undefined') {
var {Foo,Bar} = require('./foobar.js');
}
So I got rid of the require part in the files and set up a mocha.opts file in my test folder with this content. The paths are relative to the root folder:
--require test/mocha.opts.js
mocha.opts.js content. The paths are relative to the location of the file:
global.assert = require('assert');
global.Foo = require("../foobar.js").Foo;
global.Bar = require("../foobar.js").Bar;
global.Buzz = require("../buzz.js");
According to apple's documentation I can import one JS file into another with an import statement. And yes I am able to use JS functions and recursively call other JS functions.
But can I include node modules into my automation. Node/npm module seems to have a lot of tools that makes life easier and avoid code duplication.
And actually I was able to use one node module called moment.js through the following call in my code
#import "../node_modules/moment/moment.js"
But I am not have the same luck with other npm modules. I tried couple Faker.js, Charlatan.js and I getting the following error in Faker.js
Script threw an uncaught JavaScript error: Can't find variable: window
on line 618 of Faker.js
Looking at *.js files it looks like it has something to do with the way these modules are packaged. My JS knowledge isn't getting me anywhere.
The last few lines of moment js file
// CommonJS module is defined
if (hasModule) {
module.exports = moment;
}
/*global ender:false */
if (typeof ender === 'undefined') {
// here, `this` means `window` in the browser, or `global` on the server
// add `moment` as a global object via a string identifier,
// for Closure Compiler "advanced" mode
this['moment'] = moment;
}
/*global define:false */
if (typeof define === "function" && define.amd) {
define("moment", [], function () {
return moment;
});
}
Last few lines of Faker js file
if (typeof define == 'function'){
define(function(){
return Faker;
});
}
else if(typeof module !== 'undefined' && module.exports) {
module.exports = Faker;
}
else {
window.Faker = Faker;
}
I am perfectly able to play with these modules in node console, so nothing wrong with the modules, it just how to include/require them in my JS files.
Had to do two things for Faker to work for me
remove 'use strict'
Check if window is undefined
Add this statement
this['Faker'] = Faker;