diff options
author | 2021-04-02 20:44:23 -0600 | |
---|---|---|
committer | 2021-04-02 20:44:23 -0600 | |
commit | 008ffc295133bb35d537dd3b8edfb31b808a423b (patch) | |
tree | 6be8472ec944f0a68eee3825860f42747a97b020 /src | |
parent | aa333c2f297eae846580ba66304a9b4d88a7643b (diff) | |
download | astro-008ffc295133bb35d537dd3b8edfb31b808a423b.tar.gz astro-008ffc295133bb35d537dd3b8edfb31b808a423b.tar.zst astro-008ffc295133bb35d537dd3b8edfb31b808a423b.zip |
Fix scoping issues (#58)
Diffstat (limited to 'src')
-rw-r--r-- | src/@types/postcss-icss-keyframes.d.ts | 5 | ||||
-rw-r--r-- | src/compiler/optimize/postcss-scoped-styles/index.ts | 20 | ||||
-rw-r--r-- | src/compiler/optimize/styles.ts | 15 |
3 files changed, 31 insertions, 9 deletions
diff --git a/src/@types/postcss-icss-keyframes.d.ts b/src/@types/postcss-icss-keyframes.d.ts new file mode 100644 index 000000000..14c330b6e --- /dev/null +++ b/src/@types/postcss-icss-keyframes.d.ts @@ -0,0 +1,5 @@ +declare module 'postcss-icss-keyframes' { + import type { Plugin } from 'postcss'; + + export default function (options: { generateScopedName(keyframesName: string, filepath: string, css: string): string }): Plugin; +} diff --git a/src/compiler/optimize/postcss-scoped-styles/index.ts b/src/compiler/optimize/postcss-scoped-styles/index.ts index 01c0acd94..23350869c 100644 --- a/src/compiler/optimize/postcss-scoped-styles/index.ts +++ b/src/compiler/optimize/postcss-scoped-styles/index.ts @@ -1,4 +1,4 @@ -import { Plugin } from 'postcss'; +import { Declaration, Plugin } from 'postcss'; interface AstroScopedOptions { className: string; @@ -11,17 +11,24 @@ interface Selector { } const CSS_SEPARATORS = new Set([' ', ',', '+', '>', '~']); +const KEYFRAME_PERCENT = /\d+\.?\d*%/; /** HTML tags that should never get scoped classes */ export const NEVER_SCOPED_TAGS = new Set<string>(['base', 'body', 'font', 'frame', 'frameset', 'head', 'html', 'link', 'meta', 'noframes', 'noscript', 'script', 'style', 'title']); /** - * Scope Selectors + * Scope Rules * Given a selector string (`.btn>span,.nav>span`), add an additional CSS class to every selector (`.btn.myClass>span.myClass,.nav.myClass>span.myClass`) * @param {string} selector The minified selector string to parse. Cannot contain arbitrary whitespace (other than child selector syntax). * @param {string} className The CSS class to apply. */ -export function scopeSelectors(selector: string, className: string) { +export function scopeRule(selector: string, className: string) { + // if this is a keyframe keyword, return original selector + if (selector === 'from' || selector === 'to' || KEYFRAME_PERCENT.test(selector)) { + return selector; + } + + // For everything else, parse & scope const c = className.replace(/^\.?/, '.'); // make sure class always has leading '.' const selectors: Selector[] = []; let ss = selector; // final output @@ -30,9 +37,12 @@ export function scopeSelectors(selector: string, className: string) { { let start = 0; let lastValue = ''; + let parensOpen = false; for (let n = 0; n < ss.length; n++) { const isEnd = n === selector.length - 1; - if (isEnd || CSS_SEPARATORS.has(selector[n])) { + if (selector[n] === '(') parensOpen = true; + if (selector[n] === ')') parensOpen = false; + if (isEnd || (parensOpen === false && CSS_SEPARATORS.has(selector[n]))) { lastValue = selector.substring(start, isEnd ? undefined : n); if (!lastValue) continue; selectors.push({ start, end: isEnd ? n + 1 : n, value: lastValue }); @@ -90,7 +100,7 @@ export default function astroScopedStyles(options: AstroScopedOptions): Plugin { return { postcssPlugin: '@astro/postcss-scoped-styles', Rule(rule) { - rule.selector = scopeSelectors(rule.selector, options.className); + rule.selector = scopeRule(rule.selector, options.className); }, }; } diff --git a/src/compiler/optimize/styles.ts b/src/compiler/optimize/styles.ts index 65b429fef..807d869c9 100644 --- a/src/compiler/optimize/styles.ts +++ b/src/compiler/optimize/styles.ts @@ -2,8 +2,8 @@ import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; import autoprefixer from 'autoprefixer'; -import esbuild from 'esbuild'; import postcss, { Plugin } from 'postcss'; +import postcssKeyframes from 'postcss-icss-keyframes'; import findUp from 'find-up'; import sass from 'sass'; import type { RuntimeMode } from '../../@types/astro'; @@ -27,11 +27,9 @@ const getStyleType: Map<string, StyleType> = new Map([ ['.sass', 'sass'], ['.scss', 'scss'], ['css', 'css'], - ['postcss', 'postcss'], ['sass', 'sass'], ['scss', 'scss'], ['text/css', 'css'], - ['text/postcss', 'postcss'], ['text/sass', 'sass'], ['text/scss', 'scss'], ]); @@ -134,7 +132,16 @@ async function transformStyle(code: string, { type, filename, scopedClass, mode // 2b. Astro scoped styles (always on) postcssPlugins.push(astroScopedStyles({ className: scopedClass })); - // 2c. Autoprefixer (always on) + // 2c. Scoped @keyframes + postcssPlugins.push( + postcssKeyframes({ + generateScopedName(keyframesName) { + return `${keyframesName}-${scopedClass}`; + }, + }) + ); + + // 2d. Autoprefixer (always on) postcssPlugins.push(autoprefixer()); // 2e. Run PostCSS |