Skip to content

Commit

Permalink
build: replace gulp-autoprefixer
Browse files Browse the repository at this point in the history
* 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 angular#3536.
  • Loading branch information
crisbeto committed Mar 11, 2017
1 parent cdb3763 commit 18159c5
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 31 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@types/node": "^7.0.5",
"@types/run-sequence": "^0.0.28",
"@types/rx": "2.5.33",
"autoprefixer": "^6.7.6",
"axe-core": "^2.1.7",
"axe-webdriverjs": "^0.5.0",
"conventional-changelog": "^1.1.0",
Expand All @@ -62,7 +63,6 @@
"glob": "^7.1.1",
"google-cloud": "^0.48.0",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.1",
"gulp-better-rollup": "^1.0.2",
"gulp-clean": "^0.3.2",
"gulp-clean-css": "^3.0.3",
Expand Down
3 changes: 1 addition & 2 deletions src/demo-app/ripple/ripple-demo.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
text-align: center;
transition: all 200ms linear;
width: 200px;
user-select: none;

&.demo-ripple-disabled {
color: rgba(32, 32, 32, 0.4);
Expand All @@ -32,4 +31,4 @@
.demo-ripple-checkbox-option {
margin: 10px 0;
}
}
}
3 changes: 2 additions & 1 deletion src/lib/button-toggle/button-toggle.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import '../core/a11y/a11y';
@import '../core/style/elevation';
@import '../core/style/prefixes';
@import '../core/style/layout-common';

$mat-button-toggle-padding: 0 16px !default;
Expand Down Expand Up @@ -44,11 +45,11 @@ $mat-button-toggle-border-radius: 2px !default;
}

.mat-button-toggle-label-content {
@include user-select(none);
display: inline-block;
line-height: $mat-button-toggle-line-height;
padding: $mat-button-toggle-padding;
cursor: pointer;
user-select: none;
}

.mat-button-toggle-label-content > * {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/core/option/_option.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import '../style/menu-common';
@import '../style/prefixes';
@import '../a11y/a11y';

/**
Expand All @@ -13,8 +14,8 @@
outline: none;

&[aria-disabled='true'] {
@include user-select(none);
cursor: default;
user-select: none;
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/lib/core/style/_button-common.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
@import './prefixes';

// Mixin overriding default button styles like the gray background, the border, and the outline.
@mixin mat-button-reset {
@include user-select(none);
cursor: pointer;
user-select: none;
outline: none;
border: none;
}
31 changes: 31 additions & 0 deletions src/lib/core/style/_prefixes.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* stylelint-disable material/no-prefixes */
@mixin user-select($value) {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

@mixin placeholder {
&::placeholder {
@content;
}

&::-moz-placeholder {
@content;
}

&::-webkit-input-placeholder {
@content;
}

&:-ms-input-placeholder {
@content;
}
}

@mixin cursor-grab {
cursor: -webkit-grab;
cursor: grab;
}
/* stylelint-enable */
11 changes: 1 addition & 10 deletions src/lib/input/input-container.scss
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,7 @@ $mat-input-underline-disabled-background-image:
// Note that we can't use something like visibility: hidden or
// display: none, because IE ends up preventing the user from
// focusing the input altogether.
&::placeholder {
color: transparent;
}
&::-moz-placeholder {
color: transparent;
}
&::-webkit-input-placeholder {
color: transparent;
}
&:-ms-input-placeholder {
@include placeholder {
color: transparent;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@import '../core/style/list-common';
@import '../core/style/form-common';
@import '../core/style/variables';
@import '../core/style/prefixes';
@import '../core/a11y/a11y';

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

[aria-disabled='true'] & {
@include user-select(none);
cursor: default;
user-select: none;
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/lib/slide-toggle/slide-toggle.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import '../core/style/variables';
@import '../core/ripple/ripple';
@import '../core/style/elevation';
@import '../core/style/prefixes';
@import '../core/a11y/a11y';

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

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

outline: none;

Expand Down Expand Up @@ -100,7 +101,7 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg
transition: $swift-linear;
transition-property: transform;

cursor: grab;
@include cursor-grab;

// Once the thumb container is being dragged around, we remove the transition duration to
// make the drag feeling fast and not delayed.
Expand Down
4 changes: 4 additions & 0 deletions stylelint-config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"plugins": [
"./tools/stylelint/no-prefixes/no-prefixes.js"
],
"rules": {
"material/no-prefixes": [["last 2 versions", "not ie <= 10", "not ie_mob <= 10"]],
"color-hex-case": "lower",
"color-no-invalid-hex": true,

Expand Down
9 changes: 0 additions & 9 deletions tools/gulp/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,6 @@ export const DIST_COMPONENTS_ROOT = join(DIST_ROOT, '@angular/material');

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

export const SASS_AUTOPREFIXER_OPTIONS = {
browsers: [
'last 2 versions',
'not ie <= 10',
'not ie_mob <= 10',
],
cascade: false,
};

export const HTML_MINIFIER_OPTIONS = {
collapseWhitespace: true,
removeComments: true,
Expand Down
4 changes: 1 addition & 3 deletions tools/gulp/task_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as child_process from 'child_process';
import * as fs from 'fs';
import * as gulp from 'gulp';
import * as path from 'path';
import {NPM_VENDOR_FILES, PROJECT_ROOT, DIST_ROOT, SASS_AUTOPREFIXER_OPTIONS} from './constants';
import {NPM_VENDOR_FILES, PROJECT_ROOT, DIST_ROOT} from './constants';


/** Those imports lack typings. */
Expand All @@ -11,7 +11,6 @@ const gulpMerge = require('merge2');
const gulpRunSequence = require('run-sequence');
const gulpSass = require('gulp-sass');
const gulpSourcemaps = require('gulp-sourcemaps');
const gulpAutoprefixer = require('gulp-autoprefixer');
const gulpConnect = require('gulp-connect');
const resolveBin = require('resolve-bin');
const firebaseAdmin = require('firebase-admin');
Expand Down Expand Up @@ -45,7 +44,6 @@ export function sassBuildTask(dest: string, root: string) {
return gulp.src(_globify(root, '**/*.scss'))
.pipe(gulpSourcemaps.init())
.pipe(gulpSass().on('error', gulpSass.logError))
.pipe(gulpAutoprefixer(SASS_AUTOPREFIXER_OPTIONS))
.pipe(gulpSourcemaps.write('.'))
.pipe(gulp.dest(dest));
};
Expand Down
59 changes: 59 additions & 0 deletions tools/stylelint/no-prefixes/needs-prefix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const autoprefixer = require('autoprefixer');
const Browsers = require('autoprefixer/lib/browsers');
const Prefixes = require('autoprefixer/lib/prefixes');

/**
* Utility to be used when checking whether a CSS declaration needs to be prefixed.
* Based on https://github.com/stylelint/stylelint/blob/master/lib/utils/isAutoprefixable.js
*/
module.exports = class NeedsPrefix {
constructor(browsers) {
this._prefixes = new Prefixes(
autoprefixer.data.prefixes,
new Browsers(autoprefixer.data.browsers, browsers)
);
}

/** Checks whether an @-rule needs to be prefixed. */
atRule(identifier) {
return this._prefixes.add[`@${identifier.toLowerCase()}`];
}

/** Checks whether a selector needs to be prefixed. */
selector(identifier) {
return this._prefixes.add.selectors.some(selectorObj => {
return identifier.toLowerCase() === selectorObj.name;
});
}

/** Checks whether a media query value needs to be prefixed. */
mediaFeature(identifier) {
return identifier.toLowerCase().indexOf('device-pixel-ratio') > -1;
}

/** Checks whether a property needs to be prefixed. */
property(identifier) {
// `fill` is an edge case since it was part of a proposal that got renamed to `stretch`.
// see: https://www.w3.org/TR/css-sizing-3/#changes
if (!identifier || identifier === 'fill') return false;

const needsPrefix = autoprefixer.data.prefixes[identifier.toLowerCase()];
const browsersThatNeedPrefix = needsPrefix ? needsPrefix.browsers : null;

return !!browsersThatNeedPrefix && !!this._prefixes.browsers.selected.find(browser => {
return browsersThatNeedPrefix.indexOf(browser) > -1;
});
}

/** Checks whether a CSS property value needs to be prefixed. */
value(prop, value) {
if (!prop || !value) return false;

const possiblePrefixableValues = this._prefixes.add[prop.toLowerCase()] &&
this._prefixes.add[prop.toLowerCase()].values;

return !!possiblePrefixableValues && possiblePrefixableValues.some(valueObj => {
return value.toLowerCase() === valueObj.name;
});
}
};
88 changes: 88 additions & 0 deletions tools/stylelint/no-prefixes/no-prefixes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const stylelint = require('stylelint');
const NeedsPrefix = require('./needs-prefix');
const parseSelector = require('stylelint/lib/utils/parseSelector');

const ruleName = 'material/no-prefixes';
const messages = stylelint.utils.ruleMessages(ruleName, {
property: property => `Unprefixed property "${property}".`,
value: (property, value) => `Unprefixed value in "${property}: ${value}".`,
atRule: name => `Unprefixed @rule "${name}".`,
mediaFeature: value => `Unprefixed media feature "${value}".`,
selector: selector => `Unprefixed selector "${selector}".`
});

/**
* Stylelint plugin that warns for unprefixed CSS.
*/
const plugin = stylelint.createPlugin(ruleName, browsers => {
return (root, result) => {
if (!stylelint.utils.validateOptions(result, ruleName, {})) return;

const needsPrefix = new NeedsPrefix(browsers);

// Check all of the `property: value` pairs.
root.walkDecls(decl => {
if (needsPrefix.property(decl.prop)) {
stylelint.utils.report({
result,
ruleName,
message: messages.property(decl.prop),
node: decl,
index: (decl.raws.before || '').length
});
} else if (needsPrefix.value(decl.prop, decl.value)) {
stylelint.utils.report({
result,
ruleName,
message: messages.value(decl.prop, decl.value),
node: decl,
index: (decl.raws.before || '').length
});
}
});

// Check all of the @-rules and their values.
root.walkAtRules(rule => {
if (needsPrefix.atRule(rule.name)) {
stylelint.utils.report({
result,
ruleName,
message: messages.atRule(rule.name),
node: rule
});
} else if (needsPrefix.mediaFeature(rule.params)) {
stylelint.utils.report({
result,
ruleName,
message: messages.mediaFeature(rule.name),
node: rule
});
}
});

// Walk the rules and check if the selector needs prefixes.
root.walkRules(rule => {
// Silence warnings for SASS selectors. Stylelint does this in their own rules as well:
// https://github.com/stylelint/stylelint/blob/master/lib/utils/isStandardSyntaxSelector.js
parseSelector(rule.selector, { warn: () => {} }, rule, selectorTree => {
selectorTree.walkPseudos(pseudoNode => {
if (needsPrefix.selector(pseudoNode.value)) {
stylelint.utils.report({
result,
ruleName,
message: messages.selector(pseudoNode.value),
node: rule,
index: (rule.raws.before || '').length + pseudoNode.sourceIndex,
});
}
});
});
});

};
});


plugin.ruleName = ruleName;
plugin.messages = messages;
module.exports = plugin;

0 comments on commit 18159c5

Please sign in to comment.