module config for path and shim - javascript

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

Related

RequireJS is loading scripts that are not needed on all pages

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.

Related paths for dependencies in RequireJS define

In a requirejs module, i want to load some related files as dependencies. That files are in the same folder as module with define(). The module and related files maybe move to another location.
How can i set define's dependencies by related paths?
movableModule.js now:
define('movableModule', [
"changable/path/to/my/modules/relatedFile1",
"changable/path/to/my/modules/relatedFile2"
], function(){
console.log("movableModule loaded");
});
movableModule.js i want to be like this:
define('movableModule', [
"./relatedFile1",
"./relatedFile2"
], function(){
console.log("movableModule loaded relatively!");
});
As i know calling require.config and using baseUrl will change all routes in all modules, yes? and if no, i don't know how to use it in this case.
You should configure RequireJS to define your different paths.
Then you could request those module, just by their name :
require.config({
baseUrl: "/",
paths: {
"relatedFile1": "changable/path/to/my/modules/relatedFile1"
}
});
Then you'll be able to do :
define('movableModule', [
"relatedFile1"
], function(){
console.log("movableModule loaded");
});
Also, as mentionned in comments, you can define a partial path, and use it later for your incoming modules :
require.config({
baseUrl: "/",
paths: {
"modulePath": "changable/path/to/my/modules/"
}
});
And require your modules like that :
define('movableModule', [
"modulePath/module1",
"modulePath/module2"
], function(){
console.log("movableModule loaded");
});
Comment from the developer.

Modularizing an App using RequireJS

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.

requirejs multiple paths one library

Ignoring the fact that this might not be a good idea is it possible to get requirejs to reference one library using two different paths i.e
require.config({
paths: {
'ko': '../Lib/knockout-2.1.0.debug',
'knockout': '../Lib/knockout-2.1.0.debug',
}
});
or possibly some other way? Currently its complaining
The reason is we have some external libraries that have external dependancies on 'knockout' where as we use 'ko'
It sounds like a map configuration is a better approach here. If you have modules that require 'ko' but others require 'knockout', then simply map 'ko' to 'knockout' for your modules.
require.config({
paths: {
'knockout': '../Lib/knockout-2.1.0.debug',
},
map: {
'*': { 'ko': 'knockout' }
}
});
And if only certain module names need the remapping, replace "*" with module name in the above example.
Or, if by "where as we use 'ko'" you also mean window.ko, you can do this instead:
require.config({
paths: {
'knockout': '../Lib/knockout-2.1.0.debug',
}
});
define('ko', ['knockout'], function (punch)
{
window.ko = punch;
return punch;
});

Implementing AMD in JavaScript using RequireJS

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.

Categories