Skip to content

Commit 98fdb6a

Browse files
authored
fix: add support for multiple statements in v-on snippets (#36)
1 parent 8b68127 commit 98fdb6a

File tree

5 files changed

+74
-21
lines changed

5 files changed

+74
-21
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ Install package:
1313

1414
```sh
1515
# npm
16-
npm install vue-sfc-transformer
16+
npm install vue-sfc-transformer vue @vue/compiler-core esbuild
1717

1818
# pnpm
19-
pnpm install vue-sfc-transformer
19+
pnpm install vue-sfc-transformer vue @vue/compiler-core esbuild
2020
```
2121

2222
```js

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"test:types": "tsc --noEmit"
4545
},
4646
"peerDependencies": {
47+
"@vue/compiler-core": "^3.5.13",
4748
"esbuild": "*",
4849
"vue": "^3.5.13"
4950
},
@@ -55,6 +56,7 @@
5556
"@babel/types": "7.27.0",
5657
"@types/node": "22.14.1",
5758
"@vitest/coverage-v8": "3.1.2",
59+
"@vue/compiler-core": "3.5.13",
5860
"@vue/compiler-dom": "3.5.13",
5961
"bumpp": "10.1.0",
6062
"changelogithub": "13.13.0",

pnpm-lock.yaml

+12-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/utils/template.ts

+42-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { AttributeNode, DirectiveNode, ExpressionNode, ForParseResult, ParentNode, RootNode, SourceLocation, TemplateChildNode, TextNode } from '@vue/compiler-dom'
1+
import type { AttributeNode, DirectiveNode, ExpressionNode, ParentNode, RootNode, SourceLocation, TemplateChildNode, TextNode } from '@vue/compiler-dom'
2+
import { isFnExpressionBrowser as isFnExpression, isMemberExpressionBrowser as isMemberExpression } from '@vue/compiler-core'
23

34
// copy from `@vue/compiler-dom`
45
enum NodeTypes {
@@ -37,14 +38,8 @@ enum NodeTypes {
3738
JS_RETURN_STATEMENT,
3839
}
3940

40-
interface ExpressionTrack {
41-
type: NodeTypes
42-
name?: string
43-
forParseResult?: ForParseResult
44-
}
45-
4641
interface Expression {
47-
track: ExpressionTrack[]
42+
track: VueTemplateNode[]
4843
loc: SourceLocation
4944
src: string
5045
replacement?: string
@@ -60,7 +55,7 @@ type VueTemplateNode =
6055
function handleNode(
6156
node: VueTemplateNode | undefined,
6257
addExpression: (...expressions: Expression[]) => void,
63-
track: ExpressionTrack[],
58+
track: VueTemplateNode[],
6459
) {
6560
if (!node) {
6661
return
@@ -264,6 +259,43 @@ const defaultSnippetHandler: SnippetHandler = {
264259
standalone: false,
265260
}
266261

262+
const multipleStatementsSnippetHandler: SnippetHandler = {
263+
key: (node) => {
264+
const key = `multipleStatements$:${node.src}`
265+
const secondLastTrack = node.track.at(-2)
266+
const lastTrack = node.track.at(-1)
267+
268+
if (
269+
lastTrack?.type === NodeTypes.SIMPLE_EXPRESSION
270+
&& secondLastTrack?.type === NodeTypes.DIRECTIVE
271+
&& secondLastTrack.name === 'on'
272+
) {
273+
const isMemberExp = isMemberExpression(lastTrack)
274+
const isInlineStatement = !(isMemberExp || isFnExpression(lastTrack))
275+
276+
const hasMultipleStatements = node.src.includes(';')
277+
278+
if ((isInlineStatement || isMemberExp) && hasMultipleStatements) {
279+
return key
280+
}
281+
}
282+
283+
return null
284+
},
285+
prepare: (node, id) => `wrapper_${id}(() => {${node.src}});`,
286+
parse: (code) => {
287+
const wrapperRegex = /^(wrapper_\d+)\(\(\) => \{([\s\S]*?)\}\);$/
288+
289+
const [_, wrapperName, res] = code.trim().match(wrapperRegex) ?? []
290+
if (!wrapperName || !res) {
291+
return undefined
292+
}
293+
294+
return res.trim().replace(/;$/, '')
295+
},
296+
standalone: false,
297+
}
298+
267299
const destructureSnippetHandler: SnippetHandler = {
268300
key: (node) => {
269301
const key = `destructure$:${node.src}`
@@ -299,7 +331,7 @@ const destructureSnippetHandler: SnippetHandler = {
299331
standalone: true,
300332
}
301333

302-
const snippetHandlers = [destructureSnippetHandler, defaultSnippetHandler]
334+
const snippetHandlers = [destructureSnippetHandler, multipleStatementsSnippetHandler, defaultSnippetHandler]
303335
function getKey(expression: Expression) {
304336
for (const handler of snippetHandlers) {
305337
const key = handler.key(expression)

test/template.test.ts

+16
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ describe('transform typescript template', () => {
5858
ping();
5959
}" />"
6060
`)
61+
62+
// https://github.com/nuxt/module-builder/issues/587#issuecomment-2820414064
63+
expect(
64+
await fixture(`<div @click="a(); b()" />`),
65+
).toMatchInlineSnapshot(`
66+
"<div @click="a();
67+
b()" />"
68+
`)
69+
expect(
70+
await fixture(`<div @click="a(); () => {}; b()" />`),
71+
).toMatchInlineSnapshot(`
72+
"<div @click="a();
73+
() => {
74+
};
75+
b()" />"
76+
`)
6177
})
6278

6379
it('v-slot', async () => {

0 commit comments

Comments
 (0)