├── .gitignore ├── README.md ├── package.json └── src ├── loader.js ├── runtime.es.js └── runtime.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_mdoules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webpack-ant-icon-loader 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | 5 | 6 | ### 一、简介篇 7 | 8 | 用于解决 在引入[`ant-design`](https://ant-design.gitee.io/index-cn) `3.x`版本后(全量引入`@ant-design/icons`图标文件),导致打包生成的主文件较大的问题。 9 | 10 | 11 | ### 二、安装篇 12 | 13 | > npm 14 | 15 | ```shell 16 | npm install webpack-ant-icon-loader --save-dev 17 | ``` 18 | 19 | > yarn 20 | 21 | ```shell 22 | yarn add webpack-ant-icon-loader --dev 23 | ``` 24 | 25 | 26 | ### 三、使用篇 27 | 28 | #### 注意 29 | 30 | 如果您的项目中使用了`optimization.splitChunks` ,且设置了`chunks:'all'` 31 | 32 | 请注意,需要将字体图标的`chunk`排除下,例如: 33 | 34 | ```js 35 | module.exports = { 36 | optimization: { 37 | splitChunks: { 38 | cacheGroups: { 39 | // chunks:'all', 40 | chunks:function(chunk){ 41 | // 这里的name 可以参考在使用`webpack-ant-icon-loader`时指定的`chunkName` 42 | return chunk.name !== 'antd-icons'; 43 | }, 44 | vendors: { 45 | filename: '[name].bundle.js' 46 | } 47 | } 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | #### [webpack](https://github.com/webpack/webpack) 54 | > webpack.config.js 55 | 56 | ```js 57 | module.exports = { 58 | module:{ 59 | rules:[ 60 | { 61 | loader:'webpack-ant-icon-loader', 62 | enforce: 'pre', 63 | // options:{ 64 | // chunkName:'antd-icons' 65 | // }, 66 | include:[ 67 | require.resolve('@ant-design/icons/lib/dist') 68 | ] 69 | } 70 | ] 71 | } 72 | } 73 | 74 | ``` 75 | 76 | #### [umi](https://umijs.org/) 77 | > .umirc.js 78 | 79 | ```js 80 | export default { 81 | chainWebpack(config, { webpack }) { 82 | 83 | // code split @ant-design/icons 84 | config.module 85 | .rule('@ant-design/icons') 86 | .include.add(require.resolve('@ant-design/icons/lib/dist')).end() 87 | .use('ant-icon') 88 | .loader('webpack-ant-icon-loader'); 89 | }, 90 | } 91 | ``` 92 | 93 | #### [`react-app-rewired`](https://github.com/timarney/react-app-rewired) 94 | > config-overrides.js 95 | 96 | ```js 97 | const path = require('path'); 98 | 99 | module.exports = { 100 | // The Webpack config to use when compiling your react app for development or production. 101 | webpack: function (config, env) { 102 | // ...add your webpack config 103 | 104 | // add webpack-ant-icon-loader 105 | config.module.rules.push({ 106 | loader: 'webpack-ant-icon-loader', 107 | enforce: 'pre', 108 | include: [ 109 | require.resolve('@ant-design/icons/lib/dist') 110 | ] 111 | }) 112 | 113 | return config; 114 | }, 115 | } 116 | ``` 117 | #### [`customize-cra`](https://github.com/arackaf/customize-cra) 118 | > config-overrides.js 119 | ```js 120 | const path = require('path'); 121 | const { override } = require('customize-cra'); 122 | 123 | module.exports = override( 124 | // add webpack-ant-icon-loader 125 | (config) => { 126 | config.module.rules.push({ 127 | loader: 'webpack-ant-icon-loader', 128 | enforce: 'pre', 129 | include: [ 130 | require.resolve('@ant-design/icons/lib/dist') 131 | ] 132 | }); 133 | return config; 134 | }, 135 | ); 136 | ``` 137 | 138 | 139 | ### 四、原理篇 140 | 141 | 主要通过`webpack`代码拆分([`import`](https://webpack.js.org/guides/code-splitting/#dynamic-imports))来完成体积优化: 142 | 143 | 144 | * 把`@antd-design/icons/lib/dist`的图标文件拆成独立的`chunk`,且异步加载后自动注册该图标文件图标。 145 | 146 | 由于[`ant design`](https://ant-design.gitee.io/index-cn) 内部组件使用了很多图标,所以不考虑按需加载图标的方案,仅仅是将图标文件拆分出来,减少`主文件`的体积,(类似于加载独立的字体文件)。 147 | 148 | 149 | * 通过`webpack-ant-icon-loader` 提供的`runtime.js` 来完成延迟加载图标的刷新。 150 | 151 | 由于使用了异步加载图标文件的原因,在首次进入页面后,会导致`已渲染的图标组件但是还未注册的图标`会出现空白,解决这个问题,需要触发一次组件刷新, 所以通过`runtime.js` 在异步图标文件加载完成后,从最小层面(仅刷新`已渲染但是未注册的的图标`)进行一次性组件刷新。刷新完毕后,`runtime.js`将不再介入操作,一切回归原始流程。 152 | 153 | 154 | #### 等未来,`ant design`推出了解决方案,仅需要从`webpack.config.js`中移除当前`loader`即可。 155 | 156 | 157 | ### 五、开源许可 158 | 基于 [MIT License](http://zh.wikipedia.org/wiki/MIT_License) 开源,使用代码只需说明来源,或者引用 [license.txt](https://github.com/sofish/typo.css/blob/master/license.txt) 即可。 159 | 160 | [npm-url]: https://www.npmjs.com/package/webpack-ant-icon-loader 161 | [npm-image]: https://img.shields.io/npm/v/webpack-ant-icon-loader.svg 162 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-ant-icon-loader", 3 | "version": "1.0.8", 4 | "description": "ant design icons code split loader", 5 | "main": "src/loader.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Beven91/webpack-ant-icon-loader.git" 12 | }, 13 | "keywords": [ 14 | "ant", 15 | "design", 16 | "@ant-design", 17 | "icons", 18 | "code", 19 | "split" 20 | ], 21 | "author": "Beven", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/Beven91/webpack-ant-icon-loader/issues" 25 | }, 26 | "homepage": "https://github.com/Beven91/webpack-ant-icon-loader#readme" 27 | } -------------------------------------------------------------------------------- /src/loader.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | this.cacheable && this.cacheable(); 3 | const options = this.query || { chunkName: 'antd-icons' }; 4 | return ` 5 | import AutoReloadIcon from 'webpack-ant-icon-loader/src/runtime.js'; 6 | // 加载script标签会阻塞js执行,antd部分组件(如表格)无法在一轮循环内完成完整的渲染,故延时 7 | setTimeout(function(){ 8 | import(/* webpackChunkName: "${options.chunkName}" */'!!./dist.js').then(function(allIcons) { 9 | AutoReloadIcon.load(allIcons); 10 | }); 11 | }); 12 | export default { 13 | name:'add', 14 | theme:'fill' 15 | }`; 16 | }; 17 | -------------------------------------------------------------------------------- /src/runtime.es.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; import PropTypes from 'prop-types'; 2 | import ReactIcon from '@ant-design/icons-react'; 3 | import { generate } from '@ant-design/icons-react/es/utils'; 4 | 5 | // 原始获取图标函数  6 | const originalGetIcon = ReactIcon.get.bind(ReactIcon); 7 | // 覆盖get函数,在图标未加载前,返回一个渲染AutoReloadIcon来控制自动刷新  8 | ReactIcon.get = function (key) { 9 | const target = originalGetIcon(key); 10 | if (target) { 11 | return target; 12 | } 13 | return { 14 | icon: (primaryColor, secondaryColor) => { 15 | return { 16 | tag: function AutoReloadIconWrapper(props) { 17 | return ( 18 | 24 | ); 25 | }, 26 | }; 27 | }, 28 | }; 29 | }; 30 | 31 | export default class AutoReloadIcon extends React.Component { 32 | // 属性定义    33 | static propTypes = { 34 | // 原始图标名称      35 | originalType: PropTypes.string, 36 | // 图标原始root属性      37 | rootProps: PropTypes.object, 38 | } 39 | // 默认属性    40 | static defaultProps = { originalType: '', rootProps: {}, } 41 | // 所有延迟加载的图标实例    42 | static instances = [] 43 | // 新增实例    44 | static addInstance(instance) { 45 | this.instances.push(instance); 46 | } 47 | // 删除实例   48 | static removeInstance(instance) { 49 | const index = this.instances.indexOf(instance); 50 | this.instances.splice(index, 1); 51 | } 52 | 53 | // 刷新所有实例    54 | static load(allIcons) { 55 | // 注册所有图标      56 | ReactIcon.add(...Object.keys(allIcons).map((key) => allIcons[key])); 57 | // 还原get函数     58 | ReactIcon.get = originalGetIcon; 59 | // 通过强制刷新来,显示异步加载的图标     60 | this.instances.forEach((instance) => instance.forceUpdate()); 61 | // 移除所有队列      62 | this.instances.length = 0; 63 | } 64 | constructor(props) { 65 | super(props); 66 | AutoReloadIcon.addInstance(this); 67 | } 68 | // 组件销毁时,从监听的延迟实例中移除    69 | componentWillUnmount() { 70 | AutoReloadIcon.removeInstance(this); 71 | } 72 | 73 | 74 | // 获取字体组件    75 | getIcon() { 76 | const { originalType, primaryColor, secondaryColor } = this.props; 77 | const target = originalGetIcon(originalType); 78 | if (typeof target === 'function') { 79 | return target(primaryColor, secondaryColor); 80 | } else { 81 | return target; 82 | } 83 | } 84 | // 渲染图标    85 | render() { 86 | const { originalType, rootProps } = this.props; 87 | const target = this.getIcon(originalType); 88 | if (target) { 89 | return React.Children.only(generate(target.icon, 'svg-' + target.name, rootProps)); 90 | } 91 | else { 92 | return ; 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/runtime.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | var _iconsReact = _interopRequireDefault(require("@ant-design/icons-react")); 13 | 14 | var _utils = require("@ant-design/icons-react/es/utils"); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return right[Symbol.hasInstance](left); } else { return left instanceof right; } } 19 | 20 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 21 | 22 | function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } 23 | 24 | function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } 25 | 26 | function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } 27 | 28 | function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } 29 | 30 | function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 31 | 32 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 33 | 34 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 35 | 36 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 37 | 38 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 39 | 40 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 41 | 42 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 43 | 44 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 45 | 46 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 47 | 48 | // 原始获取图标函数  49 | var originalGetIcon = _iconsReact.default.get.bind(_iconsReact.default); // 覆盖get函数,在图标未加载前,返回一个渲染AutoReloadIcon来控制自动刷新  50 | 51 | 52 | _iconsReact.default.get = function (key) { 53 | var target = originalGetIcon(key); 54 | 55 | if (target) { 56 | return target; 57 | } 58 | 59 | return { 60 | icon: function icon(primaryColor, secondaryColor) { 61 | return { 62 | tag: function AutoReloadIconWrapper(props) { 63 | return _react.default.createElement(AutoReloadIcon, { 64 | originalType: key, 65 | rootProps: props, 66 | primaryColor: primaryColor, 67 | secondaryColor: secondaryColor 68 | }); 69 | } 70 | }; 71 | } 72 | }; 73 | }; 74 | 75 | var AutoReloadIcon = 76 | /*#__PURE__*/ 77 | function (_React$Component) { 78 | _inherits(AutoReloadIcon, _React$Component); 79 | 80 | _createClass(AutoReloadIcon, null, [{ 81 | key: "addInstance", 82 | // 属性定义    83 | // 新增实例    84 | value: function addInstance(instance) { 85 | this.instances.push(instance); 86 | } // 删除实例   87 | 88 | }, { 89 | key: "removeInstance", 90 | value: function removeInstance(instance) { 91 | var index = this.instances.indexOf(instance); 92 | this.instances.splice(index, 1); 93 | } // 刷新所有实例    94 | 95 | }, { 96 | key: "load", 97 | value: function load(allIcons) { 98 | // 注册所有图标      99 | _iconsReact.default.add.apply(_iconsReact.default, _toConsumableArray(Object.keys(allIcons).map(function (key) { 100 | return allIcons[key]; 101 | }))); // 还原get函数     102 | 103 | 104 | _iconsReact.default.get = originalGetIcon; // 通过强制刷新来,显示异步加载的图标     105 | 106 | this.instances.forEach(function (instance) { 107 | return instance.forceUpdate(); 108 | }); // 移除所有队列      109 | 110 | this.instances.length = 0; 111 | } 112 | }]); 113 | 114 | function AutoReloadIcon(props) { 115 | var _this; 116 | 117 | _classCallCheck(this, AutoReloadIcon); 118 | 119 | _this = _possibleConstructorReturn(this, _getPrototypeOf(AutoReloadIcon).call(this, props)); 120 | AutoReloadIcon.addInstance(_assertThisInitialized(_this)); 121 | return _this; 122 | } // 组件销毁时,从监听的延迟实例中移除    123 | 124 | 125 | _createClass(AutoReloadIcon, [{ 126 | key: "componentWillUnmount", 127 | value: function componentWillUnmount() { 128 | AutoReloadIcon.removeInstance(this); 129 | } // 获取字体组件    130 | 131 | }, { 132 | key: "getIcon", 133 | value: function getIcon() { 134 | var _this$props = this.props, 135 | originalType = _this$props.originalType, 136 | primaryColor = _this$props.primaryColor, 137 | secondaryColor = _this$props.secondaryColor; 138 | var target = originalGetIcon(originalType); 139 | 140 | if (typeof target === 'function') { 141 | return target(primaryColor, secondaryColor); 142 | } else { 143 | return target; 144 | } 145 | } // 渲染图标    146 | 147 | }, { 148 | key: "render", 149 | value: function render() { 150 | var _this$props2 = this.props, 151 | originalType = _this$props2.originalType, 152 | rootProps = _this$props2.rootProps; 153 | var target = this.getIcon(originalType); 154 | 155 | if (target) { 156 | return _react.default.Children.only((0, _utils.generate)(target.icon, 'svg-' + target.name, rootProps)); 157 | } else { 158 | return _react.default.createElement("svg", rootProps); 159 | } 160 | } 161 | }]); 162 | 163 | return AutoReloadIcon; 164 | }(_react.default.Component); 165 | 166 | exports.default = AutoReloadIcon; 167 | 168 | _defineProperty(AutoReloadIcon, "propTypes", { 169 | // 原始图标名称      170 | originalType: _propTypes.default.string, 171 | // 图标原始root属性      172 | rootProps: _propTypes.default.object // 默认属性    173 | 174 | }); 175 | 176 | _defineProperty(AutoReloadIcon, "defaultProps", { 177 | originalType: '', 178 | rootProps: {} // 所有延迟加载的图标实例    179 | 180 | }); 181 | 182 | _defineProperty(AutoReloadIcon, "instances", []); --------------------------------------------------------------------------------