summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/dry-moose-join.md29
-rw-r--r--packages/integrations/vue/README.md30
-rw-r--r--packages/integrations/vue/client.js5
-rw-r--r--packages/integrations/vue/package.json7
-rw-r--r--packages/integrations/vue/server.js2
-rw-r--r--packages/integrations/vue/src/index.ts26
-rw-r--r--packages/integrations/vue/test/app-entrypoint.test.js34
-rw-r--r--packages/integrations/vue/test/fixtures/app-entrypoint/astro.config.mjs8
-rw-r--r--packages/integrations/vue/test/fixtures/app-entrypoint/package.json9
-rw-r--r--packages/integrations/vue/test/fixtures/app-entrypoint/src/components/Bar.vue3
-rw-r--r--packages/integrations/vue/test/fixtures/app-entrypoint/src/components/Foo.vue5
-rw-r--r--packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/_app.ts6
-rw-r--r--packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/index.astro12
-rw-r--r--packages/integrations/vue/test/test-utils.js17
-rw-r--r--pnpm-lock.yaml16
15 files changed, 205 insertions, 4 deletions
diff --git a/.changeset/dry-moose-join.md b/.changeset/dry-moose-join.md
new file mode 100644
index 000000000..b90fac4ee
--- /dev/null
+++ b/.changeset/dry-moose-join.md
@@ -0,0 +1,29 @@
+---
+'@astrojs/vue': minor
+---
+
+Add support for the `appEntrypoint` option, which accepts a root-relative path to an app entrypoint. The default export of this file should be a function that accepts a Vue `App` instance prior to rendering. This opens up the ability to extend the `App` instance with [custom Vue plugins](https://vuejs.org/guide/reusability/plugins.html).
+
+```js
+// astro.config.mjs
+import { defineConfig } from 'astro/config';
+import vue from '@astrojs/vue';
+
+export default defineConfig({
+ integrations: [
+ vue({
+ appEntrypoint: '/src/pages/_app'
+ })
+ ]
+})
+```
+
+```js
+// src/pages/_app.ts
+import type { App } from 'vue';
+import i18nPlugin from '../plugins/i18n'
+
+export default function setup(app: App) {
+ app.use(i18nPlugin, { /* options */ })
+}
+```
diff --git a/packages/integrations/vue/README.md b/packages/integrations/vue/README.md
index 75c861ce3..c1c199b81 100644
--- a/packages/integrations/vue/README.md
+++ b/packages/integrations/vue/README.md
@@ -95,6 +95,36 @@ export default {
}
```
+### appEntrypoint
+
+You can extend the Vue `app` instance setting the `appEntrypoint` option to a root-relative import specifier (for example, `appEntrypoint: "/src/pages/_app"`).
+
+The default export of this file should be a function that accepts a Vue `App` instance prior to rendering, allowing the use of [custom Vue plugins](https://vuejs.org/guide/reusability/plugins.html), `app.use`, and other customizations for advanced use cases.
+
+__`astro.config.mjs`__
+
+```js
+import { defineConfig } from 'astro/config';
+import vue from '@astrojs/vue';
+
+export default defineConfig({
+ integrations: [
+ vue({ appEntrypoint: '/src/pages/_app' })
+ ],
+});
+```
+
+__`src/pages/_app.ts`__
+
+```js
+import type { App } from 'vue';
+import i18nPlugin from 'my-vue-i18n-plugin';
+
+export default (app: App) => {
+ app.use(i18nPlugin);
+}
+```
+
### jsx
You can use Vue JSX by setting `jsx: true`.
diff --git a/packages/integrations/vue/client.js b/packages/integrations/vue/client.js
index 648a69658..faf1ac63a 100644
--- a/packages/integrations/vue/client.js
+++ b/packages/integrations/vue/client.js
@@ -1,8 +1,9 @@
import { h, createSSRApp, createApp } from 'vue';
+import { setup } from 'virtual:@astrojs/vue/app'
import StaticHtml from './static-html.js';
export default (element) =>
- (Component, props, slotted, { client }) => {
+ async (Component, props, slotted, { client }) => {
delete props['class'];
if (!element.hasAttribute('ssr')) return;
@@ -14,9 +15,11 @@ export default (element) =>
}
if (client === 'only') {
const app = createApp({ name, render: () => h(Component, props, slots) });
+ await setup(app);
app.mount(element, false);
} else {
const app = createSSRApp({ name, render: () => h(Component, props, slots) });
+ await setup(app);
app.mount(element, true);
}
};
diff --git a/packages/integrations/vue/package.json b/packages/integrations/vue/package.json
index 7f80497b4..c5a932a3a 100644
--- a/packages/integrations/vue/package.json
+++ b/packages/integrations/vue/package.json
@@ -30,7 +30,8 @@
"scripts": {
"build": "astro-scripts build \"src/index.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist && tsc",
"build:ci": "astro-scripts build \"src/**/*.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist",
- "dev": "astro-scripts dev \"src/**/*.ts\""
+ "dev": "astro-scripts dev \"src/**/*.ts\"",
+ "test": "mocha --timeout 20000"
},
"dependencies": {
"@vitejs/plugin-vue": "^3.0.0",
@@ -39,8 +40,12 @@
"@vue/compiler-sfc": "^3.2.39"
},
"devDependencies": {
+ "@types/chai": "^4.3.3",
"astro": "workspace:*",
"astro-scripts": "workspace:*",
+ "chai": "^4.3.6",
+ "linkedom": "^0.14.17",
+ "mocha": "^9.2.2",
"vite": "^3.0.0",
"vue": "^3.2.37"
},
diff --git a/packages/integrations/vue/server.js b/packages/integrations/vue/server.js
index 8d4c6df9e..831b46a1d 100644
--- a/packages/integrations/vue/server.js
+++ b/packages/integrations/vue/server.js
@@ -1,5 +1,6 @@
import { h, createSSRApp } from 'vue';
import { renderToString } from 'vue/server-renderer';
+import { setup } from 'virtual:@astrojs/vue/app'
import StaticHtml from './static-html.js';
function check(Component) {
@@ -12,6 +13,7 @@ async function renderToStaticMarkup(Component, props, slotted) {
slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key });
}
const app = createSSRApp({ render: () => h(Component, props, slots) });
+ await setup(app);
const html = await renderToString(app);
return { html };
}
diff --git a/packages/integrations/vue/src/index.ts b/packages/integrations/vue/src/index.ts
index e8ca19025..2af3062bf 100644
--- a/packages/integrations/vue/src/index.ts
+++ b/packages/integrations/vue/src/index.ts
@@ -6,6 +6,7 @@ import type { UserConfig } from 'vite';
interface Options extends VueOptions {
jsx?: boolean | VueJsxOptions;
+ appEntrypoint?: string;
}
function getRenderer(): AstroRenderer {
@@ -31,13 +32,34 @@ function getJsxRenderer(): AstroRenderer {
};
}
+function virtualAppEntrypoint(options?: Options) {
+ const virtualModuleId = 'virtual:@astrojs/vue/app';
+ const resolvedVirtualModuleId = '\0' + virtualModuleId;
+ return {
+ name: '@astrojs/vue/virtual-app',
+ resolveId(id: string) {
+ if (id == virtualModuleId) {
+ return resolvedVirtualModuleId;
+ }
+ },
+ load(id: string) {
+ if (id === resolvedVirtualModuleId) {
+ if (options?.appEntrypoint) {
+ return `export { default as setup } from "${options.appEntrypoint}";`;
+ }
+ return `export const setup = () => {};`;
+ }
+ },
+ };
+}
+
async function getViteConfiguration(options?: Options): Promise<UserConfig> {
const config: UserConfig = {
optimizeDeps: {
include: ['@astrojs/vue/client.js', 'vue'],
- exclude: ['@astrojs/vue/server.js'],
+ exclude: ['@astrojs/vue/server.js', 'virtual:@astrojs/vue/app']
},
- plugins: [vue(options)],
+ plugins: [vue(options), virtualAppEntrypoint(options)],
ssr: {
external: ['@vue/server-renderer'],
noExternal: ['vueperslides'],
diff --git a/packages/integrations/vue/test/app-entrypoint.test.js b/packages/integrations/vue/test/app-entrypoint.test.js
new file mode 100644
index 000000000..1b53fbd21
--- /dev/null
+++ b/packages/integrations/vue/test/app-entrypoint.test.js
@@ -0,0 +1,34 @@
+import { loadFixture } from './test-utils.js';
+import { expect } from 'chai';
+import { parseHTML } from 'linkedom';
+
+describe('App Entrypoint', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/app-entrypoint/'
+ });
+ await fixture.build();
+ });
+
+ it('loads during SSR', async () => {
+ const data = await fixture.readFile('/index.html')
+ const { document } = parseHTML(data);
+ const bar = document.querySelector('#foo > #bar');
+ expect(bar).not.to.be.undefined;
+ expect(bar.textContent).to.eq('works');
+ });
+
+ it('setup included in renderer bundle', async () => {
+ const data = await fixture.readFile('/index.html')
+ const { document } = parseHTML(data);
+ const island = document.querySelector('astro-island');
+ const client = island.getAttribute('renderer-url');
+ expect(client).not.to.be.undefined;
+
+ const js = await fixture.readFile(client);
+ expect(js).to.match(/\w+\.component\(\"Bar\"/gm)
+ });
+});
diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint/astro.config.mjs b/packages/integrations/vue/test/fixtures/app-entrypoint/astro.config.mjs
new file mode 100644
index 000000000..0bf5fd95d
--- /dev/null
+++ b/packages/integrations/vue/test/fixtures/app-entrypoint/astro.config.mjs
@@ -0,0 +1,8 @@
+import { defineConfig } from 'astro/config';
+import vue from '@astrojs/vue';
+
+export default defineConfig({
+ integrations: [vue({
+ appEntrypoint: '/src/pages/_app'
+ })]
+})
diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint/package.json b/packages/integrations/vue/test/fixtures/app-entrypoint/package.json
new file mode 100644
index 000000000..3cb7d419b
--- /dev/null
+++ b/packages/integrations/vue/test/fixtures/app-entrypoint/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/vue-app-entrypoint",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*",
+ "@astrojs/vue": "workspace:*"
+ }
+}
diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint/src/components/Bar.vue b/packages/integrations/vue/test/fixtures/app-entrypoint/src/components/Bar.vue
new file mode 100644
index 000000000..9e690ea06
--- /dev/null
+++ b/packages/integrations/vue/test/fixtures/app-entrypoint/src/components/Bar.vue
@@ -0,0 +1,3 @@
+<template>
+ <div id="bar">works</div>
+</template>
diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint/src/components/Foo.vue b/packages/integrations/vue/test/fixtures/app-entrypoint/src/components/Foo.vue
new file mode 100644
index 000000000..3e648808c
--- /dev/null
+++ b/packages/integrations/vue/test/fixtures/app-entrypoint/src/components/Foo.vue
@@ -0,0 +1,5 @@
+<template>
+ <div id="foo">
+ <Bar />
+ </div>
+</template>
diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/_app.ts b/packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/_app.ts
new file mode 100644
index 000000000..bbda85382
--- /dev/null
+++ b/packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/_app.ts
@@ -0,0 +1,6 @@
+import type { App } from 'vue'
+import Bar from '../components/Bar.vue'
+
+export default function setup(app: App) {
+ app.component('Bar', Bar);
+}
diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/index.astro b/packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/index.astro
new file mode 100644
index 000000000..3240cbe0f
--- /dev/null
+++ b/packages/integrations/vue/test/fixtures/app-entrypoint/src/pages/index.astro
@@ -0,0 +1,12 @@
+---
+import Foo from '../components/Foo.vue';
+---
+
+<html>
+ <head>
+ <title>Vue App Entrypoint</title>
+ </head>
+ <body>
+ <Foo client:load />
+ </body>
+</html>
diff --git a/packages/integrations/vue/test/test-utils.js b/packages/integrations/vue/test/test-utils.js
new file mode 100644
index 000000000..2475944be
--- /dev/null
+++ b/packages/integrations/vue/test/test-utils.js
@@ -0,0 +1,17 @@
+import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js';
+
+/**
+ * @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
+ */
+
+export function loadFixture(inlineConfig) {
+ if (!inlineConfig || !inlineConfig.root)
+ throw new Error("Must provide { root: './fixtures/...' }");
+
+ // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath
+ // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test`
+ return baseLoadFixture({
+ ...inlineConfig,
+ root: new URL(inlineConfig.root, import.meta.url).toString(),
+ });
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index eed9380ab..03b392a86 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3142,12 +3142,16 @@ importers:
packages/integrations/vue:
specifiers:
+ '@types/chai': ^4.3.3
'@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
astro: workspace:*
astro-scripts: workspace:*
+ chai: ^4.3.6
+ linkedom: ^0.14.17
+ mocha: ^9.2.2
vite: ^3.0.0
vue: ^3.2.37
dependencies:
@@ -3156,11 +3160,23 @@ importers:
'@vue/babel-plugin-jsx': 1.1.1
'@vue/compiler-sfc': 3.2.40
devDependencies:
+ '@types/chai': 4.3.3
astro: link:../../astro
astro-scripts: link:../../../scripts
+ chai: 4.3.6
+ linkedom: 0.14.17
+ mocha: 9.2.2
vite: 3.1.8
vue: 3.2.40
+ packages/integrations/vue/test/fixtures/app-entrypoint:
+ specifiers:
+ '@astrojs/vue': workspace:*
+ astro: workspace:*
+ dependencies:
+ '@astrojs/vue': link:../../..
+ astro: link:../../../../../astro
+
packages/markdown/component:
specifiers:
'@types/mocha': ^9.1.1