MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Webpack 多入口与多出口的优化配置

2021-09-264.4k 阅读

Webpack 多入口与多出口的优化配置

在前端开发中,Webpack 是一个极为强大的模块打包工具。当项目逐渐变得复杂,涉及多个页面或者多个独立功能模块时,多入口与多出口的配置就显得尤为重要。合理地配置多入口与多出口,不仅可以提高代码的可维护性,还能显著优化项目的性能。

多入口配置的基础理解

多入口配置适用于项目中有多个独立的页面或者功能模块,每个入口都有自己独立的代码逻辑和依赖。例如,一个大型网站可能有首页、登录页、商品详情页等,这些页面都可以作为独立的入口进行打包。

在 Webpack 配置文件(通常是 webpack.config.js)中,多入口配置通过 entry 属性来实现。entry 可以是一个对象,对象的键是入口的名称,值是入口文件的路径。以下是一个简单的示例:

module.exports = {
    entry: {
        page1: './src/page1.js',
        page2: './src/page2.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js'
    }
};

在上述示例中,我们定义了两个入口 page1page2,分别对应 ./src/page1.js./src/page2.js 文件。output.filename 中的 [name] 占位符会被入口的名称替换,这样最终会生成 page1.bundle.jspage2.bundle.js 两个文件在 dist 目录下。

提取公共代码

当存在多个入口时,不同入口之间可能会有一些公共的代码,比如都依赖某些库或者工具函数。如果不进行处理,这些公共代码会在每个入口的打包文件中重复出现,导致文件体积增大。Webpack 提供了多种方式来提取公共代码。

  1. 使用 CommonsChunkPlugin(Webpack 4 之前) 在 Webpack 4 之前,CommonsChunkPlugin 是提取公共代码的主要方式。下面是一个示例:
const webpack = require('webpack');

module.exports = {
    entry: {
        page1: './src/page1.js',
        page2: './src/page2.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js'
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            filename: 'common.bundle.js'
        })
    ]
};

在上述配置中,CommonsChunkPlugin 会自动分析各个入口之间的依赖关系,提取出公共的代码,生成 common.bundle.js 文件。这样在 page1.bundle.jspage2.bundle.js 中就不会再重复包含这些公共代码,从而减小了文件体积。

  1. 使用 splitChunks(Webpack 4 及之后) 从 Webpack 4 开始,splitChunks 取代了 CommonsChunkPlugin,提供了更强大和灵活的公共代码提取功能。splitChunks 配置在 optimization 属性下,以下是一个基本示例:
module.exports = {
    entry: {
        page1: './src/page1.js',
        page2: './src/page2.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js'
    },
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    }
};

chunks: 'all' 表示对所有类型的 chunks(包括初始 chunk、异步 chunk 等)都进行公共代码提取。Webpack 会自动分析各个 chunk 之间的依赖关系,将公共代码提取出来。默认情况下,提取出来的公共代码会放在一个名为 vendors~[name].js 的文件中([name] 是入口的名称)。

我们还可以对 splitChunks 进行更详细的配置,例如:

module.exports = {
    entry: {
        page1: './src/page1.js',
        page2: './src/page2.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js'
    },
    optimization: {
        splitChunks: {
            chunks: 'all',
            name: 'common',
            minSize: 30000,
            minChunks: 2,
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name:'vendors',
                    chunks: 'all'
                }
            }
        }
    }
};
  • name:指定提取出来的公共代码文件的名称为 common.js
  • minSize:表示只有当公共代码的大小超过 30000 字节(约 30KB)时才会进行提取,这样可以避免提取过小的公共代码导致文件数量过多。
  • minChunks:表示至少有两个 chunk 依赖的代码才会被提取为公共代码。
  • cacheGroups:可以对不同来源的代码进行分组提取。在上述示例中,vendor 组专门用于提取来自 node_modules 的代码,生成 vendors.js 文件。

多出口配置的应用场景

多出口配置通常与多入口配置结合使用。在某些情况下,我们可能希望将不同入口打包后的文件输出到不同的目录结构或者按照不同的命名规则。例如,对于一个前后端分离的项目,前端有不同类型的页面,可能希望将管理后台的页面打包输出到一个单独的目录,而前台页面输出到另一个目录。

假设我们有两个入口 adminPagefrontPage,希望将 adminPage 打包后的文件输出到 dist/admin 目录,frontPage 打包后的文件输出到 dist/front 目录。可以这样配置:

const path = require('path');

module.exports = {
    entry: {
        adminPage: './src/adminPage.js',
        frontPage: './src/frontPage.js'
    },
    output: {
        filename: '[name].bundle.js',
        path: function (data) {
            if (data.chunk.name === 'adminPage') {
                return path.resolve(__dirname, 'dist/admin');
            }
            return path.resolve(__dirname, 'dist/front');
        }
    }
};

在上述配置中,通过 output.path 的函数形式,根据入口的名称动态地决定输出目录。这样就实现了多出口的配置。

优化多入口与多出口的打包性能

  1. 代码压缩 代码压缩可以显著减小打包文件的体积,提高加载性能。Webpack 可以通过插件来实现代码压缩。在 Webpack 4 中,默认使用 terser-webpack-plugin 来压缩 JavaScript 代码。如果要压缩 CSS 代码,可以使用 optimize-css-assets-plugin

以下是配置 terser-webpack-pluginoptimize-css-assets-plugin 的示例:

const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-plugin');

module.exports = {
    // 其他配置...
    optimization: {
        minimizer: [
            new TerserPlugin(),
            new OptimizeCSSAssetsPlugin({})
        ]
    }
};

TerserPlugin 会对 JavaScript 代码进行压缩,去除不必要的空格、注释等。OptimizeCSSAssetsPlugin 则会对 CSS 代码进行压缩。

  1. Tree Shaking Tree Shaking 是一种优化技术,它可以去除未使用的代码。在 Webpack 中,要实现 Tree Shaking,需要满足以下几个条件:
  • 使用 ES6 模块语法(importexport)。
  • 配置 mode'production',因为在生产模式下,Webpack 会默认启用 Tree Shaking。

例如,假设我们有一个 utils.js 文件:

// utils.js
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

page1.js 中只使用了 add 函数:

// page1.js
import { add } from './utils.js';

console.log(add(1, 2));

当 Webpack 进行打包时,在生产模式下,它会分析 page1.js 的依赖关系,发现 subtract 函数未被使用,从而在打包文件中去除 subtract 函数的代码,实现 Tree Shaking。

  1. 按需加载 对于多入口项目,按需加载是一种重要的性能优化手段。它可以避免在页面加载时一次性加载所有的代码,而是根据用户的操作或者页面的需求动态加载所需的代码。

在 Webpack 中,使用 import() 语法可以实现动态导入,从而实现按需加载。例如,假设 page1.js 中有一个按钮,点击按钮后需要加载一个新的模块 newModule.js

// page1.js
document.getElementById('loadButton').addEventListener('click', function () {
    import('./newModule.js').then(module => {
        module.doSomething();
    });
});

Webpack 会将 newModule.js 单独打包成一个 chunk,当按钮被点击时,才会加载这个 chunk。这样可以提高页面的初始加载速度,提升用户体验。

  1. 使用缓存 合理使用缓存可以减少重复的打包过程,提高开发和构建效率。Webpack 提供了一些配置选项来实现缓存。

在开发环境中,可以使用 webpack-dev-server,它默认启用了内存缓存,使得代码修改后能够快速重新编译并热更新。

在生产环境中,可以通过 cache-loader 来实现缓存。cache-loader 会将 loader 的执行结果缓存到磁盘,下次构建时如果文件没有变化,就可以直接使用缓存的结果,而不需要重新执行 loader。

以下是使用 cache-loader 的示例:

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    'cache-loader',
                    'babel-loader'
                ],
                include: path.resolve(__dirname,'src')
            }
        ]
    }
};

在上述配置中,cache-loader 会在 babel-loader 之前执行,将 babel-loader 的处理结果缓存起来,提高构建速度。

多入口与多出口配置中的资源管理

  1. 图片和字体资源 在多入口项目中,图片和字体等资源的管理同样重要。Webpack 可以通过 file-loaderurl-loader 来处理这些资源。

file-loader 会将文件复制到输出目录,并返回文件的 URL。url-loader 则会根据文件的大小决定是将文件转换为 base64 编码的字符串嵌入到代码中,还是像 file-loader 一样复制文件并返回 URL。

以下是配置 url-loader 的示例:

module.exports = {
    module: {
        rules: [
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192,
                            name: 'images/[name].[ext]'
                        }
                    }
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: 'fonts/[name].[ext]'
                        }
                    }
                ]
            }
        ]
    }
};

在上述配置中,对于小于 8192 字节(约 8KB)的图片,url-loader 会将其转换为 base64 编码嵌入到代码中,大于 8KB 的图片则会复制到 dist/images 目录下。字体文件则会被复制到 dist/fonts 目录下。

  1. CSS 和样式资源 对于 CSS 资源,Webpack 通常使用 style-loadercss-loader 来处理。css-loader 负责解析 CSS 文件中的 @importurl() 等语句,style-loader 则会将 CSS 代码插入到 HTML 页面的 <style> 标签中。

如果项目中使用了预处理器(如 Sass、Less 等),还需要相应的 loader,例如 sass-loaderless-loader 等。

以下是一个完整的 CSS 相关 loader 配置示例:

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                  'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.scss$/,
                use: [
                  'style-loader',
                    'css-loader',
                  'sass-loader'
                ]
            }
        ]
    }
};

在上述配置中,对于 .css 文件,先使用 css-loader 解析,再使用 style-loader 插入到页面。对于 .scss 文件,依次使用 sass-loader 将 Sass 转换为 CSS,再经过 css-loaderstyle-loader 处理。

在多入口项目中,不同入口可能有各自独立的 CSS 文件,也可能有公共的 CSS 文件。对于公共的 CSS 文件,可以通过 extract-text-webpack-plugin(Webpack 4 之前)或 mini - css - extract - plugin(Webpack 4 及之后)将 CSS 提取出来,生成单独的 CSS 文件,避免在每个入口的 JavaScript 文件中重复包含 CSS 代码。

以下是使用 mini - css - extract - plugin 的示例:

const MiniCssExtractPlugin = require('mini - css - extract - plugin');

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].css'
        })
    ]
};

在上述配置中,MiniCssExtractPlugin 会将 CSS 从 JavaScript 中提取出来,生成以入口名称命名的 CSS 文件,如 page1.csspage2.css 等。

多入口与多出口配置在实际项目中的案例分析

假设我们正在开发一个电商项目,该项目包含首页、商品列表页、商品详情页、购物车页和用户中心页等多个页面。每个页面都有自己独立的业务逻辑和样式。

  1. 入口配置 首先,我们在 webpack.config.js 中配置多入口:
const path = require('path');

module.exports = {
    entry: {
        index: './src/pages/index.js',
        productList: './src/pages/productList.js',
        productDetail: './src/pages/productDetail.js',
        cart: './src/pages/cart.js',
        userCenter: './src/pages/userCenter.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js'
    }
};

这样,每个页面都作为一个独立的入口进行打包。

  1. 公共代码提取 项目中可能会使用一些公共的库,如 lodashaxios 等,同时也有一些自定义的公共工具函数。我们使用 splitChunks 来提取公共代码:
module.exports = {
    // 入口和输出配置...
    optimization: {
        splitChunks: {
            chunks: 'all',
            name: 'common',
            minSize: 30000,
            minChunks: 2,
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name:'vendors',
                    chunks: 'all'
                }
            }
        }
    }
};

通过上述配置,公共代码会被提取到 common.js 文件中,来自 node_modules 的代码会被提取到 vendors.js 文件中。

  1. 资源管理 对于图片、字体和 CSS 等资源,我们按照前面提到的方式进行配置。例如,配置 url-loader 处理图片:
module.exports = {
    module: {
        rules: [
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192,
                            name: 'images/[name].[ext]'
                        }
                    }
                ]
            }
        ]
    }
};

配置 mini - css - extract - plugin 提取 CSS:

const MiniCssExtractPlugin = require('mini - css - extract - plugin');

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].css'
        })
    ]
};
  1. 性能优化 为了提高性能,我们开启代码压缩,在 optimization.minimizer 中配置 TerserPluginOptimizeCSSAssetsPlugin
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-plugin');

module.exports = {
    // 其他配置...
    optimization: {
        minimizer: [
            new TerserPlugin(),
            new OptimizeCSSAssetsPlugin({})
        ]
    }
};

同时,我们在生产环境中启用 Tree Shaking,通过将 mode 设置为 'production'

module.exports = {
    // 其他配置...
    mode: 'production'
};

在开发环境中,我们使用 webpack-dev-server 提供的热更新功能,加快开发速度。

通过以上一系列的配置,我们可以有效地管理电商项目中的多入口与多出口,优化项目的性能,提高用户体验。

总结多入口与多出口优化配置的要点

  1. 入口与出口的合理规划 根据项目的功能模块和页面结构,准确地定义多入口和多出口。入口的划分要考虑业务逻辑的独立性,出口的配置要满足项目的部署和文件组织需求。

  2. 公共代码提取 利用 splitChunks 等工具,合理提取公共代码,减小打包文件的体积。注意配置 minSizeminChunks 等参数,避免过度提取或提取不足。

  3. 性能优化手段 综合运用代码压缩、Tree Shaking、按需加载和缓存等性能优化手段,提高项目的加载速度和运行效率。在开发和生产环境中,根据不同的需求进行针对性的配置。

  4. 资源管理 妥善管理图片、字体、CSS 等资源,选择合适的 loader 和插件,确保资源的正确处理和输出。对于 CSS 资源,要合理提取,避免重复和冗余。

通过深入理解和熟练运用这些要点,我们可以在前端项目中实现高效的 Webpack 多入口与多出口的优化配置,打造出性能卓越的前端应用。