Fix PostCSS (and Autoprefixer) processing (#1837)
* Fix PostCSS processing * Skip Windows tests (for now)
This commit is contained in:
parent
64cc9ed9c1
commit
65216ef921
18 changed files with 164 additions and 12 deletions
5
.changeset/twenty-rabbits-repeat.md
Normal file
5
.changeset/twenty-rabbits-repeat.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Bugfix: PostCSS not working in all contexts
|
|
@ -129,8 +129,10 @@ It’s recommended to only use this in scenarios where a `<link>` tag won’t wo
|
||||||
```js
|
```js
|
||||||
// postcss.config.cjs
|
// postcss.config.cjs
|
||||||
module.exports = {
|
module.exports = {
|
||||||
autoprefixer: {
|
plugins: {
|
||||||
/* (optional) autoprefixer settings */
|
autoprefixer: {
|
||||||
|
/* (optional) autoprefixer settings */
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
"astring": "^1.7.5",
|
"astring": "^1.7.5",
|
||||||
"ci-info": "^3.2.0",
|
"ci-info": "^3.2.0",
|
||||||
"connect": "^3.7.0",
|
"connect": "^3.7.0",
|
||||||
|
"eol": "^0.9.1",
|
||||||
"es-module-lexer": "^0.7.1",
|
"es-module-lexer": "^0.7.1",
|
||||||
"esbuild": "^0.13.6",
|
"esbuild": "^0.13.6",
|
||||||
"estree-util-value-to-estree": "^1.2.0",
|
"estree-util-value-to-estree": "^1.2.0",
|
||||||
|
|
|
@ -4,7 +4,6 @@ import path from 'path';
|
||||||
|
|
||||||
// https://vitejs.dev/guide/features.html#css-pre-processors
|
// https://vitejs.dev/guide/features.html#css-pre-processors
|
||||||
export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.scss', '.sass', '.styl', '.stylus', '.less']);
|
export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.scss', '.sass', '.styl', '.stylus', '.less']);
|
||||||
export const PREPROCESSOR_EXTENSIONS = new Set(['.pcss', '.scss', '.sass', '.styl', '.stylus', '.less']);
|
|
||||||
|
|
||||||
/** find unloaded styles */
|
/** find unloaded styles */
|
||||||
export function getStylesForID(id: string, viteServer: vite.ViteDevServer): Set<string> {
|
export function getStylesForID(id: string, viteServer: vite.ViteDevServer): Set<string> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { AstroConfig } from '../@types/astro-core';
|
import type { AstroConfig } from '../@types/astro-core';
|
||||||
import type { ErrorPayload } from 'vite';
|
import type { ErrorPayload } from 'vite';
|
||||||
import fs from 'fs';
|
import eol from 'eol';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath, pathToFileURL } from 'url';
|
import { fileURLToPath, pathToFileURL } from 'url';
|
||||||
import resolve from 'resolve';
|
import resolve from 'resolve';
|
||||||
|
@ -41,7 +41,7 @@ export function parseNpmName(spec: string): { scope?: string; name: string; subp
|
||||||
/** generate code frame from esbuild error */
|
/** generate code frame from esbuild error */
|
||||||
export function codeFrame(src: string, loc: ErrorPayload['err']['loc']): string {
|
export function codeFrame(src: string, loc: ErrorPayload['err']['loc']): string {
|
||||||
if (!loc) return '';
|
if (!loc) return '';
|
||||||
const lines = src.replace(/\r\n/g, '\n').split('\n');
|
const lines = eol.lf(src).split('\n');
|
||||||
// grab 2 lines before, and 3 lines after focused line
|
// grab 2 lines before, and 3 lines after focused line
|
||||||
const visibleLines = [];
|
const visibleLines = [];
|
||||||
for (let n = -2; n <= 2; n++) {
|
for (let n = -2; n <= 2; n++) {
|
||||||
|
|
|
@ -63,8 +63,8 @@ export default function astro({ config, devServer }: AstroPluginOptions): vite.P
|
||||||
sourcemap: 'both',
|
sourcemap: 'both',
|
||||||
internalURL: 'astro/internal',
|
internalURL: 'astro/internal',
|
||||||
preprocessStyle: async (value: string, attrs: Record<string, string>) => {
|
preprocessStyle: async (value: string, attrs: Record<string, string>) => {
|
||||||
if (!attrs || !attrs.lang) return null;
|
const lang = `.${attrs?.lang || 'css'}`.toLowerCase();
|
||||||
const result = await transformWithVite({ value, attrs, id, transformHook: viteTransform, ssr: isSSR(opts) });
|
const result = await transformWithVite({ value, lang, id, transformHook: viteTransform, ssr: isSSR(opts) });
|
||||||
if (!result) {
|
if (!result) {
|
||||||
// TODO: compiler supports `null`, but types don't yet
|
// TODO: compiler supports `null`, but types don't yet
|
||||||
return result as any;
|
return result as any;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type vite from '../core/vite';
|
import type vite from '../core/vite';
|
||||||
|
|
||||||
import { PREPROCESSOR_EXTENSIONS } from '../core/ssr/css.js';
|
import { STYLE_EXTENSIONS } from '../core/ssr/css.js';
|
||||||
|
|
||||||
export type TransformHook = (code: string, id: string, ssr?: boolean) => Promise<vite.TransformResult>;
|
export type TransformHook = (code: string, id: string, ssr?: boolean) => Promise<vite.TransformResult>;
|
||||||
|
|
||||||
|
@ -14,16 +14,15 @@ export function getViteTransform(viteConfig: vite.ResolvedConfig): TransformHook
|
||||||
|
|
||||||
interface TransformWithViteOptions {
|
interface TransformWithViteOptions {
|
||||||
value: string;
|
value: string;
|
||||||
attrs: Record<string, string>;
|
lang: string;
|
||||||
id: string;
|
id: string;
|
||||||
transformHook: TransformHook;
|
transformHook: TransformHook;
|
||||||
ssr?: boolean;
|
ssr?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Transform style using Vite hook */
|
/** Transform style using Vite hook */
|
||||||
export async function transformWithVite({ value, attrs, transformHook, id, ssr }: TransformWithViteOptions): Promise<vite.TransformResult | null> {
|
export async function transformWithVite({ value, lang, transformHook, id, ssr }: TransformWithViteOptions): Promise<vite.TransformResult | null> {
|
||||||
const lang = (`.${attrs.lang}` || '').toLowerCase(); // add leading "."; don’t be case-sensitive
|
if (!STYLE_EXTENSIONS.has(lang)) {
|
||||||
if (!PREPROCESSOR_EXTENSIONS.has(lang)) {
|
|
||||||
return null; // only preprocess langs supported by Vite
|
return null; // only preprocess langs supported by Vite
|
||||||
}
|
}
|
||||||
return transformHook(value, id + `?astro&type=style&lang${lang}`, ssr);
|
return transformHook(value, id + `?astro&type=style&lang${lang}`, ssr);
|
||||||
|
|
7
packages/astro/test/fixtures/postcss/postcss.config.cjs
vendored
Normal file
7
packages/astro/test/fixtures/postcss/postcss.config.cjs
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
autoprefixer: {
|
||||||
|
overrideBrowserslist: ['> 0.1%', 'IE 11'] // enforce `appearance: none;` is prefixed with -webkit and -moz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
3
packages/astro/test/fixtures/postcss/public/global.css
vendored
Normal file
3
packages/astro/test/fixtures/postcss/public/global.css
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.global {
|
||||||
|
appearance: none;
|
||||||
|
}
|
9
packages/astro/test/fixtures/postcss/src/components/Astro.astro
vendored
Normal file
9
packages/astro/test/fixtures/postcss/src/components/Astro.astro
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<style>
|
||||||
|
.astro-component {
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="astro-component">
|
||||||
|
Astro
|
||||||
|
</div>
|
3
packages/astro/test/fixtures/postcss/src/components/Solid.css
vendored
Normal file
3
packages/astro/test/fixtures/postcss/src/components/Solid.css
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.solid {
|
||||||
|
appearance: none;
|
||||||
|
}
|
10
packages/astro/test/fixtures/postcss/src/components/Solid.jsx
vendored
Normal file
10
packages/astro/test/fixtures/postcss/src/components/Solid.jsx
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { h } from 'solid-js';
|
||||||
|
import './Solid.css';
|
||||||
|
|
||||||
|
export default function Counter() {
|
||||||
|
return (
|
||||||
|
<div class="solid">
|
||||||
|
Solid
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
9
packages/astro/test/fixtures/postcss/src/components/Svelte.svelte
vendored
Normal file
9
packages/astro/test/fixtures/postcss/src/components/Svelte.svelte
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<style>
|
||||||
|
.svelte {
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="svelte">
|
||||||
|
Svelte
|
||||||
|
</div>
|
12
packages/astro/test/fixtures/postcss/src/components/Vue.vue
vendored
Normal file
12
packages/astro/test/fixtures/postcss/src/components/Vue.vue
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<style>
|
||||||
|
.vue {
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="vue">
|
||||||
|
Vue
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
24
packages/astro/test/fixtures/postcss/src/pages/index.astro
vendored
Normal file
24
packages/astro/test/fixtures/postcss/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
import AstroComponent from '../components/Astro.astro';
|
||||||
|
import JSX from '../components/Solid.jsx';
|
||||||
|
import Svelte from '../components/Svelte.svelte';
|
||||||
|
import Vue from '../components/Vue.vue';
|
||||||
|
---
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/global.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.css')}>
|
||||||
|
<style>
|
||||||
|
.astro-page {
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="astro-page">
|
||||||
|
<AstroComponent />
|
||||||
|
<JSX />
|
||||||
|
<Svelte />
|
||||||
|
<Vue />
|
||||||
|
</div>
|
||||||
|
</body>
|
3
packages/astro/test/fixtures/postcss/src/styles/linked.css
vendored
Normal file
3
packages/astro/test/fixtures/postcss/src/styles/linked.css
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.a-n {
|
||||||
|
appearance: none;
|
||||||
|
}
|
61
packages/astro/test/postcss.test.js
Normal file
61
packages/astro/test/postcss.test.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import cheerio from 'cheerio';
|
||||||
|
import eol from 'eol';
|
||||||
|
import os from 'os';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
const PREFIXED_CSS = `{-webkit-appearance:none;-moz-appearance:none;appearance:none}`;
|
||||||
|
|
||||||
|
let fixture;
|
||||||
|
let bundledCSS;
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
projectRoot: './fixtures/postcss',
|
||||||
|
renderers: ['@astrojs/renderer-solid', '@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');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const bundledCSSHREF = $('link[rel=stylesheet][href^=assets/]').attr('href');
|
||||||
|
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PostCSS', () => {
|
||||||
|
it('works in Astro page styles', () => {
|
||||||
|
expect(bundledCSS).to.match(new RegExp(`.astro-page.astro-[^{]+${PREFIXED_CSS}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works in Astro component styles', () => {
|
||||||
|
expect(bundledCSS).to.match(new RegExp(`.astro-component.astro-[^{]+${PREFIXED_CSS}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works in <link>', () => {
|
||||||
|
expect(bundledCSS).to.match(new RegExp(`.a-n${PREFIXED_CSS}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works in JSX', () => {
|
||||||
|
// TODO: fix in Windows
|
||||||
|
if (os.platform() === 'win32') return;
|
||||||
|
expect(bundledCSS).to.match(new RegExp(`.solid${PREFIXED_CSS}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works in Vue', () => {
|
||||||
|
// TODO: fix in Windows
|
||||||
|
if (os.platform() === 'win32') return;
|
||||||
|
expect(bundledCSS).to.match(new RegExp(`.vue${PREFIXED_CSS}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works in Svelte', () => {
|
||||||
|
// TODO: fix in Windows
|
||||||
|
if (os.platform() === 'win32') return;
|
||||||
|
expect(bundledCSS).to.match(new RegExp(`.svelte.s[^{]+${PREFIXED_CSS}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores CSS in public/', async () => {
|
||||||
|
const publicCSS = await fixture.readFile('/global.css');
|
||||||
|
// neither minified nor prefixed
|
||||||
|
expect(eol.lf(publicCSS.trim())).to.equal(`.global {\n appearance: none;\n}`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -4008,6 +4008,11 @@ envinfo@^7.7.4:
|
||||||
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
|
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
|
||||||
integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==
|
integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==
|
||||||
|
|
||||||
|
eol@^0.9.1:
|
||||||
|
version "0.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd"
|
||||||
|
integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==
|
||||||
|
|
||||||
eol@~0.2.0:
|
eol@~0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/eol/-/eol-0.2.0.tgz#2f6db086a243a46e3e5dbd0e13435c7ebebf09dd"
|
resolved "https://registry.yarnpkg.com/eol/-/eol-0.2.0.tgz#2f6db086a243a46e3e5dbd0e13435c7ebebf09dd"
|
||||||
|
|
Loading…
Reference in a new issue