Introduction
Let’s talk about why optimization is necessary. If your project is small and builds quickly, you might not need to worry too much about performance optimization. However, as your project grows with more pages, features, and business logic, the build time of webpack, the underlying build tool, also increases. At this point, optimizing performance becomes crucial.
Webpack offers various avenues for performance optimization, which can be broadly categorized into two areas:
Optimization One: Optimizing the built result for production, focusing on performance during deployment (e.g., code splitting, reducing bundle size, using CDN servers, etc.).
Optimization Two: Optimizing build speed for development or production build, enhancing the speed of the build process (e.g., using exclusion, cache loaders, etc.).
The performance during production directly affects user experience, whereas build time is closely related to developers’ daily workflow. If the local development server or production build takes too long, it significantly hampers productivity.
Performance Optimization - Code Splitting
Code splitting is a critical feature in webpack:
Its primary purpose is to separate code into different bundles, which can be loaded on demand or in parallel.
By default, all JavaScript code (business logic, third-party dependencies, and modules not immediately used) is loaded on the initial page load, impacting the loading speed.
Code splitting allows creating smaller bundles and controlling resource loading priorities, thereby enhancing code loading performance.
Webpack provides three common approaches to code splitting:
- Entry Points: Manually splitting code using entry configuration
- Preventing Duplication: Avoiding duplicate code using Entry Dependencies or SplitChunksPlugin
- Dynamic Imports: Splitting code using inline functions in modules
Optimizing Entry Points - Entry Dependencies
When a project has multiple entry points, there might be issues with duplicate dependencies. Some modules might be referenced in multiple entry points, causing redundancy in the final output, increasing the output file size.
module.exports = {
entry: {
page1: {
import: './src/page1.js',
dependOn: 'shared',
},
page2: {
import: './src/page2.js',
dependOn: 'shared',
},
shared: './src/shared.js',
},
output: {
filename: '[name].bundle.js',
path: __dirname + '/dist',
},
};
Dynamic Imports
Dynamic imports are a technique in webpack for lazy loading, allowing modules to load asynchronously at runtime instead of bundling all modules into a large initial file. This approach improves the initial loading speed and reduces the initial bundle size.
const path = require('path');
module.exports = {
entry: {
main: './src/index.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
module: {
rules: [
// Add your loader rules here
],
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
In the above configuration, code splitting is achieved using optimization.splitChunks
with the option chunks: 'all'
.
Then, dynamic imports can be used in the code like this:
// Dynamically import modules where needed
const loadModule = () => import('./Module');
loadModule().then(module => {
// Use the loaded module
});
Webpack will split the modules imported using import()
into separate files. These files will be loaded asynchronously when needed during runtime.
Custom Bundle Splitting - SplitChunks
Bundle splitting is an optimization strategy that allows breaking down code into smaller pieces, enabling faster content display during loading.
Webpack provides various strategies for bundle splitting, one of which involves using the SplitChunksPlugin
plugin. This strategy is known as splitChunks
.
module.exports = {
// ...other configurations
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000, // Minimum size of the module before splitting
minChunks: 1, // Minimum number of times a module should be duplicated before splitting
maxAsyncRequests: 5, // Maximum number of parallel requests when loading modules on demand
maxInitialRequests: 3, // Maximum number of parallel requests at an entry point
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
For more information, refer to the webpack-split-chunks-plugin documentation.
Performance Optimization - CDN
CDN, or Content Delivery Network, refers to a network of interconnected servers strategically placed to deliver content to users efficiently. It ensures faster and more reliable delivery of resources such as music, images, videos, applications, and other files by utilizing servers closest to each user, providing high performance, scalability, and low-cost content delivery.
In development, CDN is typically used in two ways:
- All static resources are bundled and stored on a CDN server, and users load resources exclusively through the CDN.
- Some third-party resources are hosted on CDN servers.
Utilizing a Content Delivery Network (CDN) is a highly effective performance optimization strategy, especially within Webpack. CDN accelerates website loading speed, reduces server load, and enhances user experience. Here’s how you can configure and use CDN in Webpack:
Using CDN for Third-Party Libraries
Integrate third-party libraries used in your project (such as React, Vue, jQuery, etc.) through CDN links directly in the HTML file:
<script src="https://cdn.jsdelivr.net/npm/react@version/dist/react.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@version/dist/react-dom.min.js"></script>
Configuring Externals in Webpack
In your Webpack configuration, utilize the externals
field to inform Webpack about externally referenced modules that shouldn’t be bundled:
module.exports = {
// ...other configurations
externals: {
react: 'React',
'react-dom': 'ReactDOM',
},
};
Then, include the CDN links in the HTML file using script tags:
<script src="https://cdn.jsdelivr.net/npm/react@version/dist/react.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@version/dist/react-dom.min.js"></script>
Configuring CDN’s publicPath
In the Webpack output
field, set the publicPath
to specify the URL prefix for resource imports, typically set to the CDN’s address:
module.exports = {
// ...other configurations
output: {
// ...other output configurations
publicPath: 'https://cdn.example.com/',
},
};
This way, during Webpack build, all resource paths will be prefixed with the CDN’s address.
Performance Optimization - Extracting CSS Files
Extracting CSS files from JavaScript bundles is a common performance optimization strategy. This approach reduces the size of JavaScript files, speeds up page loading, and allows browsers to download CSS and JavaScript files in parallel, enhancing loading performance. In Webpack, you can achieve this using the mini-css-extract-plugin
plugin.
Webpack Configuration
In your Webpack configuration file, include the mini-css-extract-plugin
plugin and configure the module.rules
to handle CSS files:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ...other configurations
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
// Additional CSS loaders like postcss-loader and sass-loader can be added here
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles.css', // Filename for the extracted CSS file
}),
],
};
Including CSS Files
In your JavaScript files or entry point file, import the CSS file:
import './styles.css';
Alternatively, in the HTML file, use the link tag to include the extracted CSS file:
<link rel="stylesheet" href="styles.css">
Performance Optimization - Bundling File Naming (Hash, ContentHash, ChunkHash)
In Webpack, how files are named during bundling is a crucial performance optimization strategy. Proper naming ensures that browsers can cache files correctly, avoiding unnecessary network requests and improving application loading speed. Here are three common bundling file naming techniques: Hash, ContentHash, and ChunkHash.
Hash
Hash is generated based on file content. When file content changes, its corresponding hash value also changes. In Webpack, you can use the [hash]
placeholder to represent the hash value.
output: {
filename: 'bundle.[hash].js',
}
ContentHash
ContentHash is generated based on file content as well, but unlike Hash, it’s solely influenced by file content and remains unaffected by file name or path changes. In Webpack, you can use the [contenthash]
placeholder to represent the ContentHash value.
output: {
filename: 'bundle.[contenthash].js',
}
ChunkHash
ChunkHash is generated based on module content. Different module contents result in different ChunkHash values. In Webpack, you can use the [chunkhash]
placeholder to represent the ChunkHash value.
output: {
filename: '[name].[chunkhash].js',
}
Performance Optimization - Implementing Tree Shaking in Webpack
JavaScript Tree Shaking:
Tree Shaking in JavaScript originates from the rollup bundler, a build tool. It relies on the static syntax analysis of ES Modules (no code execution) to determine module dependencies.
Webpack 2 introduced native support for ES2015 modules, enhancing tree shaking capabilities. Webpack 4 extended this ability and introduced the sideEffects
property in package.json to indicate which files have no side effects, allowing webpack to safely remove unused code.
In Webpack 5, partial CommonJS tree shaking support was introduced.
Implementing Tree Shaking in JavaScript
Webpack implements Tree Shaking through two methods:
- usedExports: Marking certain functions as used and optimizing them using Terser.
- sideEffects: Skipping entire modules/files and checking if they have side effects.
Tree Shaking for CSS
Tree Shaking for CSS involves using additional plugins. While PurifyCss was used previously, it’s no longer maintained. An alternative is PurgeCSS, a tool for removing unused CSS.
Note: This translation includes placeholder strings like [hash]
, [contenthash]
, and [chunkhash]
to represent dynamic values. Please replace these placeholders with appropriate values based on your specific use case.
Note: This article is a translated version of the original post. For the most accurate and up-to-date information, please refer to the original source.
```
Comments