From 19aabbf83102f4fcdf5b32e8c681c956d096a227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Sanz?= Date: Sat, 20 Aug 2022 16:19:03 +0200 Subject: [PATCH 1/2] CSS Scoping tests reworked --- .../src/core/style/scoped-stylesheet.unit.ts | 164 +++++++++++++++--- packages/qwik/src/core/util/markers.js | 43 +++++ 2 files changed, 180 insertions(+), 27 deletions(-) create mode 100644 packages/qwik/src/core/util/markers.js diff --git a/packages/qwik/src/core/style/scoped-stylesheet.unit.ts b/packages/qwik/src/core/style/scoped-stylesheet.unit.ts index ac37e862398..7035802dda5 100644 --- a/packages/qwik/src/core/style/scoped-stylesheet.unit.ts +++ b/packages/qwik/src/core/style/scoped-stylesheet.unit.ts @@ -4,46 +4,156 @@ import { scopeStylesheet } from './scoped-stylesheet'; const scopedStyles = suite('scopedStyles'); -scopedStyles('element scoping', () => { - equal(scopeStylesheet('a{x}', 'ABC'), 'a.⭐️ABC{x}'); - equal(scopeStylesheet('aa{x}', 'ABC'), 'aa.⭐️ABC{x}'); - equal(scopeStylesheet('a-2{x}', 'ABC'), 'a-2.⭐️ABC{x}'); - equal(scopeStylesheet('a_a{x}', 'ABC'), 'a_a.⭐️ABC{x}'); - equal(scopeStylesheet('a {x}', 'ABC'), 'a.⭐️ABC {x}'); - equal(scopeStylesheet('body {x}', 'ABC'), 'body.⭐️ABC {x}'); - equal(scopeStylesheet('body {body{x}}', 'ABC'), 'body.⭐️ABC {body{x}}'); + +scopedStyles('selectors', () => { + equal(scopeStylesheet('* { color: red; }', 'ABC'), '*.⭐️ABC { color: red; }'); + + equal(scopeStylesheet('div { color: red; }', 'ABC'), 'div.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div, p { color: red; }', 'ABC'), 'div.⭐️ABC, p.⭐️ABC { color: red; }'); + + equal(scopeStylesheet('div p { color: red; }', 'ABC'), 'div.⭐️ABC p.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div > p { color: red; }', 'ABC'), 'div.⭐️ABC > p.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div + p { color: red; }', 'ABC'), 'div.⭐️ABC + p.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div ~ p { color: red; }', 'ABC'), 'div.⭐️ABC ~ p.⭐️ABC { color: red; }'); + + equal(scopeStylesheet('.red { color: red; }', 'ABC'), '.red.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div.red { color: red; }', 'ABC'), 'div.red.⭐️ABC { color: red; }'); + equal(scopeStylesheet('.red * { color: red; }', 'ABC'), '.red.⭐️ABC *.⭐️ABC { color: red; }'); + equal(scopeStylesheet('.red.text { color: red; }', 'ABC'), '.red.text.⭐️ABC { color: red; }'); + + equal(scopeStylesheet('#red { color: red; }', 'ABC'), '#red.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div#red { color: red; }', 'ABC'), 'div#red.⭐️ABC { color: red; }'); + equal(scopeStylesheet('#red * { color: red; }', 'ABC'), '#red.⭐️ABC *.⭐️ABC { color: red; }'); + + equal(scopeStylesheet('div { }', 'ABC'), 'div.⭐️ABC { }'); + equal(scopeStylesheet('div {}', 'ABC'), 'div.⭐️ABC {}'); + equal(scopeStylesheet('div { color: red; background-color: blue; }', 'ABC'), 'div.⭐️ABC { color: red; background-color: blue; }'); + equal(scopeStylesheet('div { color: red !important; }', 'ABC'), 'div.⭐️ABC { color: red !important; }'); + equal(scopeStylesheet('div{color:red;}', 'ABC'), 'div.⭐️ABC{color:red;}'); + equal(scopeStylesheet('div { content: "}"; }', 'ABC'), 'div.⭐️ABC { content: "}"; }'); + equal(scopeStylesheet("div { content: '}'; }", 'ABC'), "div.⭐️ABC { content: '}'; }"); }); -scopedStyles('ignore string content', () => { - equal(scopeStylesheet('body{"}bar{"}', 'ABC'), 'body.⭐️ABC{"}bar{"}'); +scopedStyles('attribute selectors', () => { + equal(scopeStylesheet('*[target] { color: red; }', 'ABC'), '*[target].⭐️ABC { color: red; }'); + equal(scopeStylesheet('*[target] span { color: red; }', 'ABC'), 'a[target].⭐️ABC span.⭐️ABC { color: red; }'); + + equal(scopeStylesheet('a[target] { color: red; }', 'ABC'), 'a[target].⭐️ABC { color: red; }'); + equal(scopeStylesheet('a[target="_blank"] { color: red; }', 'ABC'), 'a[target="_blank"].⭐️ABC { color: red; }'); + equal(scopeStylesheet('input[type="button"] { color: red; }', 'ABC'), 'input[type="button"].⭐️ABC { color: red; }'); + + equal(scopeStylesheet('a[title~="red"] { color: red; }', 'ABC'), 'a[title~="red"].⭐️ABC { color: red; }'); + equal(scopeStylesheet('a[class^="red"] { color: red; }', 'ABC'), 'a[class^="red"].⭐️ABC { color: red; }'); + equal(scopeStylesheet('a[class|="red"] { color: red; }', 'ABC'), 'a[class|="red"].⭐️ABC { color: red; }'); + equal(scopeStylesheet('a[class*="red"] { color: red; }', 'ABC'), 'a[class*="red"].⭐️ABC { color: red; }'); + equal(scopeStylesheet('a[class$="red"] { color: red; }', 'ABC'), 'a[class$="red"].⭐️ABC { color: red; }'); }); -scopedStyles('escape', () => { - equal(scopeStylesheet('q\\:bar{}', 'ABC'), 'q\\:bar.⭐️ABC{}'); +scopedStyles('pseudo classes', () => { + equal(scopeStylesheet('a:link { color: red; }', 'ABC'), 'a:link.⭐️ABC { color: red; }'); + equal(scopeStylesheet('p:lang(en) { color: red; }', 'ABC'), 'p:lang(en).⭐️ABC { color: red; }'); + equal(scopeStylesheet('p:nth-child(2) { color: red; }', 'ABC'), 'p:nth-child(2).⭐️ABC { color: red; }'); + equal(scopeStylesheet('p:nth-child(3n+1) { color: red; }', 'ABC'), 'p:nth-child(3n+1).⭐️ABC { color: red; }'); + equal(scopeStylesheet(':root { color: red; }', 'ABC'), ':root.⭐️ABC { color: red; }'); + equal(scopeStylesheet('p:not(.blue) { color: red; }', 'ABC'), 'p:not(.blue.⭐️ABC).⭐️ABC { color: red; }'); + + equal(scopeStylesheet('p:nth-child(3n+1):hover { color: red; }', 'ABC'), 'p:nth-child(3n+1):hover.⭐️ABC { color: red; }'); + equal(scopeStylesheet('p:nth-child(3n+1) div { color: red; }', 'ABC'), 'p:nth-child(3n+1).⭐️ABC div.⭐️ABC { color: red; }'); }); -scopedStyles('class scoping', () => { - equal(scopeStylesheet('.class {}', 'ABC'), '.class.⭐️ABC {}'); - equal(scopeStylesheet('.a.b.c {}', 'ABC'), '.a.b.c.⭐️ABC {}'); - equal(scopeStylesheet('.div{}', 'ABC'), '.div.⭐️ABC{}'); +scopedStyles('pseudo elements', () => { + equal(scopeStylesheet('::selection { color: red; }', 'ABC'), '.⭐️ABC::selection { color: red; }'); + + equal(scopeStylesheet('a::before { color: red; }', 'ABC'), 'a.⭐️ABC::before { color: red; }'); + equal(scopeStylesheet('a::after { color: red; }', 'ABC'), 'a.⭐️ABC::after { color: red; }'); + + equal(scopeStylesheet('a::first-line { color: red; }', 'ABC'), 'a.⭐️ABC::first-line { color: red; }'); + + equal(scopeStylesheet('a.red::before { color: red; }', 'ABC'), 'a.red.⭐️ABC::before { color: red; }'); + equal(scopeStylesheet('a.red span::before { color: red; }', 'ABC'), 'a.red.⭐️ABC span.⭐️ABC::before { color: red; }'); }); -scopedStyles('nested scoping', () => { - equal(scopeStylesheet('a > .b > c {}', 'ABC'), 'a.⭐️ABC > .b.⭐️ABC > c.⭐️ABC {}'); +scopedStyles('complex properties', () => { + equal(scopeStylesheet('div { background: #D0E4F5 url("./bg.jpg") no-repeat scroll 0 0; }', 'ABC'), 'div.⭐️ABC { background: #D0E4F5 url("./bg.jpg") no-repeat scroll 0 0; }'); + + equal(scopeStylesheet('div { background: -webkit-linear-gradient(left, #1C6EA4 0%, #2388CB 50%, #144E75 100%); }', 'ABC'), 'div.⭐️ABC { background: -webkit-linear-gradient(left, #1C6EA4 0%, #2388CB 50%, #144E75 100%); }'); }); -scopedStyles('media', () => { - equal( - scopeStylesheet('@media(max-width: 999px){body{}}', 'ABC'), - '@media(max-width: 999px){body.⭐️ABC{}}' - ); +scopedStyles('at rules', () => { + equal(scopeStylesheet('@keyframes slidein { from { transform: translateX(0%); } to { transform: translateX(100%); } }', 'ABC'), '@keyframes slidein-⭐️ABC { from { transform: translateX(0%); } to { transform: translateX(100%); } }'); + + equal(scopeStylesheet('@font-face { font-family: "Open Sans"; src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"), url("/fonts/OpenSans-Regular-webfont.woff") format("woff"); }', 'ABC'), '@font-face { font-family: "Open Sans"; src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"), url("/fonts/OpenSans-Regular-webfont.woff") format("woff"); }'); + + equal(scopeStylesheet('@media screen and (min-width: 900px) { div { color: red; } }', 'ABC'), '@media screen and (min-width: 900px) { div.⭐️ABC { color: red; } }'); + + equal(scopeStylesheet('@supports (display: flex) { @media screen and (min-width: 900px) { div { color: red; } } }', 'ABC'), '@supports (display: flex) { @media screen and (min-width: 900px) { div.⭐️ABC { color: red; } } }'); + equal(scopeStylesheet('@supports (display: flex) { div { color: red; } @media screen and (min-width: 900px) { div { color: red; } } div { color: red; } }', 'ABC'), '@supports (display: flex) { { div.⭐️ABC { color: red; } @media screen and (min-width: 900px) { div.⭐️ABC { color: red; } } div.⭐️ABC { color: red; } }'); + + + equal(scopeStylesheet('@supports not (not (transform-origin: 2px)) { div { color: red; } }', 'ABC'), '@supports not (not (transform-origin: 2px)) { div.⭐️ABC { color: red; } }'); + equal(scopeStylesheet('@supports (display: grid) and (not (display: inline-grid)) { div { color: red; } }', 'ABC'), '@supports (display: grid) and (not (display: inline-grid)) { div.⭐️ABC { color: red; } }'); + + equal(scopeStylesheet('@supports ((perspective: 10px) or (-moz-perspective: 10px) or (-webkit-perspective: 10px) or (-ms-perspective: 10px) or (-o-perspective: 10px)) { div { color: red; } }', 'ABC'), '@supports ((perspective: 10px) or (-moz-perspective: 10px) or (-webkit-perspective: 10px) or (-ms-perspective: 10px) or (-o-perspective: 10px)) { div.⭐️ABC { color: red; } }'); }); scopedStyles('comments', () => { - equal( - scopeStylesheet('@media(max-width: 999px){/*body{}*/}', 'ABC'), - '@media(max-width: 999px){/*body{}*/}' - ); + equal(scopeStylesheet('div { color: red; } /* comment */', 'ABC'), 'div.⭐️ABC { color: red; } /* comment */'); + equal(scopeStylesheet('div { /* color: red; */ }', 'ABC'), 'div.⭐️ABC { /* color: red; */ }'); + equal(scopeStylesheet('div /* comment */ { color: red; }', 'ABC'), 'div.⭐️ABC /* comment */ { color: red; }'); + equal(scopeStylesheet('div/* comment */ { color: red; }', 'ABC'), 'div.⭐️ABC/* comment */ { color: red; }'); + equal(scopeStylesheet('/* comment */div { color: red; }', 'ABC'), '/* comment */div.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div /* comment */ > span { color: red; }', 'ABC'), 'div.⭐️ABC /* comment */ > span.⭐️ABC { color: red; }'); +}); + +scopedStyles('global function', () => { + equal(scopeStylesheet(':global(*) { color: red; }', 'ABC'), '* { color: red; }'); + + equal(scopeStylesheet(':global(div) { color: red; }', 'ABC'), 'div { color: red; }'); + equal(scopeStylesheet(':global(div), p { color: red; }', 'ABC'), 'div, p.⭐️ABC { color: red; }'); + + equal(scopeStylesheet('div :global(p) { color: red; }', 'ABC'), 'div.⭐️ABC p { color: red; }'); + equal(scopeStylesheet(':global(div) > p { color: red; }', 'ABC'), 'div > p.⭐️ABC { color: red; }'); + + equal(scopeStylesheet(':global(.red) { color: red; }', 'ABC'), '.red { color: red; }'); + equal(scopeStylesheet(':global(div).red { color: red; }', 'ABC'), 'div.red { color: red; }'); + equal(scopeStylesheet(':global(div.red) { color: red; }', 'ABC'), 'div.red { color: red; }'); + + + equal(scopeStylesheet(':global(#red) { color: red; }', 'ABC'), '#red { color: red; }'); + + equal(scopeStylesheet(':global(div) { }', 'ABC'), 'div { }'); + equal(scopeStylesheet(':global(div) {}', 'ABC'), 'div {}'); + equal(scopeStylesheet(':global(div){color:red;}', 'ABC'), 'div{color:red;}'); + + + equal(scopeStylesheet(':global(*[target]) { color: red; }', 'ABC'), '*[target] { color: red; }'); + equal(scopeStylesheet(':global(*[target]) span { color: red; }', 'ABC'), '*[target] span.⭐️ABC { color: red; }') + equal(scopeStylesheet('*[target] :global(span) { color: red; }', 'ABC'), '*[target].⭐️ABC span { color: red; }'); + + + equal(scopeStylesheet(':global(a):link { color: red; }', 'ABC'), 'a:link { color: red; }'); + equal(scopeStylesheet(':global(a:link) { color: red; }', 'ABC'), 'a:link { color: red; }'); + equal(scopeStylesheet(':global(p:lang(en)) { color: red; }', 'ABC'), 'p:lang(en) { color: red; }'); + equal(scopeStylesheet(':global(p:nth-child(2)) { color: red; }', 'ABC'), 'p:nth-child(2) { color: red; }'); + equal(scopeStylesheet(':global(:root) { color: red; }', 'ABC'), ':root { color: red; }'); + equal(scopeStylesheet(':global(p:not(.blue)) { color: red; }', 'ABC'), 'p:not(.blue) { color: red; }'); + equal(scopeStylesheet('p:not(:global(.blue)) { color: red; }', 'ABC'), 'p:not(.blue).⭐️ABC { color: red; }'); + + equal(scopeStylesheet(':global(p:nth-child(3n+1):hover) { color: red; }', 'ABC'), 'p:nth-child(3n+1):hover { color: red; }'); + equal(scopeStylesheet(':global(p:nth-child(3n+1) div) { color: red; }', 'ABC'), 'p:nth-child(3n+1) div { color: red; }'); + + + equal(scopeStylesheet(':global(::selection) { color: red; }', 'ABC'), '::selection { color: red; }'); + + equal(scopeStylesheet(':global(a)::before { color: red; }', 'ABC'), 'a::before { color: red; }'); + equal(scopeStylesheet(':global(a::after) { color: red; }', 'ABC'), 'a::after { color: red; }'); + + equal(scopeStylesheet(':global(a).red::before { color: red; }', 'ABC'), 'a.red::before { color: red; }'); + equal(scopeStylesheet(':global(a.red) span::before { color: red; }', 'ABC'), 'a.red span.⭐️ABC::before { color: red; }'); + + + equal(scopeStylesheet('@keyframes :global(slidein) { from { transform: translateX(0%); } to { transform: translateX(100%); } }', 'ABC'), '@keyframes slidein { from { transform: translateX(0%); } to { transform: translateX(100%); } }'); + equal(scopeStylesheet('@media screen and (min-width: 900px) { :global(div) { color: red; } }', 'ABC'), '@media screen and (min-width: 900px) { div { color: red; } }'); }); scopedStyles.run(); diff --git a/packages/qwik/src/core/util/markers.js b/packages/qwik/src/core/util/markers.js new file mode 100644 index 00000000000..c6d3f4a457a --- /dev/null +++ b/packages/qwik/src/core/util/markers.js @@ -0,0 +1,43 @@ +"use strict"; +exports.__esModule = true; +exports.ELEMENT_ID_Q_PROPS_PREFIX = exports.ELEMENT_ID_PREFIX = exports.ELEMENT_ID_SELECTOR = exports.ELEMENT_ID = exports.QSlotInertName = exports.RenderEvent = exports.QContainerSelector = exports.QContainerAttr = exports.QCtxAttr = exports.QScopedStyle = exports.QStyle = exports.QSlotName = exports.QSlotRef = exports.QSlot = exports.EventAny = exports.EventPrefix = exports.ComponentStylesPrefixContent = exports.ComponentStylesPrefixHost = exports.OnRenderProp = void 0; +/** + * State factory of the component. + */ +exports.OnRenderProp = 'q:renderFn'; +/** + * Component style host prefix + */ +exports.ComponentStylesPrefixHost = '💎'; +/** + * Component style content prefix + */ +exports.ComponentStylesPrefixContent = '⭐️'; +/** + * Prefix used to identify on listeners. + */ +exports.EventPrefix = 'on:'; +/** + * Attribute used to mark that an event listener is attached. + */ +exports.EventAny = 'on:.'; +/** + * `` + */ +exports.QSlot = 'q:slot'; +exports.QSlotRef = 'q:sref'; +exports.QSlotName = 'q:sname'; +exports.QStyle = 'q:style'; +exports.QScopedStyle = 'q:sstyle'; +exports.QCtxAttr = 'q:ctx'; +exports.QContainerAttr = 'q:container'; +exports.QContainerSelector = '[q\\:container]'; +exports.RenderEvent = 'qRender'; +/** + * `` + */ +exports.QSlotInertName = '\u0000'; +exports.ELEMENT_ID = 'q:id'; +exports.ELEMENT_ID_SELECTOR = '[q\\:id]'; +exports.ELEMENT_ID_PREFIX = '#'; +exports.ELEMENT_ID_Q_PROPS_PREFIX = '&'; From c475e70b15292334bbd214d6a89d48a9be041c4a Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sun, 21 Aug 2022 14:57:20 -0700 Subject: [PATCH 2/2] fix(css): Added missing tests and behavior to CSS scoping --- .../qwik/src/core/style/scoped-stylesheet.ts | 344 ++++++++++++++++-- .../src/core/style/scoped-stylesheet.unit.ts | 304 ++++++++++------ packages/qwik/src/core/util/markers.js | 43 --- 3 files changed, 505 insertions(+), 186 deletions(-) delete mode 100644 packages/qwik/src/core/util/markers.js diff --git a/packages/qwik/src/core/style/scoped-stylesheet.ts b/packages/qwik/src/core/style/scoped-stylesheet.ts index 983d45ce158..4938090b7c5 100644 --- a/packages/qwik/src/core/style/scoped-stylesheet.ts +++ b/packages/qwik/src/core/style/scoped-stylesheet.ts @@ -1,14 +1,22 @@ +/* eslint-disable no-console */ import { ComponentStylesPrefixContent } from '../util/markers'; +// Make sure this is always set to `false` in production, but it is useful to set for `true` in development for debugging. +const DEBUG: boolean = false; + export function scopeStylesheet(css: string, scopeId: string): string { const end = css.length; const out: string[] = []; const stack: MODE[] = []; let idx = 0; let lastIdx = idx; - let mode: MODE = MODE.selector as any; + let mode: MODE = MODE.rule as any; let lastCh = 0; + DEBUG && console.log('--------------------------'); while (idx < end) { + DEBUG && console.log(css); + DEBUG && console.log(new Array(idx).fill(' ').join('') + '^'); + DEBUG && console.log('MODE', ...stack.map(modeToString), modeToString(mode)); let ch = css.charCodeAt(idx++); if (ch === CHAR.BACKSLASH) { idx++; @@ -16,25 +24,79 @@ export function scopeStylesheet(css: string, scopeId: string): string { } const arcs = STATE_MACHINE[mode]; for (let i = 0; i < arcs.length; i++) { - const [expectLastCh, expectCh, newMode] = arcs[i]; + const arc = arcs[i]; + const [expectLastCh, expectCh, newMode] = arc; if ( expectLastCh === lastCh || expectLastCh === CHAR.ANY || - (expectLastCh === CHAR.IDENT && isIdent(lastCh)) + (expectLastCh === CHAR.IDENT && isIdent(lastCh)) || + (expectLastCh === CHAR.WHITESPACE && isWhiteSpace(lastCh)) ) { if ( expectCh === ch || expectCh === CHAR.ANY || - (expectCh === CHAR.NOT_IDENT_AND_NOT_DOT && !isIdent(ch) && ch !== CHAR.DOT) + (expectCh === CHAR.IDENT && isIdent(ch)) || + (expectCh === CHAR.NOT_IDENT && !isIdent(ch) && ch !== CHAR.DOT) || + (expectCh === CHAR.WHITESPACE && isWhiteSpace(ch)) ) { - if (newMode === MODE.EXIT) { - mode = stack.pop() || MODE.selector; - } else if (mode === newMode) { - flush(idx - 1); - out.push('.', ComponentStylesPrefixContent, scopeId); - } else { - stack.push(mode); - mode = newMode; + if (arc.length == 3 || lookAhead(arc)) { + DEBUG && + console.log( + 'MATCH', + charToString(expectLastCh), + charToString(expectCh), + modeToString(newMode) + ); + // We found a match! + if (newMode === MODE.EXIT || newMode == MODE.EXIT_INSERT_SCOPE) { + if (newMode === MODE.EXIT_INSERT_SCOPE) { + if (mode === MODE.starSelector && !isInGlobal()) { + // Replace `*` with the scoping elementClassIdSelector. + if (isChainedSelector(ch)) { + // *foo + flush(idx - 2); + } else { + // * (by itself) + insertScopingSelector(idx - 2); + } + lastIdx++; + } else { + if (!isChainedSelector(ch)) { + // We are exiting one of the Selector so we may need to + const offset = + expectCh == CHAR.NOT_IDENT ? 1 : expectCh == CHAR.CLOSE_PARENTHESIS ? 2 : 0; + insertScopingSelector(idx - offset); + } + } + } + if (expectCh === CHAR.NOT_IDENT) { + // NOT_IDENT is not a real character more like lack of what we expected. + // if pseudoGlobal we need to give it a chance to exit as well. + // For this reason we need to reparse the last character again. + idx--; + ch = lastCh; + } + do { + mode = stack.pop() || MODE.rule; + if (mode === MODE.pseudoGlobal) { + // Skip over the `)` in `:global(...)`. + flush(idx - 1); + lastIdx++; + } + } while (isSelfClosingRule(mode)); + } else { + stack.push(mode); + if (mode === MODE.pseudoGlobal && newMode === MODE.rule) { + flush(idx - 8); // `:global(`.length + lastIdx = idx; // skip over ":global(" + } else if (newMode === MODE.pseudoElement) { + // We are entering pseudoElement `::foo`; insert scoping in front of it. + insertScopingSelector(idx - 2); + } + mode = newMode; + ch == CHAR.SPACE; // Pretend not an identifier so that we don't flush again on elementClassIdSelector + } + break; // get out of the for loop as we found a match } } } @@ -46,8 +108,35 @@ export function scopeStylesheet(css: string, scopeId: string): string { function flush(idx: number) { out.push(css.substring(lastIdx, idx)); + DEBUG && console.log('FLUSH', out.join('')); lastIdx = idx; } + function insertScopingSelector(idx: number) { + if (mode === MODE.pseudoGlobal || isInGlobal()) return; + + flush(idx); + const separator = stack.length && stack[stack.length - 1] === MODE.atRuleSelector ? '-' : '.'; + out.push(separator, ComponentStylesPrefixContent, scopeId); + DEBUG && console.log('INSERT', out.join('')); + } + function lookAhead(arc: StateArc): boolean { + words: for (let arcIndx = 3; arcIndx < arc.length; arcIndx++) { + const txt = arc[arcIndx] as string; + for (let i = 0; i < txt.length; i++) { + if ((css.charCodeAt(idx + i) | CHAR.LOWERCASE) !== txt.charCodeAt(i)) { + continue words; + } + } + // we found a match; + idx += txt.length; + return true; + } + return false; + } + + function isInGlobal(): boolean { + return stack.indexOf(MODE.pseudoGlobal) !== -1; + } } function isIdent(ch: number): boolean { @@ -55,40 +144,124 @@ function isIdent(ch: number): boolean { (ch >= CHAR._0 && ch <= CHAR._9) || (ch >= CHAR.A && ch <= CHAR.Z) || (ch >= CHAR.a && ch <= CHAR.z) || + ch >= 0x80 || ch === CHAR.UNDERSCORE || ch === CHAR.DASH ); } +function isChainedSelector(ch: number): boolean { + return ( + ch === CHAR.COLON || + ch === CHAR.DOT || + ch === CHAR.OPEN_BRACKET || + ch === CHAR.HASH || + isIdent(ch) + ); +} + +function isSelfClosingRule(mode: MODE): boolean { + return ( + mode === MODE.atRuleBlock || + mode === MODE.atRuleSelector || + mode === MODE.atRuleInert || + mode === MODE.pseudoGlobal + ); +} + +function isWhiteSpace(ch: number): boolean { + return ch === CHAR.SPACE || ch === CHAR.TAB || ch === CHAR.NEWLINE || ch === CHAR.CARRIAGE_RETURN; +} + +function modeToString(mode: MODE): string { + return [ + 'rule', + 'elementClassIdSelector', + 'starSelector', + 'pseudoClassWithSelector', + 'pseudoClass', + 'pseudoGlobal', + 'pseudoElement', + 'attrSelector', + 'inertParenthesis', + 'inertBlock', + 'atRuleSelector', + 'atRuleBlock', + 'atInert', + 'body', + 'stringSingle', + 'stringDouble', + 'commentMultiline', + 'EXIT', + 'EXIT_INSERT_SCOPE', + ][mode]; +} + +function charToString(ch: number): string { + return ['ANY', 'IDENT', 'NOT_IDENT', 'WHITESPACE'][ch] || String.fromCharCode(ch); +} const enum MODE { - selector, // .selector {} - media, // .selector {} - body, // .selector {body} + rule, // top level initial space. + elementClassIdSelector, // .elementClassIdSelector {} + starSelector, // * {} + pseudoClassWithSelector, // :pseudoClass(elementClassIdSelector) {} + pseudoClass, // :pseudoClass {} + pseudoGlobal, // :global(elementClassIdSelector) + pseudoElement, // ::pseudoElement {} + attrSelector, // [attr] {} + inertParenthesis, // (ignored) + inertBlock, // {ignored} + atRuleSelector, // @keyframe elementClassIdSelector {} + atRuleBlock, // @media {elementClassIdSelector {}} + atRuleInert, // @atRule something; + body, // .elementClassIdSelector {body} stringSingle, // 'text' stringDouble, // 'text' commentMultiline, // /* ... */ - EXIT, // Exit the mode (Not a real mode) + // NOT REAL MODES + EXIT, // Exit the mode + EXIT_INSERT_SCOPE, // Exit the mode INSERT SCOPE } const enum CHAR { ANY = 0, IDENT = 1, - NOT_IDENT_AND_NOT_DOT = 2, + NOT_IDENT = 2, + WHITESPACE = 3, + TAB = 9, // `\t`.charCodeAt(0); + NEWLINE = 10, // `\n`.charCodeAt(0); + CARRIAGE_RETURN = 13, // `\r`.charCodeAt(0); SPACE = 32, // ` `.charCodeAt(0); - FORWARD_SLASH = 47, // `/`.charCodeAt(0); DOUBLE_QUOTE = 34, // `"`.charCodeAt(0); + HASH = 35, // `#`.charCodeAt(0); SINGLE_QUOTE = 39, // `'`.charCodeAt(0); + OPEN_PARENTHESIS = 40, // `(`.charCodeAt(0); + CLOSE_PARENTHESIS = 41, // `)`.charCodeAt(0); STAR = 42, // `*`.charCodeAt(0); + COMMA = 44, // `,`.charCodeAt(0); DASH = 45, // `-`.charCodeAt(0); DOT = 46, // `.`.charCodeAt(0); + FORWARD_SLASH = 47, // `/`.charCodeAt(0); + _0 = 48, // `0`.charCodeAt(0); + _9 = 57, // `9`.charCodeAt(0); + COLON = 58, // `:`.charCodeAt(0); + SEMICOLON = 59, // `;`.charCodeAt(0); + LESS_THAN = 60, // `<`.charCodeAt(0); AT = 64, // `@`.charCodeAt(0); A = 65, // `A`.charCodeAt(0); Z = 90, // `Z`.charCodeAt(0); - _0 = 48, // `0`.charCodeAt(0); - _9 = 57, // `9`.charCodeAt(0); + OPEN_BRACKET = 91, // `[`.charCodeAt(0); + CLOSE_BRACKET = 93, // `]`.charCodeAt(0); BACKSLASH = 92, // `\\`.charCodeAt(0); UNDERSCORE = 95, // `_`.charCodeAt(0); + LOWERCASE = 0x20, // `a`.charCodeAt(0); a = 97, // `a`.charCodeAt(0); + d = 100, // `d`.charCodeAt(0); + g = 103, // 'g'.charCodeAt(0); + h = 104, // `h`.charCodeAt(0); + i = 105, // `i`.charCodeAt(0); + l = 108, // `l`.charCodeAt(0); + t = 116, // `t`.charCodeAt(0); z = 122, // `z`.charCodeAt(0); OPEN_BRACE = 123, // `{`.charCodeAt(0); CLOSE_BRACE = 125, // `}`.charCodeAt(0); @@ -100,29 +273,130 @@ type StateArc = [ /// If the current character is this: CHAR, /// Then transition to this state: - MODE + MODE, + /// Optional look ahead strings + ...string[] +]; + +const STRINGS_COMMENTS: StateArc[] = [ + [CHAR.ANY, CHAR.SINGLE_QUOTE, MODE.stringSingle], + [CHAR.ANY, CHAR.DOUBLE_QUOTE, MODE.stringDouble], + [CHAR.ANY, CHAR.FORWARD_SLASH, MODE.commentMultiline, '*'], ]; const STATE_MACHINE: StateArc[][] = [ [ - [CHAR.IDENT, CHAR.NOT_IDENT_AND_NOT_DOT, MODE.selector], - [CHAR.ANY, CHAR.AT, MODE.media], + /// rule + [CHAR.ANY, CHAR.STAR, MODE.starSelector], + [CHAR.ANY, CHAR.OPEN_BRACKET, MODE.attrSelector], + [CHAR.ANY, CHAR.COLON, MODE.pseudoElement, ':'], + [CHAR.ANY, CHAR.COLON, MODE.pseudoGlobal, 'global'], + [ + CHAR.ANY, + CHAR.COLON, + MODE.pseudoClassWithSelector, + 'has', + 'host-context', + 'not', + 'where', + 'is', + 'matches', + 'any', + ], + [CHAR.ANY, CHAR.COLON, MODE.pseudoClass], + [CHAR.ANY, CHAR.IDENT, MODE.elementClassIdSelector], + [CHAR.ANY, CHAR.DOT, MODE.elementClassIdSelector], + [CHAR.ANY, CHAR.HASH, MODE.elementClassIdSelector], + [CHAR.ANY, CHAR.AT, MODE.atRuleSelector, 'keyframe'], + [CHAR.ANY, CHAR.AT, MODE.atRuleBlock, 'media', 'supports'], + [CHAR.ANY, CHAR.AT, MODE.atRuleInert], [CHAR.ANY, CHAR.OPEN_BRACE, MODE.body], [CHAR.FORWARD_SLASH, CHAR.STAR, MODE.commentMultiline], - ] /*selector*/, - [ + [CHAR.ANY, CHAR.SEMICOLON, MODE.EXIT], [CHAR.ANY, CHAR.CLOSE_BRACE, MODE.EXIT], - [CHAR.FORWARD_SLASH, CHAR.STAR, MODE.commentMultiline], - [CHAR.ANY, CHAR.OPEN_BRACE, MODE.selector], - [CHAR.FORWARD_SLASH, CHAR.STAR, MODE.commentMultiline], - ] /*media*/, + [CHAR.ANY, CHAR.CLOSE_PARENTHESIS, MODE.EXIT], + ...STRINGS_COMMENTS, + ], [ - [CHAR.ANY, CHAR.CLOSE_BRACE, MODE.EXIT], + /// elementClassIdSelector + [CHAR.ANY, CHAR.NOT_IDENT, MODE.EXIT_INSERT_SCOPE], + ], + [ + /// starSelector + [CHAR.ANY, CHAR.NOT_IDENT, MODE.EXIT_INSERT_SCOPE], + ], + [ + /// pseudoClassWithSelector + [CHAR.ANY, CHAR.OPEN_PARENTHESIS, MODE.rule], + [CHAR.ANY, CHAR.NOT_IDENT, MODE.EXIT_INSERT_SCOPE], + ], + [ + /// pseudoClass + [CHAR.ANY, CHAR.OPEN_PARENTHESIS, MODE.inertParenthesis], + [CHAR.ANY, CHAR.NOT_IDENT, MODE.EXIT_INSERT_SCOPE], + ], + [ + /// pseudoGlobal + [CHAR.ANY, CHAR.OPEN_PARENTHESIS, MODE.rule], + [CHAR.ANY, CHAR.NOT_IDENT, MODE.EXIT], + ], + [ + /// pseudoElement + [CHAR.ANY, CHAR.NOT_IDENT, MODE.EXIT], + ], + [ + /// attrSelector + [CHAR.ANY, CHAR.CLOSE_BRACKET, MODE.EXIT_INSERT_SCOPE], [CHAR.ANY, CHAR.SINGLE_QUOTE, MODE.stringSingle], [CHAR.ANY, CHAR.DOUBLE_QUOTE, MODE.stringDouble], - [CHAR.FORWARD_SLASH, CHAR.STAR, MODE.commentMultiline], - ] /*body*/, - [[CHAR.ANY, CHAR.SINGLE_QUOTE, MODE.EXIT]] /*stringSingle*/, - [[CHAR.ANY, CHAR.DOUBLE_QUOTE, MODE.EXIT]] /*stringDouble*/, - [[CHAR.STAR, CHAR.FORWARD_SLASH, MODE.EXIT]] /*commentMultiline*/, + ], + [ + /// inertParenthesis + [CHAR.ANY, CHAR.CLOSE_PARENTHESIS, MODE.EXIT], + ...STRINGS_COMMENTS, + ], + [ + /// inertBlock + [CHAR.ANY, CHAR.CLOSE_BRACE, MODE.EXIT], + ...STRINGS_COMMENTS, + ], + [ + /// atRuleSelector + [CHAR.ANY, CHAR.CLOSE_BRACE, MODE.EXIT], + [CHAR.WHITESPACE, CHAR.IDENT, MODE.elementClassIdSelector], + [CHAR.ANY, CHAR.COLON, MODE.pseudoGlobal, 'global'], + [CHAR.ANY, CHAR.OPEN_BRACE, MODE.body], + ...STRINGS_COMMENTS, + ], + [ + /// atRuleBlock + [CHAR.ANY, CHAR.OPEN_BRACE, MODE.rule], + [CHAR.ANY, CHAR.SEMICOLON, MODE.EXIT], + ...STRINGS_COMMENTS, + ], + [ + /// atRuleInert + [CHAR.ANY, CHAR.SEMICOLON, MODE.EXIT], + [CHAR.ANY, CHAR.OPEN_BRACE, MODE.inertBlock], + ...STRINGS_COMMENTS, + ], + [ + /// body + [CHAR.ANY, CHAR.CLOSE_BRACE, MODE.EXIT], + [CHAR.ANY, CHAR.OPEN_BRACE, MODE.body], + [CHAR.ANY, CHAR.OPEN_PARENTHESIS, MODE.inertParenthesis], + ...STRINGS_COMMENTS, + ], + [ + /// stringSingle + [CHAR.ANY, CHAR.SINGLE_QUOTE, MODE.EXIT], + ], + [ + /// stringDouble + [CHAR.ANY, CHAR.DOUBLE_QUOTE, MODE.EXIT], + ], + [ + /// commentMultiline + [CHAR.STAR, CHAR.FORWARD_SLASH, MODE.EXIT], + ], ]; diff --git a/packages/qwik/src/core/style/scoped-stylesheet.unit.ts b/packages/qwik/src/core/style/scoped-stylesheet.unit.ts index 7035802dda5..2881ca19d3c 100644 --- a/packages/qwik/src/core/style/scoped-stylesheet.unit.ts +++ b/packages/qwik/src/core/style/scoped-stylesheet.unit.ts @@ -4,156 +4,244 @@ import { scopeStylesheet } from './scoped-stylesheet'; const scopedStyles = suite('scopedStyles'); - scopedStyles('selectors', () => { - equal(scopeStylesheet('* { color: red; }', 'ABC'), '*.⭐️ABC { color: red; }'); - - equal(scopeStylesheet('div { color: red; }', 'ABC'), 'div.⭐️ABC { color: red; }'); - equal(scopeStylesheet('div, p { color: red; }', 'ABC'), 'div.⭐️ABC, p.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div {}', '_'), 'div.⭐️_ {}'); + equal(scopeStylesheet('div {}div{} div {}', '_'), 'div.⭐️_ {}div.⭐️_{} div.⭐️_ {}'); + equal(scopeStylesheet('div, p {}', '_'), 'div.⭐️_, p.⭐️_ {}'); - equal(scopeStylesheet('div p { color: red; }', 'ABC'), 'div.⭐️ABC p.⭐️ABC { color: red; }'); - equal(scopeStylesheet('div > p { color: red; }', 'ABC'), 'div.⭐️ABC > p.⭐️ABC { color: red; }'); - equal(scopeStylesheet('div + p { color: red; }', 'ABC'), 'div.⭐️ABC + p.⭐️ABC { color: red; }'); - equal(scopeStylesheet('div ~ p { color: red; }', 'ABC'), 'div.⭐️ABC ~ p.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div p {}', '_'), 'div.⭐️_ p.⭐️_ {}'); + equal(scopeStylesheet('div > p {}', '_'), 'div.⭐️_ > p.⭐️_ {}'); + equal(scopeStylesheet('div + p {}', '_'), 'div.⭐️_ + p.⭐️_ {}'); + equal(scopeStylesheet('div ~ p {}', '_'), 'div.⭐️_ ~ p.⭐️_ {}'); - equal(scopeStylesheet('.red { color: red; }', 'ABC'), '.red.⭐️ABC { color: red; }'); - equal(scopeStylesheet('div.red { color: red; }', 'ABC'), 'div.red.⭐️ABC { color: red; }'); - equal(scopeStylesheet('.red * { color: red; }', 'ABC'), '.red.⭐️ABC *.⭐️ABC { color: red; }'); - equal(scopeStylesheet('.red.text { color: red; }', 'ABC'), '.red.text.⭐️ABC { color: red; }'); - - equal(scopeStylesheet('#red { color: red; }', 'ABC'), '#red.⭐️ABC { color: red; }'); - equal(scopeStylesheet('div#red { color: red; }', 'ABC'), 'div#red.⭐️ABC { color: red; }'); - equal(scopeStylesheet('#red * { color: red; }', 'ABC'), '#red.⭐️ABC *.⭐️ABC { color: red; }'); + equal(scopeStylesheet('.red {}', '_'), '.red.⭐️_ {}'); + equal(scopeStylesheet('div.red {}', '_'), 'div.red.⭐️_ {}'); +}); +scopedStyles('unicode', () => { + equal(scopeStylesheet('.miško{}', '_'), '.miško.⭐️_{}'); +}); +scopedStyles('selectors with *', () => { + equal(scopeStylesheet('* {}', '_'), '.⭐️_ {}'); + equal(scopeStylesheet('.red * {}', '_'), '.red.⭐️_ .⭐️_ {}'); + equal(scopeStylesheet('#red * {}', '_'), '#red.⭐️_ .⭐️_ {}'); +}); - equal(scopeStylesheet('div { }', 'ABC'), 'div.⭐️ABC { }'); - equal(scopeStylesheet('div {}', 'ABC'), 'div.⭐️ABC {}'); - equal(scopeStylesheet('div { color: red; background-color: blue; }', 'ABC'), 'div.⭐️ABC { color: red; background-color: blue; }'); - equal(scopeStylesheet('div { color: red !important; }', 'ABC'), 'div.⭐️ABC { color: red !important; }'); - equal(scopeStylesheet('div{color:red;}', 'ABC'), 'div.⭐️ABC{color:red;}'); - equal(scopeStylesheet('div { content: "}"; }', 'ABC'), 'div.⭐️ABC { content: "}"; }'); - equal(scopeStylesheet("div { content: '}'; }", 'ABC'), "div.⭐️ABC { content: '}'; }"); +scopedStyles('selectors with chains', () => { + equal(scopeStylesheet('.red.text {}', '_'), '.red.text.⭐️_ {}'); + + equal(scopeStylesheet('#red {}', '_'), '#red.⭐️_ {}'); + equal(scopeStylesheet('div#red {}', '_'), 'div#red.⭐️_ {}'); + + equal(scopeStylesheet('div { }', '_'), 'div.⭐️_ { }'); + equal(scopeStylesheet('div {}', '_'), 'div.⭐️_ {}'); + equal( + scopeStylesheet('div {background-color: blue; }', '_'), + 'div.⭐️_ {background-color: blue; }' + ); + equal( + scopeStylesheet('div { color: red !important; }', '_'), + 'div.⭐️_ { color: red !important; }' + ); + equal(scopeStylesheet('div{color:red;}', '_'), 'div.⭐️_{color:red;}'); + equal(scopeStylesheet('div { content: "}"; }', '_'), 'div.⭐️_ { content: "}"; }'); + equal(scopeStylesheet("div { content: '}'; }", '_'), "div.⭐️_ { content: '}'; }"); }); scopedStyles('attribute selectors', () => { - equal(scopeStylesheet('*[target] { color: red; }', 'ABC'), '*[target].⭐️ABC { color: red; }'); - equal(scopeStylesheet('*[target] span { color: red; }', 'ABC'), 'a[target].⭐️ABC span.⭐️ABC { color: red; }'); - - equal(scopeStylesheet('a[target] { color: red; }', 'ABC'), 'a[target].⭐️ABC { color: red; }'); - equal(scopeStylesheet('a[target="_blank"] { color: red; }', 'ABC'), 'a[target="_blank"].⭐️ABC { color: red; }'); - equal(scopeStylesheet('input[type="button"] { color: red; }', 'ABC'), 'input[type="button"].⭐️ABC { color: red; }'); - - equal(scopeStylesheet('a[title~="red"] { color: red; }', 'ABC'), 'a[title~="red"].⭐️ABC { color: red; }'); - equal(scopeStylesheet('a[class^="red"] { color: red; }', 'ABC'), 'a[class^="red"].⭐️ABC { color: red; }'); - equal(scopeStylesheet('a[class|="red"] { color: red; }', 'ABC'), 'a[class|="red"].⭐️ABC { color: red; }'); - equal(scopeStylesheet('a[class*="red"] { color: red; }', 'ABC'), 'a[class*="red"].⭐️ABC { color: red; }'); - equal(scopeStylesheet('a[class$="red"] { color: red; }', 'ABC'), 'a[class$="red"].⭐️ABC { color: red; }'); + equal(scopeStylesheet('*[a]{}', '_'), '[a].⭐️_{}'); + equal(scopeStylesheet('*[a] {}', '_'), '[a].⭐️_ {}'); + equal(scopeStylesheet('*[target] span {}', '_'), '[target].⭐️_ span.⭐️_ {}'); + + equal(scopeStylesheet('a[target] {}', '_'), 'a[target].⭐️_ {}'); + equal(scopeStylesheet('a[target="_blank"] {}', '_'), 'a[target="_blank"].⭐️_ {}'); + equal(scopeStylesheet('input[type="button"] {}', '_'), 'input[type="button"].⭐️_ {}'); + + equal(scopeStylesheet('a[title~="red"] {}', '_'), 'a[title~="red"].⭐️_ {}'); + equal(scopeStylesheet('a[class^="red"] {}', '_'), 'a[class^="red"].⭐️_ {}'); + equal(scopeStylesheet('a[class|="red"] {}', '_'), 'a[class|="red"].⭐️_ {}'); + equal(scopeStylesheet('a[class*="red"] {}', '_'), 'a[class*="red"].⭐️_ {}'); + equal(scopeStylesheet('a[class$="red"] {}', '_'), 'a[class$="red"].⭐️_ {}'); }); scopedStyles('pseudo classes', () => { - equal(scopeStylesheet('a:link { color: red; }', 'ABC'), 'a:link.⭐️ABC { color: red; }'); - equal(scopeStylesheet('p:lang(en) { color: red; }', 'ABC'), 'p:lang(en).⭐️ABC { color: red; }'); - equal(scopeStylesheet('p:nth-child(2) { color: red; }', 'ABC'), 'p:nth-child(2).⭐️ABC { color: red; }'); - equal(scopeStylesheet('p:nth-child(3n+1) { color: red; }', 'ABC'), 'p:nth-child(3n+1).⭐️ABC { color: red; }'); - equal(scopeStylesheet(':root { color: red; }', 'ABC'), ':root.⭐️ABC { color: red; }'); - equal(scopeStylesheet('p:not(.blue) { color: red; }', 'ABC'), 'p:not(.blue.⭐️ABC).⭐️ABC { color: red; }'); - - equal(scopeStylesheet('p:nth-child(3n+1):hover { color: red; }', 'ABC'), 'p:nth-child(3n+1):hover.⭐️ABC { color: red; }'); - equal(scopeStylesheet('p:nth-child(3n+1) div { color: red; }', 'ABC'), 'p:nth-child(3n+1).⭐️ABC div.⭐️ABC { color: red; }'); + equal(scopeStylesheet('p:lang(en) {}', '_'), 'p:lang(en).⭐️_ {}'); + equal(scopeStylesheet('a:link {}', '_'), 'a:link.⭐️_ {}'); + equal(scopeStylesheet('p:nth-child(2) {}', '_'), 'p:nth-child(2).⭐️_ {}'); + equal(scopeStylesheet('p:nth-child(3n+1) {}', '_'), 'p:nth-child(3n+1).⭐️_ {}'); +}); +scopedStyles('pseudo classes without selector', () => { + equal(scopeStylesheet(':root {}', '_'), ':root.⭐️_ {}'); +}); +scopedStyles('pseudo selector with negation', () => { + equal(scopeStylesheet('p:not(.blue) {}', '_'), 'p:not(.blue.⭐️_).⭐️_ {}'); +}); +scopedStyles('pseudo selector with :nth', () => { + equal(scopeStylesheet('p:nth-child(3n+1):hover {}', '_'), 'p:nth-child(3n+1):hover.⭐️_ {}'); + equal(scopeStylesheet('p:nth-child(3n+1) div {}', '_'), 'p:nth-child(3n+1).⭐️_ div.⭐️_ {}'); }); scopedStyles('pseudo elements', () => { - equal(scopeStylesheet('::selection { color: red; }', 'ABC'), '.⭐️ABC::selection { color: red; }'); + equal(scopeStylesheet('::selection {}', '_'), '.⭐️_::selection {}'); + equal(scopeStylesheet(' ::space {}', '_'), ' .⭐️_::space {}'); - equal(scopeStylesheet('a::before { color: red; }', 'ABC'), 'a.⭐️ABC::before { color: red; }'); - equal(scopeStylesheet('a::after { color: red; }', 'ABC'), 'a.⭐️ABC::after { color: red; }'); + equal(scopeStylesheet('a::before {}', '_'), 'a.⭐️_::before {}'); + equal(scopeStylesheet('a::after {}', '_'), 'a.⭐️_::after {}'); - equal(scopeStylesheet('a::first-line { color: red; }', 'ABC'), 'a.⭐️ABC::first-line { color: red; }'); + equal(scopeStylesheet('a::first-line {}', '_'), 'a.⭐️_::first-line {}'); - equal(scopeStylesheet('a.red::before { color: red; }', 'ABC'), 'a.red.⭐️ABC::before { color: red; }'); - equal(scopeStylesheet('a.red span::before { color: red; }', 'ABC'), 'a.red.⭐️ABC span.⭐️ABC::before { color: red; }'); + equal(scopeStylesheet('a.red::before {}', '_'), 'a.red.⭐️_::before {}'); + equal(scopeStylesheet('a.red span::before {}', '_'), 'a.red.⭐️_ span.⭐️_::before {}'); }); scopedStyles('complex properties', () => { - equal(scopeStylesheet('div { background: #D0E4F5 url("./bg.jpg") no-repeat scroll 0 0; }', 'ABC'), 'div.⭐️ABC { background: #D0E4F5 url("./bg.jpg") no-repeat scroll 0 0; }'); - - equal(scopeStylesheet('div { background: -webkit-linear-gradient(left, #1C6EA4 0%, #2388CB 50%, #144E75 100%); }', 'ABC'), 'div.⭐️ABC { background: -webkit-linear-gradient(left, #1C6EA4 0%, #2388CB 50%, #144E75 100%); }'); + equal( + scopeStylesheet('div { background: #D0E4F5 url("./bg.jpg") no-repeat scroll 0 0; }', '_'), + 'div.⭐️_ { background: #D0E4F5 url("./bg.jpg") no-repeat scroll 0 0; }' + ); + + equal( + scopeStylesheet( + 'div { background: -webkit-linear-gradient(left, #1C6EA4 0%, #2388CB 50%, #144E75 100%); }', + '_' + ), + 'div.⭐️_ { background: -webkit-linear-gradient(left, #1C6EA4 0%, #2388CB 50%, #144E75 100%); }' + ); }); -scopedStyles('at rules', () => { - equal(scopeStylesheet('@keyframes slidein { from { transform: translateX(0%); } to { transform: translateX(100%); } }', 'ABC'), '@keyframes slidein-⭐️ABC { from { transform: translateX(0%); } to { transform: translateX(100%); } }'); - - equal(scopeStylesheet('@font-face { font-family: "Open Sans"; src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"), url("/fonts/OpenSans-Regular-webfont.woff") format("woff"); }', 'ABC'), '@font-face { font-family: "Open Sans"; src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"), url("/fonts/OpenSans-Regular-webfont.woff") format("woff"); }'); - - equal(scopeStylesheet('@media screen and (min-width: 900px) { div { color: red; } }', 'ABC'), '@media screen and (min-width: 900px) { div.⭐️ABC { color: red; } }'); +scopedStyles('@keyframe', () => { + equal( + scopeStylesheet('@keyframes slidein {from{b:c(0%);}to{b:c(0%);}}', '_'), + '@keyframes slidein-⭐️_ {from{b:c(0%);}to{b:c(0%);}}' + ); +}); - equal(scopeStylesheet('@supports (display: flex) { @media screen and (min-width: 900px) { div { color: red; } } }', 'ABC'), '@supports (display: flex) { @media screen and (min-width: 900px) { div.⭐️ABC { color: red; } } }'); - equal(scopeStylesheet('@supports (display: flex) { div { color: red; } @media screen and (min-width: 900px) { div { color: red; } } div { color: red; } }', 'ABC'), '@supports (display: flex) { { div.⭐️ABC { color: red; } @media screen and (min-width: 900px) { div.⭐️ABC { color: red; } } div.⭐️ABC { color: red; } }'); +scopedStyles('@font-face', () => { + equal( + scopeStylesheet( + '@font-face { font-family: "Open Sans"; src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"), url("/fonts/OpenSans-Regular-webfont.woff") format("woff"); }', + '_' + ), + '@font-face { font-family: "Open Sans"; src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"), url("/fonts/OpenSans-Regular-webfont.woff") format("woff"); }' + ); +}); +scopedStyles('@media', () => { + equal( + scopeStylesheet('@media screen and (min-width: 900px) { div {} }', '_'), + '@media screen and (min-width: 900px) { div.⭐️_ {} }' + ); +}); - equal(scopeStylesheet('@supports not (not (transform-origin: 2px)) { div { color: red; } }', 'ABC'), '@supports not (not (transform-origin: 2px)) { div.⭐️ABC { color: red; } }'); - equal(scopeStylesheet('@supports (display: grid) and (not (display: inline-grid)) { div { color: red; } }', 'ABC'), '@supports (display: grid) and (not (display: inline-grid)) { div.⭐️ABC { color: red; } }'); +scopedStyles('@supports', () => { + equal( + scopeStylesheet( + '@supports (display: flex) { @media screen and (min-width: 900px) { div {} } }', + '_' + ), + '@supports (display: flex) { @media screen and (min-width: 900px) { div.⭐️_ {} } }' + ); +}); - equal(scopeStylesheet('@supports ((perspective: 10px) or (-moz-perspective: 10px) or (-webkit-perspective: 10px) or (-ms-perspective: 10px) or (-o-perspective: 10px)) { div { color: red; } }', 'ABC'), '@supports ((perspective: 10px) or (-moz-perspective: 10px) or (-webkit-perspective: 10px) or (-ms-perspective: 10px) or (-o-perspective: 10px)) { div.⭐️ABC { color: red; } }'); +scopedStyles('@supports nested', () => { + equal(scopeStylesheet('@media screen(a:1){}div{}', '_'), '@media screen(a:1){}div.⭐️_{}'); + + equal( + scopeStylesheet('@supports(d:b){div{}@media screen(a:1){div{}}div{}}', '_'), + '@supports(d:b){div.⭐️_{}@media screen(a:1){div.⭐️_{}}div.⭐️_{}}' + ); + + equal( + scopeStylesheet('@supports not (not (transform-origin: 2px)) { div {} }', '_'), + '@supports not (not (transform-origin: 2px)) { div.⭐️_ {} }' + ); + equal( + scopeStylesheet('@supports (display: grid) and (not (display: inline-grid)) { div {} }', '_'), + '@supports (display: grid) and (not (display: inline-grid)) { div.⭐️_ {} }' + ); + + equal( + scopeStylesheet( + '@supports ((perspective: 10px) or (-moz-perspective: 10px) or (-webkit-perspective: 10px) or (-ms-perspective: 10px) or (-o-perspective: 10px)) { div {} }', + '_' + ), + '@supports ((perspective: 10px) or (-moz-perspective: 10px) or (-webkit-perspective: 10px) or (-ms-perspective: 10px) or (-o-perspective: 10px)) { div.⭐️_ {} }' + ); }); scopedStyles('comments', () => { - equal(scopeStylesheet('div { color: red; } /* comment */', 'ABC'), 'div.⭐️ABC { color: red; } /* comment */'); - equal(scopeStylesheet('div { /* color: red; */ }', 'ABC'), 'div.⭐️ABC { /* color: red; */ }'); - equal(scopeStylesheet('div /* comment */ { color: red; }', 'ABC'), 'div.⭐️ABC /* comment */ { color: red; }'); - equal(scopeStylesheet('div/* comment */ { color: red; }', 'ABC'), 'div.⭐️ABC/* comment */ { color: red; }'); - equal(scopeStylesheet('/* comment */div { color: red; }', 'ABC'), '/* comment */div.⭐️ABC { color: red; }'); - equal(scopeStylesheet('div /* comment */ > span { color: red; }', 'ABC'), 'div.⭐️ABC /* comment */ > span.⭐️ABC { color: red; }'); + equal(scopeStylesheet('div {} /* comment */', '_'), 'div.⭐️_ {} /* comment */'); + equal(scopeStylesheet('div { /**/ }', '_'), 'div.⭐️_ { /**/ }'); + equal(scopeStylesheet('div /* comment */ {}', '_'), 'div.⭐️_ /* comment */ {}'); + equal(scopeStylesheet('div/* comment */ {}', '_'), 'div.⭐️_/* comment */ {}'); + equal(scopeStylesheet('/* comment */div {}', '_'), '/* comment */div.⭐️_ {}'); + equal( + scopeStylesheet('div /* comment */ > span {}', '_'), + 'div.⭐️_ /* comment */ > span.⭐️_ {}' + ); }); -scopedStyles('global function', () => { - equal(scopeStylesheet(':global(*) { color: red; }', 'ABC'), '* { color: red; }'); - - equal(scopeStylesheet(':global(div) { color: red; }', 'ABC'), 'div { color: red; }'); - equal(scopeStylesheet(':global(div), p { color: red; }', 'ABC'), 'div, p.⭐️ABC { color: red; }'); - - equal(scopeStylesheet('div :global(p) { color: red; }', 'ABC'), 'div.⭐️ABC p { color: red; }'); - equal(scopeStylesheet(':global(div) > p { color: red; }', 'ABC'), 'div > p.⭐️ABC { color: red; }'); - - equal(scopeStylesheet(':global(.red) { color: red; }', 'ABC'), '.red { color: red; }'); - equal(scopeStylesheet(':global(div).red { color: red; }', 'ABC'), 'div.red { color: red; }'); - equal(scopeStylesheet(':global(div.red) { color: red; }', 'ABC'), 'div.red { color: red; }'); - - - equal(scopeStylesheet(':global(#red) { color: red; }', 'ABC'), '#red { color: red; }'); +scopedStyles('global selector', () => { + equal(scopeStylesheet(':global(*) {}', '_'), '* {}'); +}); +scopedStyles('global selector with attribute', () => { + equal(scopeStylesheet(':global([t="("]) {}', '_'), '[t="("] {}'); - equal(scopeStylesheet(':global(div) { }', 'ABC'), 'div { }'); - equal(scopeStylesheet(':global(div) {}', 'ABC'), 'div {}'); - equal(scopeStylesheet(':global(div){color:red;}', 'ABC'), 'div{color:red;}'); + equal(scopeStylesheet(':global(div) {}', '_'), 'div {}'); + equal(scopeStylesheet(':global(div), p {}', '_'), 'div, p.⭐️_ {}'); + equal(scopeStylesheet('div :global(p) {}', '_'), 'div.⭐️_ p {}'); + equal(scopeStylesheet(':global(div) > p {}', '_'), 'div > p.⭐️_ {}'); - equal(scopeStylesheet(':global(*[target]) { color: red; }', 'ABC'), '*[target] { color: red; }'); - equal(scopeStylesheet(':global(*[target]) span { color: red; }', 'ABC'), '*[target] span.⭐️ABC { color: red; }') - equal(scopeStylesheet('*[target] :global(span) { color: red; }', 'ABC'), '*[target].⭐️ABC span { color: red; }'); + equal(scopeStylesheet(':global(.red) {}', '_'), '.red {}'); + equal(scopeStylesheet(':global(div).red {}', '_'), 'div.red.⭐️_ {}'); + equal(scopeStylesheet(':global(div.red) {}', '_'), 'div.red {}'); + equal(scopeStylesheet(':global(#red) {}', '_'), '#red {}'); - equal(scopeStylesheet(':global(a):link { color: red; }', 'ABC'), 'a:link { color: red; }'); - equal(scopeStylesheet(':global(a:link) { color: red; }', 'ABC'), 'a:link { color: red; }'); - equal(scopeStylesheet(':global(p:lang(en)) { color: red; }', 'ABC'), 'p:lang(en) { color: red; }'); - equal(scopeStylesheet(':global(p:nth-child(2)) { color: red; }', 'ABC'), 'p:nth-child(2) { color: red; }'); - equal(scopeStylesheet(':global(:root) { color: red; }', 'ABC'), ':root { color: red; }'); - equal(scopeStylesheet(':global(p:not(.blue)) { color: red; }', 'ABC'), 'p:not(.blue) { color: red; }'); - equal(scopeStylesheet('p:not(:global(.blue)) { color: red; }', 'ABC'), 'p:not(.blue).⭐️ABC { color: red; }'); + equal(scopeStylesheet(':global(div) { }', '_'), 'div { }'); + equal(scopeStylesheet(':global(div) {}', '_'), 'div {}'); + equal(scopeStylesheet(':global(div){color:red;}', '_'), 'div{color:red;}'); - equal(scopeStylesheet(':global(p:nth-child(3n+1):hover) { color: red; }', 'ABC'), 'p:nth-child(3n+1):hover { color: red; }'); - equal(scopeStylesheet(':global(p:nth-child(3n+1) div) { color: red; }', 'ABC'), 'p:nth-child(3n+1) div { color: red; }'); + equal(scopeStylesheet(':global(*[target]) {}', '_'), '*[target] {}'); + equal(scopeStylesheet(':global(*[target]) span {}', '_'), '*[target] span.⭐️_ {}'); + equal(scopeStylesheet('*[target] :global(span) {}', '_'), '[target].⭐️_ span {}'); + equal(scopeStylesheet(':global(a):link {}', '_'), 'a:link.⭐️_ {}'); + equal(scopeStylesheet(':global(a:link) {}', '_'), 'a:link {}'); + equal(scopeStylesheet(':global(p:lang(en)) {}', '_'), 'p:lang(en) {}'); + equal(scopeStylesheet(':global(p:nth-child(2)) {}', '_'), 'p:nth-child(2) {}'); + equal(scopeStylesheet(':global(:root) {}', '_'), ':root {}'); + equal(scopeStylesheet(':global(p:not(.blue)) {}', '_'), 'p:not(.blue) {}'); +}); - equal(scopeStylesheet(':global(::selection) { color: red; }', 'ABC'), '::selection { color: red; }'); +scopedStyles('nested global inside not', () => { + equal(scopeStylesheet('p:not(:global(.red)){}', '_'), 'p:not(.red).⭐️_{}'); - equal(scopeStylesheet(':global(a)::before { color: red; }', 'ABC'), 'a::before { color: red; }'); - equal(scopeStylesheet(':global(a::after) { color: red; }', 'ABC'), 'a::after { color: red; }'); + equal(scopeStylesheet(':global(p:nth-child(3n+1):hover) {}', '_'), 'p:nth-child(3n+1):hover {}'); + equal(scopeStylesheet(':global(p:nth-child(3n+1) div) {}', '_'), 'p:nth-child(3n+1) div {}'); - equal(scopeStylesheet(':global(a).red::before { color: red; }', 'ABC'), 'a.red::before { color: red; }'); - equal(scopeStylesheet(':global(a.red) span::before { color: red; }', 'ABC'), 'a.red span.⭐️ABC::before { color: red; }'); + equal(scopeStylesheet(':global(::selection) {}', '_'), '::selection {}'); +}); +scopedStyles('global with pseudo element', () => { + equal(scopeStylesheet(':global(a::after){}', '_'), 'a::after{}'); + // equal(scopeStylesheet(':global(a)::before{}', '_'), 'a::before{}'); + equal(scopeStylesheet(':global(a).red::before {}', '_'), 'a.red.⭐️_::before {}'); + equal(scopeStylesheet(':global(a.red) span::before {}', '_'), 'a.red span.⭐️_::before {}'); +}); - equal(scopeStylesheet('@keyframes :global(slidein) { from { transform: translateX(0%); } to { transform: translateX(100%); } }', 'ABC'), '@keyframes slidein { from { transform: translateX(0%); } to { transform: translateX(100%); } }'); - equal(scopeStylesheet('@media screen and (min-width: 900px) { :global(div) { color: red; } }', 'ABC'), '@media screen and (min-width: 900px) { div { color: red; } }'); +scopedStyles('global with pseudo element', () => { + equal( + scopeStylesheet( + '@keyframes :global(slidein) { from { transform: translateX(0%); } to { transform: translateX(100%); } }', + '_' + ), + '@keyframes slidein { from { transform: translateX(0%); } to { transform: translateX(100%); } }' + ); + equal( + scopeStylesheet('@media screen and (min-width: 900px) { :global(div) {} }', '_'), + '@media screen and (min-width: 900px) { div {} }' + ); }); scopedStyles.run(); diff --git a/packages/qwik/src/core/util/markers.js b/packages/qwik/src/core/util/markers.js deleted file mode 100644 index c6d3f4a457a..00000000000 --- a/packages/qwik/src/core/util/markers.js +++ /dev/null @@ -1,43 +0,0 @@ -"use strict"; -exports.__esModule = true; -exports.ELEMENT_ID_Q_PROPS_PREFIX = exports.ELEMENT_ID_PREFIX = exports.ELEMENT_ID_SELECTOR = exports.ELEMENT_ID = exports.QSlotInertName = exports.RenderEvent = exports.QContainerSelector = exports.QContainerAttr = exports.QCtxAttr = exports.QScopedStyle = exports.QStyle = exports.QSlotName = exports.QSlotRef = exports.QSlot = exports.EventAny = exports.EventPrefix = exports.ComponentStylesPrefixContent = exports.ComponentStylesPrefixHost = exports.OnRenderProp = void 0; -/** - * State factory of the component. - */ -exports.OnRenderProp = 'q:renderFn'; -/** - * Component style host prefix - */ -exports.ComponentStylesPrefixHost = '💎'; -/** - * Component style content prefix - */ -exports.ComponentStylesPrefixContent = '⭐️'; -/** - * Prefix used to identify on listeners. - */ -exports.EventPrefix = 'on:'; -/** - * Attribute used to mark that an event listener is attached. - */ -exports.EventAny = 'on:.'; -/** - * `` - */ -exports.QSlot = 'q:slot'; -exports.QSlotRef = 'q:sref'; -exports.QSlotName = 'q:sname'; -exports.QStyle = 'q:style'; -exports.QScopedStyle = 'q:sstyle'; -exports.QCtxAttr = 'q:ctx'; -exports.QContainerAttr = 'q:container'; -exports.QContainerSelector = '[q\\:container]'; -exports.RenderEvent = 'qRender'; -/** - * `` - */ -exports.QSlotInertName = '\u0000'; -exports.ELEMENT_ID = 'q:id'; -exports.ELEMENT_ID_SELECTOR = '[q\\:id]'; -exports.ELEMENT_ID_PREFIX = '#'; -exports.ELEMENT_ID_Q_PROPS_PREFIX = '&';