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

[Vue] add support for appEntrypoint #5075

Merged
merged 5 commits into from
Oct 13, 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
29 changes: 29 additions & 0 deletions .changeset/dry-moose-join.md
Original file line number Diff line number Diff line change
@@ -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 */ })
}
```
30 changes: 30 additions & 0 deletions packages/integrations/vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
5 changes: 4 additions & 1 deletion packages/integrations/vue/client.js
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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);
}
};
7 changes: 6 additions & 1 deletion packages/integrations/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
},
Expand Down
2 changes: 2 additions & 0 deletions packages/integrations/vue/server.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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 };
}
Expand Down
26 changes: 24 additions & 2 deletions packages/integrations/vue/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { UserConfig } from 'vite';

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

function getRenderer(): AstroRenderer {
Expand All @@ -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'],
Expand Down
34 changes: 34 additions & 0 deletions packages/integrations/vue/test/app-entrypoint.test.js
Original file line number Diff line number Diff line change
@@ -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)
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import vue from '@astrojs/vue';

export default defineConfig({
integrations: [vue({
appEntrypoint: '/src/pages/_app'
})]
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@test/vue-app-entrypoint",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*",
"@astrojs/vue": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<div id="bar">works</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div id="foo">
<Bar />
</div>
</template>
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
import Foo from '../components/Foo.vue';
---

<html>
<head>
<title>Vue App Entrypoint</title>
</head>
<body>
<Foo client:load />
</body>
</html>
17 changes: 17 additions & 0 deletions packages/integrations/vue/test/test-utils.js
Original file line number Diff line number Diff line change
@@ -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(),
});
}
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.