ant design - huge imports - reactjs

I'm using ant design library for my react application.
And I've faced with huge imports, that hurts my bundle (currently 1.1 mb in minified version because of ant-design lib).
How can I differently import antd components through all my app?
UPDATE:
Seems antd has some huge or non optimized modules.
Here the thing - only difference is import Datepicker module, and.. boom! + almost 2MB (in dev bundle ofc.)

UPD: the underlying issue seems to be resolved for the new (4.0) version of antd.
Therefore, if you try to resolve this issue for the earlier versions, the recommended way is to migrate onto antd 4
Previous answer:
At the moment, a huge part of antd dist is SVG icons.
There is no official way to deal with it yet (check the issue on github).
But a workaround exists.
Adapt webpack to resolve icons differently. In your webpack config:
module.exports = {
//...
resolve: {
alias: {
"#ant-design/icons/lib/dist$": path.resolve(__dirname, "./src/icons.js")
}
}
};
Create icons.js in the folder src/ or wherever you want it. Be sure it matches the alias path!
In this file, you define which icons antd should include.
export {
default as DownOutline
} from "#ant-design/icons/lib/outline/DownOutline";
It's also possible to do this with react-app-rewired (create-react-app modifications) within config-overrides.js

1) Prevent antd to load the all moment localization.
Add webpack plugin and configure it in webpack.config.js like the follow:
plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /ru/),
],
resolve: {
alias: {moment: `moment/moment.js`}
},
target: `web`
}
2) Use the same moment version as in antd library.
3) Use modularized antd
Use babel-plugin-import
// .babelrc or babel-loader option
{
"plugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
// `style: true` for less
]
}
I use BundleAnalyzerPlugin to analyze the bundle.
plugins: [new BundleAnalyzerPlugin()]

Looking at the docs
https://ant.design/docs/react/getting-started#Import-on-Demand
there is a recommedation to import individual components on demand.
So, you can try and replace
import { Button} from 'antd'
with
import Button from 'antd/lib/button'

I reduced my bundle size by 500KB by editing config-override.js like so:
config-override.js
const { override, fixBabelImports } = require('customize-cra');
const path = require('path');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css'
}),
// used to minimise bundle size by 500KB
function(config, env) {
const alias = config.resolve.alias || {};
alias['#ant-design/icons/lib/dist$'] = path.resolve(__dirname, './src/icons.js');
config.resolve.alias = alias;
return config;
}
);
./src/icons.js
/**
* List all antd icons you want to use in your source code
*/
export {
default as SearchOutline
} from '#ant-design/icons/lib/outline/SearchOutline';
export {
default as CloseOutline
} from '#ant-design/icons/lib/outline/CloseOutline';
export {
default as QuestionCircleOutline
} from '#ant-design/icons/lib/outline/QuestionCircleOutline';
export {
default as PlayCircleOutline
} from '#ant-design/icons/lib/outline/PlayCircleOutline';
export {
default as PauseCircleOutline
} from '#ant-design/icons/lib/outline/PauseCircleOutline';
export {
default as LoadingOutline
} from '#ant-design/icons/lib/outline/LoadingOutline';
Before
After

Those few components are certainly not 1.2M together. Looks like you are importing the whole library when you only need a few components.
To get antd to load only the needed modules you should use babel-plugin-import. Check your console log for the "You are using a whole package of antd" warning described at that link.
Check out the docs for Create-React-App for how to implement it if you're using CRA.

Try using code splitting using webpack and react router. It will help you to load the modules asynchronously. This is the only solution helped me to improve the page load time when using ant framework.

Issue which caused large bundle size has been fixed in Ant Design 4.0.
Quoting from the release announcement.
Smaller size
In antd # 3.9.0, we introduced the svg icon ([Why use the svg icon?]
()). The icon API
using the string name cannot be loaded on demand, so the svg icon file
is fully introduced, which greatly increases the size of the packaged
product. In 4.0, we adjusted the icon usage API to support tree
shaking, reducing the default package size of Antant by about 150 KB
(Gzipped).
In order to install Ant Design 4 you have to do following
npm install antd#4.0.0-rc.1
// or in yarn
yarn add antd#4.0.0-rc.1

Related

Swiper 8 and jest

Swiper 8 and Jest (support ESM) Must use import to load ES Module
enter image description here
enter image description here
How we can solve if I need to keep swiper 8 (without downgrade)
In case someone runs into this same issue but is using NextJs 12.2, next/jest and Jest 28 the answer is a variation from Francisco Barros' answer.
// jest.config.js
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Path to Next.js app to load next.config.js
dir: './'
})
/** #type {import('#jest/types').Config.InitialOptions} */
const customJestConfig = {
/**
* Custom config goes here, I am not adding it to keep this example simple.
* See next/jest examples for more information.
*/
}
module.exports = async () => ({
/**
* Using ...(await createJestConfig(customJestConfig)()) to override transformIgnorePatterns
* provided byt next/jest.
*
* #link https://github.com/vercel/next.js/issues/36077#issuecomment-1096635363
*/
...(await createJestConfig(customJestConfig)()),
/**
* Swiper uses ECMAScript Modules (ESM) and Jest provides some experimental support for it
* but "node_modules" are not transpiled by next/jest yet.
*
* #link https://github.com/vercel/next.js/issues/36077#issuecomment-1096698456
* #link https://jestjs.io/docs/ecmascript-modules
*/
transformIgnorePatterns: [
'node_modules/(?!(swiper|ssr-window|dom7)/)',
]
})
The transformIgnorePatterns on jest.config.js prevents the Swiper files from being transformed by Jest but it affects the CSS files that are provided by this package.
Mocking these CSS files is the solution with the smallest configuration.
In my case, it didn't matter much about having access to these CSS files while testing but there are other approaches if these files are relevant to you.
// jest.setup.js
import "#testing-library/jest-dom/extend-expect";
jest.mock("swiper/css", jest.fn());
export {};
I created a repository for a full reference of the necessary setup.
https://github.com/markcnunes/with-jest-and-esm
Have in mind this setup might have to change for future Next.js / next/js versions but just sharing this approach in case this is useful for other people using this same setup.
I have the same issue and still searching for a solution; I believe what the OP is asking is how can we transform swiper/react into a CJS module on the JEST pipeline.
Since using ESM Experimental in jest is not a good option...
Downgrading to v6 is not an option;
Any code that imports Swiper v8 fails in JEST because Swiper 8 only exports ESM;
A few days have passed since my original response. In the mean time I have found a solution that I have been using to effectively use Swiper v8 while still being able to test components that depend on it using jest.
The trick is to map the ESM imports to actual JavaScript and CSS files exported by Swiper, which can then be processed by babel and transpiled into CJS.
Create a file
// ./mocks/misc/swiper.js
module.exports = {
// Rewrite Swiper ESM imports as paths (allows them to be transformed w/o errors)
moduleNameMapper: {
"swiper/react": "<rootDir>/node_modules/swiper/react/swiper-react.js",
"swiper/css": "<rootDir>/node_modules/swiper/swiper.min.css",
"swiper/css/bundle": "<rootDir>/node_modules/swiper/swiper-bundle.min.css",
"swiper/css/autoplay": "<rootDir>/node_modules/swiper/modules/autoplay/autoplay.min.css",
"swiper/css/free-mode": "<rootDir>/node_modules/swiper/modules/autoplay/free-mode.min.css",
"swiper/css/navigation": "<rootDir>/node_modules/swiper/modules/autoplay/navigation.min.css",
"swiper/css/pagination": "<rootDir>/node_modules/swiper/modules/autoplay/pagination.min.css"
},
// Allow Swiper js and css mapped modules to be imported in test files
transform: {
"^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }],
"^.+\\.(css)$": "<rootDir>/jest.transform.js"
},
// Do not transform any node_modules to CJS except for Swiper and Friends
transformIgnorePatterns: ["/node_modules/(?!swiper|swiper/react|ssr-window|dom7)"]
}
Create another file in the root of your repository
// jest.transform.js
"use strict"
const path = require("path")
// Requried to fix Swiper CSS imports during jest executions, it transforms imports into filenames
module.exports = {
process: (_src, filename) => `module.exports = ${JSON.stringify(path.basename(filename))};`
}
Finally in your jest configuration use the things you created
// jest.config.js
const swiperMocks = require("./mocks/misc/swipper");
module.exports = {
...yourConfigurations
moduleNameMapper: {
...yourOtherModuleNameMappers,
...swiperMocks.moduleNameMapper,
},
transform: {
...yourOtherTransforms
...swiperMocks.transform,
},
transformIgnorePatterns: [
...yourOtherTransformsIgnorePatterns
...swiperMocks.transformIgnorePatterns,
],
}

SCSS doesn't load on Gatsby

i try to load the SASS for my Gatsby Project but if I check the source code of the web there isn't any classes from my sass.
I am a bit confused and I followed the documentation of Gatsby.
Nothing worked so my last chance is SO.
// gatsby-config.js
plugins: [
'gatsby-plugin-react-helmet',
'gatsby-plugin-fontawesome-css',
'gatsby-plugin-sass',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'assets',
path: `${__dirname}/static/`,
},
},
]
Here I import the style.
/**
* Add browser relation logic.
*/
require('./style/global.js');
import './style/sass/index.scss';
I followed the gatsby-plugin-sass documentation and I should be all set. After restarting the server and show source-code of my app there is now class name from my sass file.
Best Regards
Knome
I didn't integrate in any component. Because if I see the Source code
of chrome then there should be scss be loaded.
Ok, well... The SCSS is loaded as it should but the styles are not applied to any component because you've not set any class name.
Just do:
const IndexPage =()=>{
return <div className="grid-container">I'm your index page</div>
}
Like any other HTML element.

Storybook - no stories showing up in typescript project with custom webpack / babel

I am trying to set up Storybook in a project. My project is runing on react#^16, and I'm using typescript, with a custom babel and webpack setup for development and build. To set up storybook, I did
npx sb init
This installs everything needed. It puts a .storybook folder in the root folder, and a stories folder in my src folder with some prefab components and stories in tsx format (which is what I want):
The .storybook/main.js file seems fine:
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials"
]
}
And the average .stories.js file automatically installed by npx sb init also seems fine:
import React from 'react';
// also exported from '#storybook/react' if you can deal with breaking changes in 6.1
import { Story, Meta } from '#storybook/react/types-6-0';
import { Header, HeaderProps } from './Header';
export default {
title: 'Example/Header',
component: Header,
} as Meta;
const Template: Story<HeaderProps> = (args) => <Header {...args} />;
export const LoggedIn = Template.bind({});
LoggedIn.args = {
user: {},
};
export const LoggedOut = Template.bind({});
LoggedOut.args = {};
But when I run npm run storybook, the storybook landing page has no stories. Even though it had installed some default stories to start playing with. It says:
Oh no! Your Storybook is empty. Possible reasons why:
The glob specified in main.js isn't correct.
No stories are defined in your story files.
As requested, here is a link to the repo so you can dig a bit deeper into the structure, weback config, etc. Note I have not committed the npx sb init changes yet, so you won't see the files there, only my starting point just before running the sb init.
I haven't had any issues getting npx sb init to work with a standard create-react-app, but with my custom webpack build and typescript, its just empty. What's going wrong?
Edit: Additional detail
I realize that just running npx sb init, then npm run storybook throws this error:
ERROR in ./.storybook/preview.js-generated-config-entry.js
Module not found: Error: Can't resolve 'core-js/modules/es.array.filter'
Based on this thread, installing core-js#3 solves the problem and storybook runs, though with no stories.
It seems like the babel plugin transform-es2015-modules-amd doesn't fit right with storybook since sb still uses your babel configuration.
You might need to remove it then it would work:
{
"plugins": [
// "transform-es2015-modules-amd", // Remove this plugin
]
}
If you want to have a special babel configuration for storybook, place it .storybook/.babelrc so the configuration would be simple like this:
.storybook/.babelrc:
{
"presets": ["#babel/preset-env", "#babel/preset-react", "#babel/preset-typescript"]
}
NOTE: You might miss to forget install #babel/preset-typescript to help you transform your typescript code.
Maybe you have problems with the stories path, try to save only "../src/**/*.stories.js" in your config to see if its the reason
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx)"
]
In case of dealing with arcgis-js-api in sb, you have to declare #arcgis/webpack-plugin in storybook's webpack configuration by adding to its config.
Here are a few steps you have to do:
Add webpackFinal property in .storybook/main.js with following content:
const ArcGISPlugin = require('#arcgis/webpack-plugin');
module.exports = {
// ...
webpackFinal: (config) => {
// Add your plugin
config.plugins.push(
new ArcGISPlugin(),
);
// Since this package has used some node's API so you might have to stop using it as client side
config.node = {
...config.node,
process: false,
fs: "empty"
};
return config;
}
};
One more thing to be aware of, some components are importing scss files, so you might need to support it by adding a scss addon '#storybook/preset-scss'
// Install
npm i -D #storybook/preset-scss css-loader sass-loader style-loader
// Add to your current addons
{
addons: ['#storybook/addon-links', '#storybook/addon-essentials', '#storybook/preset-scss'],
}
Like a tmhao2005 say. Storybook still uses your babel configuration. And this is the intended behavior. This thread at github also describes how the fix similar issue.
Updated your config .storybook/main.js.
If you use .babelrc:
babel: async options => ({ ...options, babelrc: false })
Or .babel.config.js:
babel: async options => ({ ...options, configFile: false })

Move fontawesome to own bundle using dynamic import

I am trying to reduce my bundle size by splitting it into several chunks.
BundleAnalyzerPlugin tells me that Fontawesome is imported in full even though I have tried to only import the icons I need which seems odd.
My fontawesome imports
import { library } from '#fortawesome/fontawesome-svg-core';
import { faBell, faEyeSlash, faEye} from '#fortawesome/free-solid-svg-icons';
import { faBell as regularBell} from '#fortawesome/free-regular-svg-icons';
...
library.add( faBell, faEye, faEyeSlash, regularBell, regularCalendarAlt)
My fontawesome version
"#fortawesome/fontawesome": "^1.1.5",
"#fortawesome/fontawesome-free-regular": "^5.0.10",
"#fortawesome/fontawesome-free-solid": "^5.0.10",
"#fortawesome/fontawesome-svg-core": "^1.2.8",
"#fortawesome/react-fontawesome": "^0.1.3",
I am trying to use the import() splitting technique described here but can't figure out how to make it work for Fontawesome.
return import(/* webpackChunkName: "lodash" */ 'lodash').then(({ default: _ }) => {
...
}
How can I put split fontawesome into it's own bundle and load it synchronously?
Something along the lines of Prefetching/Preloading would work too, but those techniques seems to have very poor browser support? I sadly need support for Safari.
My webpack config
// Set up webpack plugins
config.plugins = [
nodeEnvPlugin,
firebasePlugin,
cssExtractPlugin,
new BundleAnalyzerPlugin(
{
excludeAssets: /node_modules/,
statsOptions: {
exclude: /node_modules/
}
}
)
]
Kind regards /K
You're trying to view node_modules which shouldn't be part of your bundle upon deployment. You should be configuring your BundleAnalyzerPlugin to view the destination of webpack output instead. That way you could fully identify which are consuming big in terms of size.
Here's the option about it from your library of choice:
https://github.com/webpack-contrib/webpack-bundle-analyzer#bundledir
Here is mine implementing the same technique you have above on fontawesome optimization
Here's my repo for your reference: https://github.com/crrmacarse/crrmacarse.github.io

Trying to add google fonts to a react / neutrino project

trying Neutrino for the first time to jumpstart a react project. Want to add google fonts to it; tried passing google URLS in as a links array in config/html but no joy. suggestions?
There are two ways you could load Google fonts into your Web-based Neutrino project.
The easiest would probably be to install the font you would like from npm, such as Works Sans:
npm install --save typeface-work-sans
Which you can then import into your project with:
import 'typeface-work-sans';
The second method would involve the links way you mentioned, by adding an external stylesheet to your local Web-based .neutrinorc.js (using react for this example):
// .neutrinorc.js
module.exports = {
use: [
['#neutrinojs/react', {
html: {
links: [
{
href: 'https://fonts.googleapis.com/css?family=Work+Sans',
rel: 'stylesheet'
}
]
}
}]
]
};

Resources