Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

A webpack boilerplate aiming to improve web performance.

License

NotificationsYou must be signed in to change notification settings

vannizhang/web-performance-optimization-with-webpack

Repository files navigation

A webpack boilerplate with configurations, plugins and best practice tips to improve the performance of your front-end app and make it load faster.

Contents

Getting Started

  1. Make sure you have a fresh version ofNode.js and NPM installed. The current Long Term Support (LTS) release is an ideal starting point

  2. Clone this repository to your computer:

    git clone https://github.com/vannizhang/web-performance-optimization-with-webpack.git
  3. From the project's root directory, install the required packages (dependencies):

    npm install
  4. To run and test the app on your local machine (http://localhost:8080):

    # it will start a server instance and begin listening for connections from localhost on port 8080npm run start
  5. To build/deploye the app, you can run:

    # it will place all files needed for deployment into the /dist directorynpm run build

HTML

Minified HTML

Minify the HTML by removing unnecessary spaces, comments and attributes to reduce the size of output HTML file and speed up load times.

TheHtmlWebpackPlugin has theminify option to control how the output html shoud be minified:

webpack.config.js

constHtmlWebpackPlugin=require("html-webpack-plugin");module.exports={//...plugins:[newHtmlWebpackPlugin({            ...minify:{html5                          :true,collapseWhitespace             :true,minifyCSS                      :true,minifyJS                       :true,minifyURLs                     :false,removeComments                 :true,removeEmptyAttributes          :true,removeOptionalTags             :true,// Remove attributes when value matches default.removeRedundantAttributes      :true,// Remove type="text/javascript" from script tags.// Other type attribute values are left intactremoveScriptTypeAttributes     :true,// Remove type="text/css" from style and link tags.// Other type attribute values are left intactremoveStyleLinkTypeAttributese :true,// Replaces the doctype with the short (HTML5) doctypeuseShortDoctype                :true}})]}

CSS

Extracts CSS

The extracted css stylesheets can be cached separately. Therefore if your app code changes, the browser only needs to fetch the JS files that changed.

UseMiniCssExtractPlugin to extract CSS into separate files:

webpack.config.js

constMiniCssExtractPlugin=require("mini-css-extract-plugin");module.exports={//...module:{rules:[//...{test:/\.css$/i,include:path.resolve(__dirname,'..','src'),use:[MiniCssExtractPlugin.loader,{loader:"css-loader",options:{sourceMap:true}},{loader:'postcss-loader'}],}]},plugins:[newMiniCssExtractPlugin({filename:'[name].[contenthash].css'}),]}

Minify CSS

Remove 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.

Use thecss-minimizer-webpack-plugin to optimize and minify the output CSS.

webpack.config.js

constCssMinimizerPlugin=require("css-minimizer-webpack-plugin");module.exports={//...optimization:{minimizer:[newCssMinimizerPlugin(),],},};

Inline Critical CSS

Inlining extracted CSS for critical (above-the-fold) content in the<head> of the HTML document eliminates the need to make an additional request to fetch these styles, which can help to speed up render times.

Use thehtml-critical-webpack-plugin to extracts, minifies and inlines above-the-fold CSS.

webpack.config.js

constHtmlCriticalPlugin=require("html-critical-webpack-plugin");module.exports={//...plugins:[newHtmlWebpackPlugin({ ...}),newMiniCssExtractPlugin({ ...}),newHtmlCriticalPlugin({base:path.join(path.resolve(__dirname),'..','dist/'),src:'index.html',dest:'index.html',inline:true,minify:true,extract:true,width:1400,height:900,penthouse:{blockJSRequests:false,}}),]}

Images

According toIlya Grigorik:

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

Compress images

UseImageMinimizerWebpackPlugin to minify PNG, JPEG, GIF, SVG and WEBP images withimagemin,squoosh,sharp orsvgo.

webpack.config.js

constImageMinimizerPlugin=require("image-minimizer-webpack-plugin");module.exports={//...optimization:{//..minimizer:[//..newImageMinimizerPlugin({minimizer:{implementation:ImageMinimizerPlugin.squooshMinify,options:{// encodeOptions: {//     mozjpeg: {//         // That setting might be close to lossless, but it’s not guaranteed//         // https://github.com/GoogleChromeLabs/squoosh/issues/85//         quality: 100,//     },// }}}})]}}

Use WebP Images

WebP 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

Useimagemin andimagemin-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:

convert2webp.js

constpath=require('path');constimageFolder=path.join(__dirname,'..','src','static','images')const{ promises}=require('node:fs')const{ promisify}=require('node:util')constfs=require('graceful-fs');constfsPromises=promises;constwriteFile=promisify(fs.writeFile);constmove2originalDir=async(files)=>{for(constfileoffiles){constcurrDestinationPath=file.destinationPath.replace(/\\/g,'/');constsource=path.parse(file.sourcePath);constdestination=path.parse(currDestinationPath);constnewDestinationPath=`${source.dir}/${destination.name}${destination.ext}`;// console.log(currDestinationPath, newDestinationPath)if(currDestinationPath===newDestinationPath){continue}awaitfsPromises.mkdir(path.dirname(newDestinationPath),{recursive:true});// save a webp file in the original directoryawaitwriteFile(newDestinationPath,file.data);// remove the original webp file because it's no longer neededawaitfsPromises.unlink(currDestinationPath)}}construn=async()=>{constimagemin=(awaitimport("imagemin")).default;constwebp=(awaitimport("imagemin-webp")).default;constprocessedPNGs=awaitimagemin([`${imageFolder}/**/*.png`],{destination:imageFolder,preserveDirectories:true,plugins:[webp({lossless:true,}),],});awaitmove2originalDir(processedPNGs)console.log("PNGs processed");constprocessedJPGs=awaitimagemin([`${imageFolder}/**/*.{jpg,jpeg}`],{destination:imageFolder,preserveDirectories:true,plugins:[webp({quality:65,}),],});awaitmove2originalDir(processedJPGs)console.log("JPGs and JPEGs processed");}run();

Modify"scripts" section inpackage.json to add"pre" scripts, so npm can automatically runconvert2webp beforenpm run build ornpm run start.

{//..."scripts":{"convert2webp":"node ./scripts/convert2webp.js","prestart":"npm run convert2webp","start":"webpack serve --mode development --open --config webpack/dev.config.js","prebuild":"npm run convert2webp","build":"webpack --mode production --config webpack/prod.config.js"},}

Here is an example of serving WebP images to WebP to newer browsers and a fallback image to older browsers:

importReactfrom'react'importnightSkyWebPfrom'../../static/images/night-sky.webp'importnightSkyJPGfrom'../../static/images/night-sky.jpg'constWebpImage=()=>{return(<picture><sourcetype="image/webp"srcSet={nightSkyWebP}/><sourcetype="image/jpeg"srcSet={nightSkyJPG}/><imgsrc={nightSkyJPG}alt=""width={500}/></picture>)}exportdefaultWebpImage

Preload and prefetch images

Preload 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.

Use@vue/preload-webpack-plugin to automatically inject resource hints tags<link rel='preload'> or<link rel='prefetch'> into the document<head>.

It's important to use<link rel='preload'>sparingly and only preload themost critical resources.

To do this, we can keep all images that need to be preloaded in the./src/static/images/preload folder, then modifyfile-loader to add prefix"preload." to the output name for the images in this folder, after that, we can setfileWhitelist option ofpreload-webpack-plugin to only inject<link rel='preload'> for images with"preload." prefix in their names.

and we can repeat the step above to inject<link rel='prefetch'> for images that are less important but will very likely be needed later.

webpack.config.js

constPreloadWebpackPlugin=require('@vue/preload-webpack-plugin');module.exports={//...module:{rules:[{test:/\.(png|jpg|gif|svg|webp)$/,use :[{loader:"file-loader",options:{name(resourcePath,resourceQuery){// add "preload." prefix to images in preload folderif(resourcePath.includes('preload')){return'preload.[contenthash].[ext]';}// add "prefetch." prefix to images in prefetch folderif(resourcePath.includes('prefetch')){return'prefetch.[contenthash].[ext]';}return'[contenthash].[ext]';},}}]},]},plugins:[newPreloadWebpackPlugin({rel:'preload',as(entry){if(/\.(png|jpg|gif|svg|webp)$/.test(entry)){return'image';}},// only inject `<link rel='preload'>` for images with `"preload."` prefix in their namesfileWhitelist:[/preload.*\.(png|jpg|gif|svg|webp)$/],include:'allAssets'}),newPreloadWebpackPlugin({rel:'prefetch',as(entry){if(/\.(png|jpg|gif|svg|webp)$/.test(entry)){return'image';}},// only inject `<link rel='prefetch'>` for images with `"prefetch."` prefix in their namesfileWhitelist:[/prefetch.*\.(png|jpg|gif|svg|webp)$/],include:'allAssets'}),]}

Lazy loading images

Lazy load offscreen images will improve the response time of the current page and then avoid loading unnecessary images that the user may not need.

Fortunately we don't need to tune webpack to enable lazy load image, just use browser-level lazy-loading with theloading attribute, uselazy 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.

You can also useIntersection Observer orevent handlers to polyfill lazy-loading of<img>:more details

Here is an example of<img> withloading="lazy":

<imgsrc="image.png"loading="lazy"alt=""width="200"height="200"><picture><sourcemedia="(min-width: 800px)"srcset="large.jpg 1x, larger.jpg 2x"><imgsrc="photo.jpg"loading="lazy"></picture>

JavaScript

Split chunks

Code 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.

Use out of the boxSplitChunksPlugin to split chunks, and we tune theoptimization.splitChunks configuration to split vendor bundles.

webpack.config.js

module.exports={//...optimization:{splitChunks:{cacheGroups:{// vendor chunkvendor:{// sync + async chunkschunks:'all',name:'vendor',// import file path containing node_modulestest:/node_modules/}}},}}

Minified JS

Like 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.

UseTerserWebpackPlugin to minify/minimize the output JavaScript files:

webpack.config.js

constTerserPlugin=require('terser-webpack-plugin');module.exports={//...optimization:{minimize:true,minimizer:[newTerserPlugin({extractComments:true,terserOptions:{compress:{drop_console:true,}}}),],},};

Lazy loading JS

Split 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.

Usereact.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 tothis ariticle:

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.

We can let Webpack know that certain bundles need to be prefetched, by adding a magic comment to the import statement:/* webpackPrefetch: true */.

here is an example of lazy loading aReact component

importReact,{Suspense,lazy,useState,useEffect}from"react";constEmojiPicker=lazy(()=>import(/* webpackPrefetch: true *//* webpackChunkName: "emoji-picker" */"./EmojiPicker"))constChatInput=()=>{const[showEmojiPicker,setShowEmojiPicker]=useState(false)return(<div><div><inputtype="text"placeholder="Type a message..."/><buttononClick={setShowEmojiPicker.bind(null,true)}>pick emojis</button></div><Suspensefallback={<spanid="loading">Loading...</span>}>{showEmojiPicker&&<EmojiPicker/>}</Suspense></div>)}exportdefaultChatInput

Here is an example of lazy loading amodule:

importReact,{useState}from'react'constRandomNumberCard=()=>{const[randomNum,setRandomNum]=useState<number>()constgetRandomNum=async()=>{// load numbers module dynamicallyconst{ generateRandomNumber}=awaitimport('../../utils/numbers')setRandomNum(generateRandomNumber(50,100))}return(<div><buttononClick={getRandomNum}> get a random number</button>{randomNum!==undefined&&<span>{randomNum}</span>}</div>)}exportdefaultRandomNumberCard

Use web worker

JavaScript 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.

Use worker loader to load web worker file, and communicate with the web worker by sending messages via the postMessage API:

Here is an example of using web worker in aReact Component:

importReact,{useEffect,useState}from'react';// load web workerimportMyWorkerfrom'worker-loader!./worker';constn=1e6;constWebWorkerExample=()=>{const[count,setCount]=useState<number>()constgetCountOfPrimeNumbers=async()=>{// create a web workerconstworker=newMyWorker();// add message event listener to receive message returned by web workerworker.addEventListener('message',function(e){setCount(e.data.message);},false);// post message and have the web woker start doing geavy tasks in a separate threadworker.postMessage(n);};return(<div>{count===undefined                 ?<buttononClick={getCountOfPrimeNumbers}>get count</button>                :<p>{count} prime numbers found</p>}</div>);};exportdefaultWebWorkerExample;

here is theworker.ts:

constctx:Worker=selfasany;// receive message from the main threadctx.onmessage=async(e)=>{if(!e.data){return0}letcount=0;// codes that get count of primes that can take long time to run...// send message back to the main thredctx.postMessage({message:count});};

Fonts

Cache font resources

Font resources are, typically, static resources that don't see frequent updates. As a result, they are ideally suited for a long max-age expiry.

In addition to the browser cache, using service worker to serve font resources with a cache-first strategy is appropriate for most use cases:

sw.js

//...self.addEventListener('fetch',(event)=>{// Let the browser do its default thing// for non-GET requests.if(event.request.method!="GET"){return;};// Check if this is a request for a font fileif(event.request.destination==='font'){// console.log('service worker fetching', event.request)event.respondWith(caches.open(cacheName).then((cache)=>{// Go to the cache firstreturncache.match(event.request.url).then((cachedResponse)=>{// Return a cached response if we have oneif(cachedResponse){returncachedResponse;}// Otherwise, hit the networkreturnfetch(event.request).then((fetchedResponse)=>{// Add the network response to the cache for later visitscache.put(event.request,fetchedResponse.clone());// Return the network responsereturnfetchedResponse;});});}));}else{return;}})

Caching

Use contenthash in output filenames

We 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.

This 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.

webpack.config.js

module.exports={//...output:{path:path.resolve(__dirname,'..','./dist'),filename:'[name].[contenthash].js',chunkFilename:'[name].[contenthash].js',clean:true}}

Others

Compress text files

Compress 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.

UseCompressionWebpackPlugin to prepare compressed versions of assets.

constCompressionPlugin=require("compression-webpack-plugin");module.exports={//...plugins:[newCompressionPlugin()]};

Resources

Contribute

Please feel free to open an issue or a pull request to suggest changes, improvements or fixes.

License

MIT

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp