Since coming across RequireJS I have started to adopt it wholeheartedly as it seems a great way of organising dependencies etc.
For my current project I have created a 'package' of requireJS-organised modules, which will provide the needed database API, to many node.js applications.
But I have come across a stumbling block ... how can I allow third party applications to use my package, without needing to faff with requireJS?
My directory structure for my applications and API is currently as follows:
api_package/node_modules
api_package/controllers/*
api_package/views/*
api_package/helpers/*
api_package/models/*
api_package/main.js
application_1/node_modules
application_1/app.js
application_2/node_modules
application_2/app.js
I need my applications to be self-contained - so they can be easily deployed - so my current work around is to copy api_package/* into the 'node_modules' directory of application_1 & 2 and setting the their requirejs config to the following:
(function() {
var requirejs;
requirejs = require('requirejs').config({
baseUrl: __dirname,
nodeRequire: require,
packages: [
{
name: 'api_package',
location: './node_modules/api_package'
}
]
});
This feels a little dirty and wrong!
Is there a better way? Am I missing some packaging feature for requireJS?
Is it even possible to hide the implementation details of my api_package (the detail being that I am using requireJS) and allow applications to use it as they would any other module:
require('api_package')
You can use the amdefine package, which allows you to code to the AMD API and have the module work in node programs without requiring those other programs to use AMD.
In addition to the documentation on the amdefine github page, this is also documented on the RequireJS website.
Related
I have a library of Java code from Android development and I'd like to reuse that in a web-app version of the same. The library code is quite generic as it was always intended to be re-usable and has, in the past, been used to generate Android/Java, App-Engine/Java, iOS/ObjC, and GWT apps.
Looking around, I think the best framework for the web app would be Angular. Rewriting the library code to Kotlin should be a relatively minor task as there are tools to do most of the work. Then it can be compiled for the JVM (for native and backend apps) or JavaScript (for web apps).
While advice for/against this plan is welcome, my actual question is...
How do I set up an IntelliJ project to do this?
I thought the obvious answer would be two modules: one for the lib and one for the app but IntelliJ doesn't allow creating a Kotlin module, only a Kotlin project.
Instead, I made a Kotlin/JS project and used Angular/CLI to create the app module beneath it (with a backend app to sit beside it sometime in the future). The library builds and the sample app runs but I haven't been able to get the latter to include the generated JS (plus .d.ts) code of the former which sits in some deep directory under build/. So maybe I'm going about it all wrong...
Wow, that was rough! After many hours of Google searches and attempts that failed, here is something that works. Perhaps I'll find cleaner methods down the road but this is acceptable for now.
Note: I wasted too much time on gradle plugins for my liking. If they're not first-party, they tend to be unmaintained, poorly documented (largely assuming that the reader already knows what they're doing), and out of date. What follows is done only with first-party support programs.
Create a top-level project with Angular/CLI followed by sub-projects for the app and library:
ng new MyGeneralProject --no-create-application
ng generate library klib
ng generate application MyApp
Now set up Gradle in the top-level directory (or wait and do this via the IDE):
gradle init
gradle wrapper
Open the top-level in IntelliJ. Create/edit the top-level build.gradle.kts file:
buildscript {}
plugins {}
repositories {}
More important is the top-level settings.gradle.kts file:
rootProject.name = "MyProject"
include("projects:klib")
This will now access the projects/klib/gradle.build.kts file:
plugins {
kotlin("js") version "1.8.0"
}
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib-js"))
testImplementation(kotlin("test-js"))
}
kotlin {
sourceSets {
all {
// allow #JsExport without opt-in each time
languageSettings.optIn("kotlin.js.ExperimentalJsExport")
}
val main by getting {
kotlin.srcDir("src/main")
}
val test by getting {
kotlin.srcDir("src/test")
dependencies {
implementation(kotlin("test"))
}
}
}
js(IR) {
moduleName = "klib"
browser {
distribution {
directory = File("$projectDir/../../dist/$moduleName")
}
}
binaries.library()
}
}
The above uses the kotlin("js") plugin for Gradle but that's provided by JetBrains as part of IntelliJ who also maintain the Kotlin language libraries. Though the documentation for it also assumes you already know everything about Gradle, it nonetheless works just fine.
The built JS/TS library will be created under the top-level directory as "dist/klib". This naming could use some improvement but it's a reasonable starting point.
In the top-level tsconfig.json file, under "compilerOptions", look for "paths" and update as desired. I went with a leading "#" to indicate a top-level import location:
{
...
"compilerOptions": {
...
"paths": {
"#klib": [
"dist/klib"
]
}
...
},
...
}
In the projects/myapp/src/app/whatever.ts file, access the converted Kotlin library:
import * as klib from '#klib'
Then the entire library is available as klib.blah.blah.blah with IntelliJ providing full completion semantics.
2023-02-07: I've found that while this approach worked okay for creating the desired Angular project, it fell apart when trying to add a jvm-based backend with which it shared code. Now trying a different tactic...
I have project that use bare Javascript (+ Jquery + Backbone) with RequireJS. I include dependencies like:
require.config({
baseUrl: "./js/",
paths: {
jquery: 'lib/jquery-1.8.2',
underscore: 'lib/underscore-1.4.2',
backbone: 'lib/backbone-1.1.2',
'backbone.localStorage': 'lib/backbone.localStorage'
},
shim: {
underscore: {
exports: "_"
},
backbone: {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
},
'backbone.localStorage': {
deps: ['backbone'],
exports: 'Backbone'
}
}
});
I want: move project to maven. I don't want to include all that deps in such ugly way. Moreover, I want to use Hibenate, Spring and another deps that should be imported via maven.
Problem: I haven't deep understanding of how do maven plugins or RequireJs work. I looked at this, but understand nothing.
Question: Can you write a step-by-step action points how to move my app to maven?
Maven is not really an answer to the (alleged) "ugliness" of the configuration you showed because it works on a completely different level. To begin with, Maven is a server-side tool and RequireJS is client-side (apologies if you already knew it, but -> "I haven't deep understanding of how do maven plugins or RequireJs work"). This configuration is specific to RequireJS so there's no way to go around it.
Even with Maven you still need to configure your front-end application and tell it where to find the required dependencies and how to initialise them. Client application's paths are web resources which can be arbitrarily different from the file structure of your project so it's not easy to automate the configuration process.
I've been using the RequireJS maven plugin (found it in one of the answers in the same question you posted) for wrapping r.js but it was rather painful to set up, especially with resource timestamping. I just feel that this approach is flawed due to fundamental differences between Java and JavaScript tooling - you'll always end up with a less-than-ideal compromise and any non-standard steps will eat a lot of time and research.
Another approach that's gaining popularity is to completely split your client- and server-side applications into separate projects, allowing you to use language-specific tooling to make the process as smooth as possible. Server-side uses Maven, client-side uses Grunt+Bower+RequireJS+etc. Both sides talk through well-defined interfaces using JSON over HTTP.
It should be easy to find some introductory reading material, but since you're mentioning Spring it could be worth starting by having a look at the reference Spring application: Sagan. It demonstrates how to split front- and back-end into separate Maven submodules that share the same common "core".
A stop-gap solution that makes working with JS libraries with Maven a little bit more civilised is using webjars. You define your dependencies directly in pom.xml and they're easily exposed to the front-end like other static assets. However, this limits your options when it comes to bundling or customisation of the libraries.
There are many ways to format JavaScript modules: AMD, CommonJS, UMD, ES6, global script. I've seen projects that structure their source code in whatever way they want and run a build process to generate a dist directory containing code in all the above formats. This has the advantage that the user of the code can just pick whichever format is most applicable to his environment.
This method works fine as long as the module has no dependencies on other modules. In the case where the modules must import other modules, there are implied complications. For example RequireJS uses a config file that looks like:
requirejs.config({
paths: {
'jquery': 'js/lib/jquery',
'ember': 'js/lib/ember',
'handlebars': 'js/lib/handlebars',
'underscore': 'js/lib/underscore'
}
});
Other loaders have equivalent mechanisms for mapping import paths.
If jQuery is a dependency, should the module import it from the path 'jquery'? What if the system in which it is being incorporated stores jQuery at the path 'libs/jquery'? In this case, is it the responsibility of the author of the system incorporating jQuery to provide aliases in the configuration of the import path?
This questioning strongly suggests that a truly reusable module must provide code formatted in all module formats as well as document clearly upon what libraries (and versions thereof) it depends and document what import paths at which those libraries are assumed to exist.
For example I could author a fancy jQuery plugin that I distribute in AMD, CommonJS, ES6, and global variations. I would document that this plugin depends on jQuery version 2.0 imported through the path 'jquery_on_a_path_that_confuses_you'. The would-be user of this plugin must copy the plugin into his project and then configure his module loader or build tool to export jQuery at the path 'jquery_on_a_path_that_confuses_you'.
As far as I can tell:
There is no standard for what to use for import paths.
There is no standard way to express the dependency, version, and import path requirements to the user of a piece of code.
There is no standard remedy to deal with clashing import paths or load multiple versions of a library.
Does there exist any plan to deal with this strange arrangement? To me it seems a little crazy to have module systems that don't know how to name their modules. Am I wrong?
You may want to check jspm.io + SystemJS which is a relatively new package manager and universal module loader which is increasing in popularity.
Please find below some presentations and article on the subject I found useful:
https://www.youtube.com/watch?v=MXzQP38mdnE,
https://vimeo.com/65042246,
https://www.youtube.com/watch?v=szJjsduHBQQ,
http://javascriptplayground.com/blog/2014/11/js-modules-jspm-systemjs/
Late with the answer, but if you're after writing plain JS code (without jQuery or other frameworks), I've found that there's the deploader.js repo, which you can use to wrap any kind of JS into modules and do dependency loading.
May worth checking out.
I've been researching CommonJs, AMD, module loading, and related issues for over a week. I feel like nothing out there does what I need. My basic need is to share code seamlessly between frontend and backend. There are various issues around this including module formats for the client side, script loading, and module format conversions/wrapping. The piece I've been struggling with recently is how to use both CommonJS and AMD (or something AMD-like) in node.js.
You can't get away from commonJs in node.js, so my thinking is that if I want to use AMD, it has to work alongside commonJs. What tools, libraries, or techniques can I use to get something AMD-like working?
For example, I would like to be able to write a module like this:
var x = require('x')
modules.exports = function(a, callback) {
if(a) {
require(['y','z'], function(y,z) {
callback(x, y.o + z.k)
}
} else {
callback(x, "ok")
}
}
Ideally:
Both node.js and the amd-like modules will have paths interpreted in the node.js way (paying attention to node_modules unless the module path starts with "/", "./", or "../")
doesn't require source conversion for the server side in a build step (ie modules will run in node.js without each one being programmatically converted)
module or require don't need to be explicitly passed into the amd-like require function
uRequire is the perfect tool for this requirement, it's all about interoperability between the module formats and their incompatibilities.
Essentially uRequire converts or translates modules from nodejs to AMD and vise versa, plus the UMD format that runs on both nodejs and the browser or a combined .is that requires no AMD loader on browser.
It will require a build step though, but that is a minor concern in contrast to the offering.
You could check out, http://dojotoolkit.org/documentation/tutorials/1.9/node/
I've only played with it a little, but has worked with what I've tried. I got it working with node-orm and remember that being a pain to get going, but might of just been me making a mess while playing with it.
Essentially you end up with AMD on the server, like:
require(["dojo/node!orm","other/amd/module"], function(orm){
//use third party commonjs module and your own amd modules here
}
It looks like you've already investigated Requirejs's suggestion to wrap commonjs modules in an AMD require (automatically during build most likely using r.js).
I am writing a Javascript application that is supposed to be hosted on various sites. The application itself uses jQuery and jQuery UI. I am aware of jQuery.noConflict(true) trick and using it currently so not to pollute the global space. My aim is to always keep jQuery and any of its plugins local to my application so as not to conflict with anything on the hosting site. It should be possible for the hosting site to load its own jQuery (maybe different version) and add some plugins.
Now, I would like to structure the app as an AMD module. It is OK with me to require from the hosting site to use an AMD loader (require.js, curl, etc.) but I wouldn't like to require any particular one - just anything conforming to AMD API (yep, I know it's not 100% ready yet).
Is it at all possible? Can I do it in an interoperable way, so that the hosting site can use e.g. either requirejs, curl or Dojo AMD loader?
For example, with this:
define(['jquery'], function($) {
var myLocaljQuery = $.noConflict(true);
... my app module implementation here ...
});
how can I specify the path for jQuery that my application wants to use? As far as I understand, I cannot do anything like this:
require.config({
paths: {
'jquery' : 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min'
}
});
define(['require'], function(require) {
require(['jquery'], function($) {
var myLocaljQuery = $.noConflict(true);
... my app module implementation here ...
});
});
because the first require is a global object and this is specific to requirejs.
After doing some more research, I think it is not feasible at the moment. At least for one thing - the module ids are global within AMD context, so I cannot simply use "jquery" or "jquery-ui" module IDs in my application and be sure that there won't be any conflict when someone hosts my application and wants to use jQuery (potentially other version) through AMD too. See for example here:
curl.js does not prevent conflicts with third-party scripts that may load other versions of jQuery as an AMD module.
As far as I understand, AMD is aimed at modularizing stuff within your own app. It doesn't have much support for integration with third-party apps, at least not when you want to do some encapsulation to avoid conflicts.