summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Drew Powers <1369770+drwpow@users.noreply.github.com> 2021-04-02 12:50:30 -0600
committerGravatar GitHub <noreply@github.com> 2021-04-02 12:50:30 -0600
commitb58b493948ef7b6453ba8c4e94c4bd2fcaea8452 (patch)
tree4bb0426f35e3c7e6a8c7523e6f0aaa1e90407113
parent004b3ea6a0b48ecc04d2a0daf9aae374983bed08 (diff)
downloadastro-b58b493948ef7b6453ba8c4e94c4bd2fcaea8452.tar.gz
astro-b58b493948ef7b6453ba8c4e94c4bd2fcaea8452.tar.zst
astro-b58b493948ef7b6453ba8c4e94c4bd2fcaea8452.zip
Fix body from being scoped (#56)
-rw-r--r--examples/snowpack/package-lock.json39
-rw-r--r--src/compiler/optimize/postcss-scoped-styles/index.ts9
-rw-r--r--src/compiler/optimize/styles.ts30
-rw-r--r--test/astro-scoped-styles.test.js1
4 files changed, 44 insertions, 35 deletions
diff --git a/examples/snowpack/package-lock.json b/examples/snowpack/package-lock.json
index ddcb402e2..afa079c75 100644
--- a/examples/snowpack/package-lock.json
+++ b/examples/snowpack/package-lock.json
@@ -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",
diff --git a/src/compiler/optimize/postcss-scoped-styles/index.ts b/src/compiler/optimize/postcss-scoped-styles/index.ts
index 0d1253350..01c0acd94 100644
--- a/src/compiler/optimize/postcss-scoped-styles/index.ts
+++ b/src/compiler/optimize/postcss-scoped-styles/index.ts
@@ -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(':');
diff --git a/src/compiler/optimize/styles.ts b/src/compiler/optimize/styles.ts
index 36e6f1d7d..72781fefe 100644
--- a/src/compiler/optimize/styles.ts
+++ b/src/compiler/optimize/styles.ts
@@ -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}'`;
+ }
}
}
}
diff --git a/test/astro-scoped-styles.test.js b/test/astro-scoped-styles.test.js
index 18870e6c6..ac84c9ffa 100644
--- a/test/astro-scoped-styles.test.js
+++ b/test/astro-scoped-styles.test.js
@@ -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', () => {