Webpack[TBC]

14 阅读2分钟

核心概念

  1. 本质:模块打包器

    • 将各种资源(JS/CSS/图片等)视为模块
    • 通过依赖关系构建依赖图
    • 输出静态资源bundle
  2. 五大核心概念

    • Entry:打包入口起点,entry: './src/index.js'
    • Output:输出位置配置,path: path.resolve(__dirname, 'dist')
    • Loaders:处理非JS文件,{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
    • Plugins:扩展功能,new HtmlWebpackPlugin()
    • Mode:环境模式,mode:'development'

核心工作原理

打包流程

graph LR
    A[入口文件 Entry] --> B[解析模块依赖]
    B --> C[构建依赖图]
    C --> D[应用 Loaders 转换]
    D --> E[打包模块资源]
    E --> F[执行插件优化]
    F --> G[输出 Bundles]
    G --> H[完成打包]

    classDef default fill:#f9f,stroke:#333,stroke-width:2px;
    classDef process fill:#6cf,stroke:#333,stroke-width:2px;
    classDef output fill:#2ecc71,stroke:#333,stroke-width:2px;
    
    class A,H process;
    class B,C,D,E,F process;
    class G output;

关键过程

  1. 入口文件
    • 从配置的入口文件开始打包过程
    • 通常是index.js/main.js
  2. 解析模块依赖
    • 分析文件中的import/require语句
    • 递归查找所有依赖模块
  3. 构建依赖图
    • 创建模块依赖关系图谱
    • 确定模块加载顺序
  4. 应用Loaders转换
    • 使用配置的Loaders处理各类资源
    • 如:JS转译、CSS预处理、图片优化
  5. 打包模块资源
    • 将所有模块合并为代码块chunk
    • 应用代码分割规则
  6. 执行插件优化
    • 运行插件进行额外处理
    • 如:代码压缩、资源注入、生成html
  7. 输出bundles
    • 将最终结果写入文件系统
    • 生成JS/CSS/资源文件
  8. 完成打包
    • 输出构建统计信息
    • 触发回调通知

配置webpack.config.js

基础结构

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.[contenthash].js',
        path: path.resolve(__dirname, 'dist'),
        clean: true,
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: 'babel-loader',
            },
        ],
    },
    plugins: [
        new HtmlWebpackPlugin({ template: './src/index.html' }),
    ],
    mode:'production',
};

Loaders

文件类型Loader配置
Javascriptbabel-loader, @babel/preset-env
CSS['style-loader', 'css-loader', 'postcss-loader']
SASS['sass-loader'] [需配合css-loader]
图片/字体type: 'asset/resource' [webpack5内置]
SVG@svg/webpack

Plugins

插件用途
HtmlWebpackPlugin生成html文件
MiniCssExtractPlugin提取CSS到单独文件
DefinePlugin定义环境变量
CleanWebpackPlugin清理构建目录
BundleAnalyzerPlugin包体积分析

高级优化策略

代码分割Code Splitting

将应用代码拆分为多个独立报chunk,按需加载或并行加载,减少初始加载体积。

目的

  1. 减少初始加载体积:避免用户下载整个应用代码
  2. 提高加载速度:并行加载多个小文件比单个大文件更快
  3. 按需加载:只加载当前视图需要的代码
  4. 缓存优化:第三方库单独打包,充分利用浏览器缓存

实现方式

  1. 入口点分割 \手动
// webpack.config.js
module.exports = {
    entry: {
        app: './src/app.js',
        vendor: ['react', 'react-dom']
    },
    output: {
        filename: '[name].bundles.js'
    }
};
  1. 动态导入
// 使用import语法
const loadComponent = () => import('./HeavyComponent');

button.addEventListener('click', () => {
    loadComponent().then(module => {
        module.default.render();
    });
});
  1. 智能分割SplitChunksPlugin
optimization: {
    splitChunks: {
        chunks: 'all',
        minSize: 20000,  // 超过20kb才分割
        maxSize: 0,
        minChunks: 1,
        cachedGroups: {
            vendors: {
                test: /[\\/]node_modules[\\/]/,
                name: 'vendors',
                priority: -10
            },
            commons:{
                name: 'commons',
                minChunks: 2,  // 被2个以上chunk引用
                priority: -20,
                reuseExistingChunk: true
            }
        }
    }
}

代码分割策略

策略类型适用场景示例
入口分割多页面应用entry: { home: './home.js', about: './about.js' }
动态分割路由组件/弹窗() => import('./UserProfile')
第三方库稳定不常更新的库cacheGroups.vendors
运行时代码Webpack运行时runtimeChunk: 'single'

Tree Shaking

  • 使用ES6模块语法import/export
  • 生产模式自动启动
  • package.json中添加:
"sideEffects": ["*.css", "*.global.js"]

缓存优化

output: {
    filename: '[name].[contenthash].js'
}

懒加载

// dynamic import
const loadModule  = () => import('./heavyModule.js');

自定义扩展

自定义loader

module.exports = function(source) {
    return source.replace(/console\.log\(.*\);?/g, '');
};

自定义plugin

class myPlugin {
    apply(complier) {
        commplier.hooks.done.tap('MyPlugin', stats => {
            console.log('编译完成!');
        });
    }
}

性能优化实践

构建速度优化

方法实现
缓存loadersuse: ['cache-loader', 'babel-loader']
多进程处理thread-loader
DLL预构建DllPlugin + DllReferencePlugin
缩小搜索范围resolve: { modules: [path.resolve('node_modules')] }

输出优化

方法实现
CDN加速output.publicPath: 'https://cdn.example.com/'
Gzip压缩compression-webpack-plugin
图片优化image-webpack-loader

原理深入

Tapable事件流机制

compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
    // 处理逻辑
    callback();
});

核心对象关系

classDiagram
  Compiler *-- Compilation
  Compilation *-- Module
  Module *-- Dependency
  Compiler : hooks
  Compilation : modules
  Compilation : chunks

HMR热更新原理

  1. 建立WebSocket连接
  2. 文件修改触发重新编译
  3. 服务端发生更新消息
  4. 客户端拉取更新模块
  5. 执行模块替换逻辑