diff --git a/assets/stylesheets/_vendor.scss b/assets/stylesheets/_vendor.scss
index 44276c9e566bbd..bd8bf83a5da623 100644
--- a/assets/stylesheets/_vendor.scss
+++ b/assets/stylesheets/_vendor.scss
@@ -5,15 +5,15 @@
.gridicon {
fill: currentColor;
- &.needs-offset g {
+ &.needs-offset use {
transform: translate( 1px, 1px ); /* translates to .5px because it's in a child element */
}
- &.needs-offset-x g {
+ &.needs-offset-x use {
transform: translate( 1px, 0 ); /* only nudges horizontally */
}
- &.needs-offset-y g {
+ &.needs-offset-y use {
transform: translate( 0, 1px ); /* only nudges vertically */
}
}
diff --git a/client/blocks/inline-help/style.scss b/client/blocks/inline-help/style.scss
index 9c0b82bdebdb53..00dc78432e8d7b 100644
--- a/client/blocks/inline-help/style.scss
+++ b/client/blocks/inline-help/style.scss
@@ -74,7 +74,7 @@
height: 36px;
width: 36px;
- g {
+ use {
transform: none;
}
}
diff --git a/client/components/async-gridicons/fallback.jsx b/client/components/async-gridicons/fallback.jsx
deleted file mode 100644
index 935e1845eb3aa8..00000000000000
--- a/client/components/async-gridicons/fallback.jsx
+++ /dev/null
@@ -1,23 +0,0 @@
-/** @format */
-
-/**
- * External dependencies
- */
-import React from 'react';
-
-/**
- * Internal dependencies
- */
-
-export default function FallbackIcon() {
- /* eslint-disable wpcalypso/jsx-classname-namespace */
- return (
-
- );
-}
diff --git a/client/components/async-gridicons/index.jsx b/client/components/async-gridicons/index.jsx
deleted file mode 100644
index c0cafbbd035e4b..00000000000000
--- a/client/components/async-gridicons/index.jsx
+++ /dev/null
@@ -1,63 +0,0 @@
-/** @format */
-
-/**
- * External dependencies
- */
-import React, { Component } from 'react';
-
-/**
- * Internal dependencies
- */
-import FallbackIcon from './fallback';
-
-const loadedIcons = new Map();
-
-function loadIcon( icon ) {
- return import( /* webpackChunkName: "gridicons", webpackInclude: /\.js$/, webpackExclude: /dist\/(index\.js|example\.js)$/, webpackMode: "lazy-once" */
- `gridicons/dist/${ icon }` ).then(
- g => {
- loadedIcons.set( icon, g.default );
- return g.default;
- },
- err => {
- loadedIcons.set( icon, false );
- console.warn( `Error loading icon '${ icon }':`, err.message ); // eslint-disable-line no-console
- }
- );
-}
-
-class AsyncGridicon extends Component {
- constructor( props ) {
- super( props );
- }
- checkAndLoad() {
- if ( ! loadedIcons.has( this.props.icon ) ) {
- loadIcon( this.props.icon ).then( () => this.update() );
- }
- }
-
- componentDidMount() {
- this.checkAndLoad();
- }
- componentDidUpdate() {
- this.checkAndLoad();
- }
-
- update = () => this.forceUpdate();
-
- componentWillUnmount() {
- this.update = () => {};
- }
-
- render() {
- const { icon = '', ...rest } = this.props;
- if ( loadedIcons.get( icon ) ) {
- const Icon = loadedIcons.get( icon );
- return ;
- }
-
- return ;
- }
-}
-
-export default AsyncGridicon;
diff --git a/client/components/external-gridicons/icons-offset.js b/client/components/external-gridicons/icons-offset.js
new file mode 100644
index 00000000000000..540e7b96ad528d
--- /dev/null
+++ b/client/components/external-gridicons/icons-offset.js
@@ -0,0 +1,83 @@
+export const iconsThatNeedOffset = [
+ 'gridicons-add-outline',
+ 'gridicons-add',
+ 'gridicons-align-image-center',
+ 'gridicons-align-image-left',
+ 'gridicons-align-image-none',
+ 'gridicons-align-image-right',
+ 'gridicons-attachment',
+ 'gridicons-bold',
+ 'gridicons-bookmark-outline',
+ 'gridicons-bookmark',
+ 'gridicons-calendar',
+ 'gridicons-cart',
+ 'gridicons-create',
+ 'gridicons-custom-post-type',
+ 'gridicons-external',
+ 'gridicons-folder',
+ 'gridicons-heading',
+ 'gridicons-help-outline',
+ 'gridicons-help',
+ 'gridicons-history',
+ 'gridicons-info-outline',
+ 'gridicons-info',
+ 'gridicons-italic',
+ 'gridicons-layout-blocks',
+ 'gridicons-link-break',
+ 'gridicons-link',
+ 'gridicons-list-checkmark',
+ 'gridicons-list-ordered',
+ 'gridicons-list-unordered',
+ 'gridicons-menus',
+ 'gridicons-minus',
+ 'gridicons-my-sites',
+ 'gridicons-notice-outline',
+ 'gridicons-notice',
+ 'gridicons-plus-small',
+ 'gridicons-plus',
+ 'gridicons-popout',
+ 'gridicons-posts',
+ 'gridicons-scheduled',
+ 'gridicons-share-ios',
+ 'gridicons-star-outline',
+ 'gridicons-star',
+ 'gridicons-stats',
+ 'gridicons-status',
+ 'gridicons-thumbs-up',
+ 'gridicons-textcolor',
+ 'gridicons-time',
+ 'gridicons-trophy',
+ 'gridicons-user-circle',
+ 'gridicons-reader-follow',
+ 'gridicons-reader-following',
+];
+
+export const iconsThatNeedOffsetY = [
+ 'gridicons-align-center',
+ 'gridicons-align-justify',
+ 'gridicons-align-left',
+ 'gridicons-align-right',
+ 'gridicons-arrow-left',
+ 'gridicons-arrow-right',
+ 'gridicons-house',
+ 'gridicons-indent-left',
+ 'gridicons-indent-right',
+ 'gridicons-minus-small',
+ 'gridicons-print',
+ 'gridicons-sign-out',
+ 'gridicons-stats-alt',
+ 'gridicons-trash',
+ 'gridicons-underline',
+ 'gridicons-video-camera',
+];
+
+export const iconsThatNeedOffsetX = [
+ 'gridicons-arrow-down',
+ 'gridicons-arrow-up',
+ 'gridicons-comment',
+ 'gridicons-clear-formatting',
+ 'gridicons-flag',
+ 'gridicons-menu',
+ 'gridicons-reader',
+ 'gridicons-strikethrough',
+];
diff --git a/client/components/external-gridicons/index.js b/client/components/external-gridicons/index.js
new file mode 100644
index 00000000000000..619cdcce163a26
--- /dev/null
+++ b/client/components/external-gridicons/index.js
@@ -0,0 +1,65 @@
+/** @format */
+
+/**
+ * External dependencies
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+import svg4everybody from 'svg4everybody';
+
+/**
+ * Internal dependencies
+ */
+import { iconsThatNeedOffset, iconsThatNeedOffsetX, iconsThatNeedOffsetY } from './icons-offset';
+
+function needsOffset( name, icons ) {
+ return icons.indexOf( name ) >= 0;
+}
+
+const isBrowser = typeof window !== 'undefined';
+if ( isBrowser ) {
+ // Polyfill SVG external content support. Noop in the evergreen build.
+ svg4everybody();
+}
+
+function ExternalGridicons( props ) {
+ const { size = 24, icon, onClick, className, ...otherProps } = props;
+ const isModulo18 = size % 18 === 0;
+
+ // Using a missing icon doesn't produce any errors, just a blank icon, which is the exact intended behaviour.
+ // This means we don't need to perform any checks on the icon name.
+ const iconName = `gridicons-${ icon }`;
+ const offsetClasses = isModulo18
+ ? [
+ needsOffset( iconName, iconsThatNeedOffset ) ? 'needs-offset' : false,
+ needsOffset( iconName, iconsThatNeedOffsetX ) ? 'needs-offset-x' : false,
+ needsOffset( iconName, iconsThatNeedOffsetY ) ? 'needs-offset-y' : false,
+ ]
+ : [];
+ const iconClass = [ 'gridicon', iconName, className, ...offsetClasses ]
+ .filter( Boolean ) // Remove all falsy values.
+ .join( ' ' );
+
+ return (
+
+ );
+}
+
+ExternalGridicons.propTypes = {
+ icon: PropTypes.string.isRequired,
+ size: PropTypes.number,
+ onClick: PropTypes.func,
+ className: PropTypes.string,
+};
+
+export default React.memo( ExternalGridicons );
diff --git a/client/my-sites/plugins/plugin-action/style.scss b/client/my-sites/plugins/plugin-action/style.scss
index 7a91ebdfd8664e..a12c07b27d841b 100644
--- a/client/my-sites/plugins/plugin-action/style.scss
+++ b/client/my-sites/plugins/plugin-action/style.scss
@@ -75,7 +75,7 @@
display: block;
}
- .gridicons-info-outline g {
+ .gridicons-info-outline use {
// revert the translate(1px,1px) done by needs-offset
transform: none;
}
diff --git a/client/my-sites/plugins/plugin-automated-transfer/style.scss b/client/my-sites/plugins/plugin-automated-transfer/style.scss
index 5f427ce885568a..af18ae66c32399 100644
--- a/client/my-sites/plugins/plugin-automated-transfer/style.scss
+++ b/client/my-sites/plugins/plugin-automated-transfer/style.scss
@@ -1,10 +1,10 @@
.plugin-automated-transfer__notice {
- .gridicons-sync g {
+ .gridicons-sync use {
animation: spinning-sync-icon linear 2s infinite;
transform-origin: center;
}
- .gridicons-checkmark g,
- .gridicons-notice g {
+ .gridicons-checkmark use,
+ .gridicons-notice use {
transform: rotate( 0deg );
}
}
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index 69d5daa14493b8..3a99eb1d730062 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -33,7 +33,7 @@
"caniuse-api": "3.0.0",
"css-loader": "2.1.1",
"duplicate-package-checker-webpack-plugin": "3.0.0",
- "mini-css-extract-plugin-with-rtl": "github:Automattic/mini-css-extract-plugin-with-rtl#af1300db7027af8caa9a3015f54a34aec545cc54",
+ "mini-css-extract-plugin-with-rtl": "github:Automattic/mini-css-extract-plugin-with-rtl",
"node-sass": "4.11.0",
"postcss-custom-properties": "8.0.9",
"postcss-loader": "3.0.0",
@@ -47,7 +47,7 @@
"dependencies": {
"autoprefixer": {
"version": "9.4.4",
- "bundled": true,
+ "resolved": "",
"dev": true,
"requires": {
"browserslist": "^4.3.7",
@@ -5280,7 +5280,8 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -5298,11 +5299,13 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5315,15 +5318,18 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5426,7 +5432,8 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -5436,6 +5443,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5448,17 +5456,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -5475,6 +5486,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5547,7 +5559,8 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -5557,6 +5570,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -5632,7 +5646,8 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -5662,6 +5677,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -5679,6 +5695,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -5717,11 +5734,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true
+ "bundled": true,
+ "optional": true
}
}
}
@@ -11442,7 +11461,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -11463,12 +11483,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -11483,17 +11505,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -11610,7 +11635,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -11622,6 +11648,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -11636,6 +11663,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -11643,12 +11671,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -11667,6 +11697,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -11747,7 +11778,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -11759,6 +11791,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -11844,7 +11877,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -11880,6 +11914,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -11899,6 +11934,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -11942,12 +11978,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
}
@@ -19065,6 +19103,11 @@
"integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=",
"dev": true
},
+ "svg4everybody": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/svg4everybody/-/svg4everybody-2.1.9.tgz",
+ "integrity": "sha1-W9n23vwTOFmgRGRtR0P6vCjbfi0="
+ },
"svgo": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.2.1.tgz",
diff --git a/package.json b/package.json
index 7dc9d1bfeedbe2..556686a765390a 100644
--- a/package.json
+++ b/package.json
@@ -184,6 +184,7 @@
"store": "2.0.12",
"striptags": "2.2.1",
"superagent": "3.8.3",
+ "svg4everybody": "2.1.9",
"textarea-caret": "3.1.0",
"tinymce": "4.8.5",
"to-title-case": "1.0.0",
diff --git a/public/images/gridicons/sprite.svg b/public/images/gridicons/sprite.svg
new file mode 100644
index 00000000000000..798fb9013e02eb
--- /dev/null
+++ b/public/images/gridicons/sprite.svg
@@ -0,0 +1,990 @@
+
+
diff --git a/webpack.config.js b/webpack.config.js
index e7b8cdefad767a..18bd79c95d182a 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -252,7 +252,7 @@ function getWebpackConfig( {
'social-logos/example': 'social-logos/build/example',
debug: path.resolve( __dirname, 'node_modules/debug' ),
store: 'store/dist/store.modern',
- gridicons$: path.resolve( __dirname, 'client/components/async-gridicons' ),
+ gridicons$: path.resolve( __dirname, 'client/components/external-gridicons' ),
},
getAliasesForExtensions( {
extensionsDirectory: path.join( __dirname, 'client', 'extensions' ),
@@ -331,6 +331,13 @@ function getWebpackConfig( {
);
}
+ // The SVG external content polyfill (svg4everybody) isn't needed for evergreen browsers, so don't bundle it.
+ if ( browserslistEnv === 'evergreen' ) {
+ webpackConfig.plugins.push(
+ new webpack.NormalModuleReplacementPlugin( /^svg4everybody$/, 'lodash/noop' )
+ );
+ }
+
return webpackConfig;
}