Fix for built scoped Vue styles (#1868)
* Fixes #1844 * Adds a changeset * Remove all special casing * Add a clarifying comment
This commit is contained in:
parent
6b598b2401
commit
0ce86dfdf3
3 changed files with 75 additions and 40 deletions
5
.changeset/serious-knives-hug.md
Normal file
5
.changeset/serious-knives-hug.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes Vue scoped styles when built
|
|
@ -1,8 +1,7 @@
|
||||||
import type { RenderedChunk } from 'rollup';
|
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 { STYLE_EXTENSIONS } from '../core/ssr/css.js';
|
||||||
import { getViteTransform, TransformHook } from '../vite-plugin-astro/styles.js';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
|
|
||||||
|
@ -56,22 +55,30 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
||||||
const { astroPageStyleMap, astroStyleMap, chunkToReferenceIdMap, pureCSSChunks } = options;
|
const { astroPageStyleMap, astroStyleMap, chunkToReferenceIdMap, pureCSSChunks } = options;
|
||||||
const styleSourceMap = new Map<string, string>();
|
const styleSourceMap = new Map<string, string>();
|
||||||
|
|
||||||
let viteTransform: TransformHook;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: PLUGIN_NAME,
|
name: PLUGIN_NAME,
|
||||||
|
|
||||||
enforce: 'pre',
|
|
||||||
|
|
||||||
configResolved(resolvedConfig) {
|
configResolved(resolvedConfig) {
|
||||||
viteTransform = getViteTransform(resolvedConfig);
|
// 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.
|
||||||
const viteCSSPost = resolvedConfig.plugins.find((p) => p.name === 'vite:css-post');
|
// We move to right before the vite:css-post plugin so that things like the
|
||||||
if (viteCSSPost) {
|
// 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
|
// Prevent this plugin's bundling behavior from running since we need to
|
||||||
// do that ourselves in order to handle updating the HTML.
|
// do that ourselves in order to handle updating the HTML.
|
||||||
delete viteCSSPost.renderChunk;
|
delete viteCSSPost.renderChunk;
|
||||||
delete viteCSSPost.generateBundle;
|
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);
|
styleSourceMap.set(id, value);
|
||||||
return null;
|
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;
|
return null;
|
||||||
|
@ -145,12 +146,40 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Delete CSS chunks so JS is not produced for them.
|
// Delete CSS chunks so JS is not produced for them.
|
||||||
generateBundle(_options, bundle) {
|
generateBundle(opts, bundle) {
|
||||||
for (const [chunkId, chunk] of Object.entries(bundle)) {
|
if(pureCSSChunks.size) {
|
||||||
if (chunk.type === 'chunk' && pureCSSChunks.has(chunk)) {
|
const pureChunkFilenames = new Set([...pureCSSChunks].map((chunk) => chunk.fileName));
|
||||||
delete bundle[chunkId];
|
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)}*/`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,27 @@ import { expect } from 'chai';
|
||||||
import cheerio from 'cheerio';
|
import cheerio from 'cheerio';
|
||||||
import { loadFixture } from './test-utils.js';
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
let fixture;
|
describe('Styles SSR', function() {
|
||||||
let index$;
|
this.timeout(5000);
|
||||||
let bundledCSS;
|
|
||||||
|
|
||||||
before(async () => {
|
let fixture;
|
||||||
fixture = await loadFixture({
|
let index$;
|
||||||
projectRoot: './fixtures/astro-styles-ssr/',
|
let bundledCSS;
|
||||||
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
|
|
||||||
|
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', () => {
|
describe('Astro styles', () => {
|
||||||
it('HTML and CSS scoped correctly', async () => {
|
it('HTML and CSS scoped correctly', async () => {
|
||||||
const $ = index$;
|
const $ = index$;
|
||||||
|
@ -94,8 +96,7 @@ describe('Styles SSR', () => {
|
||||||
expect(bundledCSS).to.include('.vue-title{');
|
expect(bundledCSS).to.include('.vue-title{');
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: fix Vue scoped styles in build bug
|
it('Scoped styles', async () => {
|
||||||
it.skip('Scoped styles', async () => {
|
|
||||||
const $ = index$;
|
const $ = index$;
|
||||||
const el = $('#vue-scoped');
|
const el = $('#vue-scoped');
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue