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

Webpack JavaScript 代码打包:从入门到优化

2021-09-201.4k 阅读

一、Webpack 基础概念与安装

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

1.1 安装 Node.js

在开始使用 Webpack 之前,确保你已经安装了 Node.js。Node.js 自带 npm(Node Package Manager),它用于安装和管理项目依赖。你可以从 Node.js 官方网站 下载并安装最新版本的 Node.js。

安装完成后,打开终端或命令提示符,输入以下命令验证安装:

node -v
npm -v

1.2 初始化项目

在你的项目目录下,打开终端并运行以下命令初始化一个新的 Node.js 项目:

npm init -y

-y 选项会使用默认设置快速初始化项目,生成一个 package.json 文件,该文件用于管理项目的依赖和脚本。

1.3 安装 Webpack 和 Webpack - CLI

Webpack 有两个核心包:webpackwebpack - CLIwebpack 是实际执行打包的核心库,而 webpack - CLI 提供了在命令行中与 Webpack 交互的工具。

在项目目录下运行以下命令安装这两个包:

npm install webpack webpack - CLI --save - dev

--save - dev 选项表示将这两个包安装为开发依赖,它们只会在开发过程中使用,而不会包含在生产环境中。

二、Webpack 基本配置与打包 JavaScript 代码

安装好 Webpack 后,我们需要配置它以告诉它如何打包我们的 JavaScript 代码。

2.1 创建 Webpack 配置文件

在项目根目录下创建一个名为 webpack.config.js 的文件,这是 Webpack 的主要配置文件。以下是一个基本的 Webpack 配置示例:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    }
};
  • entry:指定打包的入口文件,这里是 src/index.js。这是整个应用程序的起点,Webpack 会从这个文件开始递归解析依赖。
  • output:指定打包后的输出路径和文件名。path.resolve(__dirname, 'dist') 使用 path 模块获取项目根目录下的 dist 文件夹作为输出路径,filename 设置输出文件名为 bundle.js

2.2 编写示例 JavaScript 代码

src 目录下创建 index.js 文件,并编写一些简单的 JavaScript 代码:

// src/index.js
function add(a, b) {
    return a + b;
}

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

2.3 运行 Webpack 进行打包

package.json 文件的 scripts 字段中添加以下脚本:

{
    "scripts": {
        "build": "webpack --config webpack.config.js"
    }
}

然后在终端中运行以下命令进行打包:

npm run build

Webpack 会根据配置文件的设置,将 src/index.js 及其依赖打包成 dist/bundle.js

三、Webpack 加载器(Loaders)

Webpack 本身只能理解 JavaScript 和 JSON 文件。加载器(Loaders)让 Webpack 能够处理其他类型的文件,并将它们转换为有效的模块,以便在依赖图中使用。

3.1 Babel 加载器(babel - loader)

Babel 是一个 JavaScript 编译器,它允许我们使用最新的 JavaScript 语法,并将其转换为旧版本的 JavaScript,以确保在各种浏览器中都能运行。

首先,安装必要的 Babel 包:

npm install babel - loader @babel/core @babel/preset - env --save - dev
  • babel - loader:Webpack 和 Babel 之间的桥梁,用于在 Webpack 中使用 Babel。
  • @babel/core:Babel 的核心库。
  • @babel/preset - env:一个智能预设,根据目标浏览器或运行时环境自动确定需要转换的 JavaScript 语法。

webpack.config.js 中添加 module.rules 配置:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env']
                    }
                }
            }
        ]
    }
};
  • test:指定匹配的文件扩展名,这里匹配所有 .js 文件。
  • exclude:指定排除的目录,这里排除 node_modules 目录,因为我们不需要处理第三方库的代码。
  • use:指定使用的加载器及其配置。

3.2 CSS 加载器(css - loader、style - loader)

要在 Webpack 项目中处理 CSS 文件,我们需要使用 css - loaderstyle - loadercss - loader 用于解析 CSS 文件中的 @importurl() 等语句,style - loader 则将 CSS 插入到 DOM 中。

安装这两个加载器:

npm install css - loader style - loader --save - dev

webpack.config.js 中添加 CSS 加载器配置:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env']
                    }
                }
            },
            {
                test: /\.css$/,
                use: ['style - loader', 'css - loader']
            }
        ]
    }
};

加载器的执行顺序是从右到左(从下到上),所以 css - loader 先处理 CSS 文件,然后 style - loader 将其插入到 DOM 中。

四、Webpack 插件(Plugins)

Webpack 插件用于执行更广泛的任务,例如优化输出文件、管理资源和注入环境变量等。

4.1 HTMLWebpackPlugin

HTMLWebpackPlugin 会自动生成一个 HTML 文件,并将打包后的 JavaScript 文件插入到该 HTML 文件中。

安装插件:

npm install html - webpack - plugin --save - dev

webpack.config.js 中添加插件配置:

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

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env']
                    }
                }
            },
            {
                test: /\.css$/,
                use: ['style - loader', 'css - loader']
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ]
};

这里我们指定了一个模板文件 src/index.html,插件会根据这个模板生成最终的 HTML 文件,并将打包后的 JavaScript 文件自动插入。

4.2 CleanWebpackPlugin

CleanWebpackPlugin 用于在每次打包前清除输出目录,确保输出目录中只包含最新的打包结果。

安装插件:

npm install clean - webpack - plugin --save - dev

webpack.config.js 中添加插件配置:

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

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env']
                    }
                }
            },
            {
                test: /\.css$/,
                use: ['style - loader', 'css - loader']
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ]
};

CleanWebpackPlugin 的参数是要清除的目录数组,这里我们指定清除 dist 目录。

五、Webpack 模式(Mode)

Webpack 有三种模式:developmentproductionnone。每种模式都会启用不同的默认优化和插件。

5.1 development 模式

development 模式下,Webpack 会启用以下特性:

  • 开发环境优化:启用 NamedChunksPluginNamedModulesPlugin,这有助于在开发过程中更容易调试代码,因为它们会使用模块和 chunk 的原始名称,而不是哈希值。
  • 不压缩代码:打包后的代码不会进行压缩,这样更易于调试。

webpack.config.js 中设置模式为 development

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

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env']
                    }
                }
            },
            {
                test: /\.css$/,
                use: ['style - loader', 'css - loader']
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
    mode: 'development'
};

5.2 production 模式

production 模式下,Webpack 会启用以下优化:

  • 代码压缩:使用 TerserPlugin 对 JavaScript 代码进行压缩,去除多余的空格、注释等,减小文件体积。
  • 移除未使用代码:启用 UglifyJsPluginOptimizeCSSAssetsPlugin,移除未使用的代码和 CSS 规则。

webpack.config.js 中设置模式为 production

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

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env']
                    }
                }
            },
            {
                test: /\.css$/,
                use: ['style - loader', 'css - loader']
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
    mode: 'production'
};

5.3 none 模式

none 模式下,Webpack 不会启用任何默认优化,所有优化和插件都需要手动配置。这种模式通常用于需要完全自定义优化策略的场景。

webpack.config.js 中设置模式为 none

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

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env']
                    }
                }
            },
            {
                test: /\.css$/,
                use: ['style - loader', 'css - loader']
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
    mode: 'none'
};

六、Webpack 代码分割(Code Splitting)

随着项目的增长,打包后的文件体积可能会变得很大,影响页面加载性能。代码分割是一种优化技术,它允许我们将代码分割成多个 chunk,按需加载。

6.1 使用 SplitChunksPlugin 进行代码分割

SplitChunksPlugin 是 Webpack 内置的代码分割插件,它可以自动提取所有模块中的公共代码,并将其分割成单独的 chunk。

webpack.config.js 中配置 SplitChunksPlugin

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

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env']
                    }
                }
            },
            {
                test: /\.css$/,
                use: ['style - loader', 'css - loader']
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    },
    mode: 'production'
};

chunks: 'all' 表示对所有类型的 chunk(包括异步和同步)都进行代码分割。Webpack 会自动提取公共代码,并生成单独的文件。

6.2 动态导入(Dynamic Imports)

动态导入是一种在运行时按需加载模块的方式,通过 import() 语法实现。

例如,我们有一个 src/utils.js 文件:

// src/utils.js
export function greet() {
    return 'Hello, Webpack!';
}

src/index.js 中动态导入 utils.js

// src/index.js
function add(a, b) {
    return a + b;
}

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

import('./utils.js').then(({ greet }) => {
    console.log(greet());
});

Webpack 会将 utils.js 分割成一个单独的 chunk,只有当 import('./utils.js') 被执行时才会加载这个 chunk。

七、Webpack 性能优化

除了前面提到的代码分割和模式优化外,还有一些其他的性能优化技巧。

7.1 优化 Babel 配置

Babel 的转换过程可能会比较耗时,可以通过以下方式优化:

  • 配置 cacheDirectory:在 babel - loaderoptions 中添加 cacheDirectory: true,这样 Babel 会缓存转换结果,下次构建时如果文件没有变化则直接使用缓存,加快构建速度。
{
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
        loader: 'babel - loader',
        options: {
            presets: ['@babel/preset - env'],
            cacheDirectory: true
        }
    }
}
  • 使用 @babel/plugin - transform - runtime:这个插件可以避免在每个文件中重复引入一些辅助函数,减小打包后的文件体积。首先安装插件:
npm install @babel/plugin - transform - runtime @babel/runtime --save - dev

然后在 babel - loaderoptions 中添加插件配置:

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
        loader: 'babel - loader',
        options: {
            presets: ['@babel/preset - env'],
            plugins: ['@babel/plugin - transform - runtime']
        }
    }
}

7.2 优化 CSS 加载

  • 使用 MiniCssExtractPluginMiniCssExtractPlugin 可以将 CSS 从 JavaScript 中提取出来,生成单独的 CSS 文件,这样浏览器可以并行加载 CSS 和 JavaScript,提高页面加载性能。

首先安装插件:

npm install mini - css - extract - plugin --save - dev

然后在 webpack.config.js 中配置:

const path = require('path');
const HtmlWebpackPlugin = require('html - webpack - plugin');
const CleanWebpackPlugin = require('clean - webpack - plugin');
const MiniCssExtractPlugin = require('mini - css - extract - plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env'],
                        cacheDirectory: true
                    }
                }
            },
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css - loader']
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin({
            filename: 'styles.css'
        })
    ],
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    },
    mode: 'production'
};

7.3 图片优化

对于图片资源,可以使用 image - webpack - loader 对图片进行压缩。

安装插件:

npm install image - webpack - loader --save - dev

webpack.config.js 中添加图片加载器配置:

const path = require('path');
const HtmlWebpackPlugin = require('html - webpack - plugin');
const CleanWebpackPlugin = require('clean - webpack - plugin');
const MiniCssExtractPlugin = require('mini - css - extract - plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel - loader',
                    options: {
                        presets: ['@babel/preset - env'],
                        cacheDirectory: true
                    }
                }
            },
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css - loader']
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'file - loader',
                        options: {
                            name: 'images/[name].[ext]'
                        }
                    },
                    {
                        loader: 'image - webpack - loader',
                        options: {
                            mozjpeg: {
                                progressive: true,
                                quality: 65
                            },
                            // optipng.enabled: false will disable optipng
                            optipng: {
                                enabled: false
                            },
                            pngquant: {
                                quality: [0.65, 0.90],
                                speed: 4
                            },
                            gifsicle: {
                                interlaced: false
                            },
                            // the webp option will enable WEBP
                            webp: {
                                quality: 75
                            }
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin({
            filename: 'styles.css'
        })
    ],
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    },
    mode: 'production'
};

image - webpack - loader 支持多种图片格式的压缩,通过配置不同的选项可以调整压缩的程度和质量。

通过以上这些优化技巧,可以显著提高 Webpack 项目的性能,使得打包后的代码体积更小,加载速度更快。在实际项目中,需要根据具体情况选择合适的优化策略,并不断进行测试和调整,以达到最佳的性能效果。同时,Webpack 生态系统不断发展,新的工具和优化方法也在不断涌现,开发者需要持续关注并学习,以保持项目的竞争力。