Skip to content

Commit 7cfece2

Browse files
crisbetommalerba
authored andcommitted
build: replace gulp-autoprefixer (#3563)
* build: replace gulp-autoprefixer * Removes `gulp-autoprefixer` in order to avoid issues with Google's internal build system. * Switches to adding vendor prefixes via SCSS mixins. * Adds a custom Stylelint plugin that utilizies Autoprefixer to find unprefixed properties, values, @rules etc. Fixes #3536. * fix: missing import * fix: address feedback
1 parent 8f76f96 commit 7cfece2

File tree

14 files changed

+201
-31
lines changed

14 files changed

+201
-31
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@types/node": "^7.0.5",
5252
"@types/run-sequence": "^0.0.28",
5353
"@types/rx": "2.5.33",
54+
"autoprefixer": "^6.7.6",
5455
"axe-core": "^2.1.7",
5556
"axe-webdriverjs": "^0.5.0",
5657
"conventional-changelog": "^1.1.0",
@@ -62,7 +63,6 @@
6263
"glob": "^7.1.1",
6364
"google-cloud": "^0.48.0",
6465
"gulp": "^3.9.1",
65-
"gulp-autoprefixer": "^3.1.1",
6666
"gulp-better-rollup": "^1.0.2",
6767
"gulp-clean": "^0.3.2",
6868
"gulp-clean-css": "^3.0.3",

src/demo-app/ripple/ripple-demo.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
text-align: center;
1515
transition: all 200ms linear;
1616
width: 200px;
17-
user-select: none;
1817

1918
&.demo-ripple-disabled {
2019
color: rgba(32, 32, 32, 0.4);
@@ -32,4 +31,4 @@
3231
.demo-ripple-checkbox-option {
3332
margin: 10px 0;
3433
}
35-
}
34+
}

src/lib/button-toggle/button-toggle.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@import '../core/a11y/a11y';
22
@import '../core/style/elevation';
3+
@import '../core/style/vendor-prefixes';
34
@import '../core/style/layout-common';
45

56
$mat-button-toggle-padding: 0 16px !default;
@@ -44,11 +45,11 @@ $mat-button-toggle-border-radius: 2px !default;
4445
}
4546

4647
.mat-button-toggle-label-content {
48+
@include user-select(none);
4749
display: inline-block;
4850
line-height: $mat-button-toggle-line-height;
4951
padding: $mat-button-toggle-padding;
5052
cursor: pointer;
51-
user-select: none;
5253
}
5354

5455
.mat-button-toggle-label-content > * {

src/lib/core/option/_option.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import '../style/menu-common';
2+
@import '../style/vendor-prefixes';
23
@import '../a11y/a11y';
34

45
/**
@@ -13,8 +14,8 @@
1314
outline: none;
1415

1516
&[aria-disabled='true'] {
17+
@include user-select(none);
1618
cursor: default;
17-
user-select: none;
1819
}
1920
}
2021

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
@import './vendor-prefixes';
2+
13
// Mixin overriding default button styles like the gray background, the border, and the outline.
24
@mixin mat-button-reset {
5+
@include user-select(none);
36
cursor: pointer;
4-
user-select: none;
57
outline: none;
68
border: none;
79
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* stylelint-disable material/no-prefixes */
2+
@mixin user-select($value) {
3+
-webkit-user-select: none;
4+
-moz-user-select: none;
5+
-ms-user-select: none;
6+
user-select: none;
7+
}
8+
9+
@mixin input-placeholder {
10+
&::placeholder {
11+
@content;
12+
}
13+
14+
&::-moz-placeholder {
15+
@content;
16+
}
17+
18+
&::-webkit-input-placeholder {
19+
@content;
20+
}
21+
22+
&:-ms-input-placeholder {
23+
@content;
24+
}
25+
}
26+
27+
@mixin cursor-grab {
28+
cursor: -webkit-grab;
29+
cursor: grab;
30+
}
31+
/* stylelint-enable */

src/lib/input/input-container.scss

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import '../core/style/variables';
2+
@import '../core/style/vendor-prefixes';
23
@import '../core/style/form-common';
34

45

@@ -109,16 +110,7 @@ $mat-input-underline-disabled-background-image:
109110
// Note that we can't use something like visibility: hidden or
110111
// display: none, because IE ends up preventing the user from
111112
// focusing the input altogether.
112-
&::placeholder {
113-
color: transparent;
114-
}
115-
&::-moz-placeholder {
116-
color: transparent;
117-
}
118-
&::-webkit-input-placeholder {
119-
color: transparent;
120-
}
121-
&:-ms-input-placeholder {
113+
@include input-placeholder {
122114
color: transparent;
123115
}
124116
}

src/lib/select/select.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@import '../core/style/list-common';
33
@import '../core/style/form-common';
44
@import '../core/style/variables';
5+
@import '../core/style/vendor-prefixes';
56
@import '../core/a11y/a11y';
67

78
$mat-select-trigger-height: 30px !default;
@@ -28,8 +29,8 @@ $mat-select-trigger-font-size: 16px !default;
2829
font-size: $mat-select-trigger-font-size;
2930

3031
[aria-disabled='true'] & {
32+
@include user-select(none);
3133
cursor: default;
32-
user-select: none;
3334
}
3435
}
3536

src/lib/slide-toggle/slide-toggle.scss

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@import '../core/style/variables';
22
@import '../core/ripple/ripple';
33
@import '../core/style/elevation';
4+
@import '../core/style/vendor-prefixes';
45
@import '../core/a11y/a11y';
56

67
$mat-slide-toggle-thumb-size: 20px !default;
@@ -25,7 +26,7 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg
2526

2627
// Disable user selection to ensure that dragging is smooth without grabbing
2728
// some elements accidentally.
28-
user-select: none;
29+
@include user-select(none);
2930

3031
outline: none;
3132

@@ -100,7 +101,7 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg
100101
transition: $swift-linear;
101102
transition-property: transform;
102103

103-
cursor: grab;
104+
@include cursor-grab;
104105

105106
// Once the thumb container is being dragged around, we remove the transition duration to
106107
// make the drag feeling fast and not delayed.

stylelint-config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
2+
"plugins": [
3+
"./tools/stylelint/no-prefixes/no-prefixes.js"
4+
],
25
"rules": {
6+
"material/no-prefixes": [["last 2 versions", "not ie <= 10", "not ie_mob <= 10"]],
37
"color-hex-case": "lower",
48
"color-no-invalid-hex": true,
59

tools/gulp/constants.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@ export const DIST_COMPONENTS_ROOT = join(DIST_ROOT, '@angular/material');
1010

1111
export const COVERAGE_RESULT_FILE = join(DIST_ROOT, 'coverage', 'coverage-summary.json');
1212

13-
export const SASS_AUTOPREFIXER_OPTIONS = {
14-
browsers: [
15-
'last 2 versions',
16-
'not ie <= 10',
17-
'not ie_mob <= 10',
18-
],
19-
cascade: false,
20-
};
21-
2213
export const HTML_MINIFIER_OPTIONS = {
2314
collapseWhitespace: true,
2415
removeComments: true,

tools/gulp/util/task_helpers.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as child_process from 'child_process';
22
import * as fs from 'fs';
33
import * as gulp from 'gulp';
44
import * as path from 'path';
5-
import {NPM_VENDOR_FILES, PROJECT_ROOT, DIST_ROOT, SASS_AUTOPREFIXER_OPTIONS} from '../constants';
5+
import {NPM_VENDOR_FILES, PROJECT_ROOT, DIST_ROOT} from '../constants';
66

77

88
/** Those imports lack typings. */
@@ -11,7 +11,6 @@ const gulpMerge = require('merge2');
1111
const gulpRunSequence = require('run-sequence');
1212
const gulpSass = require('gulp-sass');
1313
const gulpSourcemaps = require('gulp-sourcemaps');
14-
const gulpAutoprefixer = require('gulp-autoprefixer');
1514
const gulpConnect = require('gulp-connect');
1615
const resolveBin = require('resolve-bin');
1716

@@ -44,7 +43,6 @@ export function sassBuildTask(dest: string, root: string) {
4443
return gulp.src(_globify(root, '**/*.scss'))
4544
.pipe(gulpSourcemaps.init())
4645
.pipe(gulpSass().on('error', gulpSass.logError))
47-
.pipe(gulpAutoprefixer(SASS_AUTOPREFIXER_OPTIONS))
4846
.pipe(gulpSourcemaps.write('.'))
4947
.pipe(gulp.dest(dest));
5048
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const autoprefixer = require('autoprefixer');
2+
const Browsers = require('autoprefixer/lib/browsers');
3+
const Prefixes = require('autoprefixer/lib/prefixes');
4+
5+
/**
6+
* Utility to be used when checking whether a CSS declaration needs to be prefixed. Based on
7+
* Stylelint's `no-vendor-prefix` rule, but instead of checking whether a rule has a prefix,
8+
* we check whether it needs one.
9+
* Reference https://github.com/stylelint/stylelint/blob/master/lib/utils/isAutoprefixable.js
10+
*/
11+
module.exports = class NeedsPrefix {
12+
constructor(browsers) {
13+
this._prefixes = new Prefixes(
14+
autoprefixer.data.prefixes,
15+
new Browsers(autoprefixer.data.browsers, browsers)
16+
);
17+
}
18+
19+
/** Checks whether an @-rule needs to be prefixed. */
20+
atRule(identifier) {
21+
return this._prefixes.add[`@${identifier.toLowerCase()}`];
22+
}
23+
24+
/** Checks whether a selector needs to be prefixed. */
25+
selector(identifier) {
26+
return this._prefixes.add.selectors.some(selectorObj => {
27+
return identifier.toLowerCase() === selectorObj.name;
28+
});
29+
}
30+
31+
/** Checks whether a media query value needs to be prefixed. */
32+
mediaFeature(identifier) {
33+
return identifier.toLowerCase().indexOf('device-pixel-ratio') > -1;
34+
}
35+
36+
/** Checks whether a property needs to be prefixed. */
37+
property(identifier) {
38+
// `fill` is an edge case since it was part of a proposal that got renamed to `stretch`.
39+
// see: https://www.w3.org/TR/css-sizing-3/#changes
40+
if (!identifier || identifier === 'fill') return false;
41+
42+
const needsPrefix = autoprefixer.data.prefixes[identifier.toLowerCase()];
43+
const browsersThatNeedPrefix = needsPrefix ? needsPrefix.browsers : null;
44+
45+
return !!browsersThatNeedPrefix && !!this._prefixes.browsers.selected.find(browser => {
46+
return browsersThatNeedPrefix.indexOf(browser) > -1;
47+
});
48+
}
49+
50+
/** Checks whether a CSS property value needs to be prefixed. */
51+
value(prop, value) {
52+
if (!prop || !value) return false;
53+
54+
const possiblePrefixableValues = this._prefixes.add[prop.toLowerCase()] &&
55+
this._prefixes.add[prop.toLowerCase()].values;
56+
57+
return !!possiblePrefixableValues && possiblePrefixableValues.some(valueObj => {
58+
return value.toLowerCase() === valueObj.name;
59+
});
60+
}
61+
};
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const stylelint = require('stylelint');
2+
const NeedsPrefix = require('./needs-prefix');
3+
const parseSelector = require('stylelint/lib/utils/parseSelector');
4+
5+
const ruleName = 'material/no-prefixes';
6+
const messages = stylelint.utils.ruleMessages(ruleName, {
7+
property: property => `Unprefixed property "${property}".`,
8+
value: (property, value) => `Unprefixed value in "${property}: ${value}".`,
9+
atRule: name => `Unprefixed @rule "${name}".`,
10+
mediaFeature: value => `Unprefixed media feature "${value}".`,
11+
selector: selector => `Unprefixed selector "${selector}".`
12+
});
13+
14+
/**
15+
* Stylelint plugin that warns for unprefixed CSS.
16+
*/
17+
const plugin = stylelint.createPlugin(ruleName, browsers => {
18+
return (root, result) => {
19+
if (!stylelint.utils.validateOptions(result, ruleName, {})) return;
20+
21+
const needsPrefix = new NeedsPrefix(browsers);
22+
23+
// Check all of the `property: value` pairs.
24+
root.walkDecls(decl => {
25+
if (needsPrefix.property(decl.prop)) {
26+
stylelint.utils.report({
27+
result,
28+
ruleName,
29+
message: messages.property(decl.prop),
30+
node: decl,
31+
index: (decl.raws.before || '').length
32+
});
33+
} else if (needsPrefix.value(decl.prop, decl.value)) {
34+
stylelint.utils.report({
35+
result,
36+
ruleName,
37+
message: messages.value(decl.prop, decl.value),
38+
node: decl,
39+
index: (decl.raws.before || '').length
40+
});
41+
}
42+
});
43+
44+
// Check all of the @-rules and their values.
45+
root.walkAtRules(rule => {
46+
if (needsPrefix.atRule(rule.name)) {
47+
stylelint.utils.report({
48+
result,
49+
ruleName,
50+
message: messages.atRule(rule.name),
51+
node: rule
52+
});
53+
} else if (needsPrefix.mediaFeature(rule.params)) {
54+
stylelint.utils.report({
55+
result,
56+
ruleName,
57+
message: messages.mediaFeature(rule.name),
58+
node: rule
59+
});
60+
}
61+
});
62+
63+
// Walk the rules and check if the selector needs prefixes.
64+
root.walkRules(rule => {
65+
// Silence warnings for SASS selectors. Stylelint does this in their own rules as well:
66+
// https://github.com/stylelint/stylelint/blob/master/lib/utils/isStandardSyntaxSelector.js
67+
parseSelector(rule.selector, { warn: () => {} }, rule, selectorTree => {
68+
selectorTree.walkPseudos(pseudoNode => {
69+
if (needsPrefix.selector(pseudoNode.value)) {
70+
stylelint.utils.report({
71+
result,
72+
ruleName,
73+
message: messages.selector(pseudoNode.value),
74+
node: rule,
75+
index: (rule.raws.before || '').length + pseudoNode.sourceIndex,
76+
});
77+
}
78+
});
79+
});
80+
});
81+
82+
};
83+
});
84+
85+
86+
plugin.ruleName = ruleName;
87+
plugin.messages = messages;
88+
module.exports = plugin;

0 commit comments

Comments
 (0)