fix: ensure backwards compat for pre-compiled sfc components

fix #3493
This commit is contained in:
Evan You 2021-03-27 10:53:45 -04:00
parent 9ff70be2b3
commit 37c17091fd
16 changed files with 278 additions and 96 deletions

View File

@ -1,48 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = ` exports[`scopeId compiler support should push scopeId for hoisted nodes 1`] = `
"import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, setScopeId as _setScopeId } from \\"vue\\" "import { createVNode as _createVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
_setScopeId(\\"test\\") _pushScopeId(\\"test\\")
const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"hello\\", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"world\\", -1 /* HOISTED */)
_setScopeId(null) _popScopeId()
export function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
return (_openBlock(), _createBlock(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_hoisted_1, _hoisted_1,
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */), _createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
_hoisted_2 _hoisted_2
])) ]))
}" })"
`; `;
exports[`scopeId compiler support should wrap default slot 1`] = ` exports[`scopeId compiler support should wrap default slot 1`] = `
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" "import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
const _component_Child = _resolveComponent(\\"Child\\") const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, { return (_openBlock(), _createBlock(_component_Child, null, {
default: _withCtx(() => [ default: _withId(() => [
_createVNode(\\"div\\") _createVNode(\\"div\\")
]), ]),
_: 1 /* STABLE */ _: 1 /* STABLE */
})) }))
}" })"
`; `;
exports[`scopeId compiler support should wrap dynamic slots 1`] = ` exports[`scopeId compiler support should wrap dynamic slots 1`] = `
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" "import { createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, renderList as _renderList, createSlots as _createSlots, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
const _component_Child = _resolveComponent(\\"Child\\") const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 2 /* DYNAMIC */ }, [ return (_openBlock(), _createBlock(_component_Child, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
(_ctx.ok) (_ctx.ok)
? { ? {
name: \\"foo\\", name: \\"foo\\",
fn: _withCtx(() => [ fn: _withId(() => [
_createVNode(\\"div\\") _createVNode(\\"div\\")
]) ])
} }
@ -50,29 +53,30 @@ export function render(_ctx, _cache) {
_renderList(_ctx.list, (i) => { _renderList(_ctx.list, (i) => {
return { return {
name: i, name: i,
fn: _withCtx(() => [ fn: _withId(() => [
_createVNode(\\"div\\") _createVNode(\\"div\\")
]) ])
} }
}) })
]), 1024 /* DYNAMIC_SLOTS */)) ]), 1024 /* DYNAMIC_SLOTS */))
}" })"
`; `;
exports[`scopeId compiler support should wrap named slots 1`] = ` exports[`scopeId compiler support should wrap named slots 1`] = `
"import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" "import { toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, createVNode as _createVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\"
const _withId = /*#__PURE__*/_withScopeId(\\"test\\")
export function render(_ctx, _cache) { export const render = /*#__PURE__*/_withId((_ctx, _cache) => {
const _component_Child = _resolveComponent(\\"Child\\") const _component_Child = _resolveComponent(\\"Child\\")
return (_openBlock(), _createBlock(_component_Child, null, { return (_openBlock(), _createBlock(_component_Child, null, {
foo: _withCtx(({ msg }) => [ foo: _withId(({ msg }) => [
_createTextVNode(_toDisplayString(msg), 1 /* TEXT */) _createTextVNode(_toDisplayString(msg), 1 /* TEXT */)
]), ]),
bar: _withCtx(() => [ bar: _withId(() => [
_createVNode(\\"div\\") _createVNode(\\"div\\")
]), ]),
_: 1 /* STABLE */ _: 1 /* STABLE */
})) }))
}" })"
`; `;

View File

@ -1,5 +1,5 @@
import { baseCompile } from '../src/compile' import { baseCompile } from '../src/compile'
import { SET_SCOPE_ID } from '../src/runtimeHelpers' import { PUSH_SCOPE_ID, POP_SCOPE_ID } from '../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { genFlagText } from './testUtils' import { genFlagText } from './testUtils'
@ -20,7 +20,7 @@ describe('scopeId compiler support', () => {
mode: 'module', mode: 'module',
scopeId: 'test' scopeId: 'test'
}) })
expect(code).toMatch(`default: _withCtx(() => [`) expect(code).toMatch(`default: _withId(() => [`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -36,8 +36,8 @@ describe('scopeId compiler support', () => {
scopeId: 'test' scopeId: 'test'
} }
) )
expect(code).toMatch(`foo: _withCtx(({ msg }) => [`) expect(code).toMatch(`foo: _withId(({ msg }) => [`)
expect(code).toMatch(`bar: _withCtx(() => [`) expect(code).toMatch(`bar: _withId(() => [`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -53,8 +53,8 @@ describe('scopeId compiler support', () => {
scopeId: 'test' scopeId: 'test'
} }
) )
expect(code).toMatch(/name: "foo",\s+fn: _withCtx\(/) expect(code).toMatch(/name: "foo",\s+fn: _withId\(/)
expect(code).toMatch(/name: i,\s+fn: _withCtx\(/) expect(code).toMatch(/name: i,\s+fn: _withId\(/)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -67,18 +67,19 @@ describe('scopeId compiler support', () => {
hoistStatic: true hoistStatic: true
} }
) )
expect(ast.helpers).toContain(SET_SCOPE_ID) expect(ast.helpers).toContain(PUSH_SCOPE_ID)
expect(ast.helpers).toContain(POP_SCOPE_ID)
expect(ast.hoists.length).toBe(2) expect(ast.hoists.length).toBe(2)
expect(code).toMatch( expect(code).toMatch(
[ [
`_setScopeId("test")`, `_pushScopeId("test")`,
`const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, "hello", ${genFlagText( `const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, "hello", ${genFlagText(
PatchFlags.HOISTED PatchFlags.HOISTED
)})`, )})`,
`const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "world", ${genFlagText( `const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "world", ${genFlagText(
PatchFlags.HOISTED PatchFlags.HOISTED
)})`, )})`,
`_setScopeId(null)` `_popScopeId()`
].join('\n') ].join('\n')
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()

View File

@ -129,7 +129,7 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => { return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return _renderSlot($slots, \\"default\\", {}, undefined, true) return _renderSlot($slots, \\"default\\")
}), 256 /* UNKEYED_FRAGMENT */)) }), 256 /* UNKEYED_FRAGMENT */))
} }
}" }"
@ -143,7 +143,7 @@ return function render(_ctx, _cache) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, renderSlot: _renderSlot } = _Vue
return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => { return (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
return _renderSlot($slots, \\"default\\", {}, undefined, true) return _renderSlot($slots, \\"default\\")
}), 256 /* UNKEYED_FRAGMENT */)) }), 256 /* UNKEYED_FRAGMENT */))
} }
}" }"

View File

@ -80,7 +80,7 @@ return function render(_ctx, _cache) {
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok return ok
? _renderSlot($slots, \\"default\\", { key: 0 }, undefined, true) ? _renderSlot($slots, \\"default\\", { key: 0 })
: _createCommentVNode(\\"v-if\\", true) : _createCommentVNode(\\"v-if\\", true)
} }
}" }"
@ -140,7 +140,7 @@ return function render(_ctx, _cache) {
const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue const { renderSlot: _renderSlot, createCommentVNode: _createCommentVNode } = _Vue
return ok return ok
? _renderSlot($slots, \\"default\\", { key: 0 }, undefined, true) ? _renderSlot($slots, \\"default\\", { key: 0 })
: _createCommentVNode(\\"v-if\\", true) : _createCommentVNode(\\"v-if\\", true)
} }
}" }"

View File

@ -67,7 +67,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createBlock(\\"div\\", null, [ return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || ( _cache[1] || (
_setBlockTracking(-1), _setBlockTracking(-1),
_cache[1] = _renderSlot($slots, \\"default\\", {}, undefined, true), _cache[1] = _renderSlot($slots, \\"default\\"),
_setBlockTracking(1), _setBlockTracking(1),
_cache[1] _cache[1]
) )

View File

@ -16,7 +16,6 @@ import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
function parseWithSlots(template: string, options: CompilerOptions = {}) { function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template) const ast = parse(template)
transform(ast, { transform(ast, {
slotted: false,
nodeTransforms: [ nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []), ...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet, transformSlotOutlet,
@ -340,8 +339,8 @@ describe('compiler: transform <slot> outlets', () => {
}) })
}) })
test('slot with slotted: true', async () => { test('slot with slotted: false', async () => {
const ast = parseWithSlots(`<slot/>`, { slotted: true }) const ast = parseWithSlots(`<slot/>`, { slotted: false, scopeId: 'foo' })
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,

View File

@ -404,13 +404,7 @@ describe('compiler: v-if', () => {
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: [ arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
'$slots',
'"default"',
createObjectMatcher({ key: `[0]` }),
'undefined',
'true'
]
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -423,13 +417,7 @@ describe('compiler: v-if', () => {
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: [ arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })]
'$slots',
'"default"',
createObjectMatcher({ key: `[0]` }),
'undefined',
'true'
]
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })

View File

@ -43,7 +43,9 @@ import {
SET_BLOCK_TRACKING, SET_BLOCK_TRACKING,
CREATE_COMMENT, CREATE_COMMENT,
CREATE_TEXT, CREATE_TEXT,
SET_SCOPE_ID, PUSH_SCOPE_ID,
POP_SCOPE_ID,
WITH_SCOPE_ID,
WITH_DIRECTIVES, WITH_DIRECTIVES,
CREATE_BLOCK, CREATE_BLOCK,
OPEN_BLOCK, OPEN_BLOCK,
@ -53,6 +55,7 @@ import {
import { ImportItem } from './transform' import { ImportItem } from './transform'
const PURE_ANNOTATION = `/*#__PURE__*/` const PURE_ANNOTATION = `/*#__PURE__*/`
const WITH_ID = `_withId`
type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
@ -195,11 +198,13 @@ export function generate(
indent, indent,
deindent, deindent,
newline, newline,
scopeId,
ssr ssr
} = context } = context
const hasHelpers = ast.helpers.length > 0 const hasHelpers = ast.helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module' const useWithBlock = !prefixIdentifiers && mode !== 'module'
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
const isSetupInlined = !__BROWSER__ && !!options.inline const isSetupInlined = !__BROWSER__ && !!options.inline
// preambles // preambles
@ -209,7 +214,7 @@ export function generate(
? createCodegenContext(ast, options) ? createCodegenContext(ast, options)
: context : context
if (!__BROWSER__ && mode === 'module') { if (!__BROWSER__ && mode === 'module') {
genModulePreamble(ast, preambleContext, isSetupInlined) genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
} else { } else {
genFunctionPreamble(ast, preambleContext) genFunctionPreamble(ast, preambleContext)
} }
@ -226,7 +231,14 @@ export function generate(
? args.map(arg => `${arg}: any`).join(',') ? args.map(arg => `${arg}: any`).join(',')
: args.join(', ') : args.join(', ')
if (genScopeId) {
if (isSetupInlined) { if (isSetupInlined) {
push(`${PURE_ANNOTATION}${WITH_ID}(`)
} else {
push(`const ${functionName} = ${PURE_ANNOTATION}${WITH_ID}(`)
}
}
if (isSetupInlined || genScopeId) {
push(`(${signature}) => {`) push(`(${signature}) => {`)
} else { } else {
push(`function ${functionName}(${signature}) {`) push(`function ${functionName}(${signature}) {`)
@ -291,6 +303,10 @@ export function generate(
deindent() deindent()
push(`}`) push(`}`)
if (genScopeId) {
push(`)`)
}
return { return {
ast, ast,
code: context.code, code: context.code,
@ -361,6 +377,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
function genModulePreamble( function genModulePreamble(
ast: RootNode, ast: RootNode,
context: CodegenContext, context: CodegenContext,
genScopeId: boolean,
inline?: boolean inline?: boolean
) { ) {
const { const {
@ -369,12 +386,14 @@ function genModulePreamble(
optimizeImports, optimizeImports,
runtimeModuleName, runtimeModuleName,
scopeId, scopeId,
mode helper
} = context } = context
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module' if (genScopeId) {
if (genScopeId && ast.hoists.length) { ast.helpers.push(WITH_SCOPE_ID)
ast.helpers.push(SET_SCOPE_ID) if (ast.hoists.length) {
ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
}
} }
// generate import statements for helpers // generate import statements for helpers
@ -417,6 +436,17 @@ function genModulePreamble(
newline() newline()
} }
// we technically don't need this anymore since `withCtx` already sets the
// correct scopeId, but this is necessary for backwards compat
if (genScopeId) {
push(
`const ${WITH_ID} = ${PURE_ANNOTATION}${helper(
WITH_SCOPE_ID
)}("${scopeId}")`
)
newline()
}
genHoists(ast.hoists, context) genHoists(ast.hoists, context)
newline() newline()
@ -463,7 +493,7 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
// push scope Id before initializing hoisted vnodes so that these vnodes // push scope Id before initializing hoisted vnodes so that these vnodes
// get the proper scopeId as well. // get the proper scopeId as well.
if (genScopeId) { if (genScopeId) {
push(`${helper(SET_SCOPE_ID)}("${scopeId}")`) push(`${helper(PUSH_SCOPE_ID)}("${scopeId}")`)
newline() newline()
} }
@ -476,7 +506,7 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
}) })
if (genScopeId) { if (genScopeId) {
push(`${helper(SET_SCOPE_ID)}(null)`) push(`${helper(POP_SCOPE_ID)}()`)
newline() newline()
} }
context.pure = false context.pure = false
@ -800,12 +830,15 @@ function genFunctionExpression(
node: FunctionExpression, node: FunctionExpression,
context: CodegenContext context: CodegenContext
) { ) {
const { push, indent, deindent } = context const { push, indent, deindent, scopeId, mode } = context
const { params, returns, body, newline, isSlot } = node const { params, returns, body, newline, isSlot } = node
// slot functions also need to push scopeId before rendering its content
const genScopeId =
!__BROWSER__ && isSlot && scopeId != null && mode !== 'function'
if (isSlot) { if (isSlot) {
// wrap slot functions with owner context // wrap slot functions with owner context
push(`_${helperNameMap[WITH_CTX]}(`) push(genScopeId ? `${WITH_ID}(` : `_${helperNameMap[WITH_CTX]}(`)
} }
push(`(`, node) push(`(`, node)
if (isArray(params)) { if (isArray(params)) {

View File

@ -25,7 +25,9 @@ export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``) export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``) export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``)
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``) export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
export const SET_SCOPE_ID = Symbol(__DEV__ ? `setScopeId` : ``) export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``) export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
export const UNREF = Symbol(__DEV__ ? `unref` : ``) export const UNREF = Symbol(__DEV__ ? `unref` : ``)
export const IS_REF = Symbol(__DEV__ ? `isRef` : ``) export const IS_REF = Symbol(__DEV__ ? `isRef` : ``)
@ -59,7 +61,9 @@ export const helperNameMap: any = {
[CAPITALIZE]: `capitalize`, [CAPITALIZE]: `capitalize`,
[TO_HANDLER_KEY]: `toHandlerKey`, [TO_HANDLER_KEY]: `toHandlerKey`,
[SET_BLOCK_TRACKING]: `setBlockTracking`, [SET_BLOCK_TRACKING]: `setBlockTracking`,
[SET_SCOPE_ID]: `setScopeId`, [PUSH_SCOPE_ID]: `pushScopeId`,
[POP_SCOPE_ID]: `popScopeId`,
[WITH_SCOPE_ID]: `withScopeId`,
[WITH_CTX]: `withCtx`, [WITH_CTX]: `withCtx`,
[UNREF]: `unref`, [UNREF]: `unref`,
[IS_REF]: `isRef` [IS_REF]: `isRef`

View File

@ -34,7 +34,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
slotArgs.push(createFunctionExpression([], children, false, false, loc)) slotArgs.push(createFunctionExpression([], children, false, false, loc))
} }
if (context.slotted) { if (context.scopeId && !context.slotted) {
if (!slotProps) { if (!slotProps) {
slotArgs.push(`{}`) slotArgs.push(`{}`)
} }

View File

@ -10,11 +10,13 @@ describe('ssr: scopeId', () => {
mode: 'module' mode: 'module'
}).code }).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\" "import { withScopeId as _withScopeId } from \\"vue\\"
import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
export function ssrRender(_ctx, _push, _parent, _attrs) { export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
_push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`) _push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
}" })"
`) `)
}) })
@ -26,14 +28,15 @@ describe('ssr: scopeId', () => {
mode: 'module' mode: 'module'
}).code }).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode } from \\"vue\\" "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, withScopeId as _withScopeId } from \\"vue\\"
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\" import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
export function ssrRender(_ctx, _push, _parent, _attrs) { export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withId((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`foo\`) _push(\`foo\`)
} else { } else {
@ -44,7 +47,7 @@ describe('ssr: scopeId', () => {
}), }),
_: 1 /* STABLE */ _: 1 /* STABLE */
}, _parent)) }, _parent))
}" })"
`) `)
}) })
@ -55,14 +58,15 @@ describe('ssr: scopeId', () => {
mode: 'module' mode: 'module'
}).code }).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from \\"vue\\" "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\" import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
export function ssrRender(_ctx, _push, _parent, _attrs) { export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withId((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`) _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
} else { } else {
@ -73,7 +77,7 @@ describe('ssr: scopeId', () => {
}), }),
_: 1 /* STABLE */ _: 1 /* STABLE */
}, _parent)) }, _parent))
}" })"
`) `)
}) })
@ -84,19 +88,20 @@ describe('ssr: scopeId', () => {
mode: 'module' mode: 'module'
}).code }).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from \\"vue\\" "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\" import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
export function ssrRender(_ctx, _push, _parent, _attrs) { export const ssrRender = /*#__PURE__*/_withId((_ctx, _push, _parent, _attrs) => {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
const _component_bar = _resolveComponent(\\"bar\\") const _component_bar = _resolveComponent(\\"bar\\")
_push(_ssrRenderComponent(_component_foo, _attrs, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withId((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`) _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
_push(_ssrRenderComponent(_component_bar, null, { _push(_ssrRenderComponent(_component_bar, null, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withId((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`) _push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
} else { } else {
@ -111,7 +116,7 @@ describe('ssr: scopeId', () => {
return [ return [
_createVNode(\\"span\\", null, \\"hello\\"), _createVNode(\\"span\\", null, \\"hello\\"),
_createVNode(_component_bar, null, { _createVNode(_component_bar, null, {
default: _withCtx(() => [ default: _withId(() => [
_createVNode(\\"span\\") _createVNode(\\"span\\")
]), ]),
_: 1 /* STABLE */ _: 1 /* STABLE */
@ -121,7 +126,7 @@ describe('ssr: scopeId', () => {
}), }),
_: 1 /* STABLE */ _: 1 /* STABLE */
}, _parent)) }, _parent))
}" })"
`) `)
}) })
}) })

View File

@ -105,7 +105,7 @@ describe('ssr: <slot>', () => {
_ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent, \\"hello-s\\" + _scopeId) _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent, \\"hello-s\\" + _scopeId)
} else { } else {
return [ return [
_renderSlot(_ctx.$slots, \\"default\\", {}, undefined, true) _renderSlot(_ctx.$slots, \\"default\\")
] ]
} }
}), }),

View File

@ -3,9 +3,12 @@ import {
render, render,
nodeOps, nodeOps,
serializeInner, serializeInner,
renderSlot renderSlot,
withScopeId,
pushScopeId,
popScopeId
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { setScopeId, withCtx } from '../src/componentRenderContext' import { withCtx } from '../src/componentRenderContext'
describe('scopeId runtime support', () => { describe('scopeId runtime support', () => {
test('should attach scopeId', () => { test('should attach scopeId', () => {
@ -40,7 +43,7 @@ describe('scopeId runtime support', () => {
const Child = { const Child = {
__scopeId: 'child', __scopeId: 'child',
render(this: any) { render(this: any) {
return h('div', renderSlot(this.$slots, 'default', {}, undefined, true)) return h('div', renderSlot(this.$slots, 'default'))
} }
} }
const Child2 = { const Child2 = {
@ -82,7 +85,13 @@ describe('scopeId runtime support', () => {
render(this: any) { render(this: any) {
// <div class="wrapper"><slot/></div> // <div class="wrapper"><slot/></div>
return h('div', { class: 'wrapper' }, [ return h('div', { class: 'wrapper' }, [
renderSlot(this.$slots, 'default') renderSlot(
this.$slots,
'default',
{},
undefined,
true /* noSlotted */
)
]) ])
} }
} }
@ -92,17 +101,15 @@ describe('scopeId runtime support', () => {
render(this: any) { render(this: any) {
// <Wrapper><slot/></Wrapper> // <Wrapper><slot/></Wrapper>
return h(Wrapper, null, { return h(Wrapper, null, {
default: withCtx(() => [ default: withCtx(() => [renderSlot(this.$slots, 'default')])
renderSlot(this.$slots, 'default', {}, undefined, true)
])
}) })
} }
} }
// simulate hoisted node // simulate hoisted node
setScopeId('root') pushScopeId('root')
const hoisted = h('div', 'hoisted') const hoisted = h('div', 'hoisted')
setScopeId(null) popScopeId()
const Root = { const Root = {
__scopeId: 'root', __scopeId: 'root',
@ -178,3 +185,124 @@ describe('scopeId runtime support', () => {
expect(serializeInner(root)).toBe(`<div parent></div>`) expect(serializeInner(root)).toBe(`<div parent></div>`)
}) })
}) })
describe('backwards compat with <=3.0.7', () => {
const withParentId = withScopeId('parent')
const withChildId = withScopeId('child')
test('should attach scopeId', () => {
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h('div', [h('div')])
})
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<div parent><div parent></div></div>`)
})
test('should attach scopeId to components in parent component', () => {
const Child = {
__scopeId: 'child',
render: withChildId(() => {
return h('div')
})
}
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h('div', [h(Child)])
})
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(
`<div parent><div child parent></div></div>`
)
})
test('should work on slots', () => {
const Child = {
__scopeId: 'child',
render: withChildId(function(this: any) {
return h('div', renderSlot(this.$slots, 'default'))
})
}
const withChild2Id = withScopeId('child2')
const Child2 = {
__scopeId: 'child2',
render: withChild2Id(() => h('span'))
}
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h(
Child,
withParentId(() => {
return [h('div'), h(Child2)]
})
)
})
}
const root = nodeOps.createElement('div')
render(h(App), root)
// slot content should have:
// - scopeId from parent
// - slotted scopeId (with `-s` postfix) from child (the tree owner)
expect(serializeInner(root)).toBe(
`<div child parent>` +
`<div parent child-s></div>` +
// component inside slot should have:
// - scopeId from template context
// - slotted scopeId from slot owner
// - its own scopeId
`<span child2 parent child-s></span>` +
`</div>`
)
})
// #1988
test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => {
const withParentId = withScopeId('parent')
const App = {
__scopeId: 'parent',
render: withParentId(() => {
return h(Child)
})
}
function Child() {
return h(Child2, { class: 'foo' })
}
function Child2() {
return h('div')
}
Child2.inheritAttrs = false
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(`<div parent></div>`)
})
test('hoisted nodes', async () => {
pushScopeId('foobar')
const hoisted = h('div', 'hello')
popScopeId()
const App = {
__scopeId: 'foobar',
render: () => h('div', [hoisted])
}
const root = nodeOps.createElement('div')
render(h(App), root)
expect(serializeInner(root)).toBe(
`<div foobar><div foobar>hello</div></div>`
)
})
})

View File

@ -32,10 +32,25 @@ export function setCurrentRenderingInstance(
* Set scope id when creating hoisted vnodes. * Set scope id when creating hoisted vnodes.
* @private compiler helper * @private compiler helper
*/ */
export function setScopeId(id: string | null) { export function pushScopeId(id: string | null) {
currentScopeId = id currentScopeId = id
} }
/**
* Technically we no longer need this after 3.0.8 but we need to keep the same
* API for backwards compat w/ code generated by compilers.
* @private
*/
export function popScopeId() {
currentScopeId = null
}
/**
* Only for backwards compat
* @private
*/
export const withScopeId = (_id: string) => withCtx
/** /**
* Wrap a slot function to memoize current rendering instance * Wrap a slot function to memoize current rendering instance
* @private compiler helper * @private compiler helper

View File

@ -26,7 +26,7 @@ export function renderSlot(
// this is not a user-facing function, so the fallback is always generated by // this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be a function returning an array // the compiler and guaranteed to be a function returning an array
fallback?: () => VNodeArrayChildren, fallback?: () => VNodeArrayChildren,
hasSlotted?: boolean noSlotted?: boolean
): VNode { ): VNode {
let slot = slots[name] let slot = slots[name]
@ -54,7 +54,7 @@ export function renderSlot(
? PatchFlags.STABLE_FRAGMENT ? PatchFlags.STABLE_FRAGMENT
: PatchFlags.BAIL : PatchFlags.BAIL
) )
if (hasSlotted && rendered.scopeId) { if (!noSlotted && rendered.scopeId) {
rendered.slotScopeIds = [rendered.scopeId + '-s'] rendered.slotScopeIds = [rendered.scopeId + '-s']
} }
isRenderingCompiledSlot-- isRenderingCompiledSlot--

View File

@ -226,8 +226,13 @@ export { HMRRuntime } from './hmr'
// user code should avoid relying on them. // user code should avoid relying on them.
// For compiler generated code // For compiler generated code
// should sync with '@vue/compiler-core/src/runtimeConstants.ts' // should sync with '@vue/compiler-core/src/runtimeHelpers.ts'
export { withCtx, setScopeId } from './componentRenderContext' export {
withCtx,
pushScopeId,
popScopeId,
withScopeId
} from './componentRenderContext'
export { renderList } from './helpers/renderList' export { renderList } from './helpers/renderList'
export { toHandlers } from './helpers/toHandlers' export { toHandlers } from './helpers/toHandlers'
export { renderSlot } from './helpers/renderSlot' export { renderSlot } from './helpers/renderSlot'