Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/corePublic

Commit7546248

Browse files
committed
fix(compiler-sfc): demote const reactive bindings used in v-model
1 parentd8a2de4 commit7546248

File tree

7 files changed

+230
-1
lines changed

7 files changed

+230
-1
lines changed

‎packages/compiler-core/__tests__/transforms/vModel.spec.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,5 +582,22 @@ describe('compiler: transform v-model', () => {
582582
}),
583583
)
584584
})
585+
586+
test('used on const binding',()=>{
587+
constonError=vi.fn()
588+
parseWithVModel('<div v-model="c" />',{
589+
onError,
590+
bindingMetadata:{
591+
c:BindingTypes.LITERAL_CONST,
592+
},
593+
})
594+
595+
expect(onError).toHaveBeenCalledTimes(1)
596+
expect(onError).toHaveBeenCalledWith(
597+
expect.objectContaining({
598+
code:ErrorCodes.X_V_MODEL_ON_CONST,
599+
}),
600+
)
601+
})
585602
})
586603
})

‎packages/compiler-core/src/errors.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export enum ErrorCodes {
8888
X_V_MODEL_MALFORMED_EXPRESSION,
8989
X_V_MODEL_ON_SCOPE_VARIABLE,
9090
X_V_MODEL_ON_PROPS,
91+
X_V_MODEL_ON_CONST,
9192
X_INVALID_EXPRESSION,
9293
X_KEEP_ALIVE_INVALID_CHILDREN,
9394

@@ -176,6 +177,7 @@ export const errorMessages: Record<ErrorCodes, string> = {
176177
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]:`v-model value must be a valid JavaScript member expression.`,
177178
[ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE]:`v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,
178179
[ErrorCodes.X_V_MODEL_ON_PROPS]:`v-model cannot be used on a prop, because local prop bindings are not writable.\nUse a v-bind binding combined with a v-on listener that emits update:x event instead.`,
180+
[ErrorCodes.X_V_MODEL_ON_CONST]:`v-model cannot be used on a const binding because it is not writable.`,
179181
[ErrorCodes.X_INVALID_EXPRESSION]:`Error parsing JavaScript expression: `,
180182
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]:`<KeepAlive> expects exactly one child component.`,
181183
[ErrorCodes.X_VNODE_HOOKS]:`@vnode-* hooks in templates are no longer supported. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support has been removed in 3.4.`,

‎packages/compiler-core/src/transforms/vModel.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
4848
returncreateTransformProps()
4949
}
5050

51+
// const bindings are not writable.
52+
if(
53+
bindingType===BindingTypes.LITERAL_CONST||
54+
bindingType===BindingTypes.SETUP_CONST
55+
){
56+
context.onError(createCompilerError(ErrorCodes.X_V_MODEL_ON_CONST,exp.loc))
57+
returncreateTransformProps()
58+
}
59+
5160
constmaybeRef=
5261
!__BROWSER__&&
5362
context.inline&&

‎packages/compiler-dom/src/errors.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function createDOMCompilerError(
2121
}
2222

2323
exportenumDOMErrorCodes{
24-
X_V_HTML_NO_EXPRESSION=53/* ErrorCodes.__EXTEND_POINT__ */,
24+
X_V_HTML_NO_EXPRESSION=54/* ErrorCodes.__EXTEND_POINT__ */,
2525
X_V_HTML_WITH_CHILDREN,
2626
X_V_TEXT_NO_EXPRESSION,
2727
X_V_TEXT_WITH_CHILDREN,

‎packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,44 @@ return { foo, bar, baz, y, z }
639639
}"
640640
`;
641641
642+
exports[`SFC compile <scriptsetup> > demote const reactive binding to let when used in v-model (inlineTemplate) 1`] = `
643+
"import{unrefas_unref,resolveComponentas_resolveComponent,isRefas_isRef,openBlockas_openBlock,createBlockas_createBlock} from "vue"
644+
645+
import{reactive} from 'vue'
646+
647+
export default{
648+
setup(__props) {
649+
650+
let name = reactive({first: 'john',last: 'doe' })
651+
652+
return (_ctx,_cache) => {
653+
const _component_MyComponent=_resolveComponent("MyComponent")
654+
655+
return (_openBlock(),_createBlock(_component_MyComponent, {
656+
modelValue:_unref(name),
657+
"onUpdate:modelValue":_cache[0]|| (_cache[0]=$event=> (_isRef(name)? (name).value=$event:name=$event))
658+
},null,8/* PROPS*/, ["modelValue"]))
659+
}
660+
}
661+
662+
}"
663+
`;
664+
665+
exports[`SFC compile <scriptsetup> > demote const reactive binding to let when used in v-model 1`] = `
666+
"import{reactive} from 'vue'
667+
668+
export default{
669+
setup(__props, { expose:__expose }) {
670+
__expose();
671+
672+
let name = reactive({first: 'john',last: 'doe' })
673+
674+
return {getname() {returnname },setname(v) {name=v },reactive }
675+
}
676+
677+
}"
678+
`;
679+
642680
exports[`SFC compile <scriptsetup> > errors > should allow defineProps/Emit() referencing imported binding 1`] = `
643681
"import{bar} from './bar'
644682

‎packages/compiler-sfc/__tests__/compileScript.spec.ts‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import{vi}from'vitest'
12
import{BindingTypes}from'@vue/compiler-core'
23
import{
34
assertCode,
@@ -7,6 +8,15 @@ import {
78
}from'./utils'
89
import{typeRawSourceMap,SourceMapConsumer}from'source-map-js'
910

11+
vi.mock('../src/warn',()=>({
12+
warn:vi.fn(),
13+
warnOnce:vi.fn(),
14+
}))
15+
16+
import{warnOnce}from'../src/warn'
17+
18+
constwarnOnceMock=vi.mocked(warnOnce)
19+
1020
describe('SFC compile <script setup>',()=>{
1121
test('should compile JS syntax',()=>{
1222
const{ content}=compile(`
@@ -74,6 +84,77 @@ describe('SFC compile <script setup>', () => {
7484
assertCode(content)
7585
})
7686

87+
test('demote const reactive binding to let when used in v-model',()=>{
88+
warnOnceMock.mockClear()
89+
const{ content, bindings}=compile(`
90+
<script setup>
91+
import { reactive } from 'vue'
92+
const name = reactive({ first: 'john', last: 'doe' })
93+
</script>
94+
95+
<template>
96+
<MyComponent v-model="name" />
97+
</template>
98+
`)
99+
100+
expect(content).toMatch(
101+
`let name = reactive({ first: 'john', last: 'doe' })`,
102+
)
103+
expect(bindings!.name).toBe(BindingTypes.SETUP_LET)
104+
expect(warnOnceMock).toHaveBeenCalledTimes(1)
105+
expect(warnOnceMock).toHaveBeenCalledWith(
106+
expect.stringContaining(
107+
'`v-model` cannot update a `const` reactive binding',
108+
),
109+
)
110+
assertCode(content)
111+
})
112+
113+
test('demote const reactive binding to let when used in v-model (inlineTemplate)',()=>{
114+
warnOnceMock.mockClear()
115+
const{ content, bindings}=compile(
116+
`
117+
<script setup>
118+
import { reactive } from 'vue'
119+
const name = reactive({ first: 'john', last: 'doe' })
120+
</script>
121+
122+
<template>
123+
<MyComponent v-model="name" />
124+
</template>
125+
`,
126+
{inlineTemplate:true},
127+
)
128+
129+
expect(content).toMatch(
130+
`let name = reactive({ first: 'john', last: 'doe' })`,
131+
)
132+
expect(bindings!.name).toBe(BindingTypes.SETUP_LET)
133+
expect(warnOnceMock).toHaveBeenCalledTimes(1)
134+
expect(warnOnceMock).toHaveBeenCalledWith(
135+
expect.stringContaining(
136+
'`v-model` cannot update a `const` reactive binding',
137+
),
138+
)
139+
assertCode(content)
140+
})
141+
142+
test('v-model should error on literal const bindings',()=>{
143+
expect(()=>
144+
compile(
145+
`
146+
<script setup>
147+
const foo = 1
148+
</script>
149+
<template>
150+
<input v-model="foo" />
151+
</template>
152+
`,
153+
{inlineTemplate:true},
154+
),
155+
).toThrow('v-model cannot be used on a const binding')
156+
})
157+
77158
describe('<script> and <script setup> co-usage',()=>{
78159
test('script first',()=>{
79160
const{ content}=compile(`

‎packages/compiler-sfc/src/compileScript.ts‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import{
22
BindingTypes,
3+
NodeTypes,
4+
typeTemplateChildNode,
35
UNREF,
46
isFunctionType,
7+
isSimpleIdentifier,
58
unwrapTSNode,
69
walkIdentifiers,
710
}from'@vue/compiler-dom'
@@ -138,6 +141,36 @@ export interface SFCScriptCompileOptions {
138141
customElement?:boolean|((filename:string)=>boolean)
139142
}
140143

144+
functionresolveTemplateVModelIdentifiers(sfc:SFCDescriptor):Set<string>{
145+
constids=newSet<string>()
146+
consttemplate=sfc.template
147+
if(!template?.ast)returnids
148+
149+
template.ast.children.forEach(walk)
150+
151+
functionwalk(node:TemplateChildNode){
152+
switch(node.type){
153+
caseNodeTypes.ELEMENT:
154+
for(leti=0;i<node.props.length;i++){
155+
constprop=node.props[i]
156+
if(prop.type===NodeTypes.DIRECTIVE&&prop.name==='model'){
157+
constexp=prop.exp
158+
if(exp&&exp.type===NodeTypes.SIMPLE_EXPRESSION){
159+
constexpString=exp.content.trim()
160+
if(isSimpleIdentifier(expString)&&expString!=='undefined'){
161+
ids.add(expString)
162+
}
163+
}
164+
}
165+
}
166+
node.children.forEach(walk)
167+
break
168+
}
169+
}
170+
171+
returnids
172+
}
173+
141174
exportinterfaceImportBinding{
142175
isType:boolean
143176
imported:string
@@ -760,6 +793,55 @@ export function compileScript(
760793
ctx.bindingMetadata[key]=setupBindings[key]
761794
}
762795

796+
// #11265, https://github.com/vitejs/rolldown-vite/issues/432
797+
// 6.1 demote `const foo = reactive()` to `let` when used as v-model target.
798+
// In non-inline template compilation, v-model assigns via `$setup.foo = $event`,
799+
// which requires a SETUP_LET binding (getter + setter) to keep script state in sync.
800+
// In inline mode, it generates `foo = $event`, which also requires `let`.
801+
if(sfc.template&&!sfc.template.src&&sfc.template.ast){
802+
constvModelIds=resolveTemplateVModelIdentifiers(sfc)
803+
if(vModelIds.size){
804+
consttoDemote=newSet<string>()
805+
for(constidofvModelIds){
806+
if(setupBindings[id]===BindingTypes.SETUP_REACTIVE_CONST){
807+
toDemote.add(id)
808+
}
809+
}
810+
811+
if(toDemote.size){
812+
for(constnodeofscriptSetupAst.body){
813+
if(
814+
node.type==='VariableDeclaration'&&
815+
node.kind==='const'&&
816+
!node.declare
817+
){
818+
constdemotedInDecl:string[]=[]
819+
for(constdeclofnode.declarations){
820+
if(decl.id.type==='Identifier'&&toDemote.has(decl.id.name)){
821+
demotedInDecl.push(decl.id.name)
822+
}
823+
}
824+
if(demotedInDecl.length){
825+
ctx.s.overwrite(
826+
node.start!+startOffset,
827+
node.start!+startOffset+'const'.length,
828+
'let',
829+
)
830+
for(constidofdemotedInDecl){
831+
setupBindings[id]=BindingTypes.SETUP_LET
832+
ctx.bindingMetadata[id]=BindingTypes.SETUP_LET
833+
warnOnce(
834+
`\`v-model\` cannot update a \`const\` reactive binding \`${id}\`. `+
835+
`The compiler has transformed it to \`let\` to make the update work.`,
836+
)
837+
}
838+
}
839+
}
840+
}
841+
}
842+
}
843+
}
844+
763845
// 7. inject `useCssVars` calls
764846
if(
765847
sfc.cssVars.length&&

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp