feat(compiler-dom/runtime-dom): stringify eligible static trees
This commit is contained in:
		
							parent
							
								
									e861c6da90
								
							
						
					
					
						commit
						27913e661a
					
				| @ -48,7 +48,8 @@ import { | ||||
|   WITH_SCOPE_ID, | ||||
|   WITH_DIRECTIVES, | ||||
|   CREATE_BLOCK, | ||||
|   OPEN_BLOCK | ||||
|   OPEN_BLOCK, | ||||
|   CREATE_STATIC | ||||
| } from './runtimeHelpers' | ||||
| import { ImportItem } from './transform' | ||||
| 
 | ||||
| @ -309,7 +310,12 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) { | ||||
|       // has check cost, but hoists are lifted out of the function - we need
 | ||||
|       // to provide the helper here.
 | ||||
|       if (ast.hoists.length) { | ||||
|         const staticHelpers = [CREATE_VNODE, CREATE_COMMENT, CREATE_TEXT] | ||||
|         const staticHelpers = [ | ||||
|           CREATE_VNODE, | ||||
|           CREATE_COMMENT, | ||||
|           CREATE_TEXT, | ||||
|           CREATE_STATIC | ||||
|         ] | ||||
|           .filter(helper => ast.helpers.includes(helper)) | ||||
|           .map(aliasHelper) | ||||
|           .join(', ') | ||||
|  | ||||
| @ -5,7 +5,8 @@ export { | ||||
|   CompilerOptions, | ||||
|   ParserOptions, | ||||
|   TransformOptions, | ||||
|   CodegenOptions | ||||
|   CodegenOptions, | ||||
|   HoistTransform | ||||
| } from './options' | ||||
| export { baseParse, TextModes } from './parse' | ||||
| export { | ||||
|  | ||||
| @ -1,7 +1,11 @@ | ||||
| import { ElementNode, Namespace } from './ast' | ||||
| import { ElementNode, Namespace, JSChildNode, PlainElementNode } from './ast' | ||||
| import { TextModes } from './parse' | ||||
| import { CompilerError } from './errors' | ||||
| import { NodeTransform, DirectiveTransform } from './transform' | ||||
| import { | ||||
|   NodeTransform, | ||||
|   DirectiveTransform, | ||||
|   TransformContext | ||||
| } from './transform' | ||||
| 
 | ||||
| export interface ParserOptions { | ||||
|   isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
 | ||||
| @ -26,9 +30,17 @@ export interface ParserOptions { | ||||
|   onError?: (error: CompilerError) => void | ||||
| } | ||||
| 
 | ||||
| export type HoistTransform = ( | ||||
|   node: PlainElementNode, | ||||
|   context: TransformContext | ||||
| ) => JSChildNode | ||||
| 
 | ||||
| export interface TransformOptions { | ||||
|   nodeTransforms?: NodeTransform[] | ||||
|   directiveTransforms?: Record<string, DirectiveTransform | undefined> | ||||
|   // an optional hook to transform a node being hoisted.
 | ||||
|   // used by compiler-dom to turn hoisted nodes into stringified HTML vnodes.
 | ||||
|   transformHoist?: HoistTransform | null | ||||
|   isBuiltInComponent?: (tag: string) => symbol | void | ||||
|   // Transform expressions like {{ foo }} to `_ctx.foo`.
 | ||||
|   // If this option is false, the generated code will be wrapped in a
 | ||||
|  | ||||
| @ -8,6 +8,7 @@ export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``) | ||||
| export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``) | ||||
| export const CREATE_COMMENT = Symbol(__DEV__ ? `createCommentVNode` : ``) | ||||
| export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``) | ||||
| export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``) | ||||
| export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``) | ||||
| export const RESOLVE_DYNAMIC_COMPONENT = Symbol( | ||||
|   __DEV__ ? `resolveDynamicComponent` : `` | ||||
| @ -40,6 +41,7 @@ export const helperNameMap: any = { | ||||
|   [CREATE_VNODE]: `createVNode`, | ||||
|   [CREATE_COMMENT]: `createCommentVNode`, | ||||
|   [CREATE_TEXT]: `createTextVNode`, | ||||
|   [CREATE_STATIC]: `createStaticVNode`, | ||||
|   [RESOLVE_COMPONENT]: `resolveComponent`, | ||||
|   [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`, | ||||
|   [RESOLVE_DIRECTIVE]: `resolveDirective`, | ||||
|  | ||||
| @ -115,6 +115,7 @@ export function createTransformContext( | ||||
|     cacheHandlers = false, | ||||
|     nodeTransforms = [], | ||||
|     directiveTransforms = {}, | ||||
|     transformHoist = null, | ||||
|     isBuiltInComponent = NOOP, | ||||
|     scopeId = null, | ||||
|     ssr = false, | ||||
| @ -128,6 +129,7 @@ export function createTransformContext( | ||||
|     cacheHandlers, | ||||
|     nodeTransforms, | ||||
|     directiveTransforms, | ||||
|     transformHoist, | ||||
|     isBuiltInComponent, | ||||
|     scopeId, | ||||
|     ssr, | ||||
|  | ||||
| @ -52,7 +52,10 @@ function walk( | ||||
|     ) { | ||||
|       if (!doNotHoistNode && isStaticNode(child, resultCache)) { | ||||
|         // whole tree is static
 | ||||
|         child.codegenNode = context.hoist(child.codegenNode!) | ||||
|         const hoisted = context.transformHoist | ||||
|           ? context.transformHoist(child, context) | ||||
|           : child.codegenNode! | ||||
|         child.codegenNode = context.hoist(hoisted) | ||||
|         continue | ||||
|       } else { | ||||
|         // node may contain dynamic children, but its props may be eligible for
 | ||||
|  | ||||
| @ -18,6 +18,7 @@ import { transformModel } from './transforms/vModel' | ||||
| import { transformOn } from './transforms/vOn' | ||||
| import { transformShow } from './transforms/vShow' | ||||
| import { warnTransitionChildren } from './transforms/warnTransitionChildren' | ||||
| import { stringifyStatic } from './stringifyStatic' | ||||
| 
 | ||||
| export const parserOptions = __BROWSER__ | ||||
|   ? parserOptionsMinimal | ||||
| @ -41,17 +42,16 @@ export function compile( | ||||
|   template: string, | ||||
|   options: CompilerOptions = {} | ||||
| ): CodegenResult { | ||||
|   const result = baseCompile(template, { | ||||
|   return baseCompile(template, { | ||||
|     ...parserOptions, | ||||
|     ...options, | ||||
|     nodeTransforms: [...DOMNodeTransforms, ...(options.nodeTransforms || [])], | ||||
|     directiveTransforms: { | ||||
|       ...DOMDirectiveTransforms, | ||||
|       ...(options.directiveTransforms || {}) | ||||
|     } | ||||
|     }, | ||||
|     transformHoist: __BROWSER__ ? null : stringifyStatic | ||||
|   }) | ||||
|   // debugger
 | ||||
|   return result | ||||
| } | ||||
| 
 | ||||
| export function parse(template: string, options: ParserOptions = {}): RootNode { | ||||
|  | ||||
							
								
								
									
										116
									
								
								packages/compiler-dom/src/stringifyStatic.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								packages/compiler-dom/src/stringifyStatic.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | ||||
| import { | ||||
|   NodeTypes, | ||||
|   ElementNode, | ||||
|   TransformContext, | ||||
|   TemplateChildNode, | ||||
|   SimpleExpressionNode, | ||||
|   createCallExpression, | ||||
|   HoistTransform, | ||||
|   CREATE_STATIC | ||||
| } from '@vue/compiler-core' | ||||
| import { isVoidTag, isString, isSymbol, escapeHtml } from '@vue/shared' | ||||
| 
 | ||||
| // Turn eligible hoisted static trees into stringied static nodes, e.g.
 | ||||
| //   const _hoisted_1 = createStaticVNode(`<div class="foo">bar</div>`)
 | ||||
| export const stringifyStatic: HoistTransform = (node, context) => { | ||||
|   if (shouldOptimize(node)) { | ||||
|     return createCallExpression(context.helper(CREATE_STATIC), [ | ||||
|       JSON.stringify(stringifyElement(node, context)) | ||||
|     ]) | ||||
|   } else { | ||||
|     return node.codegenNode! | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Opt-in heuristics based on:
 | ||||
| // 1. number of elements with attributes > 5.
 | ||||
| // 2. OR: number of total nodes > 20
 | ||||
| // For some simple trees, the performance can actually be worse.
 | ||||
| // it is only worth it when the tree is complex enough
 | ||||
| // (e.g. big piece of static content)
 | ||||
| function shouldOptimize(node: ElementNode): boolean { | ||||
|   let bindingThreshold = 5 | ||||
|   let nodeThreshold = 20 | ||||
| 
 | ||||
|   function walk(node: ElementNode) { | ||||
|     for (let i = 0; i < node.children.length; i++) { | ||||
|       if (--nodeThreshold === 0) { | ||||
|         return true | ||||
|       } | ||||
|       const child = node.children[i] | ||||
|       if (child.type === NodeTypes.ELEMENT) { | ||||
|         if (child.props.length > 0 && --bindingThreshold === 0) { | ||||
|           return true | ||||
|         } | ||||
|         if (walk(child)) { | ||||
|           return true | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   return walk(node) | ||||
| } | ||||
| 
 | ||||
| function stringifyElement( | ||||
|   node: ElementNode, | ||||
|   context: TransformContext | ||||
| ): string { | ||||
|   let res = `<${node.tag}` | ||||
|   for (let i = 0; i < node.props.length; i++) { | ||||
|     const p = node.props[i] | ||||
|     if (p.type === NodeTypes.ATTRIBUTE) { | ||||
|       res += ` ${p.name}` | ||||
|       if (p.value) { | ||||
|         res += `="${p.value.content}"` | ||||
|       } | ||||
|     } else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') { | ||||
|       // constant v-bind, e.g. :foo="1"
 | ||||
|       // TODO
 | ||||
|     } | ||||
|   } | ||||
|   if (context.scopeId) { | ||||
|     res += ` ${context.scopeId}` | ||||
|   } | ||||
|   res += `>` | ||||
|   for (let i = 0; i < node.children.length; i++) { | ||||
|     res += stringifyNode(node.children[i], context) | ||||
|   } | ||||
|   if (!isVoidTag(node.tag)) { | ||||
|     res += `</${node.tag}>` | ||||
|   } | ||||
|   return res | ||||
| } | ||||
| 
 | ||||
| function stringifyNode( | ||||
|   node: string | TemplateChildNode, | ||||
|   context: TransformContext | ||||
| ): string { | ||||
|   if (isString(node)) { | ||||
|     return node | ||||
|   } | ||||
|   if (isSymbol(node)) { | ||||
|     return `` | ||||
|   } | ||||
|   switch (node.type) { | ||||
|     case NodeTypes.ELEMENT: | ||||
|       return stringifyElement(node, context) | ||||
|     case NodeTypes.TEXT: | ||||
|       return escapeHtml(node.content) | ||||
|     case NodeTypes.COMMENT: | ||||
|       return `<!--${escapeHtml(node.content)}-->` | ||||
|     case NodeTypes.INTERPOLATION: | ||||
|       // constants
 | ||||
|       // TODO check eval
 | ||||
|       return (node.content as SimpleExpressionNode).content | ||||
|     case NodeTypes.COMPOUND_EXPRESSION: | ||||
|       // TODO proper handling
 | ||||
|       return node.children.map((c: any) => stringifyNode(c, context)).join('') | ||||
|     case NodeTypes.TEXT_CALL: | ||||
|       return stringifyNode(node.content, context) | ||||
|     default: | ||||
|       // static trees will not contain if/for nodes
 | ||||
|       return '' | ||||
|   } | ||||
| } | ||||
| @ -85,7 +85,12 @@ export { toHandlers } from './helpers/toHandlers' | ||||
| export { renderSlot } from './helpers/renderSlot' | ||||
| export { createSlots } from './helpers/createSlots' | ||||
| export { pushScopeId, popScopeId, withScopeId } from './helpers/scopeId' | ||||
| export { setBlockTracking, createTextVNode, createCommentVNode } from './vnode' | ||||
| export { | ||||
|   setBlockTracking, | ||||
|   createTextVNode, | ||||
|   createCommentVNode, | ||||
|   createStaticVNode | ||||
| } from './vnode' | ||||
| // Since @vue/shared is inlined into final builds,
 | ||||
| // when re-exporting from @vue/shared we need to avoid relying on their original
 | ||||
| // types so that the bundled d.ts does not attempt to import from it.
 | ||||
|  | ||||
| @ -8,7 +8,8 @@ import { | ||||
|   VNode, | ||||
|   VNodeArrayChildren, | ||||
|   createVNode, | ||||
|   isSameVNodeType | ||||
|   isSameVNodeType, | ||||
|   Static | ||||
| } from './vnode' | ||||
| import { | ||||
|   ComponentInternalInstance, | ||||
| @ -28,7 +29,8 @@ import { | ||||
|   EMPTY_ARR, | ||||
|   isReservedProp, | ||||
|   isFunction, | ||||
|   PatchFlags | ||||
|   PatchFlags, | ||||
|   NOOP | ||||
| } from '@vue/shared' | ||||
| import { | ||||
|   queueJob, | ||||
| @ -88,8 +90,15 @@ export interface RendererOptions<HostNode = any, HostElement = any> { | ||||
|   setElementText(node: HostElement, text: string): void | ||||
|   parentNode(node: HostNode): HostElement | null | ||||
|   nextSibling(node: HostNode): HostNode | null | ||||
|   querySelector(selector: string): HostElement | null | ||||
|   setScopeId(el: HostNode, id: string): void | ||||
|   querySelector?(selector: string): HostElement | null | ||||
|   setScopeId?(el: HostElement, id: string): void | ||||
|   cloneNode?(node: HostNode): HostNode | ||||
|   insertStaticContent?( | ||||
|     content: string, | ||||
|     parent: HostElement, | ||||
|     anchor: HostNode | null, | ||||
|     isSVG: boolean | ||||
|   ): HostElement | ||||
| } | ||||
| 
 | ||||
| export type RootRenderFunction<HostNode, HostElement> = ( | ||||
| @ -197,7 +206,9 @@ export function createRenderer< | ||||
|     parentNode: hostParentNode, | ||||
|     nextSibling: hostNextSibling, | ||||
|     querySelector: hostQuerySelector, | ||||
|     setScopeId: hostSetScopeId | ||||
|     setScopeId: hostSetScopeId = NOOP, | ||||
|     cloneNode: hostCloneNode, | ||||
|     insertStaticContent: hostInsertStaticContent | ||||
|   } = options | ||||
| 
 | ||||
|   const internals: RendererInternals<HostNode, HostElement> = { | ||||
| @ -233,6 +244,11 @@ export function createRenderer< | ||||
|       case Comment: | ||||
|         processCommentNode(n1, n2, container, anchor) | ||||
|         break | ||||
|       case Static: | ||||
|         if (n1 == null) { | ||||
|           mountStaticNode(n2, container, anchor, isSVG) | ||||
|         } // static nodes are noop on patch
 | ||||
|         break | ||||
|       case Fragment: | ||||
|         processFragment( | ||||
|           n1, | ||||
| @ -336,6 +352,26 @@ export function createRenderer< | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function mountStaticNode( | ||||
|     n2: HostVNode, | ||||
|     container: HostElement, | ||||
|     anchor: HostNode | null, | ||||
|     isSVG: boolean | ||||
|   ) { | ||||
|     if (n2.el != null && hostCloneNode !== undefined) { | ||||
|       hostInsert(hostCloneNode(n2.el), container, anchor) | ||||
|     } else { | ||||
|       // static nodes are only present when used with compiler-dom/runtime-dom
 | ||||
|       // which guarantees presence of hostInsertStaticContent.
 | ||||
|       n2.el = hostInsertStaticContent!( | ||||
|         n2.children as string, | ||||
|         container, | ||||
|         anchor, | ||||
|         isSVG | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function processElement( | ||||
|     n1: HostVNode | null, | ||||
|     n2: HostVNode, | ||||
| @ -374,50 +410,58 @@ export function createRenderer< | ||||
|     isSVG: boolean, | ||||
|     optimized: boolean | ||||
|   ) { | ||||
|     const el = (vnode.el = hostCreateElement(vnode.type as string, isSVG)) | ||||
|     let el: HostElement | ||||
|     const { type, props, shapeFlag, transition, scopeId } = vnode | ||||
| 
 | ||||
|     // props
 | ||||
|     if (props != null) { | ||||
|       for (const key in props) { | ||||
|         if (isReservedProp(key)) continue | ||||
|         hostPatchProp(el, key, props[key], null, isSVG) | ||||
|     if (vnode.el != null && hostCloneNode !== undefined) { | ||||
|       // If a vnode has non-null el, it means it's being reused.
 | ||||
|       // Only static vnodes can be reused, so its mounted DOM nodes should be
 | ||||
|       // exactly the same, and we can simply do a clone here.
 | ||||
|       el = vnode.el = hostCloneNode(vnode.el) as HostElement | ||||
|     } else { | ||||
|       el = vnode.el = hostCreateElement(vnode.type as string, isSVG) | ||||
|       // props
 | ||||
|       if (props != null) { | ||||
|         for (const key in props) { | ||||
|           if (isReservedProp(key)) continue | ||||
|           hostPatchProp(el, key, props[key], null, isSVG) | ||||
|         } | ||||
|         if (props.onVnodeBeforeMount != null) { | ||||
|           invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode) | ||||
|         } | ||||
|       } | ||||
|       if (props.onVnodeBeforeMount != null) { | ||||
|         invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode) | ||||
| 
 | ||||
|       // scopeId
 | ||||
|       if (__BUNDLER__) { | ||||
|         if (scopeId !== null) { | ||||
|           hostSetScopeId(el, scopeId) | ||||
|         } | ||||
|         const treeOwnerId = parentComponent && parentComponent.type.__scopeId | ||||
|         // vnode's own scopeId and the current patched component's scopeId is
 | ||||
|         // different - this is a slot content node.
 | ||||
|         if (treeOwnerId != null && treeOwnerId !== scopeId) { | ||||
|           hostSetScopeId(el, treeOwnerId + '-s') | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       // children
 | ||||
|       if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { | ||||
|         hostSetElementText(el, vnode.children as string) | ||||
|       } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { | ||||
|         mountChildren( | ||||
|           vnode.children as HostVNodeChildren, | ||||
|           el, | ||||
|           null, | ||||
|           parentComponent, | ||||
|           parentSuspense, | ||||
|           isSVG && type !== 'foreignObject', | ||||
|           optimized || vnode.dynamicChildren !== null | ||||
|         ) | ||||
|       } | ||||
|       if (transition != null && !transition.persisted) { | ||||
|         transition.beforeEnter(el) | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // scopeId
 | ||||
|     if (__BUNDLER__) { | ||||
|       if (scopeId !== null) { | ||||
|         hostSetScopeId(el, scopeId) | ||||
|       } | ||||
|       const treeOwnerId = parentComponent && parentComponent.type.__scopeId | ||||
|       // vnode's own scopeId and the current patched component's scopeId is
 | ||||
|       // different - this is a slot content node.
 | ||||
|       if (treeOwnerId != null && treeOwnerId !== scopeId) { | ||||
|         hostSetScopeId(el, treeOwnerId + '-s') | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // children
 | ||||
|     if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { | ||||
|       hostSetElementText(el, vnode.children as string) | ||||
|     } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { | ||||
|       mountChildren( | ||||
|         vnode.children as HostVNodeChildren, | ||||
|         el, | ||||
|         null, | ||||
|         parentComponent, | ||||
|         parentSuspense, | ||||
|         isSVG && type !== 'foreignObject', | ||||
|         optimized || vnode.dynamicChildren !== null | ||||
|       ) | ||||
|     } | ||||
|     if (transition != null && !transition.persisted) { | ||||
|       transition.beforeEnter(el) | ||||
|     } | ||||
|     hostInsert(el, container, anchor) | ||||
|     const vnodeMountedHook = props && props.onVnodeMounted | ||||
|     if ( | ||||
| @ -776,8 +820,14 @@ export function createRenderer< | ||||
|     const targetSelector = n2.props && n2.props.target | ||||
|     const { patchFlag, shapeFlag, children } = n2 | ||||
|     if (n1 == null) { | ||||
|       if (__DEV__ && isString(targetSelector) && !hostQuerySelector) { | ||||
|         warn( | ||||
|           `Current renderer does not support string target for Portals. ` + | ||||
|             `(missing querySelector renderer option)` | ||||
|         ) | ||||
|       } | ||||
|       const target = (n2.target = isString(targetSelector) | ||||
|         ? hostQuerySelector(targetSelector) | ||||
|         ? hostQuerySelector!(targetSelector) | ||||
|         : targetSelector) | ||||
|       if (target != null) { | ||||
|         if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { | ||||
| @ -825,7 +875,7 @@ export function createRenderer< | ||||
|       // target changed
 | ||||
|       if (targetSelector !== (n1.props && n1.props.target)) { | ||||
|         const nextTarget = (n2.target = isString(targetSelector) | ||||
|           ? hostQuerySelector(targetSelector) | ||||
|           ? hostQuerySelector!(targetSelector) | ||||
|           : targetSelector) | ||||
|         if (nextTarget != null) { | ||||
|           // move content
 | ||||
|  | ||||
| @ -39,6 +39,7 @@ export const Portal = (Symbol(__DEV__ ? 'Portal' : undefined) as any) as { | ||||
| } | ||||
| export const Text = Symbol(__DEV__ ? 'Text' : undefined) | ||||
| export const Comment = Symbol(__DEV__ ? 'Comment' : undefined) | ||||
| export const Static = Symbol(__DEV__ ? 'Static' : undefined) | ||||
| 
 | ||||
| export type VNodeTypes = | ||||
|   | string | ||||
| @ -46,6 +47,7 @@ export type VNodeTypes = | ||||
|   | typeof Fragment | ||||
|   | typeof Portal | ||||
|   | typeof Text | ||||
|   | typeof Static | ||||
|   | typeof Comment | ||||
|   | typeof SuspenseImpl | ||||
| 
 | ||||
| @ -328,6 +330,10 @@ export function createTextVNode(text: string = ' ', flag: number = 0): VNode { | ||||
|   return createVNode(Text, null, text, flag) | ||||
| } | ||||
| 
 | ||||
| export function createStaticVNode(content: string): VNode { | ||||
|   return createVNode(Static, null, content) | ||||
| } | ||||
| 
 | ||||
| export function createCommentVNode( | ||||
|   text: string = '', | ||||
|   // when used as the v-else branch, the comment node must be created as a
 | ||||
|  | ||||
| @ -1,8 +1,13 @@ | ||||
| import { RendererOptions } from '@vue/runtime-core/src' | ||||
| 
 | ||||
| const doc = (typeof document !== 'undefined' ? document : null) as Document | ||||
| const svgNS = 'http://www.w3.org/2000/svg' | ||||
| 
 | ||||
| export const nodeOps = { | ||||
|   insert: (child: Node, parent: Node, anchor?: Node) => { | ||||
| let tempContainer: HTMLElement | ||||
| let tempSVGContainer: SVGElement | ||||
| 
 | ||||
| export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = { | ||||
|   insert: (child, parent, anchor) => { | ||||
|     if (anchor != null) { | ||||
|       parent.insertBefore(child, anchor) | ||||
|     } else { | ||||
| @ -10,37 +15,50 @@ export const nodeOps = { | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   remove: (child: Node) => { | ||||
|   remove: child => { | ||||
|     const parent = child.parentNode | ||||
|     if (parent != null) { | ||||
|       parent.removeChild(child) | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   createElement: (tag: string, isSVG?: boolean): Element => | ||||
|   createElement: (tag, isSVG): Element => | ||||
|     isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag), | ||||
| 
 | ||||
|   createText: (text: string): Text => doc.createTextNode(text), | ||||
|   createText: text => doc.createTextNode(text), | ||||
| 
 | ||||
|   createComment: (text: string): Comment => doc.createComment(text), | ||||
|   createComment: text => doc.createComment(text), | ||||
| 
 | ||||
|   setText: (node: Text, text: string) => { | ||||
|   setText: (node, text) => { | ||||
|     node.nodeValue = text | ||||
|   }, | ||||
| 
 | ||||
|   setElementText: (el: HTMLElement, text: string) => { | ||||
|   setElementText: (el, text) => { | ||||
|     el.textContent = text | ||||
|   }, | ||||
| 
 | ||||
|   parentNode: (node: Node): HTMLElement | null => | ||||
|     node.parentNode as HTMLElement, | ||||
|   parentNode: node => node.parentNode as Element | null, | ||||
| 
 | ||||
|   nextSibling: (node: Node): Node | null => node.nextSibling, | ||||
|   nextSibling: node => node.nextSibling, | ||||
| 
 | ||||
|   querySelector: (selector: string): Element | null => | ||||
|     doc.querySelector(selector), | ||||
|   querySelector: selector => doc.querySelector(selector), | ||||
| 
 | ||||
|   setScopeId(el: Element, id: string) { | ||||
|   setScopeId(el, id) { | ||||
|     el.setAttribute(id, '') | ||||
|   }, | ||||
| 
 | ||||
|   cloneNode(el) { | ||||
|     return el.cloneNode(true) | ||||
|   }, | ||||
| 
 | ||||
|   insertStaticContent(content, parent, anchor, isSVG) { | ||||
|     const temp = isSVG | ||||
|       ? tempSVGContainer || | ||||
|         (tempSVGContainer = doc.createElementNS(svgNS, 'svg')) | ||||
|       : tempContainer || (tempContainer = doc.createElement('div')) | ||||
|     temp.innerHTML = content | ||||
|     const node = temp.children[0] | ||||
|     nodeOps.insert(node, parent, anchor) | ||||
|     return node | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -4,23 +4,19 @@ import { patchAttr } from './modules/attrs' | ||||
| import { patchDOMProp } from './modules/props' | ||||
| import { patchEvent } from './modules/events' | ||||
| import { isOn } from '@vue/shared' | ||||
| import { | ||||
|   ComponentInternalInstance, | ||||
|   SuspenseBoundary, | ||||
|   VNode | ||||
| } from '@vue/runtime-core' | ||||
| import { RendererOptions } from '@vue/runtime-core' | ||||
| 
 | ||||
| export function patchProp( | ||||
|   el: Element, | ||||
|   key: string, | ||||
|   nextValue: any, | ||||
|   prevValue: any, | ||||
|   isSVG: boolean, | ||||
|   prevChildren?: VNode[], | ||||
|   parentComponent?: ComponentInternalInstance, | ||||
|   parentSuspense?: SuspenseBoundary<Node, Element>, | ||||
|   unmountChildren?: any | ||||
| ) { | ||||
| export const patchProp: RendererOptions<Node, Element>['patchProp'] = ( | ||||
|   el, | ||||
|   key, | ||||
|   nextValue, | ||||
|   prevValue, | ||||
|   isSVG = false, | ||||
|   prevChildren, | ||||
|   parentComponent, | ||||
|   parentSuspense, | ||||
|   unmountChildren | ||||
| ) => { | ||||
|   switch (key) { | ||||
|     // special
 | ||||
|     case 'class': | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user