Webpack output two builds, one for ES6, one for IE11 - javascript

I have a webpack configuration of ES5/ES6 modules that works fairly well. Until recently it also ran through TargetsPlugin to generate a IE11-compatible build.
I say "until recently" because while experimenting, I noticed that dropping the babel transpiling to older language specs, we instantly shed 300KB from our bundles. If we turned this on, 90-point-something percent of our users would get a leaner, faster, better experience.
But I'm contractually obliged to support IE11. Can I do that separately?
What I want is a ES6-ish build (what we have) and a companion IE11 build. It's not difficult to target these browsers programmatically when it comes to switching out what version they see... But how do I get webpack to do it?
If it makes any difference, I don't need the IE11 version to have any fancy features. It can be one 1MB amorphous blob, or fully chunked out. However it comes, as long as it works.

Okay, turns out webpack can handle multiple configs at the same time. Where you would normally do module.exports = { ... config ... }, you can actually return an array of config objects.
So I went for something like this:
var realConfig = {
...
}
var ie11 = _.cloneDeep(real)
ie11.output.filename = 'js/ie11.[name].js'
ie11.plugins.push(
new TargetsPlugin({ browsers: ["IE >= 11"] })
)
module.exports = [ realConfig, ie11 ]
And that gives me an IE-compatible version of everything. In my templates I just detect "MSIE" in the user agent and feed that out. Probably not the most reliable but build should also work on modern browsers so I'm not concerned.

Related

Check to see if dynamic import is a module in JavaScript/TypeScript

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)

Package a node app for the browser using webpack

So this may be naive, but can I run node only style apps in the browser? I've see tuts for frontend stuff. I've seen tuts for backend stuff. I want to run https://github.com/lapwinglabs/x-ray in a browser and do something quick and dirty like (this code may not be perfect)
$(document).ready(function() {
var phantom = require('x-ray-phantom');
var Xray = require('x-ray');
var x = Xray()
.driver(phantom());
x('http://google.com', 'body')(function(err, str) {
$( "body" ).replaceWith( "str" );
});
});
To do a whole site pass though without an iframe. The purpose is so I can re-write CSS on internal company assets in sort of a global fashion. I'll be able to just pass a url and the page will be displayed. If I have custom css for it, then that will be active. I still gotta work out auth, but im not worried about it at this exact moment.
I've used x-ray with express in the past and it worked like a charm. In this context though I've taken a liking to Laravel and would like to stand up my apps in that.
The latest Laravel has "mix", a web pack front end. It works great for frontend assets. If I try to webpack x-ray though, I run into a flurry of issues like:
Module not found: Error: Can't resolve yadayadayada
So you can set an export like this:
module.exports.node = {
fs: 'empty',
net: 'empty',
tls: 'empty'
};
While that fixes module errors, I feel like I need those things. and in the browser I get:
Uncaught Error: Cannot find module "child_process"
I have a feeling things like this are not supposed to work, but I'm hoping I'm mistaken.
The simple answer is: you can't use Node built-in modules like fs or net, and therefore modules that use them, in the browser.
You can empty them out as you did with the node option, but the modules that depend on them use them for a reason and you would break them, unless you know exactly that the part, that uses the built-in modules, will not end up in your application, or at least you're not using it.
In your case, first it complained about fs, which clearly doesn't make sense in the browser, because you don't have access to the file system. And then it complains about child_process, so somewhere in the module it spawns processes, but you can't do that in the browser either, and emptying that out will very likely break the module's functionality, so you'd need to look for another module, that can be used in the browser.

How to check for version of illustrator that script is running on, and branch script based off that?

I have quite a problem as my team works on different versions of Adobe Illustrator, or have multiple versions of the software installed.
Is there a way to check for version of the adobe app that is running the script?
Especially knowing if it is 32 or 64 bit?
I need to properly define #target and BridgeTalk.target so the script runs in the current opened application. (Script is running directly from script file)
I can't seem to find any solid documentation on that topic.
Does anyone had similar issue and found and solutions or workarounds?
(Updating all the adobe software to single version is out of question sadly)
you can find the app version by calling app.version
$.writeln(app.version)
But there seems to be no way to find out if it is 32 or 64 bit
maybe the Extendscript helper object can give you some more infos. e.g.
$.writeln($.os)
This will check for sure what is the version of the app, and also if it's the 32 or 64 bit(not the os):
$.writeln(app.version); //writes the app version
$.writeln((app.path.fsName.indexOf('Program Files (x86)') > -1)?'32 bit':'64 bit'); //writes the bit version of the app
This code will work for any app that you want to check.
The only problem I can think of it's if the app is install somewhere else then Program Files or Program Files (x86). In this case you will have to use another ways.
#fabiantheblind
Using your tips i devised a piece of code that seems to be doing the trick (But it lacks elegance :P)
switch(app.version.split(".")[0])
{
case "16":
//32 bit versions run in emulated enviorment, so the $.os returns string
//containing 'emulation' substring. Not entierly sure it is reliable :P
var string = String($.os);
if(string.indexOf("emulation") > -1)
{
$.writeln("32 bit code here");
}
else
{
$.writeln("64 bit code here");
}
break;
default:
break;
}

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.

Intl.js polyfill/shim with webpack?

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.

Categories