wip: generate valid v-model and TS code in script setup inline mode

This commit is contained in:
Evan You 2020-11-17 12:42:58 -05:00
parent 6e870f5b30
commit c15bd6235e
6 changed files with 52 additions and 11 deletions

View File

@ -169,6 +169,10 @@ export interface TransformOptions extends SharedTransformCodegenOptions {
* needed to render inline CSS variables on component root * needed to render inline CSS variables on component root
*/ */
ssrCssVars?: string ssrCssVars?: string
/**
* Indicates that transforms should try to output valid TS code
*/
isTS?: boolean
onError?: (error: CompilerError) => void onError?: (error: CompilerError) => void
} }

View File

@ -30,6 +30,7 @@ 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` : ``) export const UNREF = Symbol(__DEV__ ? `unref` : ``)
export const IS_REF = Symbol(__DEV__ ? `isRef` : ``)
// 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!
@ -64,7 +65,8 @@ export const helperNameMap: any = {
[POP_SCOPE_ID]: `popScopeId`, [POP_SCOPE_ID]: `popScopeId`,
[WITH_SCOPE_ID]: `withScopeId`, [WITH_SCOPE_ID]: `withScopeId`,
[WITH_CTX]: `withCtx`, [WITH_CTX]: `withCtx`,
[UNREF]: `unref` [UNREF]: `unref`,
[IS_REF]: `isRef`
} }
export function registerRuntimeHelpers(helpers: any) { export function registerRuntimeHelpers(helpers: any) {

View File

@ -125,6 +125,7 @@ export function createTransformContext(
ssrCssVars = ``, ssrCssVars = ``,
bindingMetadata = EMPTY_OBJ, bindingMetadata = EMPTY_OBJ,
inline = false, inline = false,
isTS = false,
onError = defaultOnError onError = defaultOnError
}: TransformOptions }: TransformOptions
): TransformContext { ): TransformContext {
@ -144,6 +145,7 @@ export function createTransformContext(
ssrCssVars, ssrCssVars,
bindingMetadata, bindingMetadata,
inline, inline,
isTS,
onError, onError,
// state // state

View File

@ -14,6 +14,7 @@ import {
hasScopeRef, hasScopeRef,
isStaticExp isStaticExp
} from '../utils' } from '../utils'
import { helperNameMap, IS_REF, UNREF } from '../runtimeHelpers'
export const transformModel: DirectiveTransform = (dir, node, context) => { export const transformModel: DirectiveTransform = (dir, node, context) => {
const { exp, arg } = dir const { exp, arg } = dir
@ -24,10 +25,16 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
return createTransformProps() return createTransformProps()
} }
const rawExp = exp.loc.source
const expString = const expString =
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : exp.loc.source exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
if (!isMemberExpression(expString)) { // im SFC <script setup> inline mode, the exp may have been transformed into
// _unref(exp)
const isUnrefExp =
!__BROWSER__ && expString.startsWith(`_${helperNameMap[UNREF]}`)
if (!isMemberExpression(expString) && !isUnrefExp) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc) createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
) )
@ -53,14 +60,25 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
: createCompoundExpression(['"onUpdate:" + ', arg]) : createCompoundExpression(['"onUpdate:" + ', arg])
: `onUpdate:modelValue` : `onUpdate:modelValue`
const assigmentExp = isUnrefExp
? // v-model used on a potentially ref binding in <script setup> inline mode.
// not the most beautiful codegen here but it gets the job done.
createSimpleExpression(
`$event => { if (${context.helperString(IS_REF)}(${rawExp})) {` +
`${rawExp}.value = $event` +
` } else {${context.isTS ? `\n//@ts-ignore\n` : ``}` +
`${rawExp} = $event` +
` }}`,
false,
exp.loc
)
: createCompoundExpression([`$event => (`, exp, ` = $event)`])
const props = [ const props = [
// modelValue: foo // modelValue: foo
createObjectProperty(propName, dir.exp!), createObjectProperty(propName, dir.exp!),
// "onUpdate:modelValue": $event => (foo = $event) // "onUpdate:modelValue": $event => (foo = $event)
createObjectProperty( createObjectProperty(eventName, assigmentExp)
eventName,
createCompoundExpression([`$event => (`, exp, ` = $event)`])
)
] ]
// cache v-model handler if applicable (when it doesn't refer any scope vars) // cache v-model handler if applicable (when it doesn't refer any scope vars)

View File

@ -122,9 +122,13 @@ export const transformOn: DirectiveTransform = (
if (isInlineStatement || (shouldCache && 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)`} => ${ `${
hasMultipleStatements ? `{` : `(` isInlineStatement
}`, ? `$event`
: `${
!__BROWSER__ && context.isTS ? `\n//@ts-ignore\n` : ``
}(...args)`
} => ${hasMultipleStatements ? `{` : `(`}`,
exp, exp,
hasMultipleStatements ? `}` : `)` hasMultipleStatements ? `}` : `)`
]) ])

View File

@ -814,11 +814,12 @@ export function compileScript(
...options.templateOptions, ...options.templateOptions,
filename, filename,
source: sfc.template.content, source: sfc.template.content,
inMap: sfc.template.map,
compilerOptions: { compilerOptions: {
inline: true, inline: true,
isTS,
bindingMetadata bindingMetadata
} }
// TODO source map
}) })
if (tips.length) { if (tips.length) {
tips.forEach(warnOnce) tips.forEach(warnOnce)
@ -827,6 +828,16 @@ export function compileScript(
if (typeof err === 'string') { if (typeof err === 'string') {
throw new Error(err) throw new Error(err)
} else if (err) { } else if (err) {
if (err.loc) {
err.message +=
`\n` +
generateCodeFrame(
source,
err.loc.start.offset,
err.loc.end.offset
) +
`\n`
}
throw err throw err
} }
if (preamble) { if (preamble) {