Any time that you want to deal with nested elements, rather than regular expressions, the right tool for the job is a context-free grammar. A CFG has all of the power of a regular grammar—it itself is a superset of a regular grammar—but you can also define symbols and production rules on how symbols are combined into larger strings. CFG are more difficult than regular expressions to wrap your head around but also more powerful.
PegJS is the best JavaScript tool that I've encountered so far to work with CFG parsers.
I created a runnable solution on RunKit using your example: runkit.com/mattstrom/pegjs-example. I threw it together quickly so it might not cover every scenario you're looking for. The code along with the grammar looks like this:
const pegjs = require("pegjs");
const str = `
{{
somestuff
{{
somemorestuff
}}
}}
{{
}}
`;
const grammar = `
{
class Tag {
constructor(inner) {
this.inner = inner;
}
}
}
start
= _ first:node _ second:node _ { return [first, second]; }
/ _ node:node _ { return node; }
node
= nested
/ tag
/ content
nested
= '{{' _ inner:tag _ '}}' { return new Tag(inner); }
/ '{{' left:content _ inner:tag _ '}}' { return [left, new Tag(inner)]; }
/ '{{' inner:tag _ right:content _ '}}' { return [new Tag(inner), right]; }
tag
= '{{' _ inner:content _ '}}' { return new Tag(inner); }
content
= first:token __ rest:content { return first + ' ' + rest; }
/ first:token { return first; }
token
= chars:char* { return chars.join(''); }
char = [A-z0-9_]
_ "optional whitespace"
= [ \\t\\r\\n]*
__ "mandatory whitespace"
= [ \\t\\r\\n]+
`;
const parser = pegjs.generate(grammar);
parser.parse(str);