Conditionally require code in commonJS AMD module - javascript

I'm trying to write a cross-browser extension for Firefox and Chrome. Firefox uses the commonJS specification and Chrome just lumps everything into the global namespace like a webpage.
In order to be able to write reusable code, I'm trying to use requireJS to lood code in the Chrome extension, that way I can write commonJS modules and have them work in both environments.
I'm running into a problem when I need to conditionally require modules. For example, Firefox provides access to a simple-storage module which you should use to access the local storage. In chrome, I need to use the localStorage API that they provide. So, I've been trying to do this:
// storage.js
define(function(require, exports, module){
var store;
try {
// This module will only be available in the FF extension.
store = require('simple-storage').storage
} catch(error) {
// If it's not available, we must be in Chrome and we
// should use the localStorage object.
store = localStorage
}
// Use the store object down here.
});
However this doesn't seem to work. When I try to load the Chrome extension I get the following error:
Is there a better way to require modules with a fallback?

There are two caveats here:
1) Detect if chrome is running
// detects webKit (chrome, safari, etc..)
var isChrome = 'webKitTransform' in document.documentElement.style
2) Requirejs will parse the define() function and search for require('module') calls. To prevent the error on chrome you have write the require in some way that when requirejs parses the function body it does not recognize the call as a module dependency:
if (isChrome)
// use localStorage
else {
// set the module name in a var does the trick,
// so requirejs will not try to load this module on chrome.
var ffStorageModule = 'simple-storage';
return require(ffStorageModule);
}

Related

How to detect if script is running in a module?

I have a program with a fallback for browsers that do not support web workers. The reason I'm doing this is because I want to deploy it to cocoon's canvas+, which does not support web workers. The non web worker code can access the script 'mountainNoise.js' fine, since it uses ES6 import and export, however when the worker imports the script via "importScripts()" it throws an error since export declarations can only appear in modules.
Is there a way to detect if the code is running in a module? My main concern is duplicating the file to support both versions, but this is ridiculous since it's taking much more memory for one line of code.
The thing is that this is ground to language semantics and duplication can
Be the answer. I can suggest try catch. I know its not a good solution but maybe better than duplication.
I'm not will sure if this will work in a web worker, but it's a common pattern in js libraries that want to support both node.js and browsers.
if (
typeof module !== 'undefined' &&
typeof module.exports !== 'undefined'
) {
module.exports = MyLib
console.log('module.exports exists, import/export should be supported')
} else {
window.MyLib = MyLib
console.log('in a browser')
}

error handling in knockout spa using amd and requirejs

So I have a fairly large Single-Page-Application based on Steve Sandersons SPA template. custom bindingHandlers, validation etc all working fine.
We require the use of the XMLHttpRequests, Session and Local storage as well as some other libraries that don't have universal support (jquery 2.0 etc). I have a core module which handles identity and exposes a few services and both of these are reliant on these features. This is loaded when the user first visits the page.
I have an issue with older browsers where either a) they don't support some of the modules being loaded so they throw an error before hitting the onload function, or b) it hits the onload function but they don't support the browser features so I need to raise an exception myself and handle it in some manner.
Example: IE8 throws an 'Object doesn't support property or method 'addEventListener'' (jquery error) and IE9 Doesn't support everything I need so I throw a custom error.
define('core', ['jquery', 'browser'], function($, browser) {
if(!browser.hasFullSupport) {
throw new Error('Update your browser');
}
// aload of gubbins that requires sessionStorage etc
return {
identity: identity,
serviceA: serviceA,
serviceB: serviceB
}
}, function(err) {
// doesn't catch the ie8 error here
});
I've tried setting a window.onerror but it works differently across browser versions and in some I can't capture the error message properly, I just get a 'Script Error'.
I know I can add an error event handler for the module definitions but it doesn't capture the errors i'm experiencing.
I've seen people use
requirejs.onError = function(err) {
// something here
}
but i'm using the requirejs gulp bundler and I can't see where this object would exposed (if it is at all)
var require = {
baseUrl: ".",
paths: {
"modernizr": "bower_modules/modernizr/modernizr",
"browser": "app/browser-detect",
"crossroads": "bower_modules/crossroads/dist/crossroads.min",
"hasher": "bower_modules/hasher/dist/js/hasher.min",
"jquery": "bower_modules/jquery/dist/jquery.min",
....
// gulpfile
var requireJsRuntimeConfig = vm.runInNewContext(fs.readFileSync('src/app/require.config.js') + '; require;');
requireJsOptimizerConfig = merge(requireJsRuntimeConfig, {
out: 'scripts.js',
baseUrl: './src',
name: 'app/startup',
paths: {
requireLib: 'bower_modules/requirejs/require'
},
include: [
'requireLib',
'components/nav-bar/nav-bar',
.....
gulp.task('js', function () {
return rjs(requireJsOptimizerConfig)
.pipe(uglify())
.pipe(gulp.dest('./dist/'));
});
There's loads of components/pages and each could throw an error, but it's this initial core component loading that i'm keen to sort out. I'd like to distinguish between errors (that may or may not be caused by the browser being too old) and between me detecting that they're too old before hand, is there an elegant way to achieve this using this template model?
I'd rather not deviate too far from the template as i'm not too familiar with requirejs / gulp etc but i'm open to suggestions.
You contradict yourself:
We require the use of the XMLHttpRequests, Session and Local storage as well as some other libraries that don't have universal support (jquery 2.0 etc)
But:
IE8 throws an 'Object doesn't support property or method 'addEventListener'' (jquery error) and IE9 Doesn't support everything I need so I throw a custom error.
You can't use libraries and HTML 5 features for newer browsers and expect older browsers to still work. Libraries that do not support legacy browsers like IE 8 do not fail gracefully so you cannot handle the failure very well. And even if you found a workaround to somehow circumvent the errors, you would still need to test them all in old browsers periodically which beats the intent to support only newer browsers.
So I recommend you two approaches:
Either decide not to support olders browsers at all and test the app only in newest modern browsers. You could use modern versions of libraries that dropped support of old browsers and not worry about old browsers at all. It depends on the state of your user base. Or:
Downgrade libraries to versions that support your minimal required browsers and continue developing the app with them. Test the app and fix all errors that occur in old browsers. Use polyfills that enable new HTML 5 features in old browsers via emulation.

Unloading lazy getters in boostrap addons?

At the top of my bootstrap.js I define a bunch of lazyGetters, instead of a JSM:
const myServices = {};
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyGetter(myServices, 'sss', function(){ return Cc['#mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService) });
I heard that you have to unload imported modules. But what about "modules" that you create for lazyGetters? How would I unload those? Would I do a delete myServices?
If I do a delete on myServices which is a global, does that mean I should delete all my global variables on unload?
I read here: Forgetting to unload JavaScript modules in restartless add-ons
Forgetting to unload JavaScript modules in restartless add-ons
Another common cause of leaks is forgetting to unload JavaScript code
modules in bootstrapped add-ons. These leaks cannot be detected by
looking at about:compartments or about:memory because such modules
live within the main System compartment.
Also, when your add-on gets updated and re-enabled, the previous
module version that is still loaded will be used, which might break
your add-on entirely.
The following example shows how to unload your modules again
(bootstrap.js):
Components.utils.import("resource://gre/modules/Services.jsm");
function startup(data, reason) {
// This assumes your add-on did register some chrome
Components.utils.import("chrome://myaddon/content/mymodule.jsm");
}
function shutdown(data, reason) {
if (reason != APP_SHUTDOWN) {
// No need to do regular clean up when the application is closed
// unless you need to break circular references that might negatively
// impact the shutdown process.
return;
}
// Your add-on needs to unload all modules it ships and imported!
Components.utils.unload("chrome://myaddon/content/mymodule.jsm");
}
Note: Modules not belonging to your add-on — such as Services.jsm — should not be unloaded by your add-on, as this might cause errors and/or performance regressions and will actually increase the memoryusage.
The code you provided does not import a module, but defines a service getter, so it is fine.
(Aside: there is also a XPCOMUtils.defineLazyServiceGetter helper...)
However if you did something like this:
XPCOMUtils.defineLazyGetter(myServices, 'SomeSymbol', function() {
return Cu.import("chrome://myaddon/content/mymodule.jsm", {}).SomeSymbol;
});
then of course you'd need to Cu.unload() that module again. This is best done (IMO) by registering the module to unload as soon as it is loaded, so something like:
XPCOMUtils.defineLazyGetter(myServices, 'SomeSymbol', function() {
let rv = Cu.import("chrome://myaddon/content/mymodule.jsm", {}).SomeSymbol;
unload(function() {
Cu.unload("chrome://myaddon/content/mymodule.jsm");
});
return rv;
});
Other people just pro-actively unload all modules that could have been potentially imported or not. This is fine to, as Cu.unload()ing a module that wasn't imported just does nothing.
PS: You can still run into trouble when sticking something into another module, like.
XPCOMUtils.defineLazyServiceGetter(Services /* other module */,
'sss',
'#mozilla.org/content/style-sheet-service;1',
'nsIStyleSheetService');
In this example XPCOMUtils.jsm might still reference the strings passed as arguments from your code and hence leak your code. (Also, it is rather rude to stick stuff into modules that aren't your own and may create conflicts with other add-ons doing the same thing.)

Require js ruins code navigation

require.js states the way of defining objects inside modules with define([requiremens], object) as best way.
So every page, or other js file, will do require() call and receive modules as parameters.
This works pretty fine, each function/module has own namespace.
The problem is that I have:
// AJAX/Requests.js
define(['UI/Message'],function(Message){
var Requests={
checkResponse:function(response){
//1==ok
//0==error
//2==good message
//3==good message, but still stop
if(response.status==1){
return true;
}
else if(response.status==2){
Message.good(response.message);
return true;
}
else if(response.status==3){
Message.good(response.message);
return false;
}
else{
Message.bad(response.message);
return false;
}
}
};
return Requests;
});
Now the UI/Message is defined in the same way, and it returns object.
But when I edit file with requests, I can't navigate by code, so if I want to edit Message object, the only way is to go and open file the myself and to find function I need, rather than have the IDE jump there for me.
Is there some workaround for pycharm specifically or to require.js in common to solve this issue? When you have a lot of code it becomes a mess to navigate it, which is why I use an IDE in the first place!
And what worse: The editor never knows what functions objects have!
The one possible solution I can see is to not to use enclosed namespaces, and to declare global variable before the define() call, but in this case all objects shall be called like UI_Message, AJAX_Requests. In order to be sure, that I don't have some Message in two different locations....
And I am not sure, if require.js optimizer will use this correctly. Require.js documentation states very clear, to stay away from global variables.
It's a known issue, please star/vote.
From the issue description:
The dojo library switched to AMD's format define() for loading modules
instead of dojo.require(). Previously I was able to use Ctrl+B on
dojo.require('path.to.someJs') to jump to the declaration. This does
not work on the new format define(['path/to/someJs]', ...).
As PyCharm, WebStorm, PhpStorm and IntelliJ IDEA share the same JavaScript plug-in, this issue also applies to the product that you are using. You will continue to observe the described problem until this bug is fixed. Sorry for the inconvenience.
WebStorm (at least 6.0.2) supports code navigation with RequireJs if you're defining your modules with the CommonJs wrapper and use the exports and module arguments:
//foo.js
define(function(require, exports, module) {
//use exports to expose properties for code navigation in other modules
exports.bar = function() {}
});
Apparently, it works even if the module using it doesn't use the CommonJs wrapper format:
define(['./foo'], function(foo) {
foo.bar(); //code navigation works here
}
If the other IDEs use the same JavaScript plug-in as CrazyCoder said, it may work on their newer releases as well.

Is it possible to use requirejs when modules may have to be removed to conserve memory

We develop an application in an embedded environment. It is a high level computing environment with a complete webbrowser on top of a busybox Linux system. The only exception is that the system has a limited amount of system memory.
Our application is built in JavaScript and runs inside a Webkit based webbrowser and consists of a lot of javascript modules that are loaded in sequence (Which is not very efficient).
Some modules provide common functionality that is used by several modules. We are in the process of converting our current javascript loader with requirejs, but there is one specific need we have to address first.
Is it possible to unload a module when it has been loaded using requirejs? Assume that we dynamically loads a module using :
require(["somemodule.js"], function(m) { m.run(); } );
That works well for loading and running 'somemodule' and also resolving all dependencies for 'somemodule' and the requirejs framework will store a reference to 'somemodule' for future requests.
If we at some point need to reclaim memory, e.g to be able to load and run an infinite number of modules, we have to start removing some of them after some time. Is that possible with requirejs without altering the internal implementation?
Has anyone dealt with this kind of problem before? Most single page JS apps runs in a webbrowser on a desktop PC where memory usage usually is not a major concern.
RequireJS does not have a built-in unload feature, but it could be added perhaps as an additional part you could build into it. If you would like to have that feature, feel free to propose it in the mailing list or as a GitHub issue.
If you wanted to experiment to see if it helps your situation, what you need to do is the following:
1) Remove the defined module from the RequireJS module cache. If you are not using the multiversion support, you can do something like:
var context = require.s.contexts['_'];
delete context.defined[moduleName];
delete context.specified[moduleName];
delete context.loaded[moduleName];
2) Then you can try removing the script tag to see if that helps:
var scripts = document.getElementsByTagName('script');
for (var i = scripts.length - 1; i >= 0; i--) {
var script = scripts[i];
if (script.getAttribute('data-requiremodule') === moduleName) {
script.parentNode.removeChild(script);
break;
}
}
Note that the module may not be garbage collected if another module holds on to it via the closure function(){} that defines that other module. That other module would need to be removed too.
You can try to limit that impact by not passing in the module as a function argument, but just use require("somemodule") inside the function definition whenever you want to get a hold of dependent modules, and not holding on to that require return value for too long.
Also, in your example above, for modules that use require.def to define themselves, it should look like this (without the .js suffix):
require(["somemodule"], function(m) { m.run(); } );
Try this: require.undef(moduleName)

Categories