Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Svg icons make bunlde size too large #12011

Open
zlab opened this Issue Sep 3, 2018 · 105 comments

Comments

Projects
None yet
@zlab
Copy link

zlab commented Sep 3, 2018

  • I have searched the issues of this repository and believe that this is not a duplicate.

Version

3.9.0

Environment

webpack4

Reproduction link

https://zlab.github.io/report.html

Steps to reproduce

webpack build

What is expected?

icon 按需打包, js文件拆分

What is actually happening?

打包到chunk-vendors里去了

@ant-design-bot

This comment has been minimized.

Copy link

ant-design-bot commented Sep 3, 2018

Translation of this issue:


antd svg package size is too large, it is recommended @ant-design/icons package on demand

  • I have searched the issues of this repository and believe that this is not a duplicate.

Version

3.9.0

Environment

Webpack4

Reproduction link

https://zlab.github.io/report.html

Steps to reproduce

Webpack build

What is expected?

Icon Pack as needed, js file split

What is actually happening?

Packed into chunk-vendors

@zlab

This comment has been minimized.

Copy link
Author

zlab commented Sep 3, 2018

我只用到了几个icon

image

@zombieJ zombieJ assigned HeskeyBaozi and unassigned yutingzhao1991 Sep 3, 2018

@HeskeyBaozi

This comment has been minimized.

Copy link
Member

HeskeyBaozi commented Sep 3, 2018

<Icon /> 默认全量引入了图标库。因为不知道你会引入何种图标进行运行时引入。之前图标托管在 iconfont.cn 所以打包无感知。

这需要深入讨论
临时方案见 #12011 (comment)

@afc163

This comment has been minimized.

Copy link
Member

afc163 commented Sep 3, 2018

@yesmeck

This comment has been minimized.

Copy link
Member

yesmeck commented Sep 3, 2018

需要提供一个可以按需加载的方式,antd 组件内部也用这个方式。

import Star from 'antd/icons/star';

<Star />
@HeskeyBaozi

This comment has been minimized.

Copy link
Member

HeskeyBaozi commented Sep 3, 2018

但是这样做的话,以前的写法无法兼容

<Icon type="star" /> // should import star icon first
@zlab

This comment has been minimized.

Copy link
Author

zlab commented Sep 3, 2018

不能按需打包, 异步加载也好啊,

之前使用iconfont就是页面内容会先出来, 图标会慢慢加载出来

@zlab

This comment has been minimized.

Copy link
Author

zlab commented Sep 3, 2018

<Icon.Star />

@yesmeck

This comment has been minimized.

Copy link
Member

yesmeck commented Sep 3, 2018

但是这样做的话,以前的写法无法兼容

不会的,只要不用 <Icon /> 就不全量打包进来。

@zlab

This comment has been minimized.

Copy link
Author

zlab commented Sep 3, 2018

有些场景不能按需打包, 比如设置menu的图标, 如果可以后台配置, 这样前端是无法预知会设置成什么图标的,
异步加载比较适合, 把@ant-design/icons打成一个chunk就好

@HeskeyBaozi

This comment has been minimized.

Copy link
Member

HeskeyBaozi commented Sep 3, 2018

实际上内置使用了 <Icon /> 的组件例如 <DatePicker />, <Select /> 等,用到也会全量引入。

@ChiaJune

This comment has been minimized.

Copy link

ChiaJune commented Sep 3, 2018

如果可以后台配置, 这样前端是无法预知会设置成什么图标的,异步加载比较适合

我们做的后台系统的menu就是配置的,图标异步加载真的很需要,全量打包实在太大了。

@nuintun

This comment has been minimized.

Copy link
Contributor

nuintun commented Sep 3, 2018

感觉异步比较好。
想按需打包估计得写个像 babel-plugin-importbabel 插件了。

analyzer

@yesmeck

This comment has been minimized.

Copy link
Member

yesmeck commented Sep 3, 2018

实际上内置使用了 <Icon /> 的组件例如 <DatePicker />, <Select /> 等,用到也会全量引入。

内部的写法都改掉

@lizhancheng

This comment has been minimized.

Copy link

lizhancheng commented Sep 3, 2018

项目中有type是个变量,是只能引入全量了吗?
<Icon type={icon} />
这种有没有可以部分引入的?

@afc163

This comment has been minimized.

Copy link
Member

afc163 commented Sep 3, 2018

看看是否能用 svg symbol sprite 的方式尽可能减少全量包的大小。

https://github.com/jkphl/svg-sprite
https://css-tricks.com/svg-symbol-good-choice-icons/
https://css-tricks.com/pretty-good-svg-icon-system/

@afc163

This comment has been minimized.

Copy link
Member

afc163 commented Sep 3, 2018

另外 @ant-design/icons 内应该直接构建好 dist 包来用,直接依赖浅编译的源码并无必要。

@ycjcl868

This comment has been minimized.

Copy link
Member

ycjcl868 commented Sep 3, 2018

+1 ,确实包大了一倍

@jljsj33

This comment has been minimized.

Copy link
Member

jljsj33 commented Sep 4, 2018

💢 脑壳痛。。。。大爷的。。。

@LinFeng1997

This comment has been minimized.

Copy link

LinFeng1997 commented Sep 5, 2018

最骚的是带了很多 SourceMappingUrl

@yuanclan

This comment has been minimized.

Copy link

yuanclan commented Sep 10, 2018

搞得我都退到 3.8.2版本了

@nerdyadventurer

This comment has been minimized.

Copy link

nerdyadventurer commented Feb 24, 2019

@yesmeck , @afc163 Any plans to close this soon?

@henryzp

This comment has been minimized.

Copy link

henryzp commented Feb 28, 2019

@HeskeyBaozi @afc163 ,请问要如何使用你们新写的这个??

@nerdyadventurer

This comment has been minimized.

Copy link

nerdyadventurer commented Mar 3, 2019

@HeskeyBaozi May I ask why you guys are not working on this? It's been over five months. Do you guys consider this a low priority?

@mattrc

This comment has been minimized.

Copy link

mattrc commented Mar 3, 2019

@HeskeyBaozi May I ask why you guys are not working on this? It's been over five months. Do you guys consider this a low priority?

Agree, it's really annoying =(

@aryzing

This comment has been minimized.

Copy link

aryzing commented Mar 3, 2019

After inspecting the code of antd, I'm seeing that a component such as Button imports Icon, which in turn imports (according to the generated code)

// import everything here
import * as allIcons from '@ant-design/icons/lib/dist';

//...

// use everything here :(
ReactIcon.add.apply(
  ReactIcon,
  _toConsumableArray(
    Object.keys(allIcons).map(function(key) {
      return allIcons[key];
    })
  )
);

which causes treeshaking to fail, and thus importing all icons. Please consider pointing the import to @ant-design/icons/lib/index.es.js and not performing operations on all icons until needed. Also, check out the history of this line: https://github.com/ant-design/ant-design/blob/master/components/icon/index.tsx#L3 It's been changing a bit over time.

@poorel

This comment has been minimized.

Copy link

poorel commented Mar 8, 2019

这直接导致整个项目不能用 好吗

@henryzp

This comment has been minimized.

Copy link

henryzp commented Mar 8, 2019

@poorel

<Icon /> 默认全量引入了图标库。因为不知道你会引入何种图标进行运行时引入。之前图标托管在 iconfont.cn 所以打包无感知。

这需要深入讨论
临时方案见 #12011 (comment)

这个方案还是可以用的,我测试过了,但是你要整理出来你项目中有哪些icon,就这个略麻烦些

@chenyong

This comment has been minimized.

Copy link

chenyong commented Mar 8, 2019

但是你要整理出来你项目中有哪些icon...

之前试着看了, 发现 icon 的引用很多是 antd 自带组件做的, 而且数量也不小. 自己在业务直接引用的虽然很小, 但是考虑组件可能引用到的, 数量也不好统计了.

@henryzp

This comment has been minimized.

Copy link

henryzp commented Mar 8, 2019

@chenyong 额,那蛋疼了。。。

@zachguo

This comment has been minimized.

Copy link
Contributor

zachguo commented Mar 8, 2019

@aryzing Your approach sounds appealing. Will it result in less effort than #12888 ? That PR involves too many breaking changes, probably it's part of the reason why it's still not finished after months.

@aryzing

This comment has been minimized.

Copy link

aryzing commented Mar 10, 2019

@zachguo I looked through #12888, and I couldn't really understand what was going on. After some thought on the matter, I'm not sure we can successfully tree shake the icons because the a dynamic apis used by some components to render icons are clashing with the static nature of ES imports used for tree shaking.

The icon used in some antd components is dynamically rendered (i.e. determined at runtime) based on a prop. For example, take a look at Button's api:

<Button icon="search">Search</Button>

In order for this component to render correctly, the Button component (or the underlying Icon component used) must have access to (at run-time) all icons, and doesn't allow for tree shaking.

The solution would be to have an API such as

import { 
  SearchIcon // not sure if this actually exists
} from '@antd/some-icons-package';

<Button icon={SearchIcon}>Search</Button> {/* option 1 - component */}
<Button icon={() => <SearchIcon />}>Search</Button> {/*option 2 - render prop */}

In this scenario, you're only importing what you need, and tree shaking can kick in and drop all unused exports from the example @antd/some-icons-package.

@jinliming2

This comment has been minimized.

Copy link

jinliming2 commented Mar 11, 2019

这个问题已经超过半年了,仍然没有官方的解决方案。目前我使用的解决方案是采用 import() 进行异步加载。
This problem has been more than half a year, and there is still no formal solution. The solution I'm currently using is to use import() for asynchronous loading.

使用到 React 官方文档中的推荐的 Loadable Components
Use the Loadable Components recommended in the official React documentation.

import React from 'react';
import ReactDOM from 'react-dom';
import { Icon } from 'antd';

ReactDOM.render(
  <Icon type="github" />,
  document.getElementById('root')
);

image
image

  • 第一步,安装依赖项:@loadable/component@babel/plugin-syntax-dynamic-import
    The first step is to install dependencies: @loadable/component and @babel/plugin-syntax-dynamic-import.
  • 第二步,在项目根目录创建一个文件夹 icons,并使用脚本将 @ant-design/icons 中的所有图标进行导出:
    The second step is to create a folder icons in the project root and export all the icons in @ant-design/icon using a script:
    #!/usr/bin/env node
    const fs = require('fs');
    const path = require('path');
    const util = require('util');
    const icons = require('@ant-design/icons');
    
    const fsMkdir = util.promisify(fs.mkdir);
    const fsWriteFile = util.promisify(fs.writeFile);
    
    // https://github.com/ant-design/ant-design-icons/blob/master/packages/icons-react/src/utils.ts#L94-L108
    const mapping = {
      fill: 'fill',
      outline: 'o',
      twotone: 'twotone',
    };
    const saveFlag = { encoding: 'utf8', mode: 0o644, flag: 'w' };
    
    (async () => {
      const dir = path.join(__dirname, 'icons');
      await fsMkdir(dir, 0o755);
      const processes = Object.values(icons).map(value => {
        if (value && value.name && value.theme) {
          // https://github.com/ant-design/ant-design-icons/blob/master/packages/icons-react/src/components/Icon.tsx#L38-L42
          const file = path.join(dir, `${value.name}-${mapping[value.theme]}.js`);
          const data = `export default ${JSON.stringify(value)}`;
          return fsWriteFile(file, data, saveFlag);
        }
      });
      await Promise.all(processes);
    })();
    直接在项目下使用 node 执行此脚本即可将 @ant-design/icons 中导出的所有图标存储到 icons 目录中。
    Execute this script directly under the project using the node to store all the icons exported from @ ant-design/icons in the icons folder.
  • 第三步,复制 Antd 代码仓库中的 components/icon/index.tsx 到项目根目录,命名为 Icon.jsxIcon.js,然后将代码中的 TypeScript 类型定义删除以得到纯 JavaScript 代码。(如果你的项目使用 TypeScript,也可以直接命名为 Icon.tsxIcon.ts,也就不用再删类型定义了)
    In the third step, copy components/icon/index.tsx in the Antd code repository to the project root, and named to Icon.jsx or Icon.js, then remove the TypeScript type definition in the code to get pure JavaScript code. (If your project uses TypeScript, you can also name it directly as Icon.tsx or Icon.ts, so you don't have to delete the type definition)
    然后修改这个代码文件,使用 import() 配合 @loadable/component 来按需加载图标文件。
    Then modify the code file and use import() with @loadable/component to load the icon file as needed.
    diff -- a/Icon.tsx b/Icon.jsx
    --- a/Icon.tsx
    +++ b/Icon.jsx
    @@ -1,70 +1,29 @@
    import * as React from 'react';
    import classNames from 'classnames';
    -import * as allIcons from '@ant-design/icons/lib/dist';
    +import loadable from '@loadable/component';
    import ReactIcon from '@ant-design/icons-react';
    -import createFromIconfontCN from './IconFont';
    +import createFromIconfontCN from 'antd/es/icon/IconFont';
    import {
      svgBaseProps,
      withThemeSuffix,
      removeTypeTheme,
      getThemeFromTypeName,
      alias,
    -} from './utils';
    +} from 'antd/es/icon/utils';
    -import warning from '../_util/warning';
    +import warning from 'antd/es/_util/warning';
    -import LocaleReceiver from '../locale-provider/LocaleReceiver';
    +import LocaleReceiver from 'antd/es/locale-provider/LocaleReceiver';
    -import { getTwoToneColor, setTwoToneColor } from './twoTonePrimaryColor';
    +import { getTwoToneColor, setTwoToneColor } from 'antd/es/icon/twoTonePrimaryColor';
    +
    +const AllIcons = loadable.lib(props => import(
    +  /* webpackChunkName: "icons/icon-" */
    +  `./icons/${props.type}.js`
    +));
    
    // Initial setting
    -ReactIcon.add(...Object.keys(allIcons).map(key => (allIcons as any)[key]));
    setTwoToneColor('#1890ff');
    -let defaultTheme: ThemeType = 'outlined';
    +let defaultTheme = 'outlined';
    -let dangerousTheme: ThemeType | undefined = undefined;
    +let dangerousTheme = undefined;
    -
    -export interface TransferLocale {
    -  icon: string;
    -}
    -
    -export interface CustomIconComponentProps {
    -  width: string | number;
    -  height: string | number;
    -  fill: string;
    -  viewBox?: string;
    -  className?: string;
    -  style?: React.CSSProperties;
    -  spin?: boolean;
    -  rotate?: number;
    -  ['aria-hidden']?: string;
    -}
    -
    -export type ThemeType = 'filled' | 'outlined' | 'twoTone';
    -
    -export interface IconProps {
    -  tabIndex?: number;
    -  type?: string;
    -  className?: string;
    -  theme?: ThemeType;
    -  title?: string;
    -  onKeyUp?: React.KeyboardEventHandler<HTMLElement>;
    -  onClick?: React.MouseEventHandler<HTMLElement>;
    -  component?: React.ComponentType<CustomIconComponentProps>;
    -  twoToneColor?: string;
    -  viewBox?: string;
    -  spin?: boolean;
    -  rotate?: number;
    -  style?: React.CSSProperties;
    -  prefixCls?: string;
    -  role?: string;
    -}
    -
    -export interface IconComponent<P> extends React.SFC<P> {
    -  createFromIconfontCN: typeof createFromIconfontCN;
    -  getTwoToneColor: typeof getTwoToneColor;
    -  setTwoToneColor: typeof setTwoToneColor;
    -  unstable_ChangeThemeOfIconsDangerously?: typeof unstable_ChangeThemeOfIconsDangerously;
    -  unstable_ChangeDefaultThemeOfIcons?: typeof unstable_ChangeDefaultThemeOfIcons;
    -}
    -
    -const Icon: IconComponent<IconProps> = props => {
    +const Icon = props => {
      const {
        // affect outter <i>...</i>
        className,
    @@ -107,7 +66,7 @@
        [`anticon-spin`]: !!spin || type === 'loading',
      });
    
    -  let innerNode: React.ReactNode;
    +  let innerNode;
    
      const svgStyle = rotate
        ? {
    @@ -116,7 +75,7 @@
          }
        : undefined;
    
    -  const innerSvgProps: CustomIconComponentProps = {
    +  const innerSvgProps = {
        ...svgBaseProps,
        className: svgClassString,
        style: svgStyle,
    @@ -165,12 +124,19 @@
          dangerousTheme || theme || defaultTheme,
        );
        innerNode = (
    -      <ReactIcon
    -        className={svgClassString}
    -        type={computedType}
    -        primaryColor={twoToneColor}
    -        style={svgStyle}
    -      />
    +      <AllIcons type={computedType}>
    +        {({ default: loadedIcon }) => {
    +          ReactIcon.add(loadedIcon);
    +          return (
    +            <ReactIcon
    +              className={svgClassString}
    +              type={computedType}
    +              primaryColor={twoToneColor}
    +              style={svgStyle}
    +            />
    +          );
    +        }}
    +      </AllIcons>
        );
      }
    
    @@ -181,7 +147,7 @@
    
      return (
        <LocaleReceiver componentName="Icon">
    -      {(locale: TransferLocale) => (
    +      {locale => (
            <i
              aria-label={type && `${locale.icon}: ${type}`}
              {...restProps}
    @@ -196,7 +162,7 @@
      );
    };
    
    -function unstable_ChangeThemeOfIconsDangerously(theme?: ThemeType) {
    +function unstable_ChangeThemeOfIconsDangerously(theme) {
      warning(
        false,
        'Icon',
    @@ -206,7 +172,7 @@
      dangerousTheme = theme;
    }
    
    -function unstable_ChangeDefaultThemeOfIcons(theme: ThemeType) {
    +function unstable_ChangeDefaultThemeOfIcons(theme) {
      warning(
        false,
        'Icon',
  • 最后一步,配置 Webpack:
    The final step is to configure Webpack:
    diff -- a/webpack.js b/webpack.js
    --- a/webpack.js
    +++ b/webpack.js
    @@ -1,4 +1,5 @@
     const path = require('path');
    +const webpack = require('webpack');
     const HtmlWebpackPlugin = require('html-webpack-plugin');
     const TerserPlugin = require('terser-webpack-plugin');
    
    @@ -33,12 +34,16 @@
       plugins: [
         new HtmlWebpackPlugin({
           title: 'Awesome Page',
           template: path.join(__dirname, 'public/index.html'),
           meta: {
             viewport: 'width=device-width, initial-scale=1',
           },
           hash: true,
         }),
    +    new webpack.NormalModuleReplacementPlugin(
    +      /node_modules\/antd\/es\/icon\/index\.js/,
    +      path.resolve(__dirname, 'Icon.jsx')
    +    ),
       ],
       resolve: {
         modules: ['node_modules'],
    @@ -56,13 +61,14 @@
           use: [{
             loader: 'babel-loader',
             options: {
               presets: [
                 ['@babel/preset-env', { targets: '> 2.486%, not dead' }],
                 '@babel/preset-react',
               ],
               plugins: [
    +            ['@babel/plugin-syntax-dynamic-import'],
                 ['@babel/plugin-proposal-class-properties'],
                 ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }],
               ],
             },
           }]

这个解决方案的优点是,你可以保留所有图标,在可配置后台管理系统中也可以任意修改图标而不用担心缺少图标;并且图标是按需加载的,只有开始渲染特定页面的图标时才会加载对应的图标文件。必须加载的只有打包后的 index 中有 50K 左右的图标列表数据(在 Icon.jsx 中的 webpack 魔术注释中指定更短的名字可以缩小这个体积)。
The advantage of this solution is that you can keep all the icons, you can also modify icons in the configurable background management system without worrying about the missing icons, and the icons are loaded on demand, only the icons needed to be rendered will be loaded when you start rendering a specific page. The packaged index file which must be loaded has only about 50K of icon list data (specifying a shorter name in the Webpack magic comment in Icon.jsx can reduce this size).

这个方案的缺点是,每一个图标都包含了除图标内容以外的额外的 Webpack 模块信息,因此可以看到 icons 文件夹的大小比之前整体打包的单文件大小要大很多,加上 index 中的 50K 图标列表数据,实际打包下来的尺寸接近 3M。但是由于实际上能使用到的图标来来回回也就那么几个、十几个,所以用户浏览器下载的数据量不会太大,并且由于是异步加载,只要单页面同时显示的图标数不多,对加载速度影响也就不大。
The downside of this solution is that each icon contains Webpack module information in addition to the icon content, so you can see that the size of the icons folder is much larger than the size of the single file packaged before, and 50K of icon list data in the index file, the actual packaged size is close to 3M. However, because the icons that can actually be used are only a few or a dozen, the amount of data downloaded by the user's browser is not too large, and since it is asynchronously loaded, as long as the number of icons displayed on a single page is not more, the impact on the loading speed is not large.

@muzea

This comment has been minimized.

Copy link
Contributor

muzea commented Mar 11, 2019

@jinliming2 很酷炫的修改,才发现webpack的import其实可以传变量的

Fully dynamic statements, such as import(foo), will fail because webpack requires at least some file location information. This is because foo could potentially be any path to any file in your system or project. The import() must contain at least some information about where the module is located, so bundling can be limited to a specific directory or set of files.

居然只要有一个目录的前缀就可以了,之前以为是不行的。
话说回来,antd用户也不是只用webpack,这就有点尴尬了。

@zachguo

This comment has been minimized.

Copy link
Contributor

zachguo commented Mar 11, 2019

@aryzing It seems the option 1 of your proposed APIs are actually what #12888 is trying to achieve. A lot of breaking changes would be involved, so probably we still need to wait for Ant Design team to finish it.

@ShanaMaid

This comment has been minimized.

Copy link

ShanaMaid commented Mar 13, 2019

may consider using xhr to load svg asynchronously?
archer-svgs

@Beven91

This comment has been minimized.

Copy link

Beven91 commented Mar 14, 2019

由于ant deisgn本身组件会使用到较多的图标,所以是否考虑将图标文件改为异步加载方案,这样就不用占用主文件体积,可以参考下这个:

Since ant deisgn itself will use more icons, so consider changing the icon file to an asynchronous loading scheme, so that you don't need to occupy the main file size, you can refer to this:

webpack-ant-icon-loader

@IssuehuntBot

This comment has been minimized.

Copy link

IssuehuntBot commented Mar 14, 2019

@rororofff has funded $2.00 to this issue.


@AustinGreen

This comment has been minimized.

Copy link

AustinGreen commented Mar 15, 2019

For anyone who is intimidated by the length of this conversation and just wants to use create-react-app and ant-design, I created a boilerplate project here https://github.com/AustinGreen/cra-antd-starter

This includes a fix for the icon bundle size problem, and a custom webpack config without ejecting. Thanks to @ndbroadbent, @patricklafrance, and the whole ant-design team for all their hard work.

@saostad

This comment has been minimized.

Copy link

saostad commented Mar 19, 2019

For anyone who is intimidated by the length of this conversation and just wants to use create-react-app and ant-design, I created a boilerplate project here https://github.com/AustinGreen/cra-antd-starter

This includes a fix for the icon bundle size problem, and a custom webpack config without ejecting. Thanks to @ndbroadbent, @patricklafrance, and the whole ant-design team for all their hard work.

How can I use it in Nextjs app?

@anjmao

This comment has been minimized.

Copy link

anjmao commented Mar 20, 2019

@AustinGreen I tried your example.

image

For an app with sidebar and one button to have a size of ~100kB Gzipped is still way too much.

@AustinGreen

This comment has been minimized.

Copy link

AustinGreen commented Mar 20, 2019

@anjmao if there is a way to further reduce the bundle size please let me know and I'll add it in. If you're building an enterprise web application, ~100kB Gzipped for a front-end framework, ~5 components (from a comprehensive component library), icons, and other utilities is pretty standard.

@Oscar-ren

This comment has been minimized.

Copy link

Oscar-ren commented Mar 21, 2019

@anjmao if there is a way to further reduce the bundle size please let me know and I'll add it in. If you're building an enterprise web application, ~100kB Gzipped for a front-end framework, ~5 components (from a comprehensive component library), icons, and other utilities is pretty standard.

#12011 (comment) . Here's a good solution

@AustinGreen

This comment has been minimized.

Copy link

AustinGreen commented Mar 21, 2019

@Oscar-ren the project is already using an alias for icons (which is why the bundle size is only ~100kB and not ~400kB)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.