core-vapor icon indicating copy to clipboard operation
core-vapor copied to clipboard

perf: template abbreviation of end tag

Open CathLee opened this issue 11 months ago • 3 comments

related #112 I've been working on optimizing the abbreviation of end tags in templates and would greatly appreciate it if you could review my solution at your convenience.So there is my solution:

Take the HTML code <div><span></span></div> as an example. The parsing mechanism sequentially executes handling (<div, <span) and closing (</) actions. My optimization's primary objective is to leverage self-closing tags whenever feasible, especially for the sequence's terminal element.

My strategy unfolds in two steps:

  1. Determine whether a close action directly follows a handle action, identified by this.currentStage === 'stateInTagName'. When this condition is met, we flag the current Node with isShouldSelfClosing. This flag is later used during the transformElement phase to implement the optimization. If the condition isn't met, we don't set the flag.
  2. Modify the currentStage attribute to accurately represent the ongoing parsing phase, thus accommodating various tag processing scenarios.

here is some draft code

// packages/compiler-core/src/tokenizer.ts
private stateInClosingTagName(c: number): void {
    if (c === CharCodes.Gt || isWhitespace(c)) {
      this.cbs.onclosetag(this.sectionStart, this.index)
      this.cbs.onclosetag(
        this.sectionStart,
        this.index,
        this.currentStage === 'stateInTagName',// `close` operation after `handle` operation
      )
      this.sectionStart = -1

      this.state = State.AfterClosingTagName
      this.stateAfterClosingTagName(c)
    }
    this.currentStage === 'stateInClosingTagName' //change currentStage in different flow
  }
// packages/compiler-core/src/parser.ts
onclosetag(start, end, isLastElement) {
    const name = getSlice(start, end)
    if (!currentOptions.isVoidTag(name)) {
      let found = false
      for (let i = 0; i < stack.length; i++) {
        const e = stack[i]
        if (e.tag.toLowerCase() === name.toLowerCase()) {
          found = true
           // if is the isLastElement make a tag with `isShouldSelfClosing`
          if (isLastElement) {
            e.isShouldSelfClosing = true
          }
          if (i > 0) {
            emitError(ErrorCodes.X_MISSING_END_TAG, stack[0].loc.start.offset)
          }
          for (let j = 0; j <= i; j++) {
            const el = stack.shift()!
            onCloseTag(el, end, j < i)
          }
          break
        }
      }
      if (!found) {
        emitError(ErrorCodes.X_INVALID_END_TAG, backTrack(start, CharCodes.Lt))
      }
    }
  },
// packages/compiler-vapor/src/transforms/transformElement.ts 
const { node } = context
  if (node.isShouldSelfClosing) {
    context.template += context.childrenTemplate.join('')
  } else {
    context.template += `>` + context.childrenTemplate.join('')
  }
  context.template += `>` + context.childrenTemplate.join('')
  // TODO remove unnecessary close tag, e.g. if it's the last element of the template
  if (!isVoidTag(tag)) {
    const { node } = context
    if (node.isShouldSelfClosing) {
      context.template += ` />`
    } else {
      context.template += `</${tag}>`
    }
  }

I've integrated the isShouldSelfClosing flag into the standard Node structure and have made the necessary updates in tokenizer.ts. However, I'm uncertain if this is the best approach.

Any feedback or advice you could offer on this strategy would be incredibly valuable to me~~ ps:code is in https://github.com/CathLee/core-vapor/tree/transform/close_tag

CathLee avatar Mar 25 '24 16:03 CathLee