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

Ensure appEntrypoint is referenced in Vue components #9490

Merged
merged 8 commits into from
Jan 5, 2024
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
5 changes: 5 additions & 0 deletions .changeset/odd-rivers-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/vue': patch
---

Fixes a bug that caused styles referenced by `appEntrypoint` to be excluded from the build
27 changes: 22 additions & 5 deletions packages/integrations/vue/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import vue from '@vitejs/plugin-vue';
import type { Options as VueJsxOptions } from '@vitejs/plugin-vue-jsx';
import type { AstroIntegration, AstroRenderer } from 'astro';
import type { Plugin, UserConfig } from 'vite';
import { MagicString } from '@vue/compiler-sfc';

interface Options extends VueOptions {
jsx?: boolean | VueJsxOptions;
Expand Down Expand Up @@ -39,6 +40,7 @@ function virtualAppEntrypoint(options?: Options): Plugin {

let isBuild: boolean;
let root: string;
let appEntrypoint: string | undefined;

return {
name: '@astrojs/vue/virtual-app',
Expand All @@ -47,6 +49,11 @@ function virtualAppEntrypoint(options?: Options): Plugin {
},
configResolved(config) {
root = config.root;
if (options?.appEntrypoint) {
appEntrypoint = options.appEntrypoint.startsWith('.')
? path.resolve(root, options.appEntrypoint)
: options.appEntrypoint;
}
},
resolveId(id: string) {
if (id == virtualModuleId) {
Expand All @@ -55,11 +62,7 @@ function virtualAppEntrypoint(options?: Options): Plugin {
},
load(id: string) {
if (id === resolvedVirtualModuleId) {
if (options?.appEntrypoint) {
const appEntrypoint = options.appEntrypoint.startsWith('.')
? path.resolve(root, options.appEntrypoint)
: options.appEntrypoint;

if (appEntrypoint) {
return `\
import * as mod from ${JSON.stringify(appEntrypoint)};

Expand All @@ -80,6 +83,20 @@ export const setup = async (app) => {
return `export const setup = () => {};`;
}
},
// Ensure that Vue components reference appEntrypoint directly
// This allows Astro to assosciate global styles imported in this file
// with the pages they should be injected to
transform(code, id) {
if (!appEntrypoint) return;
if (id.endsWith('.vue')) {
const s = new MagicString(code);
s.prepend(`import ${JSON.stringify(appEntrypoint)};\n`);
return {
code: s.toString(),
map: s.generateMap({ hires: 'boundary' })
}
}
},
};
}

Expand Down
67 changes: 67 additions & 0 deletions packages/integrations/vue/test/app-entrypoint-css.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { loadFixture } from './test-utils.js';
import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio';

describe('App Entrypoint CSS', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;

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

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

it('injects styles referenced in appEntrypoint', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerioLoad(html);

// test 1: basic component renders
expect($('#foo > #bar').text()).to.eq('works');

// test 2: injects the global style on the page
expect($('style').first().text().trim()).to.eq(':root{background-color:red}');
});

it('does not inject styles to pages without a Vue component', async () => {
const html = await fixture.readFile('/unrelated/index.html');
const $ = cheerioLoad(html);

expect($('style').length).to.eq(0);
expect($('link[rel="stylesheet"]').length).to.eq(0);
});
})

describe('dev', () => {
let devServer;
before(async () => {
devServer = await fixture.startDevServer();
})
after(async () => {
await devServer.stop();
})

it('loads during SSR', async () => {
const html = await fixture.fetch('/').then((res) => res.text());
const $ = cheerioLoad(html);

// test 1: basic component renders
expect($('#foo > #bar').text()).to.eq('works');
// test 2: injects the global style on the page
expect($('style').first().text().replace(/\s+/g, '')).to.eq(':root{background-color:red;}');
});

it('does not inject styles to pages without a Vue component', async () => {
const html = await fixture.fetch('/unrelated').then((res) => res.text());
const $ = cheerioLoad(html);

expect($('style').length).to.eq(0);
expect($('link[rel="stylesheet"]').length).to.eq(0);
});
})
});
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/app.ts' })
],
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@test/vue-app-entrypoint-css",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/vue": "workspace:*",
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { App } from 'vue'
import Bar from './components/Bar.vue'
// Important! Test that styles here are injected to the page
import '/src/main.css'


export default function setup(app: App) {
app.component('Bar', Bar);
}
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,3 @@
:root {
background-color: red;
}
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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<title>Unrelated page</title>
</head>
<body>
<h1>I shouldn't have styles</h1>
</body>
</html>
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

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