Просмотр исходного кода

properly perserve whitespace in <pre> (fix #3341)

Evan You 9 лет назад
Родитель
Сommit
04e2b53cd7

+ 1 - 0
flow/compiler.js

@@ -8,6 +8,7 @@ declare type CompilerOptions = {
   isUnaryTag?: (tag: string) => ?boolean; // check if a tag is unary for the platform
   isReservedTag?: (tag: string) => ?boolean; // check if a tag is a native for the platform
   mustUseProp?: (attr: string) => ?boolean; // check if an attribute should be bound as a property
+  isPreTag?: (attr: string) => ?boolean; // check if a tag needs to preserve whitespace
   getTagNamespace?: (tag: string) => ?string; // check the namespace for a tag
   transforms?: Array<Function>; // a list of transforms on parsed AST before codegen
   preserveWhitespace?: boolean;

+ 14 - 5
src/compiler/parser/index.js

@@ -30,6 +30,7 @@ const decodeHTMLCached = cached(decodeHTML)
 let warn
 let platformGetTagNamespace
 let platformMustUseProp
+let platformIsPreTag
 let preTransforms
 let transforms
 let postTransforms
@@ -45,6 +46,7 @@ export function parse (
   warn = options.warn || baseWarn
   platformGetTagNamespace = options.getTagNamespace || no
   platformMustUseProp = options.mustUseProp || no
+  platformIsPreTag = options.isPreTag || no
   preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
   transforms = pluckModuleFunction(options.modules, 'transformNode')
   postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
@@ -53,6 +55,7 @@ export function parse (
   const preserveWhitespace = options.preserveWhitespace !== false
   let root
   let currentParent
+  let inVPre = false
   let inPre = false
   let warned = false
   parseHTML(template, {
@@ -97,13 +100,16 @@ export function parse (
         preTransforms[i](element, options)
       }
 
-      if (!inPre) {
+      if (!inVPre) {
         processPre(element)
         if (element.pre) {
-          inPre = true
+          inVPre = true
         }
       }
-      if (inPre) {
+      if (platformIsPreTag(element.tag)) {
+        inPre = true
+      }
+      if (inVPre) {
         processRawAttrs(element)
       } else {
         processFor(element)
@@ -178,6 +184,9 @@ export function parse (
       currentParent = stack[stack.length - 1]
       // check pre state
       if (element.pre) {
+        inVPre = false
+      }
+      if (platformIsPreTag(element.tag)) {
         inPre = false
       }
     },
@@ -192,13 +201,13 @@ export function parse (
         }
         return
       }
-      text = currentParent.tag === 'pre' || text.trim()
+      text = inPre || text.trim()
         ? decodeHTMLCached(text)
         // only preserve whitespace if its not right after a starting tag
         : preserveWhitespace && currentParent.children.length ? ' ' : ''
       if (text) {
         let expression
-        if (!inPre && text !== ' ' && (expression = parseText(text, delimiters))) {
+        if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {
           currentParent.children.push({
             type: 2,
             expression,

+ 14 - 14
src/core/config.js

@@ -4,22 +4,22 @@ import { no, noop } from 'shared/util'
 
 export type Config = {
   // user
-  optionMergeStrategies: { [key: string]: Function },
-  silent: boolean,
-  devtools: boolean,
-  errorHandler: ?Function,
-  ignoredElements: ?Array<string>,
-  keyCodes: { [key: string]: number },
+  optionMergeStrategies: { [key: string]: Function };
+  silent: boolean;
+  devtools: boolean;
+  errorHandler: ?Function;
+  ignoredElements: ?Array<string>;
+  keyCodes: { [key: string]: number };
   // platform
-  isReservedTag: (x?: string) => boolean,
-  isUnknownElement: (x?: string) => boolean,
-  getTagNamespace: (x?: string) => string | void,
-  mustUseProp: (x?: string) => boolean,
+  isReservedTag: (x?: string) => boolean;
+  isUnknownElement: (x?: string) => boolean;
+  getTagNamespace: (x?: string) => string | void;
+  mustUseProp: (x?: string) => boolean;
   // internal
-  _assetTypes: Array<string>,
-  _lifecycleHooks: Array<string>,
-  _maxUpdateCount: number,
-  _isServer: boolean
+  _assetTypes: Array<string>;
+  _lifecycleHooks: Array<string>;
+  _maxUpdateCount: number;
+  _isServer: boolean;
 }
 
 const config: Config = {

+ 6 - 2
src/platforms/web/compiler/index.js

@@ -6,7 +6,10 @@ import { compile as baseCompile } from 'compiler/index'
 import { detectErrors } from 'compiler/error-detector'
 import modules from './modules/index'
 import directives from './directives/index'
-import { isIE, isReservedTag, isUnaryTag, mustUseProp, getTagNamespace } from '../util/index'
+import {
+  isIE, isReservedTag, isUnaryTag,
+  mustUseProp, getTagNamespace, isPreTag
+} from '../util/index'
 
 const cache: { [key: string]: CompiledFunctionResult } = Object.create(null)
 
@@ -19,7 +22,8 @@ export const baseOptions: CompilerOptions = {
   isReservedTag,
   isUnaryTag,
   mustUseProp,
-  getTagNamespace
+  getTagNamespace,
+  isPreTag
 }
 
 export function compile (

+ 2 - 0
src/platforms/web/util/element.js

@@ -55,6 +55,8 @@ export const isSVG = makeMap(
   true
 )
 
+export const isPreTag = (tag: ?string): boolean => tag === 'pre'
+
 export const isReservedTag = (tag: string): ?boolean => {
   return isHTMLTag(tag) || isSVG(tag)
 }

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

@@ -310,4 +310,14 @@ describe('parser', () => {
     expect(spy1).toHaveBeenCalledWith('img')
     expect(spy2).toHaveBeenCalledWith('img')
   })
+
+  it('preserve whitespace in <pre> tag', function () {
+    const options = extend({}, baseOptions)
+    const ast = parse('<pre><code>  \n<span>hi</span>\n  </code></pre>', options)
+    const code = ast.children[0]
+    expect(code.children[0].type).toBe(3)
+    expect(code.children[0].text).toBe('  \n')
+    expect(code.children[2].type).toBe(3)
+    expect(code.children[2].text).toBe('\n  ')
+  })
 })