const _withId = _withScopeId(\\"test\\")
_pushScopeId(\\"test\\")
-const _hoisted_1 = _createVNode(\\"div\\", null, \\"hello\\", -1)
-const _hoisted_2 = _createVNode(\\"div\\", null, \\"world\\", -1)
+const _hoisted_1 = _createVNode(\\"div\\", null, \\"hello\\", -2 /* HOISTED */)
+const _hoisted_2 = _createVNode(\\"div\\", null, \\"world\\", -2 /* HOISTED */)
_popScopeId()
export const render = _withId(function render(_ctx, _cache) {
PUSH_SCOPE_ID,
POP_SCOPE_ID
} from '../src/runtimeHelpers'
+import { PatchFlags } from '@vue/shared'
+import { genFlagText } from './testUtils'
describe('scopeId compiler support', () => {
test('should only work in module mode', () => {
expect(code).toMatch(
[
`_pushScopeId("test")`,
- `const _hoisted_1 = _createVNode("div", null, "hello", -1)`,
- `const _hoisted_2 = _createVNode("div", null, "world", -1)`,
+ `const _hoisted_1 = _createVNode("div", null, "hello", ${genFlagText(
+ PatchFlags.HOISTED
+ )})`,
+ `const _hoisted_2 = _createVNode("div", null, "world", ${genFlagText(
+ PatchFlags.HOISTED
+ )})`,
`_popScopeId()`
].join('\n')
)
"const _Vue = Vue
const { createVNode: _createVNode } = _Vue
-const _hoisted_1 = _createVNode(\\"div\\", { key: \\"foo\\" }, null, -1)
+const _hoisted_1 = _createVNode(\\"div\\", { key: \\"foo\\" }, null, -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
const _hoisted_1 = _createVNode(\\"p\\", null, [
_createVNode(\\"span\\"),
_createVNode(\\"span\\")
-], -1)
+], -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
const _hoisted_1 = _createVNode(\\"div\\", null, [
_createCommentVNode(\\"comment\\")
-], -1)
+], -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
"const _Vue = Vue
const { createVNode: _createVNode } = _Vue
-const _hoisted_1 = _createVNode(\\"span\\", null, null, -1)
-const _hoisted_2 = _createVNode(\\"div\\", null, null, -1)
+const _hoisted_1 = _createVNode(\\"span\\", null, null, -2 /* HOISTED */)
+const _hoisted_2 = _createVNode(\\"div\\", null, null, -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
"const _Vue = Vue
const { createVNode: _createVNode } = _Vue
-const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -1)
+const _hoisted_1 = _createVNode(\\"span\\", { class: \\"inline\\" }, \\"hello\\", -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
const _directive_foo = _resolveDirective(\\"foo\\")
return (_openBlock(), _createBlock(\\"div\\", null, [
- _withDirectives(_createVNode(\\"div\\", _hoisted_1, null, 32 /* NEED_PATCH */), [
+ _withDirectives(_createVNode(\\"div\\", _hoisted_1, null, -1 /* NEED_PATCH */), [
[_directive_foo]
])
]))
"const _Vue = Vue
const { createVNode: _createVNode } = _Vue
-const _hoisted_1 = _createVNode(\\"span\\", null, \\"foo \\" + _toDisplayString(1) + \\" \\" + _toDisplayString(true), -1)
+const _hoisted_1 = _createVNode(\\"span\\", null, \\"foo \\" + _toDisplayString(1) + \\" \\" + _toDisplayString(true), -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
"const _Vue = Vue
const { createVNode: _createVNode } = _Vue
-const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toDisplayString(1), -1)
+const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toDisplayString(1), -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
const { createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(\\"div\\", null, [
- _createVNode(\\"div\\", { ref: foo }, null, 32 /* NEED_PATCH */)
+ _createVNode(\\"div\\", { ref: foo }, null, -1 /* NEED_PATCH */)
]))
}
}"
const { createVNode: _createVNode } = _Vue
const _hoisted_1 = { id: \\"foo\\" }
-const _hoisted_2 = _createVNode(\\"span\\", null, null, -1)
+const _hoisted_2 = _createVNode(\\"span\\", null, null, -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
key: 0,
id: \\"foo\\"
}
-const _hoisted_2 = _createVNode(\\"span\\", null, null, -1)
+const _hoisted_2 = _createVNode(\\"span\\", null, null, -2 /* HOISTED */)
return function render(_ctx, _cache) {
with (this) {
const _directive_foo = _resolveDirective(\\"foo\\")
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
- return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */)), [
+ return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, -1 /* NEED_PATCH */)), [
[_directive_foo]
])
}), 256 /* UNKEYED_FRAGMENT */))
) {
if (!doNotHoistNode && isStaticNode(child, resultCache)) {
// whole tree is static
- ;(child.codegenNode as VNodeCall).patchFlag = PatchFlags.HOISTED + ``
+ ;(child.codegenNode as VNodeCall).patchFlag =
+ PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
const hoisted = context.transformHoist
? context.transformHoist(child, context)
: child.codegenNode!
DirectiveArguments,
createVNodeCall
} from '../ast'
-import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
+import { PatchFlags, PatchFlagNames, isSymbol, isOn } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
RESOLVE_DIRECTIVE,
// patchFlag & dynamicPropNames
if (patchFlag !== 0) {
if (__DEV__) {
- const flagNames = Object.keys(PatchFlagNames)
- .map(Number)
- .filter(n => n > 0 && patchFlag & n)
- .map(n => PatchFlagNames[n])
- .join(`, `)
- vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
+ if (patchFlag < 0) {
+ // special flags (negative and mutually exclusive)
+ vnodePatchFlag = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
+ } else {
+ // bitwise flags
+ const flagNames = Object.keys(PatchFlagNames)
+ .map(Number)
+ .filter(n => n > 0 && patchFlag & n)
+ .map(n => PatchFlagNames[n])
+ .join(`, `)
+ vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
+ }
} else {
vnodePatchFlag = String(patchFlag)
}
let hasRef = false
let hasClassBinding = false
let hasStyleBinding = false
+ let hasHydrationEventBinding = false
let hasDynamicKeys = false
const dynamicPropNames: string[] = []
const analyzePatchFlag = ({ key, value }: Property) => {
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
+ const name = key.content
+ if (!isComponent && isOn(name) && name.toLowerCase() !== 'onclick') {
+ // This flag is for hydrating event handlers only. We omit the flag for
+ // click handlers becaues hydration gives click dedicated fast path.
+ hasHydrationEventBinding = true
+ }
if (
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
isStaticNode(value))
) {
+ // skip if the prop is a cached handler or has constant value
return
}
- const name = key.content
if (name === 'ref') {
hasRef = true
} else if (name === 'class') {
if (dynamicPropNames.length) {
patchFlag |= PatchFlags.PROPS
}
+ if (hasHydrationEventBinding) {
+ patchFlag |= PatchFlags.HYDRATE_EVENTS
+ }
}
if (patchFlag === 0 && (hasRef || runtimeDirectives.length > 0)) {
- patchFlag |= PatchFlags.NEED_PATCH
+ patchFlag = PatchFlags.NEED_PATCH
}
return {
with (this) {
const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue
- return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */)), [
+ return _withDirectives((_openBlock(), _createBlock(\\"div\\", null, null, -1 /* NEED_PATCH */)), [
[_vShow, a]
])
}
isReservedProp,
isFunction,
PatchFlags,
- NOOP
+ NOOP,
+ isOn
} from '@vue/shared'
import {
queueJob,
options: RendererOptions<HostNode, HostElement>
): {
render: RootRenderFunction<HostNode, HostElement>
+ hydrate: RootRenderFunction<HostNode, HostElement>
createApp: CreateAppFunction<HostElement>
} {
type HostVNode = VNode<HostNode, HostElement>
// props
if (props != null) {
for (const key in props) {
- if (isReservedProp(key)) continue
- hostPatchProp(el, key, props[key], null, isSVG)
+ if (!isReservedProp(key)) {
+ hostPatchProp(el, key, props[key], null, isSVG)
+ }
}
if (props.onVnodeBeforeMount != null) {
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
container._vnode = vnode
}
+ function hydrate(vnode: HostVNode, container: any) {
+ hydrateNode(container.firstChild, vnode, container)
+ flushPostFlushCbs()
+ }
+
+ // TODO handle mismatches
+ function hydrateNode(
+ node: any,
+ vnode: HostVNode,
+ container: any,
+ parentComponent: ComponentInternalInstance | null = null
+ ): any {
+ const { type, shapeFlag } = vnode
+ switch (type) {
+ case Text:
+ case Comment:
+ case Static:
+ vnode.el = node
+ return node.nextSibling
+ case Fragment:
+ vnode.el = node
+ const anchor = (vnode.anchor = hydrateChildren(
+ node.nextSibling,
+ vnode.children as HostVNode[],
+ container,
+ parentComponent
+ ))
+ return anchor.nextSibling
+ case Portal:
+ // TODO
+ break
+ default:
+ if (shapeFlag & ShapeFlags.ELEMENT) {
+ return hydrateElement(node, vnode, parentComponent)
+ } else if (shapeFlag & ShapeFlags.COMPONENT) {
+ // TODO
+ } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
+ // TODO
+ } else if (__DEV__) {
+ warn('Invalid HostVNode type:', type, `(${typeof type})`)
+ }
+ }
+ }
+
+ function hydrateElement(
+ el: any,
+ vnode: HostVNode,
+ parentComponent: ComponentInternalInstance | null
+ ) {
+ vnode.el = el
+ const { props, patchFlag } = vnode
+ // skip props & children if this is hoisted static nodes
+ if (patchFlag !== PatchFlags.HOISTED) {
+ // props
+ if (props !== null) {
+ if (
+ patchFlag & PatchFlags.FULL_PROPS ||
+ patchFlag & PatchFlags.HYDRATE_EVENTS
+ ) {
+ for (const key in props) {
+ if (!isReservedProp(key) && isOn(key)) {
+ hostPatchProp(el, key, props[key], null)
+ }
+ }
+ } else if (props.onClick != null) {
+ // Fast path for click listeners (which is most often) to avoid
+ // iterating through props.
+ hostPatchProp(el, 'onClick', props.onClick, null)
+ }
+ // vnode mounted hook
+ const { onVnodeMounted } = props
+ if (onVnodeMounted != null) {
+ queuePostFlushCb(() => {
+ invokeDirectiveHook(onVnodeMounted, parentComponent, vnode, null)
+ })
+ }
+ }
+ // children
+ if (
+ vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN &&
+ // skip if element has innerHTML / textContent
+ !(props !== null && (props.innerHTML || props.textContent))
+ ) {
+ hydrateChildren(
+ el.firstChild,
+ vnode.children as HostVNode[],
+ el,
+ parentComponent
+ )
+ }
+ }
+ return el.nextSibling
+ }
+
+ function hydrateChildren(
+ node: any,
+ vnodes: HostVNode[],
+ container: any,
+ parentComponent: ComponentInternalInstance | null = null
+ ) {
+ for (let i = 0; i < vnodes.length; i++) {
+ // TODO can skip normalizeVNode in optimized mode
+ // (need hint on rendered markup?)
+ const vnode = (vnodes[i] = normalizeVNode(vnodes[i]))
+ node = hydrateNode(node, vnode, container, parentComponent)
+ }
+ return node
+ }
+
return {
render,
+ hydrate,
createApp: createAppAPI(render)
}
}
EMPTY_ARR,
extend,
normalizeClass,
- normalizeStyle
+ normalizeStyle,
+ PatchFlags
} from '@vue/shared'
import {
ComponentInternalInstance,
if (
shouldTrack > 0 &&
currentBlock !== null &&
+ // the EVENTS flag is only for hydration and if it is the only flag, the
+ // vnode should not be considered dynamic.
+ patchFlag !== PatchFlags.HYDRATE_EVENTS &&
(patchFlag > 0 ||
+ patchFlag === PatchFlags.NEED_PATCH ||
shapeFlag & ShapeFlags.SUSPENSE ||
shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
// Importing from the compiler, will be tree-shaken in prod
import { isFunction, isString, isHTMLTag, isSVGTag } from '@vue/shared'
-const { render: baseRender, createApp: baseCreateApp } = createRenderer({
+const {
+ render: baseRender,
+ hydrate: baseHydrate,
+ createApp: baseCreateApp
+} = createRenderer({
patchProp,
...nodeOps
})
// use explicit type casts here to avoid import() calls in rolled-up d.ts
export const render = baseRender as RootRenderFunction<Node, Element>
+export const hydrate = baseHydrate as RootRenderFunction<Node, Element>
export const createApp: CreateAppFunction<Element> = (...args) => {
const app = baseCreateApp(...args)
// exclusive with CLASS, STYLE and PROPS.
FULL_PROPS = 1 << 4,
- // Indicates an element that only needs non-props patching, e.g. ref or
- // directives (onVnodeXXX hooks). It simply marks the vnode as "need patch",
- // since every patched vnode checks for refs and onVnodeXXX hooks.
- // This flag is never directly matched against, it simply serves as a non-zero
- // value.
- NEED_PATCH = 1 << 5,
+ // Indicates an element with event listeners (which need to be attached
+ // during hydration)
+ HYDRATE_EVENTS = 1 << 5,
// Indicates a fragment whose children order doesn't change.
STABLE_FRAGMENT = 1 << 6,
// Components with this flag are always force updated.
DYNAMIC_SLOTS = 1 << 9,
- // A special flag that indicates a hoisted, static vnode.
- HOISTED = -1,
+ // SPECIAL FLAGS -------------------------------------------------------------
+
+ // Special flags are negative integers. They are never matched against using
+ // bitwise operators (bitwise matching should only happen in branches where
+ // patchFlag > 0), and are mutually exclusive. When checking for a speical
+ // flag, simply check patchFlag === FLAG.
+
+ // Indicates an element that only needs non-props patching, e.g. ref or
+ // directives (onVnodeXXX hooks). since every patched vnode checks for refs
+ // and onVnodeXXX hooks, itt simply marks the vnode so that a parent block
+ // will track it.
+ NEED_PATCH = -1,
+
+ // Indicates a hoisted static vnode. This is a hint for hydration to skip
+ // the entire sub tree since static content never needs to be updated.
+ HOISTED = -2,
// A special flag that indicates that the diffing algorithm should bail out
// of optimized mode. This is only on block fragments created by renderSlot()
// when encountering non-compiler generated slots (i.e. manually written
// render functions, which should always be fully diffed)
- BAIL = -2
+ BAIL = -3
}
// runtime object for public consumption
[PatchFlags.CLASS]: `CLASS`,
[PatchFlags.STYLE]: `STYLE`,
[PatchFlags.PROPS]: `PROPS`,
- [PatchFlags.NEED_PATCH]: `NEED_PATCH`,
[PatchFlags.FULL_PROPS]: `FULL_PROPS`,
+ [PatchFlags.HYDRATE_EVENTS]: `HYDRATE_EVENTS`,
[PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
[PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
[PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
+ [PatchFlags.NEED_PATCH]: `NEED_PATCH`,
+ [PatchFlags.HOISTED]: `HOISTED`,
[PatchFlags.BAIL]: `BAIL`
}