16
loading...
This website collects cookies to deliver better user experience
something-big-and-bloated
npx create-react-app something-big-and-bloated --template typescript
npm install @craco/craco webpack-bundle-analyzer --save-dev
craco
in the root of our project to include our webpack plugin:craco.config.js
const BundleAnalyzerPlugin =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports = function () {
return {
webpack: {
plugins: [new BundleAnalyzerPlugin({ analyzerMode: "server" })],
},
};
};
npm run build
script for a production build, it will use the standard react-scripts
method. craco build
it will still run the same process, but inject in any webpack configuration you have included in your craco.config.js
file. Pretty sweet. package.json
scripts:{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"analyze": "craco build"
}
}
npm run analyze
analyzerMode
to "server"
in our craco config, we will automatically get our browser open with the results served as a webpage (you can use the "json"
option if you want the output without involving the browser)create-react-app
v4.0.3 for this tutorial the values I get are 205kb / 135kb / 44kb for stat / parsed / gzipped respectively. You can see right out of the box we get a bit of overhead (though for most users that's a small price to pay for convenience). @mui/material
.@mui/material
.
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled --save
npm run analyze
src
:src/CustomSpeedDial.tsx
import React from "react";
import Box from "@mui/material/Box";
import SpeedDial from "@mui/material/SpeedDial";
import SpeedDialIcon from "@mui/material/SpeedDialIcon";
import SpeedDialAction from "@mui/material/SpeedDialAction";
import FileCopyIcon from "@mui/icons-material/FileCopyOutlined";
import SaveIcon from "@mui/icons-material/Save";
import PrintIcon from "@mui/icons-material/Print";
import ShareIcon from "@mui/icons-material/Share";
const actions = [
{ icon: <FileCopyIcon />, name: "Copy" },
{ icon: <SaveIcon />, name: "Save" },
{ icon: <PrintIcon />, name: "Print" },
{ icon: <ShareIcon />, name: "Share" },
];
export default function CustomSpeedDial() {
return (
<Box sx={{ height: 320, transform: "translateZ(0px)", flexGrow: 1 }}>
<SpeedDial
ariaLabel="SpeedDial basic example"
sx={{ position: "absolute", bottom: 16, left: 16 }}
icon={<SpeedDialIcon />}
>
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
/>
))}
</Box>
);
}
App.tsx
file with the following:src/App.tsx
import React from "react";
import CustomSpeedDial from "./CustomSpeedDial";
function App() {
return <CustomSpeedDial />;
}
export default App;
npm run start
npm run analyze
src/CustomSpeedDial.tsx
:import React from "react";
import Box from "@mui/material/Box";
import SpeedDial from "@mui/material/SpeedDial";
import SpeedDialIcon from "@mui/material/SpeedDialIcon";
import SpeedDialAction from "@mui/material/SpeedDialAction";
import FileCopyIcon from "@mui/icons-material/FileCopyOutlined";
import SaveIcon from "@mui/icons-material/Save";
import PrintIcon from "@mui/icons-material/Print";
import ShareIcon from "@mui/icons-material/Share";
// NEW
import Button from "@mui/material/Button";
const actions = [
{ icon: <FileCopyIcon />, name: "Copy" },
{ icon: <SaveIcon />, name: "Save" },
{ icon: <PrintIcon />, name: "Print" },
{ icon: <ShareIcon />, name: "Share" },
];
export default function CustomSpeedDial() {
return (
<Box sx={{ height: 320, transform: "translateZ(0px)", flexGrow: 1 }}>
{/* NEW */}
<Button variant="contained">Hello world!</Button>
<SpeedDial
ariaLabel="SpeedDial basic example"
sx={{ position: "absolute", bottom: 16, left: 16 }}
icon={<SpeedDialIcon />}
>
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
/>
))}
</SpeedDial>
</Box>
);
}
npm run analyze
again we get.... almost the same! 677kb / 278kb / 89kb. We can see that the button extremely little to the bundle size since most of the building blocks for it were already included with the Speed Dial. CustomSpeedDial
component (if ESLint complains about import order, place the line after all your imports statements)src/CustomSpeedDial.tsx
const material = require("@mui/material");
npm run analyze
require
in our CustomSpeedDial
we're going to leave it there, and introduce something called code spltting as another option available to you.App.tsx
. There's a lot to unpack here, so we'll just show the code first and break it down:src/App.tsx
import CircularProgress from "@mui/material/CircularProgress";
import Button from "@mui/material/Button";
import React, { Suspense, useState } from "react";
// 1
const CustomSpeedDial = React.lazy(() => import("./CustomSpeedDial"));
function App() {
// 2
const [showSpeedDial, setShowSpeedDial] = useState(false);
// 4
if (showSpeedDial) {
return (
// 5
<Suspense fallback={<CircularProgress />}>
<CustomSpeedDial />
</Suspense>
);
}
return (
// 3
<Button variant="contained" onClick={() => setShowSpeedDial(true)}>
Click to load speed dial
</Button>
);
}
export default App;
CustomSpeedDial
module. Remember that's the one that uses require
for the entire MUI package and comes in at 1-2MB. By using the lazy import, what happens is that the import only occurs when our primary component here (CustomSpeedDial) actually tries to render it. We'll see that it doesn't by default.
false
value means we will not be rendering CustomSpeedDial
Button
imported directly from MUI. When this button is pressed it sets the value of showSpeedDial
to true.showSpeedDial
is true we take this branch at the next render. The reason we get a re-render is because we updated a stateful React value (showSpeedDial).Suspense
component is to tells React what to render while waiting for the module to import. Depending on the size it could take a second or more. For our example we are using MUI's CircularProgress
to imply a loading state while the module is loading. Once it loads it switches to render the children of the Suspense component.
npm run analyze
.js
chunks when you toggle the drawer on the left.3.5d1a4e88.chunk.js
(1.52mb / 475kb / 122kb) isn't even used on the default load of our app. Based on our learnings earlier, we can see that huge chunk is definitely our CustomSpeedDial.tsx
component that imports all of MUI with require
commonJS import. 2.c5828938.chunk.js
that includes things like Button
and ButtonBase
. This is the chunk that will load on every page load. We can look at the size (451kb / 214kb / 69kb) and validate that in a moment. bpm run build
build
directory that was created. If you have your own local serve you prefer to use, use it! If not just add the serve package:npm install serve --save-dev
build
directory:npx serve build
c5828938
chunk we identified (remember this is a served production build, so we are working with GZIP sizes just like your real app would for real users) CustomSpeedDial
component inside of it. craco
to allow us to configure webpack. Any application running webpack can potentially benefit from these tips!craco
(although personally I feel it's not as intuitive to read the data) it still gets the job done well. Read about it here.