I'm new to grunt and file optimalisation. I have combined some JavaScript files (with grunt concat) into one file release.js and then minified it into release.min.js.
Now I am loading the combined files (they are jQuery ui widgets) with a requirejs script.
My question is, is this the right way to do this? Look's kinda strange to reference release.min.js four times. Is this causing requirejs to load the release.min.js four times? Or only once? It would be silly to load a big minified file a few times over.
requirejs.config({
baseUrl: '/site/scripts',
shim: {
layout: {
deps: ['jquery', 'jquery-ui']
} ,
serviceTraining: {
deps: ['jquery', 'jquery-ui']
},
serviceMail: {
deps: ['jquery', 'jquery-ui']
},
forms: {
deps: ['jquery', 'jquery-ui']
}
},
paths: {
layout: 'custom/release/release.min',
forms: 'custom/release/release.min',
serviceTraining: 'custom/release/release.min',
serviceMail: 'custom/release/release.min'
}
});
require(['layout', 'serviceMail', 'serviceTraining', 'forms'], function() {
$(function() {
// theme
var layoutPlugin = $( "body" ).layout({ siteName: "Website name", domainName: "website.com" });
layoutPlugin.layout( "init" );
// Forms
var formsPlugin = $( "body" ).forms();
formsPlugin.forms( "init" );
// Load mail services
var serviceMail = $.services.serviceMail();
serviceMail.subscribe( $(".form--subscribe") );
// Subscribe to the MailChimp e-mail list
serviceMail.contact( $(".form--contact") );
// Register for training
var serviceTraining = $.services.serviceTraining();
serviceTraining.register( $(".form--training-register") );
});
});
As always, all suggestions are welcome. Thank you!
Having two paths that point to the same file is in general a good way to get loading errors. (I say "in general" because there may be specific cases that will work. It is just luck though. You're not supposed to do it.)
What you should do is use instead of paths the bundles configuration option:
bundles: {
'custom/release/release.min': ['layout', 'forms',
'serviceTraining', 'serviceMail']
}
This tells RequireJS that when you want to load any of the four modules in the array, it should load the module custom/release/release.min to find them.
Related
I am looking for a way to create and include a single script which will house all my Google Analytics event tracking code. There are various points in my application where I want to be able to track clicks and interaction and I would like to be able to have all these functions in a single file.
My problem is I can't find a suitable way of doing that with RequireJS, which my site uses.
This is an example of what I would have. I'd like to target an anchor with a class of resend and trigger a GA event.
Resend
Sure that is simple enough, but I don't want to have to require a module everywhere I want to do event tracking. Is this necessary or is there a clearer/cleaner way to do it? I should point out I don't want to include Google Analytics as there are many tutorials on how to do that and I am already doing that through the traditional way of having it in the footer - it's only GA event tracking code I want to include.
This is my requireJS config.js:
require = {
baseUrl: '/assets/js',
paths: {
// Amcharts.
'amcharts': '/assets/vendor/amcharts/dist/amcharts/amcharts',
'amcharts.funnel': '/assets/vendor/amcharts/dist/amcharts/funnel',
'amcharts.gauge': '/assets/vendor/amcharts/dist/amcharts/gauge',
bootstrap: '/assets/vendor/bootstrap/dist/js/bootstrap.min',
jquery: '/assets/vendor/jquery/dist/jquery.min',
jstz: '//cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.4/jstz.min',
pwstrength: '/assets/vendor/pwstrength-bootstrap/dist/pwstrength-bootstrap-1.2.7.min',
},
shim: {
'amcharts.funnel': {
deps: [ 'amcharts' ],
exports: 'AmCharts',
init: function() {
AmCharts.isReady = true;
}
},
'amcharts.gauge': {
deps: [ 'amcharts' ],
exports: 'AmCharts',
init: function() {
AmCharts.isReady = true;
}
},
pwstrength: {
deps: [
'jquery'
]
},
bootstrap: {
deps: [
'jquery'
]
}
}
};
// Apply the urlArgs here for cache busting.
require.urlArgs = requireBase.urlArgs;
This is how I ended up configuring my RequireJS config.js.
require = {
baseUrl: '/assets/js',
paths: {
'gaEventTracking': '/assets/src/js/crmpicco/gaEventTracking',
},
shim: {
'gaEventTracking': {
deps: [
'jquery'
]
},
}
};
// Apply the urlArgs here for cache busting.
require.urlArgs = requireBase.urlArgs;
There then exists a gaEventTracking.js in the /assets/src/js/crmpicco directory.
I'm new to working with RequireJS, and am trying to figure out shimming 3rd-party, interdependent scripts. Specifically, I'm trying to get the Stanford Crypto scripts imported.
Basically, the suite is comprised of the core (jsbn.js, jsbn2.js, base64.js, rng.js, and prng4.js), a basic RSA script (rsa.js), and an extended RSA script (rsa2.js).
rsa.js defines the global variable-object RSAKey, and rsa2.js references it.
function RSAKey() {
this.n = null;
this.e = 0;
this.d = null;
this.p = null;
this.q = null;
this.dmp1 = null;
this.dmq1 = null;
this.coeff = null;
}
I've set up my shim in a way that I thought was correct, but I get the error "RSAKey is not defined" in rsa2.js. The following is my shim:
require.config({
paths: {
'jsbn': "../StanfordRSA/jsbn.js",
'jsbn2': "../StanfordRSA/jsbn2.js",
'base64': "../StanfordRSA/base64.js",
'rng': "../StanfordRSA/rng.js",
'prng4': "../StanfordRSA/prng4.js",
'rsa': "../StanfordRSA/rsa.js",
'rsa2': "../StanfordRSA/rsa2.js"
},
shim: {
'rsa': {
deps: ['jsbn', 'jsbn2', 'base64', 'rng', 'prng4'],
exports: "RSAKey"
},
'rsa2': {
deps: ['rsa']
}
}
});
My understanding, then, is that if I set 'rsa2' as a requirement in one of my RequireJS modules, it would look at the shim and see that rsa2 is dependent on rsa, which is dependent on the core and exports RSAKey...But that's not what's happening, and it seems like either rsa isn't loading, or it isn't loading correctly. (Please note that all of this works using raw script tags. I'm trying to convert an already existing, already functioning webapp to RequireJS)
Thoughts?
Your basic setup is correct, except for 2 things:
(really important!) You have to omit the .js extensions!!!
You probably have missed the exact dependencies between the scripts.
After some experimentation and reading the comments at the top of the scripts, the working configuration is:
require.config({
paths: {
'jsbn': "../StanfordRSA/jsbn",
'jsbn2': "../StanfordRSA/jsbn2",
'base64': "../StanfordRSA/base64",
'rng': "../StanfordRSA/rng",
'prng4': "../StanfordRSA/prng4",
'rsa': "../StanfordRSA/rsa",
'rsa2': "../StanfordRSA/rsa2"
},
shim: {
'rng': {
deps: ['prng4']
},
'jsbn2': {
deps: ['jsbn']
},
'rsa': {
deps: ['jsbn', 'rng'],
exports: 'RSAKey'
},
'rsa2': {
deps: ['rsa', 'jsbn2'],
exports: 'RSAKey'
}
}
});
Check out a plunk here.
I'm using RequireJS (version 2.1.14) and would like to concatenate my JavaScript files into one single app-built.js.
I've created a little node module which reads my app.js, extracts the project paths and gets executed once I run node build in the js directory of my application.
The node module (build.js):
var fs = require('fs'),
path = require('path'),
directory = __dirname + path.sep,
requirejs = require(directory + 'vendor/r.js');
fs.readFile(directory + 'app.js', 'utf8', function(err, data) {
if (err) {
console.log('Error: ' + err);
return
} else {
data = data.replace(/'/g, '"').replace(/\s+/g, '');
var paths = data.substr(data.indexOf('{'), data.indexOf('}')),
paths = paths.substr(0, paths.indexOf('}') + 1),
paths = JSON.parse(paths);
createAppBuilt(paths);
}
});
function createAppBuilt(paths) {
var config = {
baseUrl: __dirname,
paths: paths,
name: 'app',
out: 'app-built.js',
preserveLicenseComments: false,
findNestedDependencies: true,
removeCombined: true
};
requirejs.optimize(config, function(buildResponse) {
var contents = fs.readFileSync(config.out, 'utf8');
console.log('Created app-built.js');
}, function(err) {
console.log('Error: ' + err);
return;
});
}
app.js:
var paths = {
'jquery': 'vendor/jquery-1.11.0.min',
// other paths
};
// Set language, necessary for validtaion plugin -> validation.js
if (Modernizr.localstorage) {
localStorage.getItem('language') || localStorage.setItem('language', navigator.language || navigator.userLanguage);
}
requirejs.config({
paths: paths,
shim: {
touchswipe: {
deps: ['jquery']
},
icheck: {
deps: ['jquery']
},
validate: {
deps: ['jquery']
},
mask: {
deps: ['jquery']
},
chosenImage: {
deps: ['jquery', 'chosen']
},
cookie: {
deps: ['jquery']
}
}
});
require(['globals', 'jquery', 'underscore'], function() {
var initial = ['main'];
if (!Modernizr.localstorage) {
initial.push('cookie');
}
require(initial, function(Main) {
$(function() {
if (!Modernizr.localstorage) {
$.cookie.json = true;
}
Main.init();
});
});
});
The app-built.js gets generated but when I include it in my index.php all the other modules get loaded as well. How can I prevent the loading of all modules and only load the app-built.js?
I recommend you look into http://webpack.github.io/
or http://browserify.org/ as these solve this problem for you.
They allow you to use require much as before, yet the code is compiled/concatenated into a single file.
Webpack allows for a bit more flexibility in loading different chunks of code for different parts of your site, but Browserify is the most well-known so far.
There may be a cost in switching over to these, as I don't think that they're 100% compatible requirejs, however they bring great advantages.
Here's someone's journey from RequireJS to Browserify with some Pros and Cons.
Separate modules into different files, e.g. app-built.js, user-built.js. Then load script when it's needed.
Here's a demo: http://plnkr.co/edit/s6hUOEHjRbDhtGxaagdR?p=preview .
When page loaded, requirejs only loads global.js. After clicking the Change Color button, requirejs starts to load colorfy.js and random-color.js, which required by colorfy.js.
I am not sure about the exact details, but, yet if you don't have an exports option, r.js doesn't define a named module for you, that causes to actually load the script.
I assume you have jquery plugins there so add this extra exports option:
shim: {
touchswipe: {
deps: ['jquery'],
exports: 'jQuery.fn.touchswipe'
},
This should force r.js to build a named module for touchswipe:
define("touchswipe", (function (global) {
return function () {
var ret, fn;
return ret || global.jQuery.fn.touchswipe;
};
}(this)));
Note that, exports option might not build this named module, in that case your best bet is to include this manually.
Again I am not sure about why and how this happens, It must be a bug in requirejs, it's unlikely there is a tweak for this.
Changing the r.js optimizer (to uglify2) solved the problem for me:
var config = {
baseUrl: __dirname,
paths: paths,
name: 'app',
out: 'app-built.js',
findNestedDependencies: true,
preserveLicenseComments: false,
removeCombined: true,
optimize: 'uglify2'
};
I need some help with the concept of only loading modules when they are needed using requireJS
this is my main.js
require(['jquery', 'path/somemodule'],
function($, somemodule) {
$(document).ready(function() {
somemodule.init()
})
})
and in the somemodule.js
define(['jquery', 'path/someothermodule'], function ($, someothermodule) {
"use strict";
var somemodule;
somemodule = {
init: function () {
someothermodule.init()
}
}
return somemodule;
)}
right now somemodule.js and someothermodule.js is loaded on all pages. How do I only load it when it's needed?
When you require a module2 from module1 using the standard define() syntax module1 will not load/run until module2 has been fully loaded. That looks like this:
// inside module1
define(['module2'], function(mod2) {
// we don't get here until AFTER module2 has already been loaded
});
An alternative to lazy-load module2 looks like this:
// inside module1
define([], function() {
require(['module2'], function(mod2) {
// we don't get here until AFTER module2 has already been loaded
});
// but we DO get here without having loaded module2
});
Now you have to program somewhat carefully to make sure you don't run into any issues with asynchronicity.
In your case you can modify your main.js file
require(['jquery'],
function($) {
// jquery is loaded, but somemodule has not
if(thisPageNeedsSomeModule) {
require(['path/somemodule'],
function(somemodule) {
// now somemodule has loaded
$(document).ready(function() {
somemodule.init()
})
});
}
})
Your main.js file will load any file paths provided to it, so long as other elements of your application specify them as dependencies. See my example main.js file:
require.config({
paths: {
'app': 'app',
'underscore':'bower_components/underscore/underscore-min',
'backbone':'bower_components/backbone/backbone-min',
'marionette':'bower_components/backbone.marionette/lib/backbone.marionette.min',
'jquery': 'bower_components/jquery/jquery.min',
'tpl':'bower_components/requirejs-tpl/tpl',
'bootstrap':'bower_components/bootstrap/dist/js/bootstrap.min',
'leaflet':'bower_components/leaflet/leaflet',
'leaflet.markercluster':'bower_components/leaflet/leaflet.markercluster',
},
shim: {
'underscore': {
exports: '_'
},
'leaflet': {
exports: 'L'
},
'leaflet.markercluster': {
deps: ['leaflet']
},
'backbone': {
deps: ['underscore']
},
'marionette': {
deps: ['backbone']
},
'jquery': {
exports: '$'
},
'bootstrap': {
deps: ['jquery']
},
'app': {
deps: ['jquery', 'leaflet','bootstrap', 'leaflet.markercluster', 'marionette', 'tpl']
},
'app.elem': {
deps:['app']
},
'app.api': {
deps:['app']
}
}
})
require(['app','app.api','app.elem'], function() {
App.start();
})
And my initial application file:
define(['router', 'collections/moments'], function(router, momentCollection) {
// Boot the app!
App = new Marionette.Application();
App.LocResolve = false; // Have we resolved the user's location?
App.Locating = true; // Are we actively tracking the user's location?
App.FileReader = window.FileReader ? new FileReader : null;
App.Position = null; // Instant access to Lat & Lng of user.
App.MomentsRaw = null; // Keep cached copy of returned data for comparison.
App.Moments = new momentCollection; // Current collection of moments.
App.Markers = new L.MarkerClusterGroup(); // Create Marker Cluster Group
App.View = null; // Current view.
// Marionette Regions
App.addRegions({
header: '#header',
map: '#map',
list: '#list',
modal: '#modal',
});
return App
})
I noticed that you aren't passing in a configuration object - is this intentional? If you use R.js, the build optimizer, it will automatically remove unused vendor files for you.
In short, sets paths to your vendor files in the require.js config, then call upon them via define() whenever you need a particular asset. This will ensure that only files you need are used. Hope this helps!
I can't seem to figure out how to load Bootstrap via RequireJS. None of the examples that I found worked for me.
Here is my shim:
require.config({
// Sets the js folder as the base directory for all future relative paths
baseUrl: "./js",
urlArgs: "bust=" + (new Date()).getTime(),
waitSeconds: 200,
// 3rd party script alias names (Easier to type "jquery" than "libss/jquery, etc")
// probably a good idea to keep version numbers in the file names for updates checking
paths: {
// Core libsraries
// --------------
"jquery": "libs/jquery",
"underscore": "libs/lodash",
"backbone": "libs/backbone",
"marionette": "libs/backbone.marionette",
// Plugins
// -------
"bootstrap": "libs/plugins/bootstrap",
"text": "libs/plugins/text",
"responsiveSlides": "libs/plugins/responsiveslides.min",
'googlemaps': 'https://maps.googleapis.com/maps/api/js?key=AIzaSyDdqRFLz6trV6FkyjTuEm2k-Q2-MjZOByM&sensor=false',
// Application Folders
// -------------------
"collections": "app/collections",
"models": "app/models",
"routers": "app/routers",
"templates": "app/templates",
"views": "app/views",
"layouts": "app/layouts",
"configs": "app/config"
},
// Sets the configuration for your third party scripts that are not AMD compatible
shim: {
"responsiveSlides": ["jquery"],
"bootstrap": ["jquery"],
"backbone": {
// Depends on underscore/lodash and jQuery
"deps": ["underscore", "jquery"],
// Exports the global window.Backbone object
"exports": "Backbone"
},
"marionette": {
// Depends on underscore/lodash and jQuery
"deps": ["backbone", "underscore", "jquery"],
// Exports the global window.Backbone object
"exports": "Marionette"
},
'googlemaps': { 'exports': 'GoogleMaps' },
// Backbone.validateAll plugin that depends on Backbone
"backbone.validate": ["backbone"]
},
enforceDefine: true
});
and here is how I call Bootstrap:
define([
"jquery",
"underscore",
"backbone",
"marionette",
"collections/Navigations",
'bootstrap',
],
function($, _, Backbone, Marionette, Navigations, Bootstrap){
The error that I get is this:
Uncaught Error: No define call for bootstrap
Any ideas on how to get this resolved?
I found a working example here:
https://github.com/sudo-cm/requirejs-bootstrap-demo
I followed it to get my code to work.
According to that demo, especially app.js, you simply make a shim to catch Bootstrap's dependency on jQuery,
requirejs.config({
// pathsオプションの設定。"module/name": "path"を指定します。拡張子(.js)は指定しません。
paths: {
"jquery": "lib/jquery-1.8.3.min",
"jquery.bootstrap": "lib/bootstrap.min"
},
// shimオプションの設定。モジュール間の依存関係を定義します。
shim: {
"jquery.bootstrap": {
// jQueryに依存するのでpathsで設定した"module/name"を指定します。
deps: ["jquery"]
}
}
});
and then mark Bootstrap as a dependency of the app itself, so that it loads before app.js.
// require(["module/name", ...], function(params){ ... });
require(["jquery", "jquery.bootstrap"], function ($) {
$('#myModalButton').show();
});
Finally, since app.js is the data-main,
<script type="text/javascript" src="./assets/js/require.min.js" data-main="./assets/js/app.js"></script>
Bootstrap's JS is guaranteed to load before any application code.
Bootstrap lib does not return any object like jQuery, Underscore or Backbone: this script just modifies the jQuery object with the addition of new methods. So, if you want to use the Bootstrap library, you just have to add in the modules and use the jquery method as usual (without declarating Bootstrap like param, because the value is undefined):
define([
"jquery",
"underscore",
"backbone",
"marionette",
"collections/Navigations",
"bootstrap",
],
function($,_,Backbone,Marionette,Navigations){
$("#blabla").modal("show"); //Show a modal using Bootstrap, for instance
});
I found it was sufficient to add the following to my requirejs.config call (pseudocode):
requirejs.config({
...
shim: {
'bootstrap': {
deps: ['jquery']
}
}
});
I like to use Require.Js ORDER plugin, what it does? Simply loads all your Libraries in order, in this case you won't get any errors, ohh and bootstrap depends on jQuery, so we need to use shim in this case:
requirejs.config({
baseUrl: "./assets",
paths: {
order: '//requirejs.org/docs/release/1.0.5/minified/order',
jquery: 'http://code.jquery.com/jquery-2.1.0.min',
bootstrap: '//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min'
},
shim: {
'bootstrap': {
deps:['jquery']
}
}
});
require(['order!jquery', 'order!bootstrap'], function($) {
});