Lesson 17-Source Code Analysis of Classic Performance Optimization Projects

In modern front-end engineering, the performance optimization of build tools and frameworks directly impacts development experience and production performance. Webpack, Vite, and Next.js, as industry-leading solutions, have underlying optimization mechanisms worth exploring. This tutorial reveals how these tools achieve efficient builds, fast development, and optimized rendering through core source code analysis.

Webpack Performance Optimization Source Code Analysis

Tree Shaking Implementation Principle

Tree Shaking eliminates unused code by statically analyzing ES Module import/export relationships. Webpack uses the Acorn parser to build an AST and employs a Mark-and-Sweep algorithm for this functionality.

In webpack/lib/optimize/ChunkGraph.js, module dependencies are constructed as a graph structure:

// Simplified dependency graph construction
class ChunkGraph {
  constructor() {
    this.moduleGraph = new ModuleGraph();
    this.dependencies = new Map(); // Module dependency graph
  }

  connectModule(module) {
    const ast = this.parseModule(module);
    this.analyzeExports(ast, module);
    this.analyzeImports(ast, module);
  }

  analyzeExports(ast, module) {
    // Traverse AST to identify export statements
    traverse(ast, {
      ExportNamedDeclaration(path) {
        const declarations = path.node.declaration.declarations;
        declarations.forEach(decl => {
          module.exports.add(decl.id.name); // Record exported variables
        });
      }
    });
  }

  analyzeImports(ast, module) {
    // Traverse AST to identify import statements
    traverse(ast, {
      ImportDeclaration(path) {
        const source = path.node.source.value;
        const specifiers = path.node.specifiers;
        specifiers.forEach(spec => {
          module.imports.set(spec.local.name, {
            source,
            exported: spec.imported.name
          });
        });
      }
    });
  }
}

During optimization, webpack/lib/optimize/RemoveUnusedModulesPlugin.js traverses the dependency graph to mark unused exports:

class RemoveUnusedModulesPlugin {
  apply(compiler) {
    compiler.hooks.optimizeModules.tap('RemoveUnusedModules', modules => {
      modules.forEach(module => {
        const usedExports = new Set();
        module.dependencies.forEach(dep => {
          if (dep.module) {
            dep.module.exports.forEach(exportName => {
              if (this.isExportUsed(dep.module, exportName)) {
                usedExports.add(exportName);
              }
            });
          }
        });
        module.usedExports = Array.from(usedExports);
      });
    });
  }
}

In the code generation phase, webpack/lib/CodeGenerationModule.js generates optimized code based on usedExports.

Code Splitting and Lazy Loading Implementation

Webpack implements dynamic imports via import(), transforming them into __webpack_require__.e calls. The dynamic loading template is defined in webpack/lib/web/JsonpTemplatePlugin.js:

// Dynamic loading template
class JsonpTemplatePlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('JsonpTemplatePlugin', compilation => {
      compilation.mainTemplate.hooks.requireEnsure.tap('JsonpTemplatePlugin', (source, chunk) => {
        return `
          ${source}
          __webpack_require__.e(/* chunkId */ ${chunk.id}).then(__webpack_require__.bind(null, /* moduleId */ ${chunk.entryModule.id}));
        `;
      });
    });
  }
}

Code splitting strategies are implemented in webpack/lib/optimize/SplitChunksPlugin.js:

class SplitChunksPlugin {
  optimizeChunks(chunks) {
    chunks.forEach(chunk => {
      if (this.shouldSplit(chunk)) {
        const newChunk = this.createSplitChunk(chunk);
        chunks.push(newChunk);
      }
    });
  }

  shouldSplit(chunk) {
    // Decide splitting based on minSize, minChunks, etc.
    return chunk.size() > this.options.minSize;
  }
}

Lazy loading runtime logic is implemented in webpack/runtime/jsonpChunkLoading.js:

// Dynamic loading implementation
self['webpackJsonp'] = self['webpackJsonp'] || [];
function __webpack_require__.e(chunkId) {
  return Promise.all(
    Object.keys(installedChunks).filter(key => installedChunks[key] === 0)
      .map(key => {
        return new Promise(resolve => {
          const script = document.createElement('script');
          script.src = __webpack_require__.p + chunkId + '.js';
          script.onload = resolve;
          document.head.appendChild(script);
        });
      })
  );
}

Caching and Persistence Implementation

Webpack 5 introduced persistent caching via webpack/lib/cache/PersistentCache.js:

class PersistentCache {
  constructor(options) {
    this.cache = new Map();
    this.fs = options.fs;
    this.context = options.context;
  }

  async get(cacheKey) {
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }

    const fileCache = await this.fs.readFile(this.getCacheFilePath(cacheKey));
    const data = JSON.parse(fileCache);
    this.cache.set(cacheKey, data);
    return data;
  }

  async store(cacheKey, data) {
    this.cache.set(cacheKey, data);
    await this.fs.writeFile(
      this.getCacheFilePath(cacheKey),
      JSON.stringify(data)
    );
  }
}

File system caching uses content hashes for naming, ensuring only changed files are rebuilt:

// Output filename hashing
output: {
  filename: '[name].[contenthash].js',
  chunkFilename: '[name].[contenthash].chunk.js'
}

Plugin System Performance Optimization

Webpack’s plugin system is based on Tapable’s event flow, with plugins hooking into the build process. Performance optimization focuses on reducing unnecessary hook triggers.

Plugin registration in webpack/lib/Compiler.js:

class Compiler {
  constructor() {
    this.hooks = {
      compile: new SyncHook(),
      emit: new AsyncSeriesHook(),
      // Other hooks...
    };
  }

  apply(plugins) {
    plugins.forEach(plugin => plugin.apply(this));
  }
}

Optimized plugin example (avoiding redundant processing):

class MyOptimizedPlugin {
  apply(compiler) {
    let processed = false;

    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      if (processed) return callback();
      processed = true;

      // Execute optimization logic
      callback();
    });
  }
}

Webpack Performance Analysis Tools

Webpack Bundle Analyzer generates visualized reports of module sizes, with core logic in webpack-bundle-analyzer/lib/analyzer.js:

class BundleAnalyzerPlugin {
  generateReport(stats) {
    const modules = stats.modules;
    const sizeMap = new Map();

    modules.forEach(module => {
      sizeMap.set(module.name, module.size);
    });

    // Use D3.js to generate visualization chart
    this.renderChart(sizeMap);
  }
}

Performance analysis API integration example:

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap(webpackConfig);

Vite Performance Optimization Source Code Analysis

ESM-Based Development Server Implementation

Vite leverages native browser ESM support, starting a development server in vite/src/node/server/index.js:

async function createServer() {
  const server = http.createServer(async (req, res) => {
    if (req.url.endsWith('.js')) {
      // Directly return ESM source code
      const filePath = resolveFilePath(req.url);
      const code = await fs.readFile(filePath, 'utf-8');
      res.setHeader('Content-Type', 'application/javascript');
      res.end(code);
    }
  });
}

Dependency pre-bundling is implemented in vite/src/node/server/optimizer/index.js:

async function optimizeDeps() {
  const deps = await scanImports();
  await Promise.all(
    deps.map(dep => {
      return buildDep(dep); // Pre-build with esbuild
    })
  );
}

On-Demand Compilation and Hot Module Replacement (HMR)

Vite’s HMR is implemented in vite/src/node/server/hmr.js:

function handleHMR(ws, file) {
  const modules = getAffectedModules(file);
  ws.send(JSON.stringify({
    type: 'update',
    updates: modules.map(m => ({
      type: 'js-update',
      path: m.url,
      timestamp: Date.now()
    }))
  }));
}

On-demand compilation is handled in vite/src/node/server/transformRequest.js:

async function transformRequest(url) {
  if (url.endsWith('.vue')) {
    return transformVueFile(url); // Compile Vue single-file components on demand
  }
  // Handle other files...
}

Rollup Integration Optimization for Production Builds

Vite switches to Rollup for production builds, with integration in vite/src/node/build/index.js:

async function build() {
  const bundle = await rollup.rollup({
    input: config.build.rollupOptions.input,
    plugins: [vuePlugin(), ...config.plugins]
  });

  await bundle.write({
    format: 'es',
    dir: config.build.outDir,
    sourcemap: true
  });
}

Rollup plugin example (Tree Shaking enhancement):

function vuePlugin() {
  return {
    transform(code, id) {
      if (id.endsWith('.vue')) {
        return transformVueToESM(code); // Convert to ESM format
      }
    }
  };
}

Module Caching Mechanism Implementation

Vite’s module caching system is implemented in vite/src/node/cacheNode.js:

class ModuleGraph {
  constructor() {
    this.modules = new Map(); // Cache parsed modules
  }

  getModule(url) {
    if (this.modules.has(url)) {
      return this.modules.get(url);
    }

    const module = this.resolveModule(url);
    this.modules.set(url, module);
    return module;
  }
}

Cache invalidation uses file system watching:

watcher.on('change', filePath => {
  const url = filePathToUrl(filePath);
  moduleGraph.invalidateModule(url); // Invalidate cache
});

Vite Performance Optimization Strategies

Preload hints are added in vite/src/node/server/transformRequest.js:

function addPreloadHints(code, deps) {
  return code + `
    <link rel="modulepreload" href="${deps.join('')}">
  `;
}

CSS code splitting is handled in vite/src/node/plugins/css.js:

function transformCSS(code, id) {
  return {
    code: `export default ${JSON.stringify(code)}`,
    map: null
  };
}

Next.js Performance Optimization Source Code Analysis

SSR and SSG Implementation Principles

Next.js’s SSR is implemented in next/server/render.js:

async function renderToHTML(req, res, pagePath, query) {
  const html = await renderPageComponent(req, res, pagePath, query);
  return `
    <!DOCTYPE html>
    <html>
      <head>${getHead(html)}</head>
      <body>${html}</body>
    </html>
  `;
}

SSG pre-generates HTML during build, implemented in next/build/index.js:

async function buildStaticPages() {
  const pages = await detectPages();
  await Promise.all(
    pages.map(page => {
      return generateStaticHTML(page); // Pre-generate HTML
    })
  );
}

Image Optimization and Dynamic Import

The Image component in next/client/image.js implements responsive loading:

function Image(props) {
  const [src, setSrc] = useState(props.placeholder);

  useEffect(() => {
    const img = new Image();
    img.src = props.src;
    img.onload = () => setSrc(props.src);
  }, []);

  return <img src={src} />;
}

Dynamic imports are implemented in next/dynamic.js:

function dynamic(importFunc, options) {
  return React.lazy(() => importFunc().then(mod => ({
    default: mod[options?.ssr ? 'default' : 'Component']
  })));
}

Route Prefetching and Lazy Loading

Route prefetching is implemented in next/client/router.js:

function prefetch(route) {
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = getNextPagePath(route);
  document.head.appendChild(link);
}

Lazy-loaded components use next/dynamic for code splitting:

const LazyComponent = dynamic(() => import('../components/HeavyComponent'));

Static Asset Optimization (CDN, Caching)

Static asset handling is implemented in next/server/static.js:

async function serveStatic(req, res, path) {
  const filePath = join(STATIC_DIR, path);
  const etag = generateETag(await fs.readFile(filePath));

  res.setHeader('ETag', etag);
  res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');

  sendFile(res, filePath);
}

CDN integration is configured in next.config.js:

module.exports = {
  assetPrefix: 'https://cdn.example.com',
};

Next.js Performance Monitoring Tools

Performance data collection is implemented in next/dist/client/performance.js:

function initPerformance() {
  if (typeof window !== 'undefined') {
    const metrics = {};

    window.performance.addEventListener('measure', event => {
      metrics[event.name] = event.duration;
    });

    return metrics;
  }
}

Analytics tool integration example:

// pages/_app.js
import { useRouter } from 'next/router';
import { useEffect } from 'react';

export default function App({ Component, pageProps }) {
  const router = useRouter();

  useEffect(() => {
    const handleRouteChange = url => {
      trackPageView(url); // Send performance data
    };

    router.events.on('routeChangeComplete', handleRouteChange);
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange);
    };
  }, []);
}

By deeply analyzing the source code of these classic projects, developers can master core optimization techniques for build tools and frameworks, applying them to real-world projects to enhance application performance. These strategies are not only applicable to current tech stacks but also provide critical insights for future architectural evolution.

Share your love