exports[`compiler: parse > Edge Cases > invalid html 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Edge Cases > self closing multiple tag 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Edge Cases > valid html 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><![CDATA[cdata]]></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><svg><![CDATA[cdata]]></svg></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > DUPLICATE_ATTRIBUTE > <template><div id="" id=""></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template>< 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template></ 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[ 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[cdata 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!-- 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!--comment 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_IN_TAG > <div></div 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id = 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc" 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc"/ 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc' 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc'/ 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc / 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id= /></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id= ></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > MISSING_END_TAG_NAME > <template></></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a"bc=''></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a'bc=''></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a<bc=''></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar"></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar'></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar<div></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar=baz></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar\`></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME > <template><div =></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME > <template><div =foo=bar></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME > <template><?xml?></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > UNEXPECTED_SOLIDUS_IN_TAG > <template><div a/b></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><![CDATA[</div>]]></svg> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><!--</div>--></svg> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>{{'</div>'}}</template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>a </ b</template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <textarea></div></textarea> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END > <div v-foo:[sef fsef] /> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [],
exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div></template> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > <div>{{ foo</div> 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"children": [
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"content": "{{",
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ foo 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"content": "{{ foo",
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{}} 1`] = `
{
- "cached": 0,
+ "cached": [],
"children": [
{
"content": {
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`scopeId compiler support > should push scopeId for hoisted nodes 1`] = `
-"import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"
-
-const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)
-const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", -1 /* HOISTED */))
-const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", -1 /* HOISTED */))
-
-export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, [
- _hoisted_1,
- _createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
- _hoisted_2
- ]))
-}"
-`;
-
exports[`scopeId compiler support > should wrap default slot 1`] = `
"import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from "vue"
directives: [],
imports: [],
hoists: [],
- cached: 0,
+ cached: [],
temps: 0,
codegenNode: createSimpleExpression(`null`, false),
loc: locStub,
test('CacheExpression', () => {
const { code } = generate(
createRoot({
- cached: 1,
+ cached: [],
codegenNode: createCacheExpression(
1,
createSimpleExpression(`foo`, false),
test('CacheExpression w/ isVNode: true', () => {
const { code } = generate(
createRoot({
- cached: 1,
+ cached: [],
codegenNode: createCacheExpression(
1,
createSimpleExpression(`foo`, false),
import { baseCompile } from '../src/compile'
-import { POP_SCOPE_ID, PUSH_SCOPE_ID } from '../src/runtimeHelpers'
-import { PatchFlags } from '@vue/shared'
-import { genFlagText } from './testUtils'
/**
* Ensure all slot functions are wrapped with _withCtx
expect(code).toMatch(/name: i,\s+fn: _withCtx\(/)
expect(code).toMatchSnapshot()
})
-
- test('should push scopeId for hoisted nodes', () => {
- const { ast, code } = baseCompile(
- `<div><div>hello</div>{{ foo }}<div>world</div></div>`,
- {
- mode: 'module',
- scopeId: 'test',
- hoistStatic: true,
- },
- )
- expect(ast.helpers).toContain(PUSH_SCOPE_ID)
- expect(ast.helpers).toContain(POP_SCOPE_ID)
- expect(ast.hoists.length).toBe(2)
- ;[
- `const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)`,
- `const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText(
- PatchFlags.HOISTED,
- )}))`,
- `const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText(
- PatchFlags.HOISTED,
- )}))`,
- ].forEach(c => expect(code).toMatch(c))
- expect(code).toMatchSnapshot()
- })
})
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: hoistStatic transform > hoist element with static key 1`] = `
+exports[`compiler: cacheStatic transform > cache element with static key 1`] = `
"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
+ ])))
}
}"
`;
-exports[`compiler: hoistStatic transform > hoist nested static tree 1`] = `
+exports[`compiler: cacheStatic transform > cache nested children array 1`] = `
"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, [
- /*#__PURE__*/_createElementVNode("span"),
- /*#__PURE__*/_createElementVNode("span")
-], -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("p", null, [
+ _createElementVNode("span"),
+ _createElementVNode("span")
+ ], -1 /* CACHED */),
+ _createElementVNode("p", null, [
+ _createElementVNode("span"),
+ _createElementVNode("span")
+ ], -1 /* CACHED */)
+ ])))
}
}"
`;
-exports[`compiler: hoistStatic transform > hoist nested static tree with comments 1`] = `
+exports[`compiler: cacheStatic transform > cache nested static tree with comments 1`] = `
"const _Vue = Vue
-const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [
- /*#__PURE__*/_createCommentVNode("comment")
-], -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
return function render(_ctx, _cache) {
with (_ctx) {
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", null, [
+ _createCommentVNode("comment")
+ ], -1 /* CACHED */)
+ ])))
}
}"
`;
-exports[`compiler: hoistStatic transform > hoist siblings with common non-hoistable parent 1`] = `
+exports[`compiler: cacheStatic transform > cache siblings including text with common non-hoistable parent 1`] = `
"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
-const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, null, -1 /* HOISTED */)
-const _hoisted_3 = [
- _hoisted_1,
- _hoisted_2
-]
return function render(_ctx, _cache) {
with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+ const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _hoisted_3))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", null, null, -1 /* CACHED */),
+ _createTextVNode("foo"),
+ _createElementVNode("div", null, null, -1 /* CACHED */)
+ ])))
}
}"
`;
-exports[`compiler: hoistStatic transform > hoist simple element 1`] = `
+exports[`compiler: cacheStatic transform > cache single children array 1`] = `
"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
+ ])))
}
}"
`;
-exports[`compiler: hoistStatic transform > hoist static props for elements with directives 1`] = `
+exports[`compiler: cacheStatic transform > hoist static props for elements with directives 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
}"
`;
-exports[`compiler: hoistStatic transform > hoist static props for elements with dynamic text children 1`] = `
+exports[`compiler: cacheStatic transform > hoist static props for elements with dynamic text children 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
}"
`;
-exports[`compiler: hoistStatic transform > hoist static props for elements with unhoistable children 1`] = `
+exports[`compiler: cacheStatic transform > hoist static props for elements with unhoistable children 1`] = `
"const _Vue = Vue
const { createVNode: _createVNode, createElementVNode: _createElementVNode } = _Vue
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist class with static object value 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static interpolation 1`] = `
"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = {
- class: /*#__PURE__*/_normalizeClass({ foo: true })
-}
return function render(_ctx, _cache) {
with (_ctx) {
- const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+ const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, [
- _createElementVNode("span", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
- ]))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
+ ])))
}
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static interpolation 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static prop value 1`] = `
"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "foo " + /*#__PURE__*/_toDisplayString(1) + " " + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
+ ])))
}
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static prop value 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > clone hoisted array children in v-for + HMR mode 1`] = `
"const _Vue = Vue
-const { createElementVNode: _createElementVNode } = _Vue
-
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
return function render(_ctx, _cache) {
with (_ctx) {
- const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, [
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
+ _createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */)
+ ]))]))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ ]))
}
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist SVG with directives 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > hoist class with static object value 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
+const _hoisted_1 = {
+ class: /*#__PURE__*/_normalizeClass({ foo: true })
+}
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock("div", null, [
+ _createElementVNode("span", _hoisted_1, _toDisplayString(_ctx.bar), 1 /* TEXT */)
+ ]))
+ }
+}"
+`;
+
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache SVG with directives 1`] = `
+"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const _directive_foo = _resolveDirective("foo")
return (_openBlock(), _createElementBlock("div", null, [
- _withDirectives((_openBlock(), _createElementBlock("svg", null, _hoisted_2)), [
+ _withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
+ ]))), [
[_directive_foo]
])
]))
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist elements with cached handlers + other bindings 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers + other bindings 1`] = `
"import { normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist elements with cached handlers 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers 1`] = `
"import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables (2) 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (2) 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables (v-slot) 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (v-slot) 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist keyed template v-for with plain element child 1`] = `
+exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache keyed template v-for with plain element child 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > should NOT hoist components 1`] = `
+exports[`compiler: cacheStatic transform > should NOT cache components 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic key 1`] = `
+exports[`compiler: cacheStatic transform > should NOT cache element with dynamic key 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic props (but hoist the props list) 1`] = `
+exports[`compiler: cacheStatic transform > should NOT cache element with dynamic props (but hoist the props list) 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
}"
`;
-exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic ref 1`] = `
+exports[`compiler: cacheStatic transform > should NOT cache element with dynamic ref 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: hoistStatic transform > should NOT hoist root node 1`] = `
+exports[`compiler: cacheStatic transform > should cache v-if props/children if static 1`] = `
"const _Vue = Vue
+const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
+
+const _hoisted_1 = {
+ key: 0,
+ id: "foo"
+}
return function render(_ctx, _cache) {
with (_ctx) {
- const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+ const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
- return (_openBlock(), _createElementBlock("div"))
+ return (_openBlock(), _createElementBlock("div", null, [
+ ok
+ ? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", null, null, -1 /* CACHED */)
+ ])))
+ : _createCommentVNode("v-if", true)
+ ]))
}
}"
`;
-exports[`compiler: hoistStatic transform > should hoist v-for children if static 1`] = `
+exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = { id: "foo" }
-const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
-const _hoisted_3 = [
- _hoisted_2
-]
return function render(_ctx, _cache) {
with (_ctx) {
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
- return (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
+ return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("span", null, null, -1 /* CACHED */)
+ ])))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
}"
`;
-
-exports[`compiler: hoistStatic transform > should hoist v-if props/children if static 1`] = `
-"const _Vue = Vue
-const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
-
-const _hoisted_1 = {
- key: 0,
- id: "foo"
-}
-const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
-const _hoisted_3 = [
- _hoisted_2
-]
-
-return function render(_ctx, _cache) {
- with (_ctx) {
- const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
-
- return (_openBlock(), _createElementBlock("div", null, [
- ok
- ? (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
- : _createCommentVNode("v-if", true)
- ]))
- }
-}"
-`;
import { transformText } from '../../src/transforms/transformText'
import { PatchFlags } from '@vue/shared'
-const hoistedChildrenArrayMatcher = (startIndex = 1, length = 1) => ({
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- elements: new Array(length).fill(0).map((_, i) => ({
- type: NodeTypes.ELEMENT,
- codegenNode: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_${startIndex + i}`,
- },
- })),
+const cachedChildrenArrayMatcher = (
+ tags: string[],
+ needArraySpread = false,
+) => ({
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ needArraySpread,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: tags.map(tag => {
+ if (tag === '') {
+ return {
+ type: NodeTypes.TEXT_CALL,
+ }
+ } else {
+ return {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: JSON.stringify(tag),
+ },
+ }
+ }
+ }),
+ },
})
-function transformWithHoist(template: string, options: CompilerOptions = {}) {
+function transformWithCache(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transform(ast, {
hoistStatic: true,
return ast
}
-describe('compiler: hoistStatic transform', () => {
- test('should NOT hoist root node', () => {
+describe('compiler: cacheStatic transform', () => {
+ test('should NOT cache root node', () => {
// if the whole tree is static, the root still needs to be a block
// so that it's patched in optimized mode to skip children
- const root = transformWithHoist(`<div/>`)
- expect(root.hoists.length).toBe(0)
+ const root = transformWithCache(`<div/>`)
expect(root.codegenNode).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
tag: `"div"`,
})
- expect(generate(root).code).toMatchSnapshot()
+ expect(root.cached.length).toBe(0)
+ })
+
+ test('cache root node children', () => {
+ // we don't have access to the root codegenNode during the transform
+ // so we only cache each child individually
+ const root = transformWithCache(
+ `<span class="inline">hello</span><span class="inline">hello</span>`,
+ )
+ expect(root.codegenNode).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ children: [
+ { codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } },
+ { codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } },
+ ],
+ })
+ expect(root.cached.length).toBe(2)
})
- test('hoist simple element', () => {
- const root = transformWithHoist(
+ test('cache single children array', () => {
+ const root = transformWithCache(
`<div><span class="inline">hello</span></div>`,
)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- props: createObjectMatcher({ class: 'inline' }),
- children: {
- type: NodeTypes.TEXT,
- content: `hello`,
- },
- },
- hoistedChildrenArrayMatcher(),
- ])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
- children: { content: `_hoisted_2` },
+ children: cachedChildrenArrayMatcher(['span']),
})
+ expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
- test('hoist nested static tree', () => {
- const root = transformWithHoist(`<div><p><span/><span/></p></div>`)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"p"`,
- props: undefined,
- children: [
- { type: NodeTypes.ELEMENT, tag: `span` },
- { type: NodeTypes.ELEMENT, tag: `span` },
- ],
- },
- hoistedChildrenArrayMatcher(),
- ])
+ test('cache nested children array', () => {
+ const root = transformWithCache(
+ `<div><p><span/><span/></p><p><span/><span/></p></div>`,
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject(
+ cachedChildrenArrayMatcher(['p', 'p']),
+ )
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache nested static tree with comments', () => {
+ const root = transformWithCache(`<div><div><!--comment--></div></div>`)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject(
+ cachedChildrenArrayMatcher(['div']),
+ )
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache siblings including text with common non-hoistable parent', () => {
+ const root = transformWithCache(`<div><span/>foo<div/></div>`)
+ expect((root.codegenNode as VNodeCall).children).toMatchObject(
+ cachedChildrenArrayMatcher(['span', '', 'div']),
+ )
+ expect(root.cached.length).toBe(1)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('cache inside default slot', () => {
+ const root = transformWithCache(`<Foo>{{x}}<span/></Foo>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: '_hoisted_2',
+ properties: [
+ {
+ key: { content: 'default' },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: [
+ {
+ type: NodeTypes.TEXT_CALL,
+ },
+ // first slot child cached
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ },
+ },
+ ],
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
})
- expect(generate(root).code).toMatchSnapshot()
})
- test('hoist nested static tree with comments', () => {
- const root = transformWithHoist(`<div><div><!--comment--></div></div>`)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: undefined,
- children: [{ type: NodeTypes.COMMENT, content: `comment` }],
- },
- hoistedChildrenArrayMatcher(),
- ])
+ test('cache default slot as a whole', () => {
+ const root = transformWithCache(`<Foo><span/><span/></Foo>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: `_hoisted_2`,
+ properties: [
+ {
+ key: { content: 'default' },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ { type: NodeTypes.ELEMENT },
+ { type: NodeTypes.ELEMENT },
+ ],
+ },
+ },
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
})
- expect(generate(root).code).toMatchSnapshot()
})
- test('hoist siblings with common non-hoistable parent', () => {
- const root = transformWithHoist(`<div><span/><div/></div>`)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- },
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- },
- hoistedChildrenArrayMatcher(1, 2),
- ])
+ test('cache inside named slot', () => {
+ const root = transformWithCache(
+ `<Foo><template #foo>{{x}}<span/></template></Foo>`,
+ )
expect((root.codegenNode as VNodeCall).children).toMatchObject({
- content: '_hoisted_3',
+ properties: [
+ {
+ key: { content: 'foo' },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: [
+ {
+ type: NodeTypes.TEXT_CALL,
+ },
+ // first slot child cached
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ },
+ },
+ ],
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
})
- expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist components', () => {
- const root = transformWithHoist(`<div><Comp/></div>`)
- expect(root.hoists.length).toBe(0)
+ test('cache named slot as a whole', () => {
+ const root = transformWithCache(
+ `<Foo><template #foo><span/><span/></template></Foo>`,
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { content: 'foo' },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ { type: NodeTypes.ELEMENT },
+ { type: NodeTypes.ELEMENT },
+ ],
+ },
+ },
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
+ })
+ })
+
+ test('cache dynamically named slot as a whole', () => {
+ const root = transformWithCache(
+ `<Foo><template #[foo]><span/><span/></template></Foo>`,
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { content: 'foo', isStatic: false },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ { type: NodeTypes.ELEMENT },
+ { type: NodeTypes.ELEMENT },
+ ],
+ },
+ },
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
+ })
+ })
+
+ test('cache dynamically named (expression) slot as a whole', () => {
+ const root = transformWithCache(
+ `<Foo><template #[foo+1]><span/><span/></template></Foo>`,
+ { prefixIdentifiers: true },
+ )
+ expect((root.codegenNode as VNodeCall).children).toMatchObject({
+ properties: [
+ {
+ key: { type: NodeTypes.COMPOUND_EXPRESSION },
+ value: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ { type: NodeTypes.ELEMENT },
+ { type: NodeTypes.ELEMENT },
+ ],
+ },
+ },
+ },
+ },
+ {
+ /* _ slot flag */
+ },
+ ],
+ })
+ })
+
+ test('should NOT cache components', () => {
+ const root = transformWithCache(`<div><Comp/></div>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
},
},
])
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist element with dynamic props (but hoist the props list)', () => {
- const root = transformWithHoist(`<div><div :id="foo"/></div>`)
+ test('should NOT cache element with dynamic props (but hoist the props list)', () => {
+ const root = transformWithCache(`<div><div :id="foo"/></div>`)
expect(root.hoists.length).toBe(1)
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
},
},
])
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
- test('hoist element with static key', () => {
- const root = transformWithHoist(`<div><div key="foo"/></div>`)
- expect(root.hoists.length).toBe(2)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"div"`,
- props: createObjectMatcher({ key: 'foo' }),
- },
- hoistedChildrenArrayMatcher(),
- ])
+ test('cache element with static key', () => {
+ const root = transformWithCache(`<div><div key="foo"/></div>`)
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
- children: { content: `_hoisted_2` },
+ children: cachedChildrenArrayMatcher(['div']),
})
+ expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist element with dynamic key', () => {
- const root = transformWithHoist(`<div><div :key="foo"/></div>`)
- expect(root.hoists.length).toBe(0)
+ test('should NOT cache element with dynamic key', () => {
+ const root = transformWithCache(`<div><div :key="foo"/></div>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
},
},
])
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist element with dynamic ref', () => {
- const root = transformWithHoist(`<div><div :ref="foo"/></div>`)
- expect(root.hoists.length).toBe(0)
+ test('should NOT cache element with dynamic ref', () => {
+ const root = transformWithCache(`<div><div :ref="foo"/></div>`)
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.ELEMENT,
},
},
])
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with directives', () => {
- const root = transformWithHoist(`<div><div id="foo" v-foo/></div>`)
+ const root = transformWithCache(`<div><div id="foo" v-foo/></div>`)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
},
},
])
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with dynamic text children', () => {
- const root = transformWithHoist(
+ const root = transformWithCache(
`<div><div id="foo">{{ hello }}</div></div>`,
)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
},
},
])
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist static props for elements with unhoistable children', () => {
- const root = transformWithHoist(`<div><div id="foo"><Comp/></div></div>`)
+ const root = transformWithCache(`<div><div id="foo"><Comp/></div></div>`)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
},
},
])
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
- test('should hoist v-if props/children if static', () => {
- const root = transformWithHoist(
+ test('should cache v-if props/children if static', () => {
+ const root = transformWithCache(
`<div><div v-if="ok" id="foo"><span/></div></div>`,
)
expect(root.hoists).toMatchObject([
key: `[0]`, // key injected by v-if branch
id: 'foo',
}),
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- },
- hoistedChildrenArrayMatcher(2),
])
expect(
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode,
).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: {
- // blocks should NOT be hoisted
+ // blocks should NOT be cached
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
- children: { content: `_hoisted_3` },
+ children: cachedChildrenArrayMatcher(['span']),
},
})
+ expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('should hoist v-for children if static', () => {
- const root = transformWithHoist(
+ const root = transformWithCache(
`<div><div v-for="i in list" id="foo"><span/></div></div>`,
)
expect(root.hoists).toMatchObject([
createObjectMatcher({
id: 'foo',
}),
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- },
- hoistedChildrenArrayMatcher(2),
])
const forBlockCodegen = (
(root.children[0] as ElementNode).children[0] as ForNode
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
- children: { content: `_hoisted_3` },
+ children: cachedChildrenArrayMatcher(['span']),
})
+ expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
describe('prefixIdentifiers', () => {
- test('hoist nested static tree with static interpolation', () => {
- const root = transformWithHoist(
+ test('cache nested static tree with static interpolation', () => {
+ const root = transformWithCache(
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
{
prefixIdentifiers: true,
},
)
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- props: undefined,
- children: {
- type: NodeTypes.COMPOUND_EXPRESSION,
- },
- },
- hoistedChildrenArrayMatcher(),
- ])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
- children: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_2`,
- },
+ children: cachedChildrenArrayMatcher(['span']),
})
+ expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
- test('hoist nested static tree with static prop value', () => {
- const root = transformWithHoist(
+ test('cache nested static tree with static prop value', () => {
+ const root = transformWithCache(
`<div><span :foo="0">{{ 1 }}</span></div>`,
{
prefixIdentifiers: true,
},
)
-
- expect(root.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- tag: `"span"`,
- props: createObjectMatcher({ foo: `[0]` }),
- children: {
- type: NodeTypes.INTERPOLATION,
- content: {
- content: `1`,
- isStatic: false,
- constType: ConstantTypes.CAN_STRINGIFY,
- },
- },
- },
- hoistedChildrenArrayMatcher(),
- ])
expect(root.codegenNode).toMatchObject({
tag: `"div"`,
props: undefined,
- children: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `_hoisted_2`,
- },
+ children: cachedChildrenArrayMatcher(['span']),
})
+ expect(root.cached.length).toBe(1)
expect(generate(root).code).toMatchSnapshot()
})
test('hoist class with static object value', () => {
- const root = transformWithHoist(
+ const root = transformWithCache(
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
{
prefixIdentifiers: true,
expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist expressions that refer scope variables', () => {
- const root = transformWithHoist(
+ test('should NOT cache expressions that refer scope variables', () => {
+ const root = transformWithCache(
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
{
prefixIdentifiers: true,
},
)
- expect(root.hoists.length).toBe(0)
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist expressions that refer scope variables (2)', () => {
- const root = transformWithHoist(
+ test('should NOT cache expressions that refer scope variables (2)', () => {
+ const root = transformWithCache(
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
{
prefixIdentifiers: true,
},
)
- expect(root.hoists.length).toBe(0)
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist expressions that refer scope variables (v-slot)', () => {
- const root = transformWithHoist(
+ test('should NOT cache expressions that refer scope variables (v-slot)', () => {
+ const root = transformWithCache(
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
{
prefixIdentifiers: true,
},
)
- expect(root.hoists.length).toBe(0)
+ expect(root.cached.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist elements with cached handlers', () => {
- const root = transformWithHoist(
+ test('should NOT cache elements with cached handlers', () => {
+ const root = transformWithCache(
`<div><div><div @click="foo"/></div></div>`,
{
prefixIdentifiers: true,
},
)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.hoists.length).toBe(0)
expect(
generate(root, {
).toMatchSnapshot()
})
- test('should NOT hoist elements with cached handlers + other bindings', () => {
- const root = transformWithHoist(
+ test('should NOT cache elements with cached handlers + other bindings', () => {
+ const root = transformWithCache(
`<div><div><div :class="{}" @click="foo"/></div></div>`,
{
prefixIdentifiers: true,
},
)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.hoists.length).toBe(0)
expect(
generate(root, {
).toMatchSnapshot()
})
- test('should NOT hoist keyed template v-for with plain element child', () => {
- const root = transformWithHoist(
+ test('should NOT cache keyed template v-for with plain element child', () => {
+ const root = transformWithCache(
`<div><template v-for="item in items" :key="item"><span/></template></div>`,
)
expect(root.hoists.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
- test('should NOT hoist SVG with directives', () => {
- const root = transformWithHoist(
+ test('should NOT cache SVG with directives', () => {
+ const root = transformWithCache(
`<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`,
)
- expect(root.hoists.length).toBe(2)
+ expect(root.cached.length).toBe(1)
+ expect(root.codegenNode).toMatchObject({
+ children: [
+ {
+ tag: 'svg',
+ // only cache the children, not the svg tag itself
+ codegenNode: {
+ children: {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ },
+ },
+ },
+ ],
+ })
expect(generate(root).code).toMatchSnapshot()
})
- test('clone hoisted array children in HMR mode', () => {
- const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
- hmr: true,
- })
- expect(root.hoists.length).toBe(2)
- expect(root.codegenNode).toMatchObject({
+ test('clone hoisted array children in v-for + HMR mode', () => {
+ const root = transformWithCache(
+ `<div><div v-for="i in 1"><span class="hi"></span></div></div>`,
+ {
+ hmr: true,
+ },
+ )
+ expect(root.cached.length).toBe(1)
+ const forBlockCodegen = (
+ (root.children[0] as ElementNode).children[0] as ForNode
+ ).codegenNode
+ expect(forBlockCodegen).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: FRAGMENT,
+ props: undefined,
children: {
- content: '[..._hoisted_2]',
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: RENDER_LIST,
},
+ patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
})
+ const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
+ expect(innerBlockCodegen.returns).toMatchObject({
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ children: cachedChildrenArrayMatcher(
+ ['span'],
+ true /* needArraySpread */,
+ ),
+ })
+ expect(generate(root).code).toMatchSnapshot()
})
})
})
prefixIdentifiers: true,
cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const codegen = (root.children[0] as PlainElementNode)
.codegenNode as VNodeCall
// should not list cached prop in dynamicProps
cacheHandlers: true,
},
)
- expect(root.cached).toBe(0)
+ expect(root.cached.length).toBe(0)
const codegen = (
(root.children[0] as ForNode).children[0] as PlainElementNode
).codegenNode as VNodeCall
cacheHandlers: true,
})
expect(root.cached).not.toBe(2)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
})
test('should mark update handler dynamic if it refers slot scope variables', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
prefixIdentifiers: true,
cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
prefixIdentifiers: true,
cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
cacheHandlers: true,
isNativeTag: tag => tag === 'div',
})
- expect(root.cached).toBe(0)
+ expect(root.cached.length).toBe(0)
})
test('should not be cached inside v-once', () => {
},
)
expect(root.cached).not.toBe(2)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
})
test('inline function expression handler', () => {
prefixIdentifiers: true,
cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
cacheHandlers: true,
},
)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
},
)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
cacheHandlers: true,
},
)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
prefixIdentifiers: true,
cacheHandlers: true,
})
- expect(root.cached).toBe(1)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
+ expect(root.cached.length).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined()
describe('compiler: v-once transform', () => {
test('as root node', () => {
const root = transformWithOnce(`<div :id="foo" v-once />`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
test('on nested plain element', () => {
const root = transformWithOnce(`<div><div :id="foo" v-once /></div>`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
test('on component', () => {
const root = transformWithOnce(`<div><Comp :id="foo" v-once /></div>`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
test('on slot outlet', () => {
const root = transformWithOnce(`<div><slot v-once /></div>`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION,
test('inside v-once', () => {
const root = transformWithOnce(`<div v-once><div v-once/></div>`)
expect(root.cached).not.toBe(2)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
})
// cached nodes should be ignored by hoistStatic transform
const root = transformWithOnce(`<div><div v-once /></div>`, {
hoistStatic: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.hoists.length).toBe(0)
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
test('with v-if/else', () => {
const root = transformWithOnce(`<div v-if="BOOLEAN" v-once /><p v-else/>`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.children[0]).toMatchObject({
type: NodeTypes.IF,
test('with v-for', () => {
const root = transformWithOnce(`<div v-for="i in list" v-once />`)
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
expect(root.children[0]).toMatchObject({
type: NodeTypes.FOR,
directives: string[]
hoists: (JSChildNode | null)[]
imports: ImportItem[]
- cached: number
+ cached: (CacheExpression | null)[]
temps: number
ssrHelpers?: symbol[]
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
export enum ConstantTypes {
NOT_CONSTANT = 0,
CAN_SKIP_PATCH,
- CAN_HOIST,
+ CAN_CACHE,
CAN_STRINGIFY,
}
| SlotsExpression // component slots
| ForRenderListExpression // v-for fragment call
| SimpleExpressionNode // hoisted
+ | CacheExpression // cached
| undefined
patchFlag: string | undefined
dynamicProps: string | SimpleExpressionNode | undefined
type: NodeTypes.JS_CACHE_EXPRESSION
index: number
value: JSChildNode
- isVNode: boolean
+ needPauseTracking: boolean
+ needArraySpread: boolean
}
export interface MemoExpression extends CallExpression {
}
export interface SlotFunctionExpression extends FunctionExpression {
- returns: TemplateChildNode[]
+ returns: TemplateChildNode[] | CacheExpression
}
// createSlots({ ... }, [
directives: [],
hoists: [],
imports: [],
- cached: 0,
+ cached: [],
temps: 0,
codegenNode: undefined,
loc: locStub,
export function createCacheExpression(
index: number,
value: JSChildNode,
- isVNode: boolean = false,
+ needPauseTracking: boolean = false,
): CacheExpression {
return {
type: NodeTypes.JS_CACHE_EXPRESSION,
index,
value,
- isVNode,
+ needPauseTracking: needPauseTracking,
+ needArraySpread: false,
loc: locStub,
}
}
CREATE_TEXT,
CREATE_VNODE,
OPEN_BLOCK,
- POP_SCOPE_ID,
- PUSH_SCOPE_ID,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
RESOLVE_FILTER,
ssrRuntimeModuleName,
} = context
- if (genScopeId && ast.hoists.length) {
- ast.helpers.add(PUSH_SCOPE_ID)
- ast.helpers.add(POP_SCOPE_ID)
- }
-
// generate import statements for helpers
if (ast.helpers.size) {
const helpers = Array.from(ast.helpers)
return
}
context.pure = true
- const { push, newline, helper, scopeId, mode } = context
- const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
+ const { push, newline } = context
newline()
- // generate inlined withScopeId helper
- if (genScopeId) {
- push(
- `const _withScopeId = n => (${helper(
- PUSH_SCOPE_ID,
- )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
- )
- newline()
- }
-
for (let i = 0; i < hoists.length; i++) {
const exp = hoists[i]
if (exp) {
- const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
- push(
- `const _hoisted_${i + 1} = ${
- needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
- }`,
- )
+ push(`const _hoisted_${i + 1} = `)
genNode(exp, context)
- if (needScopeIdWrapper) {
- push(`)`)
- }
newline()
}
}
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
const { push, helper, indent, deindent, newline } = context
+ const { needPauseTracking, needArraySpread } = node
+ if (needArraySpread) {
+ push(`[...(`)
+ }
push(`_cache[${node.index}] || (`)
- if (node.isVNode) {
+ if (needPauseTracking) {
indent()
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
newline()
}
push(`_cache[${node.index}] = `)
genNode(node.value, context)
- if (node.isVNode) {
+ if (needPauseTracking) {
push(`,`)
newline()
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
deindent()
}
push(`)`)
+ if (needArraySpread) {
+ push(`)]`)
+ }
}
function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
type PropsExpression,
} from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
-export { getConstantType } from './transforms/hoistStatic'
+export { getConstantType } from './transforms/cacheStatic'
export { generateCodeFrame } from '@vue/shared'
// v2 compat only
*/
prefixIdentifiers?: boolean
/**
- * Hoist static VNodes and props objects to `_hoisted_x` constants
+ * Cache static VNodes and props objects to `_hoisted_x` constants
* @default false
*/
hoistStatic?: boolean
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``)
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
+/**
+ * @deprecated no longer needed in 3.5+ because we no longer hoist element nodes
+ * but kept for backwards compat
+ */
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
+/**
+ * @deprecated kept for backwards compat
+ */
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
export const UNREF = Symbol(__DEV__ ? `unref` : ``)
helperNameMap,
} from './runtimeHelpers'
import { isVSlot } from './utils'
-import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
+import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic'
import type { CompilerCompatOptions } from './compat/compatConfig'
// There are two types of transforms:
hoists: (JSChildNode | null)[]
imports: ImportItem[]
temps: number
- cached: number
+ cached: (CacheExpression | null)[]
identifiers: { [name: string]: number | undefined }
scopes: {
vFor: number
addIdentifiers(exp: ExpressionNode | string): void
removeIdentifiers(exp: ExpressionNode | string): void
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
- cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
+ cache(exp: JSChildNode, isVNode?: boolean): CacheExpression
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
// 2.x Compat only
directives: new Set(),
hoists: [],
imports: [],
+ cached: [],
constantCache: new WeakMap(),
temps: 0,
- cached: 0,
identifiers: Object.create(null),
scopes: {
vFor: 0,
`_hoisted_${context.hoists.length}`,
false,
exp.loc,
- ConstantTypes.CAN_HOIST,
+ ConstantTypes.CAN_CACHE,
)
identifier.hoisted = exp
return identifier
},
cache(exp, isVNode = false) {
- return createCacheExpression(context.cached++, exp, isVNode)
+ const cacheExp = createCacheExpression(
+ context.cached.length,
+ exp,
+ isVNode,
+ )
+ context.cached.push(cacheExp)
+ return cacheExp
},
}
const context = createTransformContext(root, options)
traverseNode(root, context)
if (options.hoistStatic) {
- hoistStatic(root, context)
+ cacheStatic(root, context)
}
if (!options.ssr) {
createRootCodegen(root, context)
import {
+ type CacheExpression,
type CallExpression,
type ComponentNode,
ConstantTypes,
ElementTypes,
+ type ExpressionNode,
type JSChildNode,
NodeTypes,
type ParentNode,
type PlainElementNode,
type RootNode,
type SimpleExpressionNode,
+ type SlotFunctionExpression,
type TemplateChildNode,
type TemplateNode,
+ type TextCallNode,
type VNodeCall,
createArrayExpression,
getVNodeBlockHelper,
} from '../ast'
import type { TransformContext } from '../transform'
import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
-import { isSlotOutlet } from '../utils'
+import { findDir, isSlotOutlet } from '../utils'
import {
GUARD_REACTIVE_PROPS,
NORMALIZE_CLASS,
OPEN_BLOCK,
} from '../runtimeHelpers'
-export function hoistStatic(root: RootNode, context: TransformContext) {
+export function cacheStatic(root: RootNode, context: TransformContext) {
walk(
root,
+ undefined,
context,
// Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes.
function walk(
node: ParentNode,
+ parent: ParentNode | undefined,
context: TransformContext,
doNotHoistNode: boolean = false,
+ inFor = false,
) {
const { children } = node
- const originalCount = children.length
- let hoistedCount = 0
-
+ const toCache: (PlainElementNode | TextCallNode)[] = []
for (let i = 0; i < children.length; i++) {
const child = children[i]
- // only plain elements & text calls are eligible for hoisting.
+ // only plain elements & text calls are eligible for caching.
if (
child.type === NodeTypes.ELEMENT &&
child.tagType === ElementTypes.ELEMENT
? ConstantTypes.NOT_CONSTANT
: getConstantType(child, context)
if (constantType > ConstantTypes.NOT_CONSTANT) {
- if (constantType >= ConstantTypes.CAN_HOIST) {
+ if (constantType >= ConstantTypes.CAN_CACHE) {
;(child.codegenNode as VNodeCall).patchFlag =
- PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
- child.codegenNode = context.hoist(child.codegenNode!)
- hoistedCount++
+ PatchFlags.CACHED + (__DEV__ ? ` /* CACHED */` : ``)
+ toCache.push(child)
continue
}
} else {
flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) &&
getGeneratedPropsConstantType(child, context) >=
- ConstantTypes.CAN_HOIST
+ ConstantTypes.CAN_CACHE
) {
const props = getNodeProps(child)
if (props) {
}
}
}
+ } else if (child.type === NodeTypes.TEXT_CALL) {
+ const constantType = doNotHoistNode
+ ? ConstantTypes.NOT_CONSTANT
+ : getConstantType(child, context)
+ if (constantType >= ConstantTypes.CAN_CACHE) {
+ toCache.push(child)
+ continue
+ }
}
// walk further
if (isComponent) {
context.scopes.vSlot++
}
- walk(child, context)
+ walk(child, node, context, false, inFor)
if (isComponent) {
context.scopes.vSlot--
}
} else if (child.type === NodeTypes.FOR) {
// Do not hoist v-for single child because it has to be a block
- walk(child, context, child.children.length === 1)
+ walk(child, node, context, child.children.length === 1, true)
} else if (child.type === NodeTypes.IF) {
for (let i = 0; i < child.branches.length; i++) {
// Do not hoist v-if single child because it has to be a block
walk(
child.branches[i],
+ node,
context,
child.branches[i].children.length === 1,
+ inFor,
)
}
}
}
- if (hoistedCount && context.transformHoist) {
- context.transformHoist(children, context, node)
+ let cachedAsArray = false
+ if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
+ if (
+ node.tagType === ElementTypes.ELEMENT &&
+ node.codegenNode &&
+ node.codegenNode.type === NodeTypes.VNODE_CALL &&
+ isArray(node.codegenNode.children)
+ ) {
+ // all children were hoisted - the entire children array is cacheable.
+ node.codegenNode.children = getCacheExpression(
+ createArrayExpression(node.codegenNode.children),
+ )
+ cachedAsArray = true
+ } else if (
+ node.tagType === ElementTypes.COMPONENT &&
+ node.codegenNode &&
+ node.codegenNode.type === NodeTypes.VNODE_CALL &&
+ node.codegenNode.children &&
+ !isArray(node.codegenNode.children) &&
+ node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
+ ) {
+ // default slot
+ const slot = getSlotNode(node.codegenNode, 'default')
+ if (slot) {
+ slot.returns = getCacheExpression(
+ createArrayExpression(slot.returns as TemplateChildNode[]),
+ )
+ cachedAsArray = true
+ }
+ } else if (
+ node.tagType === ElementTypes.TEMPLATE &&
+ parent &&
+ parent.type === NodeTypes.ELEMENT &&
+ parent.tagType === ElementTypes.COMPONENT &&
+ parent.codegenNode &&
+ parent.codegenNode.type === NodeTypes.VNODE_CALL &&
+ parent.codegenNode.children &&
+ !isArray(parent.codegenNode.children) &&
+ parent.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
+ ) {
+ // named <template> slot
+ const slotName = findDir(node, 'slot', true)
+ const slot =
+ slotName &&
+ slotName.arg &&
+ getSlotNode(parent.codegenNode, slotName.arg)
+ if (slot) {
+ slot.returns = getCacheExpression(
+ createArrayExpression(slot.returns as TemplateChildNode[]),
+ )
+ cachedAsArray = true
+ }
+ }
}
- // all children were hoisted - the entire children array is hoistable.
- if (
- hoistedCount &&
- hoistedCount === originalCount &&
- node.type === NodeTypes.ELEMENT &&
- node.tagType === ElementTypes.ELEMENT &&
- node.codegenNode &&
- node.codegenNode.type === NodeTypes.VNODE_CALL &&
- isArray(node.codegenNode.children)
- ) {
- const hoisted = context.hoist(
- createArrayExpression(node.codegenNode.children),
- )
+ if (!cachedAsArray) {
+ for (const child of toCache) {
+ child.codegenNode = context.cache(child.codegenNode!)
+ }
+ }
+
+ function getCacheExpression(value: JSChildNode): CacheExpression {
+ const exp = context.cache(value)
// #6978, #7138, #7114
- // a hoisted children array inside v-for can caused HMR errors since
- // it might be mutated when mounting the v-for list
- if (context.hmr) {
- hoisted.content = `[...${hoisted.content}]`
+ // a cached children array inside v-for can caused HMR errors since
+ // it might be mutated when mounting the first item
+ if (inFor && context.hmr) {
+ exp.needArraySpread = true
+ }
+ return exp
+ }
+
+ function getSlotNode(
+ node: VNodeCall,
+ name: string | ExpressionNode,
+ ): SlotFunctionExpression | undefined {
+ if (
+ node.children &&
+ !isArray(node.children) &&
+ node.children.type === NodeTypes.JS_OBJECT_EXPRESSION
+ ) {
+ const slot = node.children.properties.find(
+ p => p.key === name || (p.key as SimpleExpressionNode).content === name,
+ )
+ return slot && slot.value
}
- node.codegenNode.children = hoisted
+ }
+
+ if (toCache.length && context.transformHoist) {
+ context.transformHoist(children, context, node)
}
}
export function getConstantType(
- node: TemplateChildNode | SimpleExpressionNode,
+ node: TemplateChildNode | SimpleExpressionNode | CacheExpression,
context: TransformContext,
): ConstantTypes {
const { constantCache } = context
}
}
return returnType
+ case NodeTypes.JS_CACHE_EXPRESSION:
+ return ConstantTypes.CAN_CACHE
default:
if (__DEV__) {
const exhaustiveCheck: never = node
toValidAssetId,
} from '../utils'
import { buildSlots } from './vSlot'
-import { getConstantType } from './hoistStatic'
+import { getConstantType } from './cacheStatic'
import { BindingTypes } from '../options'
import {
CompilerDeprecationTypes,
if (isLiteral) {
node.constType = ConstantTypes.CAN_STRINGIFY
} else {
- node.constType = ConstantTypes.CAN_HOIST
+ node.constType = ConstantTypes.CAN_CACHE
}
}
return node
import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers'
import { PatchFlagNames, PatchFlags } from '@vue/shared'
-import { getConstantType } from './hoistStatic'
+import { getConstantType } from './cacheStatic'
// Merge adjacent text nodes and expressions into a single expression
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
renderExp.arguments.push(
loop as ForIteratorExpression,
createSimpleExpression(`_cache`),
- createSimpleExpression(String(context.cached++)),
+ createSimpleExpression(String(context.cached.length)),
)
+ // increment cache count
+ context.cached.push(null)
} else {
renderExp.arguments.push(
createFunctionExpression(
`${keyIndex}`,
false,
locStub,
- ConstantTypes.CAN_HOIST,
+ ConstantTypes.CAN_CACHE,
),
)
const { children } = branch
dir.exp!,
createFunctionExpression(undefined, codegenNode),
`_cache`,
- String(context.cached++),
+ String(context.cached.length),
]) as MemoExpression
+ // increment cache count
+ context.cached.push(null)
}
}
}
`{ ${modifiers} }`,
false,
dir.loc,
- ConstantTypes.CAN_HOIST,
+ ConstantTypes.CAN_CACHE,
),
),
)
import {
type BlockCodegenNode,
+ type CacheExpression,
type CallExpression,
type DirectiveNode,
type ElementNode,
// Check if a node contains expressions that reference current context scope ids
export function hasScopeRef(
- node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined,
+ node:
+ | TemplateChildNode
+ | IfBranchNode
+ | ExpressionNode
+ | CacheExpression
+ | undefined,
ids: TransformContext['identifiers'],
): boolean {
if (!node || Object.keys(ids).length === 0) {
return hasScopeRef(node.content, ids)
case NodeTypes.TEXT:
case NodeTypes.COMMENT:
+ case NodeTypes.JS_CACHE_EXPRESSION:
return false
default:
if (__DEV__) {
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`stringify static html > should bail for <option> elements with number values 1`] = `
-"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+exports[`stringify static html > escape 1`] = `
+"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("select", null, [
- /*#__PURE__*/_createElementVNode("option", { value: 1 }),
- /*#__PURE__*/_createElementVNode("option", { value: 1 }),
- /*#__PURE__*/_createElementVNode("option", { value: 1 }),
- /*#__PURE__*/_createElementVNode("option", { value: 1 }),
- /*#__PURE__*/_createElementVNode("option", { value: 1 })
-], -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 1)
+ ])))
+}"
+`;
+
+exports[`stringify static html > serializing constant bindings 1`] = `
+"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
+ ])))
}"
`;
-exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
+exports[`stringify static html > should bail for <option> elements with number values 1`] = `
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [
- /*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
- /*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
- /*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
- /*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
- /*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
- /*#__PURE__*/_createElementVNode("img", { src: _imports_0_ })
-], -1 /* HOISTED */)
-const _hoisted_2 = [
- _hoisted_1
-]
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("select", null, [
+ _createElementVNode("option", { value: 1 }),
+ _createElementVNode("option", { value: 1 }),
+ _createElementVNode("option", { value: 1 }),
+ _createElementVNode("option", { value: 1 }),
+ _createElementVNode("option", { value: 1 })
+ ], -1 /* CACHED */)
+ ])))
+}"
+`;
+
+exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = `
+"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", null, [
+ _createElementVNode("span", { class: "foo" }, "foo"),
+ _createElementVNode("span", { class: "foo" }, "foo"),
+ _createElementVNode("span", { class: "foo" }, "foo"),
+ _createElementVNode("span", { class: "foo" }, "foo"),
+ _createElementVNode("span", { class: "foo" }, "foo"),
+ _createElementVNode("img", { src: _imports_0_ })
+ ], -1 /* CACHED */)
+ ])))
}"
`;
exports[`stringify static html > should work for <option> elements with string values 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
-const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
-const _hoisted_2 = [
- _hoisted_1
-]
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
+ ])))
+}"
+`;
+
+exports[`stringify static html > should work for multiple adjacent nodes 1`] = `
+"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
+ ])))
}"
`;
-exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
+exports[`stringify static html > should work on eligible content (elements > 20) 1`] = `
+"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
+ ])))
+}"
+`;
+
+exports[`stringify static html > should work on eligible content (elements with binding > 5) 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
-const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
-const _hoisted_2 = [
- _hoisted_1
-]
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
+ ])))
+}"
+`;
+
+exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
+"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
+ ])))
}"
`;
exports[`stringify static html > stringify v-html 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
-const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2)
-
return function render(_ctx, _cache) {
- return _hoisted_1
+ return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2))
}"
`;
exports[`stringify static html > stringify v-text 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
-const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2)
-
return function render(_ctx, _cache) {
- return _hoisted_1
+ return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2))
}"
`;
exports[`stringify static html > stringify v-text with escape 1`] = `
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
-const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code>text1</code></pre><div class><span class>1</span><span class>2</span></div>", 2)
-
return function render(_ctx, _cache) {
- return _hoisted_1
+ return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code>text1</code></pre><div class><span class>1</span><span class>2</span></div>", 2))
}"
`;
return code.repeat(n)
}
+ /**
+ * Assert cached node NOT stringified
+ */
+ function cachedArrayBailedMatcher(n = 1) {
+ return {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: new Array(n).fill(0).map(() => ({
+ // should remain VNODE_CALL instead of JS_CALL_EXPRESSION
+ codegenNode: { type: NodeTypes.VNODE_CALL },
+ })),
+ },
+ }
+ }
+
+ /**
+ * Assert cached node is stringified (no content check)
+ */
+ function cachedArraySuccessMatcher(n = 1) {
+ return {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: new Array(n).fill(0).map(() => ({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: CREATE_STATIC,
+ })),
+ },
+ }
+ }
+
+ /**
+ * Assert cached node stringified with desired content and node count
+ */
+ function cachedArrayStaticNodeMatcher(content: string, count: number) {
+ return {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: CREATE_STATIC,
+ arguments: [JSON.stringify(content), String(count)],
+ },
+ ],
+ },
+ }
+ }
+
test('should bail on non-eligible static trees', () => {
const { ast } = compileWithStringify(
`<div><div><div>hello</div><div>hello</div></div></div>`,
)
- // should be a normal vnode call
- expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
+ // should be cached children array
+ expect(ast.cached[0]!.value.type).toBe(NodeTypes.JS_ARRAY_EXPRESSION)
})
test('should work on eligible content (elements with binding > 5)', () => {
- const { ast } = compileWithStringify(
+ const { code, ast } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo"/>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>`,
)
+
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `<div>${repeat(
- `<span class="foo"></span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- )}</div>`,
- ),
- '1',
- ],
- }, // the children array is hoisted as well
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `<div>${repeat(
+ `<span class="foo"></span>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
+ 1,
+ ),
])
+
+ expect(code).toMatchSnapshot()
})
test('should work on eligible content (elements > 20)', () => {
- const { ast } = compileWithStringify(
+ const { code, ast } = compileWithStringify(
`<div><div>${repeat(
`<span/>`,
StringifyThresholds.NODE_COUNT,
)}</div></div>`,
)
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `<div>${repeat(
- `<span></span>`,
- StringifyThresholds.NODE_COUNT,
- )}</div>`,
- ),
- '1',
- ],
- },
- // the children array is hoisted as well
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `<div>${repeat(`<span></span>`, StringifyThresholds.NODE_COUNT)}</div>`,
+ 1,
+ ),
])
+
+ expect(code).toMatchSnapshot()
})
test('should work for multiple adjacent nodes', () => {
- const { ast } = compileWithStringify(
+ const { ast, code } = compileWithStringify(
`<div>${repeat(
`<span class="foo"/>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
)
- // should have 6 hoisted nodes (including the entire array),
- // but 2~5 should be null because they are merged into 1
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- repeat(
- `<span class="foo"></span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- ),
- ),
- '5',
- ],
- },
- null,
- null,
- null,
- null,
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ repeat(
+ `<span class="foo"></span>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ ),
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ ),
])
+
+ expect(code).toMatchSnapshot()
})
test('serializing constant bindings', () => {
- const { ast } = compileWithStringify(
+ const { ast, code } = compileWithStringify(
`<div><div :style="{ color: 'red' }">${repeat(
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>`,
)
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `<div style="color:red;">${repeat(
- `<span class="foo bar">1 + false</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- )}</div>`,
- ),
- '1',
- ],
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `<div style="color:red;">${repeat(
+ `<span class="foo bar">1 + false</span>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
+ 1,
+ ),
])
+ expect(code).toMatchSnapshot()
})
test('escape', () => {
- const { ast } = compileWithStringify(
+ const { ast, code } = compileWithStringify(
`<div><div>${repeat(
`<span :class="'foo' + '>ar'">{{ 1 }} + {{ '<' }}</span>` +
`<span>&</span>`,
)}</div></div>`,
)
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `<div>${repeat(
- `<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- )}</div>`,
- ),
- '1',
- ],
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `<div>${repeat(
+ `<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</div>`,
+ 1,
+ ),
])
+ expect(code).toMatchSnapshot()
})
- test('should bail on bindings that are hoisted but not stringifiable', () => {
+ test('should bail on bindings that are cached but not stringifiable', () => {
const { ast, code } = compile(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
'_imports_0_',
false,
node.loc,
- ConstantTypes.CAN_HOIST,
+ ConstantTypes.CAN_CACHE,
)
node.props[0] = {
type: NodeTypes.DIRECTIVE,
],
},
)
- expect(ast.hoists).toMatchObject([
- {
- // the expression and the tree are still hoistable
- // but should stay NodeTypes.VNODE_CALL
- // if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION
- type: NodeTypes.VNODE_CALL,
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
- ])
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
expect(code).toMatchSnapshot()
})
],
},
)
- expect(ast.hoists).toMatchObject([
- {
- // the hoisted node should be NodeTypes.JS_CALL_EXPRESSION
- // of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL
- type: NodeTypes.JS_CALL_EXPRESSION,
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
- ])
+ expect(ast.cached).toMatchObject([cachedArraySuccessMatcher()])
expect(code).toMatchSnapshot()
})
// #1128
- test('should bail on non attribute bindings', () => {
+ test('should bail on non-attribute bindings', () => {
const { ast } = compileWithStringify(
`<div><div><input indeterminate>${repeat(
`<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>`,
)
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
- ])
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
const { ast: ast2 } = compileWithStringify(
`<div><div><input :indeterminate="true">${repeat(
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>`,
)
- expect(ast2.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
- ])
- })
+ expect(ast2.cached).toMatchObject([cachedArrayBailedMatcher()])
- test('should bail on non attribute bindings', () => {
- const { ast } = compileWithStringify(
+ const { ast: ast3 } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<input indeterminate></div></div>`,
)
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
- ])
+ expect(ast3.cached).toMatchObject([cachedArrayBailedMatcher()])
- const { ast: ast2 } = compileWithStringify(
+ const { ast: ast4 } = compileWithStringify(
`<div><div>${repeat(
`<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<input :indeterminate="true"></div></div>`,
)
- expect(ast2.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
- ])
+ expect(ast4.cached).toMatchObject([cachedArrayBailedMatcher()])
})
test('should bail on tags that has placement constraints (eg.tables related tags)', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</tbody></table>`,
)
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
- ])
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
})
test('should bail inside slots', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</foo>`,
)
- expect(ast.hoists.length).toBe(
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- )
- ast.hoists.forEach(node => {
- expect(node).toMatchObject({
- type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
- })
- })
+ expect(ast.cached).toMatchObject([
+ cachedArrayBailedMatcher(StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
+ ])
const { ast: ast2 } = compileWithStringify(
`<foo><template #foo>${repeat(
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</template></foo>`,
)
- expect(ast2.hoists.length).toBe(
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- )
- ast2.hoists.forEach(node => {
- expect(node).toMatchObject({
- type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
- })
- })
+ expect(ast2.cached).toMatchObject([
+ cachedArrayBailedMatcher(StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
+ ])
})
test('should remove attribute for `null`', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
)
- expect(ast.hoists[0]).toMatchObject({
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `${repeat(
- `<span></span>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- )}`,
- ),
- '5',
- ],
- })
+
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ repeat(`<span></span>`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ ),
+ ])
})
// #6617
StringifyThresholds.NODE_COUNT,
)}`,
)
- expect(ast.hoists[0]).toMatchObject({
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `<button>enable</button>${repeat(
- `<div></div>`,
- StringifyThresholds.NODE_COUNT,
- )}`,
- ),
- '21',
- ],
- })
+ expect(ast.cached).toMatchObject([
+ {
+ type: NodeTypes.JS_CACHE_EXPRESSION,
+ value: {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: CREATE_STATIC,
+ arguments: [
+ JSON.stringify(
+ `<button>enable</button>${repeat(
+ `<div></div>`,
+ StringifyThresholds.NODE_COUNT,
+ )}`,
+ ),
+ '21',
+ ],
+ },
+ },
+ ])
})
test('should stringify svg', () => {
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</svg></div>`,
)
- expect(ast.hoists[0]).toMatchObject({
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `${svg}${repeat(
- repeated,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- )}</svg>`,
- ),
- '1',
- ],
- })
+
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `${svg}${repeat(
+ repeated,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</svg>`,
+ 1,
+ ),
+ ])
})
// #5439
)}</select></div>`,
)
// should be optimized now
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.JS_CALL_EXPRESSION,
- callee: CREATE_STATIC,
- arguments: [
- JSON.stringify(
- `<select>${repeat(
- `<option value="1"></option>`,
- StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
- )}</select>`,
- ),
- '1',
- ],
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
+ expect(ast.cached).toMatchObject([
+ cachedArrayStaticNodeMatcher(
+ `<select>${repeat(
+ `<option value="1"></option>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
+ )}</select>`,
+ 1,
+ ),
])
expect(code).toMatchSnapshot()
})
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</select></div>`,
)
- expect(ast.hoists).toMatchObject([
- {
- type: NodeTypes.VNODE_CALL,
- },
- {
- type: NodeTypes.JS_ARRAY_EXPRESSION,
- },
- ])
+ expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
expect(code).toMatchSnapshot()
})
})
prefixIdentifiers: true,
cacheHandlers: true,
})
- expect(root.cached).toBe(1)
+ expect(root.cached.length).toBe(1)
// should not treat cached handler as dynamicProp, so it should have no
// dynamicProps flags and only the hydration flag
expect((root as any).children[0].codegenNode.patchFlag).toBe(
*/
import {
CREATE_STATIC,
+ type CacheExpression,
ConstantTypes,
type ElementNode,
ElementTypes,
type ExpressionNode,
type HoistTransform,
- type JSChildNode,
Namespaces,
NodeTypes,
type PlainElementNode,
type TemplateChildNode,
type TextCallNode,
type TransformContext,
+ type VNodeCall,
+ createArrayExpression,
createCallExpression,
isStaticArgOf,
} from '@vue/compiler-core'
import {
escapeHtml,
+ isArray,
isBooleanAttr,
isKnownHtmlAttr,
isKnownSvgAttr,
return
}
+ const isParentCached =
+ parent.type === NodeTypes.ELEMENT &&
+ parent.codegenNode &&
+ parent.codegenNode.type === NodeTypes.VNODE_CALL &&
+ parent.codegenNode.children &&
+ !isArray(parent.codegenNode.children) &&
+ parent.codegenNode.children.type === NodeTypes.JS_CACHE_EXPRESSION
+
let nc = 0 // current node count
let ec = 0 // current element with binding count
const currentChunk: StringifiableNode[] = []
// will insert / hydrate
String(currentChunk.length),
])
- // replace the first node's hoisted expression with the static vnode call
- replaceHoist(currentChunk[0], staticCall, context)
- if (currentChunk.length > 1) {
- for (let i = 1; i < currentChunk.length; i++) {
- // for the merged nodes, set their hoisted expression to null
- replaceHoist(currentChunk[i], null, context)
+ if (isParentCached) {
+ ;((parent.codegenNode as VNodeCall).children as CacheExpression).value =
+ createArrayExpression([staticCall])
+ } else {
+ // replace the first node's hoisted expression with the static vnode call
+ ;(currentChunk[0].codegenNode as CacheExpression).value = staticCall
+ if (currentChunk.length > 1) {
+ // remove merged nodes from children
+ const deleteCount = currentChunk.length - 1
+ children.splice(currentIndex - currentChunk.length + 1, deleteCount)
+ // also adjust index for the remaining cache items
+ const cacheIndex = context.cached.indexOf(
+ currentChunk[currentChunk.length - 1]
+ .codegenNode as CacheExpression,
+ )
+ if (cacheIndex > -1) {
+ for (let i = cacheIndex; i < context.cached.length; i++) {
+ const c = context.cached[i]
+ if (c) c.index -= deleteCount
+ }
+ context.cached.splice(cacheIndex - deleteCount + 1, deleteCount)
+ }
+ return deleteCount
}
-
- // also remove merged nodes from children
- const deleteCount = currentChunk.length - 1
- children.splice(currentIndex - currentChunk.length + 1, deleteCount)
- return deleteCount
}
}
return 0
let i = 0
for (; i < children.length; i++) {
const child = children[i]
- const hoisted = getHoistedNode(child)
- if (hoisted) {
- // presence of hoisted means child must be a stringifiable node
- const node = child as StringifiableNode
- const result = analyzeNode(node)
+ const isCached = isParentCached || getCachedNode(child)
+ if (isCached) {
+ // presence of cached means child must be a stringifiable node
+ const result = analyzeNode(child as StringifiableNode)
if (result) {
// node is stringifiable, record state
nc += result[0]
ec += result[1]
- currentChunk.push(node)
+ currentChunk.push(child as StringifiableNode)
continue
}
}
stringifyCurrentChunk(i)
}
-const getHoistedNode = (node: TemplateChildNode) =>
- ((node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.ELEMENT) ||
- node.type == NodeTypes.TEXT_CALL) &&
- node.codegenNode &&
- node.codegenNode.type === NodeTypes.SIMPLE_EXPRESSION &&
- node.codegenNode.hoisted
+const getCachedNode = (
+ node: TemplateChildNode,
+): CacheExpression | undefined => {
+ if (
+ ((node.type === NodeTypes.ELEMENT &&
+ node.tagType === ElementTypes.ELEMENT) ||
+ node.type === NodeTypes.TEXT_CALL) &&
+ node.codegenNode &&
+ node.codegenNode.type === NodeTypes.JS_CACHE_EXPRESSION
+ ) {
+ return node.codegenNode
+ }
+}
const dataAriaRE = /^(data|aria)-/
const isStringifiableAttr = (name: string, ns: Namespaces) => {
)
}
-const replaceHoist = (
- node: StringifiableNode,
- replacement: JSChildNode | null,
- context: TransformContext,
-) => {
- const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted!
- context.hoists[context.hoists.indexOf(hoistToReplace)] = replacement
-}
-
const isNonStringifiable = /*#__PURE__*/ makeMap(
`caption,thead,tr,th,tbody,td,tfoot,colgroup,col`,
)
/**
- * for a hoisted node, analyze it and return:
+ * for a cached node, analyze it and return:
* - false: bailed (contains non-stringifiable props or runtime constant)
* - [nc, ec] where
* - nc is the number of nodes inside
} else if (c.type === NodeTypes.INTERPOLATION) {
res += toDisplayString(evaluateConstant(c.content))
} else {
- res += evaluateConstant(c)
+ res += evaluateConstant(c as ExpressionNode)
}
})
return res
exports[`SFC compile <script setup> > inlineTemplate mode > should work 1`] = `
"import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, "static", -1 /* HOISTED */)
-
import { ref } from 'vue'
export default {
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("div", null, _toDisplayString(count.value), 1 /* TEXT */),
- _hoisted_1
+ _cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}
}
const _hoisted_1 = _imports_0 + '#fragment'
-const _hoisted_2 = /*#__PURE__*/_createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)
-const _hoisted_3 = /*#__PURE__*/_createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _hoisted_2,
- _hoisted_3
+ _cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */)),
+ _cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
import _imports_1 from '/bar.png'
-const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
-const _hoisted_6 = [
- _hoisted_1
-]
-
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _hoisted_6))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
+ ])))
}"
`;
const _hoisted_1 = _imports_0 + ', ' + _imports_0 + ' 2x'
const _hoisted_2 = _imports_0 + ' 1x, ' + "/foo/logo.png" + ' 2x'
-const _hoisted_3 = /*#__PURE__*/_createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* HOISTED */)
-const _hoisted_4 = /*#__PURE__*/_createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _hoisted_3,
- _hoisted_4
+ _cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* CACHED */)),
+ _cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_8 = "/logo.png" + ', ' + _imports_0 + ' 2x'
-const _hoisted_9 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: ""
-}, null, -1 /* HOISTED */)
-const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_1
-}, null, -1 /* HOISTED */)
-const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_2
-}, null, -1 /* HOISTED */)
-const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_3
-}, null, -1 /* HOISTED */)
-const _hoisted_13 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_4
-}, null, -1 /* HOISTED */)
-const _hoisted_14 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_5
-}, null, -1 /* HOISTED */)
-const _hoisted_15 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_6
-}, null, -1 /* HOISTED */)
-const _hoisted_16 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_7
-}, null, -1 /* HOISTED */)
-const _hoisted_17 = /*#__PURE__*/_createElementVNode("img", {
- src: "/logo.png",
- srcset: "/logo.png, /logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_18 = /*#__PURE__*/_createElementVNode("img", {
- src: "https://example.com/logo.png",
- srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_19 = /*#__PURE__*/_createElementVNode("img", {
- src: "/logo.png",
- srcset: _hoisted_8
-}, null, -1 /* HOISTED */)
-const _hoisted_20 = /*#__PURE__*/_createElementVNode("img", {
- src: "data:image/png;base64,i",
- srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
-}, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _hoisted_9,
- _hoisted_10,
- _hoisted_11,
- _hoisted_12,
- _hoisted_13,
- _hoisted_14,
- _hoisted_15,
- _hoisted_16,
- _hoisted_17,
- _hoisted_18,
- _hoisted_19,
- _hoisted_20
+ _cache[0] || (_cache[0] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: ""
+ }, null, -1 /* CACHED */)),
+ _cache[1] || (_cache[1] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_1
+ }, null, -1 /* CACHED */)),
+ _cache[2] || (_cache[2] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_2
+ }, null, -1 /* CACHED */)),
+ _cache[3] || (_cache[3] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_3
+ }, null, -1 /* CACHED */)),
+ _cache[4] || (_cache[4] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_4
+ }, null, -1 /* CACHED */)),
+ _cache[5] || (_cache[5] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_5
+ }, null, -1 /* CACHED */)),
+ _cache[6] || (_cache[6] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_6
+ }, null, -1 /* CACHED */)),
+ _cache[7] || (_cache[7] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_7
+ }, null, -1 /* CACHED */)),
+ _cache[8] || (_cache[8] = _createElementVNode("img", {
+ src: "/logo.png",
+ srcset: "/logo.png, /logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[9] || (_cache[9] = _createElementVNode("img", {
+ src: "https://example.com/logo.png",
+ srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[10] || (_cache[10] = _createElementVNode("img", {
+ src: "/logo.png",
+ srcset: _hoisted_8
+ }, null, -1 /* CACHED */)),
+ _cache[11] || (_cache[11] = _createElementVNode("img", {
+ src: "data:image/png;base64,i",
+ srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
+ }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
exports[`compiler sfc: transform srcset > transform srcset w/ base 1`] = `
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
-const _hoisted_1 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: ""
-}, null, -1 /* HOISTED */)
-const _hoisted_2 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: "/foo/logo.png"
-}, null, -1 /* HOISTED */)
-const _hoisted_3 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: "/foo/logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_4 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: "/foo/logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_5 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: "/foo/logo.png, /foo/logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_6 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: "/foo/logo.png 2x, /foo/logo.png"
-}, null, -1 /* HOISTED */)
-const _hoisted_7 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
-}, null, -1 /* HOISTED */)
-const _hoisted_8 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
-}, null, -1 /* HOISTED */)
-const _hoisted_9 = /*#__PURE__*/_createElementVNode("img", {
- src: "/logo.png",
- srcset: "/logo.png, /logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
- src: "https://example.com/logo.png",
- srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
- src: "/logo.png",
- srcset: "/logo.png, /foo/logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
- src: "data:image/png;base64,i",
- srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
-}, null, -1 /* HOISTED */)
-
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _hoisted_1,
- _hoisted_2,
- _hoisted_3,
- _hoisted_4,
- _hoisted_5,
- _hoisted_6,
- _hoisted_7,
- _hoisted_8,
- _hoisted_9,
- _hoisted_10,
- _hoisted_11,
- _hoisted_12
+ _cache[0] || (_cache[0] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: ""
+ }, null, -1 /* CACHED */)),
+ _cache[1] || (_cache[1] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: "/foo/logo.png"
+ }, null, -1 /* CACHED */)),
+ _cache[2] || (_cache[2] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: "/foo/logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[3] || (_cache[3] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: "/foo/logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[4] || (_cache[4] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: "/foo/logo.png, /foo/logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[5] || (_cache[5] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: "/foo/logo.png 2x, /foo/logo.png"
+ }, null, -1 /* CACHED */)),
+ _cache[6] || (_cache[6] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
+ }, null, -1 /* CACHED */)),
+ _cache[7] || (_cache[7] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
+ }, null, -1 /* CACHED */)),
+ _cache[8] || (_cache[8] = _createElementVNode("img", {
+ src: "/logo.png",
+ srcset: "/logo.png, /logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[9] || (_cache[9] = _createElementVNode("img", {
+ src: "https://example.com/logo.png",
+ srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[10] || (_cache[10] = _createElementVNode("img", {
+ src: "/logo.png",
+ srcset: "/logo.png, /foo/logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[11] || (_cache[11] = _createElementVNode("img", {
+ src: "data:image/png;base64,i",
+ srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
+ }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
-const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: ""
-}, null, -1 /* HOISTED */)
-const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_1
-}, null, -1 /* HOISTED */)
-const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_2
-}, null, -1 /* HOISTED */)
-const _hoisted_13 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_3
-}, null, -1 /* HOISTED */)
-const _hoisted_14 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_4
-}, null, -1 /* HOISTED */)
-const _hoisted_15 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_5
-}, null, -1 /* HOISTED */)
-const _hoisted_16 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_6
-}, null, -1 /* HOISTED */)
-const _hoisted_17 = /*#__PURE__*/_createElementVNode("img", {
- src: "./logo.png",
- srcset: _hoisted_7
-}, null, -1 /* HOISTED */)
-const _hoisted_18 = /*#__PURE__*/_createElementVNode("img", {
- src: "/logo.png",
- srcset: _hoisted_8
-}, null, -1 /* HOISTED */)
-const _hoisted_19 = /*#__PURE__*/_createElementVNode("img", {
- src: "https://example.com/logo.png",
- srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
-}, null, -1 /* HOISTED */)
-const _hoisted_20 = /*#__PURE__*/_createElementVNode("img", {
- src: "/logo.png",
- srcset: _hoisted_9
-}, null, -1 /* HOISTED */)
-const _hoisted_21 = /*#__PURE__*/_createElementVNode("img", {
- src: "data:image/png;base64,i",
- srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
-}, null, -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
- _hoisted_10,
- _hoisted_11,
- _hoisted_12,
- _hoisted_13,
- _hoisted_14,
- _hoisted_15,
- _hoisted_16,
- _hoisted_17,
- _hoisted_18,
- _hoisted_19,
- _hoisted_20,
- _hoisted_21
+ _cache[0] || (_cache[0] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: ""
+ }, null, -1 /* CACHED */)),
+ _cache[1] || (_cache[1] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_1
+ }, null, -1 /* CACHED */)),
+ _cache[2] || (_cache[2] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_2
+ }, null, -1 /* CACHED */)),
+ _cache[3] || (_cache[3] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_3
+ }, null, -1 /* CACHED */)),
+ _cache[4] || (_cache[4] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_4
+ }, null, -1 /* CACHED */)),
+ _cache[5] || (_cache[5] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_5
+ }, null, -1 /* CACHED */)),
+ _cache[6] || (_cache[6] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_6
+ }, null, -1 /* CACHED */)),
+ _cache[7] || (_cache[7] = _createElementVNode("img", {
+ src: "./logo.png",
+ srcset: _hoisted_7
+ }, null, -1 /* CACHED */)),
+ _cache[8] || (_cache[8] = _createElementVNode("img", {
+ src: "/logo.png",
+ srcset: _hoisted_8
+ }, null, -1 /* CACHED */)),
+ _cache[9] || (_cache[9] = _createElementVNode("img", {
+ src: "https://example.com/logo.png",
+ srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
+ }, null, -1 /* CACHED */)),
+ _cache[10] || (_cache[10] = _createElementVNode("img", {
+ src: "/logo.png",
+ srcset: _hoisted_9
+ }, null, -1 /* CACHED */)),
+ _cache[11] || (_cache[11] = _createElementVNode("img", {
+ src: "data:image/png;base64,i",
+ srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
+ }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
-const _hoisted_10 = /*#__PURE__*/_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
-const _hoisted_22 = [
- _hoisted_10
-]
export function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _hoisted_22))
+ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ _createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
+ ])))
}"
`;
'input',
{ type: 'checkbox', indeterminate: '' },
null,
- PatchFlags.HOISTED,
+ PatchFlags.CACHED,
),
)
expect((container.firstChild as any).indeterminate).toBe(true)
const forcePatch = type === 'input' || type === 'option'
// skip props & children if this is hoisted static nodes
// #5405 in dev, always hydrate children for HMR
- if (__DEV__ || forcePatch || patchFlag !== PatchFlags.HOISTED) {
+ if (__DEV__ || forcePatch || patchFlag !== PatchFlags.CACHED) {
if (dirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'created')
}
scopeId: vnode.scopeId,
slotScopeIds: vnode.slotScopeIds,
children:
- __DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
+ __DEV__ && patchFlag === PatchFlags.CACHED && isArray(children)
? (children as VNode[]).map(deepCloneVNode)
: children,
target: vnode.target,
// fast paths only.
patchFlag:
extraProps && vnode.type !== Fragment
- ? patchFlag === PatchFlags.HOISTED // hoisted node
+ ? patchFlag === PatchFlags.CACHED // hoisted node
? PatchFlags.FULL_PROPS
: patchFlag | PatchFlags.FULL_PROPS
: patchFlag,
// optimized normalization for template-compiled render fns
export function cloneIfMounted(child: VNode): VNode {
- return (child.el === null && child.patchFlag !== PatchFlags.HOISTED) ||
+ return (child.el === null && child.patchFlag !== PatchFlags.CACHED) ||
child.memo
? child
: cloneVNode(child)
*/
/**
- * Indicates a hoisted static vnode. This is a hint for hydration to skip
+ * Indicates a cached static vnode. This is also a hint for hydration to skip
* the entire sub tree since static content never needs to be updated.
*/
- HOISTED = -1,
+ CACHED = -1,
/**
* A special flag that indicates that the diffing algorithm should bail out
* of optimized mode. For example, on block fragments created by renderSlot()
[PatchFlags.NEED_PATCH]: `NEED_PATCH`,
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
[PatchFlags.DEV_ROOT_FRAGMENT]: `DEV_ROOT_FRAGMENT`,
- [PatchFlags.HOISTED]: `HOISTED`,
+ [PatchFlags.CACHED]: `HOISTED`,
[PatchFlags.BAIL]: `BAIL`,
}