{"id":15413352,"url":"https://github.com/vannizhang/web-performance-optimization-with-webpack","last_synced_at":"2025-04-19T11:38:35.160Z","repository":{"id":96363439,"uuid":"495618129","full_name":"vannizhang/web-performance-optimization-with-webpack","owner":"vannizhang","description":"A webpack boilerplate aiming to improve web performance.","archived":false,"fork":false,"pushed_at":"2023-05-04T20:42:30.000Z","size":7484,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-29T07:22:27.255Z","etag":null,"topics":["code-splitting","image-minify","lazy-loading","performance-optimization","react-boilerplate","typescript-boilerplate","web-performance","webp-image","webpack","webpack-boilerplate"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vannizhang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-05-24T00:40:01.000Z","updated_at":"2024-12-01T18:14:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"22adcc76-3302-4e30-b164-8ea482d94f76","html_url":"https://github.com/vannizhang/web-performance-optimization-with-webpack","commit_stats":{"total_commits":40,"total_committers":1,"mean_commits":40.0,"dds":0.0,"last_synced_commit":"d4a1422b84a4cef005245016c0b1e65d706bb25a"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vannizhang%2Fweb-performance-optimization-with-webpack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vannizhang%2Fweb-performance-optimization-with-webpack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vannizhang%2Fweb-performance-optimization-with-webpack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vannizhang%2Fweb-performance-optimization-with-webpack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vannizhang","download_url":"https://codeload.github.com/vannizhang/web-performance-optimization-with-webpack/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249685239,"owners_count":21310569,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-splitting","image-minify","lazy-loading","performance-optimization","react-boilerplate","typescript-boilerplate","web-performance","webp-image","webpack","webpack-boilerplate"],"created_at":"2024-10-01T16:56:43.513Z","updated_at":"2025-04-19T11:38:35.138Z","avatar_url":"https://github.com/vannizhang.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Web performance optimization with Webpack\nA webpack boilerplate with configurations, plugins and best practice tips to improve the performance of your front-end app and make it load faster. \n\n## Contents\n- [HTML](#html)\n    - [Minified HTML](#minified-html)\n- [CSS](#css)\n    - [Extracts CSS](#extracts-css)\n    - [Minified CSS](#minified-css)\n    - [Inline Critical CSS](#inline-critical-css)\n- [Images](#images)\n    - [Compress images](#compress-images)\n    - [Use WebP Images](#use-webp-images)\n    - [Preload and prefetch images](#preload-and-prefetch-images)\n    - [Lazy loading images](#lazy-loading-images)\n    \u003c!---\n    - [Serve responsive images](#serve-responsive-images-not-finished)\n    --\u003e\n- [JavaScript](#javascript)\n    - [Split chunks](#split-chunks)\n    - [Minified JS](#minified-js)\n    - [Lazy Loading JS](#lazy-loading-js)\n    - [Use web worker](#use-web-worker)\n- [Fonts](#fonts)\n    \u003c!---\n    - [Preconnect to load fonts faster](#preconnect-to-load-fonts-faster)\n    --\u003e\n    - [Cache font resources](#cache-font-resources)\n- [Caching](#caching)\n    - [Use `[contenthash]` in output filenames](#use-contenthash-in-output-filenames)\n- [Others](#others)\n    - [Compress text files](#compress-text-files)\n\n## Getting Started\n1. Make sure you have a fresh version of [Node.js](https://nodejs.org/en/) and NPM installed. The current Long Term Support (LTS) release is an ideal starting point\n\n2. Clone this repository to your computer: \n    ```sh\n    git clone https://github.com/vannizhang/web-performance-optimization-with-webpack.git\n    ```\n\n\n3. From the project's root directory, install the required packages (dependencies):\n\n    ```sh\n    npm install\n    ```\n\n4. To run and test the app on your local machine (http://localhost:8080):\n\n    ```sh\n    # it will start a server instance and begin listening for connections from localhost on port 8080\n    npm run start\n    ```\n\n5. To build/deploye the app, you can run:\n\n    ```sh\n    # it will place all files needed for deployment into the /dist directory \n    npm run build\n    ```\n\n## HTML\n\n### Minified HTML \nMinify the HTML by removing unnecessary spaces, comments and attributes to reduce the size of output HTML file and speed up load times.\n\nThe [`HtmlWebpackPlugin`](https://webpack.js.org/plugins/html-webpack-plugin/) has the [`minify`](https://github.com/jantimon/html-webpack-plugin#minification) option to control how the output html shoud be minified:\n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\n\nmodule.exports = {\n    //...\n    plugins: [\n        new HtmlWebpackPlugin({\n            ...\n            minify: {\n                html5                          : true,\n                collapseWhitespace             : true,\n                minifyCSS                      : true,\n                minifyJS                       : true,\n                minifyURLs                     : false,\n                removeComments                 : true,\n                removeEmptyAttributes          : true,\n                removeOptionalTags             : true,\n                // Remove attributes when value matches default.\n                removeRedundantAttributes      : true,\n                // Remove type=\"text/javascript\" from script tags. \n                // Other type attribute values are left intact\n                removeScriptTypeAttributes     : true,\n                // Remove type=\"text/css\" from style and link tags. \n                // Other type attribute values are left intact\n                removeStyleLinkTypeAttributese : true,\n                // Replaces the doctype with the short (HTML5) doctype\n                useShortDoctype                : true\n            }\n        })\n    ]\n}\n```\n\n## CSS\n\n### Extracts CSS\nThe extracted css stylesheets can be cached separately. Therefore if your app code changes, the browser only needs to fetch the JS files that changed.\n\nUse [`MiniCssExtractPlugin`](https://webpack.js.org/plugins/mini-css-extract-plugin/) to extract CSS into separate files:\n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nconst MiniCssExtractPlugin = require(\"mini-css-extract-plugin\");\n\nmodule.exports = {\n    //...\n    module: {\n        rules: [\n            //...\n            {\n                test: /\\.css$/i,\n                include: path.resolve(__dirname, '..', 'src'),\n                use: [\n                    MiniCssExtractPlugin.loader,\n                    {\n                        loader: \"css-loader\", options: {\n                            sourceMap: true\n                        }\n                    }, \n                    {\n                        loader: 'postcss-loader'\n                    }\n                ],\n            }\n        ]\n    },\n    plugins: [\n        new MiniCssExtractPlugin({\n            filename: '[name].[contenthash].css'\n        }),\n    ]\n}\n```\n\n### Minify CSS\n\nRemove unnecessary characters, such as comments, whitespaces, and indentation to reduce the size of output CSS files and speed up how long it takes for the browser to download and execute it.\n\nUse the [`css-minimizer-webpack-plugin`](https://webpack.js.org/plugins/css-minimizer-webpack-plugin/) to optimize and minify the output CSS.\n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nconst CssMinimizerPlugin = require(\"css-minimizer-webpack-plugin\");\n\nmodule.exports = {\n    //...\n    optimization: {\n        minimizer: [\n            new CssMinimizerPlugin(),\n        ],\n    },\n};\n```\n\n### Inline Critical CSS\nInlining extracted CSS for critical (above-the-fold) content in the `\u003chead\u003e` of the HTML document eliminates the need to make an additional request to fetch these styles, which can help to speed up render times.\n\nUse the [`html-critical-webpack-plugin`](https://github.com/anthonygore/html-critical-webpack-plugin) to extracts, minifies and inlines above-the-fold CSS.\n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nconst HtmlCriticalPlugin = require(\"html-critical-webpack-plugin\");\n\nmodule.exports = {\n    //...\n    plugins: [\n        new HtmlWebpackPlugin({ ... }),\n        new MiniCssExtractPlugin({ ... }),\n        new HtmlCriticalPlugin({\n            base: path.join(path.resolve(__dirname), '..', 'dist/'),\n            src: 'index.html',\n            dest: 'index.html',\n            inline: true,\n            minify: true,\n            extract: true,\n            width: 1400,\n            height: 900,\n            penthouse: {\n                blockJSRequests: false,\n            }\n        }),\n    ]\n}\n```\n\n## Images\nAccording to [Ilya Grigorik](https://www.igvita.com/):\n\u003e Images often account for most of the downloaded bytes on a web page and also often occupy a significant amount of visual space. As a result, optimizing images can often yield some of the largest byte savings and performance improvements for your website. [More details](https://web.dev/compress-images/)\n\n### Compress images\n\nUse [`ImageMinimizerWebpackPlugin`](https://webpack.js.org/plugins/image-minimizer-webpack-plugin/) to minify PNG, JPEG, GIF, SVG and WEBP images with [`imagemin`](https://github.com/imagemin/imagemin), [squoosh](https://github.com/GoogleChromeLabs/squoosh), [sharp](https://github.com/lovell/sharp) or [svgo](https://github.com/svg/svgo).\n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nconst ImageMinimizerPlugin = require(\"image-minimizer-webpack-plugin\");\n\nmodule.exports = {\n    //...\n    optimization: {\n        //..\n        minimizer: [\n            //..\n            new ImageMinimizerPlugin({\n                minimizer: { \n                    implementation: ImageMinimizerPlugin.squooshMinify,\n                    options: {\n                        // encodeOptions: {\n                        //     mozjpeg: {\n                        //         // That setting might be close to lossless, but it’s not guaranteed\n                        //         // https://github.com/GoogleChromeLabs/squoosh/issues/85\n                        //         quality: 100,\n                        //     },\n                        // }\n                    }\n                }\n            })\n        ]\n    }\n}\n```\n\n### Use WebP Images\nWebP images are smaller than their JPEG and PNG counterparts - usually on the magnitude of a 25–35% reduction in filesize. This decreases page sizes and improves performance. [More details](https://web.dev/serve-images-webp/)\n\nUse `imagemin` and  `imagemin-webp` to convert images to WebP, here is a script that converts all JPEG and PNG images in the `./src/static/images` folder to WebP:\n\n[convert2webp.js](./scripts/convert2webp.js)\n```js\nconst path = require('path');\nconst imageFolder = path.join(__dirname, '..', 'src', 'static', 'images')\nconst { promises } = require('node:fs')\nconst { promisify } = require('node:util')\nconst fs = require('graceful-fs');\n\nconst fsPromises = promises;\nconst writeFile = promisify(fs.writeFile);\n\nconst move2originalDir = async(files)=\u003e{\n\n    for(const file of files){\n        const currDestinationPath = file.destinationPath.replace(/\\\\/g, '/');\n\n        const source = path.parse(file.sourcePath);\n        const destination = path.parse(currDestinationPath);\n        const newDestinationPath = `${source.dir}/${destination.name}${destination.ext}`;\n\n        // console.log(currDestinationPath, newDestinationPath)\n\n        if(currDestinationPath === newDestinationPath){\n            continue\n        }\n\n        await fsPromises.mkdir(path.dirname(newDestinationPath), { recursive: true });\n\n        // save a webp file in the original directory\n        await writeFile(newDestinationPath, file.data);\n\n        // remove the original webp file because it's no longer needed\n        await fsPromises.unlink(currDestinationPath)\n    }\n}\n\nconst run = async () =\u003e {\n    const imagemin = (await import(\"imagemin\")).default;\n    const webp = (await import(\"imagemin-webp\")).default;\n\n    const processedPNGs = await imagemin([`${imageFolder}/**/*.png`], {\n        destination: imageFolder,\n        preserveDirectories: true,\n        plugins: [\n            webp({\n                lossless: true,\n            }),\n        ],\n    });\n\n    await move2originalDir(processedPNGs)\n    console.log(\"PNGs processed\");\n\n    const processedJPGs = await imagemin([`${imageFolder}/**/*.{jpg,jpeg}`], {\n        destination: imageFolder,\n        preserveDirectories: true,\n        plugins: [\n            webp({\n                quality: 65,\n            }),\n        ],\n    });\n\n    await move2originalDir(processedJPGs)\n    console.log(\"JPGs and JPEGs processed\");\n}\n\nrun();\n```\n\nModify `\"scripts\"` section in `package.json` to add `\"pre\"` scripts, so npm can automatically run `convert2webp` before `npm run build` or `npm run start`.\n```js\n{\n    //...\n    \"scripts\": {\n        \"convert2webp\": \"node ./scripts/convert2webp.js\",\n        \"prestart\": \"npm run convert2webp\",\n        \"start\": \"webpack serve --mode development --open --config webpack/dev.config.js\",\n        \"prebuild\": \"npm run convert2webp\",\n        \"build\": \"webpack --mode production --config webpack/prod.config.js\"\n    },\n}\n```\n\nHere is an example of serving WebP images to WebP to newer browsers and a fallback image to older browsers:\n```js\nimport React from 'react'\nimport nightSkyWebP from '../../static/images/night-sky.webp'\nimport nightSkyJPG from '../../static/images/night-sky.jpg'\n\nconst WebpImage = () =\u003e {\n    return (\n        \u003cpicture\u003e\n            \u003csource type=\"image/webp\" srcSet={nightSkyWebP} /\u003e\n            \u003csource type=\"image/jpeg\" srcSet={nightSkyJPG} /\u003e\n            \u003cimg src={nightSkyJPG} alt=\"\" width={500}/\u003e\n        \u003c/picture\u003e\n    )\n}\n\nexport default WebpImage\n```\n\n### Preload and prefetch images\n\nPreload lets you tell the browser about critical resources that you want to load as soon as possible, before they are discovered in HTML, CSS or JavaScript files. This is especially useful for resources that are critical but not easily discoverable, such as banner images included in JavaScript or CSS file.\n\nUse [`@vue/preload-webpack-plugin`](https://github.com/vuejs/preload-webpack-plugin) to automatically inject resource hints tags `\u003clink rel='preload'\u003e` or `\u003clink rel='prefetch'\u003e` into the document `\u003chead\u003e`.\n\nIt's important to use `\u003clink rel='preload'\u003e` **sparingly** and only preload the **most critical** resources. \n\nTo do this, we can keep all images that need to be preloaded in the `./src/static/images/preload` folder, then modify `file-loader` to add prefix `\"preload.\"` to the output name for the images in this folder, after that, we can set `fileWhitelist` option of `preload-webpack-plugin` to only inject `\u003clink rel='preload'\u003e` for images with `\"preload.\"` prefix in their names.\n\nand we can repeat the step above to inject `\u003clink rel='prefetch'\u003e` for images that are less important but will very likely be needed later. \n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nconst PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');\n\nmodule.exports = {\n    //...\n    module: {\n        rules: [\n            { \n                test: /\\.(png|jpg|gif|svg|webp)$/,  \n                use : [\n                    {\n                        loader: \"file-loader\",\n                        options: {\n                            name(resourcePath, resourceQuery){\n\n                                // add \"preload.\" prefix to images in preload folder\n                                if(resourcePath.includes('preload')){\n                                    return 'preload.[contenthash].[ext]';\n                                } \n                                \n                                // add \"prefetch.\" prefix to images in prefetch folder\n                                if (resourcePath.includes('prefetch')){\n                                    return 'prefetch.[contenthash].[ext]';\n                                }\n\n                                return '[contenthash].[ext]';\n                            },\n                        }\n                    }\n                ]\n            },\n        ]\n    },\n    plugins: [\n        new PreloadWebpackPlugin({\n            rel: 'preload',\n            as(entry) {\n                if (/\\.(png|jpg|gif|svg|webp)$/.test(entry)) {\n                    return 'image';\n                }\n            },\n            // only inject `\u003clink rel='preload'\u003e` for images with `\"preload.\"` prefix in their names\n            fileWhitelist: [\n                /preload.*\\.(png|jpg|gif|svg|webp)$/\n            ],\n            include: 'allAssets'\n        }),\n        new PreloadWebpackPlugin({\n            rel: 'prefetch',\n            as(entry) {\n                if (/\\.(png|jpg|gif|svg|webp)$/.test(entry)) {\n                    return 'image';\n                }\n            },\n            // only inject `\u003clink rel='prefetch'\u003e` for images with `\"prefetch.\"` prefix in their names\n            fileWhitelist: [\n                /prefetch.*\\.(png|jpg|gif|svg|webp)$/\n            ],\n            include: 'allAssets'\n        }),\n    ]\n}\n```\n\n### Lazy loading images\nLazy load offscreen images will improve the response time of the current page and then avoid loading unnecessary images that the user may not need.\n\nFortunately we don't need to tune webpack to enable lazy load image, just use browser-level lazy-loading with the `loading` attribute, use `lazy` as the value to tell the browser to load the image immediately if it is in the viewport, and to fetch other images when the user scrolls near them.\n\nYou can also use `Intersection Observer` or `event handlers` to polyfill lazy-loading of `\u003cimg\u003e`: [more details](https://web.dev/lazy-loading-images/#images-inline-intersection-observer)\n\nHere is an example of `\u003cimg\u003e` with `loading=\"lazy\"`:\n```html\n\u003cimg src=\"image.png\" loading=\"lazy\" alt=\"…\" width=\"200\" height=\"200\"\u003e\n\u003cpicture\u003e\n    \u003csource media=\"(min-width: 800px)\" srcset=\"large.jpg 1x, larger.jpg 2x\"\u003e\n    \u003cimg src=\"photo.jpg\" loading=\"lazy\"\u003e\n\u003c/picture\u003e\n```\n\n\u003c!---\n###  Serve responsive images (not finished)\nAccording to [this article](https://web.dev/serve-responsive-images/):\n\u003e Serving desktop-sized images to mobile devices can use 2–4x more data than needed. Instead of a \"one-size-fits-all\" approach to images, serve different image sizes to different devices.\n\nIt seems [`responsive-loader`](https://github.com/dazuaz/responsive-loader) can be a good tool to use but I encountered error when using it with TypeScipt. \n--\u003e\n\n## JavaScript\n### Split chunks\nCode split vendors (dependencies) into a separate bundle to improve caching. Our application code changes more often than the vendor code because we adjust versions of your dependencies less frequently. Split vendor bundles allows the broswer to continue using cached vendor bundle as long as it's not change.\n\nUse out of the box `SplitChunksPlugin` to split chunks, and we tune the [`optimization.splitChunks`](https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks) configuration to split vendor bundles.\n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nmodule.exports = {\n    //...\n    optimization: {\n        splitChunks: {\n            cacheGroups: {\n                // vendor chunk\n                vendor: {\n                    // sync + async chunks\n                    chunks: 'all',\n                    name: 'vendor',\n                    // import file path containing node_modules\n                    test: /node_modules/\n                }\n            }\n        },\n    }\n}\n```\n\n### Minified JS\nLike HTML and CSS files, removing all unnecessary spaces, comments and break will reduce the size of your JavaScript files and speed up your site's page load times. \n\nUse [`TerserWebpackPlugin`](https://webpack.js.org/plugins/terser-webpack-plugin/) to minify/minimize the output JavaScript files:\n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nconst TerserPlugin = require('terser-webpack-plugin');\n\nmodule.exports = {\n    //...\n    optimization: {\n        minimize: true,\n        minimizer: [\n            new TerserPlugin({\n                extractComments: true,\n                terserOptions: {\n                    compress: {\n                        drop_console: true,\n                    }\n                }\n            }), \n        ],\n    },\n};\n```\n\n### Lazy loading JS\nSplit the non-critical codes into its own bundle and reduce the size of initial bundle can make the initial load faster. Then dynamically import these non-critical codes on demand.\n\nUse `react.lazy` to dynamic import a component. The components or modules that we know are likely to be used at some point in the application can be prefetched, according to [this ariticle](https://www.patterns.dev/posts/prefetch/):\n\n\u003e Modules that are prefetched are requested and loaded by the browser even before the user requested the resource. When the browser is idle and calculates that it's got enough bandwidth, it will make a request in order to load the resource, and cache it. Having the resource cached can reduce the loading time significantly.\n\nWe can let Webpack know that certain bundles need to be prefetched, by adding a magic comment to the import statement: `/* webpackPrefetch: true */`.\n\nhere is an example of lazy loading a [React component](./src/components/LazyLoadComponent/EmojiPicker.tsx)\n```js\nimport React, { Suspense, lazy, useState, useEffect } from \"react\";\n\nconst EmojiPicker = lazy(()=\u003eimport(\n    /* webpackPrefetch: true */ \n    /* webpackChunkName: \"emoji-picker\" */\n    \"./EmojiPicker\"\n))\n\nconst ChatInput = () =\u003e {\n    const [ showEmojiPicker, setShowEmojiPicker ] = useState(false)\n\n    return (\n        \u003cdiv\u003e\n            \u003cdiv\u003e\n                \u003cinput type=\"text\" placeholder=\"Type a message...\" /\u003e\n                \u003cbutton onClick={setShowEmojiPicker.bind(null, true)}\u003epick emojis\u003c/button\u003e\n            \u003c/div\u003e\n            \n            \u003cSuspense fallback={\u003cspan id=\"loading\"\u003eLoading...\u003c/span\u003e}\u003e\n                { showEmojiPicker \u0026\u0026 \u003cEmojiPicker /\u003e }\n            \u003c/Suspense\u003e\n        \u003c/div\u003e\n    )\n}\n\nexport default ChatInput\n```\n\nHere is an example of lazy loading a [module](./src/utils/numbers.ts):\n```js\nimport React, { useState } from 'react'\n\nconst RandomNumberCard = () =\u003e {\n\n    const [ randomNum, setRandomNum ] = useState\u003cnumber\u003e()\n\n    const getRandomNum = async()=\u003e{\n\n        // load numbers module dynamically\n        const { generateRandomNumber } = await import(\n            '../../utils/numbers'\n        )\n        setRandomNum(generateRandomNumber(50, 100))\n    }\n\n    return (\n        \u003cdiv\u003e\n            \u003cbutton onClick={getRandomNum}\u003e get a random number \u003c/button\u003e\n            { randomNum !== undefined \u0026\u0026 \u003cspan\u003e { randomNum } \u003c/span\u003e }\n        \u003c/div\u003e\n    )\n}\n\nexport default RandomNumberCard\n```\n\n### Use web worker\nJavaScript runs on the browser’s main thread, right alongside style calculations, layout, and, in many cases, paint. If your JavaScript runs for a long time, it will block these other tasks, potentially causing frames to be missed. Move pure computational work (code doesn’t require DOM access) to Web Workers and run it off the browser's main thread can thus improve the rendering performance significantly.\n\nUse worker loader to load web worker file, and communicate with the web worker by sending messages via the postMessage API:\n\nHere is an example of using web worker in a [React Component](./src/components/WebWorker/WebWorkerExample.tsx):\n\n```js\nimport React, { useEffect, useState } from 'react';\n\n// load web worker\nimport MyWorker from 'worker-loader!./worker';\n\nconst n = 1e6;\n\nconst WebWorkerExample = () =\u003e {\n    const [ count, setCount ] = useState\u003cnumber\u003e()\n\n    const getCountOfPrimeNumbers = async ()=\u003e {\n\n        // create a web worker\n        const worker = new MyWorker();\n\n        // add message event listener to receive message returned by web worker\n        worker.addEventListener(\n            'message',\n            function (e) {\n                setCount(e.data.message);\n            },\n            false\n        );\n\n        // post message and have the web woker start doing geavy tasks in a separate thread\n        worker.postMessage(n);\n    };\n\n    return (\n        \u003cdiv\u003e\n            { count === undefined \n                ? \u003cbutton onClick={getCountOfPrimeNumbers}\u003eget count\u003c/button\u003e\n                : \u003cp\u003e{count} prime numbers found\u003c/p\u003e\n            }\n        \u003c/div\u003e\n        \n    );\n};\n\nexport default WebWorkerExample;\n```\n\n\nhere is the [worker.ts](./src/components/WebWorker/worker.ts):\n```js\nconst ctx: Worker = self as any;\n\n// receive message from the main thread\nctx.onmessage = async (e) =\u003e {\n\n    if(!e.data){\n        return 0\n    }\n\n    let count = 0;\n\n    // codes that get count of primes that can take long time to run...\n\n    // send message back to the main thred\n    ctx.postMessage({ message: count });\n};\n```\n\n\u003c!--\n### tree shaking\n--\u003e\n\n## Fonts\n\u003c!--\n### Preconnect to load fonts faster:\nIf the site loads fonts from a third-party site, use the preconnect resource hint to establish early connection(s) with the third-party origin.\n\n```js\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\nconst HtmlWebpackPreconnectPlugin = require('html-webpack-preconnect-plugin');\n\nmodule.exports = {\n    //...\n    plugins: [\n        new HtmlWebpackPlugin({\n            filename: 'index.html',\n            //...\n            preconnect: [\n                'https://fonts.googleapis.com',\n                'https://fonts.gstatic.com'\n            ]\n        }),\n        // enabled preconnect plugin\n        new HtmlWebpackPreconnectPlugin(),\n    ]\n}\n```\n--\u003e\n\n### Cache font resources\nFont resources are, typically, static resources that don't see frequent updates. As a result, they are ideally suited for a long max-age expiry. \n\nIn addition to the browser cache, using service worker to serve font resources with a cache-first strategy is appropriate for most use cases:\n\n[sw.js](./src/serviceWorker/sw.js)\n```js\n//...\nself.addEventListener('fetch', (event)=\u003e{\n    \n    // Let the browser do its default thing\n    // for non-GET requests.\n    if (event.request.method != \"GET\") {\n        return;\n    };\n\n    // Check if this is a request for a font file\n    if (event.request.destination === 'font') {\n\n        // console.log('service worker fetching', event.request)\n        event.respondWith(caches.open(cacheName).then((cache) =\u003e {\n            // Go to the cache first\n            return cache.match(event.request.url).then((cachedResponse) =\u003e {\n                // Return a cached response if we have one\n                if (cachedResponse) {\n                    return cachedResponse;\n                }\n\n                // Otherwise, hit the network\n                return fetch(event.request).then((fetchedResponse) =\u003e {\n                    // Add the network response to the cache for later visits\n                    cache.put(event.request, fetchedResponse.clone());\n\n                    // Return the network response\n                    return fetchedResponse;\n                });\n            });\n        }));\n\n    } else {\n        return;\n    }\n})\n```\n\n## Caching\n### Use contenthash in output filenames\n\nWe always want to have our static files be cahced by the browser with a long expiry time to improve the load speed. However, everytime when we make a change to our static files, we will want to make sure the browser can get the latest version of that file instead of retrieving the old one from the cache.\n\nThis can be achieved by adding `[contenthash]` to the output filenames, `[contenthash]` is unique hash based on the content of an asset. When the asset's content changes, `[contenthash]` will change as well.\n\n[`webpack.config.js`](./webpack/prod.config.js)\n```js\nmodule.exports = {\n    //...\n    output: {\n        path: path.resolve(__dirname, '..', './dist'),\n        filename: '[name].[contenthash].js',\n        chunkFilename: '[name].[contenthash].js',\n        clean: true\n    }\n}\n```\n\n\u003c!--\n## service worker\n### cache strategy\n- cache fonts and other static files\naccording to https://developer.chrome.com/docs/workbox/caching-strategies-overview/, **Cache first, falling back to network** is a great strategy to apply to all static assets (such as CSS, JavaScript, images, and fonts), especially hash-versioned ones. It offers a speed boost for immutable assets by side-stepping any content freshness checks with the server the HTTP cache may kick off. More importantly, any cached assets will be available offline.\n\nhttps://developer.chrome.com/docs/workbox/using-workbox-without-precaching/#webpack\n--\u003e\n\n## Others\n\n### Compress text files\nCompress text files and reduce the size of these files can improve load speed, normally, this is handled by a server like Apache or Nginx on runtime, but you might want to pre-build compressed assets to save the runtime cost. \n\nUse [`CompressionWebpackPlugin`](https://webpack.js.org/plugins/compression-webpack-plugin/) to prepare compressed versions of assets.\n\n```js\nconst CompressionPlugin = require(\"compression-webpack-plugin\");\n\nmodule.exports = {\n    //...\n    plugins: [\n        new CompressionPlugin()\n    ]\n};\n```\n\u003c!--\n### preconnect, and dns-prefecting\n--\u003e\n\n## Resources\n- [Fast load times](https://web.dev/fast/)\n- [Front-End Performance Checklist](https://github.com/thedaviddias/Front-End-Performance-Checklist)\n- [Awesome Webpack Perf ](https://github.com/iamakulov/awesome-webpack-perf)\n- [Critical CSS and Webpack: Automatically Minimize Render-Blocking CSS](https://vuejsdevelopers.com/2017/07/24/critical-css-webpack/)\n- [Webpack - How to convert jpg/png to webp via image-webpack-loader](https://stackoverflow.com/questions/58827843/webpack-how-to-convert-jpg-png-to-webp-via-image-webpack-loader)\n- [Best practices for fonts](https://web.dev/font-best-practices/)\n- [Web performance](https://developer.mozilla.org/en-US/docs/Learn/Performance)\n- [An in-depth guide to performance optimization with webpack](https://blog.logrocket.com/guide-performance-optimization-webpack/)\n\n## Contribute\nPlease feel free to open an issue or a pull request to suggest changes, improvements or fixes.\n\n## License\n[MIT](./LICENSE)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvannizhang%2Fweb-performance-optimization-with-webpack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvannizhang%2Fweb-performance-optimization-with-webpack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvannizhang%2Fweb-performance-optimization-with-webpack/lists"}