I have jQuery and a jQuery plugin loaded via RequireJS.
This is how my requirejs.config looks like:
requirejs.config({
baseUrl: "http://mysite.example.com",
"paths": {
// libraries
"jquery": "Static/js/library/jquery/jquery-1.10.2",
"jquery_sortable" : "Static/js/library/jquery-sortable/jquery-sortable",
shim: {
'jquery_sortable': ['jquery']
}
}
});
When I refresh the page two times or more very quickly, sometimes I get an exception in the plugin code that:
Uncaught ReferenceError: jQuery is not defined.
Basically, my plugin does not use the shim I have set for it.
What is the most reliable way to specify my shim config ??
Could be because you were not constructing as expected. As you notice you were putting shim inside paths. May be.
"paths": {
// libraries
"jquery": "Static/js/library/jquery/jquery-1.10.2",
"jquery_sortable" : "Static/js/library/jquery-sortable/jquery-sortable",
shim: {
'jquery_sortable': ['jquery']
}
}
-------EDIT AFTER COMMENT------
Maybe trying to define the dep as declared in the manual?
'foo': {
deps: ['bar'],
...
source: http://requirejs.org/docs/api.html#config-shim
It's weird because all the examples I have seen online put the shim config at the last bit of the requirejs.config but putting it at the top seemed to solve MY problem.
requirejs.config({
baseUrl: "http://mysite.example.com",
shim: {
'jquery_sortable': ['jquery']
},
"paths": {
// libraries
"jquery": "Static/js/library/jquery/jquery-1.10.2",
"jquery_sortable" : "Static/js/library/jquery-sortable/jquery-sortable"
}
});
I feel that this occurs because RequireJS is loading scripts asynchronously. This happens for performance reasons, i.e. scripts will be loaded in parallel. This is OK for normal AMD modules; RequireJS will keep the dependency tree from the define(["deps"], function(){...}) calls but will defer the execution of function() until its dependencies are resolved.
However with non-AMD, shimmed modules (like jQuery-UI), the entire script will be executed on load. This means that, if by any chance the "sortable" is loaded before "jquery", it will not find its dependency and fail as you write.
I believe the only robust workaround is to include jQuery as a plain old <script> tag, right after require.js. This way you cannot use the data-main attribute to load your entry point, you will have to require it separately:
<script src="scripts/lib/requirejs/require.js"></script>
<script src="scripts/lib/jquery/jquery.js"></script>
<script>
require(["app/bootstrap"]);
</script>
Related
Require.js loads every module on every page, so I get JavaScript errors on pages that don't need the loaded scripts. Specifically, the news-filter.js is loading on my search page, and causing the error:
jquery-1.12.3.min.js:2 Uncaught Error: Syntax error, unrecognized expression: "li." from this line in the news-filter.js
$("ul.mediaListing").children("li."+chosenYear).filter("."+chosenCategory).each(function(c) {
Am I missing somthing about how reqire.js determines what scripts are needed on each page?
My main.js file is:
requirejs.config({
baseUrl: [system-view:internal]"/render/file.act?path=/assets/scripts/"[/system-view:internal] [system-view:external]"/assets/scripts/"[/system-view:external],
paths: {
"jquery": "libs/jquery/jquery-1.12.3.min",
"velocity": "libs/velocity/velocity",
"bgstretch": "plugins/background-stretch/background-stretch",
"campus-map": "modules/campus-map",
"velocity-ui": "libs/velocity/velocity.ui",
"slick": "plugins/slick/slick",
"iscroll": "plugins/iscroll/iscroll",
"dotdotdot": "plugins/dotdotdot/jquery.dotdotdot.min.umd",
"select": "plugins/select/select",
"accordion": "modules/accordion",
"news-filter": "modules/news-filter",
"codebird": "modules/codebird",
"social-feed": "modules/social-feed"
},
shim: {
"slick": ["jquery"],
"select": ["jquery"],
"bgstretch": {
deps: ["jquery"]
},
"accordion": ["jquery"],
"codebird": ["jquery"],
"social-feed": {
dep: ["jquery", "codebird"],
exports: "displayFeed"
},
"campus-map": {
deps: [ "jquery" ]
},
"velocity": {
deps: [ "jquery" ]
},
"velocity-ui": {
deps: [ "velocity" ]
}
},
map: {
'*': {
'jQuery': 'jquery'
}
}
});
requirejs(
['jquery', 'modules/utils', 'modules/custom.ui', 'libs/jquery/paginga.jquery', "modules/social-feed", "modules/news-filter"],
function ($, utils, ui, paga, social, news) {
ui();
$(".paginate").paginga({
// use default options
});
});
I like a very modular approach and RequireJS has a lot of different ways to use it. I'll share how I typically have it set up which accomplishes what you are looking for, is streamlined and makes it easy to implement and understand.
I avoid having anything require in my main js completely. First I will create a bundle that includes both the base require.js and a JS file I create called config.js. I will have this bundle loaded in my layout page so it's always available. If you aren't using MVC, the idea is just to make sure Require and my custom config file are always loaded together and always available so do what you need to for that.
Config.js is very simple, in your case just taking your code it will look like this:
var require = {
baseUrl: [system-view:internal]"/render/file.act?path=/assets/scripts/"
[/system-view:internal] [system-view:external]"/assets/scripts/"[/system-
view:external],
paths: {
"jquery": "libs/jquery/jquery-1.12.3.min",
"velocity": "libs/velocity/velocity",
"bgstretch": "plugins/background-stretch/background-stretch",
"campus-map": "modules/campus-map",
"velocity-ui": "libs/velocity/velocity.ui",
"slick": "plugins/slick/slick",
"iscroll": "plugins/iscroll/iscroll",
"dotdotdot": "plugins/dotdotdot/jquery.dotdotdot.min.umd",
"select": "plugins/select/select",
"accordion": "modules/accordion",
"news-filter": "modules/news-filter",
"codebird": "modules/codebird",
"social-feed": "modules/social-feed"
},
shim: {
"slick": ["jquery"],
"select": ["jquery"],
"bgstretch": {
deps: ["jquery"]
},
"accordion": ["jquery"],
"codebird": ["jquery"],
"social-feed": {
dep: ["jquery", "codebird"],
exports: "displayFeed"
},
"campus-map": {
deps: [ "jquery" ]
},
"velocity": {
deps: [ "jquery" ]
},
"velocity-ui": {
deps: [ "velocity" ]
}
},
map: {
'*': {
'jQuery': 'jquery'
}
}
};
That's it. I tend to have all of my javascript files associated to each HTML page separated, so in the paths section of the set up I'll have the view name and then the location in my source of the corresponding javascript file. Then in my HTML page when I'm adding in scripts, I'll simply state
<script> require(['sign-in']); </script>
This will grab the script file I have defined in the require variable for my view. Then in each script file, sign-in.js for example for this one, I will wrap all of the scrip in a define statement, so at the top of each JS file you can clearly see what dependencies you will load and use in that page. It's clean, it's a framework, it works wonderfully, and it keeps you from loading things you don't need.
In the self contained JS file you would do:
define(['jquery', 'lodash', 'bootstrap'], function ($, _) {
//All JS code here
}):
I will have all my libraries that need a selector defined first and then everything else after. That's it, hopefully a real example will help you.
Am I missing somthing about how reqire.js determines what scripts are needed on each page?
Sure looks like you are. You show a main.js file that has this (reformatted to help readability):
requirejs(['jquery', 'modules/utils', 'modules/custom.ui',
'libs/jquery/paginga.jquery', "modules/social-feed",
"modules/news-filter"],
If you use this main.js on all your pages, then all the modules you list there are going to be loaded, which means that modules/news-filter is going to be loaded on all pages. This, irrespective of whether any code actually uses it.
The way RequireJS works, any dependency listed in a require call is loaded. And for each module loaded, any dependency they list in their define call or in a shim set for them in your configuration is also loaded. It does not matter one bit if something is listed but not actually used by your code.
Tangential remark about your configuration. In your paths you have:
"news-filter": "modules/news-filter"
But then you refer to it as modules/news-filter in your require call instead of news-filters. You should use news-filter or remove the mapping you've set in paths. RequireJS does now allow referring to the same JavaScript file through two different module names in the same context. If you load your module as modules/news-filter in one place and as news-filter somewhere else, you're going to run into problems.
If you need to use two different names to access the same JavaScript file, you use map. What map does is tell RequireJS "when you get a request for module X, load module Y instead". So RequireJS replaces the module name requested with a different one.
Maybe I have fundamentally misunderstood how requirejs config works but I thought my configuration below made some libraries global so I could just use them in other files while only having to require and define files that I needed to use within the individual script. However I cannot reference $ (jQuery) in my application code without getting a reference error indicating it is not globally accessible. I've isolated the problem to the simple example below.
My file set up is as follows:
test
|
|-index.html
|-TestApp.js
|-MainApp.js
|-lib
| |-require.js
| |-jquery.js
| |-loadash.js
| |-backbone.js
|-css
|-test.css
The library file versions are RequireJS 2.1.22, jQuery 2.0.3, Loadash 3.10.1 and Backbone 1.2.1. I'm just trying to set up my environment and the approach I am taking is to pass my TestApp.js file to require.js to load the required files and bootstrap the application code in MainApp.js. The script in index.html is as follows:
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' type='text/css' href='css/test.css'/>
</head>
<body>
<div></div>
<script src="./lib/require.js" type="text/javascript" data-main="./TestApp.js"></script>
</body>
</html>
The referenced css script file simply ensured the div is visible as an orange square. See below:
div {
height: 100px;
width: 100px;
background-color: #FA6900;
border-radius: 5px;
}
It's the script line in index.html that then kicks off the application code by passing my configuration file to requirejs. This is the TestApp.js passed across as data-main. The TestApp.js is here:
require.config({
paths: {
'jquery': 'lib/jquery',
'lodash': 'lib/lodash',
'backbone': 'lib/backbone'
},
map: {
'*': {
// Backbone requires underscore. This forces requireJS to load lodash instead:
'underscore': 'lodash'
}
},
shim: {
jquery: {exports: '$'},
underscore: {
deps: ['jquery'],
exports: '_'
},
backbone: {
deps: ['underscore'],
exports: 'Backbone'
},
TestApp: {
deps: ['backbone'],
exports: 'TestApp'
}
}
});
require(['MainApp'], function(MainApp) {
MainApp.run();
});
The file above references the paths to the library files I want to use, I then remap loadash to be loaded when underscore is required (I need some of the extra loadash capability), I then use the shim to ensure the dependancies are correct as the files are loaded. Passing this config file to require.js in the index.html seems to be working as all of the files are showing as loaded in my browser. However the problem seems to be they do not appear to be globally accessible as I thought they would be.
Following the config section the last require call loads the MainApp.js file and calls the exposed run function. The MainApp.js looks like this:
define(function(require) {
var run = function() {
$(document).ready(function() {
$('div').click(function() {
$('div').fadeOut('slow');
});
});
};
return {
run: run
};
});
As far as I understood I should not need to require the files I already mentioned in the require config, I thought they should be loaded and available to this code. This is where I have misunderstood what is going on or have missed a step out. The exposed run function is being called but the first line that calls $ throws the error:
ReferenceError: Can't find variable: $
So my questions are:
What have I got wrong in my thinking?
(or) What am I doing incorrectly?
What should I be doing in order to preload and make available
frequently referenced libraries so that I do not need to require and
define them in every file I have?
As far as I understood I should not need to require the files I already mentioned in the require config, I thought they should be loaded and available to this code.
You misunderstood how RequireJS works. You should read the documentation from start to finish. For now, here are things you should change.
You should require jquery in your MainApp module:
define(function(require) {
var $ = require("jquery");
You should remove your shims that you have for jquery, underscore and backbone as they all call define and shim is only for code that does not call define. I don't know what TestApp is but if it is your own code, you really should make it into a proper AMD module and remove the shim.
#Louis has made me realise the error in what I was doing above. Changing the shim in TestApp.js so that is reads:
MainApp: {
deps: ['backbone'],
exports: 'MainApp'
}
Corrected the problem, now Backbone, $ and _ are all available to the rest of my application code without cluttering up each files require. i.e. I do not need to begin every file with:
define (['lib/jquery', 'lib/loadash', 'lib/backbone'], function($, _ , Backbone) {
Given in my actual app the list of common deps is quite large this means I only need to define locally used resources and can control the paths from a single location.
I'm using backbone.js and require.js. I have a script with files dependencies but the problem is that a file is not loaded before executing my script. So, a function is not defined. Here is the code exemple :
define([
'jquery',
'jqueryUi',
'holder',
'knob',
'jquery.ui.widget',
'iframeTransport',
'fileupload',
'knobScript',
], function($, ui, Holder) {
$(document).ready(function() {
$('#upload').fileupload({...}); // This one is unedefined because the script from the file fileupload is not completely loaded
});
});
Is someone has a solution to be sure that the script fileuplaod called in define is fully loaded before executing the script with the function (functionFromFileupload) ?
Thank for your help
To complement Evgeniy's comment: The problem is not that fileupload is loaded after your function. It will be loaded before running your function, that is the contract of Require. (If you can confirm it doesn't, then it would probably be a misconfiguration or less probably a bug of Require.)
Most probably the problem is that, sometimes, fileupload may be loaded before jQuery. Thus, it does not find the jQuery object to plug to and $(...).fileupload(...) fails. Use shim in the Require configuration, e.g. as:
require.config({
...
shim: {
fileupload: {
deps: ["jquery"]
}
...
}
...
});
You will probably have to shim other things too, e.g. jQueryUI.
The line $("body").slimScroll(); throws error, because slimScroll is not defined. That is because require.js probably doesn't load automatically shim keys, if you are requiring any module, that is specified as dependency in shim (i think if i have shim jquery.slimscroll and its dependency is jquery, then when i require jquery, it automatically loads jquery.slimscroll - this behavior doesn't happen).
So the right way is call define(['jquery', 'jquery.slimscroll') function ($) ..., so require knows i need jquery.slimscroll and because it is in shim, it will be loaded a bit different, because it is not AMD compatible.
Problem is that if i have lot of jquery plugins, i need always pass all plugins that i need in define call. Is there any way, how to solve this type of annoying behavior and write just define(['jquery'], function ($) ..?
require.config({
baseUrl: 'js/bower',
paths: {
react: 'react/react-with-addons',
jquery: 'jquery/dist/jquery',
'jquery-private': '../jquery-private',
'jquery.slimscroll': 'jquery-slimscroll/jquery.slimscroll.min'
},
map: {
'*': {
jquery: 'jquery-private',
},
'jquery-private': {
jquery: 'jquery'
}
},
shim: {
'jquery.slimscroll': ['jquery']
}
});
require(['jquery'], function ($) {
$("body").slimScroll();
});
You could just add the jquery plugins to the dependencies of your jquery-private module.
define(['jquery', 'jquery.slimscroll', ...], function (jQuery) {
return jQuery.noConflict(true);
});
This way, when any module requires jquery, the plugins are also loaded at the same time.
I'm assuming that jquery-private is meant to load jQuery so that it does not conflict with other version of jQuery. That's what my example reflects above but it does not generally matter what the module actually does.
With this method you still need to have a shim configuration for each plugin that is not an AMD module. Moreover, your plugins will have to get a map setting just like for jquery-private to get the real jquery module. Otherwise, there will be a circular dependency.
I am not able to order the non AMD modules with shim config.
My shim config is like this. Even if I wanted to use require-jquery.js but still two non AMD modules will be jquery ui and jqGrid. jqGrid itself has a few plugins which must be loaded only when jqGrid has been loaded.
requireconfig.js
require.config({
baseUrl: '../jsp',
paths: {
app: '../js/app',
jquerygrid: 'http://view.jqueryui.com/grid',
lib: '../js/lib',
plugins: '../js/plugins',
jquery: '../js/lib/jquery-1.9.1',
jqueryui: [ 'http://ajax.googleapis.com/ajax/libs/jqueryui/'+
'1.9.2/jquery-ui'],
canjs: 'http://canjs.us/release/latest/can.view.mustache',
uigrid:'../js/plugins/mydataview',
jqgrid: '../js/plugins/grid.locale-en'
},
shim: {
jqueryui: {
exports: "$",
deps: ['jquery']
},
uigrid: {
deps:[
'jqueryui',
'http://view.jqueryui.com/grid/ui/jquery.ui.dataview.js',
'http://view.jqueryui.com/grid/ui/jquery.ui.grid.js',
'http://view.jqueryui.com/grid/ui/jquery.ui.observable.js',
'http://view.jqueryui.com/grid/ui/jquery.ui.dataviewlocal.js',
'http://view.jqueryui.com/grid/grid-spf/pager.js',
'http://view.jqueryui.com/grid/grid-editing/grid.selectable.js',
'http://view.jqueryui.com/grid/grid-editing/navigator.js',
'http://view.jqueryui.com/grid/grid-editing/localstore.js',
'http://view.jqueryui.com/grid/grid-editing/helpers.js',
'http://view.jqueryui.com/grid/external/jquery.tmpl.js',
'http://view.jqueryui.com/grid/grid-spf/grid-filter.js',
'http://view.jqueryui.com/grid/grid-spf/grid-sort.js'
]
},
canjs:{
deps: ['jquery','http://canjs.us/release/1.1.4/can.jquery.js']
},
jqgrid:['jqueryui','../js/plugins/jquery.jqGrid.src.js']
}
});
And my calling HTML is
<script type="text/javascript" src="../js/require.js"></script>
<script type="text/javascript" src="../js/requireconfig.js"></script>
<script type="text/javascript">
require(['jqgrid'], function($){
$("#mygrid").jqGrid({
pager: "#mygridpager"
})
});
</script>
In different runs I am getting different errors:
Sometimes :
Uncaught ReferenceError: jQuery is not defined ..... jquery.jqGrid.src.js:3589
An ofcourse this does not give an erro. But it looks like some hack because requirejs does not support order. Nested require calls are also less elegant. May be if there is a requirejs deferred like when().then() like chain can make it look better.
<script type="text/javascript">
require(['jquery'], function(){
require(['jqgrid'], function(){
$("#mygrid").jqGrid({
pager: "#mygridpager"
});
});
});
</script>
This sample fiddle has no errors when the JS files are loaded by RequireJS.
I think the crux of the problem is the '../js/plugins/jquery.jqGrid.src.js' file is loaded by RequireJS, but RequireJS does not know that this file itself has dependencies. And so when this file is loaded, its dependencies have not been loaded yet.
So you might need to be completely explicit with RequireJS as to which modules are dependent on which other modules. For example add more paths and more shims:
paths:
jqgridlocale: 'http://www.trirand.com/blog/jqgrid/js/i18n/grid.locale-en',
jqgrid: 'http://www.trirand.com/blog/jqgrid/js/jquery.jqGrid.min'
shims:
jqgrid:{
deps:['jqueryui','jqgridlocale']
},
jqgridlocale:{
deps:['jqueryui']
}
Now RequireJS knows that both jqgrid and jqgridlocale need jqueryui (and thus jquery) to have been loaded first.
I would also explicitly require() jQuery, as you are using it directly. It is more informative when reading the code to see that jQuery is being used directly.
require(['jquery','jqgrid'], function($){
$("#mygrid").jqGrid({
pager: "#mygridpager"
})
});