diff --git a/jest.config.js b/jest.config.js index 5d9067c8..08864805 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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 index 00000000..32c7c99a --- /dev/null +++ b/packages/runtime-core/src/helpers/scopeId.ts @@ -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): (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 + } +} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 2629f0f3..aa624d5e 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -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 diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 4747ed6d..4fe570b6 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -63,7 +63,7 @@ export interface RendererOptions { key: string, value: any, oldValue: any, - isSVG: boolean, + isSVG?: boolean, prevChildren?: VNode[], parentComponent?: ComponentInternalInstance | null, parentSuspense?: SuspenseBoundary | null, @@ -83,6 +83,7 @@ export interface RendererOptions { parentNode(node: HostNode): HostElement | null nextSibling(node: HostNode): HostNode | null querySelector(selector: string): HostElement | null + setScopeId(el: HostNode, id: string): void } export type RootRenderFunction = ( @@ -189,7 +190,8 @@ export function createRenderer< setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, - querySelector: hostQuerySelector + querySelector: hostQuerySelector, + setScopeId: hostSetScopeId } = options const internals: RendererInternals = { @@ -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) { diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 89f2b48c..05cbcf54 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -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 { props: VNodeProps | null key: string | number | null ref: string | Ref | ((ref: object | null) => void) | null + scopeId: string | null // SFC only children: NormalizedChildren component: ComponentInternalInstance | null suspense: SuspenseBoundary | 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( : vnode.props, key: vnode.key, ref: vnode.ref, + scopeId: vnode.scopeId, children: vnode.children, target: vnode.target, shapeFlag: vnode.shapeFlag, diff --git a/packages/runtime-dom/src/nodeOps.ts b/packages/runtime-dom/src/nodeOps.ts index accc7019..332e6654 100644 --- a/packages/runtime-dom/src/nodeOps.ts +++ b/packages/runtime-dom/src/nodeOps.ts @@ -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, '') + } } diff --git a/packages/runtime-test/src/nodeOps.ts b/packages/runtime-test/src/nodeOps.ts index 6d0c7c61..1352f766 100644 --- a/packages/runtime-test/src/nodeOps.ts +++ b/packages/runtime-test/src/nodeOps.ts @@ -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 }