summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Emanuele Stoppa <my.burning@gmail.com> 2023-08-08 12:59:56 +0100
committerGravatar GitHub <noreply@github.com> 2023-08-08 12:59:56 +0100
commit7bd1b86f85c06fdde0a1ed9146d01bac69990671 (patch)
treec684a68e359d643ac174f9f7ef5af802de0709de
parentba73dea0262e89c9145aeea13252ea2da3f5ecd1 (diff)
downloadastro-7bd1b86f85c06fdde0a1ed9146d01bac69990671.tar.gz
astro-7bd1b86f85c06fdde0a1ed9146d01bac69990671.tar.zst
astro-7bd1b86f85c06fdde0a1ed9146d01bac69990671.zip
feat: new attribute scope style strategy (#7893)
-rw-r--r--.changeset/neat-suns-search.md17
-rw-r--r--packages/astro/package.json2
-rw-r--r--packages/astro/src/@types/astro.ts13
-rw-r--r--packages/astro/src/core/config/schema.ts4
-rw-r--r--packages/astro/test/0-css.test.js42
-rw-r--r--packages/astro/test/astro-partial-html.test.js2
-rw-r--r--packages/astro/test/config-vite-css-target.test.js2
-rw-r--r--packages/astro/test/scoped-style-strategy.test.js34
-rw-r--r--pnpm-lock.yaml14
9 files changed, 101 insertions, 29 deletions
diff --git a/.changeset/neat-suns-search.md b/.changeset/neat-suns-search.md
new file mode 100644
index 000000000..da743c9c7
--- /dev/null
+++ b/.changeset/neat-suns-search.md
@@ -0,0 +1,17 @@
+---
+'astro': major
+---
+
+Implements a new scope style strategy called `"attribute"`. When enabled, styles are applied using `data-*` attributes.
+
+The **default** value of `scopedStyleStrategy` is `"attribute"`.
+
+If you want to use the previous behaviour, you have to use the `"where"` option:
+
+```diff
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
++ scopedStyleStrategy: 'where',
+});
+```
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 0e1f5700d..8e505e8b0 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -116,7 +116,7 @@
"test:e2e:match": "playwright test -g"
},
"dependencies": {
- "@astrojs/compiler": "^1.8.0",
+ "@astrojs/compiler": "^1.8.1",
"@astrojs/internal-helpers": "workspace:*",
"@astrojs/markdown-remark": "workspace:*",
"@astrojs/telemetry": "workspace:*",
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 4c51a7eaf..2cba086f5 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -20,9 +20,10 @@ import type { AstroConfigSchema } from '../core/config';
import type { AstroTimer } from '../core/config/timer';
import type { AstroCookies } from '../core/cookies';
import type { LogOptions, LoggerLevel } from '../core/logger/core';
-import { AstroIntegrationLogger } from '../core/logger/core';
+import type { AstroIntegrationLogger } from '../core/logger/core';
import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server';
import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js';
+
export type {
MarkdownHeading,
MarkdownMetadata,
@@ -609,19 +610,21 @@ export interface AstroUserConfig {
/**
* @docs
* @name scopedStyleStrategy
- * @type {('where' | 'class')}
+ * @type {('where' | 'class' | 'attribute')}
* @default `'where'`
* @version 2.4
* @description
*
* 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.
+ * - `'where'` - Use `:where` selectors, causing no specifity increase.
+ * - `'class'` - Use class-based selectors, causing a +1 specifity increase.
+ * - `'attribute'` - Use `data-` attributes, causing no 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.
+ * Using `'attribute'` is useful in case there's manipulation of the class attributes, so the styling emitted by Astro doesn't go in conflict with the user's business logic.
*/
- scopedStyleStrategy?: 'where' | 'class';
+ scopedStyleStrategy?: 'where' | 'class' | 'attribute';
/**
* @docs
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 282f7844e..417f918bb 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -87,9 +87,9 @@ export const AstroConfigSchema = z.object({
.optional()
.default('static'),
scopedStyleStrategy: z
- .union([z.literal('where'), z.literal('class')])
+ .union([z.literal('where'), z.literal('class'), z.literal('attribute')])
.optional()
- .default('where'),
+ .default('attribute'),
adapter: z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }).optional(),
integrations: z.preprocess(
// preprocess
diff --git a/packages/astro/test/0-css.test.js b/packages/astro/test/0-css.test.js
index 76bfba296..9a05074fb 100644
--- a/packages/astro/test/0-css.test.js
+++ b/packages/astro/test/0-css.test.js
@@ -39,15 +39,27 @@ describe('CSS', function () {
it('HTML and CSS scoped correctly', async () => {
const el1 = $('#dynamic-class');
const el2 = $('#dynamic-vis');
- const classes = $('#class').attr('class').split(' ');
- const scopedClass = classes.find((name) => /^astro-[A-Za-z0-9-]+/.test(name));
+ const classes = $('#class');
+ let scopedAttribute;
+ for (const [key] of Object.entries(classes[0].attribs)) {
+ if (/^data-astro-cid-[A-Za-z0-9-]+/.test(key)) {
+ // Ema: this is ugly, but for reasons that I don't want to explore, cheerio
+ // lower case the hash of the attribute
+ scopedAttribute = key
+ .toUpperCase()
+ .replace('data-astro-cid-'.toUpperCase(), 'data-astro-cid-');
+ }
+ }
+ if (!scopedAttribute) {
+ throw new Error("Couldn't find scoped attribute");
+ }
// 1. check HTML
- expect(el1.attr('class')).to.equal(`blue ${scopedClass}`);
- expect(el2.attr('class')).to.equal(`visible ${scopedClass}`);
+ expect(el1.attr('class')).to.equal(`blue`);
+ expect(el2.attr('class')).to.equal(`visible`);
// 2. check CSS
- const expected = `.blue:where(.${scopedClass}){color:#b0e0e6}.color\\:blue:where(.${scopedClass}){color:#b0e0e6}.visible:where(.${scopedClass}){display:block}`;
+ const expected = `.blue[${scopedAttribute}],.color\\:blue[${scopedAttribute}]{color:#b0e0e6}.visible[${scopedAttribute}]{display:block}`;
expect(bundledCSS).to.include(expected);
});
@@ -60,8 +72,12 @@ describe('CSS', function () {
expect($('#no-scope').attr('class')).to.equal(undefined);
});
- it('Child inheritance', async () => {
- expect($('#passed-in').attr('class')).to.match(/outer astro-[A-Z0-9]+ astro-[A-Z0-9]+/);
+ it('Child inheritance', (done) => {
+ for (const [key] of Object.entries($('#passed-in')[0].attribs)) {
+ if (/^data-astro-cid-[A-Za-z0-9-]+/.test(key)) {
+ done();
+ }
+ }
});
it('Using hydrated components adds astro-island styles', async () => {
@@ -70,11 +86,11 @@ describe('CSS', function () {
});
it('<style lang="sass">', async () => {
- expect(bundledCSS).to.match(new RegExp('h1\\:where\\(.astro-[^{]*{color:#90ee90}'));
+ expect(bundledCSS).to.match(new RegExp('h1\\[data-astro-cid-[^{]*{color:#90ee90}'));
});
it('<style lang="scss">', async () => {
- expect(bundledCSS).to.match(new RegExp('h1\\:where\\(.astro-[^{]*{color:#ff69b4}'));
+ expect(bundledCSS).to.match(new RegExp('h1\\[data-astro-cid-[^{]*{color:#ff69b4}'));
});
});
@@ -331,10 +347,10 @@ describe('CSS', function () {
it('resolves Astro styles', async () => {
const allInjectedStyles = $('style').text();
- expect(allInjectedStyles).to.contain('.linked-css:where(.astro-');
- expect(allInjectedStyles).to.contain('.linked-sass:where(.astro-');
- expect(allInjectedStyles).to.contain('.linked-scss:where(.astro-');
- expect(allInjectedStyles).to.contain('.wrapper:where(.astro-');
+ expect(allInjectedStyles).to.contain('.linked-css[data-astro-cid-');
+ expect(allInjectedStyles).to.contain('.linked-sass[data-astro-cid-');
+ expect(allInjectedStyles).to.contain('.linked-scss[data-astro-cid-');
+ expect(allInjectedStyles).to.contain('.wrapper[data-astro-cid-');
});
it('resolves Styles from React', async () => {
diff --git a/packages/astro/test/astro-partial-html.test.js b/packages/astro/test/astro-partial-html.test.js
index 6073f1bd1..162c6985d 100644
--- a/packages/astro/test/astro-partial-html.test.js
+++ b/packages/astro/test/astro-partial-html.test.js
@@ -26,7 +26,7 @@ describe('Partial HTML', async () => {
// test 2: correct CSS present
const allInjectedStyles = $('style').text();
- expect(allInjectedStyles).to.match(/\:where\(\.astro-[^{]+{color:red}/);
+ expect(allInjectedStyles).to.match(/\[data-astro-cid-[^{]+{color:red}/);
});
it('injects framework styles', async () => {
diff --git a/packages/astro/test/config-vite-css-target.test.js b/packages/astro/test/config-vite-css-target.test.js
index 1dc2cce32..cb9fa8de2 100644
--- a/packages/astro/test/config-vite-css-target.test.js
+++ b/packages/astro/test/config-vite-css-target.test.js
@@ -32,7 +32,7 @@ describe('CSS', function () {
it('vite.build.cssTarget is respected', async () => {
expect(bundledCSS).to.match(
- new RegExp('.class\\:where\\(.astro-[^{]*{top:0;right:0;bottom:0;left:0}')
+ new RegExp('.class\\[data-astro-[^{]*{top:0;right:0;bottom:0;left:0}')
);
});
});
diff --git a/packages/astro/test/scoped-style-strategy.test.js b/packages/astro/test/scoped-style-strategy.test.js
index 022ef3d6f..a59f227ad 100644
--- a/packages/astro/test/scoped-style-strategy.test.js
+++ b/packages/astro/test/scoped-style-strategy.test.js
@@ -3,7 +3,7 @@ import * as cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
describe('scopedStyleStrategy', () => {
- describe('default', () => {
+ describe('scopedStyleStrategy: "where"', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let stylesheet;
@@ -11,6 +11,7 @@ describe('scopedStyleStrategy', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/scoped-style-strategy/',
+ scopedStyleStrategy: 'where',
});
await fixture.build();
@@ -57,4 +58,35 @@ describe('scopedStyleStrategy', () => {
expect(stylesheet).to.match(/h1\.astro/);
});
});
+
+ 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('does not include :where pseudo-selector', () => {
+ expect(stylesheet).to.not.match(/:where/);
+ });
+
+ it('does not include the class name directly in the selector', () => {
+ expect(stylesheet).to.not.match(/h1\.astro/);
+ });
+
+ it('includes the data attribute hash', () => {
+ expect(stylesheet).to.include('h1[data-astro-cid-');
+ });
+ });
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 667ec98bf..fca6dde17 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -480,8 +480,8 @@ importers:
packages/astro:
dependencies:
'@astrojs/compiler':
- specifier: ^1.8.0
- version: 1.8.0
+ specifier: ^1.8.1
+ version: 1.8.1
'@astrojs/internal-helpers':
specifier: workspace:*
version: link:../internal-helpers
@@ -5391,6 +5391,10 @@ packages:
/@astrojs/compiler@1.8.0:
resolution: {integrity: sha512-E0TI/uyO8n+IPSZ4Fvl9Lne8JKEasR6ZMGvE2G096oTWOXSsPAhRs2LomV3z+/VRepo2h+t/SdVo54wox4eJwA==}
+ dev: false
+
+ /@astrojs/compiler@1.8.1:
+ resolution: {integrity: sha512-C28qplQzgIJ+JU9S+1wNx+ue2KCBUp0TTAd10EWAEkk4RsL3Tzlw0BYvLDDb4KP9jS48lXmR4/1TtZ4aavYJ8Q==}
/@astrojs/internal-helpers@0.1.2:
resolution: {integrity: sha512-YXLk1CUDdC9P5bjFZcGjz+cE/ZDceXObDTXn/GCID4r8LjThuexxi+dlJqukmUpkSItzQqgzfWnrPLxSFPejdA==}
@@ -5400,7 +5404,7 @@ packages:
resolution: {integrity: sha512-oEw7AwJmzjgy6HC9f5IdrphZ1GVgfV/+7xQuyf52cpTiRWd/tJISK3MsKP0cDkVlfodmNABNFnAaAWuLZEiiiA==}
hasBin: true
dependencies:
- '@astrojs/compiler': 1.8.0
+ '@astrojs/compiler': 1.8.1
'@jridgewell/trace-mapping': 0.3.18
'@vscode/emmet-helper': 2.8.8
events: 3.3.0
@@ -15600,7 +15604,7 @@ packages:
resolution: {integrity: sha512-dPzop0gKZyVGpTDQmfy+e7FKXC9JT3mlpfYA2diOVz+Ui+QR1U4G/s+OesKl2Hib2JJOtAYJs/l+ovgT0ljlFA==}
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
dependencies:
- '@astrojs/compiler': 1.8.0
+ '@astrojs/compiler': 1.8.1
prettier: 2.8.8
sass-formatter: 0.7.6
dev: true
@@ -15609,7 +15613,7 @@ packages:
resolution: {integrity: sha512-lJ/mG/Lz/ccSwNtwqpFS126mtMVzFVyYv0ddTF9wqwrEG4seECjKDAyw/oGv915rAcJi8jr89990nqfpmG+qdg==}
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
dependencies:
- '@astrojs/compiler': 1.8.0
+ '@astrojs/compiler': 1.8.1
prettier: 2.8.8
sass-formatter: 0.7.6
synckit: 0.8.5