Implement scopedStyleStrategy (#6771)
* Implement scopedStyleStrategy * Add changeset * Update compiler * Specify the eswalker version * Update compiler * Update .changeset/green-cups-hammer.md Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update .changeset/green-cups-hammer.md Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/green-cups-hammer.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
parent
49514e4ce4
commit
3326492b94
9 changed files with 2200 additions and 2037 deletions
21
.changeset/green-cups-hammer.md
Normal file
21
.changeset/green-cups-hammer.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
'astro': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Implements a new class-based scoping strategy
|
||||||
|
|
||||||
|
This implements the [Scoping RFC](https://github.com/withastro/roadmap/pull/543), providing a way to opt in to increased style specificity for Astro component styles.
|
||||||
|
|
||||||
|
This prevents bugs where global styles override Astro component styles due to CSS ordering and the use of element selectors.
|
||||||
|
|
||||||
|
To enable class-based scoping, you can set it in your config:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
scopedStyleStrategy: 'class'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the 0-specificity `:where` pseudo-selector is still the default strategy. The intent is to change `'class'` to be the default in 3.0.
|
|
@ -106,7 +106,7 @@
|
||||||
"test:e2e:match": "playwright test -g"
|
"test:e2e:match": "playwright test -g"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^1.3.2",
|
"@astrojs/compiler": "^1.4.0",
|
||||||
"@astrojs/language-server": "^1.0.0",
|
"@astrojs/language-server": "^1.0.0",
|
||||||
"@astrojs/markdown-remark": "^2.1.4",
|
"@astrojs/markdown-remark": "^2.1.4",
|
||||||
"@astrojs/telemetry": "^2.1.1",
|
"@astrojs/telemetry": "^2.1.1",
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
"@babel/types": "^7.18.4",
|
"@babel/types": "^7.18.4",
|
||||||
"@types/babel__core": "^7.1.19",
|
"@types/babel__core": "^7.1.19",
|
||||||
"@types/yargs-parser": "^21.0.0",
|
"@types/yargs-parser": "^21.0.0",
|
||||||
"acorn": "^8.8.1",
|
"acorn": "^8.8.2",
|
||||||
"boxen": "^6.2.1",
|
"boxen": "^6.2.1",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"ci-info": "^3.3.1",
|
"ci-info": "^3.3.1",
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
"devalue": "^4.2.0",
|
"devalue": "^4.2.0",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"es-module-lexer": "^1.1.0",
|
"es-module-lexer": "^1.1.0",
|
||||||
"estree-walker": "^3.0.1",
|
"estree-walker": "3.0.0",
|
||||||
"execa": "^6.1.0",
|
"execa": "^6.1.0",
|
||||||
"fast-glob": "^3.2.11",
|
"fast-glob": "^3.2.11",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
|
|
|
@ -494,6 +494,23 @@ export interface AstroUserConfig {
|
||||||
*/
|
*/
|
||||||
trailingSlash?: 'always' | 'never' | 'ignore';
|
trailingSlash?: 'always' | 'never' | 'ignore';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @docs
|
||||||
|
* @name scopedStyleStrategy
|
||||||
|
* @type {('where' | 'class')}
|
||||||
|
* @default `'where'`
|
||||||
|
* @description
|
||||||
|
* @version 2.3.5
|
||||||
|
*
|
||||||
|
* Specify the strategy used for scoping styles within Astro components. Choose from:
|
||||||
|
* - `'where'` - Use `:where` selectors, causing no specifity increase.
|
||||||
|
* - `'class'` - Use class-based selectors, causing a +1 specifity increase.
|
||||||
|
*
|
||||||
|
* Using `'class'` is helpful when you want to ensure that element selectors within an Astro component override global style defaults (e.g. from a global stylesheet).
|
||||||
|
* Using `'where'` gives you more control over specifity, but requires that you use higher-specifity selectors, layers, and other tools to control which selectors are applied.
|
||||||
|
*/
|
||||||
|
scopedStyleStrategy?: 'where' | 'class';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @docs
|
* @docs
|
||||||
* @name adapter
|
* @name adapter
|
||||||
|
|
|
@ -42,6 +42,7 @@ export async function compile({
|
||||||
sourcemap: 'both',
|
sourcemap: 'both',
|
||||||
internalURL: 'astro/server/index.js',
|
internalURL: 'astro/server/index.js',
|
||||||
astroGlobalArgs: JSON.stringify(astroConfig.site),
|
astroGlobalArgs: JSON.stringify(astroConfig.site),
|
||||||
|
scopedStyleStrategy: astroConfig.scopedStyleStrategy,
|
||||||
resultScopedSlot: true,
|
resultScopedSlot: true,
|
||||||
preprocessStyle: createStylePreprocessor({
|
preprocessStyle: createStylePreprocessor({
|
||||||
filename,
|
filename,
|
||||||
|
|
|
@ -71,6 +71,10 @@ export const AstroConfigSchema = z.object({
|
||||||
.union([z.literal('static'), z.literal('server')])
|
.union([z.literal('static'), z.literal('server')])
|
||||||
.optional()
|
.optional()
|
||||||
.default('static'),
|
.default('static'),
|
||||||
|
scopedStyleStrategy: z
|
||||||
|
.union([z.literal('where'), z.literal('class')])
|
||||||
|
.optional()
|
||||||
|
.default('where'),
|
||||||
adapter: z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }).optional(),
|
adapter: z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }).optional(),
|
||||||
integrations: z.preprocess(
|
integrations: z.preprocess(
|
||||||
// preprocess
|
// preprocess
|
||||||
|
|
8
packages/astro/test/fixtures/scoped-style-strategy/package.json
vendored
Normal file
8
packages/astro/test/fixtures/scoped-style-strategy/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "@test/scoped-style-strategy",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"astro": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
13
packages/astro/test/fixtures/scoped-style-strategy/src/pages/index.astro
vendored
Normal file
13
packages/astro/test/fixtures/scoped-style-strategy/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>scopedStyleStrategy</title>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>scopedStyleStrategy</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
60
packages/astro/test/scoped-style-strategy.test.js
Normal file
60
packages/astro/test/scoped-style-strategy.test.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
describe('scopedStyleStrategy', () => {
|
||||||
|
describe('default', () => {
|
||||||
|
/** @type {import('./test-utils').Fixture} */
|
||||||
|
let fixture;
|
||||||
|
let stylesheet;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/scoped-style-strategy/',
|
||||||
|
});
|
||||||
|
await fixture.build();
|
||||||
|
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const $link = $('link[rel=stylesheet]');
|
||||||
|
const href = $link.attr('href');
|
||||||
|
stylesheet = await fixture.readFile(href);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes :where pseudo-selector', () => {
|
||||||
|
expect(stylesheet).to.match(/:where/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not includes the class name directly in the selector', () => {
|
||||||
|
expect(stylesheet).to.not.match(/h1\.astro/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('scopedStyleStrategy: "class"', () => {
|
||||||
|
/** @type {import('./test-utils').Fixture} */
|
||||||
|
let fixture;
|
||||||
|
let stylesheet;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/scoped-style-strategy/',
|
||||||
|
scopedStyleStrategy: 'class'
|
||||||
|
});
|
||||||
|
await fixture.build();
|
||||||
|
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const $link = $('link[rel=stylesheet]');
|
||||||
|
const href = $link.attr('href');
|
||||||
|
stylesheet = await fixture.readFile(href);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not include :where pseudo-selector', () => {
|
||||||
|
expect(stylesheet).to.not.match(/:where/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes the class name directly in the selector', () => {
|
||||||
|
expect(stylesheet).to.match(/h1\.astro/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
4107
pnpm-lock.yaml
generated
4107
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue