Require.js is loading modules even inside an IF statement - javascript

I'm finding some problems when loading certain .js module via require.js
Problem is: I only need to load certain module in some pages, not the entire website. Therefore I'm placing code this way:
if($('.module-news').length > 0 + $('.module-ticket-euromillones-multiple').length + $('.module-news-page').length > 0) {
require('_modules/news-grid').init();
}
This code search in the HTML if a class="module-news" exists (news page). If so, then load the module with the javascript.
That is NOT working. The IF is evaluating correctly, but module news-grid is ALWAYS loading no matter .module-news exists or not.
I found out that if I change the string of the module path for a variable, then requires behaves correctly, but that makes no sense. This how, following code works:
var name = "_modules/news-grid";
if($('.module-news').length > 0 + $('.module-ticket-euromillones-multiple').length + $('.module-news-page').length > 0) {
require(name).init();
}
Is this a known issue with require? Am I missing something? (maybe in the requirejs.config settings?
Help appreciated

Solution
You should be using the regular RequireJS idiom for calling require:
if (...) {
require(['_modules/news-grid'], function (news_grid) {
news_grid.init();
});
}
Why It Fails
You are using RequireJS' feature which lets you write require calls in the CommonJS format var module = require("module name"). You cannot use it for loading modules conditionally.
The call require('module name') (with a string as the first argument rather than a dependency array) is a convenience. It will return a module but if and only if the module is already loaded. If the module is not already loaded, then the call will fail. The only reason you don't have to worry about pre-loading the modules before calling require('module name') is that RequireJS does it for you.
define(function (require) {
var foo = require('foo');
});
is interpreted by RequireJS as:
define(['require', 'foo'], function (require) {
var foo = require('foo');
});
It scans the function for calls to require in the CommonJS idiom and generates a list of modules that it loads before executing the module. Conditionals are completely transparent to this process. Any require call with a string as the first parameter will be detected and the module it means to load will be added to the dependencies loaded before define's callback is called...
RequireJS won't detect those cases where something else than a string is passed to require. If you write:
var name = "foo";
var foo = require(name);
RequireJS will not know that you want to load foo and will not add foo to the list of dependencies. Is this a solution? No, because remember what I said earlier require('module name') will return a module, and not fail, only if the module is already loaded.
If you want your module to be loaded conditionally, then abandon the CommonJS idiom.

Related

inject a require into a js vendor module

About the framework (while I think the problem itself does not heavily rely on that): Angular 2 with Webpack
There is the library Leaflet.heat which relies on simpleheat. I got the missing type definitions under control.
I'm importing the libraries in my vendor.ts
[...]
import 'simpleheat';
import 'leaflet.heat/src/HeatLayer';
[...]
Inside of the HeatLayer class, the function simpleheat:
[simpleheat.js]
if (typeof module !== 'undefined') module.exports = simpleheat;
function simpleheat(canvas) {
...
is called. However, the HeatLayer module file does not require simpleheat inside it's file.
Thus, creating an instance of L.HeatLayer works, but the execution of the respective code in it's function fails with
ReferenceError: simpleheat is not defined
Now, adding (for testing purposes) simpleheat = require('simpleheat'); into the HeatLayer file (a vendor), it works.
Understandably, I don't want to modify a vendor file.
Question:
What options do I have, to make the function simpleheat accessible from inside the HeatLayer module?
One Solution I just found:
Change the vendor.ts to the following:
(<any>window).simpleheat = require('simpleheat');
import 'leaflet.heat/src/HeatLayer';
Are there others/better?

How does lazy module loading work in typescript?

In this typescript book section the author explains lazy loading with the following example:
import foo = require('foo');
export function loadFoo() {
// This is lazy loading `foo` and using the original module *only* as a type annotation
var _foo: typeof foo = require('foo');
// Now use `_foo` as a variable instead of `foo`.
}
According to the author typescript only loads the type of foo on the first call to require but on the second call when a foo var is created it loads the entire module required to create the var _foo.
How does this work. Can someone show a more detailed example of what's going on under the hood?
Typescript 2.4 now supports Dynamic Import Expressions where you can lazily import modules.
Here is the example:
async function getZipFile(name: string, files: File[]): Promise<File> {
const zipUtil = await import('./utils/create-zip-file');
const zipContents = await zipUtil.getContentAsBlob(files);
return new File(zipContents, name);
}
Behind the hood it is still using require as you can see in the transpiled code here
It's mentioned in the typescript handbook
The compiler detects whether each module is used in the emitted
JavaScript. If a module identifier is only ever used as part of a type
annotations and never as an expression, then no require call is
emitted for that module.
In this example, the first foo (the one without an underscore) is used only once in the type annotation as an argument of typeof, so the first require('foo') is omitted from the generated javascript code. You can check generated .js file to see that, and there would be only one call to the require at runtine, the 'second' one.
When loadFoo() is called, require('foo') is executed, calling built-in node.js require() function which loads foo module at runtime in the usual way.

RequireJs - why is Jquery injected and JSZip is not?

I'm trying to understand how requireJs works, could somebody please explain to me, why in the following example:
http://plnkr.co/edit/HEDc8F19wICMy0zeGWpH?p=preview
More specifically here:
require(['ble'], function () {
$('#someDiv').html(Ble.A());//This works fine
var zip = new JSZip();//This fails with JSZip is not defined
console.log(zip);
});
Jquery is defined, but JSZip is not? I also tried other combinations, but only one that seems to work is when I manually specify jszip in require array like this:
require(['jszip','ble'], function (JSZip) {
$('#someDiv').html(Ble.A());
var zip = new JSZip();
console.log(zip);
});
I know that documentation states:
The shim config only sets up code relationships. To load modules that
are part of or use shim config, a normal require/define call is
needed. Setting shim by itself does not trigger code to load.
But then - is jquery some kind of "special case" and I should normally, inject my dependencies manually even if they are specified in shim config section?
ASWER:
So it turns out jQuery is indeed a special case, and normally a manual injection of dependencies is required...
If you look in the source code of jQuery you will find the following:
// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.
// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function() {
return jQuery;
});
}
This means that jQuery will - when required - define itself as jquery.
require(['jszip','ble'], function (JSZip) {
In this above statement, it imports the jszip and returns a object as JSZip.
var zip = new JSZip();
Here that object is used. So with this code you did not get error.
Thus, For jszip, just require is not enough.

Use function from the main.js in imported module

I'm trying to include IOUtil.js and ChannelReplacement.js in my add-on, using the Cu.import(...) function. These two both use xpcom_generateQI, which I'm trying to obtain from the XPCOM jsm, but the two scripts cant access it.
const {Cc, Ci, Cu, Cr} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const xpcom_generateQI = XPCOMUtils.generateQI;
Cu.import(self.data.url("IOUtil.js"));
Cu.import(self.data.url("ChannelReplacement.js"));
gives me xpcom_generateQI is not defined.
How do I access a function which is defined in main.js?
Issues
Don't use Cu.import for local SDK modules. Don't write JS code modules for SDK add-ons, the SDK uses CommonJS-style modules together with the require() facility which also comes with proper cleanup for free, which cannot be said for JS code modules and Cu.import (you'd need to properly Cu.unload everything and likely kill some references yourself).
That https-everywhere stuff are neither JS code modules nor SDK modules, but uses the subscript loader. Either convert it to SDK code modules, or use the subscript loader yourself.
It is OK to import built-in JS Code modules in different scopes/modules. There is not actually a need to make available xpcom_generateQI from main (although it can be done; well, get to that).
To be future proof, you should bind your xpcom_generateQI shortcut properly, as in XPCOMUtils.generateQI.bind(XPCOMUtils). Otherwise, if the implementation changes and requires a proper this, your stuff will break.
To export something from any CommonJS module, you need to put it into the exports module. See the first link.
To import something, use require() (first link again).
Be aware of circular references, where Module A imports Module B imports Module A. Right now this kinda works (but only kinda, because some stuff might not be available from Module A when Module B imports it like this, as Module A is not fully loaded). Better avoid it.
Example 1 (circular)
So here is a example with circular require (main imports modules imports main)
main.js
function someFunction() {
console.log("some function called");
}
exports.someFunction = someFunction;
var mod = require("./module");
mod.anotherFunction();
module.js
const { someFunction } = require("./main");
exports.anotherFunction = function() {
someFunction();
}
Now, because of circular references this is a fragile construct. If works right now, but when modules get more complex or the SDK changes, it might break... Better put someFunction into a third module.
Example 2 (avoiding circular imports)
main.js
var mod = require("./module");
mod.anotherFunction();
// Or call someFunction directly
var { someFunction } = require("./utils");
someFunction();
module.js
const { someFunction } = require("./utils");
exports.anotherFunction = function() {
someFunction();
}
utils.js
function someFunction() {
console.log("some function called");
}
exports.someFunction = someFunction;
There are no circles anymore. If you wanted to reuse xpcom_generateQI, you'd put it as a property of exports in utils.js (in this example) and later require it with require("./utils").
https-everywhere
The https-everywhere stuff needs to be either converted or loaded using the subscript loader. I would recommend against the subscript loader, because in all likelihood the verbatim https-everywhere code does not clean up after itself. I'd actually also recommend against just converting it by throwing some stuff in (exports.xzy = ...). This code is not meant to be run in the SDK. Better create your own implementation and just borrow ideas from https-everywhere where needed.

How to include anonymous functions into RequireJS dependencies?

I am starting to use RequireJS now and I was already able to add my project dependencies but I still cannot add a jQuery anonymous function yet.
For example, with my normal_file.js I do something like:
normal_file.js:
define(['dependency1'], function(Dependency) {
var Test1 = ...;
return Test1;
});
Bu from a file that has no module, like the example below, I don't know how to encapsulate it:
lib_file.js:
(function ($) {
// Do stuff...
})(window.jQuery);
the lib_file was not made by me and I'm not sure on how it really works, but I would gess it is an anonymous auto-executed function, is that so?.
Anyway, my goal is to use both files in my main code, like below:
main.js:
requirejs.config({
baseUrl:'/static/editorial/js/',
paths: {
jquery: 'third_party/jquery-1.10.2',
react: 'third_party/react-with-addons'
}
});
var dependencies = [
'third_party/react-with-addons',
'third_party/jquery-1.10.2',
'build/utils/normal_file,
'third_party/lib_file
];
require(dependencies, function(React, $, Test1, ??) {
// do my stuff
});
How should I encapsulate that anonymous function in order to add it as a dependency to my main file?
From the RequireJS docs:
Ideally the scripts you load will be modules that are defined by
calling define(). However, you may need to use some traditional/legacy
"browser globals" scripts that do not express their dependencies via
define(). For those, you can use the shim config. To properly express
their dependencies.
Read this: http://requirejs.org/docs/api.html#config-shim
It has a really good explanation of what you have to do, and gives a nice example.
Basically, you just need to set up a shim config for lib_file.js so Require knows to load the right dependencies before giving you access to that script.

Categories