Skip to content

Commit

Permalink
Implement scopedStyleStrategy (#6771)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored May 3, 2023
1 parent 49514e4 commit 3326492
Show file tree
Hide file tree
Showing 9 changed files with 2,231 additions and 2,068 deletions.
21 changes: 21 additions & 0 deletions .changeset/green-cups-hammer.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 3 additions & 3 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"test:e2e:match": "playwright test -g"
},
"dependencies": {
"@astrojs/compiler": "^1.3.2",
"@astrojs/compiler": "^1.4.0",
"@astrojs/language-server": "^1.0.0",
"@astrojs/markdown-remark": "^2.1.4",
"@astrojs/telemetry": "^2.1.1",
Expand All @@ -119,7 +119,7 @@
"@babel/types": "^7.18.4",
"@types/babel__core": "^7.1.19",
"@types/yargs-parser": "^21.0.0",
"acorn": "^8.8.1",
"acorn": "^8.8.2",
"boxen": "^6.2.1",
"chokidar": "^3.5.3",
"ci-info": "^3.3.1",
Expand All @@ -130,7 +130,7 @@
"devalue": "^4.2.0",
"diff": "^5.1.0",
"es-module-lexer": "^1.1.0",
"estree-walker": "^3.0.1",
"estree-walker": "3.0.0",
"execa": "^6.1.0",
"fast-glob": "^3.2.11",
"github-slugger": "^2.0.0",
Expand Down
17 changes: 17 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,23 @@ export interface AstroUserConfig {
*/
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
* @name adapter
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/compile/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export async function compile({
sourcemap: 'both',
internalURL: 'astro/server/index.js',
astroGlobalArgs: JSON.stringify(astroConfig.site),
scopedStyleStrategy: astroConfig.scopedStyleStrategy,
resultScopedSlot: true,
preprocessStyle: createStylePreprocessor({
filename,
Expand Down
4 changes: 4 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export const AstroConfigSchema = z.object({
.union([z.literal('static'), z.literal('server')])
.optional()
.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(),
integrations: z.preprocess(
// preprocess
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@test/scoped-style-strategy",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<html>
<head>
<title>scopedStyleStrategy</title>
<style>
h1 {
color: green;
}
</style>
</head>
<body>
<h1>scopedStyleStrategy</h1>
</body>
</html>
60 changes: 60 additions & 0 deletions packages/astro/test/scoped-style-strategy.test.js
Original file line number Diff line number Diff line change
@@ -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/);
});
});
});
Loading

0 comments on commit 3326492

Please sign in to comment.