I'm using Requirejs to load the JavaScript in our web app. The issues is that I'm getting an undefined object being passed to a module which, when used in other modules, is instantiated perfectly fine.
OK, here's the setup. My main.js file which requirejs runs on startup:
require.config({
baseUrl: "/scripts",
paths: {
demographics: "Demographics/demographics",
complaints: "Complaints/complaints",
}
});
require(["templates", "demographics", "complaints", "crossDomain"], function (templates, demographics, complaints) {
"use strict";
console.log("0");
console.log(demographics === undefined);
demographics.View.display();
});
A lot of the config has been stripped to just the core files in this problem.
Here's Demographics.js:
define(["ko", "templates", "complaints", "globals", "underscore"], function (ko, templates, complaints, globals) {
// Stuff removed.
return {
View: view
};
});
and Complaints.js
define([
"demographics",
"ko",
"templates",
"complaints",
"visualeffects",
"globals",
"webservice",
"underscore",
"typewatcher",
"imagesloaded"],
function (demographics, ko, templates, complaints, visualeffects, globals, webservice) {
"use strict";
console.log("1");
console.log(demographics === undefined);
return {
View: view
};
});
The problem is this - in Complaints.js the demographics parameter passed via the define config is undefined. The console log out tells me that "demographics === undefined" is true.
However, when the main.js file executes, the demographics parameter passed to it is not undefined, it is, as expected, an instantiated object.
Now I'm stuck since I can't see why in complaints.js that demographics variable is undefined. Can anyone spot what I'm missing please?
You have a circular dependency. The demographics module depends on complaints and complaints depends on demographics. As per the documentation:
If you define a circular dependency (a needs b and b needs a), then in this case when b's module function is called, it will get an undefined value for a.
The solution, if you can't remove the circular dependency, is to asynchronously require one of the two modules within the other on demand (say when the view is instantiated instead of when the module that defines the view is executed). Again, the docs cover this topic fairly well.
Another case is when you accidentally type require instead of define when defining a module, took me some time to notice this.
I had a similar problem. In my case, when defining a module, I'd written:
define('some_dependency', ...
instead of
define(['some_dependency'], ...
Another possible reason is implementing the module's interface (AMD, CommonJS), but forgetting to return anything. I just did that.
I just encountered another reason:
define(function () {
return {};
}()); // <-- notice the '()' typo.
This "typo" causes no JS errors for this one and can make it confusing to figure out especially in a complicated application with many potential circular dependancies.
The reason, of course, is the "typo" is valid JS that simply calls the function you define thereby passing its result to define() rather then the function as intended.
One other possible reason that may look obvious in hindsight is an error in your module's code. In my case, I was trying to get an attribute from an undefined variable. The error is logged in the console, but for some reason I was not seeing it/mistaking it for the undefined module error.
Related
I have 2 separate js bundles that load on the same html page, so webpack.config.js:
entry: {
preload: "./preload.tsx",
main: "./index.tsx"
}
GOAL: preload.js produces a Promise of a cache table that needs to be accessed by main.js.
But they need to be separate bundles, so import won't work which will lead to execution of preload.js twice. I've tried to:
add a global variable (couldn't figure out where the actual definition should go so Uncaught ReferenceError: global_variable is not defined
add an extra property to window obejct (couldn't figure out how to make it work with TypeScript 3.9. Tried pretty much everything here and more. The only thing that worked was (window as any) which looks evil
I don't want hacky workarounds like using LocalStorage or DOM manipulation.
I couldn't find a decent solution as of TypeScript 3.9. Any suggestions will be appreciated.
Like #Elias mentioned in a comment I think a prefer solution is a dynamic import one, but to answer the Question, You can do one of two things:
1. Use ProvidePlugin:
plugins: [
new webpack.ProvidePlugin({
globalVar: path.resolve(path.join(__dirname, 'src/GlobalVarModule'))
});
]
GlobalBarModule.ts:
var globalVar = "my global var value"
module.exports = globalVar;
And simple usage:
Index.js:
console.log(globalVar);
Whenever the identifier (globalVar) is encountered as free variable in a module, the module is loaded automatically and the identifier is filled with the exports of the loaded module (or property in order to support named exports).
-From Docs.
2. Use Externals to let webpack know your are consuming a variable from the window:
externals: {
globalVar: path.resolve(path.join(__dirname, 'src/GlobalVarModule'))
}
Somewhere early in your code:
globalVar = "my global var value";
Usage:
import globalVar from 'globalVar';
console.log(globalVar);
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.
I am currently building a base for AngularJS in combination with RequireJS and so far I got everything working. there's just a little thing that I do not understand at this point. I have a file which creates the angular module, when this module is created it requires a controller and assigns it to the module. The strange thing though, the controller needs the module as dependency while in the module's file the module has not been returned yet because the require statement is executed before the return statement. This somehow seems to work but it has a bad smell to it.
Module file:
// Home is defined here and can later be used in controllers (and Services)
define('home', ['require', 'angular'], function(require, angular) {
var homeModule = angular.module('AngularBase.home', ['AngularBase.core']);
homeModule.config(['$controllerProvider', '$provide', '$compileProvider', function($controllerProvider, $provide, $compileProvider) {
// We need this in order to support lazy loading
homeModule.controller = $controllerProvider.register;
homeModule.factory = $provide.factory;
// And more, not relevant at this moment
}]);
// It loads the controller that depends on this module here
require(['modules/home/controllers/homeController'], function() {
// Dependencies loaded
});
// Yet in my mind controllers that need this module can only use it when the following return statement is called.
return homeModule;
});
Controller File:
// As you can see this controller depends on home while home hasn't returned its module yet
// Yet it seems to work just fine
define(['home'], function(home) {
home.controller('homeController', ['$scope', 'homeService', function($scope, homeService) {
$scope.title = 'Home controller';
}]);
});
I assume that it is not a good approach to do it like this and therefore I need some suggestions on how to make this happen in a clean way. I thought about grabbing the AngularBase.home module via angular.module('AngularBase.home') in the controller file and defining my controller on this. This however no longer allows me to insert a mockModule for testing in this controller via RequireJS's map function.
map: {
'*' : {
'home' : 'mock-module'
}
}
Any suggestions on how to refactor this into a more clean solution?
I have found the solution to my problem. In the end it seems to be just fine to do it the way I am currently doing it. When a file is called and has a define statement in it it will wait until all dependencies are available until the function is executed. This means that the controller will actually wait for the module to finish initializing before calling its function to register itself.
The way I am doing it above is just fine.
Source: http://www.slideshare.net/iivanoo/handlebars-and-requirejs (slides 11 till 24)
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.
I have a set of angular $resource defined in a module called 'App.API' in a single file which I cannot touch because it is generated. (With loopback-angular, a tool to generate angular $resource from server side model definitions)
Let's take the Product dependency as en example, later in the app, I want to override its prototype, like this :
module('App.NewModule', ['App.API']).run(['Product', function(Product) {
Product.prototype.getTitle = function() {
return 'Product name is ' + this.name;
};
// From now on I can use p.getTitle() on every Product $resource
});
It works.
The thing is, I have many different files, each containing modules, and I am experiencing a dependency injection issue : I can access the getTitle function inside NewModule, but not inside other modules.
Question : How can I override a dependency object prototype and make it available to other modules ?
I tried to define the prototype functions in this way instead, thinking that Product prototype would be modified. Maybe not early enough :
module('App.API').run(['Product', function(Product) {
Product.prototype.getTitle = function() {
return 'Product name is ' + this.name;
};
});
It does not work : using getTitle in another module (using App.API/Product as a dependency) on a Product instance still throws a undefined is not a function error, even while Product object is correctly injected.
Actually, I just messed up the dependency definitions / orders.
I have three files :
app.js for module App (dependant on module App.API)
api.js for module App.API
product.js containing Product prototype
As stated in the question, I was doing :
// in product.js
module('App.API').run(['Product', function(Product) { ... }]);
// in app.js
var appModule = module('App', ['App.API']);
But the App.API module was defined in another file, which is a bit messed up because you never know for sure which one will load first, unless dealing with in in the js loader and loosing parallel downloads.
So I explicitly specified the modules and dependencies, at the expense of adding more dependency to declare in my app (but it works and is more stable) :
// in product.js
module('ApiProduct', ['App.API']).run(['Product', function(Product) { ... }]);
// in app.js
var appModule = module('App', ['App.API', 'ApiProduct']);
Note : In my first attempt, I defined the prototype in a new module in a .config() block, but it was not working, maybe because App.API services were not loaded yet. With .run() it works and my getTitle prototype is available everywhere I need Product provider.