Why split the <script> tag when writing it with document.write()?
Asked Answered
L

5

284

Why do some sites (or advertisers that give clients javascript code) employ a technique of splitting the <script> and/or </script> tags up within document.write() calls?

I noticed that Amazon does this as well, for example:

<script type='text/javascript'>
  if (typeof window['jQuery'] == 'undefined') document.write('<scr'+'ipt type="text/javascript" src="http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js"></sc'+'ript>');
</script>
Lungfish answered 25/10, 2008 at 7:58 Comment(0)
Y
385

</script> has to be broken up because otherwise it would end the enclosing <script></script> block too early. Really it should be split between the < and the /, because a script block is supposed (according to SGML) to be terminated by any end-tag open (ETAGO) sequence (i.e. </):

Although the STYLE and SCRIPT elements use CDATA for their data model, for these elements, CDATA must be handled differently by user agents. Markup and entities must be treated as raw text and passed to the application as is. The first occurrence of the character sequence "</" (end-tag open delimiter) is treated as terminating the end of the element's content. In valid documents, this would be the end tag for the element.

However in practice browsers only end parsing a CDATA script block on an actual </script> close-tag.

In XHTML there is no such special handling for script blocks, so any < (or &) character inside them must be &escaped; like in any other element. However then browsers that are parsing XHTML as old-school HTML will get confused. There are workarounds involving CDATA blocks, but it's easiest simply to avoid using these characters unescaped. A better way of writing a script element from script that works on either type of parser would be:

<script type="text/javascript">
    document.write('\x3Cscript type="text/javascript" src="foo.js">\x3C/script>');
</script>
Yerkovich answered 25/10, 2008 at 8:33 Comment(9)
\/ is a valid escape sequence for /, so why not just use that instead of those string literal escapes for <? E.g. document.write('<script src=foo.js><\/script>');. Also, </script> is not the only character sequence that can close a <script> element. Some more info here: mathiasbynens.be/notes/etagoAmalbergas
@Mathias: <\/script> is fine in this case, but it would only work in HTML; in XHTML without extra CDATA section wrapping, it's still a well-formedness error. Also you can use \x3C in inline event handler attributes where < would also be invalid in both HTML and XHTML, so it has a wider applicability: if I were choosing one, easily-automated way to escape sensitive characters in JS string literals for all contexts, that's the one I'd go for.Yerkovich
In HTML, < can be used in inline event handler attributes. html5.validator.nu/… And you’re right about the XHTML compatibility of \x3C an sich, but since XHTML doesn’t support document.write (or innerHTML) anyway I don’t see how that’s relevant.Amalbergas
@MathiasBynens—document.write is irrelevant, it just happens to be the example. The OP could have used innerHTML, it's the about hiding hiding the </ character sequence from the markup parser, wherever it occurs. It's just that most parsers tolerate it inside a script element when strictly they shouldn't (but HTML parsers are very tolerant). You are correct though that <\/ suffices in all cases for HTML.Selfcentered
Who writes the web technology specs??, it seems not people who actually use the technologies in question.Rechabite
I don't think escaping the opening < is necessary.... document.write('<script src="foo.js">\x3C/script>') seems to be sufficient in all browsers back to IE6. (I left out the type attribute because it's not required in HTML5, nor is it enforced as required by any browser.)Lothaire
@MattBrowne It's not just browsers you need to worry about. I got bit by this using PHP's DOMDocument to process some HTML that contained some JS that built and inserted an <iframe>. The parser saw the opening tag and created a DOM node, but the tag wasn't complete, and the parser started escaping spaces in the rest of the JS code with %20!Onetoone
@MattBrowne - just for completeness sake, if the javascript: label in required in inline scripts in (older) IE if the first script on the page is not JavaScript (e.g. vbscript), then perhaps it is also required on script tags coming after a non-js scriptLupitalupo
Why not use template literals - document.write(`<script src="foo.js"></script>`)Tasman
B
39

Here's another variation I've used when wanting to generate a script tag inline (so it executes immediately) without needing any form of escapes:

<script>
    var script = document.createElement('script');
    script.src = '/path/to/script.js';
    document.write(script.outerHTML);
</script>

(Note: contrary to most examples on the net, I'm not setting type="text/javascript" on neither the enclosing tag, nor the generated one: there is no browser not having that as the default, and so it is redundant, but will not hurt either, if you disagree).

Bedmate answered 16/9, 2013 at 16:21 Comment(2)
Good point re: type. The default is defined as "text/javascript" as of HTML5, so it's a useless attribute. w3.org/html/wg/drafts/html/master/…Granddad
This one is better than the accepted answer because this variation can be actually minified. This 'x3C/script>' will become '</script>' after minification.Curia
E
21

I think is for prevent the browser's HTML parser from interpreting the <script>, and mainly the </script> as the closing tag of the actual script, however I don't think that using document.write is a excellent idea for evaluating script blocks, why don't use the DOM...

var newScript = document.createElement("script");
...
Elsi answered 25/10, 2008 at 8:8 Comment(1)
It is necessary to prevent the parser from closing the script-block prematurely ...Hephzipah
C
12

The </script> inside the Javascript string litteral is interpreted by the HTML parser as a closing tag, causing unexpected behaviour (see example on JSFiddle).

To avoid this, you can place your javascript between comments (this style of coding was common practice, back when Javascript was poorly supported among browsers). This would work (see example in JSFiddle):

<script type="text/javascript">
    <!--
    if (jQuery === undefined) {
        document.write('<script type="text/javascript" src="http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js"></script>');
    }
    // -->
</script>

...but to be honest, using document.write is not something I would consider best practice. Why not manipulating the DOM directly?

<script type="text/javascript">
    <!--
    if (jQuery === undefined) {
        var script = document.createElement('script');
        script.setAttribute('type', 'text/javascript');
        script.setAttribute('src', 'http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js');
        document.body.appendChild(script);
    }
    // -->
</script>
Corrianne answered 25/11, 2014 at 10:4 Comment(4)
If you want to use pure JS, you will want to still use document.write ;)Polypary
Sorry, I meant to write - this requires jQuery Library, right? When I used, document.body.append, it threw an error that document.body.append is not a function.Polypary
Sorry, my mistake: I wrote append instead of appendChild. Corrected the answer. Thank you for noticing!Corrianne
This looks so much more general than splitting up the string manually. E.g. splitting is not feasible if the string is inserted by a template engine.Hallucinosis
R
9

The solution Bobince posted works perfectly for me. I wanted to offer an alternative method as well for future visitors:

if (typeof(jQuery) == 'undefined') {
    (function() {
        var sct = document.createElement('script');
        sct.src = ('https:' == document.location.protocol ? 'https' : 'http') +
          '://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js';
        sct.type = 'text/javascript';
        sct.async = 'true';
        var domel = document.getElementsByTagName('script')[0];
        domel.parentNode.insertBefore(sct, domel);
    })();
}

In this example, I've included a conditional load for jQuery to demonstrate use case. Hope that's useful for someone!

Rattigan answered 4/12, 2013 at 12:55 Comment(4)
no need to detect protocol - protocol-less URI works just fine ('//foo.com/bar.js' etc)Unzip
no need to set async either. it's set on for all dynamically created script tags.Manille
Saved me! When using document.write(<script>) my site was showing a blank screen. This worked.Subcutaneous
@dmp except when running from file system where the protocol will be file://Lupitalupo

© 2022 - 2024 — McMap. All rights reserved.