diff --git a/compiler/generate/visitors/RawMustacheTag.js b/compiler/generate/visitors/RawMustacheTag.js
new file mode 100644
index 0000000000..1b0b39e302
--- /dev/null
+++ b/compiler/generate/visitors/RawMustacheTag.js
@@ -0,0 +1,47 @@
+import deindent from '../utils/deindent.js';
+
+export default {
+ enter ( generator, node ) {
+ const name = generator.current.counter( 'raw' );
+
+ generator.addSourcemapLocations( node.expression );
+ const { snippet } = generator.contextualise( node.expression );
+
+ // we would have used comments here, but the `insertAdjacentHTML` api only
+ // exists for `Element`s.
+ const before = `${name}_before`;
+ generator.addElement( before, `document.createElement( 'noscript' )`, true );
+ const after = `${name}_after`;
+ generator.addElement( after, `document.createElement( 'noscript' )`, true );
+
+ const isToplevel = generator.current.localElementDepth === 0;
+
+ const mountStatement = deindent`
+ ${before}.insertAdjacentHTML( 'afterend', ${snippet} );
+ `;
+ const detachStatement = deindent`
+ while ( ${before}.nextSibling && ${before}.nextSibling !== ${after} ) {
+ ${before}.parentNode.removeChild( ${before}.nextSibling );
+ }
+ `;
+
+ if ( isToplevel ) {
+ generator.current.mountStatements.push(mountStatement);
+ } else {
+ generator.current.initStatements.push(mountStatement);
+ }
+
+ generator.current.updateStatements.push( deindent`
+ ${detachStatement}
+ ${mountStatement}
+ ` );
+
+ if ( isToplevel ) {
+ const { detachStatements } = generator.current;
+ // we need `before` and `after` to still be in the DOM when running the
+ // detach code, so splice in the detach code *before* detaching
+ // `before`/`after`.
+ detachStatements.splice( detachStatements.length - 2, 0, detachStatement);
+ }
+ }
+};
diff --git a/compiler/generate/visitors/index.js b/compiler/generate/visitors/index.js
index 70ba9e3298..8e125e5d8b 100644
--- a/compiler/generate/visitors/index.js
+++ b/compiler/generate/visitors/index.js
@@ -3,6 +3,7 @@ import EachBlock from './EachBlock.js';
import Element from './Element.js';
import IfBlock from './IfBlock.js';
import MustacheTag from './MustacheTag.js';
+import RawMustacheTag from './RawMustacheTag.js';
import Text from './Text.js';
import YieldTag from './YieldTag.js';
@@ -12,6 +13,7 @@ export default {
Element,
IfBlock,
MustacheTag,
+ RawMustacheTag,
Text,
YieldTag
};
diff --git a/test/compiler/raw-mustaches/_config.js b/test/compiler/raw-mustaches/_config.js
new file mode 100644
index 0000000000..850a169e02
--- /dev/null
+++ b/test/compiler/raw-mustaches/_config.js
@@ -0,0 +1,16 @@
+const ns = '';
+export default {
+ data: {
+ raw: 'raw html!!!\\o/'
+ },
+ html: `before${ns}raw html!!!\\o/${ns}after`,
+
+ test ( assert, component, target ) {
+ component.set({ raw: '' });
+ assert.equal( target.innerHTML, `before${ns}${ns}after` );
+ component.set({ raw: 'how about unclosed elements?' });
+ assert.equal( target.innerHTML, `before${ns}how about unclosed elements?${ns}after` );
+ component.teardown();
+ assert.equal( target.innerHTML, '' );
+ }
+};
diff --git a/test/compiler/raw-mustaches/main.html b/test/compiler/raw-mustaches/main.html
new file mode 100644
index 0000000000..1a733843df
--- /dev/null
+++ b/test/compiler/raw-mustaches/main.html
@@ -0,0 +1 @@
+before{{{raw}}}after