I am using Require.js in a project. If you’re defining a module, the variable define is added to the global namespace.
Recently I had a variable name conflict [but curiously, only in Safari] because there was a HTML tag with an ID of define, and according to the HTML5 specification:
The Window interface supports named properties. The supported property names at any moment consist of the following:
- …
- …
- the value of the id content attribute of any HTML element in the active document with a non-empty id content attribute.
I think it’s a terrible idea to pollute the JS global namespace with every HTML element that has an ID, but my opinion alone is not enough to change the way a browser behaves.
In my particular instance, I’ve found this answer that will wrap Require.js; but in a more general sense: is there a way to guard against this detail in the specification? Is there a technique for preventing this GNS pollution (without the obvious answer: “Don’t use IDs”)? Could the conflict at least be made obvious to the developer (which might not be me), through the JS console?
This behavior of HTML5 has no effect on RequireJS itself. I've run the following in Firefox and Chrome. As expected, the define symbol in the global space was RequireJS's define function.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/xhtml; charset=utf-8"/>
</head>
<body>
<p id="define">Paragraph.</p>
<button id="redefine">Redefine</button>
<script type="text/javascript">
console.log("before requirejs:", typeof window.define);
</script>
<script type="text/javascript" src="js/require.js"></script>
<script>
require.config({
baseUrl: "./js",
paths: {
jquery: 'jquery-1.10.2'
},
});
console.log("requirejs loaded:", typeof window.define);
require(["jquery"], function ($) {
$("#redefine").click(function () {
$("#define").replaceWith("<p id='define'>New paragraph.</p>");
console.log("redefined:", typeof window.define);
require(["foo"], function (foo) {
console.log(foo);
});
});
});
</script>
</body>
</html>
I use typeof to avoid getting a dump of the entire source of the function. It is enough for our purposes. The "Redefine" button is to simulate a change to the DOM tree which one might think would overwrite the value of window.define. (The module foo is a trival module that only returns the value "foo".) The require inside the click handler shows that RequireJS is still functional. Using the code above, the output to the console shows that:
Even if originally window.define was the DOM element, RequireJS will redefine define to its own value.
Once RequireJS has defined define, changes to the DOM tree do not change it.
So RequireJS does not need to be protected from it.
But what about code that insists on using window.define to access an element with an id of "define"? Such code is defective and should be fixed. This SO question covers the issue very well. Even if RequireJS were made to avoid touching the global define, the code which relies on window.define being a DOM element would still be brittle.
Is there a technique for preventing this GNS pollution (without the obvious answer: “Don’t use IDs”)?
Other than the obvious answer, no. (I take "use something else than IDs" to be equivalent to "don't use IDs".)
Could the conflict at least be made obvious to the developer (which might not be me), through the JS console?
A JS virtual machine could probably detect the conflict but I don't know of any that does this.
It's a bug in how Safari handles global variables in JavaScript. No other browser behaves the say Safari does.
This repository contains a set of files illustrating the problem. The index.html file will show a failure on the console if loaded in Safari. It loads fine in Firefox, Chrome, IE and Opera. The test.html file pinpoints the issue. All browsers except Safari treat a variable declaration in JavaScript like var foo at the top level scope as shadowing any variable of the same name that was created due to an element having an id attribute set. Loaded in any browser except Safari, the console.log that outputs start of redefine: will show undefined undefined for the values of the variables. In Safari, it shows p#define p#define.
An issue has been filed with RequireJS to suggest a change that would take care of this case.
If the suggested change does not go through, then the workaround is to execute define = undefined before loading RequireJS. There is no need to do anything more.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With