I've written a full web app and I bootstrap it with require in this line at the end of my HTML document:
<script src="js/vendor/require-2.1.6.min.js" data-main="js/main.js"></script>
Inside main.js, I declare my app using the following two functions:
requirejs.config({
paths: {
'jquery': 'vendor/jquery-1.9.1.min',
'lodash': 'vendor/lodash-1.3.1.min',
'knockout': 'vendor/knockout-2.2.1.min',
'bootstrap': 'vendor/bootstrap-2.3.2.min'
},
shim: { 'bootstrap': { deps: ['jquery'] } }
});
requirejs(dependencies, function main(dependencies) { ... });
Now, I want to port my app as a jQuery plugin. As such, the end result I desire is to wrap my app in a jQuery fn,
(function($) { $.fn.myPlugin = function() { ... }; }(jQuery));
modularize the code to compile into a single js file (plugin-1.0.0.min.js), include the code in another project, with or without AMD, and load my app into a div using
$('#pluginDiv').myPlugin({ options: {} });
First, do I need to change the requirejs/requirejs.config functions to declares in order to package my app as a modular component? Or do I simply leave my app like this? What about the config?
Next, how do I expose my plugin to the jQuery which the plugin user will be using, e.g. the one declared in the global scope? Will this work if they're using an AMD?
UPDATE
For question 1, I moved my requirejs.config to a build file, build.js, which requires some additional properties:
({
baseUrl: ".",
paths: {
'jquery': 'vendor/jquery-1.9.1.min',
'lodash': 'vendor/lodash-1.3.1.min',
'knockout': 'vendor/knockout-2.2.1.min',
'bootstrap': 'vendor/bootstrap-2.3.2.min'
},
shim: { 'bootstrap': { deps: ['jquery'] } },
optimize: "none", // for debug
name: "main",
out: "plugin.js"
})
I was able to use r.js to compile this and it works great.
I am still stuck on question two, however. The code in my compiled myPlugin.js is as follows:
requirejs(dependencies, function main(dependencies) {
(function($) {
$.fn.myPlugin = function(options) {
...
return this;
};
})(jQuery);
});
where dependencies does not include jQuery. Then, I bootstrap the app by calling:
<script src="js/vendor/require-2.1.6.min.js" data-main="js/app.js"></script>
And the code in app.js is
requirejs.config({
paths: { 'jquery': 'vendor/jquery-1.9.1.min' },
shim: { 'myPlugin': ['jquery'] }
});
requirejs(['jquery','myPlugin'], function main($) {
$('#plugin').myPlugin(options);
});
However, my code always attempts to bind the plugin (from app.js), fails when the plugin is not a method on jQuery, and THEN loads the compiled plugin code and creates the method on jQuery. What's the problem here??
UPDATE 2
So, I've created a modular JavaScript file plugin.js using requirejs and its optimizer. The main code in the compiled plugin script,
requirejs(dependencies, function main(dependencies) {
(function($) {
$.fn.plugin = function(options) { return this; }
})(window.jQuery);
);
doesn't get called until after the main code in the parent app:
requirejs(['jquery','plugin'], function main($) {
$('#plugin').plugin({});
});
I assume this is because they are both making calls to requirejs. So the problem here is how to write my plugin such that it is usable within an AMD loader.
Sorry, I'm still figuring out the right questions to ask.
There is a pretty standard convention for modularizing plugins for use with and without requirejs. It looks something like this:
(function(){
var makeplugin = function(dependancies){
//do your plugin
};
if(define && define.amd) {
defined(dependancies,function(dependancies){
makeplugin(dependancies);
});
} else {
makeplugin(dependancies)
}
}());
Because your plugin uses require internally, but your parent app doesn't have to, you can load requirejs using $.getScript()
(function(){
var makeplugin = function($){
//do your plugin
};
if(define && define.amd) {
// require is defined already, just use the plugin and have it load what it needs
define(["jquery"],makeplugin);
} else if(jQuery){
// load require
jQuery.getScript("/vendor/require",function(){
require.config({
// your config
});
makeplugin(jQuery);
});
} else {
throw "requirejs or jquery are required for this plugin";
}
}());
It isn't pretty but it should work.
Related
I'm building an AMD module which uses multiple libraries with globals from a subfolder. Too reduce code and prevent globals leaking I want to use a config, but it seems the require config is only usable in the global context (how ironic)?!
What I want to do inside my module is basically this:
define(['require'], function(require) {
require.config({
baseUrl: 'sub/directory/',
paths: {
libfoo: 'libfoo23.min',
},
shim: {
libfoo: {
exports: 'Foo'
}
});
require(['libfoo'], function(Foo) {});
});
No, you can't do that.
And there is a good reason for it: If the module loading configuration is defined inside a module that requires the configuration to be loaded, who comes first? It's a chicken and egg problem.
RequireJS tries to minimize global pollution; it succeeds in that by introducing just 2 globals, required for bootstrapping (require() and define()).
I usually do it this way.
config.js
require.config({
baseUrl: 'sub/directory/',
paths: {
libfoo: 'libfoo23.min',
},
shim: {
libfoo: {
exports: 'Foo'
}
});
bootstrap.js
define([ 'config' ], function() {
require([ 'main' ]);
});
And in the HTML, I bootstrap this way:
<script data-main="/public/app/bootstrap" src="~/public/vendor/requirejs/require.js"></script>
Everything else (the app) start in main.js
I'm having a lot of troubles with RequireJS, I get errors simply by using a define module.
Let's assume I have this configuration:
require.config({
baseUrl: "js/",
paths: {
jquery: "libs/jquery/jquery-2.0.2.min",
handlebars: "libs/backbone/template/handlebars",
lodash: "libs/backbone/template/lodash.min",
backbone: "libs/backbone/backbone-1.0.0.min",
helper:"app/helper",
jquery_cookie:"libs/jquery/plugin/jquery.cookie",
text:"libs/require/text-2.0.7"
},
shim: {
"lodash": {
exports: '_'
},
"handlebars": {
deps:["jquery"],
exports: 'Handlebars'
},
"backbone": {
deps: ["helper", "lodash", "handlebars", "jquery"],
exports: "Backbone"
}
},
urlArgs: "bust=" + (new Date()).getTime()
});
define(["jquery"], function ($) {
console.log('define "jquery" on config.js');
return $;
});
console.log("end config.js");
First, I've tried the classic way, I've loaded a config file where I have all the dependances of my JavaScript files, I'm using jQuery, Backbone and other libs.
<script type="text/javascript" data-main="config" src="js/libs/require/require-2.1.6.min.js"></script>
<script type="text/javascript">
require(["jquery"], function ($) {
console.log("jquery loaded");
});
</script>
With this configuration, i get these errors and logs sequence:
Uncaught Error: Mismatched anonymous define() module: function (){return r}
> http://requirejs.org/docs/errors.html#mismatch
end config.js
define "jquery" on config.js
It seems the error is referred to the function require.config(...);
I thought it was a load problem, so I've tried to use the var require as described in RequireJS site without solving it.
The absurd thing is I'm working on a fullscreen app built in Backbone with the same configuration without getting any problem, here I need to work more with different views and I cannot start, basically the whole difference is here I'm using CakePHP.
Why this won't work, where I'm wrong?
The jQuery version you are using already has AMD support. So you don't have to use shim for it. You can directly give the path and start using it.
Since the module is already defined in the jQuery library, it throws errors when you try to redefine it. The following code tries to define an anonymous module.
define(["jquery"], function ($) {
console.log('define "jquery" on config.js');
return $;
});
remove the above code and should work fine. The following code uses require method to load the jquery module.
require(["jquery"], function ($) {
console.log($(document));
});
Hopefully this works.
I am trying to load KnockoutFire, a KnockoutJS plugin using RequireJS. It is only 1 file: https://github.com/hiroshi/knockoutFire/blob/master/knockoutFire.js
The plugin does two things:
Defines the global var "KnockoutFire"
Extends the core KnockoutJS ("ko") lib
So for this lib to load properly it needs to be able to access KnockoutJS through the global var "ko". Problem is I am loading KnockoutJS using proper AMD and there is no global "ko" available for KnockoutFire to reference. I have tried shimming both libs and various other tricks to get it to work, but I am stumped. Everything produces the error:
ReferenceError: ko is not defined
Here is what I am working with:
require.config({
enforceDefine: false,
baseUrl: 'assets/js/',
paths: {
'knockout': '//ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1'
},
shim: {
'knockout': { exports: 'ko' },
'knockoutfire': { exports: 'KnockoutFire', deps: ['knockout'] }
},
waitSeconds : 15
});
require(['knockoutfire'], function() {
log(KnockoutFire);
});
I've tried doing weird stuff too like:
require(['knockout'], function(ko) {
require(['knockoutfire'], function(kofire, ko) {
log(KnockoutFire);
});
});
Or:
define(['knockout'], function(ko) {
require(['knockoutfire'], function(ko) {
log(KnockoutFire);
});
});
If I download the third party lib and add this to it:
define(['knockout'], function(ko) {
<!--- third party lib --->
});
everything works fine.
Is there a way to achieve the same result without having to modify the third party lib?
When you need these scripts use order! plugin http://requirejs.org/docs/1.0/docs/api.html#order to ensure you load knockout before knockoutfire. i.e. in your file :
define('require','exports','order!knockout','order!knockoutfire',
function(req,exp,knockout,knockoutfire){
//trimmed
I think a closer look at http://knockoutjs.com/downloads/knockout-2.2.1.debug.js might reveal the answer. There is special AMD handling, and I'm not sure that's playing well with the things I've tried (using shim and exports).
This hack works though. We basically control the globals assignments with a bootstrap module.
require.config({
enforceDefine: false,
baseUrl: 'assets/js/',
paths: {
'jquery': 'http://code.jquery.com/jquery-1.9.1',
'knockout': 'http://knockoutjs.com/downloads/knockout-2.2.1.debug',
'knockoutfire': 'https://raw.github.com/hiroshi/knockoutFire/master/knockoutFire'
},
waitSeconds: 15
});
define("bootstrap", ['jquery', 'knockout'], function ($, ko) {
console.log("bootstrap", "jQuery " + $.fn.jquery, ko);
window.ko = ko;
var dfd = $.Deferred();
require(['knockoutfire'], function (kof) {
dfd.resolve(KnockoutFire);
});
return dfd.promise();
});
require(['bootstrap'], function (bootstrap) {
bootstrap.then(function () {
console.log("ko", ko, "KnockoutFire", KnockoutFire);
});
});
Outputs:
bootstrap jQuery 1.9.1 Object {exportSymbol: function, exportProperty: function, version: "2.2.1", utils: Object, cleanNode: function…}
ko Object {exportSymbol: function, exportProperty: function, version: "2.2.1", utils: Object, cleanNode: function…}
KnockoutFire Object {version: "0.0.3", utils: Object, observable: function, mapObservable: function}
Example here.
This answer was inspired by #Paul's answer:
define("globalko", ['knockout'], function (ko) {
window.ko = ko;
});
require(['knockoutfire', 'globalko'], function (kofire) {
console.log(kofire);
});
This loads both libs without error.
Anyone have any suggestions for KO or KOFire to make their libs easier to work with with AMD?
assuming you have something like following in knockoufire
define('require','exports','ko',function(req,exp,ko){
//trimmed
Try the following line on top of knockoutfire:
var ko = ko.ko
So I'm working with jquery.flot and jquery.flot.selection and since define({... loads modules asynchronously I am having a problem because the selection plugin is trying to push itself into $.plot.plugins (which is created by jquery.flot) but at that moment $.plot.plugins still isn't defined.
I found that the "shim" argument in require.config should help me with this but I am having no luck...
so here's the rundown...
jquery.flot creates $.plot
and jquery.flot.selection adds itself to $.plot.plugins
what I've tried...
shim:{
'js/lib/jquery.flot':{
exports:'$.plot'
},
'js/lib/jquery.flot.selection':{
deps:['js/lib/jquery.flot']
}
}
also
shim:{
'js/lib/jquery.flot.selection':['js/lib/jquery.flot']
}
my plugin looks like this..
define(['jquery','lib/jquery.flot','lib/jquery.flot.selection'], function() {
(function($) {
// jQuery plugin definition
.....
I also tried
define(['jquery'],function(){
require[('js/lib/jquery.flot.selection'],function(){
//jQuery plugin definition
...
What should I do???
For anyone who happens to run into this in the future, RequireJS can be a little opaque when it comes to diagnosing problems. Here's a working example of RequireJS 2 with jquery.flot and jquery.flot.selection, without the use module.
It's a little hard to tell what's different between this example and the question above, thus the opaqueness!
require.config({
shim: {
'jquery': {
exports: '$'
},
'jquery.flot': {
deps: ['jquery'],
exports: '$.plot'
},
'jquery.flot.selection': {
deps: ['jquery.flot']
}
},
paths: {
'jquery': '//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min',
'jquery.flot': '//cdnjs.cloudflare.com/ajax/libs/flot/0.8.1/jquery.flot.min',
'jquery.flot.selection': 'https://raw.github.com/flot/flot/master/jquery.flot.selection'
}
});
require(['jquery', 'jquery.flot', 'jquery.flot.selection'], function ($) {
// jQuery plugin definition
console.log($.plot);
$('#x').text($.plot.plugins);
});
I am totally new to RequireJS so I'm still trying to find my way around it. I had a project that was working perfectly fine, then I decided to use RequireJS so I messed it up :)
With that out of the way, I have a few questions about RequireJS and how it figures out everything. I have the file hierarchy inside the scripts folder:
I have the following line inside my _Layout.cshtml file:
<script data-main="#Url.Content("~/Scripts/bootstrap.js")" src="#Url.Content("~/Scripts/require-2.0.6.js")" type="text/javascript"></script>
And here's my bootstrap.js file:
require.config({
shim: {
'jQuery': {
exports: 'jQuery'
},
'Knockout': {
exports: 'ko'
},
'Sammy': {
exports: 'Sammy'
},
'MD': {
exports: 'MD'
}
},
paths: {
'jQuery': 'jquery-1.8.1.min.js',
'Knockout': 'knockout-2.1.0.js',
'Sammy': 'sammy/sammy.js',
'MD': 'metro/md.core.js',
'pubsub': 'utils/jquery.pubsub.js',
'waitHandle': 'utils/bsynchro.jquery.utils.js',
'viewModelBase': 'app/metro.core.js',
'bindingHandlers': 'app/bindingHandlers.js',
'groupingViewModel': 'app/grouping-page.js',
'pagingViewModel': 'app/paging-page.js'
}
});
require(['viewModelBase', 'bindingHandlers', 'Knockout', 'jQuery', 'waitHandle', 'MD'], function (ViewModelBase, BindingHandlers, ko, $, waitHandle, MD) {
BindingHandlers.init();
$(window).resize(function () {
waitHandle.waitForFinalEvent(function () {
MD.UI.recalculateAll();
}, 500, "WINDOW_RESIZING");
});
var viewModelBase = Object.create(ViewModelBase);
ko.applyBindings(viewModelBase);
viewModelBase.initialize();
});
require(['viewModelBase', 'bindingHandlers', 'Knockout'], function (ViewModelBase, BindingHandlers, ko) {
BindingHandlers.init();
var viewModelBase = new ViewModelBase();
ko.applyBindings(viewModelBase);
viewModelBase.initialize();
});
Then I implemented my modules by using the define function. An example is the pubsub module:
define(['jQuery'], function ($) {
var
publish = function(eventName) {
//implementation
},
subscribe = function(eventName, fn) {
//implementation
}
return {
publish: publish,
subscribe: subscribe
}
});
I've basically done the same thing to all of my javascript files. Note that the actual file containing the pubsub module is jquery.pubsub.js inside the /Scripts/utils folder. This is also the case with other modules as well.
UPDATE:
Ok I updated my bootstrap file now that I think I understand what a shim is and why I should be using it. But it's still not working for me, although I've also declared all the paths that I think would've caused me trouble in getting them right. The thing is that it's not even going into my require callback inside the bootstrap file, so I guess I still have a problem in the way I'm configuring or defining my modules?
Well, for one, if you are going to use a non-amd library, say jQuery, with require and have the jQuery function passed to the callback, you need to specify a shim with exports in your require config, like so:
require.config({
shim: {
jQuery: {
exports: '$'
}
},
paths: {
jQuery: 'jquery-1.8.1.min.js',
}
});
Other than that I'm not sure I understand what your issue is exactly.
If you are using ASP.NET MVC take a look at RequireJS for .NET
The RequireJS for .NET project smoothly integrates the RequireJS framework with ASP.NET MVC on the server side using xml configuration files, action filter attributes, a base controller for inheritance and helper classes.
I did not completely understand what the problem is. But if it relates to JS libraries to be loaded with require.js then this boot file works for me:
require.config({
paths: {
"jquery": "/scripts/jquery-1.8.2",
"sammy": "/scripts/sammy-0.7.1"
},
shim: {
"sammy": {
deps: ["jquery"],
exports: "Sammy"
}
}
});
require(["jquery", "sammy"], function ($) {
$(document).ready(function () {
alert("DOM ready");
});
});
Please, note, there is no '.js' in paths.
BTW, if you use MVC 4, you don't need #Url.Content in 'href' and 'src' any more.