Skip to content

Commit 14e586c

Browse files
minht11Princesseuh
andauthored
fix(vue): vue regular script block exports not being recognized inside editor (#8998)
Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
1 parent d979b8f commit 14e586c

File tree

9 files changed

+105
-14
lines changed

9 files changed

+105
-14
lines changed

.changeset/two-oranges-knock.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/vue': patch
3+
---
4+
5+
Adds editor support for Vue non setup script blocks and Vue 3.3 generics.

packages/integrations/vue/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"astro-scripts": "workspace:*",
5252
"chai": "^4.3.7",
5353
"linkedom": "^0.15.1",
54+
"cheerio": "1.0.0-rc.12",
5455
"mocha": "^10.2.0",
5556
"vite": "^4.4.9",
5657
"vue": "^3.3.4"

packages/integrations/vue/src/editor.cts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export function toTSX(code: string, className: string): string {
66
// NOTE: As you can expect, using regexes for this is not exactly the most reliable way of doing things
77
// However, I couldn't figure out a way to do it using Vue's compiler, I tried looking at how Volar does it, but I
88
// didn't really understand everything happening there and it seemed to be pretty Volar-specific. I do believe
9-
// someone more knowledgable on Vue's internals could figure it out, but since this solution is good enough for most
9+
// someone more knowledgeable on Vue's internals could figure it out, but since this solution is good enough for most
1010
// Vue components (and it's an improvement over, well, nothing), it's alright, I think
1111
try {
1212
const parsedResult = parse(code);
@@ -18,29 +18,39 @@ export function toTSX(code: string, className: string): string {
1818
`;
1919
}
2020

21-
if (parsedResult.descriptor.scriptSetup) {
22-
const definePropsType =
23-
parsedResult.descriptor.scriptSetup.content.match(/defineProps<([\s\S]+)>/m);
21+
// Vue supports 2 type of script blocks: setup and non-setup
22+
const regularScriptBlockContent = parsedResult.descriptor.script?.content ?? '';
23+
const { scriptSetup } = parsedResult.descriptor;
24+
25+
if (scriptSetup) {
26+
const definePropsType = scriptSetup.content.match(/defineProps<([\S\s]+?)>\s?\(\)/m);
27+
const propsGeneric = scriptSetup.attrs.generic;
28+
const propsGenericType = propsGeneric ? `<${propsGeneric}>` : '';
2429

2530
if (definePropsType) {
2631
result = `
27-
${parsedResult.descriptor.scriptSetup.content}
32+
${regularScriptBlockContent}
33+
${scriptSetup.content}
2834
29-
export default function ${className}__AstroComponent_(_props: ${definePropsType[1]}): any {
35+
export default function ${className}__AstroComponent_${propsGenericType}(_props: ${definePropsType[1]}): any {
3036
<div></div>
3137
}
3238
`;
3339
} else {
34-
const defineProps =
35-
parsedResult.descriptor.scriptSetup.content.match(/defineProps\([\s\S]+\)/m);
40+
// TODO. Find a way to support generics when using defineProps without passing explicit types.
41+
// Right now something like this `defineProps({ prop: { type: Array as PropType<T[]> } })`
42+
// won't be correctly typed in Astro.
43+
const defineProps = scriptSetup.content.match(/defineProps\([\s\S]+\)/m);
3644

3745
if (defineProps) {
3846
result = `
3947
import { defineProps } from '@vue/runtime-core';
4048
49+
${regularScriptBlockContent}
50+
4151
const Props = ${defineProps[0]}
4252
43-
export default function ${className}__AstroComponent_(_props: typeof Props): any {
53+
export default function ${className}__AstroComponent_${propsGenericType}(_props: typeof Props): any {
4454
<div></div>
4555
}
4656
`;

packages/integrations/vue/test/app-entrypoint.test.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { loadFixture } from './test-utils.js';
22
import { expect } from 'chai';
3+
import { load as cheerioLoad } from 'cheerio';
34
import { parseHTML } from 'linkedom';
5+
46
describe('App Entrypoint', () => {
57
/** @type {import('./test-utils').Fixture} */
68
let fixture;
@@ -13,11 +15,21 @@ describe('App Entrypoint', () => {
1315
});
1416

1517
it('loads during SSR', async () => {
16-
const data = await fixture.readFile('/index.html');
17-
const { document } = parseHTML(data);
18-
const bar = document.querySelector('#foo > #bar');
19-
expect(bar).not.to.be.undefined;
20-
expect(bar.textContent).to.eq('works');
18+
const html = await fixture.readFile('/index.html');
19+
const $ = cheerioLoad(html);
20+
21+
// test 1: basic component renders
22+
expect($('#foo > #bar').text()).to.eq('works');
23+
24+
// test 2: component with multiple script blocks renders and exports
25+
// values from non setup block correctly
26+
expect($('#multiple-script-blocks').text()).to.equal('2 4');
27+
28+
// test 3: component using generics renders
29+
expect($('#generics').text()).to.equal('generic');
30+
31+
// test 4: component using generics and multiple script blocks renders
32+
expect($('#generics-and-blocks').text()).to.equal('1 3!!!');
2133
});
2234

2335
it('setup included in renderer bundle', async () => {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script lang="ts" setup generic="T extends 'generic' | 'not-generic'">
2+
interface GenericComponentProps {
3+
value: T
4+
}
5+
6+
defineProps<GenericComponentProps>()
7+
</script>
8+
9+
<template>
10+
<div id="generics">
11+
{{ value }}
12+
</div>
13+
</template>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
export const customFormatter = (num: number) => `${num * 3}!!!`
3+
4+
export type FormatNumber<T> = (num: T) => string;
5+
</script>
6+
7+
<script lang="ts" setup generic="T extends number, Formatter extends FormatNumber<T>">
8+
const props = defineProps<{
9+
value: T,
10+
formatter: Formatter
11+
}>()
12+
</script>
13+
14+
<template>
15+
<div id="generics-and-blocks">
16+
{{ value }} {{ props.formatter(props.value) }}
17+
</div>
18+
</template>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
export const doubleNumber = (num: number) => num * 2
3+
</script>
4+
5+
<script lang="ts" setup>
6+
defineProps({
7+
value: {
8+
type: Number,
9+
required: true
10+
}
11+
})
12+
</script>
13+
14+
<template>
15+
<div id="multiple-script-blocks">
16+
{{ doubleNumber(value) }} <slot />
17+
</div>
18+
</template>

packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/index.astro

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
---
22
import Foo from '../components/Foo.vue';
3+
import MultipleScriptBlocks, { doubleNumber } from '../components/MultipleScriptBlocks.vue';
4+
import GenericComponent from '../components/Generics.vue';
5+
import GenericsAndBlocks, { customFormatter } from '../components/GenericsAndBlocks.vue';
36
---
47

58
<html>
@@ -8,5 +11,13 @@ import Foo from '../components/Foo.vue';
811
</head>
912
<body>
1013
<Foo client:load />
14+
15+
<MultipleScriptBlocks value={1}>
16+
{doubleNumber(2)}
17+
</MultipleScriptBlocks>
18+
19+
<GenericComponent value={'generic'} />
20+
21+
<GenericsAndBlocks value={1} formatter={customFormatter} />
1122
</body>
1223
</html>

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)