Merge branch 'script-setup-2'
This commit is contained in:
commit
e521de1663
@ -60,12 +60,13 @@ type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
|
|||||||
|
|
||||||
export interface CodegenResult {
|
export interface CodegenResult {
|
||||||
code: string
|
code: string
|
||||||
|
preamble: string
|
||||||
ast: RootNode
|
ast: RootNode
|
||||||
map?: RawSourceMap
|
map?: RawSourceMap
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CodegenContext
|
export interface CodegenContext
|
||||||
extends Omit<Required<CodegenOptions>, 'bindingMetadata'> {
|
extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
|
||||||
source: string
|
source: string
|
||||||
code: string
|
code: string
|
||||||
line: number
|
line: number
|
||||||
@ -199,12 +200,18 @@ export function generate(
|
|||||||
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 genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
|
||||||
|
const isSetupInlined = !!options.inline
|
||||||
|
|
||||||
// preambles
|
// preambles
|
||||||
|
// in setup() inline mode, the preamble is generated in a sub context
|
||||||
|
// and returned separately.
|
||||||
|
const preambleContext = isSetupInlined
|
||||||
|
? createCodegenContext(ast, options)
|
||||||
|
: context
|
||||||
if (!__BROWSER__ && mode === 'module') {
|
if (!__BROWSER__ && mode === 'module') {
|
||||||
genModulePreamble(ast, context, genScopeId)
|
genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
|
||||||
} else {
|
} else {
|
||||||
genFunctionPreamble(ast, context)
|
genFunctionPreamble(ast, preambleContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// binding optimizations
|
// binding optimizations
|
||||||
@ -213,10 +220,17 @@ export function generate(
|
|||||||
: ``
|
: ``
|
||||||
// enter render function
|
// enter render function
|
||||||
if (!ssr) {
|
if (!ssr) {
|
||||||
if (genScopeId) {
|
if (isSetupInlined) {
|
||||||
push(`const render = ${PURE_ANNOTATION}_withId(`)
|
if (genScopeId) {
|
||||||
|
push(`${PURE_ANNOTATION}_withId(`)
|
||||||
|
}
|
||||||
|
push(`(_ctx, _cache${optimizeSources}) => {`)
|
||||||
|
} else {
|
||||||
|
if (genScopeId) {
|
||||||
|
push(`const render = ${PURE_ANNOTATION}_withId(`)
|
||||||
|
}
|
||||||
|
push(`function render(_ctx, _cache${optimizeSources}) {`)
|
||||||
}
|
}
|
||||||
push(`function render(_ctx, _cache${optimizeSources}) {`)
|
|
||||||
} else {
|
} else {
|
||||||
if (genScopeId) {
|
if (genScopeId) {
|
||||||
push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
|
push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
|
||||||
@ -290,6 +304,7 @@ export function generate(
|
|||||||
return {
|
return {
|
||||||
ast,
|
ast,
|
||||||
code: context.code,
|
code: context.code,
|
||||||
|
preamble: isSetupInlined ? preambleContext.code : ``,
|
||||||
// SourceMapGenerator does have toJSON() method but it's not in the types
|
// SourceMapGenerator does have toJSON() method but it's not in the types
|
||||||
map: context.map ? (context.map as any).toJSON() : undefined
|
map: context.map ? (context.map as any).toJSON() : undefined
|
||||||
}
|
}
|
||||||
@ -356,7 +371,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
|||||||
function genModulePreamble(
|
function genModulePreamble(
|
||||||
ast: RootNode,
|
ast: RootNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
genScopeId: boolean
|
genScopeId: boolean,
|
||||||
|
inline?: boolean
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
push,
|
push,
|
||||||
@ -423,7 +439,10 @@ function genModulePreamble(
|
|||||||
|
|
||||||
genHoists(ast.hoists, context)
|
genHoists(ast.hoists, context)
|
||||||
newline()
|
newline()
|
||||||
push(`export `)
|
|
||||||
|
if (!inline) {
|
||||||
|
push(`export `)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function genAssets(
|
function genAssets(
|
||||||
|
@ -7,7 +7,8 @@ export {
|
|||||||
TransformOptions,
|
TransformOptions,
|
||||||
CodegenOptions,
|
CodegenOptions,
|
||||||
HoistTransform,
|
HoistTransform,
|
||||||
BindingMetadata
|
BindingMetadata,
|
||||||
|
BindingTypes
|
||||||
} from './options'
|
} from './options'
|
||||||
export { baseParse, TextModes } from './parse'
|
export { baseParse, TextModes } from './parse'
|
||||||
export {
|
export {
|
||||||
|
@ -61,11 +61,47 @@ export type HoistTransform = (
|
|||||||
parent: ParentNode
|
parent: ParentNode
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
export interface BindingMetadata {
|
export const enum BindingTypes {
|
||||||
[key: string]: 'data' | 'props' | 'setup' | 'options'
|
DATA = 'data',
|
||||||
|
PROPS = 'props',
|
||||||
|
SETUP = 'setup',
|
||||||
|
CONST = 'const',
|
||||||
|
OPTIONS = 'options'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransformOptions {
|
export interface BindingMetadata {
|
||||||
|
[key: string]: BindingTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SharedTransformCodegenOptions {
|
||||||
|
/**
|
||||||
|
* Transform expressions like {{ foo }} to `_ctx.foo`.
|
||||||
|
* If this option is false, the generated code will be wrapped in a
|
||||||
|
* `with (this) { ... }` block.
|
||||||
|
* - This is force-enabled in module mode, since modules are by default strict
|
||||||
|
* and cannot use `with`
|
||||||
|
* @default mode === 'module'
|
||||||
|
*/
|
||||||
|
prefixIdentifiers?: boolean
|
||||||
|
/**
|
||||||
|
* Generate SSR-optimized render functions instead.
|
||||||
|
* The resulting function must be attached to the component via the
|
||||||
|
* `ssrRender` option instead of `render`.
|
||||||
|
*/
|
||||||
|
ssr?: boolean
|
||||||
|
/**
|
||||||
|
* Optional binding metadata analyzed from script - used to optimize
|
||||||
|
* binding access when `prefixIdentifiers` is enabled.
|
||||||
|
*/
|
||||||
|
bindingMetadata?: BindingMetadata
|
||||||
|
/**
|
||||||
|
* Compile the function for inlining inside setup().
|
||||||
|
* This allows the function to directly access setup() local bindings.
|
||||||
|
*/
|
||||||
|
inline?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransformOptions extends SharedTransformCodegenOptions {
|
||||||
/**
|
/**
|
||||||
* An array of node transforms to be applied to every AST node.
|
* An array of node transforms to be applied to every AST node.
|
||||||
*/
|
*/
|
||||||
@ -128,26 +164,15 @@ export interface TransformOptions {
|
|||||||
* SFC scoped styles ID
|
* SFC scoped styles ID
|
||||||
*/
|
*/
|
||||||
scopeId?: string | null
|
scopeId?: string | null
|
||||||
/**
|
|
||||||
* Generate SSR-optimized render functions instead.
|
|
||||||
* The resulting function must be attached to the component via the
|
|
||||||
* `ssrRender` option instead of `render`.
|
|
||||||
*/
|
|
||||||
ssr?: boolean
|
|
||||||
/**
|
/**
|
||||||
* SFC `<style vars>` injection string
|
* SFC `<style vars>` injection string
|
||||||
* needed to render inline CSS variables on component root
|
* needed to render inline CSS variables on component root
|
||||||
*/
|
*/
|
||||||
ssrCssVars?: string
|
ssrCssVars?: string
|
||||||
/**
|
|
||||||
* Optional binding metadata analyzed from script - used to optimize
|
|
||||||
* binding access when `prefixIdentifiers` is enabled.
|
|
||||||
*/
|
|
||||||
bindingMetadata?: BindingMetadata
|
|
||||||
onError?: (error: CompilerError) => void
|
onError?: (error: CompilerError) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CodegenOptions {
|
export interface CodegenOptions extends SharedTransformCodegenOptions {
|
||||||
/**
|
/**
|
||||||
* - `module` mode will generate ES module import statements for helpers
|
* - `module` mode will generate ES module import statements for helpers
|
||||||
* and export the render function as the default export.
|
* and export the render function as the default export.
|
||||||
@ -189,11 +214,6 @@ export interface CodegenOptions {
|
|||||||
* @default 'Vue'
|
* @default 'Vue'
|
||||||
*/
|
*/
|
||||||
runtimeGlobalName?: string
|
runtimeGlobalName?: string
|
||||||
// we need to know this during codegen to generate proper preambles
|
|
||||||
prefixIdentifiers?: boolean
|
|
||||||
bindingMetadata?: BindingMetadata
|
|
||||||
// generate ssr-specific code?
|
|
||||||
ssr?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
||||||
|
@ -29,6 +29,7 @@ export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
|
|||||||
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
|
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
|
||||||
export const WITH_SCOPE_ID = Symbol(__DEV__ ? `withScopeId` : ``)
|
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` : ``)
|
||||||
|
|
||||||
// Name mapping for runtime helpers that need to be imported from 'vue' in
|
// Name mapping for runtime helpers that need to be imported from 'vue' in
|
||||||
// generated code. Make sure these are correctly exported in the runtime!
|
// generated code. Make sure these are correctly exported in the runtime!
|
||||||
@ -62,7 +63,8 @@ export const helperNameMap: any = {
|
|||||||
[PUSH_SCOPE_ID]: `pushScopeId`,
|
[PUSH_SCOPE_ID]: `pushScopeId`,
|
||||||
[POP_SCOPE_ID]: `popScopeId`,
|
[POP_SCOPE_ID]: `popScopeId`,
|
||||||
[WITH_SCOPE_ID]: `withScopeId`,
|
[WITH_SCOPE_ID]: `withScopeId`,
|
||||||
[WITH_CTX]: `withCtx`
|
[WITH_CTX]: `withCtx`,
|
||||||
|
[UNREF]: `unref`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerRuntimeHelpers(helpers: any) {
|
export function registerRuntimeHelpers(helpers: any) {
|
||||||
|
@ -22,7 +22,8 @@ import {
|
|||||||
isArray,
|
isArray,
|
||||||
NOOP,
|
NOOP,
|
||||||
PatchFlags,
|
PatchFlags,
|
||||||
PatchFlagNames
|
PatchFlagNames,
|
||||||
|
EMPTY_OBJ
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { defaultOnError } from './errors'
|
import { defaultOnError } from './errors'
|
||||||
import {
|
import {
|
||||||
@ -122,7 +123,8 @@ export function createTransformContext(
|
|||||||
scopeId = null,
|
scopeId = null,
|
||||||
ssr = false,
|
ssr = false,
|
||||||
ssrCssVars = ``,
|
ssrCssVars = ``,
|
||||||
bindingMetadata = {},
|
bindingMetadata = EMPTY_OBJ,
|
||||||
|
inline = false,
|
||||||
onError = defaultOnError
|
onError = defaultOnError
|
||||||
}: TransformOptions
|
}: TransformOptions
|
||||||
): TransformContext {
|
): TransformContext {
|
||||||
@ -141,6 +143,7 @@ export function createTransformContext(
|
|||||||
ssr,
|
ssr,
|
||||||
ssrCssVars,
|
ssrCssVars,
|
||||||
bindingMetadata,
|
bindingMetadata,
|
||||||
|
inline,
|
||||||
onError,
|
onError,
|
||||||
|
|
||||||
// state
|
// state
|
||||||
|
@ -207,11 +207,11 @@ export function getStaticType(
|
|||||||
case NodeTypes.TEXT_CALL:
|
case NodeTypes.TEXT_CALL:
|
||||||
return getStaticType(node.content, resultCache)
|
return getStaticType(node.content, resultCache)
|
||||||
case NodeTypes.SIMPLE_EXPRESSION:
|
case NodeTypes.SIMPLE_EXPRESSION:
|
||||||
return node.isConstant
|
return node.isRuntimeConstant
|
||||||
? node.isRuntimeConstant
|
? StaticType.HAS_RUNTIME_CONSTANT
|
||||||
? StaticType.HAS_RUNTIME_CONSTANT
|
: node.isConstant
|
||||||
: StaticType.FULL_STATIC
|
? StaticType.FULL_STATIC
|
||||||
: StaticType.NOT_STATIC
|
: StaticType.NOT_STATIC
|
||||||
case NodeTypes.COMPOUND_EXPRESSION:
|
case NodeTypes.COMPOUND_EXPRESSION:
|
||||||
let returnType = StaticType.FULL_STATIC
|
let returnType = StaticType.FULL_STATIC
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
@ -26,7 +26,10 @@ import {
|
|||||||
isSymbol,
|
isSymbol,
|
||||||
isOn,
|
isOn,
|
||||||
isObject,
|
isObject,
|
||||||
isReservedProp
|
isReservedProp,
|
||||||
|
capitalize,
|
||||||
|
camelize,
|
||||||
|
EMPTY_OBJ
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
@ -37,7 +40,8 @@ import {
|
|||||||
TO_HANDLERS,
|
TO_HANDLERS,
|
||||||
TELEPORT,
|
TELEPORT,
|
||||||
KEEP_ALIVE,
|
KEEP_ALIVE,
|
||||||
SUSPENSE
|
SUSPENSE,
|
||||||
|
UNREF
|
||||||
} from '../runtimeHelpers'
|
} from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
getInnerRange,
|
getInnerRange,
|
||||||
@ -50,6 +54,7 @@ import {
|
|||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { buildSlots } from './vSlot'
|
import { buildSlots } from './vSlot'
|
||||||
import { getStaticType } from './hoistStatic'
|
import { getStaticType } from './hoistStatic'
|
||||||
|
import { BindingTypes } from '../options'
|
||||||
|
|
||||||
// some directive transforms (e.g. v-model) may return a symbol for runtime
|
// some directive transforms (e.g. v-model) may return a symbol for runtime
|
||||||
// import, which should be used instead of a resolveDirective call.
|
// import, which should be used instead of a resolveDirective call.
|
||||||
@ -246,8 +251,32 @@ export function resolveComponentType(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. user component (from setup bindings)
|
// 3. user component (from setup bindings)
|
||||||
if (context.bindingMetadata[tag] === 'setup') {
|
const bindings = context.bindingMetadata
|
||||||
return `$setup[${JSON.stringify(tag)}]`
|
if (bindings !== EMPTY_OBJ) {
|
||||||
|
const checkType = (type: BindingTypes) => {
|
||||||
|
let resolvedTag = tag
|
||||||
|
if (
|
||||||
|
bindings[resolvedTag] === type ||
|
||||||
|
bindings[(resolvedTag = camelize(tag))] === type ||
|
||||||
|
bindings[(resolvedTag = capitalize(camelize(tag)))] === type
|
||||||
|
) {
|
||||||
|
return resolvedTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tagFromSetup = checkType(BindingTypes.SETUP)
|
||||||
|
if (tagFromSetup) {
|
||||||
|
return context.inline
|
||||||
|
? // setup scope bindings may be refs so they need to be unrefed
|
||||||
|
`${context.helperString(UNREF)}(${tagFromSetup})`
|
||||||
|
: `$setup[${JSON.stringify(tagFromSetup)}]`
|
||||||
|
}
|
||||||
|
const tagFromConst = checkType(BindingTypes.CONST)
|
||||||
|
if (tagFromConst) {
|
||||||
|
return context.inline
|
||||||
|
? // in inline mode, const setup bindings (e.g. imports) can be used as-is
|
||||||
|
tagFromConst
|
||||||
|
: `$setup[${JSON.stringify(tagFromConst)}]`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. user component (resolve)
|
// 4. user component (resolve)
|
||||||
|
@ -28,6 +28,8 @@ import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
|
|||||||
import { validateBrowserExpression } from '../validateExpression'
|
import { validateBrowserExpression } from '../validateExpression'
|
||||||
import { parse } from '@babel/parser'
|
import { parse } from '@babel/parser'
|
||||||
import { walk } from 'estree-walker'
|
import { walk } from 'estree-walker'
|
||||||
|
import { UNREF } from '../runtimeHelpers'
|
||||||
|
import { BindingTypes } from '../options'
|
||||||
|
|
||||||
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||||
|
|
||||||
@ -97,12 +99,31 @@ export function processExpression(
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
const { bindingMetadata } = context
|
const { inline, bindingMetadata } = context
|
||||||
const prefix = (raw: string) => {
|
const prefix = (raw: string) => {
|
||||||
const source = hasOwn(bindingMetadata, raw)
|
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
|
||||||
? `$` + bindingMetadata[raw]
|
if (inline) {
|
||||||
: `_ctx`
|
// setup inline mode
|
||||||
return `${source}.${raw}`
|
if (type === BindingTypes.CONST) {
|
||||||
|
return raw
|
||||||
|
} else if (type === BindingTypes.SETUP) {
|
||||||
|
return `${context.helperString(UNREF)}(${raw})`
|
||||||
|
} else if (type === BindingTypes.PROPS) {
|
||||||
|
// use __props which is generated by compileScript so in ts mode
|
||||||
|
// it gets correct type
|
||||||
|
return `__props.${raw}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === BindingTypes.CONST) {
|
||||||
|
// setup const binding in non-inline mode
|
||||||
|
return `$setup.${raw}`
|
||||||
|
} else if (type) {
|
||||||
|
return `$${type}.${raw}`
|
||||||
|
} else {
|
||||||
|
// fallback to ctx
|
||||||
|
return `_ctx.${raw}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fast path if expression is a simple identifier.
|
// fast path if expression is a simple identifier.
|
||||||
@ -110,6 +131,10 @@ export function processExpression(
|
|||||||
// bail on parens to prevent any possible function invocations.
|
// bail on parens to prevent any possible function invocations.
|
||||||
const bailConstant = rawExp.indexOf(`(`) > -1
|
const bailConstant = rawExp.indexOf(`(`) > -1
|
||||||
if (isSimpleIdentifier(rawExp)) {
|
if (isSimpleIdentifier(rawExp)) {
|
||||||
|
// const bindings exposed from setup - we know they never change
|
||||||
|
if (bindingMetadata[node.content] === BindingTypes.CONST) {
|
||||||
|
node.isRuntimeConstant = true
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!asParams &&
|
!asParams &&
|
||||||
!context.identifiers[rawExp] &&
|
!context.identifiers[rawExp] &&
|
||||||
@ -161,9 +186,9 @@ export function processExpression(
|
|||||||
if (!isDuplicate(node)) {
|
if (!isDuplicate(node)) {
|
||||||
const needPrefix = shouldPrefix(node, parent)
|
const needPrefix = shouldPrefix(node, parent)
|
||||||
if (!knownIds[node.name] && needPrefix) {
|
if (!knownIds[node.name] && needPrefix) {
|
||||||
if (isPropertyShorthand(node, parent)) {
|
if (isStaticProperty(parent) && parent.shorthand) {
|
||||||
// property shorthand like { foo }, we need to add the key since we
|
// property shorthand like { foo }, we need to add the key since
|
||||||
// rewrite the value
|
// we rewrite the value
|
||||||
node.prefix = `${node.name}: `
|
node.prefix = `${node.name}: `
|
||||||
}
|
}
|
||||||
node.name = prefix(node.name)
|
node.name = prefix(node.name)
|
||||||
@ -278,46 +303,65 @@ const isStaticProperty = (node: Node): node is ObjectProperty =>
|
|||||||
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
|
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
|
||||||
!node.computed
|
!node.computed
|
||||||
|
|
||||||
const isPropertyShorthand = (node: Node, parent: Node) => {
|
|
||||||
return (
|
|
||||||
isStaticProperty(parent) &&
|
|
||||||
parent.value === node &&
|
|
||||||
parent.key.type === 'Identifier' &&
|
|
||||||
parent.key.name === (node as Identifier).name &&
|
|
||||||
parent.key.start === node.start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isStaticPropertyKey = (node: Node, parent: Node) =>
|
const isStaticPropertyKey = (node: Node, parent: Node) =>
|
||||||
isStaticProperty(parent) && parent.key === node
|
isStaticProperty(parent) && parent.key === node
|
||||||
|
|
||||||
function shouldPrefix(identifier: Identifier, parent: Node) {
|
function shouldPrefix(id: Identifier, parent: Node) {
|
||||||
|
// declaration id
|
||||||
if (
|
if (
|
||||||
!(
|
(parent.type === 'VariableDeclarator' ||
|
||||||
isFunction(parent) &&
|
parent.type === 'ClassDeclaration') &&
|
||||||
// not id of a FunctionDeclaration
|
parent.id === id
|
||||||
((parent as any).id === identifier ||
|
|
||||||
// not a params of a function
|
|
||||||
parent.params.includes(identifier))
|
|
||||||
) &&
|
|
||||||
// not a key of Property
|
|
||||||
!isStaticPropertyKey(identifier, parent) &&
|
|
||||||
// not a property of a MemberExpression
|
|
||||||
!(
|
|
||||||
(parent.type === 'MemberExpression' ||
|
|
||||||
parent.type === 'OptionalMemberExpression') &&
|
|
||||||
parent.property === identifier &&
|
|
||||||
!parent.computed
|
|
||||||
) &&
|
|
||||||
// not in an Array destructure pattern
|
|
||||||
!(parent.type === 'ArrayPattern') &&
|
|
||||||
// skip whitelisted globals
|
|
||||||
!isGloballyWhitelisted(identifier.name) &&
|
|
||||||
// special case for webpack compilation
|
|
||||||
identifier.name !== `require` &&
|
|
||||||
// is a special keyword but parsed as identifier
|
|
||||||
identifier.name !== `arguments`
|
|
||||||
) {
|
) {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFunction(parent)) {
|
||||||
|
// function decalration/expression id
|
||||||
|
if ((parent as any).id === id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// params list
|
||||||
|
if (parent.params.includes(id)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// property key
|
||||||
|
// this also covers object destructure pattern
|
||||||
|
if (isStaticPropertyKey(id, parent)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// array destructure pattern
|
||||||
|
if (parent.type === 'ArrayPattern') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// member expression property
|
||||||
|
if (
|
||||||
|
(parent.type === 'MemberExpression' ||
|
||||||
|
parent.type === 'OptionalMemberExpression') &&
|
||||||
|
parent.property === id &&
|
||||||
|
!parent.computed
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// is a special keyword but parsed as identifier
|
||||||
|
if (id.name === 'arguments') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip whitelisted globals
|
||||||
|
if (isGloballyWhitelisted(id.name)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case for webpack compilation
|
||||||
|
if (id.name === 'require') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ export const transformOn: DirectiveTransform = (
|
|||||||
if (exp && !exp.content.trim()) {
|
if (exp && !exp.content.trim()) {
|
||||||
exp = undefined
|
exp = undefined
|
||||||
}
|
}
|
||||||
let isCacheable: boolean = context.cacheHandlers && !exp
|
let shouldCache: boolean = context.cacheHandlers && !exp
|
||||||
if (exp) {
|
if (exp) {
|
||||||
const isMemberExp = isMemberExpression(exp.content)
|
const isMemberExp = isMemberExpression(exp.content)
|
||||||
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
|
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
|
||||||
@ -83,8 +83,11 @@ export const transformOn: DirectiveTransform = (
|
|||||||
isInlineStatement && context.removeIdentifiers(`$event`)
|
isInlineStatement && context.removeIdentifiers(`$event`)
|
||||||
// with scope analysis, the function is hoistable if it has no reference
|
// with scope analysis, the function is hoistable if it has no reference
|
||||||
// to scope variables.
|
// to scope variables.
|
||||||
isCacheable =
|
shouldCache =
|
||||||
context.cacheHandlers &&
|
context.cacheHandlers &&
|
||||||
|
// runtime constants don't need to be cached
|
||||||
|
// (this is analyzed by compileScript in SFC <script setup>)
|
||||||
|
!(exp.type === NodeTypes.SIMPLE_EXPRESSION && exp.isRuntimeConstant) &&
|
||||||
// #1541 bail if this is a member exp handler passed to a component -
|
// #1541 bail if this is a member exp handler passed to a component -
|
||||||
// we need to use the original function to preserve arity,
|
// we need to use the original function to preserve arity,
|
||||||
// e.g. <transition> relies on checking cb.length to determine
|
// e.g. <transition> relies on checking cb.length to determine
|
||||||
@ -98,7 +101,7 @@ export const transformOn: DirectiveTransform = (
|
|||||||
// to a function, turn it into invocation (and wrap in an arrow function
|
// to a function, turn it into invocation (and wrap in an arrow function
|
||||||
// below) so that it always accesses the latest value when called - thus
|
// below) so that it always accesses the latest value when called - thus
|
||||||
// avoiding the need to be patched.
|
// avoiding the need to be patched.
|
||||||
if (isCacheable && isMemberExp) {
|
if (shouldCache && isMemberExp) {
|
||||||
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
exp.content += `(...args)`
|
exp.content += `(...args)`
|
||||||
} else {
|
} else {
|
||||||
@ -116,7 +119,7 @@ export const transformOn: DirectiveTransform = (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInlineStatement || (isCacheable && isMemberExp)) {
|
if (isInlineStatement || (shouldCache && isMemberExp)) {
|
||||||
// wrap inline statement in a function expression
|
// wrap inline statement in a function expression
|
||||||
exp = createCompoundExpression([
|
exp = createCompoundExpression([
|
||||||
`${isInlineStatement ? `$event` : `(...args)`} => ${
|
`${isInlineStatement ? `$event` : `(...args)`} => ${
|
||||||
@ -142,7 +145,7 @@ export const transformOn: DirectiveTransform = (
|
|||||||
ret = augmentor(ret)
|
ret = augmentor(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCacheable) {
|
if (shouldCache) {
|
||||||
// cache handlers so that it's always the same handler being passed down.
|
// cache handlers so that it's always the same handler being passed down.
|
||||||
// this avoids unnecessary re-renders when users use inline handlers on
|
// this avoids unnecessary re-renders when users use inline handlers on
|
||||||
// components.
|
// components.
|
||||||
|
@ -1,65 +1,491 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`SFC compile <script setup> <script setup lang="ts"> extract emits 1`] = `
|
exports[`SFC compile <script setup> CSS vars injection <script> w/ default export 1`] = `
|
||||||
"import { defineComponent as __define__ } from 'vue'
|
"const __default__ = { setup() {} }
|
||||||
import { Slots as __Slots__ } from 'vue'
|
import { useCssVars as _useCssVars } from 'vue'
|
||||||
declare function __emit__(e: 'foo' | 'bar'): void
|
const __injectCSSVars__ = () => {
|
||||||
declare function __emit__(e: 'baz', id: number): void
|
_useCssVars(_ctx => ({ color: _ctx.color }))
|
||||||
|
}
|
||||||
|
const __setup__ = __default__.setup
|
||||||
|
__default__.setup = __setup__
|
||||||
|
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
||||||
|
: __injectCSSVars__
|
||||||
|
export default __default__"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> CSS vars injection <script> w/ default export in strings/comments 1`] = `
|
||||||
|
"
|
||||||
|
// export default {}
|
||||||
|
const __default__ = {}
|
||||||
|
|
||||||
|
import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
const __injectCSSVars__ = () => {
|
||||||
|
_useCssVars(_ctx => ({ color: _ctx.color }))
|
||||||
|
}
|
||||||
|
const __setup__ = __default__.setup
|
||||||
|
__default__.setup = __setup__
|
||||||
|
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
||||||
|
: __injectCSSVars__
|
||||||
|
export default __default__"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> CSS vars injection <script> w/ no default export 1`] = `
|
||||||
|
"const a = 1
|
||||||
|
const __default__ = {}
|
||||||
|
import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
const __injectCSSVars__ = () => {
|
||||||
|
_useCssVars(_ctx => ({ color: _ctx.color }))
|
||||||
|
}
|
||||||
|
const __setup__ = __default__.setup
|
||||||
|
__default__.setup = __setup__
|
||||||
|
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
||||||
|
: __injectCSSVars__
|
||||||
|
export default __default__"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> CSS vars injection w/ <script setup> 1`] = `
|
||||||
|
"import { useCssVars as _useCssVars } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
const color = 'red'
|
||||||
|
_useCssVars(_ctx => ({ color }))
|
||||||
|
return { color }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> defineOptions() 1`] = `
|
||||||
|
"export default {
|
||||||
|
expose: [],
|
||||||
|
props: {
|
||||||
|
foo: String
|
||||||
|
},
|
||||||
|
emit: ['a', 'b'],
|
||||||
|
setup(__props, { props, emit }) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const bar = 1
|
||||||
|
|
||||||
|
return { props, emit, bar }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> errors should allow defineOptions() referencing imported binding 1`] = `
|
||||||
|
"import { bar } from './bar'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
default: () => bar
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { bar }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> errors should allow defineOptions() referencing scope var 1`] = `
|
||||||
|
"export default {
|
||||||
|
expose: [],
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
default: bar => bar + 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const bar = 1
|
||||||
|
|
||||||
|
|
||||||
|
return { bar }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> imports dedupe between user & helper 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export function setup(_: {}, { emit: myEmit }: {
|
export default {
|
||||||
emit: typeof __emit__,
|
expose: [],
|
||||||
slots: __Slots__,
|
setup() {
|
||||||
|
|
||||||
|
const foo = _ref(1)
|
||||||
|
|
||||||
|
return { foo, ref }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> imports import dedupe between <script> and <script setup> 1`] = `
|
||||||
|
"import { x } from './x'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
x()
|
||||||
|
|
||||||
|
return { x }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> imports should extract comment for import or type declarations 1`] = `
|
||||||
|
"import a from 'a' // comment
|
||||||
|
import b from 'b'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
|
||||||
|
return { a, b }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> imports should hoist and expose imports 1`] = `
|
||||||
|
"import { ref } from 'vue'
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
return { ref }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> inlineTemplate mode avoid unref() when necessary 1`] = `
|
||||||
|
"import { createVNode as _createVNode, unref as _unref, toDisplayString as _toDisplayString, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import Foo from './Foo.vue'
|
||||||
|
import other from './util'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
const constant = {}
|
||||||
|
function fn() {}
|
||||||
|
|
||||||
|
return (_ctx, _cache, $props, $setup, $data, $options) => {
|
||||||
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
|
_createVNode(Foo),
|
||||||
|
_createVNode(\\"div\\", { onClick: fn }, _toDisplayString(_unref(count)) + \\" \\" + _toDisplayString(constant) + \\" \\" + _toDisplayString(_unref(other)), 1 /* TEXT */)
|
||||||
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> inlineTemplate mode should work 1`] = `
|
||||||
|
"import { unref as _unref, toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
|
||||||
|
|
||||||
|
const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"static\\", -1 /* HOISTED */)
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
return (_ctx, _cache, $props, $setup, $data, $options) => {
|
||||||
|
return (_openBlock(), _createBlock(_Fragment, null, [
|
||||||
|
_createVNode(\\"div\\", null, _toDisplayString(_unref(count)), 1 /* TEXT */),
|
||||||
|
_hoisted_1
|
||||||
|
], 64 /* STABLE_FRAGMENT */))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar accessing ref binding 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const a = _ref(1)
|
||||||
|
console.log(a.value)
|
||||||
|
function get() {
|
||||||
|
return a.value + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return { a, get }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar array destructure 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()
|
||||||
|
const a = _ref(__a);
|
||||||
|
const b = _ref(__b);
|
||||||
|
const c = _ref(__c);
|
||||||
|
console.log(n.value, a.value, b.value, c.value)
|
||||||
|
|
||||||
|
return { n, a, b, c }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar convert ref declarations 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const foo = _ref()
|
||||||
|
const a = _ref(1)
|
||||||
|
const b = _ref({
|
||||||
|
count: 0
|
||||||
|
})
|
||||||
|
let c = () => {}
|
||||||
|
let d
|
||||||
|
|
||||||
|
return { foo, a, b, c, d }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar multi ref declarations 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const a = _ref(1), b = _ref(2), c = _ref({
|
||||||
|
count: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return { a, b, c }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar mutating ref binding 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const a = _ref(1)
|
||||||
|
const b = _ref({ count: 0 })
|
||||||
|
function inc() {
|
||||||
|
a.value++
|
||||||
|
a.value = a.value + 1
|
||||||
|
b.value.count++
|
||||||
|
b.value.count = b.value.count + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return { a, b, inc }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar nested destructure 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const [{ a: { b: __b }}] = useFoo()
|
||||||
|
const b = _ref(__b);
|
||||||
|
const { c: [__d, __e] } = useBar()
|
||||||
|
const d = _ref(__d);
|
||||||
|
const e = _ref(__e);
|
||||||
|
console.log(b.value, d.value, e.value)
|
||||||
|
|
||||||
|
return { b, d, e }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar object destructure 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()
|
||||||
|
const a = _ref(__a);
|
||||||
|
const c = _ref(__c);
|
||||||
|
const d = _ref(__d);
|
||||||
|
const f = _ref(__f);
|
||||||
|
const g = _ref(__g);
|
||||||
|
console.log(n.value, a.value, c.value, d.value, f.value, g.value)
|
||||||
|
|
||||||
|
return { n, a, c, d, f, g }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar should not convert non ref labels 1`] = `
|
||||||
|
"export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
foo: a = 1, b = 2, c = {
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> ref: syntax sugar using ref binding in property shorthand 1`] = `
|
||||||
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
const a = _ref(1)
|
||||||
|
const b = { a: a.value }
|
||||||
|
function test() {
|
||||||
|
const { a } = b
|
||||||
|
}
|
||||||
|
|
||||||
|
return { a, b, test }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> should expose top level declarations 1`] = `
|
||||||
|
"import { x } from './x'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
let a = 1
|
||||||
|
const b = 2
|
||||||
|
function c() {}
|
||||||
|
class d {}
|
||||||
|
|
||||||
|
return { a, b, c, d, x }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> with TypeScript defineOptions w/ runtime options 1`] = `
|
||||||
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
export default _defineComponent({
|
||||||
|
expose: [],
|
||||||
|
props: { foo: String },
|
||||||
|
emits: ['a', 'b'],
|
||||||
|
setup(__props, { props, emit }) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { props, emit }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract emits (union) 1`] = `
|
||||||
|
"import { Slots as _Slots, defineComponent as _defineComponent } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
export default _defineComponent({
|
||||||
|
expose: [],
|
||||||
|
emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
|
||||||
|
setup(__props, { emit }: {
|
||||||
|
props: {},
|
||||||
|
emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void),
|
||||||
|
slots: Slots,
|
||||||
attrs: Record<string, any>
|
attrs: Record<string, any>
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
|
||||||
return { }
|
|
||||||
|
return { emit }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default __define__({
|
|
||||||
emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
|
|
||||||
setup
|
|
||||||
})"
|
})"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> <script setup lang="ts"> extract props 1`] = `
|
exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract emits 1`] = `
|
||||||
"import { defineComponent as __define__ } from 'vue'
|
"import { Slots as _Slots, defineComponent as _defineComponent } from 'vue'
|
||||||
import { Slots as __Slots__ } from 'vue'
|
|
||||||
interface Test {}
|
|
||||||
|
export default _defineComponent({
|
||||||
|
expose: [],
|
||||||
|
emits: [\\"foo\\", \\"bar\\"] as unknown as undefined,
|
||||||
|
setup(__props, { emit }: {
|
||||||
|
props: {},
|
||||||
|
emit: (e: 'foo' | 'bar') => void,
|
||||||
|
slots: Slots,
|
||||||
|
attrs: Record<string, any>
|
||||||
|
}) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { emit }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SFC compile <script setup> with TypeScript defineOptions w/ type / extract props 1`] = `
|
||||||
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
|
|
||||||
|
interface Test {}
|
||||||
|
|
||||||
type Alias = number[]
|
type Alias = number[]
|
||||||
|
|
||||||
|
|
||||||
export function setup(myProps: {
|
export default _defineComponent({
|
||||||
string: string
|
expose: [],
|
||||||
number: number
|
|
||||||
boolean: boolean
|
|
||||||
object: object
|
|
||||||
objectLiteral: { a: number }
|
|
||||||
fn: (n: number) => void
|
|
||||||
functionRef: Function
|
|
||||||
objectRef: Object
|
|
||||||
array: string[]
|
|
||||||
arrayRef: Array<any>
|
|
||||||
tuple: [number, number]
|
|
||||||
set: Set<string>
|
|
||||||
literal: 'foo'
|
|
||||||
optional?: any
|
|
||||||
recordRef: Record<string, null>
|
|
||||||
interface: Test
|
|
||||||
alias: Alias
|
|
||||||
|
|
||||||
union: string | number
|
|
||||||
literalUnion: 'foo' | 'bar'
|
|
||||||
literalUnionMixed: 'foo' | 1 | boolean
|
|
||||||
intersection: Test & {}
|
|
||||||
}) {
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default __define__({
|
|
||||||
props: {
|
props: {
|
||||||
string: { type: String, required: true },
|
string: { type: String, required: true },
|
||||||
number: { type: Number, required: true },
|
number: { type: Number, required: true },
|
||||||
@ -83,321 +509,28 @@ export default __define__({
|
|||||||
literalUnionMixed: { type: [String, Number, Boolean], required: true },
|
literalUnionMixed: { type: [String, Number, Boolean], required: true },
|
||||||
intersection: { type: Object, required: true }
|
intersection: { type: Object, required: true }
|
||||||
} as unknown as undefined,
|
} as unknown as undefined,
|
||||||
setup
|
setup() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
})"
|
})"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> <script setup lang="ts"> hoist type declarations 1`] = `
|
exports[`SFC compile <script setup> with TypeScript hoist type declarations 1`] = `
|
||||||
"import { defineComponent as __define__ } from 'vue'
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
import { Slots as __Slots__ } from 'vue'
|
|
||||||
export interface Foo {}
|
export interface Foo {}
|
||||||
type Bar = {}
|
type Bar = {}
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
const a = 1
|
|
||||||
|
|
||||||
return { a }
|
export default _defineComponent({
|
||||||
|
expose: [],
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
|
||||||
|
return { }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default __define__({
|
|
||||||
setup
|
|
||||||
})"
|
})"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`SFC compile <script setup> CSS vars injection <script> w/ default export 1`] = `
|
|
||||||
"const __default__ = { setup() {} }
|
|
||||||
import { useCssVars as __useCssVars__ } from 'vue'
|
|
||||||
const __injectCSSVars__ = () => {
|
|
||||||
__useCssVars__(_ctx => ({ color: _ctx.color }))
|
|
||||||
}
|
|
||||||
const __setup__ = __default__.setup
|
|
||||||
__default__.setup = __setup__
|
|
||||||
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
|
||||||
: __injectCSSVars__
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> CSS vars injection <script> w/ default export in strings/comments 1`] = `
|
|
||||||
"
|
|
||||||
// export default {}
|
|
||||||
const __default__ = {}
|
|
||||||
|
|
||||||
import { useCssVars as __useCssVars__ } from 'vue'
|
|
||||||
const __injectCSSVars__ = () => {
|
|
||||||
__useCssVars__(_ctx => ({ color: _ctx.color }))
|
|
||||||
}
|
|
||||||
const __setup__ = __default__.setup
|
|
||||||
__default__.setup = __setup__
|
|
||||||
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
|
||||||
: __injectCSSVars__
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> CSS vars injection <script> w/ no default export 1`] = `
|
|
||||||
"const a = 1
|
|
||||||
const __default__ = {}
|
|
||||||
import { useCssVars as __useCssVars__ } from 'vue'
|
|
||||||
const __injectCSSVars__ = () => {
|
|
||||||
__useCssVars__(_ctx => ({ color: _ctx.color }))
|
|
||||||
}
|
|
||||||
const __setup__ = __default__.setup
|
|
||||||
__default__.setup = __setup__
|
|
||||||
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
|
|
||||||
: __injectCSSVars__
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> CSS vars injection w/ <script setup> 1`] = `
|
|
||||||
"import { useCssVars as __useCssVars__ } from 'vue'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
const color = 'red'
|
|
||||||
__useCssVars__(_ctx => ({ color }))
|
|
||||||
return { color }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> errors should allow export default referencing imported binding 1`] = `
|
|
||||||
"import { bar } from './bar'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
|
|
||||||
return { bar }
|
|
||||||
}
|
|
||||||
|
|
||||||
const __default__ = {
|
|
||||||
props: {
|
|
||||||
foo: {
|
|
||||||
default: () => bar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> errors should allow export default referencing re-exported binding 1`] = `
|
|
||||||
"import { bar } from './bar'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
|
|
||||||
return { bar }
|
|
||||||
}
|
|
||||||
|
|
||||||
const __default__ = {
|
|
||||||
props: {
|
|
||||||
foo: {
|
|
||||||
default: () => bar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> errors should allow export default referencing scope var 1`] = `
|
|
||||||
"export function setup() {
|
|
||||||
|
|
||||||
const bar = 1
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
const __default__ = {
|
|
||||||
props: {
|
|
||||||
foo: {
|
|
||||||
default: bar => bar + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> explicit setup signature 1`] = `
|
|
||||||
"export function setup(props, { emit }) {
|
|
||||||
emit('foo')
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export * from './x' 1`] = `
|
|
||||||
"import { toRefs as __toRefs__ } from 'vue'
|
|
||||||
import * as __export_all_0__ from './x'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
const y = 1
|
|
||||||
|
|
||||||
return Object.assign(
|
|
||||||
{ y },
|
|
||||||
__toRefs__(__export_all_0__)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export { x } 1`] = `
|
|
||||||
"export function setup() {
|
|
||||||
|
|
||||||
const x = 1
|
|
||||||
const y = 2
|
|
||||||
|
|
||||||
return { x, y }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export { x } from './x' 1`] = `
|
|
||||||
"import { x, y } from './x'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
|
|
||||||
return { x, y }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export { x as default } 1`] = `
|
|
||||||
"import x from './x'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
const y = 1
|
|
||||||
|
|
||||||
return { y }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const __default__ = x
|
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export { x as default } from './x' 1`] = `
|
|
||||||
"import { x as __default__ } from './x'
|
|
||||||
import { y } from './x'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
|
|
||||||
return { y }
|
|
||||||
}
|
|
||||||
|
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export class X() {} 1`] = `
|
|
||||||
"export function setup() {
|
|
||||||
class X {}
|
|
||||||
return { X }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export const { x } = ... (destructuring) 1`] = `
|
|
||||||
"export function setup() {
|
|
||||||
|
|
||||||
const [a = 1, { b } = { b: 123 }, ...c] = useFoo()
|
|
||||||
const { d = 2, _: [e], ...f } = useBar()
|
|
||||||
|
|
||||||
return { a, b, c, d, e, f }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export const x = ... 1`] = `
|
|
||||||
"export function setup() {
|
|
||||||
const x = 1
|
|
||||||
return { x }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export default from './x' 1`] = `
|
|
||||||
"import __default__ from './x'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export default in <script setup> 1`] = `
|
|
||||||
"export function setup() {
|
|
||||||
|
|
||||||
const y = 1
|
|
||||||
|
|
||||||
return { y }
|
|
||||||
}
|
|
||||||
|
|
||||||
const __default__ = {
|
|
||||||
props: ['foo']
|
|
||||||
}
|
|
||||||
__default__.setup = setup
|
|
||||||
export default __default__"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> exports export function x() {} 1`] = `
|
|
||||||
"export function setup() {
|
|
||||||
function x(){}
|
|
||||||
return { x }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> import dedupe between <script> and <script setup> 1`] = `
|
|
||||||
"import { x } from './x'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
x()
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> should extract comment for import or type declarations 1`] = `
|
|
||||||
"import a from 'a' // comment
|
|
||||||
import b from 'b'
|
|
||||||
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SFC compile <script setup> should hoist imports 1`] = `
|
|
||||||
"import { ref } from 'vue'
|
|
||||||
export function setup() {
|
|
||||||
|
|
||||||
return { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { setup }"
|
|
||||||
`;
|
|
||||||
|
@ -22,232 +22,220 @@ function assertCode(code: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('SFC compile <script setup>', () => {
|
describe('SFC compile <script setup>', () => {
|
||||||
test('should hoist imports', () => {
|
test('should expose top level declarations', () => {
|
||||||
assertCode(
|
|
||||||
compile(`<script setup>import { ref } from 'vue'</script>`).content
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should extract comment for import or type declarations', () => {
|
|
||||||
assertCode(
|
|
||||||
compile(`<script setup>
|
|
||||||
import a from 'a' // comment
|
|
||||||
import b from 'b'
|
|
||||||
</script>`).content
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('explicit setup signature', () => {
|
|
||||||
assertCode(
|
|
||||||
compile(`<script setup="props, { emit }">emit('foo')</script>`).content
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('import dedupe between <script> and <script setup>', () => {
|
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script>
|
|
||||||
import { x } from './x'
|
|
||||||
</script>
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { x } from './x'
|
import { x } from './x'
|
||||||
x()
|
let a = 1
|
||||||
|
const b = 2
|
||||||
|
function c() {}
|
||||||
|
class d {}
|
||||||
</script>
|
</script>
|
||||||
`)
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content.indexOf(`import { x }`)).toEqual(
|
expect(content).toMatch('return { a, b, c, d, x }')
|
||||||
content.lastIndexOf(`import { x }`)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('exports', () => {
|
test('defineOptions()', () => {
|
||||||
test('export const x = ...', () => {
|
const { content, bindings } = compile(`
|
||||||
const { content, bindings } = compile(
|
<script setup>
|
||||||
`<script setup>export const x = 1</script>`
|
import { defineOptions } from 'vue'
|
||||||
)
|
const { props, emit } = defineOptions({
|
||||||
assertCode(content)
|
props: {
|
||||||
expect(bindings).toStrictEqual({
|
foo: String
|
||||||
x: 'setup'
|
},
|
||||||
})
|
emit: ['a', 'b']
|
||||||
|
})
|
||||||
|
|
||||||
|
const bar = 1
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
// should generate working code
|
||||||
|
assertCode(content)
|
||||||
|
// should anayze bindings
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
foo: 'props',
|
||||||
|
bar: 'const',
|
||||||
|
props: 'const',
|
||||||
|
emit: 'const'
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export const { x } = ... (destructuring)', () => {
|
// should remove defineOptions import and call
|
||||||
const { content, bindings } = compile(`<script setup>
|
expect(content).not.toMatch('defineOptions')
|
||||||
export const [a = 1, { b } = { b: 123 }, ...c] = useFoo()
|
// should generate correct setup signature
|
||||||
export const { d = 2, _: [e], ...f } = useBar()
|
expect(content).toMatch(`setup(__props, { props, emit }) {`)
|
||||||
</script>`)
|
// should include context options in default export
|
||||||
assertCode(content)
|
expect(content).toMatch(`export default {
|
||||||
expect(bindings).toStrictEqual({
|
expose: [],
|
||||||
a: 'setup',
|
props: {
|
||||||
b: 'setup',
|
foo: String
|
||||||
c: 'setup',
|
},
|
||||||
d: 'setup',
|
emit: ['a', 'b'],`)
|
||||||
e: 'setup',
|
})
|
||||||
f: 'setup'
|
|
||||||
})
|
describe('imports', () => {
|
||||||
|
test('should hoist and expose imports', () => {
|
||||||
|
assertCode(
|
||||||
|
compile(`<script setup>import { ref } from 'vue'</script>`).content
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export function x() {}', () => {
|
test('should extract comment for import or type declarations', () => {
|
||||||
const { content, bindings } = compile(
|
assertCode(
|
||||||
`<script setup>export function x(){}</script>`
|
compile(`
|
||||||
|
<script setup>
|
||||||
|
import a from 'a' // comment
|
||||||
|
import b from 'b'
|
||||||
|
</script>
|
||||||
|
`).content
|
||||||
)
|
)
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
x: 'setup'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export class X() {}', () => {
|
test('dedupe between user & helper', () => {
|
||||||
const { content, bindings } = compile(
|
const { content } = compile(`
|
||||||
`<script setup>export class X {}</script>`
|
<script setup>
|
||||||
)
|
import { ref } from 'vue'
|
||||||
|
ref: foo = 1
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(bindings).toStrictEqual({
|
expect(content).toMatch(`import { ref } from 'vue'`)
|
||||||
X: 'setup'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export { x }', () => {
|
test('import dedupe between <script> and <script setup>', () => {
|
||||||
const { content, bindings } = compile(
|
const { content } = compile(`
|
||||||
`<script setup>
|
<script>
|
||||||
const x = 1
|
import { x } from './x'
|
||||||
const y = 2
|
</script>
|
||||||
export { x, y }
|
<script setup>
|
||||||
</script>`
|
import { x } from './x'
|
||||||
)
|
x()
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(bindings).toStrictEqual({
|
expect(content.indexOf(`import { x }`)).toEqual(
|
||||||
x: 'setup',
|
content.lastIndexOf(`import { x }`)
|
||||||
y: 'setup'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`export { x } from './x'`, () => {
|
|
||||||
const { content, bindings } = compile(
|
|
||||||
`<script setup>
|
|
||||||
export { x, y } from './x'
|
|
||||||
</script>`
|
|
||||||
)
|
)
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
x: 'setup',
|
|
||||||
y: 'setup'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`export default from './x'`, () => {
|
|
||||||
const { content, bindings } = compile(
|
|
||||||
`<script setup>
|
|
||||||
export default from './x'
|
|
||||||
</script>`,
|
|
||||||
{
|
|
||||||
babelParserPlugins: ['exportDefaultFrom']
|
|
||||||
}
|
|
||||||
)
|
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`export { x as default }`, () => {
|
|
||||||
const { content, bindings } = compile(
|
|
||||||
`<script setup>
|
|
||||||
import x from './x'
|
|
||||||
const y = 1
|
|
||||||
export { x as default, y }
|
|
||||||
</script>`
|
|
||||||
)
|
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
y: 'setup'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`export { x as default } from './x'`, () => {
|
|
||||||
const { content, bindings } = compile(
|
|
||||||
`<script setup>
|
|
||||||
export { x as default, y } from './x'
|
|
||||||
</script>`
|
|
||||||
)
|
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
y: 'setup'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`export * from './x'`, () => {
|
|
||||||
const { content, bindings } = compile(
|
|
||||||
`<script setup>
|
|
||||||
export * from './x'
|
|
||||||
export const y = 1
|
|
||||||
</script>`
|
|
||||||
)
|
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
y: 'setup'
|
|
||||||
// in this case we cannot extract bindings from ./x so it falls back
|
|
||||||
// to runtime proxy dispatching
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('export default in <script setup>', () => {
|
|
||||||
const { content, bindings } = compile(
|
|
||||||
`<script setup>
|
|
||||||
export default {
|
|
||||||
props: ['foo']
|
|
||||||
}
|
|
||||||
export const y = 1
|
|
||||||
</script>`
|
|
||||||
)
|
|
||||||
assertCode(content)
|
|
||||||
expect(bindings).toStrictEqual({
|
|
||||||
foo: 'props',
|
|
||||||
y: 'setup'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('<script setup lang="ts">', () => {
|
describe('inlineTemplate mode', () => {
|
||||||
|
test('should work', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>{{ count }}</div>
|
||||||
|
<div>static</div>
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
{ inlineTemplate: true }
|
||||||
|
)
|
||||||
|
// check snapshot and make sure helper imports and
|
||||||
|
// hoists are placed correctly.
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('avoid unref() when necessary', () => {
|
||||||
|
// function, const, component import
|
||||||
|
const { content } = compile(
|
||||||
|
`
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineOptions } from 'vue'
|
||||||
|
import Foo from './Foo.vue'
|
||||||
|
import other from './util'
|
||||||
|
const count = ref(0)
|
||||||
|
const constant = {}
|
||||||
|
function fn() {}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Foo/>
|
||||||
|
<div @click="fn">{{ count }} {{ constant }} {{ other }}</div>
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
{ inlineTemplate: true }
|
||||||
|
)
|
||||||
|
assertCode(content)
|
||||||
|
// no need to unref vue component import
|
||||||
|
expect(content).toMatch(`createVNode(Foo)`)
|
||||||
|
// should unref other imports
|
||||||
|
expect(content).toMatch(`unref(other)`)
|
||||||
|
// no need to unref constant literals
|
||||||
|
expect(content).not.toMatch(`unref(constant)`)
|
||||||
|
// should unref const w/ call init (e.g. ref())
|
||||||
|
expect(content).toMatch(`unref(count)`)
|
||||||
|
// no need to unref function declarations
|
||||||
|
expect(content).toMatch(`{ onClick: fn }`)
|
||||||
|
// no need to mark constant fns in patch flag
|
||||||
|
expect(content).not.toMatch(`PROPS`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with TypeScript', () => {
|
||||||
test('hoist type declarations', () => {
|
test('hoist type declarations', () => {
|
||||||
const { content, bindings } = compile(`
|
const { content } = compile(`
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
export interface Foo {}
|
export interface Foo {}
|
||||||
type Bar = {}
|
type Bar = {}
|
||||||
export const a = 1
|
|
||||||
</script>`)
|
</script>`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(bindings).toStrictEqual({ a: 'setup' })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('extract props', () => {
|
test('defineOptions w/ runtime options', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup="myProps" lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { defineOptions } from 'vue'
|
||||||
|
const { props, emit } = defineOptions({
|
||||||
|
props: { foo: String },
|
||||||
|
emits: ['a', 'b']
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`export default _defineComponent({
|
||||||
|
expose: [],
|
||||||
|
props: { foo: String },
|
||||||
|
emits: ['a', 'b'],
|
||||||
|
setup(__props, { props, emit }) {`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defineOptions w/ type / extract props', () => {
|
||||||
|
const { content, bindings } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineOptions } from 'vue'
|
||||||
interface Test {}
|
interface Test {}
|
||||||
|
|
||||||
type Alias = number[]
|
type Alias = number[]
|
||||||
|
|
||||||
declare const myProps: {
|
defineOptions<{
|
||||||
string: string
|
props: {
|
||||||
number: number
|
string: string
|
||||||
boolean: boolean
|
number: number
|
||||||
object: object
|
boolean: boolean
|
||||||
objectLiteral: { a: number }
|
object: object
|
||||||
fn: (n: number) => void
|
objectLiteral: { a: number }
|
||||||
functionRef: Function
|
fn: (n: number) => void
|
||||||
objectRef: Object
|
functionRef: Function
|
||||||
array: string[]
|
objectRef: Object
|
||||||
arrayRef: Array<any>
|
array: string[]
|
||||||
tuple: [number, number]
|
arrayRef: Array<any>
|
||||||
set: Set<string>
|
tuple: [number, number]
|
||||||
literal: 'foo'
|
set: Set<string>
|
||||||
optional?: any
|
literal: 'foo'
|
||||||
recordRef: Record<string, null>
|
optional?: any
|
||||||
interface: Test
|
recordRef: Record<string, null>
|
||||||
alias: Alias
|
interface: Test
|
||||||
|
alias: Alias
|
||||||
|
|
||||||
union: string | number
|
union: string | number
|
||||||
literalUnion: 'foo' | 'bar'
|
literalUnion: 'foo' | 'bar'
|
||||||
literalUnionMixed: 'foo' | 1 | boolean
|
literalUnionMixed: 'foo' | 1 | boolean
|
||||||
intersection: Test & {}
|
intersection: Test & {}
|
||||||
}
|
}
|
||||||
|
}>()
|
||||||
</script>`)
|
</script>`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content).toMatch(`string: { type: String, required: true }`)
|
expect(content).toMatch(`string: { type: String, required: true }`)
|
||||||
@ -277,21 +265,57 @@ import b from 'b'
|
|||||||
`literalUnionMixed: { type: [String, Number, Boolean], required: true }`
|
`literalUnionMixed: { type: [String, Number, Boolean], required: true }`
|
||||||
)
|
)
|
||||||
expect(content).toMatch(`intersection: { type: Object, required: true }`)
|
expect(content).toMatch(`intersection: { type: Object, required: true }`)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
string: 'props',
|
||||||
|
number: 'props',
|
||||||
|
boolean: 'props',
|
||||||
|
object: 'props',
|
||||||
|
objectLiteral: 'props',
|
||||||
|
fn: 'props',
|
||||||
|
functionRef: 'props',
|
||||||
|
objectRef: 'props',
|
||||||
|
array: 'props',
|
||||||
|
arrayRef: 'props',
|
||||||
|
tuple: 'props',
|
||||||
|
set: 'props',
|
||||||
|
literal: 'props',
|
||||||
|
optional: 'props',
|
||||||
|
recordRef: 'props',
|
||||||
|
interface: 'props',
|
||||||
|
alias: 'props',
|
||||||
|
union: 'props',
|
||||||
|
literalUnion: 'props',
|
||||||
|
literalUnionMixed: 'props',
|
||||||
|
intersection: 'props'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('extract emits', () => {
|
test('defineOptions w/ type / extract emits', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup="_, { emit: myEmit }" lang="ts">
|
<script setup lang="ts">
|
||||||
declare function myEmit(e: 'foo' | 'bar'): void
|
import { defineOptions } from 'vue'
|
||||||
declare function myEmit(e: 'baz', id: number): void
|
const { emit } = defineOptions<{
|
||||||
|
emit: (e: 'foo' | 'bar') => void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`props: {},\n emit: (e: 'foo' | 'bar') => void,`)
|
||||||
|
expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defineOptions w/ type / extract emits (union)', () => {
|
||||||
|
const { content } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineOptions } from 'vue'
|
||||||
|
const { emit } = defineOptions<{
|
||||||
|
emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
`)
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(
|
||||||
`declare function __emit__(e: 'foo' | 'bar'): void`
|
`props: {},\n emit: ((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void),`
|
||||||
)
|
|
||||||
expect(content).toMatch(
|
|
||||||
`declare function __emit__(e: 'baz', id: number): void`
|
|
||||||
)
|
)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(
|
||||||
`emits: ["foo", "bar", "baz"] as unknown as undefined`
|
`emits: ["foo", "bar", "baz"] as unknown as undefined`
|
||||||
@ -333,7 +357,7 @@ import b from 'b'
|
|||||||
test('w/ <script setup>', () => {
|
test('w/ <script setup>', () => {
|
||||||
assertCode(
|
assertCode(
|
||||||
compile(
|
compile(
|
||||||
`<script setup>export const color = 'red'</script>\n` +
|
`<script setup>const color = 'red'</script>\n` +
|
||||||
`<style vars="{ color }">div{ color: var(--color); }</style>`
|
`<style vars="{ color }">div{ color: var(--color); }</style>`
|
||||||
).content
|
).content
|
||||||
)
|
)
|
||||||
@ -343,9 +367,7 @@ import b from 'b'
|
|||||||
describe('async/await detection', () => {
|
describe('async/await detection', () => {
|
||||||
function assertAwaitDetection(code: string, shouldAsync = true) {
|
function assertAwaitDetection(code: string, shouldAsync = true) {
|
||||||
const { content } = compile(`<script setup>${code}</script>`)
|
const { content } = compile(`<script setup>${code}</script>`)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup()`)
|
||||||
`export ${shouldAsync ? `async ` : ``}function setup`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test('expression statement', () => {
|
test('expression statement', () => {
|
||||||
@ -356,8 +378,8 @@ import b from 'b'
|
|||||||
assertAwaitDetection(`const a = 1 + (await foo)`)
|
assertAwaitDetection(`const a = 1 + (await foo)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export', () => {
|
test('ref', () => {
|
||||||
assertAwaitDetection(`export const a = 1 + (await foo)`)
|
assertAwaitDetection(`ref: a = 1 + (await foo)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('nested statements', () => {
|
test('nested statements', () => {
|
||||||
@ -366,7 +388,7 @@ import b from 'b'
|
|||||||
|
|
||||||
test('should ignore await inside functions', () => {
|
test('should ignore await inside functions', () => {
|
||||||
// function declaration
|
// function declaration
|
||||||
assertAwaitDetection(`export async function foo() { await bar }`, false)
|
assertAwaitDetection(`async function foo() { await bar }`, false)
|
||||||
// function expression
|
// function expression
|
||||||
assertAwaitDetection(`const foo = async () => { await bar }`, false)
|
assertAwaitDetection(`const foo = async () => { await bar }`, false)
|
||||||
// object method
|
// object method
|
||||||
@ -379,6 +401,202 @@ import b from 'b'
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('ref: syntax sugar', () => {
|
||||||
|
test('convert ref declarations', () => {
|
||||||
|
const { content, bindings } = compile(`<script setup>
|
||||||
|
ref: foo
|
||||||
|
ref: a = 1
|
||||||
|
ref: b = {
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
let c = () => {}
|
||||||
|
let d
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(`import { ref as _ref } from 'vue'`)
|
||||||
|
expect(content).not.toMatch(`ref: foo`)
|
||||||
|
expect(content).not.toMatch(`ref: a`)
|
||||||
|
expect(content).not.toMatch(`ref: b`)
|
||||||
|
expect(content).toMatch(`const foo = _ref()`)
|
||||||
|
expect(content).toMatch(`const a = _ref(1)`)
|
||||||
|
expect(content).toMatch(`
|
||||||
|
const b = _ref({
|
||||||
|
count: 0
|
||||||
|
})
|
||||||
|
`)
|
||||||
|
// normal declarations left untouched
|
||||||
|
expect(content).toMatch(`let c = () => {}`)
|
||||||
|
expect(content).toMatch(`let d`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
foo: 'setup',
|
||||||
|
a: 'setup',
|
||||||
|
b: 'setup',
|
||||||
|
c: 'setup',
|
||||||
|
d: 'setup'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('multi ref declarations', () => {
|
||||||
|
const { content, bindings } = compile(`<script setup>
|
||||||
|
ref: a = 1, b = 2, c = {
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(`
|
||||||
|
const a = _ref(1), b = _ref(2), c = _ref({
|
||||||
|
count: 0
|
||||||
|
})
|
||||||
|
`)
|
||||||
|
expect(content).toMatch(`return { a, b, c }`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
a: 'setup',
|
||||||
|
b: 'setup',
|
||||||
|
c: 'setup'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not convert non ref labels', () => {
|
||||||
|
const { content } = compile(`<script setup>
|
||||||
|
foo: a = 1, b = 2, c = {
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(`foo: a = 1, b = 2`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('accessing ref binding', () => {
|
||||||
|
const { content } = compile(`<script setup>
|
||||||
|
ref: a = 1
|
||||||
|
console.log(a)
|
||||||
|
function get() {
|
||||||
|
return a + 1
|
||||||
|
}
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(`console.log(a.value)`)
|
||||||
|
expect(content).toMatch(`return a.value + 1`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cases that should not append .value', () => {
|
||||||
|
const { content } = compile(`<script setup>
|
||||||
|
ref: a = 1
|
||||||
|
console.log(b.a)
|
||||||
|
function get(a) {
|
||||||
|
return a + 1
|
||||||
|
}
|
||||||
|
</script>`)
|
||||||
|
expect(content).not.toMatch(`a.value`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('mutating ref binding', () => {
|
||||||
|
const { content } = compile(`<script setup>
|
||||||
|
ref: a = 1
|
||||||
|
ref: b = { count: 0 }
|
||||||
|
function inc() {
|
||||||
|
a++
|
||||||
|
a = a + 1
|
||||||
|
b.count++
|
||||||
|
b.count = b.count + 1
|
||||||
|
}
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(`a.value++`)
|
||||||
|
expect(content).toMatch(`a.value = a.value + 1`)
|
||||||
|
expect(content).toMatch(`b.value.count++`)
|
||||||
|
expect(content).toMatch(`b.value.count = b.value.count + 1`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('using ref binding in property shorthand', () => {
|
||||||
|
const { content } = compile(`<script setup>
|
||||||
|
ref: a = 1
|
||||||
|
const b = { a }
|
||||||
|
function test() {
|
||||||
|
const { a } = b
|
||||||
|
}
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(`const b = { a: a.value }`)
|
||||||
|
// should not convert destructure
|
||||||
|
expect(content).toMatch(`const { a } = b`)
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('object destructure', () => {
|
||||||
|
const { content, bindings } = compile(`<script setup>
|
||||||
|
ref: n = 1, ({ a, b: c, d = 1, e: f = 2, ...g } = useFoo())
|
||||||
|
console.log(n, a, c, d, f, g)
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(
|
||||||
|
`const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`\nconst a = _ref(__a);`)
|
||||||
|
expect(content).not.toMatch(`\nconst b = _ref(__b);`)
|
||||||
|
expect(content).toMatch(`\nconst c = _ref(__c);`)
|
||||||
|
expect(content).toMatch(`\nconst d = _ref(__d);`)
|
||||||
|
expect(content).not.toMatch(`\nconst e = _ref(__e);`)
|
||||||
|
expect(content).toMatch(`\nconst f = _ref(__f);`)
|
||||||
|
expect(content).toMatch(`\nconst g = _ref(__g);`)
|
||||||
|
expect(content).toMatch(
|
||||||
|
`console.log(n.value, a.value, c.value, d.value, f.value, g.value)`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`return { n, a, c, d, f, g }`)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
n: 'setup',
|
||||||
|
a: 'setup',
|
||||||
|
c: 'setup',
|
||||||
|
d: 'setup',
|
||||||
|
f: 'setup',
|
||||||
|
g: 'setup'
|
||||||
|
})
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('array destructure', () => {
|
||||||
|
const { content, bindings } = compile(`<script setup>
|
||||||
|
ref: n = 1, [a, b = 1, ...c] = useFoo()
|
||||||
|
console.log(n, a, b, c)
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(
|
||||||
|
`const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()`
|
||||||
|
)
|
||||||
|
expect(content).toMatch(`\nconst a = _ref(__a);`)
|
||||||
|
expect(content).toMatch(`\nconst b = _ref(__b);`)
|
||||||
|
expect(content).toMatch(`\nconst c = _ref(__c);`)
|
||||||
|
expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
|
||||||
|
expect(content).toMatch(`return { n, a, b, c }`)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
n: 'setup',
|
||||||
|
a: 'setup',
|
||||||
|
b: 'setup',
|
||||||
|
c: 'setup'
|
||||||
|
})
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nested destructure', () => {
|
||||||
|
const { content, bindings } = compile(`<script setup>
|
||||||
|
ref: [{ a: { b }}] = useFoo()
|
||||||
|
ref: ({ c: [d, e] } = useBar())
|
||||||
|
console.log(b, d, e)
|
||||||
|
</script>`)
|
||||||
|
expect(content).toMatch(`const [{ a: { b: __b }}] = useFoo()`)
|
||||||
|
expect(content).toMatch(`const { c: [__d, __e] } = useBar()`)
|
||||||
|
expect(content).not.toMatch(`\nconst a = _ref(__a);`)
|
||||||
|
expect(content).not.toMatch(`\nconst c = _ref(__c);`)
|
||||||
|
expect(content).toMatch(`\nconst b = _ref(__b);`)
|
||||||
|
expect(content).toMatch(`\nconst d = _ref(__d);`)
|
||||||
|
expect(content).toMatch(`\nconst e = _ref(__e);`)
|
||||||
|
expect(content).toMatch(`return { b, d, e }`)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
b: 'setup',
|
||||||
|
d: 'setup',
|
||||||
|
e: 'setup'
|
||||||
|
})
|
||||||
|
assertCode(content)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
test('<script> and <script setup> must have same lang', () => {
|
test('<script> and <script setup> must have same lang', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
@ -386,145 +604,105 @@ import b from 'b'
|
|||||||
).toThrow(`<script> and <script setup> must have the same language type`)
|
).toThrow(`<script> and <script setup> must have the same language type`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export local as default', () => {
|
const moduleErrorMsg = `cannot contain ES module exports`
|
||||||
|
|
||||||
|
test('non-type named exports', () => {
|
||||||
|
expect(() =>
|
||||||
|
compile(`<script setup>
|
||||||
|
export const a = 1
|
||||||
|
</script>`)
|
||||||
|
).toThrow(moduleErrorMsg)
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
compile(`<script setup>
|
||||||
|
export * from './foo'
|
||||||
|
</script>`)
|
||||||
|
).toThrow(moduleErrorMsg)
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
const bar = 1
|
const bar = 1
|
||||||
export { bar as default }
|
export { bar as default }
|
||||||
</script>`)
|
</script>`)
|
||||||
).toThrow(`Cannot export locally defined variable as default`)
|
).toThrow(moduleErrorMsg)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export default referencing local var', () => {
|
test('ref: non-assignment expressions', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
const bar = 1
|
ref: a = 1, foo()
|
||||||
export default {
|
</script>`)
|
||||||
props: {
|
).toThrow(`ref: statements can only contain assignment expressions`)
|
||||||
foo: {
|
})
|
||||||
default: () => bar
|
|
||||||
}
|
test('defineOptions() w/ both type and non-type args', () => {
|
||||||
|
expect(() => {
|
||||||
|
compile(`<script setup lang="ts">
|
||||||
|
import { defineOptions } from 'vue'
|
||||||
|
defineOptions<{}>({})
|
||||||
|
</script>`)
|
||||||
|
}).toThrow(`cannot accept both type and non-type arguments`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defineOptions() referencing local var', () => {
|
||||||
|
expect(() =>
|
||||||
|
compile(`<script setup>
|
||||||
|
import { defineOptions } from 'vue'
|
||||||
|
const bar = 1
|
||||||
|
defineOptions({
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
default: () => bar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
</script>`)
|
</script>`)
|
||||||
).toThrow(`cannot reference locally declared variables`)
|
).toThrow(`cannot reference locally declared variables`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('export default referencing exports', () => {
|
test('defineOptions() referencing ref declarations', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
export const bar = 1
|
import { defineOptions } from 'vue'
|
||||||
export default {
|
ref: bar = 1
|
||||||
props: bar
|
defineOptions({
|
||||||
}
|
props: { bar }
|
||||||
|
})
|
||||||
</script>`)
|
</script>`)
|
||||||
).toThrow(`cannot reference locally declared variables`)
|
).toThrow(`cannot reference locally declared variables`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should allow export default referencing scope var', () => {
|
test('should allow defineOptions() referencing scope var', () => {
|
||||||
assertCode(
|
assertCode(
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
|
import { defineOptions } from 'vue'
|
||||||
const bar = 1
|
const bar = 1
|
||||||
export default {
|
defineOptions({
|
||||||
props: {
|
props: {
|
||||||
foo: {
|
foo: {
|
||||||
default: bar => bar + 1
|
default: bar => bar + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>`).content
|
</script>`).content
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should allow export default referencing imported binding', () => {
|
test('should allow defineOptions() referencing imported binding', () => {
|
||||||
assertCode(
|
assertCode(
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
|
import { defineOptions } from 'vue'
|
||||||
import { bar } from './bar'
|
import { bar } from './bar'
|
||||||
export { bar }
|
defineOptions({
|
||||||
export default {
|
|
||||||
props: {
|
props: {
|
||||||
foo: {
|
foo: {
|
||||||
default: () => bar
|
default: () => bar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>`).content
|
</script>`).content
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should allow export default referencing re-exported binding', () => {
|
|
||||||
assertCode(
|
|
||||||
compile(`<script setup>
|
|
||||||
export { bar } from './bar'
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
foo: {
|
|
||||||
default: () => bar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>`).content
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('error on duplicated default export', () => {
|
|
||||||
expect(() =>
|
|
||||||
compile(`
|
|
||||||
<script>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
).toThrow(`Default export is already declared`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
compile(`
|
|
||||||
<script>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
const x = {}
|
|
||||||
export { x as default }
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
).toThrow(`Default export is already declared`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
compile(`
|
|
||||||
<script>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
export { x as default } from './y'
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
).toThrow(`Default export is already declared`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
compile(`
|
|
||||||
<script>
|
|
||||||
export { x as default } from './y'
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
).toThrow(`Default export is already declared`)
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
compile(`
|
|
||||||
<script>
|
|
||||||
const x = {}
|
|
||||||
export { x as default }
|
|
||||||
</script>
|
|
||||||
<script setup>
|
|
||||||
export default {}
|
|
||||||
</script>
|
|
||||||
`)
|
|
||||||
).toThrow(`Default export is already declared`)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -725,11 +903,12 @@ describe('SFC analyze <script> bindings', () => {
|
|||||||
it('works for script setup', () => {
|
it('works for script setup', () => {
|
||||||
const { bindings } = compile(`
|
const { bindings } = compile(`
|
||||||
<script setup>
|
<script setup>
|
||||||
export default {
|
import { defineOptions } from 'vue'
|
||||||
props: {
|
defineOptions({
|
||||||
foo: String,
|
props: {
|
||||||
},
|
foo: String,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
`)
|
`)
|
||||||
expect(bindings).toStrictEqual({
|
expect(bindings).toStrictEqual({
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -30,6 +30,7 @@ export interface TemplateCompiler {
|
|||||||
|
|
||||||
export interface SFCTemplateCompileResults {
|
export interface SFCTemplateCompileResults {
|
||||||
code: string
|
code: string
|
||||||
|
preamble?: string
|
||||||
source: string
|
source: string
|
||||||
tips: string[]
|
tips: string[]
|
||||||
errors: (string | CompilerError)[]
|
errors: (string | CompilerError)[]
|
||||||
@ -168,7 +169,7 @@ function doCompileTemplate({
|
|||||||
nodeTransforms = [transformAssetUrl, transformSrcset]
|
nodeTransforms = [transformAssetUrl, transformSrcset]
|
||||||
}
|
}
|
||||||
|
|
||||||
let { code, map } = compiler.compile(source, {
|
let { code, preamble, map } = compiler.compile(source, {
|
||||||
mode: 'module',
|
mode: 'module',
|
||||||
prefixIdentifiers: true,
|
prefixIdentifiers: true,
|
||||||
hoistStatic: true,
|
hoistStatic: true,
|
||||||
@ -192,7 +193,7 @@ function doCompileTemplate({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { code, source, errors, tips: [], map }
|
return { code, preamble, source, errors, tips: [], map }
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
|
function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
|
||||||
|
@ -10,10 +10,12 @@ import { SFCDescriptor } from './parse'
|
|||||||
import { rewriteDefault } from './rewriteDefault'
|
import { rewriteDefault } from './rewriteDefault'
|
||||||
import { ParserPlugin } from '@babel/parser'
|
import { ParserPlugin } from '@babel/parser'
|
||||||
|
|
||||||
|
export const CSS_VARS_HELPER = `useCssVars`
|
||||||
|
|
||||||
export function genCssVarsCode(
|
export function genCssVarsCode(
|
||||||
varsExp: string,
|
varsExp: string,
|
||||||
scoped: boolean,
|
scoped: boolean,
|
||||||
knownBindings?: Record<string, boolean>
|
knownBindings?: Record<string, any>
|
||||||
) {
|
) {
|
||||||
const exp = createSimpleExpression(varsExp, false)
|
const exp = createSimpleExpression(varsExp, false)
|
||||||
const context = createTransformContext(createRoot([]), {
|
const context = createTransformContext(createRoot([]), {
|
||||||
@ -38,7 +40,7 @@ export function genCssVarsCode(
|
|||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|
||||||
return `__useCssVars__(_ctx => (${transformedString})${
|
return `_${CSS_VARS_HELPER}(_ctx => (${transformedString})${
|
||||||
scoped ? `, true` : ``
|
scoped ? `, true` : ``
|
||||||
})`
|
})`
|
||||||
}
|
}
|
||||||
@ -65,7 +67,7 @@ export function injectCssVarsCalls(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
script +
|
script +
|
||||||
`\nimport { useCssVars as __useCssVars__ } from 'vue'\n` +
|
`\nimport { ${CSS_VARS_HELPER} as _${CSS_VARS_HELPER} } from 'vue'\n` +
|
||||||
`const __injectCSSVars__ = () => {\n${calls}}\n` +
|
`const __injectCSSVars__ = () => {\n${calls}}\n` +
|
||||||
`const __setup__ = __default__.setup\n` +
|
`const __setup__ = __default__.setup\n` +
|
||||||
`__default__.setup = __setup__\n` +
|
`__default__.setup = __setup__\n` +
|
||||||
|
144
packages/runtime-core/__tests__/apiExpose.spec.ts
Normal file
144
packages/runtime-core/__tests__/apiExpose.spec.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { nodeOps, render } from '@vue/runtime-test'
|
||||||
|
import { defineComponent, h, ref } from '../src'
|
||||||
|
|
||||||
|
describe('api: expose', () => {
|
||||||
|
test('via setup context', () => {
|
||||||
|
const Child = defineComponent({
|
||||||
|
render() {},
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
foo: ref(1),
|
||||||
|
bar: ref(2)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
bar: ref(3),
|
||||||
|
baz: ref(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const childRef = ref()
|
||||||
|
const Parent = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Child, { ref: childRef })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(childRef.value).toBeTruthy()
|
||||||
|
expect(childRef.value.foo).toBe(1)
|
||||||
|
expect(childRef.value.bar).toBe(2)
|
||||||
|
expect(childRef.value.baz).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('via options', () => {
|
||||||
|
const Child = defineComponent({
|
||||||
|
render() {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
foo: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
bar: ref(2),
|
||||||
|
baz: ref(3)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expose: ['foo', 'bar']
|
||||||
|
})
|
||||||
|
|
||||||
|
const childRef = ref()
|
||||||
|
const Parent = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Child, { ref: childRef })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(childRef.value).toBeTruthy()
|
||||||
|
expect(childRef.value.foo).toBe(1)
|
||||||
|
expect(childRef.value.bar).toBe(2)
|
||||||
|
expect(childRef.value.baz).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('options + context', () => {
|
||||||
|
const Child = defineComponent({
|
||||||
|
render() {},
|
||||||
|
expose: ['foo'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
foo: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
bar: ref(2)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
bar: ref(3),
|
||||||
|
baz: ref(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const childRef = ref()
|
||||||
|
const Parent = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Child, { ref: childRef })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(childRef.value).toBeTruthy()
|
||||||
|
expect(childRef.value.foo).toBe(1)
|
||||||
|
expect(childRef.value.bar).toBe(2)
|
||||||
|
expect(childRef.value.baz).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('options: empty', () => {
|
||||||
|
const Child = defineComponent({
|
||||||
|
render() {},
|
||||||
|
expose: [],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
foo: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const childRef = ref()
|
||||||
|
const Parent = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Child, { ref: childRef })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(childRef.value).toBeTruthy()
|
||||||
|
expect('foo' in childRef.value).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('options: empty + setup context', () => {
|
||||||
|
const Child = defineComponent({
|
||||||
|
render() {},
|
||||||
|
expose: [],
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
foo: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const childRef = ref()
|
||||||
|
const Parent = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Child, { ref: childRef })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(childRef.value).toBeTruthy()
|
||||||
|
expect(childRef.value.foo).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
91
packages/runtime-core/src/apiDefineOptions.ts
Normal file
91
packages/runtime-core/src/apiDefineOptions.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { EmitFn, EmitsOptions } from './componentEmits'
|
||||||
|
import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
|
||||||
|
import { Slots } from './componentSlots'
|
||||||
|
import { Directive } from './directives'
|
||||||
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
interface DefaultContext {
|
||||||
|
props: {}
|
||||||
|
attrs: Record<string, unknown>
|
||||||
|
emit: (...args: any[]) => void
|
||||||
|
slots: Slots
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InferredContext<P, E> {
|
||||||
|
props: Readonly<P>
|
||||||
|
attrs: Record<string, unknown>
|
||||||
|
emit: EmitFn<E>
|
||||||
|
slots: Slots
|
||||||
|
}
|
||||||
|
|
||||||
|
type InferContext<T extends Partial<DefaultContext>, P, E> = {
|
||||||
|
[K in keyof DefaultContext]: T[K] extends {} ? T[K] : InferredContext<P, E>[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a subset of full options that are still useful in the context of
|
||||||
|
* <script setup>. Technically, other options can be used too, but are
|
||||||
|
* discouraged - if using TypeScript, we nudge users away from doing so by
|
||||||
|
* disallowing them in types.
|
||||||
|
*/
|
||||||
|
interface Options<E extends EmitsOptions, EE extends string> {
|
||||||
|
emits?: E | EE[]
|
||||||
|
name?: string
|
||||||
|
inhertiAttrs?: boolean
|
||||||
|
directives?: Record<string, Directive>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile-time-only helper used for declaring options and retrieving props
|
||||||
|
* and the setup context inside `<script setup>`.
|
||||||
|
* This is stripped away in the compiled code and should never be actually
|
||||||
|
* called at runtime.
|
||||||
|
*/
|
||||||
|
// overload 1: no props
|
||||||
|
export function defineOptions<
|
||||||
|
T extends Partial<DefaultContext> = {},
|
||||||
|
E extends EmitsOptions = EmitsOptions,
|
||||||
|
EE extends string = string
|
||||||
|
>(
|
||||||
|
options?: Options<E, EE> & {
|
||||||
|
props?: undefined
|
||||||
|
}
|
||||||
|
): InferContext<T, {}, E>
|
||||||
|
|
||||||
|
// overload 2: object props
|
||||||
|
export function defineOptions<
|
||||||
|
T extends Partial<DefaultContext> = {},
|
||||||
|
E extends EmitsOptions = EmitsOptions,
|
||||||
|
EE extends string = string,
|
||||||
|
PP extends string = string,
|
||||||
|
P = Readonly<{ [key in PP]?: any }>
|
||||||
|
>(
|
||||||
|
options?: Options<E, EE> & {
|
||||||
|
props?: PP[]
|
||||||
|
}
|
||||||
|
): InferContext<T, P, E>
|
||||||
|
|
||||||
|
// overload 3: object props
|
||||||
|
export function defineOptions<
|
||||||
|
T extends Partial<DefaultContext> = {},
|
||||||
|
E extends EmitsOptions = EmitsOptions,
|
||||||
|
EE extends string = string,
|
||||||
|
PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions,
|
||||||
|
P = ExtractPropTypes<PP>
|
||||||
|
>(
|
||||||
|
options?: Options<E, EE> & {
|
||||||
|
props?: PP
|
||||||
|
}
|
||||||
|
): InferContext<T, P, E>
|
||||||
|
|
||||||
|
// implementation
|
||||||
|
export function defineOptions() {
|
||||||
|
if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`defineContext() is a compiler-hint helper that is only usable inside ` +
|
||||||
|
`<script setup> of a single file component. It will be compiled away ` +
|
||||||
|
`and should not be used in final distributed code.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return 0 as any
|
||||||
|
}
|
@ -105,7 +105,7 @@ export interface ComponentInternalOptions {
|
|||||||
export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
|
export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
|
||||||
extends ComponentInternalOptions {
|
extends ComponentInternalOptions {
|
||||||
// use of any here is intentional so it can be a valid JSX Element constructor
|
// use of any here is intentional so it can be a valid JSX Element constructor
|
||||||
(props: P, ctx: SetupContext<E>): any
|
(props: P, ctx: Omit<SetupContext<E, P>, 'expose'>): any
|
||||||
props?: ComponentPropsOptions<P>
|
props?: ComponentPropsOptions<P>
|
||||||
emits?: E | (keyof E)[]
|
emits?: E | (keyof E)[]
|
||||||
inheritAttrs?: boolean
|
inheritAttrs?: boolean
|
||||||
@ -167,10 +167,12 @@ export const enum LifecycleHooks {
|
|||||||
ERROR_CAPTURED = 'ec'
|
ERROR_CAPTURED = 'ec'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetupContext<E = EmitsOptions> {
|
export interface SetupContext<E = EmitsOptions, P = Data> {
|
||||||
|
props: P
|
||||||
attrs: Data
|
attrs: Data
|
||||||
slots: Slots
|
slots: Slots
|
||||||
emit: EmitFn<E>
|
emit: EmitFn<E>
|
||||||
|
expose: (exposed: Record<string, any>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -270,6 +272,9 @@ export interface ComponentInternalInstance {
|
|||||||
// main proxy that serves as the public instance (`this`)
|
// main proxy that serves as the public instance (`this`)
|
||||||
proxy: ComponentPublicInstance | null
|
proxy: ComponentPublicInstance | null
|
||||||
|
|
||||||
|
// exposed properties via expose()
|
||||||
|
exposed: Record<string, any> | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* alternative proxy used only for runtime-compiled render functions using
|
* alternative proxy used only for runtime-compiled render functions using
|
||||||
* `with` block
|
* `with` block
|
||||||
@ -415,6 +420,7 @@ export function createComponentInstance(
|
|||||||
update: null!, // will be set synchronously right after creation
|
update: null!, // will be set synchronously right after creation
|
||||||
render: null,
|
render: null,
|
||||||
proxy: null,
|
proxy: null,
|
||||||
|
exposed: null,
|
||||||
withProxy: null,
|
withProxy: null,
|
||||||
effects: null,
|
effects: null,
|
||||||
provides: parent ? parent.provides : Object.create(appContext.provides),
|
provides: parent ? parent.provides : Object.create(appContext.provides),
|
||||||
@ -731,10 +737,20 @@ const attrHandlers: ProxyHandler<Data> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createSetupContext(instance: ComponentInternalInstance): SetupContext {
|
function createSetupContext(instance: ComponentInternalInstance): SetupContext {
|
||||||
|
const expose: SetupContext['expose'] = exposed => {
|
||||||
|
if (__DEV__ && instance.exposed) {
|
||||||
|
warn(`expose() should be called only once per setup().`)
|
||||||
|
}
|
||||||
|
instance.exposed = proxyRefs(exposed)
|
||||||
|
}
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
// We use getters in dev in case libs like test-utils overwrite instance
|
// We use getters in dev in case libs like test-utils overwrite instance
|
||||||
// properties (overwrites should not be done in prod)
|
// properties (overwrites should not be done in prod)
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
|
get props() {
|
||||||
|
return instance.props
|
||||||
|
},
|
||||||
get attrs() {
|
get attrs() {
|
||||||
return new Proxy(instance.attrs, attrHandlers)
|
return new Proxy(instance.attrs, attrHandlers)
|
||||||
},
|
},
|
||||||
@ -743,13 +759,16 @@ function createSetupContext(instance: ComponentInternalInstance): SetupContext {
|
|||||||
},
|
},
|
||||||
get emit() {
|
get emit() {
|
||||||
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
||||||
}
|
},
|
||||||
|
expose
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
props: instance.props,
|
||||||
attrs: instance.attrs,
|
attrs: instance.attrs,
|
||||||
slots: instance.slots,
|
slots: instance.slots,
|
||||||
emit: instance.emit
|
emit: instance.emit,
|
||||||
|
expose
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,9 @@ import {
|
|||||||
reactive,
|
reactive,
|
||||||
ComputedGetter,
|
ComputedGetter,
|
||||||
WritableComputedOptions,
|
WritableComputedOptions,
|
||||||
toRaw
|
toRaw,
|
||||||
|
proxyRefs,
|
||||||
|
toRef
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
ComponentObjectPropsOptions,
|
ComponentObjectPropsOptions,
|
||||||
@ -96,7 +98,7 @@ export interface ComponentOptionsBase<
|
|||||||
setup?: (
|
setup?: (
|
||||||
this: void,
|
this: void,
|
||||||
props: Props,
|
props: Props,
|
||||||
ctx: SetupContext<E>
|
ctx: SetupContext<E, Props>
|
||||||
) => Promise<RawBindings> | RawBindings | RenderFunction | void
|
) => Promise<RawBindings> | RawBindings | RenderFunction | void
|
||||||
name?: string
|
name?: string
|
||||||
template?: string | object // can be a direct DOM node
|
template?: string | object // can be a direct DOM node
|
||||||
@ -110,6 +112,8 @@ export interface ComponentOptionsBase<
|
|||||||
directives?: Record<string, Directive>
|
directives?: Record<string, Directive>
|
||||||
inheritAttrs?: boolean
|
inheritAttrs?: boolean
|
||||||
emits?: (E | EE[]) & ThisType<void>
|
emits?: (E | EE[]) & ThisType<void>
|
||||||
|
// TODO infer public instance type based on exposed keys
|
||||||
|
expose?: string[]
|
||||||
serverPrefetch?(): Promise<any>
|
serverPrefetch?(): Promise<any>
|
||||||
|
|
||||||
// Internal ------------------------------------------------------------------
|
// Internal ------------------------------------------------------------------
|
||||||
@ -461,7 +465,9 @@ export function applyOptions(
|
|||||||
render,
|
render,
|
||||||
renderTracked,
|
renderTracked,
|
||||||
renderTriggered,
|
renderTriggered,
|
||||||
errorCaptured
|
errorCaptured,
|
||||||
|
// public API
|
||||||
|
expose
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const publicThis = instance.proxy!
|
const publicThis = instance.proxy!
|
||||||
@ -736,6 +742,21 @@ export function applyOptions(
|
|||||||
if (unmounted) {
|
if (unmounted) {
|
||||||
onUnmounted(unmounted.bind(publicThis))
|
onUnmounted(unmounted.bind(publicThis))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isArray(expose)) {
|
||||||
|
if (!asMixin) {
|
||||||
|
if (expose.length) {
|
||||||
|
const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
|
||||||
|
expose.forEach(key => {
|
||||||
|
exposed[key] = toRef(publicThis, key as any)
|
||||||
|
})
|
||||||
|
} else if (!instance.exposed) {
|
||||||
|
instance.exposed = EMPTY_OBJ
|
||||||
|
}
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn(`The \`expose\` option is ignored when used in mixins.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function callSyncHook(
|
function callSyncHook(
|
||||||
|
@ -95,6 +95,7 @@ export function renderComponentRoot(
|
|||||||
props,
|
props,
|
||||||
__DEV__
|
__DEV__
|
||||||
? {
|
? {
|
||||||
|
props,
|
||||||
get attrs() {
|
get attrs() {
|
||||||
markAttrsAccessed()
|
markAttrsAccessed()
|
||||||
return attrs
|
return attrs
|
||||||
@ -102,7 +103,7 @@ export function renderComponentRoot(
|
|||||||
slots,
|
slots,
|
||||||
emit
|
emit
|
||||||
}
|
}
|
||||||
: { attrs, slots, emit }
|
: { props, attrs, slots, emit }
|
||||||
)
|
)
|
||||||
: render(props, null as any /* we know it doesn't need it */)
|
: render(props, null as any /* we know it doesn't need it */)
|
||||||
)
|
)
|
||||||
|
@ -43,6 +43,7 @@ export { provide, inject } from './apiInject'
|
|||||||
export { nextTick } from './scheduler'
|
export { nextTick } from './scheduler'
|
||||||
export { defineComponent } from './apiDefineComponent'
|
export { defineComponent } from './apiDefineComponent'
|
||||||
export { defineAsyncComponent } from './apiAsyncComponent'
|
export { defineAsyncComponent } from './apiAsyncComponent'
|
||||||
|
export { defineOptions } from './apiDefineOptions'
|
||||||
|
|
||||||
// Advanced API ----------------------------------------------------------------
|
// Advanced API ----------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -306,12 +306,12 @@ export const setRef = (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let value: ComponentPublicInstance | RendererNode | null
|
let value: ComponentPublicInstance | RendererNode | Record<string, any> | null
|
||||||
if (!vnode) {
|
if (!vnode) {
|
||||||
value = null
|
value = null
|
||||||
} else {
|
} else {
|
||||||
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
||||||
value = vnode.component!.proxy
|
value = vnode.component!.exposed || vnode.component!.proxy
|
||||||
} else {
|
} else {
|
||||||
value = vnode.el
|
value = vnode.el
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { h, reactive, createApp, ref } from 'vue'
|
import { h, reactive, createApp, ref } from 'vue'
|
||||||
import { CompilerOptions } from '@vue/compiler-dom'
|
import { CompilerOptions } from '@vue/compiler-dom'
|
||||||
|
import { BindingTypes } from '@vue/compiler-core'
|
||||||
|
|
||||||
export const ssrMode = ref(false)
|
export const ssrMode = ref(false)
|
||||||
|
|
||||||
@ -12,9 +13,9 @@ export const compilerOptions: CompilerOptions = reactive({
|
|||||||
scopeId: null,
|
scopeId: null,
|
||||||
ssrCssVars: `{ color }`,
|
ssrCssVars: `{ color }`,
|
||||||
bindingMetadata: {
|
bindingMetadata: {
|
||||||
TestComponent: 'setup',
|
TestComponent: BindingTypes.SETUP,
|
||||||
foo: 'setup',
|
foo: BindingTypes.SETUP,
|
||||||
bar: 'props'
|
bar: BindingTypes.PROPS
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
96
test-dts/defineOptions.test-d.ts
Normal file
96
test-dts/defineOptions.test-d.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { expectType, defineOptions, Slots, describe } from './index'
|
||||||
|
|
||||||
|
describe('no args', () => {
|
||||||
|
const { props, attrs, emit, slots } = defineOptions()
|
||||||
|
expectType<{}>(props)
|
||||||
|
expectType<Record<string, unknown>>(attrs)
|
||||||
|
expectType<(...args: any[]) => void>(emit)
|
||||||
|
expectType<Slots>(slots)
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
props.foo
|
||||||
|
// should be able to emit anything
|
||||||
|
emit('foo')
|
||||||
|
emit('bar')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with type arg', () => {
|
||||||
|
const { props, attrs, emit, slots } = defineOptions<{
|
||||||
|
props: {
|
||||||
|
foo: string
|
||||||
|
}
|
||||||
|
emit: (e: 'change') => void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// explicitly declared type should be refined
|
||||||
|
expectType<string>(props.foo)
|
||||||
|
// @ts-expect-error
|
||||||
|
props.bar
|
||||||
|
|
||||||
|
emit('change')
|
||||||
|
// @ts-expect-error
|
||||||
|
emit()
|
||||||
|
// @ts-expect-error
|
||||||
|
emit('bar')
|
||||||
|
|
||||||
|
// non explicitly declared type should fallback to default type
|
||||||
|
expectType<Record<string, unknown>>(attrs)
|
||||||
|
expectType<Slots>(slots)
|
||||||
|
})
|
||||||
|
|
||||||
|
// with runtime arg
|
||||||
|
describe('with runtime arg (array syntax)', () => {
|
||||||
|
const { props, emit } = defineOptions({
|
||||||
|
props: ['foo', 'bar'],
|
||||||
|
emits: ['foo', 'bar']
|
||||||
|
})
|
||||||
|
|
||||||
|
expectType<{
|
||||||
|
foo?: any
|
||||||
|
bar?: any
|
||||||
|
}>(props)
|
||||||
|
// @ts-expect-error
|
||||||
|
props.baz
|
||||||
|
|
||||||
|
emit('foo')
|
||||||
|
emit('bar', 123)
|
||||||
|
// @ts-expect-error
|
||||||
|
emit('baz')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with runtime arg (object syntax)', () => {
|
||||||
|
const { props, emit } = defineOptions({
|
||||||
|
props: {
|
||||||
|
foo: String,
|
||||||
|
bar: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: {
|
||||||
|
foo: () => {},
|
||||||
|
bar: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expectType<{
|
||||||
|
foo?: string
|
||||||
|
bar: number
|
||||||
|
baz: unknown[]
|
||||||
|
}>(props)
|
||||||
|
|
||||||
|
props.foo && props.foo + 'bar'
|
||||||
|
props.bar + 1
|
||||||
|
// @ts-expect-error should be readonly
|
||||||
|
props.bar++
|
||||||
|
props.baz.push(1)
|
||||||
|
|
||||||
|
emit('foo')
|
||||||
|
emit('bar')
|
||||||
|
// @ts-expect-error
|
||||||
|
emit('baz')
|
||||||
|
})
|
@ -7,7 +7,11 @@
|
|||||||
"vue": ["../packages/vue/dist"]
|
"vue": ["../packages/vue/dist"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["../packages/*/__tests__", "../packages/*/src"],
|
"exclude": [
|
||||||
|
"../packages/*/__tests__",
|
||||||
|
"../packages/*/src",
|
||||||
|
"../packages/template-explorer"
|
||||||
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"../packages/global.d.ts",
|
"../packages/global.d.ts",
|
||||||
"../packages/*/dist",
|
"../packages/*/dist",
|
||||||
|
@ -4,5 +4,5 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"declaration": true
|
"declaration": true
|
||||||
},
|
},
|
||||||
"exclude": ["../packages/*/__tests__"]
|
"exclude": ["../packages/*/__tests__", "../packages/template-explorer"]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user