feat(compiler-sfc): <script setup> defineProps destructure transform (#4690)

This commit is contained in:
Evan You 2021-09-27 14:24:21 -04:00 committed by GitHub
parent d84d5ecdbd
commit 467e113b95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 717 additions and 124 deletions

View File

@ -82,6 +82,11 @@ export const enum BindingTypes {
* declared as a prop * declared as a prop
*/ */
PROPS = 'props', PROPS = 'props',
/**
* a local alias of a `<script setup>` destructured prop.
* the original is stored in __propsAliases of the bindingMetadata object.
*/
PROPS_ALIASED = 'props-aliased',
/** /**
* a let binding (may or may not be a ref) * a let binding (may or may not be a ref)
*/ */
@ -110,6 +115,7 @@ export type BindingMetadata = {
[key: string]: BindingTypes | undefined [key: string]: BindingTypes | undefined
} & { } & {
__isScriptSetup?: boolean __isScriptSetup?: boolean
__propsAliases?: Record<string, string>
} }
interface SharedTransformCodegenOptions { interface SharedTransformCodegenOptions {

View File

@ -188,11 +188,16 @@ export function processExpression(
// use __props which is generated by compileScript so in ts mode // use __props which is generated by compileScript so in ts mode
// it gets correct type // it gets correct type
return `__props.${raw}` return `__props.${raw}`
} else if (type === BindingTypes.PROPS_ALIASED) {
// prop with a different local alias (from defineProps() destructure)
return `__props.${bindingMetadata.__propsAliases![raw]}`
} }
} else { } else {
if (type && type.startsWith('setup')) { if (type && type.startsWith('setup')) {
// setup bindings in non-inline mode // setup bindings in non-inline mode
return `$setup.${raw}` return `$setup.${raw}`
} else if (type === BindingTypes.PROPS_ALIASED) {
return `$props.${bindingMetadata.__propsAliases![raw]}`
} else if (type) { } else if (type) {
return `$${type}.${raw}` return `$${type}.${raw}`
} }

View File

@ -0,0 +1,128 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sfc props transform aliasing 1`] = `
"import { toDisplayString as _toDisplayString } from \\"vue\\"
export default {
props: ['foo'],
setup(__props) {
let x = foo
let y = __props.foo
return (_ctx, _cache) => {
return _toDisplayString(__props.foo + __props.foo)
}
}
}"
`;
exports[`sfc props transform basic usage 1`] = `
"import { toDisplayString as _toDisplayString } from \\"vue\\"
export default {
props: ['foo'],
setup(__props) {
console.log(__props.foo)
return (_ctx, _cache) => {
return _toDisplayString(__props.foo)
}
}
}"
`;
exports[`sfc props transform default values w/ runtime declaration 1`] = `
"import { mergeDefaults as _mergeDefaults } from 'vue'
export default {
props: _mergeDefaults(['foo', 'bar'], {
foo: 1,
bar: () => {}
}),
setup(__props) {
return () => {}
}
}"
`;
exports[`sfc props transform default values w/ type declaration 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default /*#__PURE__*/_defineComponent({
props: {
foo: { type: Number, required: false, default: 1 },
bar: { type: Object, required: false, default: () => {} }
},
setup(__props: any) {
return () => {}
}
})"
`;
exports[`sfc props transform default values w/ type declaration, prod mode 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default /*#__PURE__*/_defineComponent({
props: {
foo: { default: 1 },
bar: { default: () => {} },
baz: null
},
setup(__props: any) {
return () => {}
}
})"
`;
exports[`sfc props transform nested scope 1`] = `
"export default {
props: ['foo', 'bar'],
setup(__props) {
function test(foo) {
console.log(foo)
console.log(__props.bar)
}
return () => {}
}
}"
`;
exports[`sfc props transform rest spread 1`] = `
"import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
export default {
props: ['foo', 'bar', 'baz'],
setup(__props) {
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"])
return () => {}
}
}"
`;

View File

@ -0,0 +1,191 @@
import { BindingTypes } from '@vue/compiler-core'
import { SFCScriptCompileOptions } from '../src'
import { compileSFCScript, assertCode } from './utils'
describe('sfc props transform', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, {
inlineTemplate: true,
propsDestructureTransform: true,
...options
})
}
test('basic usage', () => {
const { content, bindings } = compile(`
<script setup>
const { foo } = defineProps(['foo'])
console.log(foo)
</script>
<template>{{ foo }}</template>
`)
expect(content).not.toMatch(`const { foo } =`)
expect(content).toMatch(`console.log(__props.foo)`)
expect(content).toMatch(`_toDisplayString(__props.foo)`)
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS
})
})
test('nested scope', () => {
const { content, bindings } = compile(`
<script setup>
const { foo, bar } = defineProps(['foo', 'bar'])
function test(foo) {
console.log(foo)
console.log(bar)
}
</script>
`)
expect(content).not.toMatch(`const { foo, bar } =`)
expect(content).toMatch(`console.log(foo)`)
expect(content).toMatch(`console.log(__props.bar)`)
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
test: BindingTypes.SETUP_CONST
})
})
test('default values w/ runtime declaration', () => {
const { content } = compile(`
<script setup>
const { foo = 1, bar = {} } = defineProps(['foo', 'bar'])
</script>
`)
// literals can be used as-is, non-literals are always returned from a
// function
expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar'], {
foo: 1,
bar: () => {}
})`)
assertCode(content)
})
test('default values w/ type declaration', () => {
const { content } = compile(`
<script setup lang="ts">
const { foo = 1, bar = {} } = defineProps<{ foo?: number, bar?: object }>()
</script>
`)
// literals can be used as-is, non-literals are always returned from a
// function
expect(content).toMatch(`props: {
foo: { type: Number, required: false, default: 1 },
bar: { type: Object, required: false, default: () => {} }
}`)
assertCode(content)
})
test('default values w/ type declaration, prod mode', () => {
const { content } = compile(
`
<script setup lang="ts">
const { foo = 1, bar = {} } = defineProps<{ foo?: number, bar?: object, baz?: any }>()
</script>
`,
{ isProd: true }
)
// literals can be used as-is, non-literals are always returned from a
// function
expect(content).toMatch(`props: {
foo: { default: 1 },
bar: { default: () => {} },
baz: null
}`)
assertCode(content)
})
test('aliasing', () => {
const { content, bindings } = compile(`
<script setup>
const { foo: bar } = defineProps(['foo'])
let x = foo
let y = bar
</script>
<template>{{ foo + bar }}</template>
`)
expect(content).not.toMatch(`const { foo: bar } =`)
expect(content).toMatch(`let x = foo`) // should not process
expect(content).toMatch(`let y = __props.foo`)
// should convert bar to __props.foo in template expressions
expect(content).toMatch(`_toDisplayString(__props.foo + __props.foo)`)
assertCode(content)
expect(bindings).toStrictEqual({
x: BindingTypes.SETUP_LET,
y: BindingTypes.SETUP_LET,
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS_ALIASED,
__propsAliases: {
bar: 'foo'
}
})
})
test('rest spread', () => {
const { content, bindings } = compile(`
<script setup>
const { foo, bar, ...rest } = defineProps(['foo', 'bar', 'baz'])
</script>
`)
expect(content).toMatch(
`const rest = _createPropsRestProxy(__props, ["foo","bar"])`
)
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
baz: BindingTypes.PROPS,
rest: BindingTypes.SETUP_CONST
})
})
describe('errors', () => {
test('should error on deep destructure', () => {
expect(() =>
compile(
`<script setup>const { foo: [bar] } = defineProps(['foo'])</script>`
)
).toThrow(`destructure does not support nested patterns`)
expect(() =>
compile(
`<script setup>const { foo: { bar } } = defineProps(['foo'])</script>`
)
).toThrow(`destructure does not support nested patterns`)
})
test('should error on computed key', () => {
expect(() =>
compile(
`<script setup>const { [foo]: bar } = defineProps(['foo'])</script>`
)
).toThrow(`destructure cannot use computed key`)
})
test('should error when used with withDefaults', () => {
expect(() =>
compile(
`<script setup lang="ts">
const { foo } = withDefaults(defineProps<{ foo: string }>(), { foo: 'foo' })
</script>`
)
).toThrow(`withDefaults() is unnecessary when using destructure`)
})
test('should error if destructure reference local vars', () => {
expect(() =>
compile(
`<script setup>
const x = 1
const {
foo = () => x
} = defineProps(['foo'])
</script>`
)
).toThrow(`cannot reference locally declared variables`)
})
})
})

View File

@ -39,7 +39,9 @@ import {
TSInterfaceBody, TSInterfaceBody,
AwaitExpression, AwaitExpression,
Program, Program,
ObjectMethod ObjectMethod,
LVal,
Expression
} from '@babel/types' } from '@babel/types'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
import { RawSourceMap } from 'source-map' import { RawSourceMap } from 'source-map'
@ -88,9 +90,15 @@ export interface SFCScriptCompileOptions {
/** /**
* (Experimental) Enable syntax transform for using refs without `.value` * (Experimental) Enable syntax transform for using refs without `.value`
* https://github.com/vuejs/rfcs/discussions/369 * https://github.com/vuejs/rfcs/discussions/369
* @default true * @default false
*/ */
refTransform?: boolean refTransform?: boolean
/**
* (Experimental) Enable syntax transform for destructuring from defineProps()
* https://github.com/vuejs/rfcs/discussions/394
* @default false
*/
propsDestructureTransform?: boolean
/** /**
* @deprecated use `refTransform` instead. * @deprecated use `refTransform` instead.
*/ */
@ -131,6 +139,8 @@ export function compileScript(
let { script, scriptSetup, source, filename } = sfc let { script, scriptSetup, source, filename } = sfc
// feature flags // feature flags
const enableRefTransform = !!options.refSugar || !!options.refTransform const enableRefTransform = !!options.refSugar || !!options.refTransform
const enablePropsTransform = !!options.propsDestructureTransform
const isProd = !!options.isProd
const genSourceMap = options.sourceMap !== false const genSourceMap = options.sourceMap !== false
let refBindings: string[] | undefined let refBindings: string[] | undefined
@ -203,7 +213,7 @@ export function compileScript(
cssVars, cssVars,
bindings, bindings,
scopeId, scopeId,
!!options.isProd isProd
) )
content += `\nexport default __default__` content += `\nexport default __default__`
} }
@ -248,6 +258,8 @@ export function compileScript(
let hasDefineExposeCall = false let hasDefineExposeCall = false
let propsRuntimeDecl: Node | undefined let propsRuntimeDecl: Node | undefined
let propsRuntimeDefaults: ObjectExpression | undefined let propsRuntimeDefaults: ObjectExpression | undefined
let propsDestructureDecl: Node | undefined
let propsDestructureRestId: string | undefined
let propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined let propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined
let propsTypeDeclRaw: Node | undefined let propsTypeDeclRaw: Node | undefined
let propsIdentifier: string | undefined let propsIdentifier: string | undefined
@ -266,6 +278,14 @@ export function compileScript(
const typeDeclaredEmits: Set<string> = new Set() const typeDeclaredEmits: Set<string> = new Set()
// record declared types for runtime props type generation // record declared types for runtime props type generation
const declaredTypes: Record<string, string[]> = {} const declaredTypes: Record<string, string[]> = {}
// props destructure data
const propsDestructuredBindings: Record<
string, // public prop key
{
local: string // local identifier, may be different
default?: Expression
}
> = Object.create(null)
// magic-string state // magic-string state
const s = new MagicString(source) const s = new MagicString(source)
@ -337,7 +357,7 @@ export function compileScript(
} }
} }
function processDefineProps(node: Node): boolean { function processDefineProps(node: Node, declId?: LVal): boolean {
if (!isCallOf(node, DEFINE_PROPS)) { if (!isCallOf(node, DEFINE_PROPS)) {
return false return false
} }
@ -374,14 +394,62 @@ export function compileScript(
} }
} }
if (declId) {
if (enablePropsTransform && declId.type === 'ObjectPattern') {
propsDestructureDecl = declId
// props destructure - handle compilation sugar
for (const prop of declId.properties) {
if (prop.type === 'ObjectProperty') {
if (prop.computed) {
error(
`${DEFINE_PROPS}() destructure cannot use computed key.`,
prop.key
)
}
const propKey = (prop.key as Identifier).name
if (prop.value.type === 'AssignmentPattern') {
// default value { foo = 123 }
const { left, right } = prop.value
if (left.type !== 'Identifier') {
error(
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
left
)
}
// store default value
propsDestructuredBindings[propKey] = {
local: left.name,
default: right
}
} else if (prop.value.type === 'Identifier') {
// simple destucture
propsDestructuredBindings[propKey] = {
local: prop.value.name
}
} else {
error(
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
prop.value
)
}
} else {
// rest spread
propsDestructureRestId = (prop.argument as Identifier).name
}
}
} else {
propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)
}
}
return true return true
} }
function processWithDefaults(node: Node): boolean { function processWithDefaults(node: Node, declId?: LVal): boolean {
if (!isCallOf(node, WITH_DEFAULTS)) { if (!isCallOf(node, WITH_DEFAULTS)) {
return false return false
} }
if (processDefineProps(node.arguments[0])) { if (processDefineProps(node.arguments[0], declId)) {
if (propsRuntimeDecl) { if (propsRuntimeDecl) {
error( error(
`${WITH_DEFAULTS} can only be used with type-based ` + `${WITH_DEFAULTS} can only be used with type-based ` +
@ -389,6 +457,13 @@ export function compileScript(
node node
) )
} }
if (propsDestructureDecl) {
error(
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
node.callee
)
}
propsRuntimeDefaults = node.arguments[1] as ObjectExpression propsRuntimeDefaults = node.arguments[1] as ObjectExpression
if ( if (
!propsRuntimeDefaults || !propsRuntimeDefaults ||
@ -408,7 +483,7 @@ export function compileScript(
return true return true
} }
function processDefineEmits(node: Node): boolean { function processDefineEmits(node: Node, declId?: LVal): boolean {
if (!isCallOf(node, DEFINE_EMITS)) { if (!isCallOf(node, DEFINE_EMITS)) {
return false return false
} }
@ -440,6 +515,11 @@ export function compileScript(
) )
} }
} }
if (declId) {
emitIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)
}
return true return true
} }
@ -565,7 +645,7 @@ export function compileScript(
* static properties, we can directly generate more optimzied default * static properties, we can directly generate more optimzied default
* declarations. Otherwise we will have to fallback to runtime merging. * declarations. Otherwise we will have to fallback to runtime merging.
*/ */
function checkStaticDefaults() { function hasStaticWithDefaults() {
return ( return (
propsRuntimeDefaults && propsRuntimeDefaults &&
propsRuntimeDefaults.type === 'ObjectExpression' && propsRuntimeDefaults.type === 'ObjectExpression' &&
@ -582,13 +662,16 @@ export function compileScript(
if (!keys.length) { if (!keys.length) {
return `` return ``
} }
const hasStaticDefaults = checkStaticDefaults() const hasStaticDefaults = hasStaticWithDefaults()
const scriptSetupSource = scriptSetup!.content const scriptSetupSource = scriptSetup!.content
let propsDecls = `{ let propsDecls = `{
${keys ${keys
.map(key => { .map(key => {
let defaultString: string | undefined let defaultString: string | undefined
if (hasStaticDefaults) { const destructured = genDestructuredDefaultValue(key)
if (destructured) {
defaultString = `default: ${destructured}`
} else if (hasStaticDefaults) {
const prop = propsRuntimeDefaults!.properties.find( const prop = propsRuntimeDefaults!.properties.find(
(node: any) => node.key.name === key (node: any) => node.key.name === key
) as ObjectProperty | ObjectMethod ) as ObjectProperty | ObjectMethod
@ -608,7 +691,7 @@ export function compileScript(
} }
} }
if (__DEV__) { if (!isProd) {
const { type, required } = props[key] const { type, required } = props[key]
return `${key}: { type: ${toRuntimeTypeString( return `${key}: { type: ${toRuntimeTypeString(
type type
@ -632,9 +715,21 @@ export function compileScript(
return `\n props: ${propsDecls},` return `\n props: ${propsDecls},`
} }
function genDestructuredDefaultValue(key: string): string | undefined {
const destructured = propsDestructuredBindings[key]
if (destructured && destructured.default) {
const value = scriptSetup!.content.slice(
destructured.default.start!,
destructured.default.end!
)
const isLiteral = destructured.default.type.endsWith('Literal')
return isLiteral ? value : `() => ${value}`
}
}
function genSetupPropsType(node: TSTypeLiteral | TSInterfaceBody) { function genSetupPropsType(node: TSTypeLiteral | TSInterfaceBody) {
const scriptSetupSource = scriptSetup!.content const scriptSetupSource = scriptSetup!.content
if (checkStaticDefaults()) { if (hasStaticWithDefaults()) {
// if withDefaults() is used, we need to remove the optional flags // if withDefaults() is used, we need to remove the optional flags
// on props that have default values // on props that have default values
let res = `{ ` let res = `{ `
@ -754,7 +849,7 @@ export function compileScript(
// apply ref transform // apply ref transform
if (enableRefTransform && shouldTransformRef(script.content)) { if (enableRefTransform && shouldTransformRef(script.content)) {
const { rootVars, importedHelpers } = transformRefAST( const { rootRefs: rootVars, importedHelpers } = transformRefAST(
scriptAst, scriptAst,
s, s,
scriptStartOffset! scriptStartOffset!
@ -900,20 +995,9 @@ export function compileScript(
if (decl.init) { if (decl.init) {
// defineProps / defineEmits // defineProps / defineEmits
const isDefineProps = const isDefineProps =
processDefineProps(decl.init) || processWithDefaults(decl.init) processDefineProps(decl.init, decl.id) ||
if (isDefineProps) { processWithDefaults(decl.init, decl.id)
propsIdentifier = scriptSetup.content.slice( const isDefineEmits = processDefineEmits(decl.init, decl.id)
decl.id.start!,
decl.id.end!
)
}
const isDefineEmits = processDefineEmits(decl.init)
if (isDefineEmits) {
emitIdentifier = scriptSetup.content.slice(
decl.id.start!,
decl.id.end!
)
}
if (isDefineProps || isDefineEmits) { if (isDefineProps || isDefineEmits) {
if (left === 1) { if (left === 1) {
s.remove(node.start! + startOffset, node.end! + startOffset) s.remove(node.start! + startOffset, node.end! + startOffset)
@ -1004,14 +1088,19 @@ export function compileScript(
} }
// 3. Apply ref sugar transform // 3. Apply ref sugar transform
if (enableRefTransform && shouldTransformRef(scriptSetup.content)) { if (
const { rootVars, importedHelpers } = transformRefAST( (enableRefTransform && shouldTransformRef(scriptSetup.content)) ||
propsDestructureDecl
) {
const { rootRefs, importedHelpers } = transformRefAST(
scriptSetupAst, scriptSetupAst,
s, s,
startOffset, startOffset,
refBindings refBindings,
propsDestructuredBindings,
!enableRefTransform
) )
refBindings = refBindings ? [...refBindings, ...rootVars] : rootVars refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs
for (const h of importedHelpers) { for (const h of importedHelpers) {
helperImports.add(h) helperImports.add(h)
} }
@ -1019,7 +1108,7 @@ export function compileScript(
// 4. extract runtime props/emits code from setup context type // 4. extract runtime props/emits code from setup context type
if (propsTypeDecl) { if (propsTypeDecl) {
extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes) extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes, isProd)
} }
if (emitsTypeDecl) { if (emitsTypeDecl) {
extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits) extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits)
@ -1029,6 +1118,7 @@ export function compileScript(
// variables // variables
checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS) checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)
checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS) checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_PROPS) checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_PROPS)
// 6. remove non-script content // 6. remove non-script content
@ -1062,6 +1152,20 @@ export function compileScript(
for (const key in typeDeclaredProps) { for (const key in typeDeclaredProps) {
bindingMetadata[key] = BindingTypes.PROPS bindingMetadata[key] = BindingTypes.PROPS
} }
// props aliases
if (propsDestructureDecl) {
if (propsDestructureRestId) {
bindingMetadata[propsDestructureRestId] = BindingTypes.SETUP_CONST
}
for (const key in propsDestructuredBindings) {
const { local } = propsDestructuredBindings[key]
if (local !== key) {
bindingMetadata[local] = BindingTypes.PROPS_ALIASED
;(bindingMetadata.__propsAliases ||
(bindingMetadata.__propsAliases = {}))[local] = key
}
}
}
for (const [key, { isType, imported, source }] of Object.entries( for (const [key, { isType, imported, source }] of Object.entries(
userImports userImports
)) { )) {
@ -1090,12 +1194,7 @@ export function compileScript(
helperImports.add('unref') helperImports.add('unref')
s.prependRight( s.prependRight(
startOffset, startOffset,
`\n${genCssVarsCode( `\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`
cssVars,
bindingMetadata,
scopeId,
!!options.isProd
)}\n`
) )
} }
@ -1118,6 +1217,14 @@ export function compileScript(
}` }`
) )
} }
if (propsDestructureRestId) {
s.prependRight(
startOffset,
`\nconst ${propsDestructureRestId} = ${helper(
`createPropsRestProxy`
)}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))})`
)
}
// inject temp variables for async context preservation // inject temp variables for async context preservation
if (hasAwait) { if (hasAwait) {
const any = isTS ? `: any` : `` const any = isTS ? `: any` : ``
@ -1235,9 +1342,22 @@ export function compileScript(
runtimeOptions += `\n __ssrInlineRender: true,` runtimeOptions += `\n __ssrInlineRender: true,`
} }
if (propsRuntimeDecl) { if (propsRuntimeDecl) {
runtimeOptions += `\n props: ${scriptSetup.content let declCode = scriptSetup.content
.slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!) .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)
.trim()},` .trim()
if (propsDestructureDecl) {
const defaults: string[] = []
for (const key in propsDestructuredBindings) {
const d = genDestructuredDefaultValue(key)
if (d) defaults.push(`${key}: ${d}`)
}
if (defaults.length) {
declCode = `${helper(
`mergeDefaults`
)}(${declCode}, {\n ${defaults.join(',\n ')}\n})`
}
}
runtimeOptions += `\n props: ${declCode},`
} else if (propsTypeDecl) { } else if (propsTypeDecl) {
runtimeOptions += genRuntimeProps(typeDeclaredProps) runtimeOptions += genRuntimeProps(typeDeclaredProps)
} }
@ -1313,6 +1433,7 @@ export function compileScript(
} }
s.trim() s.trim()
return { return {
...scriptSetup, ...scriptSetup,
bindings: bindingMetadata, bindings: bindingMetadata,
@ -1376,12 +1497,18 @@ function walkDeclaration(
bindingType = BindingTypes.SETUP_LET bindingType = BindingTypes.SETUP_LET
} }
registerBinding(bindings, id, bindingType) registerBinding(bindings, id, bindingType)
} else if (id.type === 'ObjectPattern') { } else {
if (isCallOf(init, DEFINE_PROPS)) {
// skip walking props destructure
return
}
if (id.type === 'ObjectPattern') {
walkObjectPattern(id, bindings, isConst, isDefineCall) walkObjectPattern(id, bindings, isConst, isDefineCall)
} else if (id.type === 'ArrayPattern') { } else if (id.type === 'ArrayPattern') {
walkArrayPattern(id, bindings, isConst, isDefineCall) walkArrayPattern(id, bindings, isConst, isDefineCall)
} }
} }
}
} else if ( } else if (
node.type === 'FunctionDeclaration' || node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration' node.type === 'ClassDeclaration'
@ -1488,7 +1615,8 @@ function recordType(node: Node, declaredTypes: Record<string, string[]>) {
function extractRuntimeProps( function extractRuntimeProps(
node: TSTypeLiteral | TSInterfaceBody, node: TSTypeLiteral | TSInterfaceBody,
props: Record<string, PropTypeData>, props: Record<string, PropTypeData>,
declaredTypes: Record<string, string[]> declaredTypes: Record<string, string[]>,
isProd: boolean
) { ) {
const members = node.type === 'TSTypeLiteral' ? node.members : node.body const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const m of members) { for (const m of members) {
@ -1497,7 +1625,7 @@ function extractRuntimeProps(
m.key.type === 'Identifier' m.key.type === 'Identifier'
) { ) {
let type let type
if (__DEV__) { if (!isProd) {
if (m.type === 'TSMethodSignature') { if (m.type === 'TSMethodSignature') {
type = ['Function'] type = ['Function']
} else if (m.typeAnnotation) { } else if (m.typeAnnotation) {

View File

@ -64,7 +64,9 @@ const {
// @babel/parser plugins to enable. // @babel/parser plugins to enable.
// 'typescript' and 'jsx' will be auto-inferred from filename if provided, // 'typescript' and 'jsx' will be auto-inferred from filename if provided,
// so in most cases explicit parserPlugins are not necessary // so in most cases explicit parserPlugins are not necessary
parserPlugins: [/* ... */] parserPlugins: [
/* ... */
]
}) })
``` ```
@ -93,7 +95,7 @@ const ast = parse(src, { sourceType: 'module' })
const s = new MagicString(src) const s = new MagicString(src)
const { const {
rootVars, // ['a'] rootRefs, // ['a']
importedHelpers // ['ref'] importedHelpers // ['ref']
} = transformAST(ast, s) } = transformAST(ast, s)

View File

@ -17,7 +17,7 @@ function assertCode(code: string) {
} }
test('$ unwrapping', () => { test('$ unwrapping', () => {
const { code, rootVars } = transform(` const { code, rootRefs } = transform(`
import { ref, shallowRef } from 'vue' import { ref, shallowRef } from 'vue'
let foo = $(ref()) let foo = $(ref())
let a = $(ref(1)) let a = $(ref(1))
@ -40,12 +40,12 @@ test('$ unwrapping', () => {
// normal declarations left untouched // normal declarations left untouched
expect(code).toMatch(`let c = () => {}`) expect(code).toMatch(`let c = () => {}`)
expect(code).toMatch(`let d`) expect(code).toMatch(`let d`)
expect(rootVars).toStrictEqual(['foo', 'a', 'b']) expect(rootRefs).toStrictEqual(['foo', 'a', 'b'])
assertCode(code) assertCode(code)
}) })
test('$ref & $shallowRef declarations', () => { test('$ref & $shallowRef declarations', () => {
const { code, rootVars, importedHelpers } = transform(` const { code, rootRefs, importedHelpers } = transform(`
let foo = $ref() let foo = $ref()
let a = $ref(1) let a = $ref(1)
let b = $shallowRef({ let b = $shallowRef({
@ -70,13 +70,13 @@ test('$ref & $shallowRef declarations', () => {
// normal declarations left untouched // normal declarations left untouched
expect(code).toMatch(`let c = () => {}`) expect(code).toMatch(`let c = () => {}`)
expect(code).toMatch(`let d`) expect(code).toMatch(`let d`)
expect(rootVars).toStrictEqual(['foo', 'a', 'b']) expect(rootRefs).toStrictEqual(['foo', 'a', 'b'])
expect(importedHelpers).toStrictEqual(['ref', 'shallowRef']) expect(importedHelpers).toStrictEqual(['ref', 'shallowRef'])
assertCode(code) assertCode(code)
}) })
test('multi $ref declarations', () => { test('multi $ref declarations', () => {
const { code, rootVars, importedHelpers } = transform(` const { code, rootRefs, importedHelpers } = transform(`
let a = $ref(1), b = $ref(2), c = $ref({ let a = $ref(1), b = $ref(2), c = $ref({
count: 0 count: 0
}) })
@ -86,31 +86,31 @@ test('multi $ref declarations', () => {
count: 0 count: 0
}) })
`) `)
expect(rootVars).toStrictEqual(['a', 'b', 'c']) expect(rootRefs).toStrictEqual(['a', 'b', 'c'])
expect(importedHelpers).toStrictEqual(['ref']) expect(importedHelpers).toStrictEqual(['ref'])
assertCode(code) assertCode(code)
}) })
test('$computed declaration', () => { test('$computed declaration', () => {
const { code, rootVars, importedHelpers } = transform(` const { code, rootRefs, importedHelpers } = transform(`
let a = $computed(() => 1) let a = $computed(() => 1)
`) `)
expect(code).toMatch(` expect(code).toMatch(`
let a = _computed(() => 1) let a = _computed(() => 1)
`) `)
expect(rootVars).toStrictEqual(['a']) expect(rootRefs).toStrictEqual(['a'])
expect(importedHelpers).toStrictEqual(['computed']) expect(importedHelpers).toStrictEqual(['computed'])
assertCode(code) assertCode(code)
}) })
test('mixing $ref & $computed declarations', () => { test('mixing $ref & $computed declarations', () => {
const { code, rootVars, importedHelpers } = transform(` const { code, rootRefs, importedHelpers } = transform(`
let a = $ref(1), b = $computed(() => a + 1) let a = $ref(1), b = $computed(() => a + 1)
`) `)
expect(code).toMatch(` expect(code).toMatch(`
let a = _ref(1), b = _computed(() => a.value + 1) let a = _ref(1), b = _computed(() => a.value + 1)
`) `)
expect(rootVars).toStrictEqual(['a', 'b']) expect(rootRefs).toStrictEqual(['a', 'b'])
expect(importedHelpers).toStrictEqual(['ref', 'computed']) expect(importedHelpers).toStrictEqual(['ref', 'computed'])
assertCode(code) assertCode(code)
}) })
@ -201,7 +201,7 @@ test('should not rewrite scope variable', () => {
}) })
test('object destructure', () => { test('object destructure', () => {
const { code, rootVars } = transform(` const { code, rootRefs } = transform(`
let n = $ref(1), { a, b: c, d = 1, e: f = 2, ...g } = $(useFoo()) let n = $ref(1), { a, b: c, d = 1, e: f = 2, ...g } = $(useFoo())
let { foo } = $(useSomthing(() => 1)); let { foo } = $(useSomthing(() => 1));
console.log(n, a, c, d, f, g, foo) console.log(n, a, c, d, f, g, foo)
@ -221,12 +221,12 @@ test('object destructure', () => {
expect(code).toMatch( expect(code).toMatch(
`console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)` `console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)`
) )
expect(rootVars).toStrictEqual(['n', 'a', 'c', 'd', 'f', 'g', 'foo']) expect(rootRefs).toStrictEqual(['n', 'a', 'c', 'd', 'f', 'g', 'foo'])
assertCode(code) assertCode(code)
}) })
test('array destructure', () => { test('array destructure', () => {
const { code, rootVars } = transform(` const { code, rootRefs } = transform(`
let n = $ref(1), [a, b = 1, ...c] = $(useFoo()) let n = $ref(1), [a, b = 1, ...c] = $(useFoo())
console.log(n, a, b, c) console.log(n, a, b, c)
`) `)
@ -235,12 +235,12 @@ test('array destructure', () => {
expect(code).toMatch(`\nconst b = _shallowRef(__b);`) expect(code).toMatch(`\nconst b = _shallowRef(__b);`)
expect(code).toMatch(`\nconst c = _shallowRef(__c);`) expect(code).toMatch(`\nconst c = _shallowRef(__c);`)
expect(code).toMatch(`console.log(n.value, a.value, b.value, c.value)`) expect(code).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
expect(rootVars).toStrictEqual(['n', 'a', 'b', 'c']) expect(rootRefs).toStrictEqual(['n', 'a', 'b', 'c'])
assertCode(code) assertCode(code)
}) })
test('nested destructure', () => { test('nested destructure', () => {
const { code, rootVars } = transform(` const { code, rootRefs } = transform(`
let [{ a: { b }}] = $(useFoo()) let [{ a: { b }}] = $(useFoo())
let { c: [d, e] } = $(useBar()) let { c: [d, e] } = $(useBar())
console.log(b, d, e) console.log(b, d, e)
@ -252,7 +252,7 @@ test('nested destructure', () => {
expect(code).toMatch(`\nconst b = _shallowRef(__b);`) expect(code).toMatch(`\nconst b = _shallowRef(__b);`)
expect(code).toMatch(`\nconst d = _shallowRef(__d);`) expect(code).toMatch(`\nconst d = _shallowRef(__d);`)
expect(code).toMatch(`\nconst e = _shallowRef(__e);`) expect(code).toMatch(`\nconst e = _shallowRef(__e);`)
expect(rootVars).toStrictEqual(['b', 'd', 'e']) expect(rootRefs).toStrictEqual(['b', 'd', 'e'])
assertCode(code) assertCode(code)
}) })
@ -270,7 +270,7 @@ test('$$', () => {
}) })
test('nested scopes', () => { test('nested scopes', () => {
const { code, rootVars } = transform(` const { code, rootRefs } = transform(`
let a = $ref(0) let a = $ref(0)
let b = $ref(0) let b = $ref(0)
let c = 0 let c = 0
@ -303,7 +303,7 @@ test('nested scopes', () => {
return $$({ a, b, c, d }) return $$({ a, b, c, d })
} }
`) `)
expect(rootVars).toStrictEqual(['a', 'b', 'bar']) expect(rootRefs).toStrictEqual(['a', 'b', 'bar'])
expect(code).toMatch('a.value++ // outer a') expect(code).toMatch('a.value++ // outer a')
expect(code).toMatch('b.value++ // outer b') expect(code).toMatch('b.value++ // outer b')

View File

@ -31,7 +31,7 @@ export function shouldTransform(src: string): boolean {
return transformCheckRE.test(src) return transformCheckRE.test(src)
} }
type Scope = Record<string, boolean> type Scope = Record<string, boolean | 'prop'>
export interface RefTransformOptions { export interface RefTransformOptions {
filename?: string filename?: string
@ -43,7 +43,7 @@ export interface RefTransformOptions {
export interface RefTransformResults { export interface RefTransformResults {
code: string code: string
map: SourceMap | null map: SourceMap | null
rootVars: string[] rootRefs: string[]
importedHelpers: string[] importedHelpers: string[]
} }
@ -99,13 +99,23 @@ export function transformAST(
ast: Program, ast: Program,
s: MagicString, s: MagicString,
offset = 0, offset = 0,
knownRootVars?: string[] knownRefs?: string[],
knownProps?: Record<
string, // public prop key
{
local: string // local identifier, may be different
default?: any
}
>,
rewritePropsOnly = false
): { ): {
rootVars: string[] rootRefs: string[]
importedHelpers: string[] importedHelpers: string[]
} { } {
// TODO remove when out of experimental // TODO remove when out of experimental
if (!rewritePropsOnly) {
warnExperimental() warnExperimental()
}
const importedHelpers = new Set<string>() const importedHelpers = new Set<string>()
const rootScope: Scope = {} const rootScope: Scope = {}
@ -113,14 +123,23 @@ export function transformAST(
let currentScope: Scope = rootScope let currentScope: Scope = rootScope
const excludedIds = new WeakSet<Identifier>() const excludedIds = new WeakSet<Identifier>()
const parentStack: Node[] = [] const parentStack: Node[] = []
const propsLocalToPublicMap = Object.create(null)
if (knownRootVars) { if (knownRefs) {
for (const key of knownRootVars) { for (const key of knownRefs) {
rootScope[key] = true rootScope[key] = true
} }
} }
if (knownProps) {
for (const key in knownProps) {
const { local } = knownProps[key]
rootScope[local] = 'prop'
propsLocalToPublicMap[local] = key
}
}
function error(msg: string, node: Node) { function error(msg: string, node: Node) {
if (rewritePropsOnly) return
const e = new Error(msg) const e = new Error(msg)
;(e as any).node = node ;(e as any).node = node
throw e throw e
@ -145,17 +164,19 @@ export function transformAST(
const registerRefBinding = (id: Identifier) => registerBinding(id, true) const registerRefBinding = (id: Identifier) => registerBinding(id, true)
function walkScope(node: Program | BlockStatement) { function walkScope(node: Program | BlockStatement, isRoot = false) {
for (const stmt of node.body) { for (const stmt of node.body) {
if (stmt.type === 'VariableDeclaration') { if (stmt.type === 'VariableDeclaration') {
if (stmt.declare) continue if (stmt.declare) continue
for (const decl of stmt.declarations) { for (const decl of stmt.declarations) {
let toVarCall let toVarCall
if ( const isCall =
decl.init && decl.init &&
decl.init.type === 'CallExpression' && decl.init.type === 'CallExpression' &&
decl.init.callee.type === 'Identifier' && decl.init.callee.type === 'Identifier'
(toVarCall = isToVarCall(decl.init.callee.name)) if (
isCall &&
(toVarCall = isToVarCall((decl as any).init.callee.name))
) { ) {
processRefDeclaration( processRefDeclaration(
toVarCall, toVarCall,
@ -164,11 +185,21 @@ export function transformAST(
stmt stmt
) )
} else { } else {
const isProps =
isRoot &&
isCall &&
(decl as any).init.callee.name === 'defineProps'
for (const id of extractIdentifiers(decl.id)) { for (const id of extractIdentifiers(decl.id)) {
if (isProps) {
// for defineProps destructure, only exclude them since they
// are already passed in as knownProps
excludedIds.add(id)
} else {
registerBinding(id) registerBinding(id)
} }
} }
} }
}
} else if ( } else if (
stmt.type === 'FunctionDeclaration' || stmt.type === 'FunctionDeclaration' ||
stmt.type === 'ClassDeclaration' stmt.type === 'ClassDeclaration'
@ -303,35 +334,57 @@ export function transformAST(
} }
} }
function checkRefId( function rewriteId(
scope: Scope, scope: Scope,
id: Identifier, id: Identifier,
parent: Node, parent: Node,
parentStack: Node[] parentStack: Node[]
): boolean { ): boolean {
if (hasOwn(scope, id.name)) { if (hasOwn(scope, id.name)) {
if (scope[id.name]) { const bindingType = scope[id.name]
if (bindingType) {
const isProp = bindingType === 'prop'
if (rewritePropsOnly && !isProp) {
return true
}
// ref
if (isStaticProperty(parent) && parent.shorthand) { if (isStaticProperty(parent) && parent.shorthand) {
// let binding used in a property shorthand // let binding used in a property shorthand
// { foo } -> { foo: foo.value } // { foo } -> { foo: foo.value }
// { prop } -> { prop: __prop.prop }
// skip for destructure patterns // skip for destructure patterns
if ( if (
!(parent as any).inPattern || !(parent as any).inPattern ||
isInDestructureAssignment(parent, parentStack) isInDestructureAssignment(parent, parentStack)
) { ) {
if (isProp) {
s.appendLeft(
id.end! + offset,
`: __props.${propsLocalToPublicMap[id.name]}`
)
} else {
s.appendLeft(id.end! + offset, `: ${id.name}.value`) s.appendLeft(id.end! + offset, `: ${id.name}.value`)
} }
}
} else {
if (isProp) {
s.overwrite(
id.start! + offset,
id.end! + offset,
`__props.${propsLocalToPublicMap[id.name]}`
)
} else { } else {
s.appendLeft(id.end! + offset, '.value') s.appendLeft(id.end! + offset, '.value')
} }
} }
}
return true return true
} }
return false return false
} }
// check root scope first // check root scope first
walkScope(ast) walkScope(ast, true)
;(walk as any)(ast, { ;(walk as any)(ast, {
enter(node: Node, parent?: Node) { enter(node: Node, parent?: Node) {
parent && parentStack.push(parent) parent && parentStack.push(parent)
@ -371,7 +424,7 @@ export function transformAST(
// walk up the scope chain to check if id should be appended .value // walk up the scope chain to check if id should be appended .value
let i = scopeStack.length let i = scopeStack.length
while (i--) { while (i--) {
if (checkRefId(scopeStack[i], node, parent!, parentStack)) { if (rewriteId(scopeStack[i], node, parent!, parentStack)) {
return return
} }
} }
@ -424,7 +477,7 @@ export function transformAST(
}) })
return { return {
rootVars: Object.keys(rootScope).filter(key => rootScope[key]), rootRefs: Object.keys(rootScope).filter(key => rootScope[key] === true),
importedHelpers: [...importedHelpers] importedHelpers: [...importedHelpers]
} }
} }

View File

@ -11,7 +11,8 @@ import {
SetupContext, SetupContext,
Suspense, Suspense,
computed, computed,
ComputedRef ComputedRef,
shallowReactive
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { import {
defineEmits, defineEmits,
@ -21,7 +22,8 @@ import {
useAttrs, useAttrs,
useSlots, useSlots,
mergeDefaults, mergeDefaults,
withAsyncContext withAsyncContext,
createPropsRestProxy
} from '../src/apiSetupHelpers' } from '../src/apiSetupHelpers'
describe('SFC <script setup> helpers', () => { describe('SFC <script setup> helpers', () => {
@ -77,27 +79,63 @@ describe('SFC <script setup> helpers', () => {
expect(attrs).toBe(ctx!.attrs) expect(attrs).toBe(ctx!.attrs)
}) })
test('mergeDefaults', () => { describe('mergeDefaults', () => {
test('object syntax', () => {
const merged = mergeDefaults( const merged = mergeDefaults(
{ {
foo: null, foo: null,
bar: { type: String, required: false } bar: { type: String, required: false },
baz: String
}, },
{ {
foo: 1, foo: 1,
bar: 'baz' bar: 'baz',
baz: 'qux'
} }
) )
expect(merged).toMatchObject({ expect(merged).toMatchObject({
foo: { default: 1 }, foo: { default: 1 },
bar: { type: String, required: false, default: 'baz' } bar: { type: String, required: false, default: 'baz' },
baz: { type: String, default: 'qux' }
})
}) })
test('array syntax', () => {
const merged = mergeDefaults(['foo', 'bar', 'baz'], {
foo: 1,
bar: 'baz',
baz: 'qux'
})
expect(merged).toMatchObject({
foo: { default: 1 },
bar: { default: 'baz' },
baz: { default: 'qux' }
})
})
test('should warn missing', () => {
mergeDefaults({}, { foo: 1 }) mergeDefaults({}, { foo: 1 })
expect( expect(
`props default key "foo" has no corresponding declaration` `props default key "foo" has no corresponding declaration`
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
})
describe('createPropsRestProxy', () => {
const original = shallowReactive({
foo: 1,
bar: 2,
baz: 3
})
const rest = createPropsRestProxy(original, ['foo', 'bar'])
expect('foo' in rest).toBe(false)
expect('bar' in rest).toBe(false)
expect(rest.baz).toBe(3)
expect(Object.keys(rest)).toEqual(['baz'])
original.baz = 4
expect(rest.baz).toBe(4)
})
describe('withAsyncContext', () => { describe('withAsyncContext', () => {
// disable options API because applyOptions() also resets currentInstance // disable options API because applyOptions() also resets currentInstance

View File

@ -1,4 +1,5 @@
import { isPromise } from '../../shared/src' import { ComponentPropsOptions } from '@vue/runtime-core'
import { isArray, isPromise, isFunction } from '@vue/shared'
import { import {
getCurrentInstance, getCurrentInstance,
setCurrentInstance, setCurrentInstance,
@ -7,11 +8,7 @@ import {
unsetCurrentInstance unsetCurrentInstance
} from './component' } from './component'
import { EmitFn, EmitsOptions } from './componentEmits' import { EmitFn, EmitsOptions } from './componentEmits'
import { import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
ComponentObjectPropsOptions,
PropOptions,
ExtractPropTypes
} from './componentProps'
import { warn } from './warning' import { warn } from './warning'
// dev only // dev only
@ -195,15 +192,24 @@ function getContext(): SetupContext {
* @internal * @internal
*/ */
export function mergeDefaults( export function mergeDefaults(
// the base props is compiler-generated and guaranteed to be in this shape. raw: ComponentPropsOptions,
props: Record<string, PropOptions | null>,
defaults: Record<string, any> defaults: Record<string, any>
) { ): ComponentObjectPropsOptions {
const props = isArray(raw)
? raw.reduce(
(normalized, p) => ((normalized[p] = {}), normalized),
{} as ComponentObjectPropsOptions
)
: raw
for (const key in defaults) { for (const key in defaults) {
const val = props[key] const opt = props[key]
if (val) { if (opt) {
val.default = defaults[key] if (isArray(opt) || isFunction(opt)) {
} else if (val === null) { props[key] = { type: opt, default: defaults[key] }
} else {
opt.default = defaults[key]
}
} else if (opt === null) {
props[key] = { default: defaults[key] } props[key] = { default: defaults[key] }
} else if (__DEV__) { } else if (__DEV__) {
warn(`props default key "${key}" has no corresponding declaration.`) warn(`props default key "${key}" has no corresponding declaration.`)
@ -212,6 +218,27 @@ export function mergeDefaults(
return props return props
} }
/**
* Used to create a proxy for the rest element when destructuring props with
* defineProps().
* @internal
*/
export function createPropsRestProxy(
props: any,
excludedKeys: string[]
): Record<string, any> {
const ret: Record<string, any> = {}
for (const key in props) {
if (!excludedKeys.includes(key)) {
Object.defineProperty(ret, key, {
enumerable: true,
get: () => props[key]
})
}
}
return ret
}
/** /**
* `<script setup>` helper for persisting the current instance context over * `<script setup>` helper for persisting the current instance context over
* async/await flows. * async/await flows.

View File

@ -70,6 +70,7 @@ export {
withDefaults, withDefaults,
// internal // internal
mergeDefaults, mergeDefaults,
createPropsRestProxy,
withAsyncContext withAsyncContext
} from './apiSetupHelpers' } from './apiSetupHelpers'

View File

@ -12,7 +12,7 @@
"vite": "^2.5.10" "vite": "^2.5.10"
}, },
"dependencies": { "dependencies": {
"@vue/repl": "^0.4.1", "@vue/repl": "^0.4.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"jszip": "^3.6.0" "jszip": "^3.6.0"
} }

View File

@ -16,13 +16,27 @@ const store = new ReplStore({
: `${location.origin}/src/vue-dev-proxy` : `${location.origin}/src/vue-dev-proxy`
}) })
// enable experimental features
const sfcOptions = {
script: {
refTransform: true,
propsDestructureTransform: true
}
}
// persist state // persist state
watchEffect(() => history.replaceState({}, '', store.serialize())) watchEffect(() => history.replaceState({}, '', store.serialize()))
</script> </script>
<template> <template>
<Header :store="store" /> <Header :store="store" />
<Repl :store="store" :showCompileOutput="true" :autoResize="true" /> <Repl
:store="store"
:showCompileOutput="true"
:autoResize="true"
:sfcOptions="sfcOptions"
:clearConsole="false"
/>
</template> </template>
<style> <style>

View File

@ -920,10 +920,10 @@
resolved "https://registry.yarnpkg.com/@vue/consolidate/-/consolidate-0.17.3.tgz#9614d25a2eb263fa5df18ce98b0a576142e0ec83" resolved "https://registry.yarnpkg.com/@vue/consolidate/-/consolidate-0.17.3.tgz#9614d25a2eb263fa5df18ce98b0a576142e0ec83"
integrity sha512-nl0SWcTMzaaTnJ5G6V8VlMDA1CVVrNnaQKF1aBZU3kXtjgU9jtHMsEAsgjoRUx+T0EVJk9TgbmxGhK3pOk22zw== integrity sha512-nl0SWcTMzaaTnJ5G6V8VlMDA1CVVrNnaQKF1aBZU3kXtjgU9jtHMsEAsgjoRUx+T0EVJk9TgbmxGhK3pOk22zw==
"@vue/repl@^0.4.1": "@vue/repl@^0.4.2":
version "0.4.1" version "0.4.2"
resolved "https://registry.yarnpkg.com/@vue/repl/-/repl-0.4.1.tgz#b2062bea2baa077520eb01b87df51fff357961be" resolved "https://registry.yarnpkg.com/@vue/repl/-/repl-0.4.2.tgz#594d36061201195222bc91d187cd766d692f2046"
integrity sha512-Rq9q0MHRA0YRGBE2VSFL5ZkllqLK5HnFK9/+6Iu75M39BZAacwf808fHPTN0bhYGkJ63ur2i0sEYnnNzWzluPg== integrity sha512-Spg+M7dENa0jfjEhb2odLP5uqy28fBAMmURL+vFQUIkxcvtFaMkKY3viklhFqacfQqD6YTZfnLWSXyB+F6r/eQ==
"@zeit/schemas@2.6.0": "@zeit/schemas@2.6.0":
version "2.6.0" version "2.6.0"