From 0ce86dfdf3468a45e3f0a083ed41a5191e75f914 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 17 Nov 2021 15:57:47 -0500 Subject: [PATCH] Fix for built scoped Vue styles (#1868) * Fixes #1844 * Adds a changeset * Remove all special casing * Add a clarifying comment --- .changeset/serious-knives-hug.md | 5 ++ .../astro/src/vite-plugin-build-css/index.ts | 73 +++++++++++++------ packages/astro/test/astro-styles-ssr.test.js | 37 +++++----- 3 files changed, 75 insertions(+), 40 deletions(-) create mode 100644 .changeset/serious-knives-hug.md diff --git a/.changeset/serious-knives-hug.md b/.changeset/serious-knives-hug.md new file mode 100644 index 000000000..338be0974 --- /dev/null +++ b/.changeset/serious-knives-hug.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes Vue scoped styles when built diff --git a/packages/astro/src/vite-plugin-build-css/index.ts b/packages/astro/src/vite-plugin-build-css/index.ts index 4c96167fa..1f560b130 100644 --- a/packages/astro/src/vite-plugin-build-css/index.ts +++ b/packages/astro/src/vite-plugin-build-css/index.ts @@ -1,8 +1,7 @@ import type { RenderedChunk } from 'rollup'; -import type { Plugin as VitePlugin } from '../core/vite'; +import { Plugin as VitePlugin } from '../core/vite'; import { STYLE_EXTENSIONS } from '../core/ssr/css.js'; -import { getViteTransform, TransformHook } from '../vite-plugin-astro/styles.js'; import * as path from 'path'; import esbuild from 'esbuild'; @@ -56,22 +55,30 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin { const { astroPageStyleMap, astroStyleMap, chunkToReferenceIdMap, pureCSSChunks } = options; const styleSourceMap = new Map(); - let viteTransform: TransformHook; - return { name: PLUGIN_NAME, - enforce: 'pre', - configResolved(resolvedConfig) { - viteTransform = getViteTransform(resolvedConfig); - - const viteCSSPost = resolvedConfig.plugins.find((p) => p.name === 'vite:css-post'); - if (viteCSSPost) { + // Our plugin needs to run before `vite:css-post` which does a lot of what we do + // for bundling CSS, but since we need to control CSS we should go first. + // We move to right before the vite:css-post plugin so that things like the + // Vue plugin go before us. + const plugins = resolvedConfig.plugins as VitePlugin[]; + const viteCSSPostIndex = resolvedConfig.plugins.findIndex((p) => p.name === 'vite:css-post'); + if (viteCSSPostIndex !== -1) { + const viteCSSPost = plugins[viteCSSPostIndex]; // Prevent this plugin's bundling behavior from running since we need to // do that ourselves in order to handle updating the HTML. delete viteCSSPost.renderChunk; delete viteCSSPost.generateBundle; + + // Move our plugin to be right before this one. + const ourIndex = plugins.findIndex(p => p.name === PLUGIN_NAME); + const ourPlugin = plugins[ourIndex]; + + // Remove us from where we are now and place us right before the viteCSSPost plugin + plugins.splice(ourIndex, 1); + plugins.splice(viteCSSPostIndex - 1, 0, ourPlugin); } }, @@ -101,15 +108,9 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin { styleSourceMap.set(id, value); return null; } - if (isCSSRequest(id)) { - let result = await viteTransform(value, id); - if (result) { - styleSourceMap.set(id, result.code); - } else { - styleSourceMap.set(id, value); - } - return result; + if (isCSSRequest(id)) { + styleSourceMap.set(id, value); } return null; @@ -145,12 +146,40 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin { }, // Delete CSS chunks so JS is not produced for them. - generateBundle(_options, bundle) { - for (const [chunkId, chunk] of Object.entries(bundle)) { - if (chunk.type === 'chunk' && pureCSSChunks.has(chunk)) { - delete bundle[chunkId]; + generateBundle(opts, bundle) { + if(pureCSSChunks.size) { + const pureChunkFilenames = new Set([...pureCSSChunks].map((chunk) => chunk.fileName)); + const emptyChunkFiles = [...pureChunkFilenames] + .map((file) => path.basename(file)) + .join('|') + .replace(/\./g, '\\.') + const emptyChunkRE = new RegExp( + opts.format === 'es' + ? `\\bimport\\s*"[^"]*(?:${emptyChunkFiles})";\n?` + : `\\brequire\\(\\s*"[^"]*(?:${emptyChunkFiles})"\\);\n?`, + 'g' + ) + + for (const [chunkId, chunk] of Object.entries(bundle)) { + if (chunk.type === 'chunk') { + if(pureCSSChunks.has(chunk)) { + // Delete pure CSS chunks, these are JavaScript chunks that only import + // other CSS files, so are empty at the end of bundling. + delete bundle[chunkId]; + } else { + // Remove any pure css chunk imports from JavaScript. + // Note that this code comes from Vite's CSS build plugin. + chunk.code = chunk.code.replace( + emptyChunkRE, + // remove css import while preserving source map location + (m) => `/* empty css ${''.padEnd(m.length - 15)}*/` + ); + } + } } } + + }, }; } diff --git a/packages/astro/test/astro-styles-ssr.test.js b/packages/astro/test/astro-styles-ssr.test.js index 7241d4095..0b7fba6c7 100644 --- a/packages/astro/test/astro-styles-ssr.test.js +++ b/packages/astro/test/astro-styles-ssr.test.js @@ -2,25 +2,27 @@ import { expect } from 'chai'; import cheerio from 'cheerio'; import { loadFixture } from './test-utils.js'; -let fixture; -let index$; -let bundledCSS; +describe('Styles SSR', function() { + this.timeout(5000); -before(async () => { - fixture = await loadFixture({ - projectRoot: './fixtures/astro-styles-ssr/', - renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'], + let fixture; + let index$; + let bundledCSS; + + before(async () => { + fixture = await loadFixture({ + projectRoot: './fixtures/astro-styles-ssr/', + renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'], + }); + await fixture.build(); + + // get bundled CSS (will be hashed, hence DOM query) + const html = await fixture.readFile('/index.html'); + index$ = cheerio.load(html); + const bundledCSSHREF = index$('link[rel=stylesheet][href^=assets/]').attr('href'); + bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')); }); - await fixture.build(); - // get bundled CSS (will be hashed, hence DOM query) - const html = await fixture.readFile('/index.html'); - index$ = cheerio.load(html); - const bundledCSSHREF = index$('link[rel=stylesheet][href^=assets/]').attr('href'); - bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')); -}); - -describe('Styles SSR', () => { describe('Astro styles', () => { it('HTML and CSS scoped correctly', async () => { const $ = index$; @@ -94,8 +96,7 @@ describe('Styles SSR', () => { expect(bundledCSS).to.include('.vue-title{'); }); - // TODO: fix Vue scoped styles in build bug - it.skip('Scoped styles', async () => { + it('Scoped styles', async () => { const $ = index$; const el = $('#vue-scoped');