Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Vue JSX #4897

Merged
merged 8 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/honest-melons-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astrojs/vue': minor
'astro': patch
---

Support Vue JSX
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/render/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function guessRenderers(componentUrl?: string): string[] {
return ['@astrojs/vue'];
case 'jsx':
case 'tsx':
return ['@astrojs/react', '@astrojs/preact'];
return ['@astrojs/react', '@astrojs/preact', '@astrojs/vue (jsx)'];
default:
return ['@astrojs/react', '@astrojs/preact', '@astrojs/vue', '@astrojs/svelte'];
}
Expand Down
7 changes: 7 additions & 0 deletions packages/astro/test/fixtures/vue-jsx/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import vue from '@astrojs/vue';

// https://astro.build/config
export default defineConfig({
integrations: [vue({ jsx: true })],
});
10 changes: 10 additions & 0 deletions packages/astro/test/fixtures/vue-jsx/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@test/vue-jsx",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vue": "^3.2.39"
}
}
28 changes: 28 additions & 0 deletions packages/astro/test/fixtures/vue-jsx/src/components/Counter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { defineComponent, ref } from 'vue';

export default defineComponent({
props: {
start: {
type: String,
required: true
},
stepSize: {
type: String,
default: "1"
}
},
setup(props) {
const count = ref(parseInt(props.start))
const stepSize = ref(parseInt(props.stepSize))
const add = () => (count.value = count.value + stepSize.value);
const subtract = () => (count.value = count.value - stepSize.value);
return () => (
<div class="counter">
<h1><slot /></h1>
<button onClick={subtract}>-</button>
<pre>{count.value}</pre>
<button onClick={add}>+</button>
</div>
)
},
})
15 changes: 15 additions & 0 deletions packages/astro/test/fixtures/vue-jsx/src/components/Result.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<pre>{{ value }}</pre>
</template>

<script>

export default {
props: {
value: {
type: Number,
required: true
}
}
}
</script>
35 changes: 35 additions & 0 deletions packages/astro/test/fixtures/vue-jsx/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
import Counter from '../components/Counter.jsx'
import Result from '../components/Result.vue'
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width"
/>
<title>Vue component</title>
<style>
:global(:root) {
font-family: system-ui;
padding: 1em;
}
</style>
</head>
<body>
<main>
<Result value={2345}></Result>
<Counter start="0">SSR Rendered, No Client</Counter>
<Counter start="1" client:load>SSR Rendered, client:load</Counter>
<!-- Test island deduplication, i.e. same UID as the component above. -->
<Counter start="1" client:load>SSR Rendered, client:load</Counter>
<!-- Test island deduplication account for non-render affecting props. -->
<Counter start="1" step-size="2" client:load>SSR Rendered, client:load</Counter>
<Counter start="10" client:idle>SSR Rendered, client:idle</Counter>
<!-- Test that two client:visibles have unique uids -->
<Counter start="100" client:visible>SSR Rendered, client:visible</Counter>
<Counter start="1000" client:visible>SSR Rendered, client:visible</Counter>
</main>
</body>
</html>
30 changes: 30 additions & 0 deletions packages/astro/test/vue-jsx.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';

describe('Vue JSX', () => {
let fixture;

before(async () => {
fixture = await loadFixture({
root: './fixtures/vue-jsx/',
});
});

describe('build', () => {
before(async () => {
await fixture.build();
});

it('Can load Vue JSX', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);

const allPreValues = $('pre')
.toArray()
.map((el) => $(el).text());

expect(allPreValues).to.deep.equal(['2345', '0', '1', '1', '1', '10', '100', '1000']);
});
});
});
37 changes: 37 additions & 0 deletions packages/integrations/vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,40 @@ export default {
})],
}
```

### jsx

You can use Vue JSX by setting `jsx: true`.

__`astro.config.mjs`__

```js
import { defineConfig } from 'astro/config';
import vue from '@astrojs/vue';

export default defineConfig({
integrations: [
vue({ jsx: true })
],
});
```

This will enable rendering for both Vue and Vue JSX components. To customize the Vue JSX compiler, pass an options object instead of a boolean. See the `@vitejs/plugin-vue-jsx` [docs](https://github.com/vitejs/vite/tree/main/packages/plugin-vue-jsx) for more details.

__`astro.config.mjs`__

```js
import { defineConfig } from 'astro/config';
import vue from '@astrojs/vue';

export default defineConfig({
integrations: [
vue({
jsx: {
// treat any tag that starts with ion- as custom elements
isCustomElement: tag => tag.startsWith('ion-')
}
})
],
});
```
2 changes: 2 additions & 0 deletions packages/integrations/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
},
"dependencies": {
"@vitejs/plugin-vue": "^3.0.0",
"@vitejs/plugin-vue-jsx": "^2.0.1",
"@vue/babel-plugin-jsx": "^1.1.1",
"@vue/compiler-sfc": "^3.2.39"
},
"devDependencies": {
Expand Down
39 changes: 35 additions & 4 deletions packages/integrations/vue/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type { Options } from '@vitejs/plugin-vue';
import type { Options as VueOptions } from '@vitejs/plugin-vue';
import type { Options as VueJsxOptions } from '@vitejs/plugin-vue-jsx';
import vue from '@vitejs/plugin-vue';
import type { AstroIntegration, AstroRenderer } from 'astro';
import type { UserConfig } from 'vite';

interface Options extends VueOptions {
jsx?: boolean | VueJsxOptions;
}

function getRenderer(): AstroRenderer {
return {
name: '@astrojs/vue',
Expand All @@ -11,8 +16,23 @@ function getRenderer(): AstroRenderer {
};
}

function getViteConfiguration(options?: Options): UserConfig {
function getJsxRenderer(): AstroRenderer {
return {
name: '@astrojs/vue (jsx)',
clientEntrypoint: '@astrojs/vue/client.js',
serverEntrypoint: '@astrojs/vue/server.js',
jsxImportSource: 'vue',
jsxTransformOptions: async () => {
const jsxPlugin = (await import('@vue/babel-plugin-jsx')).default;
return {
plugins: [jsxPlugin],
};
},
};
}

async function getViteConfiguration(options?: Options): Promise<UserConfig> {
const config: UserConfig = {
optimizeDeps: {
include: ['@astrojs/vue/client.js', 'vue'],
exclude: ['@astrojs/vue/server.js'],
Expand All @@ -23,15 +43,26 @@ function getViteConfiguration(options?: Options): UserConfig {
noExternal: ['vueperslides'],
},
};

if (options?.jsx) {
const vueJsx = (await import('@vitejs/plugin-vue-jsx')).default;
const jsxOptions = typeof options.jsx === 'object' ? options.jsx : undefined;
config.plugins?.push(vueJsx(jsxOptions));
}

return config;
}

export default function (options?: Options): AstroIntegration {
return {
name: '@astrojs/vue',
hooks: {
'astro:config:setup': ({ addRenderer, updateConfig }) => {
'astro:config:setup': async ({ addRenderer, updateConfig }) => {
addRenderer(getRenderer());
updateConfig({ vite: getViteConfiguration(options) });
if (options?.jsx) {
addRenderer(getJsxRenderer());
}
updateConfig({ vite: await getViteConfiguration(options) });
},
},
};
Expand Down
Loading