132

I need something like heredoc in JavaScript. Do you have any ideas for this? I need cross-browser functionality.

I found this:

heredoc = '\
<div>\
    <ul>\
        <li><a href="#zzz">zzz</a></li>\
    </ul>\
</div>';

I think it will work for me. :)

Michael Papile
  • 6,836
  • 30
  • 30
VeroLom
  • 3,856
  • 9
  • 34
  • 48
  • 6
    Duplicate of http://stackoverflow.com/questions/805107/multiline-strings-in-javascript which has some more detailed answers. – Chadwick May 11 '11 at 22:01
  • 4
    Having to add "\" makes me frown every time. Imho, not having normal syntax for multiline strings is completely unjustified. And they could have added it at any given version, but they did not. – Lex Podgorny Mar 07 '14 at 05:07
  • 1
    Possible duplicate of [Creating multiline strings in JavaScript](https://stackoverflow.com/questions/805107/creating-multiline-strings-in-javascript) – brasofilo Aug 06 '18 at 04:34
  • unfortunately JavaScript does not support anything like heredoc. – subhashis May 18 '21 at 08:36

14 Answers14

117

Try ES6 String Template, you can do something like

var hereDoc = `
This
is
a
Multiple
Line
String
`.trim()


hereDoc == 'This\nis\na\nMultiple\nLine\nString'

=> true

You can use this great feature even in older browsers with TypeScript

Doktor J
  • 1,058
  • 1
  • 14
  • 33
mko
  • 21,334
  • 49
  • 130
  • 191
  • 5
    This is applicable if you just wanted multi-line strings. However, since you can't really change the symbol that encloses your string, it's not really heredoc. – Peeyush Kushwaha Apr 23 '16 at 09:14
  • 3
    Just as a note, the original question was asked 5 years ago before ES6 was available. This is the best practice moving forward since performance is better as well. – Henry Tseng Aug 18 '16 at 17:22
  • 4
    @HenryTseng, so are you suggesting that the answers to this question should be tailored to ancient technologies that existed 5 years ago? If the question is still open, then it deserve to take advantage of new technologies as they as created over time. That way, new users having the same issue can find "non-archaeological" information here. – asiby Oct 09 '18 at 18:26
  • 2
    No, I was making a comment as to why that solution wasn't more prominent earlier. It seems definitely like the supported way moving forward if there aren't any compatibility issues. – Henry Tseng Oct 09 '18 at 22:29
64

No, unfortunately JavaScript does not support anything like heredoc.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
40

How about this:

function MyHereDoc(){
/*HERE
<div>
   <p>
      This is written in the HEREDOC, notice the multilines :D.
   </p>
   <p>
      HERE
   </p>
   <p>
      And Here
   </p>
</div>
HERE*/
    var here = "HERE";
    var reobj = new RegExp("/\\*"+here+"\\n[\\s\\S]*?\\n"+here+"\\*/", "m");
    str = reobj.exec(MyHereDoc).toString();
    str = str.replace(new RegExp("/\\*"+here+"\\n",'m'),'').toString();
    return str.replace(new RegExp("\\n"+here+"\\*/",'m'),'').toString();
}

//Usage 
document.write(MyHereDoc());

Just replace "/*HERE" and "HERE*/" with word of choice.

Kalecser
  • 1,143
  • 12
  • 15
Zv_oDD
  • 1,838
  • 1
  • 18
  • 26
  • 4
    do all browsers/engines return the comments in the Function.toString()? that's very clever – gcb Jun 18 '13 at 00:11
  • 4
    Won't work if you have a closing comment `*/` in your heredoc. – Narigo Oct 10 '14 at 10:04
  • 2
    I would recommend using Nate Ferrero's answer, as his is a more refined and optimised example. Mine uses 3 separate regEx calls and is more of proof of concept. – Zv_oDD Oct 12 '14 at 23:42
  • 2
    Very clever... but you can't guarantee it will work in the future. It is too implementation-dependent to be a good practice. – Pierre-Olivier Vares Jun 12 '19 at 14:24
36

Building on Zv_oDD's answer, I created a similar function for easier reuse.

Warning: This is a non-standard feature of many JS interpreters, and will probably be removed at some point, but as I'm building a script to be only used in Chrome, I am using it! Do not ever rely on this for client-facing websites!

// Multiline Function String - Nate Ferrero - Public Domain
function heredoc(fn) {
  return fn.toString().match(/\/\*\s*([\s\S]*?)\s*\*\//m)[1];
};

Use:

var txt = heredoc(function () {/*
A test of horrible
Multi-line strings!
*/});

Returns:

"A test of horrible
Multi-line strings!"

Notes:

  1. Text is trimmed on both ends, so any extra whitespace on either end is OK.

Edits:

2/2/2014 - changed to not mess with the Function prototype at all and use the name heredoc instead.

5/26/2017 - updated whitespace to reflect modern coding standards.

Nate Ferrero
  • 1,408
  • 14
  • 15
  • 1
    I would use hereDoc() as my function name, but this code worked fine loading my 40k line log dump into a variable in Chrome's console – Omn Jul 09 '13 at 20:25
  • Why would you create an instance of a function and access the deprecated property __ proto __? Why not just do Function.prototype.str = function () { ... }? – John Kurlak Jan 25 '14 at 21:33
  • @JohnKurlak that is even better! I don't think I was aware that was possible when I wrote the answer. – Nate Ferrero Feb 02 '14 at 12:26
  • 2
    @NateFerrero - Awesome answer, thanks! Added an extension of my own as a separate answer. – Andrew Cheong Feb 14 '14 at 20:59
  • On my Android, nexus 4, running 5.0.1, this no longer works on Chrome. For some reason, it is deleting whitespace and comments. I can't figure out if this is a setting, but it is definitely on the client side. Any ideas for a workaround? – MLU Dec 29 '14 at 16:16
  • I asked this question here: http://stackoverflow.com/questions/27722549/android-5-0-1-chrome-is-deleting-javascript-whitespace-and-comments-is-there/27724149#27724149, and got some help, and answered it. This is a new Android feature, but there is a workaround using Cache-Control – MLU Dec 31 '14 at 17:15
  • Doesn't work if is part of your string. I was looking for a way for others to insert tags which will be inserted dynamically, without them having to mangle their strings. Still run into the problem that if your string includes , even if it's in a JS comment, it will close the original block and the rest of your code becomes text on the page. – Ken Jan 27 '16 at 17:47
  • @Ken - I would suggest having them start and end script tags with something like and then replacing that afterward. – Nate Ferrero Jan 29 '16 at 01:54
20

Depending on what flavour of JS/JS engine you're running (SpiderMonkey, AS3) you can simply write inline XML, into which you can place text on multiple lines, like heredoc:

var xml = <xml>
    Here 
    is 
    some 
    multiline 
    text!
</xml>

console.log(xml.toXMLString())
console.log(xml.toString()) // just gets the content
Dave Stewart
  • 2,324
  • 2
  • 22
  • 24
17

ES6 Template Strings has heredoc feature.

You can declare strings enclosed by back-tick (` `) and can be expanded through multiple lines.

var str = `This is my template string...
and is working across lines`;

You can also include expressions inside Template Strings. These are indicated by the Dollar sign and curly braces (${expression}).

var js = "Java Script";
var des = `Template strings can now be used in ${js} with lot of additional features`;

console.log(des); //"Template strings can now be used in Java Script with lot of additional features"

There are in fact more features such as Tagged Temple Strings and Raw Strings in it. Please find the documentation at

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
Charlie
  • 22,886
  • 11
  • 59
  • 90
14

ES5 and earlier versions

(function(){/**
some random
multi line
text here
**/}).toString().slice(15,-5);

ES6 and later versions

`some random
multi line
text here`

result

some random
multi line
text here
Ga1der
  • 171
  • 1
  • 5
9

I feel bad writing a separate answer for merely an extension to @NateFerrero's answer, but I don't feel editing his answer is appropriate either, so please upvote @NateFerrero if this answer was useful to you.

tl;dr—For those who wish to use block comments inside their heredoc...

I mainly needed Javascript heredocs to store a block of CSS, e.g.

var css = heredoc(function() {/*
    /**
     * Nuke rounded corners.
     */
    body div {
        border-top-left-radius: 0 !important;
        border-top-right-radius: 0 !important;
        border-bottom-right-radius: 0 !important;
        border-bottom-left-radius: 0 !important;
    }
*/});

As you can see however, I like to comment my CSS, and unfortunately (as hinted by the syntax highlighting) the first */ ends the overall comment, breaking the heredoc.


For this specific purpose (CSS), my workaround was to add

.replace(/(\/\*[\s\S]*?\*) \//g, '$1/')

to the chain inside @NateFerrero's heredoc; in complete form:

function heredoc (f) {
    return f.toString().match(/\/\*\s*([\s\S]*?)\s*\*\//m)[1].replace(/(\/\*[\s\S]*?\*) \//g, '$1/');
};

and use it by adding a space between the * and / for "inner" block comments, like so:

var css = heredoc(function() {/*
    /**
     * Nuke rounded corners.
     * /
    body div {
        border-top-left-radius: 0 !important;
        border-top-right-radius: 0 !important;
        border-bottom-right-radius: 0 !important;
        border-bottom-left-radius: 0 !important;
    }
*/});

The replace simply finds /* ... * / and removes the space to make /* ... */, thereby preserving the heredoc until called.


You can of course remove the comments altogether using

.replace(/\/\*[\s\S]*?\* \//g, '')

You can also support // comments if you add them to the chain:

.replace(/^\s*\/\/.*$/mg, '')

Also, you can do something other than the single space between * and /, like a -:

    /**
     * Nuke rounded corners.
     *-/

if you just update the regex appropriately:

.replace(/(\/\*[\s\S]*?\*)-\//g, '$1/')
                          ^

Or maybe you'd like an arbitrary amount of whitespace instead of a single space?

.replace(/(\/\*[\s\S]*?\*)\s+\//g, '$1/')
                          ^^^
Community
  • 1
  • 1
Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • 2
    Cool! That was a known limitation with my method, I like it :) – Nate Ferrero Mar 21 '14 at 17:46
  • Not to necro but this can be made way simpler by just having custom beginning and ending comments: `/*!$## [/*your code*/] ##$!*/`, and just having the regex check for that – Kithraya Sep 03 '20 at 04:51
8

You could use CoffeeScript, a language that compiles down to JavaScript. The code compiles one-to-one into the equivalent JS, and there is no interpretation at runtime.

And of course, it has heredocs :)

Andre Silva
  • 4,782
  • 9
  • 52
  • 65
Jakob
  • 24,154
  • 8
  • 46
  • 57
  • 24
    The correct answer is no. CoffeeScript and EJS could be used as suggestions. – alvincrespo Aug 09 '11 at 22:13
  • 4
    CoffeeScript is the correct answer to most JS issues I've encountered. If you write more than a trivial amount of JS (and you value your time and energy) you owe it to yourself to use it. – Brandon Nov 19 '11 at 13:15
  • 5
    I think this is a good answer as it provides an easy way to circumvent the absence of heredoc in javascript. Saved me a lot of time. – Stofke Dec 11 '12 at 22:24
  • 3
    If you just want a chunk of js but don't want to deal with actually writing it: http://coffeescript.org/ and use the "Try Coffeescript" button. – jcollum Mar 14 '13 at 21:59
  • 1
    This is more of an answer than the highest rated one, which is basically just... "no". I hate those kind of answers. – Matt Fletcher May 27 '14 at 10:19
  • I love Coffeescript but this isn't really an answer because my framework doesn't compile automatically and Heroku doesn't have `coffee` installed. Instead, I visit http://js2.coffee and paste the heredoc in the Coffeescript side and copy the generated Javascript. – Chloe Sep 27 '15 at 22:40
  • 1
    Using another language (and having to compile it) (and relying on dependencies) to make another language work is NOT an answer to the problem. Infact, it's a burden. – NiCk Newman Oct 28 '15 at 05:25
5

As others have said, ES6 template strings give you most of what traditional heredocs provide.

If you want to go a step further and use a tagged template string, theredoc is a nice utility function that lets you do this:

if (yourCodeIsIndented) {
  console.log(theredoc`
    Theredoc will strip the
    same amount of indentation
    from each line.

      You can still indent
      further if you want.

    It will also chop off the
    whitespace-only first and
    last lines.
  `)
}
Neall
  • 26,428
  • 5
  • 49
  • 48
  • 1
    With the availability of ES6 template strings, this is by far the best answer. Very concise and intuitive. Also doesn't introduce some new syntax. – Renaat De Muynck Jan 20 '22 at 20:15
1

You can use Sweet.js Macros to add it like so, as created by Tim Disney in this post

Note that this approach uses backticks as the string delimiters instead:

let str = macro {
    case {_ $template } => {
        var temp = #{$template}[0];
        var tempString = temp.token.value.raw;
        letstx $newTemp = [makeValue(tempString, #{here})];
        return #{$newTemp}
    }
}

str `foo bar baz`
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
0

If you have some html and jQuery at hand and the string is valid HTML, this may be useful:

<div id="heredoc"><!--heredoc content
with multiple lines, even 'quotes' or "double quotes",
beware not to leave any tag open--></div>
<script>
var str = (function() {
   var div = jQuery('#heredoc');
   var str = div.html();
   str = str.replace(/^<\!--/, "").toString();
   str = str.replace(/-->$/, "").toString();
   return str;
})();
</script>

If text have comments "<!-- -->" in between, it works as well, but a part of the text may be visible. Here's the fiddle: https://jsfiddle.net/hr6ar152/1/

Max Oriola
  • 1,296
  • 12
  • 8
0
// js heredoc - http://stackoverflow.com/a/32915549/466363
// a function with comment with eval-able string, use it just like regular string

function extractFuncCommentString(func,comments) {
  var matches = func.toString().match(/function\s*\(\)\s*\{\s*\/\*\!?\s*([\s\S]+?)\s*\*\/\s*\}/);
  if (!matches) return undefined;
  var str=matches[1];

   // i have made few flavors of comment removal add yours if you need something special, copy replacement lines from examples below, mix them
  if(comments===1 )
  {
   // keep comments, in order to keep comments  you need to convert /**/ to / * * / to be able to put them inside /**/ like /*    / * * /    */
   return (
    str
   .replace(/\/\s\*([\s\S]*?)\*\s\//g,"/*$1*/") //       change   / * text * /  to   /* text */ 
   )
  }
  else if(comments===2)
  {
   // keep comments and replace singleline comment to multiline comment
   return (
    str
   .replace(/\/\s\*([\s\S]*?)\*\s\//g,"/*$1*/") //       change   / * text * /  to   /* text */ 
   .replace(/\/\/(.*)/g,"/*$1*/")          //           change   //abc to  /*abc*/
   )
  }
  else if(comments===3)
  {
   // remove comments
   return (
      str
      .replace(/\/\s\*([\s\S]*?)\*\s\//g,"") //       match / * abc * /
      .replace(/\/\/(.*)/g,"")             // match //abc
     )
  }
  else if(comments===4)
  {
   // remove comments and trim and replace new lines with escape codes
   return (
      str
      .replace(/\/\s\*([\s\S]*?)\*\s\//g,"") //       match / * abc * /
      .replace(/\/\/(.*)/g,"")             // match //abc
      .trim() // after removing comments trim and:
      .replace(/\n/g,'\\n').replace(/\r/g,'\\r') // replace new lines with escape codes. allows further eval() of the string, you put in the comment function: a quoted text but with new lines
     )
  }
  else if(comments===5)
  {
   // keep comments comments and replace strings, might not suit when there are spaces or comments before and after quotes 
   // no comments allowed before quotes of the string
   return (
      str
      .replace(/\/\s\*([\s\S]*?)\*\s\//g,"/*$1*/") //       change   / * text * /  to   /* text */
      .replace(/\/\/(.*)/g,"/*$1*/")          //           change   //abc to  /*abc*/
      .trim() // trim space around quotes to not escape it and:
      .replace(/\n/g,'\\n').replace(/\r/g,'\\r') // replace new lines with escape codes. allows further eval() of the string, you put in the comment function: a quoted text but with new lines
     )
  }
  else 
  return str
}

example

var week=true,b=123;
var q = eval(extractFuncCommentString(function(){/*!

// this is a comment     


'select 

/ * this
is a multiline 
comment * /

 a
,b  // this is a comment  
,c
from `table`
where b='+b+' and monthweek="'+(week?'w':'m')+'" 
//+' where  a=124
order by a asc
'
*/},4));

with cache: - make a simple template function, and save the function:(second time works fast)

var myfunction_sql1;
function myfunction(week,a){


    if(!myfunction_sql1) eval('myfunction_sql1=function(week,a){return ('+extractFuncCommentString(function(){/*!
'select 

/ * this
is a multiline 
comment * /

 a
,b  // this is a comment  
,c
from `table`
where b='+b+' and monthweek="'+(week?'w':'m')+'" 
//+' where  a=124
order by a asc

'*/},4)+')}');
    q=myfunction_sql1(week,a);
    console.log(q)
}
myfunction(true,1234)
Shimon Doodkin
  • 4,310
  • 34
  • 37
  • 1
    Quite different results in FF and Chrome. – Dave Newton Oct 02 '15 at 21:09
  • what's different? Just tested in Chrome and FF and I get exactly same results. Except that in Chrome there are no newlines in Chrome's Console If you just type the var name. but the variable is the same. It's possible to print with newlines with console.log() – Shimon Doodkin Oct 03 '15 at 11:02
0

I'm posting this version as it avoids the use of regex for something so trivial.

IMHO regex is an obfuscation that was created as a practical joke amongst perl developers. the rest of the community took them seriously and we now pay the price, decades later. don't use regex, except for backward compatabilty with legacy code. there is no excuse these days to write code that is not immediately human readable and understandable. regex violates this principle on every level.

I've also added a way to add the result to the current page, not that this was asked for.

function pretty_css () {
/*
    pre { color: blue; }

*/
}
function css_src (css_fn) {
   var css = css_fn.toString();
   css = css.substr(css.indexOf("/*")+2);
   return css.substr(0,css.lastIndexOf("*/")).trim();
}

function addCss(rule) {
  let css = document.createElement('style');
  css.type = 'text/css';
  if (css.styleSheet) css.styleSheet.cssText = rule; // Support for IE
  else css.appendChild(document.createTextNode(rule)); // Support for the rest
  document.getElementsByTagName("head")[0].appendChild(css);
}

addCss(css_src(pretty_css));

document.querySelector("pre").innerHTML=css_src(pretty_css);
<pre></pre>
unsynchronized
  • 4,828
  • 2
  • 31
  • 43