From 857a762b5cd776f2835b4e792c1254bed0bd06ae Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sat, 4 Nov 2017 07:51:43 -0700 Subject: [PATCH 01/13] extracted closing self closing nodes --- .../heml-parse/src/closeSelfClosingNodes.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packages/heml-parse/src/closeSelfClosingNodes.js diff --git a/packages/heml-parse/src/closeSelfClosingNodes.js b/packages/heml-parse/src/closeSelfClosingNodes.js new file mode 100644 index 0000000..930dc8d --- /dev/null +++ b/packages/heml-parse/src/closeSelfClosingNodes.js @@ -0,0 +1,22 @@ +import selfClosingHtmlTags from 'html-tags/void' + +/** + * Clsoes + * @param {[type]} $ [description] + * @param {[type]} elements [description] + * @return {[type]} [description] + */ +export default function($, elements) { + /** collect all the self closing nodes */ + const selfClosingTags = [ + ...selfClosingHtmlTags, + ...elements.filter((element) => element.children === false).map(({ tagName }) => tagName) ] + + const $selfClosingNodes = $.findNodes(selfClosingTags).reverse() + + /** Move contents from self wrapping tags outside of itself */ + $selfClosingNodes.forEach(($node) => { + $node.after($node.html()) + $node.html('') + }) +} From 10b0b52d2e96b297ac02a831cf96c46d1d666b2f Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sat, 4 Nov 2017 07:52:53 -0700 Subject: [PATCH 02/13] extracted opening wrapping nodes --- packages/heml-parse/src/openWrappingNodes.js | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packages/heml-parse/src/openWrappingNodes.js diff --git a/packages/heml-parse/src/openWrappingNodes.js b/packages/heml-parse/src/openWrappingNodes.js new file mode 100644 index 0000000..004197f --- /dev/null +++ b/packages/heml-parse/src/openWrappingNodes.js @@ -0,0 +1,22 @@ +import htmlTags from 'html-tags' +import selfClosingHtmlTags from 'html-tags/void' +import { difference } from 'lodash' + +const wrappingHtmlTags = difference(htmlTags, selfClosingHtmlTags) + + +export default function($, elements) { + /** collect all the wrapping nodes */ + const wrappingTags = [ + ...wrappingHtmlTags, + ...elements.filter((element) => element.children !== false).map(({ tagName }) => tagName) ] + + const $wrappingNodes = $.findNodes(wrappingTags).reverse() + + /** ensure that all wrapping tags have at least a space */ + $wrappingNodes.forEach(($node) => { + if ($node.html().length === 0) { + $node.html(' ') + } + }) +} From 50b458bfe689b148f5b8bfd45f3bf1e4b803af76 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sat, 4 Nov 2017 07:53:25 -0700 Subject: [PATCH 03/13] extracted and improved extracting inline styles for processing --- .../heml-parse/src/extractInlineStyles.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/heml-parse/src/extractInlineStyles.js diff --git a/packages/heml-parse/src/extractInlineStyles.js b/packages/heml-parse/src/extractInlineStyles.js new file mode 100644 index 0000000..08050a2 --- /dev/null +++ b/packages/heml-parse/src/extractInlineStyles.js @@ -0,0 +1,27 @@ +import randomString from 'crypto-random-string' +import { compact, first } from 'lodash' + +export default function($, elements) { + /** try for head, fallback to body, then heml */ + const $head = first(compact([...$('head').toNodes(), ...$('body').toNodes(), ...$('heml').toNodes()])) + + /** move inline styles to a style tag with unique ids so they can be hit by the css processor */ + if ($head) { + const $inlineStyleNodes = $.findNodes(elements.map(({ tagName }) => tagName)).filter($node => !!$node.attr('style')) + + const inlineCSS = $inlineStyleNodes.map(($node) => { + let id = $node.attr('id') + const css = $node.attr('style') + $node.removeAttr('style') + + if (!id) { + id = `heml-${randomString(5)}` + $node.attr('id', id) + } + + return `#${id} {${css}}` + }).join('\n') + + if (inlineCSS.length > 0) $head.append(``) + } +} From 377e7089761c07cf6e0738bfa1200373022f29f1 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sat, 4 Nov 2017 07:53:36 -0700 Subject: [PATCH 04/13] call external functions --- packages/heml-parse/src/index.js | 57 ++++---------------------------- 1 file changed, 6 insertions(+), 51 deletions(-) diff --git a/packages/heml-parse/src/index.js b/packages/heml-parse/src/index.js index 5325da8..0f617a6 100644 --- a/packages/heml-parse/src/index.js +++ b/packages/heml-parse/src/index.js @@ -1,10 +1,7 @@ import { load } from 'cheerio' -import { difference, compact, first } from 'lodash' -import randomString from 'crypto-random-string' -import htmlTags from 'html-tags' -import selfClosingHtmlTags from 'html-tags/void' - -const wrappingHtmlTags = difference(htmlTags, selfClosingHtmlTags) +import closeSelfClosingNodes from './closeSelfClosingNodes' +import openWrappingNodes from './openWrappingNodes' +import extractInlineStyles from './extractInlineStyles' function parse (contents, options = {}) { const { @@ -31,51 +28,9 @@ function parse (contents, options = {}) { .map((node) => $(node)) } - const selfClosingTags = [ - ...selfClosingHtmlTags, - ...elements.filter((element) => element.children === false).map(({ tagName }) => tagName) ] - const wrappingTags = [ - ...wrappingHtmlTags, - ...elements.filter((element) => element.children !== false).map(({ tagName }) => tagName) ] - - const $selfClosingNodes = $.findNodes(selfClosingTags).reverse() - const $wrappingNodes = $.findNodes(wrappingTags).reverse() - - /** Move contents from self wrapping tags outside of itself */ - $selfClosingNodes.forEach(($node) => { - $node.after($node.html()) - $node.html('') - }) - - /** ensure that all wrapping tags have at least a zero-width, non-joining character */ - $wrappingNodes.forEach(($node) => { - if ($node.html().length === 0) { - $node.html(' ') - } - }) - - /** try for head, fallback to body, then heml */ - const $head = first(compact([...$('head').toNodes(), ...$('body').toNodes(), ...$('heml').toNodes()])) - - /** move inline styles to a style tag with unique ids so they can be hit by the css processor */ - if ($head) { - const $inlineStyleNodes = $.findNodes(elements.map(({ tagName }) => tagName)).filter($node => !!$node.attr('style')) - - const inlineCSS = $inlineStyleNodes.map(($node) => { - let id = $node.attr('id') - const css = $node.attr('style') - $node.removeAttr('style') - - if (!id) { - id = `heml-${randomString(5)}` - $node.attr('id', id) - } - - return `#${id} {${css}}` - }).join('\n') - - $head.append(``) - } + closeSelfClosingNodes($, elements) + openWrappingNodes($, elements) + extractInlineStyles($, elements) return $ } From 3573ae578b6902415738766b3646e6129d370d3d Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sat, 4 Nov 2017 21:58:59 -0700 Subject: [PATCH 05/13] added some comments --- packages/heml-parse/src/closeSelfClosingNodes.js | 9 +++++---- packages/heml-parse/src/extractInlineStyles.js | 6 ++++++ packages/heml-parse/src/openWrappingNodes.js | 7 ++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/heml-parse/src/closeSelfClosingNodes.js b/packages/heml-parse/src/closeSelfClosingNodes.js index 930dc8d..99c15b7 100644 --- a/packages/heml-parse/src/closeSelfClosingNodes.js +++ b/packages/heml-parse/src/closeSelfClosingNodes.js @@ -1,10 +1,11 @@ import selfClosingHtmlTags from 'html-tags/void' /** - * Clsoes - * @param {[type]} $ [description] - * @param {[type]} elements [description] - * @return {[type]} [description] + * The HEML is parsed as XML. If the HEML contains a self closing tag without the closing slash + * all the siblings will be treated as children. This moves the children back to their place and + * forces the tag to be self closing + * @param {Cheerio} $ + * @param {Array} elements */ export default function($, elements) { /** collect all the self closing nodes */ diff --git a/packages/heml-parse/src/extractInlineStyles.js b/packages/heml-parse/src/extractInlineStyles.js index 08050a2..3f605ec 100644 --- a/packages/heml-parse/src/extractInlineStyles.js +++ b/packages/heml-parse/src/extractInlineStyles.js @@ -1,6 +1,12 @@ import randomString from 'crypto-random-string' import { compact, first } from 'lodash' +/** + * This extracts all inline styles on elements into a style tag to be inlined later + * so that the styles can be properly expanded and later re-inlined + * @param {Cheerio} $ + * @param {Array} elements + */ export default function($, elements) { /** try for head, fallback to body, then heml */ const $head = first(compact([...$('head').toNodes(), ...$('body').toNodes(), ...$('heml').toNodes()])) diff --git a/packages/heml-parse/src/openWrappingNodes.js b/packages/heml-parse/src/openWrappingNodes.js index 004197f..1a3b651 100644 --- a/packages/heml-parse/src/openWrappingNodes.js +++ b/packages/heml-parse/src/openWrappingNodes.js @@ -4,7 +4,12 @@ import { difference } from 'lodash' const wrappingHtmlTags = difference(htmlTags, selfClosingHtmlTags) - +/** + * The HEML is parsed as XML. If the HEML contains a wrapping tag with no content it will be + * optimized to be self closing. This add a placeholder space to all empty wrapping tags + * @param {Cheerio} $ + * @param {Array} elements + */ export default function($, elements) { /** collect all the wrapping nodes */ const wrappingTags = [ From 339add30af2d8fffca2a366bc6186533299ff0af Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sat, 4 Nov 2017 22:03:15 -0700 Subject: [PATCH 06/13] added working safe selection converter to parser --- packages/heml-parse/package-lock.json | 132 +++++++++++++- packages/heml-parse/package.json | 6 +- packages/heml-parse/src/index.js | 2 + packages/heml-parse/src/safeSelectorize.js | 200 +++++++++++++++++++++ 4 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 packages/heml-parse/src/safeSelectorize.js diff --git a/packages/heml-parse/package-lock.json b/packages/heml-parse/package-lock.json index 09210a0..4598d33 100644 --- a/packages/heml-parse/package-lock.json +++ b/packages/heml-parse/package-lock.json @@ -1,6 +1,6 @@ { "name": "@heml/parse", - "version": "1.0.0", + "version": "1.0.2-0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9,11 +9,29 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.89.tgz", "integrity": "sha512-Z/67L97+6H1qJiEEHSN1SQapkWjDss1D90rAnFcQ6UxKkah9juzotK5UNEP1bDv/0lJ3NAQTnVfc/JWdgCGruA==" }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "requires": { + "color-convert": "1.9.0" + } + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, "cheerio": { "version": "1.0.0-rc.2", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", @@ -27,6 +45,19 @@ "parse5": "3.0.2" } }, + "color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -48,11 +79,26 @@ "nth-check": "1.0.1" } }, + "css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "requires": { + "cssesc": "0.1.0", + "fastparse": "1.1.1", + "regexpu-core": "1.0.0" + } + }, "css-what": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=" + }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -96,6 +142,21 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=" + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, "html-tags": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", @@ -124,6 +185,11 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", @@ -145,6 +211,24 @@ "@types/node": "6.0.89" } }, + "postcss": { + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", + "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", + "requires": { + "chalk": "2.3.0", + "source-map": "0.6.1", + "supports-color": "4.5.0" + } + }, + "postcss-safe-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-3.0.1.tgz", + "integrity": "sha1-t1Pv9sfArqXoN1++TN6L+QY/8UI=", + "requires": { + "postcss": "6.0.14" + } + }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", @@ -164,11 +248,49 @@ "util-deprecate": "1.0.2" } }, + "regenerate": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "requires": { + "regenerate": "1.3.3", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=" + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "requires": { + "jsesc": "0.5.0" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "shorthash": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/shorthash/-/shorthash-0.0.2.tgz", + "integrity": "sha1-WbJo7sveWQOLMNogK8+93rLEpOs=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", @@ -177,6 +299,14 @@ "safe-buffer": "5.1.1" } }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "requires": { + "has-flag": "2.0.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/packages/heml-parse/package.json b/packages/heml-parse/package.json index 4a03ae3..565d0f3 100644 --- a/packages/heml-parse/package.json +++ b/packages/heml-parse/package.json @@ -23,7 +23,11 @@ "dependencies": { "cheerio": "^1.0.0-rc.2", "crypto-random-string": "^1.0.0", + "css-selector-tokenizer": "^0.7.0", "html-tags": "^2.0.0", - "lodash": "^4.17.4" + "lodash": "^4.17.4", + "postcss": "^6.0.14", + "postcss-safe-parser": "^3.0.1", + "shorthash": "0.0.2" } } diff --git a/packages/heml-parse/src/index.js b/packages/heml-parse/src/index.js index 0f617a6..46f2e36 100644 --- a/packages/heml-parse/src/index.js +++ b/packages/heml-parse/src/index.js @@ -2,6 +2,7 @@ import { load } from 'cheerio' import closeSelfClosingNodes from './closeSelfClosingNodes' import openWrappingNodes from './openWrappingNodes' import extractInlineStyles from './extractInlineStyles' +import safeSelectorize from './safeSelectorize' function parse (contents, options = {}) { const { @@ -31,6 +32,7 @@ function parse (contents, options = {}) { closeSelfClosingNodes($, elements) openWrappingNodes($, elements) extractInlineStyles($, elements) + safeSelectorize($, elements) return $ } diff --git a/packages/heml-parse/src/safeSelectorize.js b/packages/heml-parse/src/safeSelectorize.js new file mode 100644 index 0000000..6ce6d2a --- /dev/null +++ b/packages/heml-parse/src/safeSelectorize.js @@ -0,0 +1,200 @@ +import postcss, { plugin } from 'postcss' +import safeParser from 'postcss-safe-parser' +import { parse as parseSelector, stringify as stringfySelector } from 'css-selector-tokenizer' +import { unique as toClass } from 'shorthash' +import { first, last } from 'lodash' + +function stringifySelectorNodes(nodes) { + return stringfySelector({ type: 'selector', nodes }) +} + +const complexRelationships = ['>', '~', '+'] +const staticPseudoSelectors = [ + 'first-child', + 'last-child', + 'first-of-type', + 'last-of-type', + 'nth-child', + 'nth-last-child', + 'nth-of-type', + 'nth-last-of-type', + 'empty' + // not ":not()" because it can contain a dynamicPseudoSelector +] +const dynamicPseudoSelectors = [ + 'hover', + 'active', + 'focus', + 'link', + 'visited', + 'target', + 'checked', + 'in-range', + 'out-of-range', + 'invalid', + 'scope' +] + +const pseudoElements = [ + 'after', + 'before', + 'first-letter', + 'first-line', + 'selection', + 'backdrop', + 'placeholder', + 'marker', + 'spelling-error', + 'grammar-error' +] + +/** + * This converts all complex selectors into classes via shorthash and applies them + * to the elements that should be selected as to allow for the most cross client support + * @param {Cheerio} $ + * @param {Array} elements + */ +function safeSelectorize($, elements) { + $.findNodes('style').forEach(($node) => { + const css = replaceComplexSelectors($, $node.html()) + + $node.html(css) + }) +} + +function replaceComplexSelectors($, contents) { + const { css } = postcss([ + plugin('postcss-safe-selectorize', () => (root) => { + root.walkRules((rule) => { + + rule.selectors = rule.selectors.map((selector) => { + if (isComplexSelector(selector)) { + const classesMap = generateClassesForSelector(selector) + + for (const [ className, selectorPart ] of classesMap) { + $(selectorPart).addClass(className) + } + + return generateReplacementSelector(selector) + } + + return selector + }) + + }) + }) + ]).process(contents, { parser: safeParser }) + + return css +} + + +/** + * checks if the given selector contains any parts that have less then ideal support + * @param {String]} selector + * @return {Boolean} isComplex + */ +function isComplexSelector(selector) { + const { nodes } = first(parseSelector(selector).nodes) + + return nodes.filter(({ type, operator, name }) => { + /** complex relationships */ + if (type === 'operator' && complexRelationships.includes(operator)) return true + + /** attribute selector */ + if (type === 'attribute') return true + + /** static pseudo selectors */ + if (type.startsWith('pseudo') && staticPseudoSelectors.includes(name)) return true + + /** universal selector */ + if (type === 'universal') return true + + return false + }).length > 0 +} + +/** + * builds a map of selectors to be replaced with the corresponding class + * @param {String} selector + * @return {Map} selectorAndClassMap + */ +function generateClassesForSelector(selector) { + const { nodes } = first(parseSelector(selector).nodes) + const map = new Map() + let selectorPartNodes = [] + + /** + * 1. gather all the nodes until a pseudo element or dynamic pseudo selector + * 2. create a class for the selector part and add selector part/class to the map + */ + nodes.forEach((node, index) => { + /** we have a matched pseudo - drop the node, build the previous nodes to a string, and add it to the map entry */ + if (node.type.startsWith('pseudo') && (pseudoElements.includes(node.name) || dynamicPseudoSelectors.includes(node.name))) { + const selectorPart = stringifySelectorNodes(selectorPartNodes) + map.set(toClass(selectorPart), selectorPart) + + /** + * keep the previous last node on so that the selector continues to work + * .i.e. a:hover > b will become these selectors ['a', 'a > b'] + */ + selectorPartNodes = selectorPartNodes.length > 0 ? [ last(selectorPartNodes) ] : [] + } + /** we are on the last element, push it on, and add the */ + else if (index === nodes.length - 1) { + selectorPartNodes.push(node) + const selectorPart = stringifySelectorNodes(selectorPartNodes) + map.set(toClass(selectorPart), selectorPart) + } + /** push the node to the current selector part */ + else { + selectorPartNodes.push(node) + } + }) + + return map +} + +/** + * generate a selector that uses the same classes as what was generated in generateClassesForSelector, but leaves in all the pseudo pieces + * @param {String} selector + * @return {Map} selectorAndClassMap + */ +function generateReplacementSelector(selector) { + const { nodes } = first(parseSelector(selector).nodes) + const map = new Map() + let selectorPartNodes = [] + let replacementSelector = '' + + /** + * 1. gather all the nodes until a pseudo element or dynamic pseudo selector + * 2. create a class for the selector part and add selector part/class to the map + */ + nodes.forEach((node, index) => { + /** we have a matched pseudo - build the previous nodes to a string, and add it to the replacement selector, append the pseudo */ + if (node.type.startsWith('pseudo') && (pseudoElements.includes(node.name) || dynamicPseudoSelectors.includes(node.name))) { + const selectorPart = stringifySelectorNodes(selectorPartNodes) + replacementSelector += `.${toClass(selectorPart)}${stringifySelectorNodes([node])} ` + + /** + * keep the previous last node on so that the selector continues to work + * .i.e. a:hover > b will become these selectors ['a', 'a > b'] + */ + selectorPartNodes = selectorPartNodes.length > 0 ? [ last(selectorPartNodes) ] : [] + } + /** we are on the last element, push it on, and add the class */ + else if (index === nodes.length - 1) { + selectorPartNodes.push(node) + const selectorPart = stringifySelectorNodes(selectorPartNodes) + replacementSelector += `.${toClass(selectorPart)} ` + } + /** push the node to the current selector part */ + else { + selectorPartNodes.push(node) + } + }) + + return replacementSelector +} + +export default safeSelectorize From 8ee5fc8227d0d49e43b280b39dc2a5ae60367b04 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sun, 5 Nov 2017 08:41:06 -0800 Subject: [PATCH 07/13] removed tagging from element expander --- .../plugins/postcss-element-expander/index.js | 8 -- .../tagAliasSelectors.js | 106 ------------------ 2 files changed, 114 deletions(-) delete mode 100644 packages/heml-styles/src/plugins/postcss-element-expander/tagAliasSelectors.js diff --git a/packages/heml-styles/src/plugins/postcss-element-expander/index.js b/packages/heml-styles/src/plugins/postcss-element-expander/index.js index ce1eca8..e03b90b 100644 --- a/packages/heml-styles/src/plugins/postcss-element-expander/index.js +++ b/packages/heml-styles/src/plugins/postcss-element-expander/index.js @@ -38,14 +38,6 @@ export default postcss.plugin('postcss-element-expander', ({ elements, aliases } return (root, result) => { for (let element of elements) { - /** - * add the element tag to any css selectors that implicitly target an element - * .i.e. #my-button that selects - */ - root.walkRules((rule) => { - tagAliasSelectors(element, aliases[element.tag], rule) - }) - /** * There are 3 (non-mutually exclusive) possibilities when it contains the element tag * diff --git a/packages/heml-styles/src/plugins/postcss-element-expander/tagAliasSelectors.js b/packages/heml-styles/src/plugins/postcss-element-expander/tagAliasSelectors.js deleted file mode 100644 index be625a4..0000000 --- a/packages/heml-styles/src/plugins/postcss-element-expander/tagAliasSelectors.js +++ /dev/null @@ -1,106 +0,0 @@ -import selectorParser from 'postcss-selector-parser' - -const simpleSelectorParser = selectorParser() - -/** - * Add the element tag to selectors from the rule that match the element alias - * @param {Object} element element definition - * @param {Array[$node]} aliases array of cheerio nodes - * @param {Rule} rule postcss node - */ -export default function (element, aliases, rule) { - if (!aliases) return - - let selectors = [] - - rule.selectors.forEach((selector) => { - const matchedAliases = aliases.filter((alias) => alias.is(selector.replace(/::?\S*/g, ''))).length > 0 - - /** the selector in an alias that doesn't target the tag already */ - if (matchedAliases && !targetsTag(selector)) { - selectors.push(appendElementSelector(element, selector)) - } - - /** dont add the original selector back in if it targets a pseudo selector */ - if (!targetsElementPseudo(element, selector)) { selectors.push(selector) } - }) - - rule.selectors = selectors -} - -/** - * checks if selector targets a tag - * @param {String} selector the selector - * @return {Boolean} if the selector targets a tag - */ -function targetsTag (selector) { - const selectors = simpleSelectorParser.process(selector).res - - return selectors.filter((selector) => { - let selectorNodes = selector.nodes.concat([]).reverse() // clone the array - - for (const node of selectorNodes) { - if (node.type === 'cominator') { break } - - if (node.type === 'tag') { return true } - } - - return false - }).length > 0 -} - -/** - * find all selectors that target the give element - * @param {Object} element the element definition - * @param {String} selector the selector - * @return {Array} the matched selectors - */ -function targetsElementPseudo (element, selector) { - const selectors = simpleSelectorParser.process(selector).res - - return selectors.filter((selector) => { - let selectorNodes = selector.nodes.concat([]).reverse() // clone the array - - for (const node of selectorNodes) { - if (node.type === 'cominator') { break } - - if (node.type === 'pseudo' && node.value.replace(/::?/, '') in element.pseudos) { - return true - } - - if (node.type === 'tag' && node.value === element.tag) { break } - } - - return false - }).length > 0 -} - -/** - * Add the element tag to the end of the selector - * @param {Object} element element definition - * @param {String} selector the selector - * @return {String} the modified selector - */ -function appendElementSelector (element, selector) { - const processor = selectorParser((selectors) => { - let combinatorNode = null - - /** - * looping breaks if we insert dynamically - */ - selectors.each((selector) => { - const elementNode = selectorParser.tag({ value: element.tag }) - selector.walk((node) => { - if (node.type === 'combinator') { combinatorNode = node } - }) - - if (combinatorNode) { - selector.insertAfter(combinatorNode, elementNode) - } else { - selector.prepend(elementNode) - } - }) - }) - - return processor.process(selector).result -} From 7f9c89d6e359b72970f3f2537cfa293f692e13f0 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sun, 5 Nov 2017 10:36:04 -0800 Subject: [PATCH 08/13] moving to multi-step css parse-processing --- packages/heml-parse/src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/heml-parse/src/index.js b/packages/heml-parse/src/index.js index 46f2e36..34e3658 100644 --- a/packages/heml-parse/src/index.js +++ b/packages/heml-parse/src/index.js @@ -2,7 +2,7 @@ import { load } from 'cheerio' import closeSelfClosingNodes from './closeSelfClosingNodes' import openWrappingNodes from './openWrappingNodes' import extractInlineStyles from './extractInlineStyles' -import safeSelectorize from './safeSelectorize' +import preprocessStyles from './preprocessStyles' function parse (contents, options = {}) { const { @@ -32,7 +32,7 @@ function parse (contents, options = {}) { closeSelfClosingNodes($, elements) openWrappingNodes($, elements) extractInlineStyles($, elements) - safeSelectorize($, elements) + preprocessStyles($, elements) return $ } From fd970964bcc62863d4cf38f1d1a1dc7626def159 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Sun, 5 Nov 2017 10:54:51 -0800 Subject: [PATCH 09/13] added alias tagging to parse-preprocessing --- packages/heml-parse/src/preprocessStyles.js | 309 ++++++++++++++++++++ packages/heml-parse/src/safeSelectorize.js | 200 ------------- 2 files changed, 309 insertions(+), 200 deletions(-) create mode 100644 packages/heml-parse/src/preprocessStyles.js delete mode 100644 packages/heml-parse/src/safeSelectorize.js diff --git a/packages/heml-parse/src/preprocessStyles.js b/packages/heml-parse/src/preprocessStyles.js new file mode 100644 index 0000000..3a8eecc --- /dev/null +++ b/packages/heml-parse/src/preprocessStyles.js @@ -0,0 +1,309 @@ +import postcss, { plugin } from 'postcss' +import safeParser from 'postcss-safe-parser' +import { parse as parseSelector, stringify as stringfySelector } from 'css-selector-tokenizer' +import { unique } from 'shorthash' +import { first, last, intersection, uniq } from 'lodash' + +function toClass(s) { + return `s${unique(s)}` +} + +function stringifySelectorNodes(nodes) { + return stringfySelector({ type: 'selector', nodes }) +} + +const complexRelationships = ['>', '~', '+'] +// not ":not()" because it can contain a dynamicPseudoSelector +const staticPseudoSelectors = [ 'first-child', 'last-child', 'first-of-type', 'last-of-type', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type', 'empty' ] +const dynamicPseudoSelectors = [ 'hover', 'active', 'focus', 'link', 'visited', 'target', 'checked', 'in-range', 'out-of-range', 'invalid', 'scope' ] +const pseudoElements = [ 'after', 'before', 'first-letter', 'first-line', 'selection', 'backdrop', 'placeholder', 'marker', 'spelling-error', 'grammar-error' ] + +/** + * process all the style tags + * @param {Cheerio} $ + * @param {Array} elements + */ +function processStyles($, elements) { + $.findNodes('style').forEach(($node) => { + const css = processCSS($, elements, $node.html()) + + $node.html(css) + }) +} + +/** + * process the given css + * @param {Cheerio} $ + * @param {String} contents some css + * @return {String} the modified css + */ +function processCSS($, elements, contents) { + const { css } = postcss([ + safeSelectorize($), + tagAllAliasSelectors($, elements), + ]).process(contents, { parser: safeParser }) + + return css +} + +const tagAllAliasSelectors = plugin('postcss-tag-aliases', ($, elements) => (root) => { + /** + * add the element tag to any css selectors that implicitly target an element + * .i.e. #my-button that selects + */ + const elementNames = elements.map(({ tagName }) => tagName) + + root.walkRules((rule) => { + let newSelectors = [] + + rule.selectors.forEach((selector) => { + /** skip if we already target a tag (no need to alias) */ + if (targetsTag(selector)) return newSelectors.push(selector) + + const selectedTags = uniq($.findNodes(queryableSelector(selector)).map(($node) => $node[0].name)) + const targetSingleTag = selectedTags.length === 1 + const selectedElements = intersection(selectedTags, elementNames) + const targetsNonElements = selectedTags.length > selectedElements.length + + /** skip if we are not targeting any elements (no need to alias) */ + if (selectedElements.length === 0) return newSelectors.push(selector) + + /** if we target only one tag/element, just drop the tag onto the selector */ + if (targetSingleTag) { + const elementName = first(selectedElements) + return newSelectors.push(appendElementSelector(elementName, selector)) + } + + /** if we target more than one tag/element, generate specific selector for each element */ + for (let elementName of selectedElements) { + newSelectors.push(buildTheElementSpecificSelector(elementName, selector, $)) + } + + /** if we target non-elements, we need to keep the original selector on */ + if (targetsNonElements) return newSelectors.push(selector) + }) + + rule.selectors = newSelectors + }) +}) + + +/** + * Add the element tag to the end of the selector + * @param {Object} element element definition + * @param {String} selector the selector + * @return {String} the modified selector + */ +function appendElementSelector (elementName, selector) { + const nodes = first(parseSelector(selector).nodes).nodes + + // default to the last node in case there is no combinator + let lastCombinatorIndex = nodes.length - 1 + nodes.forEach((node, i) => { + if (node.type === 'operator' || node.type === 'spacing') { + lastCombinatorIndex = i + 1 + } + }) + + nodes.splice(lastCombinatorIndex, 0, { name: elementName, type: 'element' }) + + return stringifySelectorNodes(nodes) +} + + +function buildTheElementSpecificSelector(elementName, selector, $) { + const nodes = first(parseSelector(selector).nodes).nodes.reverse() + const $elementNodes = $.findNodes(queryableSelector(appendElementSelector(elementName, selector))) + + for (const node of nodes) { + if (node.type === 'operator' || node.type === 'spacing') { break } + + if (node.type === 'class') { + const newClass = `${node.name}-${elementName}` + $elementNodes.forEach(($node) => $node.removeClass(node.name).addClass(newClass)) + node.name = newClass + } + + if (node.type === 'id') { + const newId = `${node.name}-${elementName}` + $elementNodes.forEach(($node) => $node.attr('id', newId)) + node.name = newId + } + } + + return appendElementSelector(elementName, stringifySelectorNodes(nodes.reverse())) +} + +function queryableSelector(s) { + return s +} + +/** + * checks if selector targets a tag + * @param {String} selector the selector + * @return {Boolean} if the selector targets a tag + */ +function targetsTag (selector) { + const nodes = first(parseSelector(selector).nodes).nodes.reverse() + + for (const node of nodes) { + if (node.type === 'operator' || node.type === 'spacing') { return false } + + if (node.type === 'tag') { return true } + } +} + + + + + + + + + + + + + + + + + + + + + + +/** + * This converts all complex selectors into classes via shorthash and applies them + * to the elements that should be selected as to allow for the most cross client support + * @param {Cheerio} $ + * @param {Array} elements + */ +const safeSelectorize = plugin('postcss-safe-selectorize', ($) => (root) => { + root.walkRules((rule) => { + rule.selectors = rule.selectors.map((selector) => { + if (isComplexSelector(selector)) { + const classesMap = generateClassesForSelector(selector) + + for (const [ className, selectorPart ] of classesMap) { + $(selectorPart).addClass(className) + } + + return generateReplacementSelector(selector) + } + + return selector + }) + }) +}) + +/** + * checks if the given selector contains any parts that have less then ideal support + * @param {String]} selector + * @return {Boolean} isComplex + */ +function isComplexSelector(selector) { + const { nodes } = first(parseSelector(selector).nodes) + + return nodes.filter(({ type, operator, name }) => { + /** complex relationships */ + if (type === 'operator' && complexRelationships.includes(operator)) return true + + /** attribute selector */ + if (type === 'attribute') return true + + /** static pseudo selectors */ + if (type.startsWith('pseudo') && staticPseudoSelectors.includes(name)) return true + + /** universal selector */ + if (type === 'universal') return true + + return false + }).length > 0 +} + +/** + * builds a map of selectors to be replaced with the corresponding class + * @param {String} selector + * @return {Map} selectorAndClassMap + */ +function generateClassesForSelector(selector) { + const { nodes } = first(parseSelector(selector).nodes) + const map = new Map() + let selectorPartNodes = [] + + /** + * 1. gather all the nodes until a pseudo element or dynamic pseudo selector + * 2. create a class for the selector part and add selector part/class to the map + */ + nodes.forEach((node, index) => { + /** we have a matched pseudo - drop the node, build the previous nodes to a string, and add it to the map entry */ + if (node.type.startsWith('pseudo') && (pseudoElements.includes(node.name) || dynamicPseudoSelectors.includes(node.name))) { + const selectorPart = stringifySelectorNodes(selectorPartNodes) + map.set(toClass(selectorPart), selectorPart) + + /** + * keep the previous last node on so that the selector continues to work + * .i.e. a:hover > b will become these selectors ['a', 'a > b'] + */ + selectorPartNodes = selectorPartNodes.length > 0 ? [ last(selectorPartNodes) ] : [] + } + /** we are on the last element, push it on, and add the */ + else if (index === nodes.length - 1) { + selectorPartNodes.push(node) + const selectorPart = stringifySelectorNodes(selectorPartNodes) + map.set(toClass(selectorPart), selectorPart) + } + /** push the node to the current selector part */ + else { + selectorPartNodes.push(node) + } + }) + + return map +} + +/** + * generate a selector that uses the same classes as what was generated in generateClassesForSelector, but leaves in all the pseudo pieces + * @param {String} selector + * @return {Map} selectorAndClassMap + */ +function generateReplacementSelector(selector) { + const { nodes } = first(parseSelector(selector).nodes) + const map = new Map() + let selectorPartNodes = [] + let replacementSelector = '' + + /** + * 1. gather all the nodes until a pseudo element or dynamic pseudo selector + * 2. create a class for the selector part and add selector part/class to the map + */ + nodes.forEach((node, index) => { + /** we have a matched pseudo - build the previous nodes to a string, and add it to the replacement selector, append the pseudo */ + if (node.type.startsWith('pseudo') && (pseudoElements.includes(node.name) || dynamicPseudoSelectors.includes(node.name))) { + const selectorPart = stringifySelectorNodes(selectorPartNodes) + replacementSelector += `.${toClass(selectorPart)}${stringifySelectorNodes([node])} ` + + /** + * keep the previous last node on so that the selector continues to work + * .i.e. a:hover > b will become these selectors ['a', 'a > b'] + */ + selectorPartNodes = selectorPartNodes.length > 0 ? [ last(selectorPartNodes) ] : [] + } + /** we are on the last element, push it on, and add the class */ + else if (index === nodes.length - 1) { + selectorPartNodes.push(node) + const selectorPart = stringifySelectorNodes(selectorPartNodes) + replacementSelector += `.${toClass(selectorPart)} ` + } + /** push the node to the current selector part */ + else { + selectorPartNodes.push(node) + } + }) + + return replacementSelector +} + +export default processStyles diff --git a/packages/heml-parse/src/safeSelectorize.js b/packages/heml-parse/src/safeSelectorize.js deleted file mode 100644 index 6ce6d2a..0000000 --- a/packages/heml-parse/src/safeSelectorize.js +++ /dev/null @@ -1,200 +0,0 @@ -import postcss, { plugin } from 'postcss' -import safeParser from 'postcss-safe-parser' -import { parse as parseSelector, stringify as stringfySelector } from 'css-selector-tokenizer' -import { unique as toClass } from 'shorthash' -import { first, last } from 'lodash' - -function stringifySelectorNodes(nodes) { - return stringfySelector({ type: 'selector', nodes }) -} - -const complexRelationships = ['>', '~', '+'] -const staticPseudoSelectors = [ - 'first-child', - 'last-child', - 'first-of-type', - 'last-of-type', - 'nth-child', - 'nth-last-child', - 'nth-of-type', - 'nth-last-of-type', - 'empty' - // not ":not()" because it can contain a dynamicPseudoSelector -] -const dynamicPseudoSelectors = [ - 'hover', - 'active', - 'focus', - 'link', - 'visited', - 'target', - 'checked', - 'in-range', - 'out-of-range', - 'invalid', - 'scope' -] - -const pseudoElements = [ - 'after', - 'before', - 'first-letter', - 'first-line', - 'selection', - 'backdrop', - 'placeholder', - 'marker', - 'spelling-error', - 'grammar-error' -] - -/** - * This converts all complex selectors into classes via shorthash and applies them - * to the elements that should be selected as to allow for the most cross client support - * @param {Cheerio} $ - * @param {Array} elements - */ -function safeSelectorize($, elements) { - $.findNodes('style').forEach(($node) => { - const css = replaceComplexSelectors($, $node.html()) - - $node.html(css) - }) -} - -function replaceComplexSelectors($, contents) { - const { css } = postcss([ - plugin('postcss-safe-selectorize', () => (root) => { - root.walkRules((rule) => { - - rule.selectors = rule.selectors.map((selector) => { - if (isComplexSelector(selector)) { - const classesMap = generateClassesForSelector(selector) - - for (const [ className, selectorPart ] of classesMap) { - $(selectorPart).addClass(className) - } - - return generateReplacementSelector(selector) - } - - return selector - }) - - }) - }) - ]).process(contents, { parser: safeParser }) - - return css -} - - -/** - * checks if the given selector contains any parts that have less then ideal support - * @param {String]} selector - * @return {Boolean} isComplex - */ -function isComplexSelector(selector) { - const { nodes } = first(parseSelector(selector).nodes) - - return nodes.filter(({ type, operator, name }) => { - /** complex relationships */ - if (type === 'operator' && complexRelationships.includes(operator)) return true - - /** attribute selector */ - if (type === 'attribute') return true - - /** static pseudo selectors */ - if (type.startsWith('pseudo') && staticPseudoSelectors.includes(name)) return true - - /** universal selector */ - if (type === 'universal') return true - - return false - }).length > 0 -} - -/** - * builds a map of selectors to be replaced with the corresponding class - * @param {String} selector - * @return {Map} selectorAndClassMap - */ -function generateClassesForSelector(selector) { - const { nodes } = first(parseSelector(selector).nodes) - const map = new Map() - let selectorPartNodes = [] - - /** - * 1. gather all the nodes until a pseudo element or dynamic pseudo selector - * 2. create a class for the selector part and add selector part/class to the map - */ - nodes.forEach((node, index) => { - /** we have a matched pseudo - drop the node, build the previous nodes to a string, and add it to the map entry */ - if (node.type.startsWith('pseudo') && (pseudoElements.includes(node.name) || dynamicPseudoSelectors.includes(node.name))) { - const selectorPart = stringifySelectorNodes(selectorPartNodes) - map.set(toClass(selectorPart), selectorPart) - - /** - * keep the previous last node on so that the selector continues to work - * .i.e. a:hover > b will become these selectors ['a', 'a > b'] - */ - selectorPartNodes = selectorPartNodes.length > 0 ? [ last(selectorPartNodes) ] : [] - } - /** we are on the last element, push it on, and add the */ - else if (index === nodes.length - 1) { - selectorPartNodes.push(node) - const selectorPart = stringifySelectorNodes(selectorPartNodes) - map.set(toClass(selectorPart), selectorPart) - } - /** push the node to the current selector part */ - else { - selectorPartNodes.push(node) - } - }) - - return map -} - -/** - * generate a selector that uses the same classes as what was generated in generateClassesForSelector, but leaves in all the pseudo pieces - * @param {String} selector - * @return {Map} selectorAndClassMap - */ -function generateReplacementSelector(selector) { - const { nodes } = first(parseSelector(selector).nodes) - const map = new Map() - let selectorPartNodes = [] - let replacementSelector = '' - - /** - * 1. gather all the nodes until a pseudo element or dynamic pseudo selector - * 2. create a class for the selector part and add selector part/class to the map - */ - nodes.forEach((node, index) => { - /** we have a matched pseudo - build the previous nodes to a string, and add it to the replacement selector, append the pseudo */ - if (node.type.startsWith('pseudo') && (pseudoElements.includes(node.name) || dynamicPseudoSelectors.includes(node.name))) { - const selectorPart = stringifySelectorNodes(selectorPartNodes) - replacementSelector += `.${toClass(selectorPart)}${stringifySelectorNodes([node])} ` - - /** - * keep the previous last node on so that the selector continues to work - * .i.e. a:hover > b will become these selectors ['a', 'a > b'] - */ - selectorPartNodes = selectorPartNodes.length > 0 ? [ last(selectorPartNodes) ] : [] - } - /** we are on the last element, push it on, and add the class */ - else if (index === nodes.length - 1) { - selectorPartNodes.push(node) - const selectorPart = stringifySelectorNodes(selectorPartNodes) - replacementSelector += `.${toClass(selectorPart)} ` - } - /** push the node to the current selector part */ - else { - selectorPartNodes.push(node) - } - }) - - return replacementSelector -} - -export default safeSelectorize From b05fbc7dce65023ca7ff19f28c9dec19fef9b698 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Mon, 6 Nov 2017 08:32:51 -0800 Subject: [PATCH 10/13] made queryableSelector work --- packages/heml-parse/src/preprocessStyles.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/heml-parse/src/preprocessStyles.js b/packages/heml-parse/src/preprocessStyles.js index 3a8eecc..0c2cb3b 100644 --- a/packages/heml-parse/src/preprocessStyles.js +++ b/packages/heml-parse/src/preprocessStyles.js @@ -134,8 +134,13 @@ function buildTheElementSpecificSelector(elementName, selector, $) { return appendElementSelector(elementName, stringifySelectorNodes(nodes.reverse())) } -function queryableSelector(s) { - return s +function queryableSelector(selector) { + const { nodes } = first(parseSelector(selector).nodes) + + /** remove all non-static pseudo selectors/elements */ + return stringifySelectorNodes(nodes.filter((node) => { + return !(node.type.startsWith('pseudo') && !staticPseudoSelectors.includes(node.name)) + })) } /** @@ -149,7 +154,7 @@ function targetsTag (selector) { for (const node of nodes) { if (node.type === 'operator' || node.type === 'spacing') { return false } - if (node.type === 'tag') { return true } + if (node.type === 'element') { return true } } } From 4ee891fed2bf7f61ed528b112c99f61dcf537d46 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Fri, 10 Nov 2017 12:56:25 -0800 Subject: [PATCH 11/13] removed default class in createElement --- packages/heml-utils/src/createElement.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/heml-utils/src/createElement.js b/packages/heml-utils/src/createElement.js index e39b887..a0003a6 100644 --- a/packages/heml-utils/src/createElement.js +++ b/packages/heml-utils/src/createElement.js @@ -27,7 +27,5 @@ export default function (name, element) { postRender () {} }) - element.defaultAttrs.class = element.defaultAttrs.class || '' - return element } From b4a6a3e917f2f22064ec20b2bb9a543b285ca28c Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Fri, 10 Nov 2017 12:59:20 -0800 Subject: [PATCH 12/13] moved toClass shorthash dep to utils package --- packages/heml-parse/package-lock.json | 5 ----- packages/heml-parse/package.json | 3 +-- packages/heml-utils/package-lock.json | 9 ++++++++- packages/heml-utils/package.json | 3 ++- packages/heml-utils/src/index.js | 3 ++- packages/heml-utils/src/toClass.js | 10 ++++++++++ 6 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 packages/heml-utils/src/toClass.js diff --git a/packages/heml-parse/package-lock.json b/packages/heml-parse/package-lock.json index 4598d33..848048a 100644 --- a/packages/heml-parse/package-lock.json +++ b/packages/heml-parse/package-lock.json @@ -281,11 +281,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, - "shorthash": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/shorthash/-/shorthash-0.0.2.tgz", - "integrity": "sha1-WbJo7sveWQOLMNogK8+93rLEpOs=" - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/packages/heml-parse/package.json b/packages/heml-parse/package.json index 565d0f3..28e5dd4 100644 --- a/packages/heml-parse/package.json +++ b/packages/heml-parse/package.json @@ -27,7 +27,6 @@ "html-tags": "^2.0.0", "lodash": "^4.17.4", "postcss": "^6.0.14", - "postcss-safe-parser": "^3.0.1", - "shorthash": "0.0.2" + "postcss-safe-parser": "^3.0.1" } } diff --git a/packages/heml-utils/package-lock.json b/packages/heml-utils/package-lock.json index 4209c69..f3f4088 100644 --- a/packages/heml-utils/package-lock.json +++ b/packages/heml-utils/package-lock.json @@ -1,6 +1,8 @@ { - "requires": true, + "name": "@heml/utils", + "version": "1.0.2-0", "lockfileVersion": 1, + "requires": true, "dependencies": { "css-groups": { "version": "0.1.1", @@ -11,6 +13,11 @@ "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "shorthash": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/shorthash/-/shorthash-0.0.2.tgz", + "integrity": "sha1-WbJo7sveWQOLMNogK8+93rLEpOs=" } } } diff --git a/packages/heml-utils/package.json b/packages/heml-utils/package.json index e5bc8b6..fbab087 100644 --- a/packages/heml-utils/package.json +++ b/packages/heml-utils/package.json @@ -23,6 +23,7 @@ "dependencies": { "@heml/render": "^1.0.2-0", "css-groups": "^0.1.1", - "lodash": "^4.17.4" + "lodash": "^4.17.4", + "shorthash": "0.0.2" } } diff --git a/packages/heml-utils/src/index.js b/packages/heml-utils/src/index.js index 77ebeed..6a428e2 100644 --- a/packages/heml-utils/src/index.js +++ b/packages/heml-utils/src/index.js @@ -4,5 +4,6 @@ import createElement from './createElement' import HEMLError from './HEMLError' import transforms from './transforms' import condition from './condition' +import toClass from './toClass' -module.exports = { createElement, renderElement, HEMLError, cssGroups, transforms, condition } +module.exports = { createElement, renderElement, HEMLError, cssGroups, transforms, condition, toClass } diff --git a/packages/heml-utils/src/toClass.js b/packages/heml-utils/src/toClass.js new file mode 100644 index 0000000..99fa1b0 --- /dev/null +++ b/packages/heml-utils/src/toClass.js @@ -0,0 +1,10 @@ +import { unique } from 'shorthash' + +/** + * generates a consistent short class-safe hash from a longer string + * @param {String} str the string used in the hash function + * @return {String} the class + */ +export default function toClass(s) { + return `c${unique(s)}` +} From 42ff076ceea11f0bd0b7f2a4a6ca962ba3ec85b6 Mon Sep 17 00:00:00 2001 From: Avi Goldman Date: Fri, 10 Nov 2017 15:41:43 -0800 Subject: [PATCH 13/13] moved css preprocessing to @heml/styles --- packages/heml-parse/package-lock.json | 125 ------------------ packages/heml-parse/package.json | 5 +- packages/heml-parse/src/index.js | 2 - packages/heml-styles/package-lock.json | 55 +++++++- packages/heml-styles/package.json | 1 + .../src/preprocess.js} | 61 ++++----- 6 files changed, 87 insertions(+), 162 deletions(-) rename packages/{heml-parse/src/preprocessStyles.js => heml-styles/src/preprocess.js} (89%) diff --git a/packages/heml-parse/package-lock.json b/packages/heml-parse/package-lock.json index 848048a..6ac6661 100644 --- a/packages/heml-parse/package-lock.json +++ b/packages/heml-parse/package-lock.json @@ -9,29 +9,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.89.tgz", "integrity": "sha512-Z/67L97+6H1qJiEEHSN1SQapkWjDss1D90rAnFcQ6UxKkah9juzotK5UNEP1bDv/0lJ3NAQTnVfc/JWdgCGruA==" }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "requires": { - "color-convert": "1.9.0" - } - }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, "cheerio": { "version": "1.0.0-rc.2", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", @@ -45,19 +27,6 @@ "parse5": "3.0.2" } }, - "color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -79,26 +48,11 @@ "nth-check": "1.0.1" } }, - "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", - "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" - } - }, "css-what": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" }, - "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=" - }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -142,21 +96,6 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "fastparse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=" - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" - }, "html-tags": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", @@ -185,11 +124,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", @@ -211,24 +145,6 @@ "@types/node": "6.0.89" } }, - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "4.5.0" - } - }, - "postcss-safe-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-3.0.1.tgz", - "integrity": "sha1-t1Pv9sfArqXoN1++TN6L+QY/8UI=", - "requires": { - "postcss": "6.0.14" - } - }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", @@ -248,44 +164,11 @@ "util-deprecate": "1.0.2" } }, - "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" - }, - "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", - "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=" - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "requires": { - "jsesc": "0.5.0" - } - }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", @@ -294,14 +177,6 @@ "safe-buffer": "5.1.1" } }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "requires": { - "has-flag": "2.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/packages/heml-parse/package.json b/packages/heml-parse/package.json index 28e5dd4..4a03ae3 100644 --- a/packages/heml-parse/package.json +++ b/packages/heml-parse/package.json @@ -23,10 +23,7 @@ "dependencies": { "cheerio": "^1.0.0-rc.2", "crypto-random-string": "^1.0.0", - "css-selector-tokenizer": "^0.7.0", "html-tags": "^2.0.0", - "lodash": "^4.17.4", - "postcss": "^6.0.14", - "postcss-safe-parser": "^3.0.1" + "lodash": "^4.17.4" } } diff --git a/packages/heml-parse/src/index.js b/packages/heml-parse/src/index.js index 34e3658..0f617a6 100644 --- a/packages/heml-parse/src/index.js +++ b/packages/heml-parse/src/index.js @@ -2,7 +2,6 @@ import { load } from 'cheerio' import closeSelfClosingNodes from './closeSelfClosingNodes' import openWrappingNodes from './openWrappingNodes' import extractInlineStyles from './extractInlineStyles' -import preprocessStyles from './preprocessStyles' function parse (contents, options = {}) { const { @@ -32,7 +31,6 @@ function parse (contents, options = {}) { closeSelfClosingNodes($, elements) openWrappingNodes($, elements) extractInlineStyles($, elements) - preprocessStyles($, elements) return $ } diff --git a/packages/heml-styles/package-lock.json b/packages/heml-styles/package-lock.json index 337ff10..d3cfb4b 100644 --- a/packages/heml-styles/package-lock.json +++ b/packages/heml-styles/package-lock.json @@ -1,6 +1,6 @@ { "name": "@heml/styles", - "version": "1.0.0", + "version": "1.0.2-0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -92,6 +92,16 @@ "write-file-stdout": "0.0.2" } }, + "css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "requires": { + "cssesc": "0.1.0", + "fastparse": "1.1.1", + "regexpu-core": "1.0.0" + } + }, "css-shorthand-expand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/css-shorthand-expand/-/css-shorthand-expand-1.1.0.tgz", @@ -131,6 +141,11 @@ "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-0.0.1.tgz", "integrity": "sha1-4Fr4xsKQ1FHvFjK0VepcgbSxOVw=" }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=" + }, "cssnano-util-get-arguments": { "version": "4.0.0-rc.2", "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0-rc.2.tgz", @@ -151,6 +166,11 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, + "fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=" + }, "flatten": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", @@ -225,6 +245,11 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.3.2.tgz", "integrity": "sha512-Y2/+DnfJJXT1/FCwUebUhLWb3QihxiSC42+ctHLGogmW2jPY6LCapMdFZXRvVP2z6qyKW7s6qncE/9gSqZiArw==" }, + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", @@ -1480,6 +1505,34 @@ "postcss-value-parser": "3.3.0" } }, + "regenerate": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "requires": { + "regenerate": "1.3.3", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=" + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "requires": { + "jsesc": "0.5.0" + } + }, "repeat-element": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", diff --git a/packages/heml-styles/package.json b/packages/heml-styles/package.json index ce46fcf..2a46dbf 100644 --- a/packages/heml-styles/package.json +++ b/packages/heml-styles/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "css-declaration-sorter": "^2.1.0", + "css-selector-tokenizer": "^0.7.0", "css-shorthand-expand": "^1.1.0", "lodash": "^4.17.4", "postcss": "^6.0.13", diff --git a/packages/heml-parse/src/preprocessStyles.js b/packages/heml-styles/src/preprocess.js similarity index 89% rename from packages/heml-parse/src/preprocessStyles.js rename to packages/heml-styles/src/preprocess.js index 0c2cb3b..60071f7 100644 --- a/packages/heml-parse/src/preprocessStyles.js +++ b/packages/heml-styles/src/preprocess.js @@ -1,12 +1,7 @@ import postcss, { plugin } from 'postcss' import safeParser from 'postcss-safe-parser' import { parse as parseSelector, stringify as stringfySelector } from 'css-selector-tokenizer' -import { unique } from 'shorthash' -import { first, last, intersection, uniq } from 'lodash' - -function toClass(s) { - return `s${unique(s)}` -} +import { get, first, last, intersection, uniq } from 'lodash' function stringifySelectorNodes(nodes) { return stringfySelector({ type: 'selector', nodes }) @@ -189,13 +184,7 @@ const safeSelectorize = plugin('postcss-safe-selectorize', ($) => (root) => { root.walkRules((rule) => { rule.selectors = rule.selectors.map((selector) => { if (isComplexSelector(selector)) { - const classesMap = generateClassesForSelector(selector) - - for (const [ className, selectorPart ] of classesMap) { - $(selectorPart).addClass(className) - } - - return generateReplacementSelector(selector) + return convertToSafeSelector(selector, $) } return selector @@ -228,42 +217,54 @@ function isComplexSelector(selector) { }).length > 0 } +function () + /** * builds a map of selectors to be replaced with the corresponding class * @param {String} selector * @return {Map} selectorAndClassMap */ -function generateClassesForSelector(selector) { +function convertToSafeSelector(selector, $) { const { nodes } = first(parseSelector(selector).nodes) - const map = new Map() - let selectorPartNodes = [] + const classes = new Map() + + + nodes.forEach((node, index) => { + if (type === 'operator' && complexRelationships.includes(operator)) { + const prevPart = getPrevPart(nodes, index) + const { id, classes, tags } = calculateSelectorStore(prevPart) + } + + if (type === 'universal') + }) + /** * 1. gather all the nodes until a pseudo element or dynamic pseudo selector * 2. create a class for the selector part and add selector part/class to the map */ - nodes.forEach((node, index) => { + // nodes.forEach((node, index) => { /** we have a matched pseudo - drop the node, build the previous nodes to a string, and add it to the map entry */ - if (node.type.startsWith('pseudo') && (pseudoElements.includes(node.name) || dynamicPseudoSelectors.includes(node.name))) { - const selectorPart = stringifySelectorNodes(selectorPartNodes) - map.set(toClass(selectorPart), selectorPart) + // if (node.type.startsWith('pseudo') && (pseudoElements.includes(node.name) || dynamicPseudoSelectors.includes(node.name))) { + // const selectorPart = stringifySelectorNodes(selectorPartNodes) + // map.set(toClass(selectorPart), selectorPart) /** * keep the previous last node on so that the selector continues to work * .i.e. a:hover > b will become these selectors ['a', 'a > b'] */ - selectorPartNodes = selectorPartNodes.length > 0 ? [ last(selectorPartNodes) ] : [] - } + // selectorPartNodes = selectorPartNodes.length > 0 ? [ last(selectorPartNodes) ] : [] + // } /** we are on the last element, push it on, and add the */ - else if (index === nodes.length - 1) { - selectorPartNodes.push(node) - const selectorPart = stringifySelectorNodes(selectorPartNodes) - map.set(toClass(selectorPart), selectorPart) - } + // else if (index === nodes.length - 1) { + // selectorPartNodes.push(node) + // const selectorPart = stringifySelectorNodes(selectorPartNodes) + // map.set(toClass(selectorPart), selectorPart) + // } /** push the node to the current selector part */ - else { - selectorPartNodes.push(node) - } + // else { + // selectorPartNodes.push(node) + // } }) return map