Преглед изворни кода

feat: add `comments` option to allow preserving comments in template (#5951)

close #5392
wenlu.wang пре 8 година
родитељ
комит
e4da249ab8

+ 4 - 0
flow/compiler.js

@@ -21,6 +21,9 @@ declare type CompilerOptions = {
 
   // runtime user-configurable
   delimiters?: [string, string]; // template delimiters
+
+  // allow user kept comments
+  comments?: boolean
 };
 
 declare type CompiledResult = {
@@ -151,6 +154,7 @@ declare type ASTText = {
   type: 3;
   text: string;
   static?: boolean;
+  isComment?: boolean;
   // 2.4 ssr optimization
   ssrOptimizability?: number;
 };

+ 1 - 0
flow/options.js

@@ -68,6 +68,7 @@ declare type ComponentOptions = {
   name?: string;
   extends?: Class<Component> | Object;
   delimiters?: [string, string];
+  comments?: boolean;
 
   // private
   _isComponent?: true;

+ 6 - 0
src/compiler/codegen/index.js

@@ -423,6 +423,8 @@ function needsNormalization (el: ASTElement): boolean {
 function genNode (node: ASTNode, state: CodegenState): string {
   if (node.type === 1) {
     return genElement(node, state)
+  } if (node.type === 3 && node.isComment) {
+    return genComment(node)
   } else {
     return genText(node)
   }
@@ -435,6 +437,10 @@ export function genText (text: ASTText | ASTExpression): string {
   })`
 }
 
+export function genComment (comment: ASTText): string {
+  return `_e('${comment.text}')`
+}
+
 function genSlot (el: ASTElement, state: CodegenState): string {
   const slotName = el.slotName || '"default"'
   const children = genChildren(el, state)

+ 3 - 0
src/compiler/parser/html-parser.js

@@ -82,6 +82,9 @@ export function parseHTML (html, options) {
           const commentEnd = html.indexOf('-->')
 
           if (commentEnd >= 0) {
+            if (options.shouldKeepComment) {
+              options.comment(html.substring(4, commentEnd))
+            }
             advance(commentEnd + 3)
             continue
           }

+ 8 - 0
src/compiler/parser/index.js

@@ -90,6 +90,7 @@ export function parse (
     isUnaryTag: options.isUnaryTag,
     canBeLeftOpenTag: options.canBeLeftOpenTag,
     shouldDecodeNewlines: options.shouldDecodeNewlines,
+    shouldKeepComment: options.comments,
     start (tag, attrs, unary) {
       // check namespace.
       // inherit parent ns if there is one
@@ -274,6 +275,13 @@ export function parse (
           })
         }
       }
+    },
+    comment (text: string) {
+      currentParent.children.push({
+        type: 3,
+        text,
+        isComment: true
+      })
     }
   })
   return root

+ 2 - 2
src/core/vdom/vnode.js

@@ -64,9 +64,9 @@ export default class VNode {
   }
 }
 
-export const createEmptyVNode = () => {
+export const createEmptyVNode = (text: string = '') => {
   const node = new VNode()
-  node.text = ''
+  node.text = text
   node.isComment = true
   return node
 }

+ 2 - 1
src/platforms/web/entry-runtime-with-compiler.js

@@ -64,7 +64,8 @@ Vue.prototype.$mount = function (
 
       const { render, staticRenderFns } = compileToFunctions(template, {
         shouldDecodeNewlines,
-        delimiters: options.delimiters
+        delimiters: options.delimiters,
+        comments: options.comments
       }, this)
       options.render = render
       options.staticRenderFns = staticRenderFns

+ 16 - 0
test/unit/features/options/comments.spec.js

@@ -0,0 +1,16 @@
+import Vue from 'vue'
+
+describe('Comments', () => {
+  it('comments should be kept', () => {
+    const vm = new Vue({
+      comments: true,
+      data () {
+        return {
+          foo: 1
+        }
+      },
+      template: '<div><span>node1</span><!--comment1-->{{foo}}<!--comment2--></div>'
+    }).$mount()
+    expect(vm.$el.innerHTML).toEqual('<span>node1</span><!--comment1-->1<!--comment2-->')
+  })
+})

+ 14 - 1
test/unit/modules/compiler/codegen.spec.js

@@ -1,7 +1,7 @@
 import { parse } from 'compiler/parser/index'
 import { optimize } from 'compiler/optimizer'
 import { generate } from 'compiler/codegen'
-import { isObject } from 'shared/util'
+import { isObject, extend } from 'shared/util'
 import { isReservedTag } from 'web/util/index'
 import { baseOptions } from 'web/compiler/options'
 
@@ -474,6 +474,19 @@ describe('codegen', () => {
     )
   })
 
+  it('generate component with comment', () => {
+    const options = extend({
+      comments: true
+    }, baseOptions)
+    const template = '<div><!--comment--></div>'
+    const generatedCode = `with(this){return _c('div',[_e('comment')])}`
+
+    const ast = parse(template, options)
+    optimize(ast, options)
+    const res = generate(ast, options)
+    expect(res.render).toBe(generatedCode)
+  })
+
   it('not specified ast type', () => {
     const res = generate(null, baseOptions)
     expect(res.render).toBe(`with(this){return _c("div")}`)

+ 14 - 0
test/unit/modules/compiler/optimizer.spec.js

@@ -1,4 +1,5 @@
 import { parse } from 'compiler/parser/index'
+import { extend } from 'shared/util'
 import { optimize } from 'compiler/optimizer'
 import { baseOptions } from 'web/compiler/options'
 
@@ -11,6 +12,19 @@ describe('optimizer', () => {
     expect(ast.children[0].static).toBe(true) // span
   })
 
+  it('simple with comment', () => {
+    const options = extend({
+      comments: true
+    }, baseOptions)
+    const ast = parse('<h1 id="section1"><span>hello world</span><!--comment--></h1>', options)
+    optimize(ast, options)
+    expect(ast.static).toBe(true) // h1
+    expect(ast.staticRoot).toBe(true)
+    expect(ast.children.length).toBe(2)
+    expect(ast.children[0].static).toBe(true) // span
+    expect(ast.children[1].static).toBe(true) // comment
+  })
+
   it('skip simple nodes', () => {
     const ast = parse('<h1 id="section1">hello</h1>', baseOptions)
     optimize(ast, baseOptions)

+ 23 - 0
test/unit/modules/compiler/parser.spec.js

@@ -548,4 +548,27 @@ describe('parser', () => {
     const ast = parse(`<script type="x/template">&gt;<foo>&lt;</script>`, options)
     expect(ast.children[0].text).toBe(`&gt;<foo>&lt;`)
   })
+
+  it('should ignore comments', () => {
+    const options = extend({}, baseOptions)
+    const ast = parse(`<div>123<!--comment here--></div>`, options)
+    expect(ast.tag).toBe('div')
+    expect(ast.children.length).toBe(1)
+    expect(ast.children[0].type).toBe(3)
+    expect(ast.children[0].text).toBe('123')
+  })
+
+  it('should kept comments', () => {
+    const options = extend({
+      comments: true
+    }, baseOptions)
+    const ast = parse(`<div>123<!--comment here--></div>`, options)
+    expect(ast.tag).toBe('div')
+    expect(ast.children.length).toBe(2)
+    expect(ast.children[0].type).toBe(3)
+    expect(ast.children[0].text).toBe('123')
+    expect(ast.children[1].type).toBe(3) // parse comment with ASTText
+    expect(ast.children[1].isComment).toBe(true) // parse comment with ASTText
+    expect(ast.children[1].text).toBe('comment here')
+  })
 })

+ 1 - 0
types/options.d.ts

@@ -54,6 +54,7 @@ export interface ComponentOptions<V extends Vue> {
   name?: string;
   extends?: ComponentOptions<Vue> | typeof Vue;
   delimiters?: [string, string];
+  comments?: boolean;
 }
 
 export interface FunctionalComponentOptions {