Move fontawesome to own bundle using dynamic import - reactjs

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

Related

After updating the query-string library, test:ci now fails

The development environment uses next.js 13.
After updating the query-string library to 8.1, test:ci now fails.
It fails at the following point.
before "query-string": "^7.1.0",
after "query-string": "^8.1.0",
error
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import * as queryString from './base.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Changed part.
before
import { stringifyUrl } from 'query-string';
.
. omission
.
stringifyUrl({url})
after
import queryString from 'query-string';
.
. omission
.
queryString.stringifyUrl({ url })
I am very troubled.
If anyone knows how to solve this problem, please let me know.
added
module.exports = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!**/*.d.ts'],
moduleDirectories: ['node_modules', 'src'],
moduleNameMapper: {
// Handle CSS imports (with CSS modules)
// https://jestjs.io/docs/webpack#mocking-css-modules
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
// Handle CSS imports (without CSS modules)
'^.+\\.(css|sass|scss)$': '<rootDir>/src/__mocks__/styleMock.ts',
// Handle image imports
// https://jestjs.io/docs/webpack#handling-static-assets
'^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i': `<rootDir>/src/__mocks__/fileMock.ts`,
// Handle ESM packages
'^react-markdown$': '<rootDir>/src/__mocks__/react-markdown.tsx',
},
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
testEnvironment: 'jest-environment-jsdom',
transform: {
// Use babel-jest to transpile tests with the next/babel preset
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
},
transformIgnorePatterns: [
'/node_modules/',
'^.+\\.module\\.(css|sass|scss)$',
],
};
The issue is query-string version 8 introduced a breaking to consuming applications because their dependencies upgraded to ESM. See their release notes here:
v8.0.0
Breaking
Require Node.js 14
This package is now pure ESM. Please read this.
Add "module": "node16", "moduleResolution": "node16" to your
tsconfig.json.
(Example)
And more!!!
It appears Jest in trying to use import but it's not configured to do so. Jest also provides some guidance for how to deal with this: https://jestjs.io/docs/ecmascript-modules
Since you're using Next.js, those steps don't really apply.
I'm reproducing and testing in a sandbox... Will update shorty.
For React apps:
Try setting "type": "module" inside package.json.
You should also update your package.json test script to:
"test": "node --experimental-vm-modules ./node_modules/.bin/jest"
Now inside jest.config.js you'll want to export transform: {}:
export default {
transform: {}
}
After all those steps, you should no longer have this issue.

Tree shaking not working for the react component library using named imports

Our company projects count is growing, so we decided to move our ui kit to a separate private npm package. For building kit before publishing we decided to use rollup.
The file structure for ui kit is quite standard we've:
src
- Components
- Alert
- Button
- Heading
- ...
HOC, static...
- index.js
Root index.js file is used only for reexporting all components.
export {default as Alert} from './Components/Alert';
export {default as Button} from './Components/Button';
...
Our main goal is to be able to get components from our kit using named imports.
import {Alert, Button} from '#company/ui-kit';
Properly working tree shaking is also important, because some of our projects are using only 2/31 components from the kit, so we want to see in final build only components what has been used and this is the main challenge. We tried many options of rollup config with single output file or chunks and realized, that tree shaking only works with chunks and what's more interesting it works only with importing chunk directly, not named import from the index.js.
import Alert from '#company/ui-kit/Alert'; // tree shaking works, only Alert get into build
import {Alert} from '#company/ui-kit'; // tree shaking not working, all kit components are got into the build
Could somebody explain me why this is happening? And is there any way to use named imports with properly working tree shaking. What is also very curious, that we tried to test tree shaking with named imports in some popular libraries like material ui and there it's not working too. Tree shaking works only with direct import from chunk.
Also need to mention that we tested tree shaking only in projects created with create-react-app. I don't really think the problem could be inside create-react-app bundler config, but maybe it will help to figure out this.
For the full picture also attaching our rollup config:
import multiInput from 'rollup-plugin-multi-input';
import babel from 'rollup-plugin-babel';
import commonjs from 'rollup-plugin-commonjs';
import external from 'rollup-plugin-peer-deps-external';
import resolve from 'rollup-plugin-node-resolve';
import copy from 'rollup-plugin-copy'
import postcss from 'rollup-plugin-postcss';
import postcssUrl from 'postcss-url';
import asset from "rollup-plugin-smart-asset";
export default {
input: ['src/index.js', 'src/Components/**/*.js'],
output: [
{
dir: './',
format: 'cjs',
sourcemap: true,
exports: 'auto',
chunkFileNames: '__chunks/[name]_[hash].js'
}
],
plugins: [
multiInput({relative: 'src/'}),
external(),
postcss({
modules: false,
extract: true,
minimize: true,
sourceMap: true,
plugins: [postcssUrl({url: 'inline', maxSize: 1000})],
}),
copy({targets: [{src: 'src/static/icons/fonts/*', dest: 'fonts'}]}),
babel({exclude: 'node_modules/**'}),
asset({url: 'copy', keepImport: true, useHash: false, keepName: true, assetsPath: 'assets/'}),
resolve(),
commonjs({include: 'node_modules/**'})
]
};
Thanks in advance for your replies!

How to make cherry picking react component library like lodash & date/fns

I have made one small library which includes 20-25 different components & made an npm package of it.
my react project, where I want to use these components, has many pages[routes] used lazy-loading so each page has its own bundle.
but when I write the statement on my home page[App.js].
import { MyModal } from 'my-react-lib';
each and every component is loaded into home page bundle. so my initial loading performance is worst. [2Mb initial bundle size]
I have read the concept of tree shaking and event tried to implement in webpack & even with rollup but they only make bundle.js but not as per mine requirement.
I am willing to achieve cherry-picking like import-export. same as date-fns & lodash does.
import format from 'date-fns/format';
import debounce from 'lodash/debounce';
how to achieve this?
import MyModal from 'my-react-lib/MyModal';
import OtherComponent from 'my-react-lib/OtherComponent';
Rollup allows you to split your library into several independent chunks. You have to provide an object, mapping names to entry points, to the input property of the rollup configuration. It looks like this:
input: {
index: 'src/index.js',
theme: 'src/Theme',
badge: 'src/components/Badge',
contentCard: 'src/components/ContentCard',
card: 'src/elements/Card',
icon: 'src/elements/Icon',
...
src/index.js looks like this:
export { default as Theme } from './Theme'
export { default as Badge } from './components/Badge'
...
Have a look at rollup’s documentation: https://rollupjs.org/guide/en/#input
The output is set to a directory:
output: [
{
dir: 'dist/es',
format: 'es',
},
],
Then you declare the entry point in your package.json as follows:
"module": "dist/es/index.js",
The modules of your library can then be imported:
import { Theme, Badge } from 'your-component-library'
You may need to package them separately.
For example the Material-UI, there are many parts of it. When we use it in the normal way
npm install #material-ui/core
If you look at their source, you would find out that there are multiple packages, each with it's own package.json file
For example the #material-ui/core pacakge.json
{
"name": "#material-ui/core",
"version": "4.9.7",
"private": false,
"author": "Material-UI Team",
...
"dependencies": {
"#babel/runtime": "^7.4.4",
"#material-ui/styles": "^4.9.6",
"#material-ui/system": "^4.9.6",
"#material-ui/types": "^5.0.0",
"#material-ui/utils": "^4.9.6",
Which means that they are actually separate and have dependencies with each other.
Well, that's the scope.

storybook #storybook/addon-options dosent work

I am trying to set some option but it doesn't work.
package.json
"devDependencies": {
"#storybook/addon-actions": "^3.4.10",
"#storybook/addon-links": "^3.4.10",
"#storybook/addon-options": "^3.4.11",
"#storybook/addon-storyshots": "^3.4.10",
"#storybook/addons": "^3.4.10",
"#storybook/react": "^3.4.11"
}
addons.js
import '#storybook/addon-options/register';
config.js
import { configure } from '#storybook/react';
import { setOptions } from '#storybook/addon-options';
setOptions({ name: 'my name' });
// automatically import all files ending in *.stories.js
const req = require.context('../stories', true, /.stories.js$/);
function loadStories() {
req.keys().forEach((filename) => req(filename));
}
configure(loadStories, module);
I am trying to understand where i s, going wrong for a few hours.
Edit after testing with provided versions:
I tested this locally with the above versions, and what it looks like is that because #storybook/react and #storybook/addons are on different versions, even by a patch, #storybook/react ends up installing it's own addons dependency and the 2 become out of sync.
If this is the case, you will likely see an error like "accessing nonexistent addons channel" in the console.
To fix this you will need to increase the version of the addons dependency to v3.4.11 AND reinstall the react dependency.
npm install --save-dev #storybook/addons#3.4.11 #storybook/react#3.4.11
Note: If you only update addons to v3.4.11 without reinstalling the react dependency, it won't fully sync up, because the react would already have installed it's own addon subdependency.
An image of the filesystem within node_modules directoy:
Previously Answered for Storybook v4:
According to the documentation, you have to apply the settings as a decorator (and there is no setOptions function, but there is a withOptions function).
So try this:
import { addDecorator, configure } from '#storybook/react';
import { withOptions } from '#storybook/addon-options';
addDecorator(withOptions({ name: 'my name' }));
// rest of the config goes here
Also, make sure to register the addon by adding the following line in your addons.js file:
import '#storybook/addon-options/register';
Reference: https://www.npmjs.com/package/#storybook/addon-options
Aside: The reason it has to be within a decorator is because the UI of storybook is rendered as part of each story, so you can't just set options globally without having each story apply those settings.

ant design - huge imports

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

Resources