From bc2a4ac6924baf99eac5f0dd212949397c71c47e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 20 Dec 2023 12:50:31 -0600 Subject: [PATCH 1/7] fix(#6827): ensure `appEntrypoint` is referenced in Vue components --- packages/integrations/vue/src/index.ts | 27 +++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/integrations/vue/src/index.ts b/packages/integrations/vue/src/index.ts index 8cd1172a9b4a..09df5a30f68f 100644 --- a/packages/integrations/vue/src/index.ts +++ b/packages/integrations/vue/src/index.ts @@ -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; @@ -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', @@ -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) { @@ -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)}; @@ -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 "${appEntrypoint}";\n`); + return { + code: s.toString(), + map: s.generateMap() + } + } + }, }; } From c8aed0aa38c76e9495c9a87fc031b5ef4f011340 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 20 Dec 2023 12:50:38 -0600 Subject: [PATCH 2/7] chore: add test --- .../vue/test/app-entrypoint-css.test.js | 67 +++++++++++++++++++ .../app-entrypoint-css/astro.config.mjs | 8 +++ .../fixtures/app-entrypoint-css/package.json | 9 +++ .../fixtures/app-entrypoint-css/src/app.ts | 9 +++ .../app-entrypoint-css/src/components/Bar.vue | 3 + .../app-entrypoint-css/src/components/Foo.vue | 5 ++ .../fixtures/app-entrypoint-css/src/main.css | 3 + .../app-entrypoint-css/src/pages/index.astro | 12 ++++ .../src/pages/unrelated.astro | 8 +++ 9 files changed, 124 insertions(+) create mode 100644 packages/integrations/vue/test/app-entrypoint-css.test.js create mode 100644 packages/integrations/vue/test/fixtures/app-entrypoint-css/astro.config.mjs create mode 100644 packages/integrations/vue/test/fixtures/app-entrypoint-css/package.json create mode 100644 packages/integrations/vue/test/fixtures/app-entrypoint-css/src/app.ts create mode 100644 packages/integrations/vue/test/fixtures/app-entrypoint-css/src/components/Bar.vue create mode 100644 packages/integrations/vue/test/fixtures/app-entrypoint-css/src/components/Foo.vue create mode 100644 packages/integrations/vue/test/fixtures/app-entrypoint-css/src/main.css create mode 100644 packages/integrations/vue/test/fixtures/app-entrypoint-css/src/pages/index.astro create mode 100644 packages/integrations/vue/test/fixtures/app-entrypoint-css/src/pages/unrelated.astro diff --git a/packages/integrations/vue/test/app-entrypoint-css.test.js b/packages/integrations/vue/test/app-entrypoint-css.test.js new file mode 100644 index 000000000000..b629f1d25f58 --- /dev/null +++ b/packages/integrations/vue/test/app-entrypoint-css.test.js @@ -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); + }); + }) +}); diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint-css/astro.config.mjs b/packages/integrations/vue/test/fixtures/app-entrypoint-css/astro.config.mjs new file mode 100644 index 000000000000..c4a9f8f33f57 --- /dev/null +++ b/packages/integrations/vue/test/fixtures/app-entrypoint-css/astro.config.mjs @@ -0,0 +1,8 @@ +import { defineConfig } from 'astro/config'; +import vue from '@astrojs/vue'; + +export default defineConfig({ + integrations: [ + vue({ appEntrypoint: '/src/app.ts' }) + ], +}) diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint-css/package.json b/packages/integrations/vue/test/fixtures/app-entrypoint-css/package.json new file mode 100644 index 000000000000..b34b4b99c50f --- /dev/null +++ b/packages/integrations/vue/test/fixtures/app-entrypoint-css/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/vue-app-entrypoint-css", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/vue": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/app.ts b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/app.ts new file mode 100644 index 000000000000..05742cb890fe --- /dev/null +++ b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/app.ts @@ -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); +} diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/components/Bar.vue b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/components/Bar.vue new file mode 100644 index 000000000000..9e690ea06adc --- /dev/null +++ b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/components/Bar.vue @@ -0,0 +1,3 @@ + diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/components/Foo.vue b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/components/Foo.vue new file mode 100644 index 000000000000..3e648808cb72 --- /dev/null +++ b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/components/Foo.vue @@ -0,0 +1,5 @@ + diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/main.css b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/main.css new file mode 100644 index 000000000000..5c197d2cfeda --- /dev/null +++ b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/main.css @@ -0,0 +1,3 @@ +:root { + background-color: red; +} diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/pages/index.astro b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/pages/index.astro new file mode 100644 index 000000000000..3240cbe0fd73 --- /dev/null +++ b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/pages/index.astro @@ -0,0 +1,12 @@ +--- +import Foo from '../components/Foo.vue'; +--- + + + + Vue App Entrypoint + + + + + diff --git a/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/pages/unrelated.astro b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/pages/unrelated.astro new file mode 100644 index 000000000000..0952e25a76f1 --- /dev/null +++ b/packages/integrations/vue/test/fixtures/app-entrypoint-css/src/pages/unrelated.astro @@ -0,0 +1,8 @@ + + + Unrelated page + + +

I shouldn't have styles

+ + From 41dd52ae660b2478fcf50ee5a959b68970d5d3ef Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 20 Dec 2023 12:51:48 -0600 Subject: [PATCH 3/7] chore: add changeset --- .changeset/odd-rivers-learn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/odd-rivers-learn.md diff --git a/.changeset/odd-rivers-learn.md b/.changeset/odd-rivers-learn.md new file mode 100644 index 000000000000..3f11720b96d5 --- /dev/null +++ b/.changeset/odd-rivers-learn.md @@ -0,0 +1,5 @@ +--- +'@astrojs/vue': patch +--- + +Fixes a bug that caused styles referenced by `appEntrypoint` to be excluded from the build From 87ec09dbf7106a088046de876e37e2bc66dc15fd Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 20 Dec 2023 14:47:12 -0600 Subject: [PATCH 4/7] fix: windows handling --- packages/integrations/vue/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/integrations/vue/src/index.ts b/packages/integrations/vue/src/index.ts index 09df5a30f68f..80d9ddbc7890 100644 --- a/packages/integrations/vue/src/index.ts +++ b/packages/integrations/vue/src/index.ts @@ -51,8 +51,9 @@ function virtualAppEntrypoint(options?: Options): Plugin { root = config.root; if (options?.appEntrypoint) { appEntrypoint = options.appEntrypoint.startsWith('.') - ? path.resolve(root, options.appEntrypoint) + ? path.resolve(root, options.appEntrypoint).replace(root, '') : options.appEntrypoint; + appEntrypoint = appEntrypoint?.replaceAll('\\', '/') } }, resolveId(id: string) { From 973d559528a684bbda4b4891115e0d6b4a31fdae Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 5 Jan 2024 08:56:21 -0600 Subject: [PATCH 5/7] Update packages/integrations/vue/src/index.ts Co-authored-by: Bjorn Lu --- packages/integrations/vue/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/vue/src/index.ts b/packages/integrations/vue/src/index.ts index 80d9ddbc7890..d05472423449 100644 --- a/packages/integrations/vue/src/index.ts +++ b/packages/integrations/vue/src/index.ts @@ -94,7 +94,7 @@ export const setup = async (app) => { s.prepend(`import "${appEntrypoint}";\n`); return { code: s.toString(), - map: s.generateMap() + map: s.generateMap({ hires: 'boundary' }) } } }, From bdacfc3363ce3d502d43d7fba2052c55901ac87c Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 5 Jan 2024 08:59:37 -0600 Subject: [PATCH 6/7] chore: address review feedback --- packages/integrations/vue/src/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/integrations/vue/src/index.ts b/packages/integrations/vue/src/index.ts index d05472423449..3d34e084ac0d 100644 --- a/packages/integrations/vue/src/index.ts +++ b/packages/integrations/vue/src/index.ts @@ -51,9 +51,8 @@ function virtualAppEntrypoint(options?: Options): Plugin { root = config.root; if (options?.appEntrypoint) { appEntrypoint = options.appEntrypoint.startsWith('.') - ? path.resolve(root, options.appEntrypoint).replace(root, '') + ? path.resolve(root, options.appEntrypoint) : options.appEntrypoint; - appEntrypoint = appEntrypoint?.replaceAll('\\', '/') } }, resolveId(id: string) { @@ -91,7 +90,7 @@ export const setup = async (app) => { if (!appEntrypoint) return; if (id.endsWith('.vue')) { const s = new MagicString(code); - s.prepend(`import "${appEntrypoint}";\n`); + s.prepend(`import ${JSON.stringify(appEntrypoint)};\n`); return { code: s.toString(), map: s.generateMap({ hires: 'boundary' }) From c6c82f2334670a86544dcda43a93cfefb9c96c6d Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 5 Jan 2024 09:00:42 -0600 Subject: [PATCH 7/7] chore: update lockfile --- pnpm-lock.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 260a33c040e3..db8072172bcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4906,6 +4906,15 @@ importers: specifier: 5.0.1 version: 5.0.1 + packages/integrations/vue/test/fixtures/app-entrypoint-css: + dependencies: + '@astrojs/vue': + specifier: workspace:* + version: link:../../.. + astro: + specifier: workspace:* + version: link:../../../../../astro + packages/integrations/vue/test/fixtures/app-entrypoint-no-export-default: dependencies: '@astrojs/vue':