Skip to content

Commit 60237d9

Browse files
committed
feat: add v-model & v-slot
1 parent 69b325c commit 60237d9

File tree

13 files changed

+620
-8
lines changed

13 files changed

+620
-8
lines changed

playground/App.vue

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import Count2 from './Count.vue'
44
import If from './if.vue'
55
import For from './for.vue'
66
import Slot from './slot.vue'
7+
import Model from './model.vue'
8+
import Show from './show.vue'
79
810
export default defineComponent({
911
setup() {
@@ -33,17 +35,27 @@ export default defineComponent({
3335
3436
<fieldset>
3537
<legend>v-if</legend>
36-
<If></If>
38+
<If />
3739
</fieldset>
3840
3941
<fieldset>
4042
<legend>v-for</legend>
41-
<For></For>
43+
<For />
4244
</fieldset>
4345
4446
<fieldset>
4547
<legend>v-slot</legend>
46-
<Slot></Slot>
48+
<Slot />
49+
</fieldset>
50+
51+
<fieldset>
52+
<legend>v-model</legend>
53+
<Model />
54+
</fieldset>
55+
56+
<fieldset>
57+
<legend>v-show</legend>
58+
<Show />
4759
</fieldset>
4860
</>
4961
)

playground/model.vue

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script setup lang="tsx">
2+
import { ref } from 'vue'
3+
4+
let model = ref('model')
5+
6+
const Comp = (props) => {
7+
console.log(props)
8+
return <input value={props.modelValue} onInput={e=>props['onUpdate:modelValue'](e.target.value)} />
9+
}
10+
11+
const Render = (
12+
<>
13+
<input v-model={model.value}></input>
14+
<Comp v-model={model.value} />
15+
{model.value}
16+
</>
17+
)
18+
defineRender(Render)
19+
</script>

playground/show.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script setup lang="tsx">
2+
import { ref } from 'vue'
3+
4+
let show = ref(false)
5+
6+
7+
const Render = (
8+
<>
9+
<input v-model={show.value} type="checkbox"/>
10+
<span v-show={show.value}>{show.value}</span>
11+
</>
12+
)
13+
defineRender(Render)
14+
</script>

src/core/compiler/compile.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { transformText } from './transforms/transformText'
2828
import { transformVBind } from './transforms/vBind'
2929
import { transformVOn } from './transforms/vOn'
3030
import { transformVSlot } from './transforms/vSlot'
31+
import { transformVModel } from './transforms/vModel'
32+
import { transformVShow } from './transforms/vShow'
3133
import type { JSXElement, JSXFragment, Program } from '@babel/types'
3234

3335
export interface VaporCodegenResult
@@ -129,6 +131,8 @@ export function getBaseTransformPreset(
129131
{
130132
bind: transformVBind,
131133
on: transformVOn,
134+
model: transformVModel,
135+
show: transformVShow,
132136
},
133137
]
134138
}

src/core/compiler/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ export { transformChildren } from './transforms/transformChildren'
1616
export { transformVBind } from './transforms/vBind'
1717
export { transformVOn } from './transforms/vOn'
1818
export { transformVSlot } from './transforms/vSlot'
19+
export { transformVModel } from './transforms/vModel'
20+
export { transformVShow } from './transforms/vShow'

src/core/compiler/transforms/transformElement.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export const isReservedProp = /* #__PURE__ */ makeMap(
3939

4040
const __BROWSER__ = false
4141

42+
const isEventRegex = /^on[A-Z]/
43+
const isDirectiveRegex = /^v-[a-z]/
44+
4245
export const transformElement: NodeTransform = (node, context) => {
4346
return function postTransformElement() {
4447
;({ node } = context)
@@ -255,9 +258,17 @@ function transformProp(
255258
context: TransformContext<JSXElement>,
256259
): DirectiveTransformResult | void {
257260
if (prop.type === 'JSXSpreadAttribute') return
258-
let name = prop.name.type === 'JSXIdentifier' ? prop.name.name : ''
261+
let name =
262+
prop.name.type === 'JSXIdentifier'
263+
? prop.name.name
264+
: prop.name.type === 'JSXNamespacedName'
265+
? prop.name.namespace.name
266+
: ''
259267

260-
if (!prop.value || prop.value.type === 'StringLiteral') {
268+
if (
269+
!isDirectiveRegex.test(name) &&
270+
(!prop.value || prop.value.type === 'StringLiteral')
271+
) {
261272
if (isReservedProp(name)) return
262273
return {
263274
key: resolveSimpleExpression(name, true, prop.name.loc!),
@@ -268,7 +279,11 @@ function transformProp(
268279
}
269280
}
270281

271-
name = /^on[A-Z]/.test(name) ? 'on' : 'bind'
282+
name = isEventRegex.test(name)
283+
? 'on'
284+
: isDirectiveRegex.test(name)
285+
? name.slice(2)
286+
: 'bind'
272287
const directiveTransform = context.options.directiveTransforms[name]
273288
if (directiveTransform) {
274289
return directiveTransform(prop, node, context)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { transformVModel as _transformVModel } from '@vue-vapor/compiler-vapor'
2+
import { resolveDirectiveNode, resolveNode } from '../utils'
3+
import type { DirectiveTransform } from '../transform'
4+
5+
export const transformVModel: DirectiveTransform = (dir, node, context) => {
6+
return _transformVModel(
7+
resolveDirectiveNode(dir, context),
8+
resolveNode(node, context),
9+
context as any,
10+
)
11+
}

src/core/compiler/transforms/vShow.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { transformVShow as _transformVShow } from '@vue-vapor/compiler-vapor'
2+
import { resolveDirectiveNode, resolveNode } from '../utils'
3+
import type { DirectiveTransform } from '../transform'
4+
5+
export const transformVShow: DirectiveTransform = (dir, node, context) => {
6+
return _transformVShow(
7+
resolveDirectiveNode(dir, context),
8+
resolveNode(node, context),
9+
context as any,
10+
)
11+
}

src/core/compiler/transforms/vSlot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type SlotBlockIRNode,
66
} from '../ir'
77
import { isComponentNode, resolveExpression } from '../utils'
8-
import { newBlock } from './utils'
8+
import { newBlock } from './utils'
99
import type { JSXAttribute, JSXElement } from '@babel/types'
1010
import type { NodeTransform, TransformContext } from '../transform'
1111

src/core/compiler/utils.ts

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { isGloballyAllowed, isString } from '@vue-vapor/shared'
22
import {
33
type AttributeNode,
4+
type DirectiveNode,
45
type ElementNode,
6+
ElementTypes,
7+
Namespaces,
58
NodeTypes,
69
type SimpleExpressionNode,
10+
type TextNode,
711
findDir as _findDir,
812
findProp as _findProp,
913
createSimpleExpression,
@@ -19,6 +23,7 @@ import type {
1923
BigIntLiteral,
2024
CallExpression,
2125
Expression,
26+
JSXAttribute,
2227
JSXElement,
2328
Node,
2429
NumericLiteral,
@@ -81,7 +86,7 @@ export function getLiteralExpressionValue(
8186
export function resolveExpression(
8287
node: Node | undefined | null,
8388
context: TransformContext,
84-
) {
89+
): SimpleExpressionNode {
8590
const isStatic =
8691
!!node &&
8792
(node.type === 'StringLiteral' ||
@@ -154,6 +159,99 @@ export function resolveLocation(
154159
}
155160
}
156161

162+
export function resolveValue(
163+
value: JSXAttribute['value'],
164+
context: TransformContext,
165+
): TextNode | undefined {
166+
return value
167+
? {
168+
type: NodeTypes.TEXT,
169+
content:
170+
value.type === 'StringLiteral'
171+
? value.value
172+
: value.type === 'JSXExpressionContainer'
173+
? context.ir.source.slice(
174+
value.expression.start!,
175+
value.expression.end!,
176+
)
177+
: '',
178+
loc: resolveLocation(value.loc, context),
179+
}
180+
: undefined
181+
}
182+
183+
export function resolveNode(
184+
node: JSXElement,
185+
context: TransformContext,
186+
): ElementNode {
187+
const tag =
188+
node.openingElement.name.type === 'JSXIdentifier'
189+
? node.openingElement.name.name
190+
: ''
191+
const loc = resolveLocation(node.loc, context)
192+
const tagType = isComponentNode(node)
193+
? ElementTypes.COMPONENT
194+
: ElementTypes.ELEMENT
195+
const props = node.openingElement.attributes.reduce(
196+
(result, attr) => {
197+
if (attr.type === 'JSXAttribute') {
198+
if (tagType === ElementTypes.COMPONENT) {
199+
result.push(resolveDirectiveNode(attr, context))
200+
} else {
201+
result.push({
202+
type: NodeTypes.ATTRIBUTE,
203+
name: `${attr.name.name}`,
204+
nameLoc: resolveLocation(attr.name.loc, context),
205+
value: resolveValue(attr.value, context),
206+
loc: resolveLocation(attr.loc, context),
207+
})
208+
}
209+
}
210+
return result
211+
},
212+
[] as Array<AttributeNode | DirectiveNode>,
213+
)
214+
215+
return {
216+
type: NodeTypes.ELEMENT,
217+
props,
218+
children: [],
219+
tag,
220+
loc,
221+
ns: Namespaces.HTML,
222+
tagType,
223+
isSelfClosing: !!node.selfClosing,
224+
codegenNode: undefined,
225+
}
226+
}
227+
228+
export function resolveDirectiveNode(
229+
node: JSXAttribute,
230+
context: TransformContext,
231+
): VaporDirectiveNode {
232+
const { value, name } = node
233+
const nameString = name.type === 'JSXIdentifier' ? name.name : ''
234+
const argString = name.type === 'JSXNamespacedName' ? name.namespace.name : ''
235+
236+
const arg =
237+
name.type === 'JSXNamespacedName'
238+
? resolveSimpleExpression(argString, true, name.namespace.loc)
239+
: undefined
240+
const exp = value ? resolveExpression(value, context) : undefined
241+
242+
const [tag, ...modifiers] = argString.split('_')
243+
244+
return {
245+
type: NodeTypes.DIRECTIVE,
246+
name: nameString,
247+
rawName: `${name}:${tag}`,
248+
exp,
249+
arg,
250+
loc: resolveLocation(node.loc, context),
251+
modifiers,
252+
}
253+
}
254+
157255
export function isComponentNode(node: Node): node is JSXElement {
158256
if (node.type !== 'JSXElement') return false
159257

0 commit comments

Comments
 (0)