mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Fix VITE_ASSET bug (#3439)
* Fix VITE_ASSET bug * Updated test that depended on esbuild output * Fix some more tests * Fix css config and postcss tests * Git client only working * Fix static build test * Update tailwind tests * Fix build * Fix css bundling tests * Updated some more tests for windows * Remove tests that are no longer relevant * Cause it to break * Fix bug and add explanation * Adds a changeset * Inline comments about what the hashing is doing * Update packages/astro/src/vite-plugin-build-css/index.ts Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> * Update to the lockfile * Minify css * Update tailwind tests Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
parent
2f4ee560dd
commit
ac3c60d48d
29 changed files with 336 additions and 420 deletions
7
.changeset/red-knives-sit.md
Normal file
7
.changeset/red-knives-sit.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes importing npm packages within CSS
|
||||
|
||||
This change fixes a longstanding bug where the string `VITE_ASSET` was left in CSS when trying to import CSS packages. The fix comes thanks to an upstream Vite feature that allows us to hand off most of the CSS bundling work to Vite.
|
|
@ -77,7 +77,7 @@
|
|||
"test:e2e:match": "playwright test -g"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^0.15.0",
|
||||
"@astrojs/compiler": "^0.15.1",
|
||||
"@astrojs/language-server": "^0.13.4",
|
||||
"@astrojs/markdown-remark": "^0.10.1",
|
||||
"@astrojs/prism": "0.4.1",
|
||||
|
|
|
@ -112,19 +112,17 @@ export function* getPageDatasByChunk(
|
|||
}
|
||||
}
|
||||
|
||||
export function* getPageDatasByClientOnlyChunk(
|
||||
export function* getPageDatasByClientOnlyID(
|
||||
internals: BuildInternals,
|
||||
chunk: RenderedChunk
|
||||
viteid: ViteID
|
||||
): Generator<PageBuildData, void, unknown> {
|
||||
const pagesByClientOnly = internals.pagesByClientOnly;
|
||||
if (pagesByClientOnly.size) {
|
||||
for (const [modulePath] of Object.entries(chunk.modules)) {
|
||||
// prepend with `/@fs` to match the path used in the compiler's transform() call
|
||||
const pathname = `/@fs${prependForwardSlash(modulePath)}`;
|
||||
if (pagesByClientOnly.has(pathname)) {
|
||||
for (const pageData of pagesByClientOnly.get(pathname)!) {
|
||||
yield pageData;
|
||||
}
|
||||
const pathname = `/@fs${prependForwardSlash(viteid)}`;
|
||||
const pageBuildDatas = pagesByClientOnly.get(pathname)
|
||||
if(pageBuildDatas) {
|
||||
for(const pageData of pageBuildDatas) {
|
||||
yield pageData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,12 @@
|
|||
import { BuildInternals } from '../core/build/internal';
|
||||
import type { ModuleInfo, PluginContext } from 'rollup';
|
||||
import type { GetModuleInfo, ModuleInfo } from 'rollup';
|
||||
import type { PageBuildData } from '../core/build/types';
|
||||
|
||||
import * as path from 'path';
|
||||
import esbuild from 'esbuild';
|
||||
import { Plugin as VitePlugin } from 'vite';
|
||||
import { isCSSRequest } from '../core/render/util.js';
|
||||
import {
|
||||
getPageDatasByChunk,
|
||||
getPageDataByViteID,
|
||||
hasPageDataByViteID,
|
||||
getPageDatasByClientOnlyChunk,
|
||||
} from '../core/build/internal.js';
|
||||
|
||||
const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css';
|
||||
|
||||
// This is a virtual module that represents the .astro <style> usage on a page
|
||||
const ASTRO_STYLE_PREFIX = '@astro-inline-style';
|
||||
|
||||
const ASTRO_PAGE_STYLE_PREFIX = '@astro-page-all-styles';
|
||||
|
||||
function isStyleVirtualModule(id: string) {
|
||||
return id.startsWith(ASTRO_STYLE_PREFIX);
|
||||
}
|
||||
|
||||
function isPageStyleVirtualModule(id: string) {
|
||||
return id.startsWith(ASTRO_PAGE_STYLE_PREFIX);
|
||||
}
|
||||
|
||||
function isRawOrUrlModule(id: string) {
|
||||
return id.match(/(\?|\&)([^=]+)(raw|url)/gm);
|
||||
}
|
||||
import { getPageDataByViteID, getPageDatasByClientOnlyID } from '../core/build/internal.js';
|
||||
import { resolvedPagesVirtualModuleId } from '../core/app/index.js';
|
||||
import crypto from 'crypto';
|
||||
|
||||
interface PluginOptions {
|
||||
internals: BuildInternals;
|
||||
|
@ -38,82 +15,84 @@ interface PluginOptions {
|
|||
|
||||
export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
||||
const { internals } = options;
|
||||
const styleSourceMap = new Map<string, string>();
|
||||
|
||||
function* walkStyles(
|
||||
ctx: PluginContext,
|
||||
id: string,
|
||||
seen = new Set<string>()
|
||||
): Generator<[string, string], void, unknown> {
|
||||
// This walks up the dependency graph and yields out each ModuleInfo object.
|
||||
function* walkParentInfos(id: string, ctx: {getModuleInfo: GetModuleInfo}, seen = new Set<string>()): Generator<ModuleInfo, void, unknown> {
|
||||
seen.add(id);
|
||||
if (styleSourceMap.has(id)) {
|
||||
yield [id, styleSourceMap.get(id)!];
|
||||
}
|
||||
|
||||
const info = ctx.getModuleInfo(id);
|
||||
if (info) {
|
||||
for (const importedId of [...info.importedIds, ...info.dynamicallyImportedIds]) {
|
||||
if (!seen.has(importedId) && !isRawOrUrlModule(importedId)) {
|
||||
yield* walkStyles(ctx, importedId, seen);
|
||||
}
|
||||
if(info) {
|
||||
yield info;
|
||||
}
|
||||
const importers = (info?.importers || []).concat(info?.dynamicImporters || []);
|
||||
for(const imp of importers) {
|
||||
if(seen.has(imp)) {
|
||||
continue;
|
||||
}
|
||||
yield * walkParentInfos(imp, ctx, seen);
|
||||
}
|
||||
}
|
||||
|
||||
// This function walks the dependency graph, going up until it finds a page component.
|
||||
// This could be a .astro page or a .md page.
|
||||
function* getTopLevelPages(id: string, ctx: {getModuleInfo: GetModuleInfo}): Generator<string, void, unknown> {
|
||||
for(const info of walkParentInfos(id, ctx)) {
|
||||
const importers = (info?.importers || []).concat(info?.dynamicImporters || []);
|
||||
if(importers.length <= 2 && importers[0] === resolvedPagesVirtualModuleId) {
|
||||
yield info.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This walks the dependency graph looking for styles that are imported
|
||||
* by a page and then creates a chunking containing all of the styles for that page.
|
||||
* Since there is only 1 entrypoint for the entire app, we do this in order
|
||||
* to prevent adding all styles to all pages.
|
||||
*/
|
||||
async function addStyles(this: PluginContext) {
|
||||
for (const id of this.getModuleIds()) {
|
||||
if (hasPageDataByViteID(internals, id)) {
|
||||
let pageStyles = '';
|
||||
for (const [_styleId, styles] of walkStyles(this, id)) {
|
||||
pageStyles += styles;
|
||||
}
|
||||
function createHashOfPageParents(id: string, ctx: {getModuleInfo: GetModuleInfo}): string {
|
||||
const parents = Array.from(getTopLevelPages(id, ctx)).sort();
|
||||
const hash = crypto.createHash('sha256');
|
||||
for(const page of parents) {
|
||||
hash.update(page, 'utf-8');
|
||||
}
|
||||
return hash.digest('hex').slice(0, 8);
|
||||
}
|
||||
|
||||
// Pages with no styles, nothing more to do
|
||||
if (!pageStyles) continue;
|
||||
|
||||
const { code: minifiedCSS } = await esbuild.transform(pageStyles, {
|
||||
loader: 'css',
|
||||
minify: true,
|
||||
});
|
||||
const referenceId = this.emitFile({
|
||||
name: 'entry' + '.css',
|
||||
type: 'asset',
|
||||
source: minifiedCSS,
|
||||
});
|
||||
const fileName = this.getFileName(referenceId);
|
||||
|
||||
// Add CSS file to the page's pageData, so that it will be rendered with
|
||||
// the correct links.
|
||||
getPageDataByViteID(internals, id)?.css.add(fileName);
|
||||
}
|
||||
function* getParentClientOnlys(id: string, ctx: {getModuleInfo: GetModuleInfo}): Generator<PageBuildData, void, unknown> {
|
||||
for(const info of walkParentInfos(id, ctx)) {
|
||||
yield * getPageDatasByClientOnlyID(internals, info.id);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
name: '@astrojs/rollup-plugin-build-css',
|
||||
|
||||
configResolved(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.
|
||||
// We move to right before the vite:css-post plugin so that things like the
|
||||
// Vue plugin go before us.
|
||||
// Our plugin needs to run before `vite:css-post` because we have to modify
|
||||
// The bundles before vite:css-post sees them. We can remove this code
|
||||
// after this bug is fixed: https://github.com/vitejs/vite/issues/8330
|
||||
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;
|
||||
|
||||
// Wrap the renderChunk hook in CSSPost to enable minification.
|
||||
// We do this instead of setting minification globally to avoid minifying
|
||||
// server JS.
|
||||
const renderChunk = viteCSSPost.renderChunk;
|
||||
if(renderChunk) {
|
||||
viteCSSPost.renderChunk = async function(...args) {
|
||||
const minifyOption = resolvedConfig.build.minify;
|
||||
if(minifyOption === false) {
|
||||
resolvedConfig.build.minify = 'esbuild';
|
||||
}
|
||||
const result = await renderChunk.apply(this, args);
|
||||
if(typeof result === 'string') {
|
||||
return {
|
||||
code: result
|
||||
};
|
||||
}
|
||||
resolvedConfig.build.minify = minifyOption;
|
||||
return result || null;
|
||||
};
|
||||
}
|
||||
|
||||
// Move our plugin to be right before this one.
|
||||
const ourIndex = plugins.findIndex((p) => p.name === PLUGIN_NAME);
|
||||
const ourIndex = plugins.findIndex((p) => p.name === '@astrojs/rollup-plugin-build-css');
|
||||
const ourPlugin = plugins[ourIndex];
|
||||
|
||||
// Remove us from where we are now and place us right before the viteCSSPost plugin
|
||||
|
@ -121,110 +100,80 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
|||
plugins.splice(viteCSSPostIndex - 1, 0, ourPlugin);
|
||||
}
|
||||
},
|
||||
async resolveId(id) {
|
||||
if (isPageStyleVirtualModule(id)) {
|
||||
return id;
|
||||
}
|
||||
if (isStyleVirtualModule(id)) {
|
||||
return id;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
outputOptions(outputOptions) {
|
||||
const manualChunks = outputOptions.manualChunks || Function.prototype;
|
||||
outputOptions.manualChunks = function(id, ...args) {
|
||||
// Defer to user-provided `manualChunks`, if it was provided.
|
||||
if(typeof manualChunks == 'object') {
|
||||
if(id in manualChunks) {
|
||||
return manualChunks[id];
|
||||
}
|
||||
} else if(typeof manualChunks === 'function') {
|
||||
const outid = manualChunks.call(this, id, ...args);
|
||||
if(outid) {
|
||||
return outid;
|
||||
}
|
||||
}
|
||||
|
||||
// For CSS, create a hash of all of the pages that use it.
|
||||
// This causes CSS to be built into shared chunks when used by multiple pages.
|
||||
if (isCSSRequest(id)) {
|
||||
return createHashOfPageParents(id, args[0]);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async transform(value, id) {
|
||||
if (isStyleVirtualModule(id)) {
|
||||
styleSourceMap.set(id, value);
|
||||
}
|
||||
if (isCSSRequest(id)) {
|
||||
styleSourceMap.set(id, value);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
async renderChunk(_code, chunk) {
|
||||
if (options.target === 'server') return null;
|
||||
|
||||
let chunkCSS = '';
|
||||
let isPureCSS = true;
|
||||
for (const [id] of Object.entries(chunk.modules)) {
|
||||
if (!isCSSRequest(id) && !isPageStyleVirtualModule(id)) {
|
||||
isPureCSS = false;
|
||||
}
|
||||
if (styleSourceMap.has(id)) {
|
||||
chunkCSS += styleSourceMap.get(id)!;
|
||||
}
|
||||
async generateBundle(_outputOptions, bundle) {
|
||||
type ViteMetadata = {
|
||||
importedAssets: Set<string>;
|
||||
importedCss: Set<string>;
|
||||
}
|
||||
|
||||
if (!chunkCSS) return null; // don’t output empty .css files
|
||||
for (const [_, chunk] of Object.entries(bundle)) {
|
||||
if(chunk.type === 'chunk') {
|
||||
const c = chunk;
|
||||
if('viteMetadata' in chunk) {
|
||||
const meta = chunk['viteMetadata'] as ViteMetadata;
|
||||
|
||||
if (isPureCSS) {
|
||||
internals.pureCSSChunks.add(chunk);
|
||||
}
|
||||
// Chunks that have the viteMetadata.importedCss are CSS chunks
|
||||
if(meta.importedCss.size) {
|
||||
// For the client build, client:only styles need to be mapped
|
||||
// over to their page. For this chunk, determine if it's a child of a
|
||||
// client:only component and if so, add its CSS to the page it belongs to.
|
||||
if(options.target === 'client') {
|
||||
for(const [id] of Object.entries(c.modules)) {
|
||||
for(const pageData of getParentClientOnlys(id, this)) {
|
||||
for(const importedCssImport of meta.importedCss) {
|
||||
pageData.css.add(importedCssImport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { code: minifiedCSS } = await esbuild.transform(chunkCSS, {
|
||||
loader: 'css',
|
||||
minify: true,
|
||||
});
|
||||
const referenceId = this.emitFile({
|
||||
name: chunk.name + '.css',
|
||||
type: 'asset',
|
||||
source: minifiedCSS,
|
||||
});
|
||||
|
||||
if (chunk.type === 'chunk') {
|
||||
const fileName = this.getFileName(referenceId);
|
||||
for (const pageData of getPageDatasByChunk(internals, chunk)) {
|
||||
pageData.css.add(fileName);
|
||||
}
|
||||
// Adds this CSS for client:only components to the appropriate page
|
||||
for (const pageData of getPageDatasByClientOnlyChunk(internals, chunk)) {
|
||||
pageData.css.add(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Delete CSS chunks so JS is not produced for them.
|
||||
async generateBundle(opts, bundle) {
|
||||
const hasPureCSSChunks = internals.pureCSSChunks.size;
|
||||
const pureChunkFilenames = new Set(
|
||||
[...internals.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'
|
||||
);
|
||||
|
||||
// Crawl the module graph to find CSS chunks to create
|
||||
await addStyles.call(this);
|
||||
|
||||
for (const [chunkId, chunk] of Object.entries(bundle)) {
|
||||
if (chunk.type === 'chunk') {
|
||||
// Removes imports for pure CSS chunks.
|
||||
if (hasPureCSSChunks) {
|
||||
if (internals.pureCSSChunks.has(chunk) && !chunk.exports.length) {
|
||||
// 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)}*/`
|
||||
);
|
||||
// For this CSS chunk, walk parents until you find a page. Add the CSS to that page.
|
||||
for(const [id] of Object.entries(c.modules)) {
|
||||
for(const pageViteID of getTopLevelPages(id, this)) {
|
||||
const pageData = getPageDataByViteID(internals, pageViteID);
|
||||
for(const importedCssImport of meta.importedCss) {
|
||||
pageData?.css.add(importedCssImport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.type === 'chunk') {
|
||||
// This simply replaces single quotes with double quotes because the vite:css-post
|
||||
// plugin only works with single for some reason. This code can be removed
|
||||
// When the Vite bug is fixed: https://github.com/vitejs/vite/issues/8330
|
||||
const exp = new RegExp(`(\\bimport\\s*)[']([^']*(?:[a-z]+\.[0-9a-z]+\.m?js))['](;\n?)`, 'g');
|
||||
chunk.code = chunk.code.replace(exp, (_match, begin, chunkPath, end) => {
|
||||
return begin + '"' + chunkPath + '"' + end;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ describe('CSS', function () {
|
|||
const html = await fixture.readFile('/index.html');
|
||||
$ = cheerio.load(html);
|
||||
const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href');
|
||||
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
|
||||
bundledCSS = (await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')))
|
||||
.replace(/\s/g, '').replace('/n', '');
|
||||
});
|
||||
|
||||
describe('Astro Styles', () => {
|
||||
|
@ -102,7 +103,7 @@ describe('CSS', function () {
|
|||
expect(el.attr('class')).to.include(moduleClass);
|
||||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`));
|
||||
expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy`));
|
||||
});
|
||||
|
||||
it('.sass', async () => {
|
||||
|
@ -112,7 +113,7 @@ describe('CSS', function () {
|
|||
expect(el.attr('class')).to.include('react-sass-title');
|
||||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(new RegExp(`.react-sass-title[^{]*{font-family:fantasy}`));
|
||||
expect(bundledCSS).to.match(new RegExp(`.react-sass-title[^{]*{font-family:fantasy`));
|
||||
});
|
||||
|
||||
it('.scss', async () => {
|
||||
|
@ -122,7 +123,7 @@ describe('CSS', function () {
|
|||
expect(el.attr('class')).to.include('react-scss-title');
|
||||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(new RegExp(`.react-scss-title[^{]*{font-family:fantasy}`));
|
||||
expect(bundledCSS).to.match(new RegExp(`.react-scss-title[^{]*{font-family:fantasy`));
|
||||
});
|
||||
|
||||
it('.module.sass', async () => {
|
||||
|
@ -134,7 +135,7 @@ describe('CSS', function () {
|
|||
expect(el.attr('class')).to.include(moduleClass);
|
||||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`));
|
||||
expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy`));
|
||||
});
|
||||
|
||||
it('.module.scss', async () => {
|
||||
|
@ -146,7 +147,7 @@ describe('CSS', function () {
|
|||
expect(el.attr('class')).to.include(moduleClass);
|
||||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`));
|
||||
expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy`));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -185,7 +186,7 @@ describe('CSS', function () {
|
|||
expect(el.attr('class')).to.include(moduleClass);
|
||||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:cursive}`));
|
||||
expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:cursive`));
|
||||
});
|
||||
|
||||
it('<style lang="sass">', async () => {
|
||||
|
@ -222,7 +223,7 @@ describe('CSS', function () {
|
|||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(
|
||||
new RegExp(`.svelte-css.${scopedClass}[^{]*{font-family:Comic Sans MS`)
|
||||
new RegExp(`.svelte-css.${scopedClass}[^{]*{font-family:ComicSansMS`)
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -238,7 +239,7 @@ describe('CSS', function () {
|
|||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(
|
||||
new RegExp(`.svelte-sass.${scopedClass}[^{]*{font-family:Comic Sans MS`)
|
||||
new RegExp(`.svelte-sass.${scopedClass}[^{]*{font-family:ComicSansMS`)
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -254,7 +255,7 @@ describe('CSS', function () {
|
|||
|
||||
// 2. check CSS
|
||||
expect(bundledCSS).to.match(
|
||||
new RegExp(`.svelte-scss.${scopedClass}[^{]*{font-family:Comic Sans MS`)
|
||||
new RegExp(`.svelte-scss.${scopedClass}[^{]*{font-family:ComicSansMS`)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,7 +28,12 @@ describe('Client only components', () => {
|
|||
it('Adds the CSS to the page', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerioLoad(html);
|
||||
expect($('link[rel=stylesheet]')).to.have.lengthOf(2);
|
||||
|
||||
const href = $('link[rel=stylesheet]').attr('href');
|
||||
const css = await fixture.readFile(href);
|
||||
|
||||
expect(css).to.match(/yellowgreen/, 'Svelte styles are added');
|
||||
expect(css).to.match(/Courier New/, 'Global styles are added');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -60,6 +65,11 @@ describe('Client only components subpath', () => {
|
|||
it('Adds the CSS to the page', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerioLoad(html);
|
||||
expect($('link[rel=stylesheet]')).to.have.lengthOf(2);
|
||||
|
||||
const href = $('link[rel=stylesheet]').attr('href');
|
||||
const css = await fixture.readFile(href.replace(/\/blog/, ''));
|
||||
|
||||
expect(css).to.match(/yellowgreen/, 'Svelte styles are added');
|
||||
expect(css).to.match(/Courier New/, 'Global styles are added');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('CSS Bundling (ESM import)', () => {
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/astro-css-bundling-import/',
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('CSS output in import order', async () => {
|
||||
// note: this test is a little confusing, but the main idea is that
|
||||
// page-2.astro contains all of page-1.astro, plus some unique styles.
|
||||
// we only test page-2 to ensure the proper order is observed.
|
||||
const html = await fixture.readFile('/page-2/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
let css = '';
|
||||
|
||||
for (const style of $('link[rel=stylesheet]')) {
|
||||
const href = style.attribs.href.replace(/^\.\./, '');
|
||||
if (!href) continue;
|
||||
css += await fixture.readFile(href);
|
||||
}
|
||||
|
||||
// test 1: insure green comes after red (site.css)
|
||||
expect(css.indexOf('p{color:green}')).to.be.greaterThan(css.indexOf('p{color:red}'));
|
||||
|
||||
// test 2: insure green comes after blue (page-1.css)
|
||||
expect(css.indexOf('p{color:green}')).to.be.greaterThan(css.indexOf('p{color:#00f}'));
|
||||
});
|
||||
|
||||
it('no empty CSS files', async () => {
|
||||
for (const page of ['/page-1/index.html', '/page-2/index.html']) {
|
||||
const html = await fixture.readFile(page);
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
for (const style of $('link[rel=stylesheet]')) {
|
||||
const href = style.attribs.href.replace(/^\.\./, '');
|
||||
if (!href) continue;
|
||||
const css = await fixture.readFile(href);
|
||||
|
||||
expect(css).to.be.ok;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('?raw and ?url CSS imports are ignored', async () => {
|
||||
// note: this test is a little confusing as well, but the main idea is that
|
||||
// page-3.astro should have site.css imported as an ESM in InlineLayout.astro
|
||||
// as well as the styles from page-3.css as an inline <style>.
|
||||
const html = await fixture.readFile('/page-3/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
let css = '';
|
||||
|
||||
for (const style of $('link[rel=stylesheet]')) {
|
||||
const href = style.attribs.href.replace(/^\.\./, '');
|
||||
if (!href) continue;
|
||||
css += await fixture.readFile(href);
|
||||
}
|
||||
|
||||
// test 1: insure green is included (site.css)
|
||||
expect(css.indexOf('p{color:red}')).to.be.greaterThanOrEqual(0);
|
||||
|
||||
// test 2: insure purple is not included as an import (page-3.css)
|
||||
// this makes sure the styles imported with ?raw and ?url weren't bundled
|
||||
expect(css.indexOf('p{color:purple}')).to.be.lessThan(0);
|
||||
|
||||
// test 3: insure purple was inlined (page-3.css inlined with set:html)
|
||||
// this makes sure the styles imported with ?url were inlined
|
||||
let inlineCss = $('style').html().replace(/\s/g, '').replace('/n', '');
|
||||
expect(inlineCss.indexOf('p{color:purple;}')).to.be.greaterThanOrEqual(0);
|
||||
});
|
||||
});
|
|
@ -13,6 +13,6 @@ describe('Vite Config', async () => {
|
|||
it('Allows overriding bundle naming options in the build', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
expect($('link').attr('href')).to.equal('/assets/testing-entry.css');
|
||||
expect($('link').attr('href')).to.match(/\/assets\/testing-[a-z0-9]+\.css/)
|
||||
});
|
||||
});
|
||||
|
|
49
packages/astro/test/css-assets.test.js
Normal file
49
packages/astro/test/css-assets.test.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('Assets in CSS', () => {
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/css-assets/',
|
||||
vite: {
|
||||
build: {
|
||||
assetsInlineLimit: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
function getAllMatches(re, text) {
|
||||
let count = 0;
|
||||
while (re.exec(text) !== null) {
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
async function getCSSForPage(pathname) {
|
||||
const html = await fixture.readFile(pathname);
|
||||
const $ = cheerio.load(html);
|
||||
const cssPath = $('link').attr('href');
|
||||
const css = await fixture.readFile(cssPath);
|
||||
return css;
|
||||
}
|
||||
|
||||
it('Bundled CSS does not have __VITE_ASSET__', async () => {
|
||||
let css = await getCSSForPage('/one/index.html');
|
||||
expect(css).to.not.contain('__VITE_ASSET__');
|
||||
css = await getCSSForPage('/two/index.html');
|
||||
expect(css).to.not.contain('__VITE_ASSET__');
|
||||
});
|
||||
|
||||
it('Pages contain only their own CSS', async () => {
|
||||
let css = await getCSSForPage('/one/index.html');
|
||||
expect(getAllMatches(/font-face/g, css)).to.equal(1);
|
||||
css = await getCSSForPage('/two/index.html');
|
||||
expect(getAllMatches(/font-face/g, css)).to.equal(1);
|
||||
});
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"name": "@test/astro-css-bundling-import",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
---
|
||||
import "../styles/site.css"
|
||||
|
||||
const {title} = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{title}</title>
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul>
|
||||
<li><a href="/page-1">Page 1</a></li>
|
||||
<li><a href="/page-1">Page 2</a></li>
|
||||
<!-- <li><a href="/page-2-reduced-layout">Page 2 reduced layout</a></li> -->
|
||||
</ul>
|
||||
<slot></slot>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,28 +0,0 @@
|
|||
---
|
||||
import "../styles/site.css"
|
||||
|
||||
const {title} = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul>
|
||||
<li><a href="/page-1">Page 1</a></li>
|
||||
<li><a href="/page-2">Page 2</a></li>
|
||||
<li><a href="/page-3">Page 3</a></li>
|
||||
<!-- <li><a href="/page-2-reduced-layout">Page 2 reduced layout</a></li> -->
|
||||
</ul>
|
||||
<main id="page">
|
||||
<slot></slot>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
import BaseLayout from "./BaseLayout.astro"
|
||||
import "../styles/page-one.css"
|
||||
|
||||
const {title} = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout title={title}>
|
||||
<main id="page">
|
||||
<slot></slot>
|
||||
</main>
|
||||
</BaseLayout>
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
import PageLayout from "../layouts/PageLayout.astro"
|
||||
---
|
||||
|
||||
<PageLayout title="Page 1">
|
||||
<h1>Page 1</h1>
|
||||
<p>Nothing to see here. Check <a href="/page-2">Page 2</a></p>
|
||||
</PageLayout>
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
import PageLayout from "../layouts/PageLayout.astro"
|
||||
import "../styles/page-two.css"
|
||||
---
|
||||
|
||||
<PageLayout title="Page 2">
|
||||
<h1>Page 2</h1>
|
||||
<p>This text should be green, because we want <code>page-2.css</code> to override <code>site.css</code></p>
|
||||
<p>This works in the dev-server. However in the prod build, the text is blue. Execute <code>npm run build</code> and then execute <code>npx http-server dist/</code>.</p>
|
||||
<p>We can view the built html at <a href="https://github-qoihup--8080.local.webcontainer.io/page-2/">https://github-qoihup--8080.local.webcontainer.io/page-2/</a>. The color there is blue.</p>
|
||||
|
||||
<p>If it helps debug the issue, rename the <code>page-1.astro</code> file to <code>page-1.astro.bak</code>. Build the prod site and view it. This time the color is green.</p>
|
||||
</PageLayout>
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
import PageLayout from "../layouts/InlineLayout.astro"
|
||||
import styles from "../styles/page-three.css?raw"
|
||||
import stylesUrl from "../styles/page-one.css?url"
|
||||
---
|
||||
|
||||
<PageLayout title="Page 3">
|
||||
<style set:html={styles}></style>
|
||||
|
||||
<h1>Page 3</h1>
|
||||
<p>This text should be purple, because we want the inlined <code>page-3.css</code> to override <code>site.css</code></p>
|
||||
</PageLayout>
|
|
@ -1,3 +0,0 @@
|
|||
p {
|
||||
color: blue;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
p {
|
||||
color: purple;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
p {
|
||||
color: green;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
p {
|
||||
color: red;
|
||||
}
|
||||
|
||||
h1 {
|
||||
outline: 1px solid red;
|
||||
}
|
9
packages/astro/test/fixtures/css-assets/package.json
vendored
Normal file
9
packages/astro/test/fixtures/css-assets/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@test/astro-sitemap-rss",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*",
|
||||
"@astrojs/test-font-awesome-package": "file:./packages/font-awesome"
|
||||
}
|
||||
}
|
19
packages/astro/test/fixtures/css-assets/packages/font-awesome/fontawesome-webfont.woff2
vendored
Normal file
19
packages/astro/test/fixtures/css-assets/packages/font-awesome/fontawesome-webfont.woff2
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
HTTP/2 200
|
||||
date: Tue, 24 May 2022 14:15:13 GMT
|
||||
content-type: font/woff2
|
||||
content-length: 77160
|
||||
access-control-allow-origin: *
|
||||
cache-control: public, max-age=31536000
|
||||
last-modified: Mon, 24 Oct 2016 21:33:21 GMT
|
||||
etag: "12d68-1vSMun0Hb7by/Wupk6dbncHsvww"
|
||||
via: 1.1 fly.io
|
||||
fly-request-id: 01FDSA73RWTQANDR54ZMWPKC4B
|
||||
cf-cache-status: HIT
|
||||
age: 23685787
|
||||
accept-ranges: bytes
|
||||
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
|
||||
strict-transport-security: max-age=31536000; includeSubDomains; preload
|
||||
x-content-type-options: nosniff
|
||||
server: cloudflare
|
||||
cf-ray: 7106a46819b78005-IAD
|
||||
|
19
packages/astro/test/fixtures/css-assets/packages/font-awesome/fontawesome-webfont2.woff2
vendored
Normal file
19
packages/astro/test/fixtures/css-assets/packages/font-awesome/fontawesome-webfont2.woff2
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
HTTP/2 200
|
||||
date: Tue, 24 May 2022 14:15:13 GMT2
|
||||
content-type: font/woff2
|
||||
content-length: 77160
|
||||
access-control-allow-origin: *
|
||||
cache-control: public, max-age=31536000
|
||||
last-modified: Mon, 24 Oct 2016 21:33:21 GMT
|
||||
etag: "12d68-1vSMun0Hb7by/Wupk6dbncHsvww"
|
||||
via: 1.1 fly.io
|
||||
fly-request-id: 01FDSA73RWTQANDR54ZMWPKC4B
|
||||
cf-cache-status: HIT
|
||||
age: 23685787
|
||||
accept-ranges: bytes
|
||||
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
|
||||
strict-transport-security: max-age=31536000; includeSubDomains; preload
|
||||
x-content-type-options: nosniff
|
||||
server: cloudflare
|
||||
cf-ray: 7106a46819b78005-IAD
|
||||
|
4
packages/astro/test/fixtures/css-assets/packages/font-awesome/package.json
vendored
Normal file
4
packages/astro/test/fixtures/css-assets/packages/font-awesome/package.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "@astrojs/test-font-awesome-package",
|
||||
"version": "0.0.1"
|
||||
}
|
20
packages/astro/test/fixtures/css-assets/src/pages/one.astro
vendored
Normal file
20
packages/astro/test/fixtures/css-assets/src/pages/one.astro
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<html>
|
||||
<head><title>Test</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'FA';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('@astrojs/test-font-awesome-package/fontawesome-webfont.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'FA';
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
22
packages/astro/test/fixtures/css-assets/src/pages/two.astro
vendored
Normal file
22
packages/astro/test/fixtures/css-assets/src/pages/two.astro
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Test2</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'FA2';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('@astrojs/test-font-awesome-package/fontawesome-webfont2.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'FA2';
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>test2</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -4,7 +4,7 @@ import eol from 'eol';
|
|||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('PostCSS', () => {
|
||||
const PREFIXED_CSS = `{-webkit-appearance:none;appearance:none}`;
|
||||
const PREFIXED_CSS = `{-webkit-appearance:none;appearance:none`;
|
||||
|
||||
let fixture;
|
||||
let bundledCSS;
|
||||
|
@ -18,7 +18,8 @@ describe('PostCSS', () => {
|
|||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href');
|
||||
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
|
||||
bundledCSS = (await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')))
|
||||
.replace(/\s/g, '').replace('/n', '');
|
||||
});
|
||||
|
||||
it('works in Astro page styles', () => {
|
||||
|
@ -42,8 +43,8 @@ describe('PostCSS', () => {
|
|||
});
|
||||
|
||||
it('ignores CSS in public/', async () => {
|
||||
const publicCSS = await fixture.readFile('/global.css');
|
||||
const publicCSS = (await fixture.readFile('/global.css')).trim().replace(/\s/g, '').replace('/n', '');
|
||||
// neither minified nor prefixed
|
||||
expect(eol.lf(publicCSS.trim())).to.equal(`.global {\n appearance: none;\n}`);
|
||||
expect(eol.lf(publicCSS)).to.equal(`.global{appearance:none;}`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('Tailwind', () => {
|
|||
|
||||
// tailwind escapes brackets, `font-[900]` compiles to `font-\[900\]`
|
||||
expect(bundledCSS, 'supports arbitrary value classes').to.match(
|
||||
/\.font-\\\[900\\\]{font-weight:900}/
|
||||
/\.font-\\\[900\\\]{/
|
||||
);
|
||||
|
||||
// custom theme colors were included
|
||||
|
|
|
@ -459,7 +459,7 @@ importers:
|
|||
|
||||
packages/astro:
|
||||
specifiers:
|
||||
'@astrojs/compiler': ^0.15.0
|
||||
'@astrojs/compiler': ^0.15.1
|
||||
'@astrojs/language-server': ^0.13.4
|
||||
'@astrojs/markdown-remark': ^0.10.1
|
||||
'@astrojs/prism': 0.4.1
|
||||
|
@ -545,7 +545,7 @@ importers:
|
|||
yargs-parser: ^21.0.1
|
||||
zod: ^3.17.3
|
||||
dependencies:
|
||||
'@astrojs/compiler': 0.15.0
|
||||
'@astrojs/compiler': 0.15.1
|
||||
'@astrojs/language-server': 0.13.4
|
||||
'@astrojs/markdown-remark': link:../markdown/remark
|
||||
'@astrojs/prism': link:../astro-prism
|
||||
|
@ -873,12 +873,6 @@ importers:
|
|||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-css-bundling-import:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/astro-css-bundling-nested-layouts:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
|
@ -1117,6 +1111,17 @@ importers:
|
|||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/css-assets:
|
||||
specifiers:
|
||||
'@astrojs/test-font-awesome-package': file:./packages/font-awesome
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
'@astrojs/test-font-awesome-package': file:packages/astro/test/fixtures/css-assets/packages/font-awesome
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/css-assets/packages/font-awesome:
|
||||
specifiers: {}
|
||||
|
||||
packages/astro/test/fixtures/custom-404:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
|
@ -2059,8 +2064,8 @@ packages:
|
|||
leven: 3.1.0
|
||||
dev: true
|
||||
|
||||
/@astrojs/compiler/0.15.0:
|
||||
resolution: {integrity: sha512-gsw9OP/mfMIrdbrBaG5BpP98sWiAymeeVJTi365OwzDvOaePUzKqAMGCQd07RAfeX9eQzTkjDeJJZkIoyt575w==}
|
||||
/@astrojs/compiler/0.15.1:
|
||||
resolution: {integrity: sha512-4PcWD7lfX1c6J9GwaVVbQjrMQEtqx3jXRGj92TCIc0EQ6kRKxLymdzRS9eW8yCsgJp6Ym4nCzQ8ifTSVSphjDQ==}
|
||||
dependencies:
|
||||
tsm: 2.2.1
|
||||
uvu: 0.5.3
|
||||
|
@ -13807,3 +13812,9 @@ packages:
|
|||
|
||||
/zwitch/2.0.2:
|
||||
resolution: {integrity: sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==}
|
||||
|
||||
file:packages/astro/test/fixtures/css-assets/packages/font-awesome:
|
||||
resolution: {directory: packages/astro/test/fixtures/css-assets/packages/font-awesome, type: directory}
|
||||
name: '@astrojs/test-font-awesome-package'
|
||||
version: 0.0.1
|
||||
dev: false
|
||||
|
|
Loading…
Reference in a new issue