Skip to content

Commit 6c0acef

Browse files
committed
Shim CSS Mixins in terms of CSS Custom Properties (#3587)
* check for native css variables * keep custom properties, still unpack mixins * make `updateStyles` (modulo @apply) work with native custom properties. make `getComputedStyleValue` work with native custom properties. * modifications * sigh * fix tests in firefox safari and chrome 49 * add back missing styleutil code in x-styling * support all the fallback cases with nested parens * fix rules with property and mixin definitions for native * First draft of @apply shim ```css --foo: { color: red; border: 2px solid black; } @apply --foo; ``` becomes ```css --foo-color: red; --foo-border: 2px solid black; color: var(--foo-color); border: var(--foo-border); ``` * step 3 * Process stylesheets in custom-styles move apply-shim to lib Fix some tests * always add semicolon in flattenMixin * reset order back to master * step 4 * Temporary ordering fixup for native @apply shim. * better step 5 * make custom-style support @apply shim. * * make @apply regex match globally * fix custom-style test's late registrations. * Collect default property values between consumption and application No more global for defaults No need to process keyframe rules * stupid stateful regexes * Handle realiasing mixins (step 6) Fix up shim var syntax `var(--a, --b)` to `var(--a, var(--b))` Disable native variables in Safari 9.1 until https://bugs.webkit.org/show_bug.cgi?id=155782 is fixed * Add more tests for apply shim Test new `@apply --foo` syntax Test aliasing mixins with var Test fixing bad var syntax `var(--a, --b)` -> `var(--a, var(--b))` * fix :root for apply shim * add StyleTransformer smoke test. * Move regexes from style-properties to style-util for easier sharing Clean up dependencies in apply-shim Remove lazyRegister hack * Move check for CSS Custom Properties to settings lib Enabled only if support is detected and `lazyRegister` setting is used. Override with `Polymer = {useNativeCSSProperties: true}` before polymer is loaded. * add apply shim to smoke test * actually let uses set useNativeCSSProperties false * SCOPE_SELECTORS needs to work with built selectors for custom-style and elements * No need to decorate styles for apply shim * apply mixins and gather defaults incrementally * Fix @apply consumption to incrementally process rule text When >1 mixin is consumed, correctly use properties produced by last mixin as fallbacks for current mixin LOTS more comments, more descriptive comment names Micro-opts * MORE APPLY SHIM TESTING Actually test apply shim (needs lazyRegister), and test with shadow dom * Make sure @apply without parens works in property shim as expected * Simpler tracking of mixin properties Address naming feedback Use property map in apply map to keep better track of properties to set to 'initial' when mixins are redefined * Support builds of CSS PolymerBuild supports one property, css, which can be 'shady' or 'shadow'. 'shadow' is a "universal" build, which will work in shadydom or shadowdom. 'shady' will only work in shadydom. * patch a few spots that custom property shim needs to know about the build * still need to transform the selectors if a shadow build was the source * support tests with build to choose the right property * make `importHref` avoid re-importing already loaded resources. * One more spot a shady build will break custom property shim :root will be calculated to `hostname > *.hostname` by the build * A few more allowances for the builds * mark elements and custom-styles as built, no globals * use propertyDataFromStyles for :host and :root Fixes #3610 `:host(tag-name)` * make tests pass by hacking `propertyDataFromStyles`, needs refactoring. * Search for properties in :host and :root rules at the same time - Remove caching, does not apply with @media may invalidate - custom-style will forward css-build state to the rules for property lookup * Don't expect shady built styles to be in head Can't be sharded like that :( Fix polyfill shadowdom and style-defaults with matchesSelector on <html> element Fix url test with getPropertyValue when quotes are not present (the string is escaped with '/') * Fixes #3637. Normalizes attached timing between Shady and Shadow DOM under native Custom Elements. * Fixes #3638. Avoid spamming document.head with already loaded link elements when importHref is called repeatedly with the same url. * actually listen for the error event (unclear how to test error without server support) * fix typo * fix lint errors * fix bad merge conflict * Safari 9.1.1 is still busted, drop minor version check for AppleWebKit * only apply statically shimmed styles if the element has cssText (this optimization can be made because elements always have a placeholder comment node to indicate style position). * * slight optimization: cache cssBuild info on element. * avoid decorating elements whenever there is a css build (of any type) * revert dom scoping change and add clarifying comment about why this is needed when there is a non-shady build. * formatting * custom-style: avoid applying shimmed custom properties when native custom properties are in use. * avoid shimming styles under shady dom when there is a shady css build. * custom-style: when native custom properties are in use and no build is available, avoid applying shimmed properties and only update the style to reflect apply shim changes. * avoid work in the presence of a css build. * add test for :host(element-name) * correct custom-style under shadow build when using shady and custom properties. * fix tests to not rely on order in className * Support custom-style with css-build status in HTMLImports polyfill - Copy css-build status to cloned style in main document - Move css-build status out of ast - Instead forward a reference to the style in the ast walker callback * Clean up logic in custom-style _apply More easy to see that no work happens if using a targeted css build with native custom properties * Loop over all property names ever used for a mixin Add test to show that redefining a mixin works as expected * Bail early if rule does not have properties checking for native css custom properties is not needed in the property shim anymore * Comments. * test that invalid @media rules do *not* apply via the custom properties shim. * Use `_-_` as seperator for apply-shim created variables Differentiates apply-shim variables more obviously from user variables #3587 (comment) * Fix custom-style test with new separator * fix a few more tests for built styles * Revert "Fixes #3637. Normalizes attached timing between Shady and Shadow DOM under native Custom Elements." This reverts commit 9d272e0. * [ci skip] PolymerBuild global has been removed
1 parent adef722 commit 6c0acef

24 files changed

Lines changed: 1254 additions & 221 deletions

‎src/lib/apply-shim.html‎

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
<!--
2+
@license
3+
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4+
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5+
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6+
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7+
Code distributed by Google as part of the polymer project is also
8+
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9+
-->
10+
<link rel="import" href="style-util.html">
11+
<script>
12+
/**
13+
* The apply shim simulates the behavior of `@apply` proposed at
14+
* https://tabatkins.github.io/specs/css-apply-rule/.
15+
* The approach is to convert a property like this:
16+
*
17+
* --foo: {color: red; background: blue;}
18+
*
19+
* to this:
20+
*
21+
* --foo_-_color: red;
22+
* --foo_-_background: blue;
23+
*
24+
* Then where `@apply --foo` is used, that is converted to:
25+
*
26+
* color: var(--foo_-_color);
27+
* background: var(--foo_-_background);
28+
*
29+
* This approach generally works but there are some issues and limitations.
30+
* Consider, for example, that somewhere *between* where `--foo` is set and used,
31+
* another element sets it to:
32+
*
33+
* --foo: { border: 2px solid red; }
34+
*
35+
* We must now ensure that the color and background from the previous setting
36+
* do not apply. This is accomplished by changing the property set to this:
37+
*
38+
* --foo_-_border: 2px solid red;
39+
* --foo_-_color: initial;
40+
* --foo_-_background: initial;
41+
*
42+
* This works but introduces one new issue.
43+
* Consider this setup at the point where the `@apply` is used:
44+
*
45+
* background: orange;
46+
* @apply --foo;
47+
*
48+
* In this case the background will be unset (initial) rather than the desired
49+
* `orange`. We address this by altering the property set to use a fallback
50+
* value like this:
51+
*
52+
* color: var(--foo_-_color);
53+
* background: var(--foo_-_background, orange);
54+
* border: var(--foo_-_border);
55+
*
56+
* Note that the default is retained in the property set and the `background` is
57+
* the desired `orange`. This leads us to a limitation.
58+
*
59+
* Limitation 1:
60+
61+
* Only properties in the rule where the `@apply`
62+
* is used are considered as default values.
63+
* If another rule matches the element and sets `background` with
64+
* less specificity than the rule in which `@apply` appears,
65+
* the `background` will not be set.
66+
*
67+
* Limitation 2:
68+
*
69+
* When using Polymer's `updateStyles` api, new properties may not be set for
70+
* `@apply` properties.
71+
72+
*/
73+
Polymer.ApplyShim = (function(){
74+
'use strict';
75+
76+
var styleUtil = Polymer.StyleUtil;
77+
78+
var MIXIN_MATCH = styleUtil.rx.MIXIN_MATCH;
79+
var VAR_ASSIGN = styleUtil.rx.VAR_ASSIGN;
80+
var VAR_MATCH = styleUtil.rx.VAR_MATCH;
81+
var APPLY_NAME_CLEAN = /;\s*/m;
82+
83+
// separator used between mixin-name and mixin-property-name when producing properties
84+
// NOTE: plain '-' may cause collisions in user styles
85+
var MIXIN_VAR_SEP = '_-_';
86+
87+
// map of mixin to property names
88+
// --foo: {border: 2px} -> (--foo, ['border'])
89+
var mixinMap = {};
90+
91+
function mapSet(name, prop) {
92+
name = name.trim();
93+
mixinMap[name] = prop;
94+
}
95+
96+
function mapGet(name) {
97+
name = name.trim();
98+
return mixinMap[name];
99+
}
100+
101+
// "parse" a mixin definition into a map of properties and values
102+
// cssTextToMap('border: 2px solid black') -> ('border', '2px solid black')
103+
function cssTextToMap(text) {
104+
var props = text.split(';');
105+
var out = {};
106+
for (var i = 0, p, sp; i < props.length; i++) {
107+
p = props[i];
108+
if (p) {
109+
sp = p.split(':');
110+
// ignore lines that aren't definitions like @media
111+
if (sp.length > 1) {
112+
// some properties may have ':' in the value, like data urls
113+
out[sp[0].trim()] = sp.slice(1).join(':');
114+
}
115+
}
116+
}
117+
return out;
118+
}
119+
120+
function produceCssProperties(matchText, propertyName, valueProperty, valueMixin) {
121+
// handle case where property value is a mixin
122+
if (valueProperty) {
123+
VAR_MATCH.lastIndex = 0;
124+
var m = VAR_MATCH.exec(valueProperty);
125+
if (m) {
126+
var value = m[2];
127+
if (mapGet(value)){
128+
valueMixin = '@apply ' + value + ';';
129+
}
130+
}
131+
}
132+
if (!valueMixin) {
133+
return matchText;
134+
}
135+
var mixinAsProperties = consumeCssProperties(valueMixin);
136+
var prefix = matchText.slice(0, matchText.indexOf('--'));
137+
var mixinValues = cssTextToMap(mixinAsProperties);
138+
var oldProperties = mapGet(propertyName);
139+
var combinedProps = mixinValues;
140+
if (oldProperties) {
141+
// NOTE: since we use mixin, the map of properties is updated here
142+
// and this is what we want.
143+
combinedProps = Polymer.Base.mixin(oldProperties, mixinValues);
144+
} else {
145+
mapSet(propertyName, combinedProps);
146+
}
147+
var out = [];
148+
var p, v;
149+
// set variables defined by current mixin
150+
for (p in combinedProps) {
151+
v = mixinValues[p];
152+
// if property not defined by current mixin, set initial
153+
if (v === undefined) {
154+
v = 'initial';
155+
}
156+
out.push(propertyName + MIXIN_VAR_SEP + p + ': ' + v);
157+
}
158+
return prefix + out.join('; ') + ';';
159+
}
160+
161+
// fix shim'd var syntax
162+
// var(--a, --b) -> var(--a, var(--b));
163+
function fixVars(matchText, prefix, value, fallback) {
164+
// if fallback doesn't exist, or isn't a broken variable, abort
165+
if (!fallback || fallback.indexOf('--') !== 0) {
166+
return matchText;
167+
}
168+
return [prefix, 'var(', value, ', var(', fallback, '));'].join('');
169+
}
170+
171+
// produce variable consumption at the site of mixin consumption
172+
// @apply --foo; -> for all props (${propname}: var(--foo_-_${propname}, ${fallback[propname]}}))
173+
// Example:
174+
// border: var(--foo_-_border); padding: var(--foo_-_padding, 2px)
175+
function atApplyToCssProperties(mixinName, fallbacks) {
176+
mixinName = mixinName.replace(APPLY_NAME_CLEAN, '');
177+
var vars = [];
178+
var mixinProperties = mapGet(mixinName);
179+
if (mixinProperties) {
180+
var p, parts, f;
181+
for (p in mixinProperties) {
182+
f = fallbacks && fallbacks[p];
183+
parts = [p, ': var(', mixinName, MIXIN_VAR_SEP, p];
184+
if (f) {
185+
parts.push(',', f);
186+
}
187+
parts.push(')');
188+
vars.push(parts.join(''));
189+
}
190+
}
191+
return vars.join('; ');
192+
}
193+
194+
// replace mixin consumption with variable consumption
195+
function consumeCssProperties(text) {
196+
var m;
197+
// loop over text until all mixins with defintions have been applied
198+
while((m = MIXIN_MATCH.exec(text))) {
199+
var matchText = m[0];
200+
var mixinName = m[1];
201+
var idx = m.index;
202+
// collect properties before apply to be "defaults" if mixin might override them
203+
// match includes a "prefix", so find the start and end positions of @apply
204+
var applyPos = idx + matchText.indexOf('@apply');
205+
var afterApplyPos = idx + matchText.length;
206+
// find props defined before this @apply
207+
var textBeforeApply = text.slice(0, applyPos);
208+
var textAfterApply = text.slice(afterApplyPos);
209+
var defaults = cssTextToMap(textBeforeApply);
210+
var replacement = atApplyToCssProperties(mixinName, defaults);
211+
// use regex match position to replace mixin, keep linear processing time
212+
text = [textBeforeApply, replacement, textAfterApply].join('');
213+
// move regex search to _after_ replacement
214+
MIXIN_MATCH.lastIndex = idx + replacement.length;
215+
}
216+
return text;
217+
}
218+
219+
var ApplyShim = {
220+
_map: mixinMap,
221+
_separator: MIXIN_VAR_SEP,
222+
transform: function(styles) {
223+
styleUtil.forRulesInStyles(styles, this._boundTransformRule);
224+
},
225+
transformRule: function(rule) {
226+
rule.cssText = this.transformCssText(rule.parsedCssText);
227+
// :root was only used for variable assignment in property shim,
228+
// but generates invalid selectors with real properties.
229+
// replace with `:host > *`, which serves the same effect
230+
if (rule.selector === ':root') {
231+
rule.selector = ':host > *';
232+
}
233+
},
234+
transformCssText: function(cssText) {
235+
// fix shim variables
236+
cssText = cssText.replace(VAR_MATCH, fixVars);
237+
// produce variables
238+
cssText = cssText.replace(VAR_ASSIGN, produceCssProperties);
239+
// consume mixins
240+
return consumeCssProperties(cssText);
241+
}
242+
};
243+
244+
ApplyShim._boundTransformRule = ApplyShim.transformRule.bind(ApplyShim);
245+
return ApplyShim;
246+
})();
247+
</script>

‎src/lib/css-parse.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
var cssText = '';
113113
if (node.cssText || node.rules) {
114114
var r$ = node.rules;
115-
if (r$ && (preserveProperties || !this._hasMixinRules(r$))) {
115+
if (r$ && !this._hasMixinRules(r$)) {
116116
for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) {
117117
cssText = this.stringify(r, preserveProperties, cssText);
118118
}
@@ -175,7 +175,7 @@
175175
port: /@import[^;]*;/gim,
176176
customProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,
177177
mixinProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,
178-
mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim,
178+
mixinApply: /@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,
179179
varApply: /[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,
180180
keyframesRule: /^@[^\s]*keyframes/,
181181
multipleSpaces: /\s+/g

0 commit comments

Comments
 (0)