feat(sfc): scopeId runtime support
This commit is contained in:
parent
04e11187b9
commit
69c9dbc825
@ -5,7 +5,7 @@ module.exports = {
|
|||||||
__TEST__: true,
|
__TEST__: true,
|
||||||
__VERSION__: require('./package.json').version,
|
__VERSION__: require('./package.json').version,
|
||||||
__BROWSER__: false,
|
__BROWSER__: false,
|
||||||
__BUNDLER__: false,
|
__BUNDLER__: true,
|
||||||
__RUNTIME_COMPILE__: true,
|
__RUNTIME_COMPILE__: true,
|
||||||
__FEATURE_OPTIONS__: true,
|
__FEATURE_OPTIONS__: true,
|
||||||
__FEATURE_SUSPENSE__: true
|
__FEATURE_SUSPENSE__: true
|
||||||
|
34
packages/runtime-core/src/helpers/scopeId.ts
Normal file
34
packages/runtime-core/src/helpers/scopeId.ts
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,7 @@ export { toString } from './helpers/toString'
|
|||||||
export { toHandlers } from './helpers/toHandlers'
|
export { toHandlers } from './helpers/toHandlers'
|
||||||
export { renderSlot } from './helpers/renderSlot'
|
export { renderSlot } from './helpers/renderSlot'
|
||||||
export { createSlots } from './helpers/createSlots'
|
export { createSlots } from './helpers/createSlots'
|
||||||
|
export { pushScopeId, popScopeId, withScopeId } from './helpers/scopeId'
|
||||||
export { setBlockTracking, createTextVNode, createCommentVNode } from './vnode'
|
export { setBlockTracking, createTextVNode, createCommentVNode } from './vnode'
|
||||||
// Since @vue/shared is inlined into final builds,
|
// Since @vue/shared is inlined into final builds,
|
||||||
// when re-exporting from @vue/shared we need to avoid relying on their original
|
// when re-exporting from @vue/shared we need to avoid relying on their original
|
||||||
|
@ -63,7 +63,7 @@ export interface RendererOptions<HostNode = any, HostElement = any> {
|
|||||||
key: string,
|
key: string,
|
||||||
value: any,
|
value: any,
|
||||||
oldValue: any,
|
oldValue: any,
|
||||||
isSVG: boolean,
|
isSVG?: boolean,
|
||||||
prevChildren?: VNode<HostNode, HostElement>[],
|
prevChildren?: VNode<HostNode, HostElement>[],
|
||||||
parentComponent?: ComponentInternalInstance | null,
|
parentComponent?: ComponentInternalInstance | null,
|
||||||
parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
|
parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
@ -83,6 +83,7 @@ export interface RendererOptions<HostNode = any, HostElement = any> {
|
|||||||
parentNode(node: HostNode): HostElement | null
|
parentNode(node: HostNode): HostElement | null
|
||||||
nextSibling(node: HostNode): HostNode | null
|
nextSibling(node: HostNode): HostNode | null
|
||||||
querySelector(selector: string): HostElement | null
|
querySelector(selector: string): HostElement | null
|
||||||
|
setScopeId(el: HostNode, id: string): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RootRenderFunction<HostNode, HostElement> = (
|
export type RootRenderFunction<HostNode, HostElement> = (
|
||||||
@ -189,7 +190,8 @@ export function createRenderer<
|
|||||||
setElementText: hostSetElementText,
|
setElementText: hostSetElementText,
|
||||||
parentNode: hostParentNode,
|
parentNode: hostParentNode,
|
||||||
nextSibling: hostNextSibling,
|
nextSibling: hostNextSibling,
|
||||||
querySelector: hostQuerySelector
|
querySelector: hostQuerySelector,
|
||||||
|
setScopeId: hostSetScopeId
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const internals: RendererInternals<HostNode, HostElement> = {
|
const internals: RendererInternals<HostNode, HostElement> = {
|
||||||
@ -368,7 +370,9 @@ export function createRenderer<
|
|||||||
const tag = vnode.type as string
|
const tag = vnode.type as string
|
||||||
isSVG = isSVG || tag === 'svg'
|
isSVG = isSVG || tag === 'svg'
|
||||||
const el = (vnode.el = hostCreateElement(tag, isSVG))
|
const el = (vnode.el = hostCreateElement(tag, isSVG))
|
||||||
const { props, shapeFlag, transition } = vnode
|
const { props, shapeFlag, transition, scopeId } = vnode
|
||||||
|
|
||||||
|
// props
|
||||||
if (props != null) {
|
if (props != null) {
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
if (isReservedProp(key)) continue
|
if (isReservedProp(key)) continue
|
||||||
@ -378,6 +382,19 @@ export function createRenderer<
|
|||||||
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
|
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) {
|
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
||||||
hostSetElementText(el, vnode.children as string)
|
hostSetElementText(el, vnode.children as string)
|
||||||
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||||
|
@ -21,6 +21,7 @@ import { DirectiveBinding } from './directives'
|
|||||||
import { SuspenseImpl } from './components/Suspense'
|
import { SuspenseImpl } from './components/Suspense'
|
||||||
import { TransitionHooks } from './components/BaseTransition'
|
import { TransitionHooks } from './components/BaseTransition'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
import { currentScopeId } from './helpers/scopeId'
|
||||||
|
|
||||||
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
||||||
__isFragment: true
|
__isFragment: true
|
||||||
@ -90,6 +91,7 @@ export interface VNode<HostNode = any, HostElement = any> {
|
|||||||
props: VNodeProps | null
|
props: VNodeProps | null
|
||||||
key: string | number | null
|
key: string | number | null
|
||||||
ref: string | Ref | ((ref: object | null) => void) | null
|
ref: string | Ref | ((ref: object | null) => void) | null
|
||||||
|
scopeId: string | null // SFC only
|
||||||
children: NormalizedChildren<HostNode, HostElement>
|
children: NormalizedChildren<HostNode, HostElement>
|
||||||
component: ComponentInternalInstance | null
|
component: ComponentInternalInstance | null
|
||||||
suspense: SuspenseBoundary<HostNode, HostElement> | null
|
suspense: SuspenseBoundary<HostNode, HostElement> | null
|
||||||
@ -246,6 +248,7 @@ export function createVNode(
|
|||||||
props,
|
props,
|
||||||
key: (props !== null && props.key) || null,
|
key: (props !== null && props.key) || null,
|
||||||
ref: (props !== null && props.ref) || null,
|
ref: (props !== null && props.ref) || null,
|
||||||
|
scopeId: currentScopeId,
|
||||||
children: null,
|
children: null,
|
||||||
component: null,
|
component: null,
|
||||||
suspense: null,
|
suspense: null,
|
||||||
@ -296,6 +299,7 @@ export function cloneVNode<T, U>(
|
|||||||
: vnode.props,
|
: vnode.props,
|
||||||
key: vnode.key,
|
key: vnode.key,
|
||||||
ref: vnode.ref,
|
ref: vnode.ref,
|
||||||
|
scopeId: vnode.scopeId,
|
||||||
children: vnode.children,
|
children: vnode.children,
|
||||||
target: vnode.target,
|
target: vnode.target,
|
||||||
shapeFlag: vnode.shapeFlag,
|
shapeFlag: vnode.shapeFlag,
|
||||||
|
@ -38,5 +38,9 @@ export const nodeOps = {
|
|||||||
nextSibling: (node: Node): Node | null => node.nextSibling,
|
nextSibling: (node: Node): Node | null => node.nextSibling,
|
||||||
|
|
||||||
querySelector: (selector: string): Element | null =>
|
querySelector: (selector: string): Element | null =>
|
||||||
doc.querySelector(selector)
|
doc.querySelector(selector),
|
||||||
|
|
||||||
|
setScopeId(el: Element, id: string) {
|
||||||
|
el.setAttribute(id, '')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,6 +228,10 @@ function querySelector(): any {
|
|||||||
throw new Error('querySelector not supported in test renderer.')
|
throw new Error('querySelector not supported in test renderer.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setScopeId(el: TestElement, id: string) {
|
||||||
|
el.props[id] = ''
|
||||||
|
}
|
||||||
|
|
||||||
export const nodeOps = {
|
export const nodeOps = {
|
||||||
insert,
|
insert,
|
||||||
remove,
|
remove,
|
||||||
@ -238,5 +242,6 @@ export const nodeOps = {
|
|||||||
setElementText,
|
setElementText,
|
||||||
parentNode,
|
parentNode,
|
||||||
nextSibling,
|
nextSibling,
|
||||||
querySelector
|
querySelector,
|
||||||
|
setScopeId
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user