summaryrefslogtreecommitdiff
path: root/config/vue3migration/compiler.js
blob: d6b6e1e7533a7c223f95b1d4294e664d73306c51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
const { parse, compile: compilerDomCompile } = require('@vue/compiler-dom');

const COMMENT_NODE_TYPE = 3;

const hasProp = (node, prop) => node.props?.some((p) => p.name === prop);

function modifyKeysInsideTemplateTag(templateNode) {
  if (!templateNode.tag === 'template' || !hasProp(templateNode, 'for')) {
    return;
  }

  let keyCandidate = null;
  for (const node of templateNode.children) {
    const keyBindingIndex = node.props
      ? node.props.findIndex((prop) => prop.arg && prop.arg.content === 'key')
      : -1;

    if (keyBindingIndex !== -1 && !hasProp(node, 'for')) {
      if (!keyCandidate) {
        keyCandidate = node.props[keyBindingIndex];
      }
      node.props.splice(keyBindingIndex, 1);
    }
  }

  if (keyCandidate) {
    templateNode.props.push(keyCandidate);
  }
}

function getSlotName(node) {
  return node?.props?.find((prop) => prop.name === 'slot')?.arg?.content;
}

function filterCommentNodeAndTrailingSpace(node, idx, list) {
  if (node.type === COMMENT_NODE_TYPE) {
    return false;
  }

  if (node.content !== ' ') {
    return true;
  }

  if (list[idx - 1]?.type === COMMENT_NODE_TYPE) {
    return false;
  }

  return true;
}

function filterCommentNodes(node) {
  const { length: originalLength } = node.children;
  // eslint-disable-next-line no-param-reassign
  node.children = node.children.filter(filterCommentNodeAndTrailingSpace);
  if (node.children.length !== originalLength) {
    // trim remaining spaces
    while (node.children.at(-1)?.content === ' ') {
      node.children.pop();
    }
  }
}

function dropVOnceForChildrenInsideVIfBecauseOfIssue7725(node) {
  // See https://github.com/vuejs/core/issues/7725 for details
  if (!hasProp(node, 'if')) {
    return;
  }

  node.children?.forEach((child) => {
    if (Array.isArray(child.props)) {
      // eslint-disable-next-line no-param-reassign
      child.props = child.props.filter((prop) => prop.name !== 'once');
    }
  });
}

function fixSameSlotsInsideTemplateFailingWhenUsingWhitespacePreserveDueToIssue6063(node) {
  // See https://github.com/vuejs/core/issues/6063 for details
  // eslint-disable-next-line no-param-reassign
  node.children = node.children.filter((child, idx) => {
    if (child.content !== ' ') {
      // We need to drop only comment nodes
      return true;
    }

    const previousNodeSlotName = getSlotName(node.children[idx - 1]);
    const nextNodeSlotName = getSlotName(node.children[idx + 1]);

    if (previousNodeSlotName && previousNodeSlotName === nextNodeSlotName) {
      // We have a space beween two slot entries with same slot name, we need to drop it
      return false;
    }

    return true;
  });
}

module.exports = {
  parse,
  compile(template, options) {
    const rootNode = parse(template, options);

    const pendingNodes = [rootNode];
    while (pendingNodes.length) {
      const currentNode = pendingNodes.pop();
      if (Array.isArray(currentNode.children)) {
        // This one will be dropped all together with compiler when we drop Vue.js 2 support
        modifyKeysInsideTemplateTag(currentNode);

        dropVOnceForChildrenInsideVIfBecauseOfIssue7725(currentNode);

        // See https://github.com/vuejs/core/issues/7909 for details
        // However, this issue applies not only to root-level nodes
        // But on any level comments could change slot emptiness detection
        // so we simply drop them
        filterCommentNodes(currentNode);

        fixSameSlotsInsideTemplateFailingWhenUsingWhitespacePreserveDueToIssue6063(currentNode);

        currentNode.children.forEach((child) => pendingNodes.push(child));
      }
    }

    return compilerDomCompile(rootNode, options);
  },
};