Fix body from being scoped (#56)
This commit is contained in:
parent
004b3ea6a0
commit
b58b493948
4 changed files with 44 additions and 35 deletions
39
examples/snowpack/package-lock.json
generated
39
examples/snowpack/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
// don‘t 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(':');
|
||||
|
|
|
@ -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;
|
||||
// don‘t 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}'`;
|
||||
// don‘t 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}'`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 it‘s worth a discussion
|
||||
'body h1': `body h1${className}`, // body shouldn‘t be scoped; it‘s not a component
|
||||
};
|
||||
|
||||
ScopedStyles('Scopes correctly', () => {
|
||||
|
|
Loading…
Reference in a new issue