├── .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", []);
--------------------------------------------------------------------------------