fix(compiler-core): hoist pure annotations should apply to all nested calls

This commit is contained in:
Evan You 2020-05-01 18:36:34 -04:00
parent cad25d95a3
commit c5e7d8b532
4 changed files with 56 additions and 46 deletions

View File

@ -2,14 +2,14 @@
exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = ` exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = `
"import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\" "import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/ _withScopeId(\\"test\\") const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
_pushScopeId(\\"test\\") _pushScopeId(\\"test\\")
const _hoisted_1 = /*#__PURE__*/ _createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/ _createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */)
_popScopeId() _popScopeId()
export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1, _hoisted_1,
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */), _createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
@ -20,9 +20,9 @@ export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) {
exports[`scopeId compiler support should wrap default slot 1`] = ` exports[`scopeId compiler support should wrap default slot 1`] = `
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\" "import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/ _withScopeId(\\"test\\") const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
const _component_Child = _resolveComponent(\\"Child\\") const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, { return (_openBlock(), _createBlock(_component_Child, null, {
@ -36,9 +36,9 @@ export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) {
exports[`scopeId compiler support should wrap dynamic slots 1`] = ` exports[`scopeId compiler support should wrap dynamic slots 1`] = `
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\" "import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/ _withScopeId(\\"test\\") const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
const _component_Child = _resolveComponent(\\"Child\\") const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 1 }, [ return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 1 }, [
@ -64,9 +64,9 @@ export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) {
exports[`scopeId compiler support should wrap named slots 1`] = ` exports[`scopeId compiler support should wrap named slots 1`] = `
"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\" "import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/ _withScopeId(\\"test\\") const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
const _component_Child = _resolveComponent(\\"Child\\") const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, { return (_openBlock(), _createBlock(_component_Child, null, {
@ -83,9 +83,9 @@ export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) {
exports[`scopeId compiler support should wrap render function 1`] = ` exports[`scopeId compiler support should wrap render function 1`] = `
"import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\" "import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/ _withScopeId(\\"test\\") const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export const render = /*#__PURE__*/ _withId(function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId(function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\")) return (_openBlock(), _createBlock(\\"div\\"))
})" })"
`; `;

View File

@ -20,9 +20,9 @@ describe('scopeId compiler support', () => {
scopeId: 'test' scopeId: 'test'
}) })
expect(ast.helpers).toContain(WITH_SCOPE_ID) expect(ast.helpers).toContain(WITH_SCOPE_ID)
expect(code).toMatch(`const _withId = /*#__PURE__*/ _withScopeId("test")`) expect(code).toMatch(`const _withId = /*#__PURE__*/_withScopeId("test")`)
expect(code).toMatch( expect(code).toMatch(
`export const render = /*#__PURE__*/ _withId(function render(` `export const render = /*#__PURE__*/_withId(function render(`
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -85,10 +85,10 @@ describe('scopeId compiler support', () => {
expect(code).toMatch( expect(code).toMatch(
[ [
`_pushScopeId("test")`, `_pushScopeId("test")`,
`const _hoisted_1 = /*#__PURE__*/ _createVNode("div", null, "hello", ${genFlagText( `const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, "hello", ${genFlagText(
PatchFlags.HOISTED PatchFlags.HOISTED
)})`, )})`,
`const _hoisted_2 = /*#__PURE__*/ _createVNode("div", null, "world", ${genFlagText( `const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "world", ${genFlagText(
PatchFlags.HOISTED PatchFlags.HOISTED
)})`, )})`,
`_popScopeId()` `_popScopeId()`

View File

@ -4,7 +4,7 @@ exports[`compiler: hoistStatic transform hoist element with static key 1`] = `
"const _Vue = Vue "const _Vue = Vue
const { createVNode: _createVNode } = _Vue const { createVNode: _createVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/ _createVNode(\\"div\\", { key: \\"foo\\" }, null, -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", { key: \\"foo\\" }, null, -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
@ -21,9 +21,9 @@ exports[`compiler: hoistStatic transform hoist nested static tree 1`] = `
"const _Vue = Vue "const _Vue = Vue
const { createVNode: _createVNode } = _Vue const { createVNode: _createVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/ _createVNode(\\"p\\", null, [ const _hoisted_1 = /*#__PURE__*/_createVNode(\\"p\\", null, [
_createVNode(\\"span\\"), /*#__PURE__*/_createVNode(\\"span\\"),
_createVNode(\\"span\\") /*#__PURE__*/_createVNode(\\"span\\")
], -1 /* HOISTED */) ], -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
@ -41,8 +41,8 @@ exports[`compiler: hoistStatic transform hoist nested static tree with comments
"const _Vue = Vue "const _Vue = Vue
const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue const { createVNode: _createVNode, createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/ _createVNode(\\"div\\", null, [ const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, [
_createCommentVNode(\\"comment\\") /*#__PURE__*/_createCommentVNode(\\"comment\\")
], -1 /* HOISTED */) ], -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
@ -60,8 +60,8 @@ exports[`compiler: hoistStatic transform hoist siblings with common non-hoistabl
"const _Vue = Vue "const _Vue = Vue
const { createVNode: _createVNode } = _Vue const { createVNode: _createVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/ _createVNode(\\"span\\", null, null, -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createVNode(\\"span\\", null, null, -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/ _createVNode(\\"div\\", null, null, -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createVNode(\\"div\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
@ -79,7 +79,7 @@ exports[`compiler: hoistStatic transform hoist simple element 1`] = `
"const _Vue = Vue "const _Vue = Vue
const { createVNode: _createVNode } = _Vue const { createVNode: _createVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/ _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
@ -172,7 +172,7 @@ exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static t
"const _Vue = Vue "const _Vue = Vue
const { createVNode: _createVNode } = _Vue const { createVNode: _createVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/ _createVNode(\\"span\\", null, \\"foo \\" + _toDisplayString(1) + \\" \\" + _toDisplayString(true), -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createVNode(\\"span\\", null, \\"foo \\" + /*#__PURE__*/_toDisplayString(1) + \\" \\" + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
@ -189,7 +189,7 @@ exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static t
"const _Vue = Vue "const _Vue = Vue
const { createVNode: _createVNode } = _Vue const { createVNode: _createVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/ _createVNode(\\"span\\", { foo: 0 }, _toDisplayString(1), -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createVNode(\\"span\\", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
@ -346,7 +346,7 @@ exports[`compiler: hoistStatic transform should hoist v-for children if static 1
const { createVNode: _createVNode } = _Vue const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" } const _hoisted_1 = { id: \\"foo\\" }
const _hoisted_2 = /*#__PURE__*/ _createVNode(\\"span\\", null, null, -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createVNode(\\"span\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {
@ -371,7 +371,7 @@ const _hoisted_1 = {
key: 0, key: 0,
id: \\"foo\\" id: \\"foo\\"
} }
const _hoisted_2 = /*#__PURE__*/ _createVNode(\\"span\\", null, null, -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createVNode(\\"span\\", null, null, -1 /* HOISTED */)
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
with (_ctx) { with (_ctx) {

View File

@ -54,6 +54,8 @@ import {
} from './runtimeHelpers' } from './runtimeHelpers'
import { ImportItem } from './transform' import { ImportItem } from './transform'
const PURE_ANNOTATION = `/*#__PURE__*/`
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
export interface CodegenResult { export interface CodegenResult {
@ -69,6 +71,7 @@ export interface CodegenContext extends Required<CodegenOptions> {
column: number column: number
offset: number offset: number
indentLevel: number indentLevel: number
pure: boolean
map?: SourceMapGenerator map?: SourceMapGenerator
helper(key: symbol): string helper(key: symbol): string
push(code: string, node?: CodegenNode): void push(code: string, node?: CodegenNode): void
@ -107,6 +110,7 @@ function createCodegenContext(
line: 1, line: 1,
offset: 0, offset: 0,
indentLevel: 0, indentLevel: 0,
pure: false,
map: undefined, map: undefined,
helper(key) { helper(key) {
return `_${helperNameMap[key]}` return `_${helperNameMap[key]}`
@ -201,7 +205,7 @@ export function generate(
// enter render function // enter render function
if (genScopeId && !ssr) { if (genScopeId && !ssr) {
push(`const render = /*#__PURE__*/ _withId(`) push(`const render = ${PURE_ANNOTATION}_withId(`)
} }
if (!ssr) { if (!ssr) {
push(`function render(_ctx, _cache) {`) push(`function render(_ctx, _cache) {`)
@ -400,7 +404,9 @@ function genModulePreamble(
} }
if (genScopeId) { if (genScopeId) {
push(`const _withId = /*#__PURE__*/ ${helper(WITH_SCOPE_ID)}("${scopeId}")`) push(
`const _withId = ${PURE_ANNOTATION}${helper(WITH_SCOPE_ID)}("${scopeId}")`
)
newline() newline()
} }
@ -432,6 +438,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
if (!hoists.length) { if (!hoists.length) {
return return
} }
context.pure = true
const { push, newline, helper, scopeId, mode } = context const { push, newline, helper, scopeId, mode } = context
const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function' const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
newline() newline()
@ -445,13 +452,6 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
hoists.forEach((exp, i) => { hoists.forEach((exp, i) => {
push(`const _hoisted_${i + 1} = `) push(`const _hoisted_${i + 1} = `)
// make hosit function calls tree-shakable
if (
exp.type === NodeTypes.VNODE_CALL ||
exp.type === NodeTypes.JS_CALL_EXPRESSION
) {
push(`/*#__PURE__*/ `)
}
genNode(exp, context) genNode(exp, context)
newline() newline()
}) })
@ -460,6 +460,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
push(`${helper(POP_SCOPE_ID)}()`) push(`${helper(POP_SCOPE_ID)}()`)
newline() newline()
} }
context.pure = false
} }
function genImports(importsOptions: ImportItem[], context: CodegenContext) { function genImports(importsOptions: ImportItem[], context: CodegenContext) {
@ -634,7 +635,8 @@ function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
} }
function genInterpolation(node: InterpolationNode, context: CodegenContext) { function genInterpolation(node: InterpolationNode, context: CodegenContext) {
const { push, helper } = context const { push, helper, pure } = context
if (pure) push(PURE_ANNOTATION)
push(`${helper(TO_DISPLAY_STRING)}(`) push(`${helper(TO_DISPLAY_STRING)}(`)
genNode(node.content, context) genNode(node.content, context)
push(`)`) push(`)`)
@ -676,13 +678,16 @@ function genExpressionAsPropertyKey(
function genComment(node: CommentNode, context: CodegenContext) { function genComment(node: CommentNode, context: CodegenContext) {
if (__DEV__) { if (__DEV__) {
const { push, helper } = context const { push, helper, pure } = context
if (pure) {
push(PURE_ANNOTATION)
}
push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node) push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
} }
} }
function genVNodeCall(node: VNodeCall, context: CodegenContext) { function genVNodeCall(node: VNodeCall, context: CodegenContext) {
const { push, helper } = context const { push, helper, pure } = context
const { const {
tag, tag,
props, props,
@ -699,6 +704,9 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
if (isBlock) { if (isBlock) {
push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `) push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `)
} }
if (pure) {
push(PURE_ANNOTATION)
}
push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node) push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node)
genNodeList( genNodeList(
genNullableArgs([tag, props, children, patchFlag, dynamicProps]), genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
@ -725,12 +733,14 @@ function genNullableArgs(args: any[]): CallExpression['arguments'] {
// JavaScript // JavaScript
function genCallExpression(node: CallExpression, context: CodegenContext) { function genCallExpression(node: CallExpression, context: CodegenContext) {
const callee = isString(node.callee) const { push, helper, pure } = context
? node.callee const callee = isString(node.callee) ? node.callee : helper(node.callee)
: context.helper(node.callee) if (pure) {
context.push(callee + `(`, node) push(PURE_ANNOTATION)
}
push(callee + `(`, node)
genNodeList(node.arguments, context) genNodeList(node.arguments, context)
context.push(`)`) push(`)`)
} }
function genObjectExpression(node: ObjectExpression, context: CodegenContext) { function genObjectExpression(node: ObjectExpression, context: CodegenContext) {