Module Bundlers: Webpack, Parcel & Vite

Reviewed & published by Brayan K

Master the tools that power modern JavaScript development. Learn bundling, code splitting, tree shaking, HMR, and production optimization across Webpack, Vite, and Parcel.

Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Bundler configurations require a local development environment. To practice:

What You'll Learn

Why Module Bundlers?

Modern apps use ES modules, TypeScript, JSX, SCSS, and hundreds of dependencies. Browsers can't handle all of this natively. Bundlers transform your development code into optimized, browser-ready production builds.

What Bundlers Do

The Big Three

Before you press Run on the config blocks: most of the code in this lesson is configuration — webpack.config.js, vite.config.js and friends. Those files are read by a bundler on your machine, not by a browser, so running them in the editor here reports errors like require is not defined or Cannot use import statement outside a module. That is expected. Read them, copy them into a real project, and run npm run build there.

The three exercises further down are runnable, because they model what a bundler does using plain JavaScript you can execute right here.

Webpack Configuration

Webpack is the most flexible bundler with a massive plugin ecosystem. It requires configuration but gives you complete control over every aspect of your build.

// webpack.config.js - Basic Webpack Configuration
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  // Entry point - where bundler starts
  entry: "./src/index.js",

  // Output configuration
  output: {
    filename: "[name].[contenthash].js",
    path: path.resolve(__dirname, "dist"),
    clean: true // Clean /dist folder before each build
  },

  // Development or production mode
  mode: process.env.NODE_ENV || "development",

  // Module rules - how to handle different file types
  module: {
    rules: [
      // JavaScript/JSX with Babel
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"]
          }
        }
      },
      // CSS files
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"]
      },
      // Images
      {
        test: /\.(png|jpg|gif|svg)$/,
        type: "asset/resource"
      }
    ]
  },

  // Plugins extend Webpack functionality
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    }),
    new MiniCssExtractPlugin({
      filename: "[name].[contenthash].css"
    })
  ],

  // Development server
  devServer: {
    static: "./dist",
    hot: true,
    port: 3000
  },

  // Code splitting optimization
  optimization: {
    splitChunks: {
      chunks: "all"
    },
    runtimeChunk: "single"
  }
};

💡 Key Concepts: Entry points define where bundling starts. Loaders transform files (babel-loader, css-loader). Plugins extend functionality (HtmlWebpackPlugin, MiniCssExtractPlugin).

Webpack Production Optimization

Production builds require aggressive optimization: minification, code splitting, tree shaking, and vendor chunking for optimal caching.

// webpack.config.js - Production Optimization
const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
  mode: "production",
  
  // Multiple entry points for large apps
  entry: {
    main: "./src/main.js",
    admin: "./src/admin.js",
    analytics: "./src/analytics.js"
  },

  output: {
    filename: "[name].[contenthash].js",
    path: __dirname + "/dist",
    // Enable long-term caching
    chunkFilename: "[name].[contenthash].chunk.js"
  },

  optimization: {
    minimize: true,
    minimizer: [
      // Minify JavaScript
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // Remove console.log
            pure_funcs: ["console.info", "console.debug"]
          }
        }
      }),
      // Minify CSS
      new CssMinimizerPlugin()
    ],
    
    // Split vendor code for better caching
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all"
        },
        // Separate React into its own chunk
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: "react",
          chunks: "all",
          priority: 10
        }
      }
    },
    
    // Single runtime chunk for all entries
    runtimeChunk: "single"
  },

  plugins: [
    // Analyze bundle size
    new BundleAnalyzerPlugin({
      analyzerMode: "static",
      openAnalyzer: false
    })
  ]
};

Vite Configuration

Vite uses native ES modules during development for instant server start and updates. It's the fastest development experience available and the modern default choice.

// vite.config.js - Vite Configuration
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { resolve } from "path";

export default defineConfig({
  // Plugins
  plugins: [react()],

  // Path aliases
  resolve: {
    alias: {
      "@": resolve(__dirname, "src"),
      "@components": resolve(__dirname, "src/components"),
      "@utils": resolve(__dirname, "src/utils")
    }
  },

  // Development server
  server: {
    port: 3000,
    open: true,
    // Proxy API requests
    proxy: {
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true
      }
    }
  },

  // Build configuration
  build: {
    // Target modern browsers
    target: "es2018",
    
    // Use esbuild for minification (fastest)
    minify: "esbuild",
    
    // Split CSS per chunk
    cssCodeSplit: true,
    
    // Disable sourcemaps for production
    sourcemap: false,
    
    // Chunk splitting
    rollupOptions: {
      output: {
        manualChunks: {
          // Separate vendor chunks
          vendor: ["react", "react-dom"],
          router: ["react-router-dom"],
          query: ["@tanstack/react-query"]
        }
      }
    }
  },

  // Environment variables prefix
  envPrefix: "VITE_"
});

✅ Why Vite is Fast: During development, Vite serves raw ES modules to the browser — no bundling required! For production, it uses Rollup with ESBuild for lightning-fast builds.

Writing Vite Plugins

Vite plugins follow Rollup's plugin interface with additional Vite-specific hooks. You can transform code, inject scripts, and customize the build process.

// Writing a Custom Vite Plugin
function myVitePlugin() {
  return {
    name: "my-vite-plugin",

    // Runs during config resolution
    config(config, { command }) {
      console.log("Building for:", command);
    },

    // Transform source code
    transform(code, id) {
      // Convert .txt files to JS modules
      if (id.endsWith(".txt")) {
        return {
          code: `export default ${JSON.stringify(code)}`,
          map: null
        };
      }
    },

    // Modify the HTML
    transformIndexHtml(html) {
      return html.replace(
        "</head>",
        '<script>console.log("Injected!")</script></head>'
      );
    },

    // Build hooks
    buildStart() {
      console.log("Build started!");
    },

    buildEnd() {
      console.log("Build finished!");
    }
  };
}

// Use in vite.config.js
export default defineConfig({
  plugins: [myVitePlugin()]
});

Code Splitting & Lazy Loading

Code splitting creates separate chunks that load on demand, dramatically improving initial load times. Use dynamic imports to load routes, features, and heavy libraries only when needed.

// Code Splitting with Dynamic Imports

// 1. Route-based code splitting
const routes = [
  {
    path: "/",
    // Lazy load the home page
    component: () => import("./pages/Home.js")
  },
  {
    path: "/dashboard",
    // Lazy load dashboard (admin only)
    component: () => import("./pages/Dashboard.js")
  },
  {
    path: "/settings",
    component: () => import("./pages/Settings.js")
  }
];

// 2. Feature-based code splitting
async function loadChartLibrary() {
  // Only load heavy library when needed
  const { Chart } = await import("chart.js");
  return new Chart(canvas, config);
}

button.addEventListener("click", async () => {
  const chart = await loadChartLibrary();
  chart.render();
});

// 3. Conditional code splitting
async function loadLocale(lang) {
  // Load language pack on demand
  const messages = await import(`./locales/${lang}.js`);
  return messages.default;
}

// 4. Component-level splitting (React)
const HeavyComponent = React.lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyComponent />
    </Suspense>
  );
}

// 5. Named chunks for better debugging
import(/* webpackChunkName: "admin" */ "./admin/AdminPanel.js");
import(/* webpackChunkName: "charts" */ "./charts/ChartModule.js");

Tree Shaking

Tree shaking eliminates unused code from your final bundle. It works best with ES modules and side-effect-free code.

// Tree Shaking - Dead Code Elimination

// ❌ BAD: Imports entire library (no tree shaking)
import _ from "lodash";
const result = _.map([1, 2, 3], x => x * 2);

// ✅ GOOD: Import only what you need (tree shakeable)
import { map } from "lodash-es";
const result = map([1, 2, 3], x => x * 2);


// ❌ BAD: Side effects prevent tree shaking
export function usefulFunction() {
  return "I am useful";
}

export function unusedFunction() {
  return "I am never used";
}

// Side effect - runs when module is imported!
console.log("Module loaded!");


// ✅ GOOD: Pure modules are fully tree-shakeable
// utils.js - mark as side-effect free in package.json
export function usefulFunction() {
  return "I am useful";
}

export function unusedFunction() {
  return "I am never used"; // Will be removed!
}


// package.json - Mark package as side-effect free
{
  "name": "my-library",
  "sideEffects": false
  // OR specify files with side effects:
  // "sideEffects": ["*.css", "*.scss"]
}


// Tree Shaking Tips:
// 1. Use ES modules (import/export), not CommonJS (require)
// 2. Prefer libraries with ES module builds (*-es packages)
// 3. Avoid side effects in modules
// 4. Use "sideEffects" field in package.json
// 5. Enable minification (removes unused code)

Worked Example: A Bundler in Miniature

Tree shaking and code splitting sound like magic until you see the algorithm, which is surprisingly small. A bundler walks your imports from the entry file, keeps everything it reaches, and discards everything it does not. That is it.

The program below does exactly that over a pretend project, and prints the bundle, the dead code and the sizes. Read the comments first, then press Run and check the output against the list at the bottom of the code.

// -- WORKED EXAMPLE - a bundler in miniature --
  // Real bundlers do far more than this, but the two jobs at the heart of
  // Webpack, Vite and Rollup are exactly the two below: walk the import graph
  // from an entry point, then throw away everything you never reached.

  // A pretend project. Each "file" lists what it imports, plus a size in KB so
  // you can see the effect on the bundle your users have to download.
  const project = {
    "main.js":   { imports: ["cart.js", "format.js"], sizeKb: 2 },
    "cart.js":   { imports: ["format.js"],            sizeKb: 4 },
    "format.js": { imports: [],                       sizeKb: 3 },
    "admin.js":  { imports: ["charts.js"],            sizeKb: 12 },
    "charts.js": { imports: [],                       sizeKb: 40 },
    "unused.js": { imports: [],                       sizeKb: 7 }
  };

  // JOB 1 - build the module graph. Start at the entry file and follow every
  // import it declares, then every import THOSE files declare, and so on.
  function collectModules(entry, files) {
    const seen = new Set();      // files already pulled in
    const queue = [entry];       // files still to inspect

    while (queue.length > 0) {
      const name = queue.shift();          // take the next file off the front
      if (seen.has(name)) continue;        // already handled - and this is also
                                           // what stops circular imports looping
      seen.add(name);

      const file = files[name];
      if (!file) continue;                 // a name with no file = a broken import
      file.imports.forEach(dep => queue.push(dep));   // queue what it needs
    }

    return seen;
  }

  const included = collectModules("main.js", project);
  console.log("in the bundle:", [...included].join(", "));

  // JOB 2 - anything the walk never reached is dead code.
  // That is tree shaking, and it really is this simple in principle.
  const shakenOut = Object.keys(project).filter(name => !included.has(name));
  console.log("shaken out: ", shakenOut.join(", "));

  // The part your users actually feel: size.
  const sumSize = (names) => names.reduce((kb, n) => kb + project[n].sizeKb, 0);
  console.log("bundle:", sumSize([...included]) + "kb");
  console.log("saved: ", sumSize(shakenOut) + "kb");

  // And this is why code splitting exists. Load the admin screen with a dynamic
  // import() rather than a normal import, and it becomes its own chunk -
  // downloaded only by the people who actually open it.
  const adminChunk = collectModules("admin.js", project);
  console.log("admin chunk:", [...adminChunk].join(", "));
  console.log("admin chunk size:", sumSize([...adminChunk]) + "kb, downloaded on demand");

  // Expected output:
  // in the bundle: main.js, cart.js, format.js
  // shaken out:  admin.js, charts.js, unused.js
  // bundle: 9kb
  // saved:  59kb
  // admin chunk: admin.js, charts.js
  // admin chunk size: 52kb, downloaded on demand

Notice why tree shaking needs ES modules. This walk only works because import and export are static — a bundler can read them without running your code. require(someVariable) can point anywhere at runtime, which is why CommonJS code is so much harder to shake.

🎯 Your Turn — Finish the Tree Shaker

Same idea, different project, and three blanks for you. The numbers in the expected output are the point: an unused legacy.js dragging in a 88kb library is the single most common reason a bundle is bigger than anyone expects.

// 🎯 YOUR TURN - finish the bundler
  // Three blanks, all in the two steps that matter: walking the import graph,
  // and throwing away whatever the walk never reached.

  const project = {
    "main.js":   { imports: ["cart.js", "format.js"], sizeKb: 2 },
    "cart.js":   { imports: ["format.js"],            sizeKb: 4 },
    "format.js": { imports: [],                       sizeKb: 3 },
    "legacy.js": { imports: ["jquery.js"],            sizeKb: 9 },
    "jquery.js": { imports: [],                       sizeKb: 88 }
  };

  function collectModules(entry, files) {
    const seen = new Set();
    const queue = [entry];

    while (queue.length > 0) {
      const name = queue.shift();
      if (seen.has(name)) continue;      // also what stops circular imports looping
      seen.add(name);

      // 👉 1) Keep the walk going: queue up everything this file imports.
      //       files[name].imports is an array of file names, and 'dep' is one of them.
      files[name].imports.forEach(dep => ___);
    }

    return seen;
  }

  const included = collectModules("main.js", project);
  console.log("bundled:", [...included].join(", "));

  // 👉 2) Dead code is every file in the project that 'included' never reached.
  //       Hint: a Set has a .has(name) method.
  const dead = Object.keys(project).filter(name => ___);
  console.log("shaken out:", dead.join(", "));

  // 👉 3) Add up the sizeKb of a list of file names.
  //       'kb' is the running total so far and 'n' is the current file name.
  const sumSize = (names) => names.reduce((kb, n) => ___, 0);

  console.log("bundle size:", sumSize([...included]) + "kb");
  console.log("removed:", sumSize(dead) + "kb");

  // ✅ Expected output:
  // bundled: main.js, cart.js, format.js
  // shaken out: legacy.js, jquery.js
  // bundle size: 9kb
  // removed: 97kb

Blank 1 is where people get stuck. forEach does not collect anything for you — you have to push each dependency onto queue yourself, or the walk stops after the entry file and your "bundle" is one module long.

Hot Module Replacement (HMR)

HMR updates modules in the browser without a full page refresh, preserving application state. Vite provides instant HMR out of the box.

// Hot Module Replacement (HMR)

// How HMR works internally:
// 1. You edit a file
// 2. Bundler compiles JUST that file
// 3. Sends update via WebSocket
// 4. Browser replaces the module
// 5. State is preserved (no page refresh!)


// Vite - HMR is automatic for most files
// For custom HMR handling:
if (import.meta.hot) {
  // Accept updates for this module
  import.meta.hot.accept((newModule) => {
    console.log("Module updated!", newModule);
  });

  // Clean up before module is replaced
  import.meta.hot.dispose((data) => {
    // Save state before update
    data.savedState = currentState;
    clearInterval(timer);
  });

  // Access data from previous version
  if (import.meta.hot.data.savedState) {
    currentState = import.meta.hot.data.savedState;
  }
}


// Webpack - Manual HMR API
if (module.hot) {
  module.hot.accept("./module.js", () => {
    // Handle the updated module
    console.log("Module updated!");
  });

  module.hot.dispose((data) => {
    // Cleanup before replacement
  });
}


// React Fast Refresh (built into Vite/CRA)
// - Preserves component state during updates
// - Only refreshes changed components
// - Falls back to full reload when needed

// CSS HMR
// Both Vite and Webpack automatically hot-reload CSS
// without losing JavaScript state

Environment Variables

Bundlers inject environment-specific variables at build time, allowing different configurations for development, staging, and production.

// Environment Variables in Bundlers

// Vite - Uses import.meta.env
// .env file:
// VITE_API_URL=https://api.example.com
// VITE_DEBUG=true

// Access in code:
console.log(import.meta.env.VITE_API_URL);
console.log(import.meta.env.VITE_DEBUG);
console.log(import.meta.env.MODE); // "development" or "production"
console.log(import.meta.env.DEV);  // true in dev
console.log(import.meta.env.PROD); // true in production


// Webpack - Uses DefinePlugin
// webpack.config.js
const webpack = require("webpack");

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      "process.env.API_URL": JSON.stringify("https://api.example.com"),
      "process.env.DEBUG": JSON.stringify(true),
      __DEV__: JSON.stringify(process.env.NODE_ENV !== "production")
    })
  ]
};

// Access in code:
console.log(process.env.API_URL);
if (__DEV__) {
  console.log("Development mode");
}


// Environment-specific files
// .env                - All environments
// .env.local          - Local overrides (gitignored)
// .env.development    - Development mode
// .env.production     - Production mode
// .env.test           - Test mode

// Priority (Vite): .env.production.local > .env.production > .env.local > .env

Module Federation (Microfrontends)

Webpack Module Federation allows separate applications to share code at runtime — the foundation of microfrontend architecture used by Amazon, Netflix, and Shopify.

// Webpack Module Federation (Microfrontends)

// Host Application - webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      
      // Import modules from remote apps
      remotes: {
        // "internal name": "remote_name@URL"
        headerApp: "header@http://localhost:3001/remoteEntry.js",
        productApp: "products@http://localhost:3002/remoteEntry.js",
        cartApp: "cart@http://localhost:3003/remoteEntry.js"
      },
      
      // Shared dependencies (avoid duplication)
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.0.0" }
      }
    })
  ]
};

// Use remote components in host
import React, { Suspense } from "react";

const Header = React.lazy(() => import("headerApp/Header"));
const ProductList = React.lazy(() => import("productApp/ProductList"));
const Cart = React.lazy(() => import("cartApp/Cart"));

function App() {
  return (
    <div>
      <Suspense fallback="Loading header...">
        <Header />
      </Suspense>
      <Suspense fallback="Loading products...">
        <ProductList />
      </Suspense>
      <Suspense fallback="Loading cart...">
        <Cart />
      </Suspense>
    </div>
  );
}


// Remote Application - webpack.config.js
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "header",
      filename: "remoteEntry.js",
      
      // Expose modules to other apps
      exposes: {
        "./Header": "./src/components/Header.js",
        "./Navigation": "./src/components/Navigation.js"
      },
      
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true }
      }
    })
  ]
};

Server-Side Rendering (SSR)

SSR requires bundling both client and server code. Vite has built-in SSR support with separate entry points for server rendering and client hydration.

// Server-Side Rendering (SSR) Build Configuration

// Vite SSR - vite.config.js
import { defineConfig } from "vite";

export default defineConfig({
  build: {
    // SSR entry point
    ssr: true,
    rollupOptions: {
      input: "./src/entry-server.js"
    }
  },
  
  ssr: {
    // Don't bundle these (use Node.js require)
    noExternal: ["some-package-to-bundle"]
  }
});

// entry-server.js - Server entry point
import { renderToString } from "react-dom/server";
import App from "./App";

export function render(url) {
  const html = renderToString(<App url={url} />);
  return html;
}

// entry-client.js - Client entry point (hydration)
import { hydrateRoot } from "react-dom/client";
import App from "./App";

hydrateRoot(document.getElementById("root"), <App />);


// Express server example
import express from "express";
import { render } from "./dist/server/entry-server.js";

const app = express();

app.use("*", async (req, res) => {
  const appHtml = render(req.originalUrl);
  
  const html = `
    <!DOCTYPE html>
    <html>
      <head><title>SSR App</title></head>
      <body>
        <div id="root">${appHtml}</div>
        <script type="module" src="/entry-client.js"></script>
      </body>
    </html>
  `;
  
  res.status(200).set({ "Content-Type": "text/html" }).end(html);
});

app.listen(3000);

Parcel: Zero Configuration

Parcel automatically detects and handles all file types without configuration. Perfect for prototypes, learning, and simple projects.

// Parcel - Zero Configuration Bundler

// No config file needed! Just run:
// npx parcel index.html

// Parcel automatically detects and handles:
// - JavaScript/TypeScript
// - JSX/TSX
// - CSS/SCSS/LESS
// - Images and fonts
// - HTML

// Optional .parcelrc for customization
{
  "extends": "@parcel/config-default",
  "transformers": {
    "*.svg": ["@parcel/transformer-svg-react"]
  },
  "optimizers": {
    "*.js": ["@parcel/optimizer-terser"]
  }
}


// package.json scripts
{
  "scripts": {
    "dev": "parcel src/index.html",
    "build": "parcel build src/index.html",
    "clean": "rm -rf dist .parcel-cache"
  },
  
  // Configure build targets
  "targets": {
    "default": {
      "distDir": "./dist",
      "publicUrl": "./"
    }
  },
  
  // Browser targets
  "browserslist": "> 0.5%, last 2 versions, not dead"
}


// Multi-entry builds
// npx parcel src/index.html src/admin.html

// Library mode
{
  "main": "dist/main.js",
  "module": "dist/module.js",
  "source": "src/index.js",
  "targets": {
    "main": { "includeNodeModules": false },
    "module": { "includeNodeModules": false }
  }
}

Choosing the Right Bundler

Each bundler excels in different scenarios. Choose based on your project's needs, team experience, and long-term requirements.

🏆 Mini-Challenge: Enforce a Performance Budget

No blanks now — a brief, an outline and a test harness. A performance budget is the cheapest bundle-size protection a team can have: agree a limit per chunk, then fail the build when someone crosses it. Every bundler can do this; here you write the check yourself.

The vendor line is the realistic one. Nobody writes 137kb of vendor code on purpose; it accumulates one npm install at a time, which is exactly why the check has to be automatic rather than something a person remembers to do.

Key Takeaways

Core Concepts

When to Use Each

Practice quiz

Why do modern apps need module bundlers, according to the lesson?

  • Browsers can't natively handle ES modules, TypeScript, JSX, SCSS, and hundreds of dependencies
  • Bundlers make code run on the server
  • Browsers refuse to load JavaScript without them
  • They are only needed for CSS

Answer: Browsers can't natively handle ES modules, TypeScript, JSX, SCSS, and hundreds of dependencies. Bundlers transform development code (ES modules, TS, JSX, SCSS, many deps) into optimized, browser-ready production builds.

Which bundler does the lesson call the fastest dev experience and the modern default?

  • Webpack
  • Parcel
  • Vite
  • Rollup

Answer: Vite. Vite serves native ES modules in development for instant startup, making it the fastest dev experience and the modern default choice.

What does tree shaking do?

  • Combines all CSS into one file
  • Eliminates unused (dead) code from the final bundle
  • Splits code into lazy-loaded chunks
  • Adds source maps for debugging

Answer: Eliminates unused (dead) code from the final bundle. Tree shaking eliminates unused code from the final bundle and works best with ES modules and side-effect-free code.

Which bundler is described as requiring zero configuration?

  • Webpack
  • Vite
  • Rollup
  • Parcel

Answer: Parcel. Parcel is the zero-configuration bundler; you just run it and it auto-detects and handles all file types.

What enables code splitting so chunks load on demand?

  • The require() function
  • Dynamic imports (import())
  • The eval() function
  • CSS @import rules

Answer: Dynamic imports (import()). Code splitting uses dynamic imports like import('./pages/Home.js') to load routes, features, and heavy libraries only when needed.

What does Hot Module Replacement (HMR) do?

  • Minifies code on each save
  • Updates modules in the browser without a full page refresh, preserving state
  • Compresses bundles with Brotli
  • Removes unused code at build time

Answer: Updates modules in the browser without a full page refresh, preserving state. HMR updates modules in the browser without a full refresh, preserving application state. Vite provides instant HMR out of the box.

In Vite, how do you access environment variables in code?

  • process.env.X
  • import.meta.env.X
  • window.env.X
  • global.env.X

Answer: import.meta.env.X. Vite exposes env variables via import.meta.env (e.g. import.meta.env.VITE_API_URL), while Webpack uses DefinePlugin with process.env.

Which Webpack feature is the foundation of microfrontend architecture?

  • Module Federation
  • Tree shaking
  • Source maps
  • DevServer proxy

Answer: Module Federation. Webpack Module Federation lets separate applications share code at runtime, which is the basis of microfrontends used by Amazon, Netflix, and Shopify.

For tree shaking, which module system should you use?

  • CommonJS (require)
  • ES modules (import/export)
  • AMD modules
  • UMD modules

Answer: ES modules (import/export). The lesson's tree-shaking tips say to use ES modules (import/export) rather than CommonJS (require) so unused code can be removed.

What three things do a Webpack config's entry, loaders, and plugins control?

  • Where bundling starts, how files are transformed, and extra functionality
  • Server port, database, and routing
  • Only CSS, fonts, and images
  • Testing, linting, and formatting

Answer: Where bundling starts, how files are transformed, and extra functionality. Entry points define where bundling starts, loaders transform files (babel-loader, css-loader), and plugins extend functionality (HtmlWebpackPlugin, etc.).

Continue this course