Repository: vuejs/vue
Pull request: fix #8041: templates inside v-pre should be rendered to HTML

close #8041

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [x] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

I don't believe this is breaking as content inside of `v-pre` is considered to be static. _However_ it does change the outputted static content in the case where a `<template>` tag is labeled with `v-pre` or inside of a `v-pre` element.

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [x] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**

I pushed a 'compiled' version of this to a different branch to show the working behavior on jsFiddle. The original fiddle from issue #8041: https://jsfiddle.net/zct418xa/1/
A fiddle with the updated behavior using vue.js compiled from this change: https://jsfiddle.net/f7nv9218/

Changed files:
filename: src/compiler/codegen/index.js
patch: @@ -18,6 +18,7 @@ export class CodegenState {
   maybeComponent: (el: ASTElement) => boolean;
   onceId: number;
   staticRenderFns: Array<string>;
+  pre: boolean;
 
   constructor (options: CompilerOptions) {
     this.options = options
@@ -29,6 +30,7 @@ export class CodegenState {
     this.maybeComponent = (el: ASTElement) => !(isReservedTag(el.tag) && !el.component)
     this.onceId = 0
     this.staticRenderFns = []
+    this.pre = false
   }
 }
 
@@ -58,7 +60,7 @@ export function genElement (el: ASTElement, state: CodegenState): string {
     return genFor(el, state)
   } else if (el.if && !el.ifProcessed) {
     return genIf(el, state)
-  } else if (el.tag === 'template' && !el.slotTarget) {
+  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
     return genChildren(el, state) || 'void 0'
   } else if (el.tag === 'slot') {
     return genSlot(el, state)
@@ -88,7 +90,15 @@ export function genElement (el: ASTElement, state: CodegenState): string {
 // hoist static sub-trees out
 function genStatic (el: ASTElement, state: CodegenState): string {
   el.staticProcessed = true
+  // Some elements (templates) need to behave differently inside of a v-pre
+  // node.  All pre nodes are static roots, so we can use this as a location to
+  // wrap a state change and reset it upon exiting the pre node.
+  const originalPreState = state.pre
+  if (el.pre) {
+    state.pre = el.pre
+  }
   state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
+  state.pre = originalPreState
   return `_m(${
     state.staticRenderFns.length - 1
   }${

filename: test/unit/modules/compiler/codegen.spec.js
patch: @@ -595,6 +595,18 @@ describe('codegen', () => {
     expect(res.render).toBe(generatedCode)
   })
 
+  // #8041
+  it('does not squash templates inside v-pre', () => {
+    const template = '<div v-pre><template><p>{{msg}}</p></template></div>'
+    const generatedCode = `with(this){return _m(0)}`
+    const renderFn = `with(this){return _c('div',{pre:true},[_c('template',[_c('p',[_v("{{msg}}")])])],2)}`
+    const ast = parse(template, baseOptions)
+    optimize(ast, baseOptions)
+    const res = generate(ast, baseOptions)
+    expect(res.render).toBe(generatedCode)
+    expect(res.staticRenderFns).toEqual([renderFn])
+  })
+
   it('not specified ast type', () => {
     const res = generate(null, baseOptions)
     expect(res.render).toBe(`with(this){return _c("div")}`)

filename: test/unit/modules/compiler/parser.spec.js
patch: @@ -250,6 +250,15 @@ describe('parser', () => {
     expect(ast.children[0].children[0].text).toBe('{{msg}}')
   })
 
+  it('v-pre directive should leave template in DOM', () => {
+    const ast = parse('<div v-pre id="message1"><template id="template1"><p>{{msg}}</p></template></div>', baseOptions)
+    expect(ast.pre).toBe(true)
+    expect(ast.attrs[0].name).toBe('id')
+    expect(ast.attrs[0].value).toBe('"message1"')
+    expect(ast.children[0].attrs[0].name).toBe('id')
+    expect(ast.children[0].attrs[0].value).toBe('"template1"')
+  })
+
   it('v-for directive basic syntax', () => {
     const ast = parse('<ul><li v-for="item in items"></li></ul>', baseOptions)
     const liAst = ast.children[0]