Fix body from being scoped (#56)

This commit is contained in:
Drew Powers 2021-04-02 12:50:30 -06:00 committed by GitHub
parent 004b3ea6a0
commit b58b493948
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 35 deletions

View file

@ -980,14 +980,8 @@
"@babel/traverse": "^7.13.0",
"@snowpack/plugin-sass": "^1.4.0",
"@snowpack/plugin-svelte": "^3.6.0",
"@snowpack/plugin-vue": "^2.3.0",
"@types/babel__generator": "^7.6.2",
"@types/babel__traverse": "^7.11.1",
"@types/estree": "0.0.46",
"@types/node": "^14.14.31",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.2",
"@vue/server-renderer": "^3.0.7",
"@snowpack/plugin-vue": "^2.4.0",
"@vue/server-renderer": "^3.0.10",
"acorn": "^7.4.0",
"acorn-jsx": "^5.3.1",
"astring": "^1.7.0",
@ -997,6 +991,8 @@
"domhandler": "^4.0.0",
"es-module-lexer": "^0.4.1",
"esbuild": "^0.10.1",
"estree-walker": "^3.0.0",
"fdir": "^5.0.0",
"find-up": "^5.0.0",
"github-slugger": "^1.3.0",
"gray-matter": "^4.0.2",
@ -1012,9 +1008,9 @@
"react-dom": "^17.0.1",
"rollup": "^2.43.1",
"sass": "^1.32.8",
"snowpack": "^3.1.2",
"snowpack": "^3.2.2",
"svelte": "^3.35.0",
"vue": "^3.0.7",
"vue": "^3.0.10",
"yargs-parser": "^20.2.7"
},
"dependencies": {
@ -1168,7 +1164,6 @@
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"lodash": "^4.17.19",
@ -1284,7 +1279,6 @@
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz",
"integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0"
}
@ -1293,7 +1287,6 @@
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz",
"integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==",
"dev": true,
"requires": {
"@babel/types": "^7.3.0"
}
@ -1301,8 +1294,7 @@
"@types/estree": {
"version": "0.0.46",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
"integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==",
"dev": true
"integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg=="
},
"@types/github-slugger": {
"version": "1.3.0",
@ -1328,8 +1320,7 @@
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/pug": {
"version": "2.0.4",
@ -1341,7 +1332,6 @@
"version": "17.0.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.3.tgz",
"integrity": "sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@ -1351,8 +1341,7 @@
"csstype": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz",
"integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==",
"dev": true
"integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g=="
}
}
},
@ -1360,7 +1349,6 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-Icd9KEgdnFfJs39KyRyr0jQ7EKhq8U6CcHRMGAS45fp5qgUvxL3ujUCfWFttUK2UErqZNj97t9gsVPNAqcwoCg==",
"dev": true,
"requires": {
"@types/react": "*"
}
@ -1377,8 +1365,7 @@
"@types/scheduler": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
"integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==",
"dev": true
"integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA=="
},
"@types/yargs-parser": {
"version": "20.2.0",
@ -2600,7 +2587,8 @@
"estree-walker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.0.tgz",
"integrity": "sha512-s6ceX0NFiU/vKPiKvFdR83U1Zffu7upwZsGwpoqfg5rbbq1l50WQ5hCeIvM6E6oD4shUHCYMsiFPns4Jk0YfMQ=="
"integrity": "sha512-s6ceX0NFiU/vKPiKvFdR83U1Zffu7upwZsGwpoqfg5rbbq1l50WQ5hCeIvM6E6oD4shUHCYMsiFPns4Jk0YfMQ==",
"dev": true
},
"esutils": {
"version": "2.0.3",
@ -4500,8 +4488,7 @@
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"to-readable-stream": {
"version": "1.0.0",

View file

@ -12,6 +12,9 @@ interface Selector {
const CSS_SEPARATORS = new Set([' ', ',', '+', '>', '~']);
/** 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
* Given a selector string (`.btn>span,.nav>span`), add an additional CSS class to every selector (`.btn.myClass>span.myClass,.nav.myClass>span.myClass`)
@ -62,6 +65,12 @@ export function scopeSelectors(selector: string, className: string) {
continue;
}
// dont scope body, title, etc.
if (NEVER_SCOPED_TAGS.has(value)) {
ss = head + value + tail;
continue;
}
// scope everything else
let newSelector = ss.substring(start, end);
const pseudoIndex = newSelector.indexOf(':');

View file

@ -7,7 +7,7 @@ import sass from 'sass';
import { RuntimeMode } from '../../@types/astro';
import { OptimizeOptions, Optimizer } from '../../@types/optimizer';
import type { TemplateNode } from '../../parser/interfaces';
import astroScopedStyles from './postcss-scoped-styles/index.js';
import astroScopedStyles, { NEVER_SCOPED_TAGS } from './postcss-scoped-styles/index.js';
type StyleType = 'css' | 'scss' | 'sass' | 'postcss';
@ -26,9 +26,6 @@ const getStyleType: Map<string, StyleType> = new Map([
['text/scss', 'scss'],
]);
/** HTML tags that should never get scoped classes */
const NEVER_SCOPED_TAGS = new Set<string>(['html', 'head', 'body', 'script', 'style', 'link', 'meta']);
/** Should be deterministic, given a unique filename */
function hashFromFilename(filename: string): string {
const hash = crypto.createHash('sha256');
@ -55,6 +52,15 @@ export interface TransformStyleOptions {
mode: RuntimeMode;
}
/** given a class="" string, does it contain a given class? */
function hasClass(classList: string, className: string): boolean {
if (!className) return false;
for (const c of classList.split(' ')) {
if (className === c.trim()) return true;
}
return false;
}
/** Convert styles to scoped CSS */
async function transformStyle(code: string, { type, filename, scopedClass, mode }: TransformStyleOptions): Promise<StyleTransformResult> {
let styleType: StyleType = 'css'; // important: assume CSS as default
@ -149,12 +155,18 @@ export default function optimizeStyles({ compileOptions, filename, fileID }: Opt
const attr = node.attributes[classIndex];
for (let k = 0; k < attr.value.length; k++) {
if (attr.value[k].type === 'Text') {
// string literal
attr.value[k].raw += ' ' + scopedClass;
attr.value[k].data += ' ' + scopedClass;
// dont add same scopedClass twice
if (!hasClass(attr.value[k].data, scopedClass)) {
// string literal
attr.value[k].raw += ' ' + scopedClass;
attr.value[k].data += ' ' + scopedClass;
}
} else if (attr.value[k].type === 'MustacheTag' && attr.value[k]) {
// MustacheTag
attr.value[k].content = `(${attr.value[k].content}) + ' ${scopedClass}'`;
// dont add same scopedClass twice (this check is a little more basic, but should suffice)
if (!attr.value[k].content.includes(`' ${scopedClass}'`)) {
// MustacheTag
attr.value[k].content = `(${attr.value[k].content}) + ' ${scopedClass}'`;
}
}
}
}

View file

@ -18,6 +18,7 @@ const tests = {
'.class :global(*)': `.class${className} *`,
'.class :global(.nav:not(.is-active))': `.class${className} .nav:not(.is-active)`, // preserve nested parens
'.class:not(.is-active)': `.class${className}:not(.is-active)`, // Note: the :not() selector can NOT contain multiple classes, so this is correct; if this causes issues for some people then its worth a discussion
'body h1': `body h1${className}`, // body shouldnt be scoped; its not a component
};
ScopedStyles('Scopes correctly', () => {