Is the Immediately-Invoked Function Expression (IIFE) pattern really necessary when writing userscripts?

In general, no; the IIFE pattern is seldom useful for wrapping a whole userscript (see edge cases below). That's a throwback to many years ago when some engines (briefly) did not wrap scripts by default.

In fact, if you include the obsolete @unwrap directive, the script engines will all now ignore it.

Here are some reasons to use the IIFE pattern:

  • It is currently the only way to enforce strict mode in Violentmonkey (only) for the whole script. But this is an oversight for that one engine only and hopefully will be rectified soonish.
  • It can squelch a harmless Parsing error: 'return' outside of function warning if you use BOTH: (1) A script-wide return and (2) an external LINTer.
    Some old Greasemonkey versions would also warn about this, while still working perfectly.
  • (I thought there was a 3rd edge case. But got interrupted and can't remember what it was.)

Consider this test script:

// ==UserScript==
// @name     _Scope and Strict-Mode Demo script
// @match    https://stackoverflow.com/*
// @unwrap
// @grant    none
// ==/UserScript==
/* eslint-disable no-multi-spaces, curly */
'use strict';

if (location.pathname.includes("/users/") ) {
    console.log ("Terminating script early.");
    return;  // In external LINTers, this will cause a harmless warning.
}

var cantSeeMeInConsole      = "neener neener";
window.canSomestimesSeeMe   = "Howdy";

console.log (`In Strict mode: ${bInStrictMode() }; \`cantSeeMeInConsole\`: ${cantSeeMeInConsole}`);

function bInStrictMode () {
    var inStrict = false;
    var dummyObj = {};
    Object.defineProperty (dummyObj, 'foo', {value: "bar", writable: false } );

    try { dummyObj.foo = "fee"; }
    catch (e) { inStrict = true; }
    return inStrict;
}
  • Run on Firefox and Chrome.
  • Safari and Opera should give same results.
  • Microsoft Edge probably gives same results. (But I don't care much if it doesn't.)
  • Run using Tampermonkey, Violentmonkey, and Greasemonkey 4.

Script scoping:

In all cases, the userscript is scoped/wrapped. The page can't see code, nor variables like cantSeeMeInConsole.
Beware that script page conflicts can still occur in @grant none mode.

Script sandboxing:

Additional isolations apply, depending on: (a) the userscript engine, (b) the browser, and (c) the @grant mode.
For example, using Greasemonkey, or changing the grant mode kills the page's ability to see canSomestimesSeeMe.

Strict mode:

  • In Tampermonkey and Greasemonkey, placing 'use strict'; up top like that switches the whole userscript into strict mode.
  • It's somewhat of a bug that this doesn't happen in Violentmonkey.
  • Additionally, in Tampermonkey's advanced options, you can set "strict mode" to [Default/Always/Disabled] for all scripts.

In a related note, if the script does not use @run-at settings, there is no point in using $(document).ready() or its shorthand.