From 68321a67d78fff7e28c9848d9f8c8f909bd85001 Mon Sep 17 00:00:00 2001 From: RoyEden Date: Mon, 23 Aug 2021 20:50:15 -0300 Subject: [PATCH 1/6] fix(compiler): Fixed parser adding all whitespace within class or style attributes --- .../compiler-core/__tests__/parse.spec.ts | 131 ++++++++++++++++++ packages/compiler-core/src/parse.ts | 12 ++ 2 files changed, 143 insertions(+) diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 0e839d26bc7..63152c930be 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -1015,6 +1015,137 @@ describe('compiler: parse', () => { }) }) + // https://github.com/vuejs/vue-next/issues/4251 + test('class attribute should ignore whitespace when parsed', () => { + const ast = baseParse('
') + const element = ast.children[0] as ElementNode + + expect(element).toStrictEqual({ + children: [], + codegenNode: undefined, + isSelfClosing: false, + loc: { + end: { + column: 10, + line: 3, + offset: 29 + }, + source: '
', + start: { + column: 1, + line: 1, + offset: 0 + } + }, + ns: 0, + props: [ + { + loc: { + end: { + column: 3, + line: 3, + offset: 22 + }, + source: 'class=" \n\t c \t\n "', + start: { + column: 6, + line: 1, + offset: 5 + } + }, + name: 'class', + type: 6, + value: { + content: 'c', + loc: { + end: { + column: 3, + line: 3, + offset: 22 + }, + source: '" \n\t c \t\n "', + start: { + column: 12, + line: 1, + offset: 11 + } + }, + type: 2 + } + } + ], + tag: 'div', + tagType: 0, + type: 1 + }) + }) + + test('style attribute should ignore whitespace when parsed', () => { + const ast = baseParse( + '
' + ) + const element = ast.children[0] as ElementNode + + expect(element).toStrictEqual({ + children: [], + codegenNode: undefined, + isSelfClosing: false, + loc: { + end: { + column: 10, + line: 4, + offset: 44 + }, + source: '
', + start: { + column: 1, + line: 1, + offset: 0 + } + }, + ns: 0, + props: [ + { + loc: { + end: { + column: 3, + line: 4, + offset: 37 + }, + source: 'style=" \n\t color: \n\t \'#000\' \t\n "', + start: { + column: 6, + line: 1, + offset: 5 + } + }, + name: 'style', + type: 6, + value: { + content: "color: '#000'", + loc: { + end: { + column: 3, + line: 4, + offset: 37 + }, + source: '" \n\t color: \n\t \'#000\' \t\n "', + start: { + column: 12, + line: 1, + offset: 11 + } + }, + type: 2 + } + } + ], + tag: 'div', + tagType: 0, + type: 1 + }) + }) + test('directive with no value', () => { const ast = baseParse('
') const directive = (ast.children[0] as ElementNode).props[0] diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 310766347e0..e1f232aca8c 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -716,6 +716,18 @@ function parseAttributes( } const attr = parseAttribute(context, attributeNames) + + // Trim whitespace between class or style + // https://github.com/vuejs/vue-next/issues/4251 + if ( + (attr.name === 'class' || attr.name === 'style') && + (attr as AttributeNode).value?.content + ) { + ;(attr as AttributeNode).value!.content = (attr as AttributeNode) + .value!.content.replace(/\s+/g, ' ') + .trim() + } + if (type === TagType.Start) { props.push(attr) } From 1dc1ff1b8f49f735c78607ed5559b6dec6154a0d Mon Sep 17 00:00:00 2001 From: RoyEden Date: Mon, 23 Aug 2021 20:51:18 -0300 Subject: [PATCH 2/6] fix(shared): normalizeStyle and normalizeClass now purge whitespace --- packages/shared/src/normalizeProp.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/normalizeProp.ts b/packages/shared/src/normalizeProp.ts index cab8090692e..c6c672ae23c 100644 --- a/packages/shared/src/normalizeProp.ts +++ b/packages/shared/src/normalizeProp.ts @@ -15,7 +15,10 @@ export function normalizeStyle( : (normalizeStyle(item) as NormalizedStyle) if (normalized) { for (const key in normalized) { - res[key] = normalized[key] + res[key.replace(/\s+/g, ' ').trim()] = + typeof normalized[key] === 'string' + ? (normalized[key] as string).replace(/\s+/g, ' ').trim() + : normalized[key] } } } @@ -80,7 +83,7 @@ export function normalizeClass(value: unknown): string { } } } - return res.trim() + return res.replace(/\s+/g, ' ').trim() } export function normalizeProps(props: Record | null) { From e825608186a6e7e06c6a2cbce24a46bea1a91ad4 Mon Sep 17 00:00:00 2001 From: RoyEden Date: Tue, 24 Aug 2021 20:36:32 -0300 Subject: [PATCH 3/6] feat(compiler): removed unnecessary style whitespace parsing for static styles --- .../compiler-core/__tests__/parse.spec.ts | 66 ------------------- packages/compiler-core/src/parse.ts | 15 ++--- 2 files changed, 6 insertions(+), 75 deletions(-) diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 63152c930be..801440911b4 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -1080,72 +1080,6 @@ describe('compiler: parse', () => { }) }) - test('style attribute should ignore whitespace when parsed', () => { - const ast = baseParse( - '
' - ) - const element = ast.children[0] as ElementNode - - expect(element).toStrictEqual({ - children: [], - codegenNode: undefined, - isSelfClosing: false, - loc: { - end: { - column: 10, - line: 4, - offset: 44 - }, - source: '
', - start: { - column: 1, - line: 1, - offset: 0 - } - }, - ns: 0, - props: [ - { - loc: { - end: { - column: 3, - line: 4, - offset: 37 - }, - source: 'style=" \n\t color: \n\t \'#000\' \t\n "', - start: { - column: 6, - line: 1, - offset: 5 - } - }, - name: 'style', - type: 6, - value: { - content: "color: '#000'", - loc: { - end: { - column: 3, - line: 4, - offset: 37 - }, - source: '" \n\t color: \n\t \'#000\' \t\n "', - start: { - column: 12, - line: 1, - offset: 11 - } - }, - type: 2 - } - } - ], - tag: 'div', - tagType: 0, - type: 1 - }) - }) - test('directive with no value', () => { const ast = baseParse('
') const directive = (ast.children[0] as ElementNode).props[0] diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index e1f232aca8c..fbc308777d2 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -715,17 +715,14 @@ function parseAttributes( emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES) } - const attr = parseAttribute(context, attributeNames) + let attr = parseAttribute(context, attributeNames) - // Trim whitespace between class or style + // Trim whitespace between class // https://github.com/vuejs/vue-next/issues/4251 - if ( - (attr.name === 'class' || attr.name === 'style') && - (attr as AttributeNode).value?.content - ) { - ;(attr as AttributeNode).value!.content = (attr as AttributeNode) - .value!.content.replace(/\s+/g, ' ') - .trim() + if (attr.name === 'class') { + attr = attr as AttributeNode + if (attr.value?.content) + attr.value!.content = attr.value!.content.replace(/\s+/g, ' ').trim() } if (type === TagType.Start) { From 56bff6b70674d5e8b2d5a8c4e451cfacb5c09a3c Mon Sep 17 00:00:00 2001 From: RoyEden Date: Fri, 3 Sep 2021 21:54:02 -0300 Subject: [PATCH 4/6] feat(shared): Added `normalizeWhitespace` function to abstract whitespace cleaning in attributes --- packages/runtime-core/src/index.ts | 3 ++- packages/shared/src/normalizeProp.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index b182f7e7472..28bee62689e 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -286,7 +286,8 @@ export { toHandlerKey, normalizeProps, normalizeClass, - normalizeStyle + normalizeStyle, + normalizeWhitespace } from '@vue/shared' // For test-utils diff --git a/packages/shared/src/normalizeProp.ts b/packages/shared/src/normalizeProp.ts index c6c672ae23c..7ce8ab991cf 100644 --- a/packages/shared/src/normalizeProp.ts +++ b/packages/shared/src/normalizeProp.ts @@ -3,6 +3,10 @@ import { isNoUnitNumericStyleProp } from './domAttrConfig' export type NormalizedStyle = Record +export function normalizeWhitespace(value: string) { + return value.replace(/\s+/g, ' ').trim() +} + export function normalizeStyle( value: unknown ): NormalizedStyle | string | undefined { @@ -15,9 +19,9 @@ export function normalizeStyle( : (normalizeStyle(item) as NormalizedStyle) if (normalized) { for (const key in normalized) { - res[key.replace(/\s+/g, ' ').trim()] = + res[normalizeWhitespace(key)] = typeof normalized[key] === 'string' - ? (normalized[key] as string).replace(/\s+/g, ' ').trim() + ? normalizeWhitespace(normalized[key] as string) : normalized[key] } } @@ -83,7 +87,7 @@ export function normalizeClass(value: unknown): string { } } } - return res.replace(/\s+/g, ' ').trim() + return normalizeWhitespace(res) } export function normalizeProps(props: Record | null) { From 9e5304570e4dbdbfff574a60f6ca18974e257312 Mon Sep 17 00:00:00 2001 From: RoyEden Date: Fri, 3 Sep 2021 21:55:51 -0300 Subject: [PATCH 5/6] feat(compiler): abstracted `trimAttrValue` into a util that can trim whitespace in attributes --- packages/compiler-core/src/parse.ts | 9 +++------ packages/compiler-core/src/utils.ts | 23 ++++++++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index dbe1122e28e..cdf71ccfa1b 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -11,7 +11,8 @@ import { advancePositionWithMutation, advancePositionWithClone, isCoreComponent, - isBindKey + isBindKey, + trimAttrValue } from './utils' import { Namespaces, @@ -719,11 +720,7 @@ function parseAttributes( // Trim whitespace between class // https://github.com/vuejs/vue-next/issues/4251 - if (attr.name === 'class') { - attr = attr as AttributeNode - if (attr.value?.content) - attr.value!.content = attr.value!.content.replace(/\s+/g, ' ').trim() - } + trimAttrValue(attr, 'class') if (type === TagType.Start) { props.push(attr) diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index e3bc04bec69..3d3fdf26851 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -5,6 +5,7 @@ import { NodeTypes, CallExpression, createCallExpression, + AttributeNode, DirectiveNode, ElementTypes, TemplateChildNode, @@ -42,7 +43,13 @@ import { WITH_MEMO, OPEN_BLOCK } from './runtimeHelpers' -import { isString, isObject, hyphenate, extend } from '@vue/shared' +import { + isString, + isObject, + hyphenate, + extend, + normalizeWhitespace +} from '@vue/shared' import { PropsExpression } from './transforms/transformElement' export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode => @@ -510,3 +517,17 @@ export function makeBlock( helper(getVNodeBlockHelper(inSSR, node.isComponent)) } } + +// Trims all whitespace inside of specific attribute/s and replaces it with a single whitespace +export function trimAttrValue( + node: AttributeNode | DirectiveNode, + name: string | string[] +) { + if ( + node.type === NodeTypes.ATTRIBUTE && + name.includes(node.name) && + node.value?.content + ) { + node.value.content = normalizeWhitespace(node.value.content) + } +} From 0505a24e8398379ab49fe5777874485d6285b2e1 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 7 Sep 2021 11:54:49 -0400 Subject: [PATCH 6/6] refactor: remove unnecessary abstrations --- packages/compiler-core/src/parse.ts | 13 +++++++++---- packages/compiler-core/src/utils.ts | 23 +---------------------- packages/runtime-core/src/index.ts | 3 +-- packages/shared/src/normalizeProp.ts | 11 ++--------- 4 files changed, 13 insertions(+), 37 deletions(-) diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index cdf71ccfa1b..c937a7e1a7f 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -11,8 +11,7 @@ import { advancePositionWithMutation, advancePositionWithClone, isCoreComponent, - isBindKey, - trimAttrValue + isBindKey } from './utils' import { Namespaces, @@ -716,11 +715,17 @@ function parseAttributes( emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES) } - let attr = parseAttribute(context, attributeNames) + const attr = parseAttribute(context, attributeNames) // Trim whitespace between class // https://github.com/vuejs/vue-next/issues/4251 - trimAttrValue(attr, 'class') + if ( + attr.type === NodeTypes.ATTRIBUTE && + attr.value && + attr.name === 'class' + ) { + attr.value.content = attr.value.content.replace(/\s+/g, ' ').trim() + } if (type === TagType.Start) { props.push(attr) diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index 3d3fdf26851..e3bc04bec69 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -5,7 +5,6 @@ import { NodeTypes, CallExpression, createCallExpression, - AttributeNode, DirectiveNode, ElementTypes, TemplateChildNode, @@ -43,13 +42,7 @@ import { WITH_MEMO, OPEN_BLOCK } from './runtimeHelpers' -import { - isString, - isObject, - hyphenate, - extend, - normalizeWhitespace -} from '@vue/shared' +import { isString, isObject, hyphenate, extend } from '@vue/shared' import { PropsExpression } from './transforms/transformElement' export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode => @@ -517,17 +510,3 @@ export function makeBlock( helper(getVNodeBlockHelper(inSSR, node.isComponent)) } } - -// Trims all whitespace inside of specific attribute/s and replaces it with a single whitespace -export function trimAttrValue( - node: AttributeNode | DirectiveNode, - name: string | string[] -) { - if ( - node.type === NodeTypes.ATTRIBUTE && - name.includes(node.name) && - node.value?.content - ) { - node.value.content = normalizeWhitespace(node.value.content) - } -} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 28bee62689e..b182f7e7472 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -286,8 +286,7 @@ export { toHandlerKey, normalizeProps, normalizeClass, - normalizeStyle, - normalizeWhitespace + normalizeStyle } from '@vue/shared' // For test-utils diff --git a/packages/shared/src/normalizeProp.ts b/packages/shared/src/normalizeProp.ts index 7ce8ab991cf..cab8090692e 100644 --- a/packages/shared/src/normalizeProp.ts +++ b/packages/shared/src/normalizeProp.ts @@ -3,10 +3,6 @@ import { isNoUnitNumericStyleProp } from './domAttrConfig' export type NormalizedStyle = Record -export function normalizeWhitespace(value: string) { - return value.replace(/\s+/g, ' ').trim() -} - export function normalizeStyle( value: unknown ): NormalizedStyle | string | undefined { @@ -19,10 +15,7 @@ export function normalizeStyle( : (normalizeStyle(item) as NormalizedStyle) if (normalized) { for (const key in normalized) { - res[normalizeWhitespace(key)] = - typeof normalized[key] === 'string' - ? normalizeWhitespace(normalized[key] as string) - : normalized[key] + res[key] = normalized[key] } } } @@ -87,7 +80,7 @@ export function normalizeClass(value: unknown): string { } } } - return normalizeWhitespace(res) + return res.trim() } export function normalizeProps(props: Record | null) {