wip: optimize condenseWhitespace

This commit is contained in:
Evan You 2023-11-14 21:55:16 +08:00
parent 9aa8effec3
commit 622d34efe1
2 changed files with 71 additions and 32 deletions

View File

@ -99,7 +99,7 @@ const enum State {
InEntity InEntity
} }
function isWhitespace(c: number): boolean { export function isWhitespace(c: number): boolean {
return ( return (
c === CharCodes.Space || c === CharCodes.Space ||
c === CharCodes.NewLine || c === CharCodes.NewLine ||

View File

@ -13,9 +13,9 @@ import {
createRoot createRoot
} from '../ast' } from '../ast'
import { ParserOptions } from '../options' import { ParserOptions } from '../options'
import Tokenizer, { CharCodes } from './Tokenizer' import Tokenizer, { CharCodes, isWhitespace } from './Tokenizer'
import { CompilerCompatOptions } from '../compat/compatConfig' import { CompilerCompatOptions } from '../compat/compatConfig'
import { NO, extend, hasOwn } from '@vue/shared' import { NO, extend } from '@vue/shared'
import { defaultOnError, defaultOnWarn } from '../errors' import { defaultOnError, defaultOnWarn } from '../errors'
type OptionalOptions = type OptionalOptions =
@ -55,7 +55,6 @@ export const defaultParserOptions: MergedParserOptions = {
comments: __DEV__ comments: __DEV__
} }
const directiveTestRE = /^(v-[A-Za-z0-9-]|:|\.|@|#)/
const directiveParseRE = const directiveParseRE =
/(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i
@ -82,7 +81,7 @@ let currentInput = ''
let currentElement: ElementNode | null = null let currentElement: ElementNode | null = null
let currentProp: AttributeNode | DirectiveNode | null = null let currentProp: AttributeNode | DirectiveNode | null = null
let currentAttrValue = '' let currentAttrValue = ''
let currentAttrs: Record<string, true> | null = null let currentAttrs: Set<string> = new Set()
let inPre = 0 let inPre = 0
let inVPre = 0 let inVPre = 0
const stack: ElementNode[] = [] const stack: ElementNode[] = []
@ -118,7 +117,7 @@ const tokenizer = new Tokenizer(
foreignContext.shift() foreignContext.shift()
} }
if (!currentOptions.isVoidTag?.(name)) { if (!currentOptions.isVoidTag(name)) {
const pos = stack.findIndex(e => e.tag === name) const pos = stack.findIndex(e => e.tag === name)
if (pos !== -1) { if (pos !== -1) {
for (let index = 0; index <= pos; index++) { for (let index = 0; index <= pos; index++) {
@ -143,12 +142,12 @@ const tokenizer = new Tokenizer(
onattribname(start, end) { onattribname(start, end) {
const name = getSlice(start, end) const name = getSlice(start, end)
if (hasOwn(currentAttrs!, name)) { if (currentAttrs.has(name)) {
// TODO emit error DUPLICATE_ATTRIBUTE // TODO emit error DUPLICATE_ATTRIBUTE
} else { } else {
currentAttrs![name] = true currentAttrs.add(name)
} }
if (!inVPre && directiveTestRE.test(name)) { if (!inVPre && isDirective(name)) {
// directive // directive
const match = directiveParseRE.exec(name)! const match = directiveParseRE.exec(name)!
const firstChar = name[0] const firstChar = name[0]
@ -328,7 +327,7 @@ function emitOpenTag(name: string, start: number) {
}, },
codegenNode: undefined codegenNode: undefined
} }
currentAttrs = {} currentAttrs.clear()
} }
function endOpenTag(end: number) { function endOpenTag(end: number) {
@ -347,7 +346,6 @@ function endOpenTag(end: number) {
onCloseTag(currentElement!, end) onCloseTag(currentElement!, end)
} }
currentElement = null currentElement = null
currentAttrs = null
} }
function closeCurrentTag(end: number) { function closeCurrentTag(end: number) {
@ -390,12 +388,6 @@ function onCloseTag(el: ElementNode, end: number) {
} }
const windowsNewlineRE = /\r\n/g const windowsNewlineRE = /\r\n/g
const consecutiveWhitespaceRE = /[\t\r\n\f ]+/g
const nonWhitespaceRE = /[^\t\r\n\f ]/
function isEmptyText(content: string) {
return !nonWhitespaceRE.test(content)
}
function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] { function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
const shouldCondense = currentOptions.whitespace !== 'preserve' const shouldCondense = currentOptions.whitespace !== 'preserve'
@ -404,27 +396,24 @@ function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
const node = nodes[i] const node = nodes[i]
if (node.type === NodeTypes.TEXT) { if (node.type === NodeTypes.TEXT) {
if (!inPre) { if (!inPre) {
if (isEmptyText(node.content)) { if (isAllWhitespace(node.content)) {
const prev = nodes[i - 1] const prev = nodes[i - 1]?.type
const next = nodes[i + 1] const next = nodes[i + 1]?.type
// Remove if: // Remove if:
// - the whitespace is the first or last node, or: // - the whitespace is the first or last node, or:
// - (condense mode) the whitespace is between twos comments, or: // - (condense mode) the whitespace is between two comments, or:
// - (condense mode) the whitespace is between comment and element, or: // - (condense mode) the whitespace is between comment and element, or:
// - (condense mode) the whitespace is between two elements AND contains newline // - (condense mode) the whitespace is between two elements AND contains newline
if ( if (
!prev || !prev ||
!next || !next ||
(shouldCondense && (shouldCondense &&
((prev.type === NodeTypes.COMMENT && ((prev === NodeTypes.COMMENT &&
next.type === NodeTypes.COMMENT) || (next === NodeTypes.COMMENT || next === NodeTypes.ELEMENT)) ||
(prev.type === NodeTypes.COMMENT && (prev === NodeTypes.ELEMENT &&
next.type === NodeTypes.ELEMENT) || (next === NodeTypes.COMMENT ||
(prev.type === NodeTypes.ELEMENT && (next === NodeTypes.ELEMENT &&
next.type === NodeTypes.COMMENT) || hasNewlineChar(node.content))))))
(prev.type === NodeTypes.ELEMENT &&
next.type === NodeTypes.ELEMENT &&
/[\r\n]/.test(node.content))))
) { ) {
removedWhitespace = true removedWhitespace = true
nodes[i] = null as any nodes[i] = null as any
@ -435,7 +424,7 @@ function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
} else if (shouldCondense) { } else if (shouldCondense) {
// in condense mode, consecutive whitespaces in text are condensed // in condense mode, consecutive whitespaces in text are condensed
// down to a single space. // down to a single space.
node.content = node.content.replace(consecutiveWhitespaceRE, ' ') node.content = condense(node.content)
} }
} else { } else {
// #6410 normalize windows newlines in <pre>: // #6410 normalize windows newlines in <pre>:
@ -448,6 +437,42 @@ function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
return removedWhitespace ? nodes.filter(Boolean) : nodes return removedWhitespace ? nodes.filter(Boolean) : nodes
} }
function isAllWhitespace(str: string) {
for (let i = 0; i < str.length; i++) {
if (!isWhitespace(str.charCodeAt(i))) {
return false
}
}
return true
}
function hasNewlineChar(str: string) {
for (let i = 0; i < str.length; i++) {
const c = str.charCodeAt(i)
if (c === CharCodes.NewLine || c === CharCodes.CarriageReturn) {
return true
}
}
return false
}
function condense(str: string) {
let ret = ''
let prevCharIsWhitespace = false
for (let i = 0; i < str.length; i++) {
if (isWhitespace(str.charCodeAt(i))) {
if (!prevCharIsWhitespace) {
ret += ' '
prevCharIsWhitespace = true
}
} else {
ret += str[i]
prevCharIsWhitespace = false
}
}
return ret
}
function addNode(node: TemplateChildNode) { function addNode(node: TemplateChildNode) {
getParent().children.push(node) getParent().children.push(node)
} }
@ -456,11 +481,25 @@ function getParent() {
return stack[0] || currentRoot return stack[0] || currentRoot
} }
function isDirective(name: string) {
switch (name[0]) {
case ':':
case '.':
case '@':
case '#':
return true
case 'v':
return name[1] === '-'
default:
return false
}
}
function reset() { function reset() {
tokenizer.reset() tokenizer.reset()
currentElement = null currentElement = null
currentProp = null currentProp = null
currentAttrs = null currentAttrs.clear()
currentAttrValue = '' currentAttrValue = ''
stack.length = 0 stack.length = 0
foreignContext.length = 1 foreignContext.length = 1