]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(sfc): scopeId runtime support
authorEvan You <yyx990803@gmail.com>
Mon, 16 Dec 2019 18:33:10 +0000 (13:33 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 17 Dec 2019 17:31:38 +0000 (12:31 -0500)
jest.config.js
packages/runtime-core/src/helpers/scopeId.ts [new file with mode: 0644]
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts
packages/runtime-dom/src/nodeOps.ts
packages/runtime-test/src/nodeOps.ts

index 5d9067c8ecaa0b0912a7464953ec1c7eb4a208db..088648053da928d729e9ddbc5670e3b7dc459d67 100644 (file)
@@ -5,7 +5,7 @@ module.exports = {
     __TEST__: true,
     __VERSION__: require('./package.json').version,
     __BROWSER__: false,
-    __BUNDLER__: false,
+    __BUNDLER__: true,
     __RUNTIME_COMPILE__: true,
     __FEATURE_OPTIONS__: true,
     __FEATURE_SUSPENSE__: true
diff --git a/packages/runtime-core/src/helpers/scopeId.ts b/packages/runtime-core/src/helpers/scopeId.ts
new file mode 100644 (file)
index 0000000..32c7c99
--- /dev/null
@@ -0,0 +1,34 @@
+// SFC scoped style ID management.
+// These are only used in esm-bundler builds, but since exports cannot be
+// conditional, we can only drop inner implementations in non-bundler builds.
+
+export let currentScopeId: string | null = null
+const scopeIdStack: string[] = []
+
+export function pushScopeId(id: string) {
+  if (__BUNDLER__) {
+    scopeIdStack.push((currentScopeId = id))
+  }
+}
+
+export function popScopeId() {
+  if (__BUNDLER__) {
+    scopeIdStack.pop()
+    currentScopeId = scopeIdStack[scopeIdStack.length - 1] || null
+  }
+}
+
+export function withScopeId(id: string): <T extends Function>(fn: T) => T {
+  if (__BUNDLER__) {
+    return ((fn: Function) => {
+      return function(this: any) {
+        pushScopeId(id)
+        const res = fn.apply(this, arguments)
+        popScopeId()
+        return res
+      }
+    }) as any
+  } else {
+    return undefined as any
+  }
+}
index 2629f0f379436361c0c4b48ed6db050733d39829..aa624d5ef7ddf43b7ae4477affbdb13c79cf1eac 100644 (file)
@@ -82,6 +82,7 @@ export { toString } from './helpers/toString'
 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'
 // Since @vue/shared is inlined into final builds,
 // when re-exporting from @vue/shared we need to avoid relying on their original
index 4747ed6d512c771db74da07cbf97558fcbfed3a0..4fe570b6ba813bc6a762052f589affc86ea41105 100644 (file)
@@ -63,7 +63,7 @@ export interface RendererOptions<HostNode = any, HostElement = any> {
     key: string,
     value: any,
     oldValue: any,
-    isSVG: boolean,
+    isSVG?: boolean,
     prevChildren?: VNode<HostNode, HostElement>[],
     parentComponent?: ComponentInternalInstance | null,
     parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
@@ -83,6 +83,7 @@ export interface RendererOptions<HostNode = any, HostElement = any> {
   parentNode(node: HostNode): HostElement | null
   nextSibling(node: HostNode): HostNode | null
   querySelector(selector: string): HostElement | null
+  setScopeId(el: HostNode, id: string): void
 }
 
 export type RootRenderFunction<HostNode, HostElement> = (
@@ -189,7 +190,8 @@ export function createRenderer<
     setElementText: hostSetElementText,
     parentNode: hostParentNode,
     nextSibling: hostNextSibling,
-    querySelector: hostQuerySelector
+    querySelector: hostQuerySelector,
+    setScopeId: hostSetScopeId
   } = options
 
   const internals: RendererInternals<HostNode, HostElement> = {
@@ -368,7 +370,9 @@ export function createRenderer<
     const tag = vnode.type as string
     isSVG = isSVG || tag === 'svg'
     const el = (vnode.el = hostCreateElement(tag, isSVG))
-    const { props, shapeFlag, transition } = vnode
+    const { props, shapeFlag, transition, scopeId } = vnode
+
+    // props
     if (props != null) {
       for (const key in props) {
         if (isReservedProp(key)) continue
@@ -378,6 +382,19 @@ export function createRenderer<
         invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
       }
     }
+
+    // scopeId
+    if (__BUNDLER__ && 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 + '::slot')
+      }
+    }
+
+    // children
     if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
       hostSetElementText(el, vnode.children as string)
     } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
index 89f2b48c01fb9afecf1aaf90bbba6ef9ffd640fa..05cbcf54103634ded6b4309cd1ec9d9b0682a34c 100644 (file)
@@ -21,6 +21,7 @@ import { DirectiveBinding } from './directives'
 import { SuspenseImpl } from './components/Suspense'
 import { TransitionHooks } from './components/BaseTransition'
 import { warn } from './warning'
+import { currentScopeId } from './helpers/scopeId'
 
 export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
   __isFragment: true
@@ -90,6 +91,7 @@ export interface VNode<HostNode = any, HostElement = any> {
   props: VNodeProps | null
   key: string | number | null
   ref: string | Ref | ((ref: object | null) => void) | null
+  scopeId: string | null // SFC only
   children: NormalizedChildren<HostNode, HostElement>
   component: ComponentInternalInstance | null
   suspense: SuspenseBoundary<HostNode, HostElement> | null
@@ -246,6 +248,7 @@ export function createVNode(
     props,
     key: (props !== null && props.key) || null,
     ref: (props !== null && props.ref) || null,
+    scopeId: currentScopeId,
     children: null,
     component: null,
     suspense: null,
@@ -296,6 +299,7 @@ export function cloneVNode<T, U>(
       : vnode.props,
     key: vnode.key,
     ref: vnode.ref,
+    scopeId: vnode.scopeId,
     children: vnode.children,
     target: vnode.target,
     shapeFlag: vnode.shapeFlag,
index accc701955cc3a29f298cb64e2f4c51be562cfd7..332e6654c45aeed87520570e4d96db24d03a3ec3 100644 (file)
@@ -38,5 +38,9 @@ export const nodeOps = {
   nextSibling: (node: Node): Node | null => node.nextSibling,
 
   querySelector: (selector: string): Element | null =>
-    doc.querySelector(selector)
+    doc.querySelector(selector),
+
+  setScopeId(el: Element, id: string) {
+    el.setAttribute(id, '')
+  }
 }
index 6d0c7c61748a74995c6f3feb58b53a6c99052a87..1352f7668c4fd1bf1d2047e7c5e7ce9e74dbdc2f 100644 (file)
@@ -228,6 +228,10 @@ function querySelector(): any {
   throw new Error('querySelector not supported in test renderer.')
 }
 
+function setScopeId(el: TestElement, id: string) {
+  el.props[id] = ''
+}
+
 export const nodeOps = {
   insert,
   remove,
@@ -238,5 +242,6 @@ export const nodeOps = {
   setElementText,
   parentNode,
   nextSibling,
-  querySelector
+  querySelector,
+  setScopeId
 }