I'm using React-Intl with webpack and I need the Intl shim to support Safari and IE, but I don't want to load it for browsers that already support the Intl spec.
The polyfill is pretty huge (900kb), how can I ensure it only gets loaded in browsers that don't support it already?
There are a few things that you need to do.
Make sure to require intl/Intl which loads the core library and not all of the associated countries. This will reduce the size of the library from from around 900kb to around 150kb.
Use webpack's require.ensure or require([]) functions to dynamically require Intl.js only when needed. This will create a separate bundle for just the Intl.js file, which will be loaded on an as-needed basis.
lib/shim.js
// shim for Intl needs to be loaded dynamically
// so we callback when we're done to represent
// some kind of "shimReady" event
module.exports = function(callback) {
if (!window.Intl) {
require(['intl/Intl'], function(Intl) {
window.Intl = Intl;
callback();
});
} else {
setTimeout(callback, 0); // force async
}
};
app.js
var shimReady = require('lib/shim');
shimReady(startApp);
Note: You may also need access to country-specific information and properties. For my basic needs I didn't actually require this data, but if you do be sure to read the section on Intl.js website loading locale data.
An simple (non-webpack) approach would be to do something like this in your HTML:
<script>
if (!window.Intl) { document.write('<script src="/js/Intl.min.js"><\/script>'); }
</script>
Though document.write is not considered a best practice, this would provide a blocking approach to solving the problem, resulting in considerably less code in your the application. This solution does not use webpack, but may be a suitable solution for many people in this situation.
Related
I am working on a ScriptManager class for a project that was created many years ago. The original code read scripts from a database, and these scripts are different depending on the customer and installation (the application is a desktop app that uses Chrome Embedded Framework to display web pages). The code would read custom JavaScript code and eval() it, which of course is highly undesirable.
I am replacing this code with a ScriptManager class that can support dynamically inserted code, and the ScriptManager is capable of loading code as a module using JavaScript's dynamic import() command, or loading code as pure script by creating a script tag dynamically in the document.
My problem is that there are many different possible custom code blocks in the database, and not all are modules; some will be pure script until those can be converted to modules at a later time. My code can handle this as described above, but I need a way to detect if the script code from the database is a module, so I can either use the import() command or insert a script tag if it is not.
I am solving this temporarily by making sure any module script code has "export const isModule = true", and checking this after calling import(). This works, but any code that is pure script still results in a module variable, but with no exports in it. If possible I don't want the other developers to have to remember to add isModule = true to any modules they develop in the future.
Is there a way to check that code is a module without having to do complex analysis of the code to check if there are exports in it? Since import() still returns an object and throws no errors if there are no exports, I don't know how to detect this.
UPDATE: Here are some examples of how this is intended to work:
// Not real code, pretend that function gets the string of the script.
let code = getSomeCodeFromTheDatabase();
// Save the code for later loading.
let filename = 'some-filename.js';
saveCodeToFile(code, filename);
// Attempt to dynamically import the script as a module.
let module = await import(filename);
// If it is NOT a module, load it instead as a script tag.
// This is where I need to be able to detect if the code is
// a module or pure script.
if (!module.isModule) {
let scriptTag = document.createElement('script');
scriptTag.src = filename;
document.head.appendChild(script);
}
So if you look here How can I tell if a particular module is a CommonJS module or an ES6 module? you will see I answered a similar question.
So the thing is Sarah, modules are defined by the way that they resolve. Module-types resolving differently is what, not only makes them incompatible with one another, but it is also why we name them differently. Originally Transpillers like Babel & TypeScript were invented because of differences in ECMA-262 Specifications, and the desire to support people who didn't have the updated specifications, as well as supporting the newer features for those who did.
Today transpilers are still being used, conceptually, much the same way. They still help us maintain a single code base while supporting older specifications, and features in newer specifications, at the same time, but the also have the added benefit of being able to generate multiple different builds from a single code base. They use this to support different module types. In node.js, the primary module-type is CJS, but the future lies in ESM modules, so package maintainers have opted to dual build the projects. The use the TypeScript Compiler (aka Transpiler) to emit a CJS build, and an ESM build.
Now this is where things get complicated, because you cannot tell just by looking at a module if it CJS or ESM in this situation, **you absolutely have to inspect its code, and check if it has more than one tsconfig.json file (because it would need at-least 2 to maintain a bi-modular build (which are becoming increasingly common with the turn of each day)
My Suggestion to You:
Use well documented packages. Good packages should be documented well, and those packages should state in their README.md document, what type of package/module they are, and if the package supports more than one module type. If in doubt you can either come and ask here, or better yet, you can ask the maintainer by creating an issue, asking them to add that information to their README.md document.
You can check that there are no export after import. In chrome import() added empty default for non module.
function isNotModule(module) {
return (!Object.keys(module).length) || (!!module.default && typeof module.default === 'object' && !Object.keys(module.default).length)
}
import('./test.js')
.then((module) => {
console.log('./test.js',isNotModule(module))
})
May be it's better to check source code via regex to check if it contains export
something like this
const reg = new RegExp('([^\w]|^)export((\s+)\w|(\s*{))')
reg.test(source)
I've been reading about es6 module loaders and I just don't quite understand how it works and am hoping someone can enlighten me.
In the practical workflows link above they have an example like this
System.import('app/app').then(function(app) {
// app is now the Module object with exports as getters
});
No problem with that - I get it. But then I see stuff like this
var $ = require('jquery');
and get really confused. What happens if at the time of this call jquery has not yet been transferred to the browser? Does the thread just spin? Does the browser parse your script behind-the-scenes and reform it into a callback like RequireJs does? Is what it does configurable? Are there specific limitations?
Can someone give me a rundown?
The ES6 Module Loader will fetch the source, determine dependencies, and wait until those dependencies have loaded before executing the module. So by the time the require executes, the dependency is already sitting there waiting to be executed.
When loading CommonJS through an ES6 module loader, we rely on statically parsing out the require statements from the source, and only executing the source once those requires have loaded.
In this way we can support CommonJS in the browser loaded dynamically. Circular references are treated identically to the way they are handled in Node.
The regular expressions parsing out the require are actually pretty reliable and quick, while taking into account comments and surrounding tokens. See https://github.com/systemjs/systemjs/blob/master/lib/extension-cjs.js#L10 for the one used by SystemJS.
There is one remaining limitation with this approach and that is dynamic and conditional CommonJS requires like if (condition) require('some' + 'name') aren't detected properly. This is a necessary cost though to make CommonJS behave as a fully asynchronous module format in the browser.
I've came across this situation a few times before and always found a tricky way to make it work but I'd like to know if there's a best practice for that:
Sometimes you have to use several JavaScript SDKs on a page example: Google jsapi and jQuery.
Google calls this function when the SDK is ready to be used:
google.setOnLoadCallback(MyFunction);
jQuery does everything in this callback:
document.ready();
What if I want to manipulate the dom using jQuery AFTER Google's callback.
What's the best way to tell the browser: "Wait for jQuery AND Google to be ready and do the stuff..."
Is it best to have the jQuery callback nested inside Google callback? Or the other way round? I'm a bit confused.
You can do this in different ways, one of which is to use a global variable;
var googleLoaded = false;
google.setOnLoadCallback(function(){
googleLoaded = true;
mainCallback();
});
document.ready(mainCallback);
function mainCallback(){
if(googleLoaded){
...
...
}
}
As a general solution, you can do as given below
var jsLibsToLoad = 5;
jsLib1.onLoad(mainCallback);
jsLib2.onLoad(mainCallback);
jsLib3.onLoad(mainCallback);
jsLib4.onLoad(mainCallback);
jsLib5.onLoad(mainCallback);
function mainCallback(){
if(!(--jsLibsToLoad)){
...
...
}
}
I would recommend you to use some AMD tool like RequireJS. RequireJS is a JavaScript file and module loader, and it is really good with dependancy management. In your particular case (using of some traditional/legacy "browser globals" scripts) you should additionally use Shim Config.
At the start using AMD is a little bit tricky, but it gave a great benefits when you get used to it. It will improve the speed and quality of your code.
I'm currently building an app where the frontend is doing a lot of the heavy lifting.
To keep everything neat and organised I'd like to use requirejs. However, to use require.js to its' full extent all the modules I use should be AMD compliant.
Which means that every time a module that I use is updated I need to either wait for an AMD-compliant version to appear or make one myself ( Which I currently don't know how to ).
This is a real turnoff.
Looking at this https://github.com/jrburke/backbone/blob/optamd/backbone.js it seems to me that making a module like Backbone AMD-compliant isn't as straightforward as wrapping the plugin into a generic function.
Is there a more or less straightforward way of making a module AMD-compliant?
Well his version is pretty bullet-proofed so it'll run under a variety of circumstances. Since you know the environment you are running in and what is available/what isn't then you can make some assumptions that will let you do something that is much more straightforward.
Check out this gist where I make bacbkonejs an AMD module assuming jQuery, underscore and define are in the global scope and I don't need commonjs support:
https://gist.github.com/2762813
I just add
define(function() {
var obj = {};
obj._ = window._;
obj.jQuery = window.jQuery;
to the top and
.call(obj);
return obj.Backbone;
});
to the bottom.
Thanks to #SimonSmith for bringing UseJS to my attention. UseJS is an AMD loader plugin that will allow you to load non-amd formatted modules without modifying them. I haven't used use myself yet but it looks promising: https://github.com/tbranyen/use.js/
UPDATE
RequireJS 2.0 now directly supports the functionality you are looking for via shim configs: https://github.com/jrburke/requirejs/wiki/Upgrading-to-RequireJS-2.0#wiki-shim
I’m currently testing various asynchronous-resource-loaders to see which one I want to use. At the moment Curl is throwing a ‘Promise already completed’ error…and their documentation says ‘this should never happen’.
I “suspect” I must to use a ‘define’ within each file being loaded (hope not). Further, their documentation says Curl can work with non-AMD javascript files. But...I am new to AMD and since Curl is far-faster than the other ones I'm testing...I am willing to put some time into understanding this better.
Lastly...
Even though FireBug shows the errors...all the files STILL LOAD asynchronously! But, BECAUSE there are errors...the 'then' portion of the code never gets called.
So My Questions Are:
Do I have to update all the JavaScript file-objects to be contained in a 'define'? (...hope not)
Can you see any other problem syntactically?
The Head’s Code Looks Like This:
<script type="text/javascript">
///<summary>Configuration</summary>
curl = { baseUrl: 'Includes/JavaScript/' };
</script>
<script src="Loaders/Curl/curl.js" type="text/javascript"></script>
<script type="text/javascript">
function onSuccess() {
}
function onError(ex) {
//alert(ex);
}
require(["MooTools/Core/mootools-1.2.2-core-jm",
"MooTools/mGeneral",
"jQuery/Core/jquery-1.3.2",
"jQuery/Core/jquery.tools.min",
"jQuery/ThirdPartyPlugIns/jquery.tmpl"])
.then(onSuccess, onError);
//require(["jQuery/TopUp/top_up-min"], null);
require(["jQuery/ThirdPartyPlugIns/jquery.dimensions",
"jQuery/ThirdPartyPlugIns/jQuery.Color.Animations",
"jQuery/ThirdPartyPlugIns/jquery.corners.min",
"jQuery/ThirdPartyPlugIns/jquery.tipsy",
"jQuery/ThirdPartyPlugIns/jquery.numberformatter-1.1.0",
"jQuery/ThirdPartyPlugIns/jquery.tipsy"]);
require(["general",
"WindowCenter",
"ThirdPartyPlugin/KeyBoardCapture",
"ThirdPartyPlugin/bsn.AutoSuggest_2.1.3",
"ee/8Ball",
"ee/EE"]);
</script>
Again...I'm sure it is caused by inexperience with AMD-styled code, but I still need help...so any is appreciated.
Typically, you should wrap your modules in a define() or append a define() at the end of the file if those modules have no dependencies. It seems, though, that those modules all depend on jQuery, if not other modules/files.
Since you're not using standard AMD require()/define() protocol, AMD is not really helping you with these modules. Unless you plan to write your own modules using define(), then you could use just about any async loader.
That said, there is a way to make curl.js work with non-AMD modules/files. Use the js! plugin. Here's your first block of files translated to use the js! plugin (note I also added the ".js" ext back on which I like to do with non-modules):
// we load the core files first, then get the next block that depends on them
curl([
"js!MooTools/Core/mootools-1.2.2-core-jm.js",
"js!jQuery/Core/jquery-1.3.2.js"
]).next([
"js!MooTools/mGeneral.js",
"js!jQuery/Core/jquery.tools.min.js",
"js!jQuery/ThirdPartyPlugIns/jquery.tmpl.js"
]).then(onSuccess, onError);
If any of those files within each array depend on each other, you can also use the !order suffix on them to ensure they wait for other files before executing/evaluating (be sure you're setting proper cache headers, though). Actually, the !order suffix is the fastest method as long as there are no caching issues (mobile browsers add some additional constraints on file size).
Were there any other error messages? Specifically, I would expect curl.js to throw at least one error besides just "Promise not completed".
Also, please check the Net tab (Firebug) or the Network tab (Chrome) to check that curl.js is looking in the correct location for the modules.
FWIW, I am planning to remove the alias require --> curl. The reason is that the global require function is non-standard (and explicitly not standardized in the AMD proposal). I suggest you use curl() instead of require().
curl.js also allows it's top-level api to be aliased explicitly via the "apiName" config param if you really, really want to use the name "require". :)
<script>curl = { apiName: "require" }; </script>
<script src="path/to/curl.js"></script>
<script>require(["some/modules"]).then(success, failure);</script>
More FWIW: the standard require is typically only needed in a module and can be requested by including it as a dependency:
define(["require"], function (require) {
require(["another/module"], function (another) {
// use another module here
});
});
-- John
If you're only doing normal javascript file loading (not modules), as it appears, i would encourage you to check out LABjs (http://labjs.com). LABjs focuses on being the most performance optimized loading solution (to the exclusion of some other features like module/dependency style stuff).
In fact, LABjs 2.0a (https://github.com/getify/LABjs/blob/master/next/LAB.src.js), which will be fully released in the next few days, is really exceptionally fast (even more than 1.2) at loading general scripts in parallel. I encourage you to give it a try, unless (as John eludes to above) you plan to go to module syntax... then stick with Curl or RequireJS.