问题 1: 请简要描述 Webpack 的核心概念
Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。
它能将各种类型的资源(如 JavaScript、CSS、图片、字体等)视为模块,通过分析模块间的依赖关系,将其打包成适合在浏览器中运行的静态资源。
主要作用包括:
模块打包:- 把分散的模块整合为一个或多个 bundle 文件;
资源转换:- 如使用 babel-loader 将 ES6 + 代码转换为 ES5、
- css-loader 将 SCSS 转换为 CSS 等;
代码优化- 如 Tree Shaking 去除未使用代码、压缩代码减小文件体积;
代码拆分- 将代码按需求拆分成多个块,实现按需加载,提升性能。
主要包括:
- 入口(entry):从入口开始寻找依赖
构建依赖图- 单入口:
- 多入口:
多用于多页面或者分离第三方库
- 输出(output)
- path: 指定输出的路径(绝对路径)
- filename:指定输出的文件名,可以使用
[name] [hash]占位符- 可以用hash来确保文件名的唯一性
- 缓存优化:浏览器会对已经访问过的文件进行缓存
- hash可以作为文件版本的一种标识
- 避免文件命名冲突导致的覆盖
- 可以用hash来确保文件名的唯一性
- 加载器(loader):预处理,将
不同文件类型转换为Webpack 能处理的模块- 多个:从右到左执行
- 插件(plugin):执行更广泛的任务,如打包优化、资源管理、注入环境变量等
- HtmlWebpackPlugin:自动生成 HTML 文件,并将打包后的 JavaScript 和 CSS 文件引入其中,方便部署
- MiniCssExtractPlugin:将 CSS 从 JavaScript 中提取出来,生成单独的 CSS 文件
- CleanWebpackPlugin:在每次构建前清理输出目录,确保输出目录只包含最新的文件
问题 2: Webpack 的热模块更新(HMR)是什么?它是如何工作的?
工作原理:
- Webpack 在开发服务器中内置了 HMR 服务器。
- 当
文件被修改,Webpack会重新编译该模块。 - 编译后的模块会被发送到浏览器端。
- 浏览器端的 HMR 运行时会接收更新的模块并替换旧模块,同时保持应用的状态。
问题3:什么是 Tree Shaking?Webpack 如何实现 Tree Shaking?
去除死代码
开启方式:
- webpack4 以上mode为production时会自动开启
- 也可通过
optimization.minimize手动配置TerserPlugin等优化工具
Webpack 实现 Tree Shaking 需满足以下条件:
- 使用 ES6 模块语法,因为 ES6 模块是静态可分析的,Webpack 能够确定哪些模块和代码被实际使用。
- 在
mode设置为'production'时,Webpack 默认使用TerserPlugin进行压缩,它会自动进行 Tree Shaking。
问题4: 如何在 Webpack 中实现代码拆分?
-
多入口方式:在
entry中配置多个入口,每个入口对应一个独立的输出文件,适用于多页面应用。 -
使用
splitChunks插件:通过optimization.splitChunks配置,可将公共代码提取到单独的 chunk 中。 -
动态导入:使用 ES2020 的动态导入语法
import(),Webpack 会自动将动态导入的模块拆分成单独的 chunk。例如:const module = await import('./module.js');。
entry: {
page1: './src/page1.js',
page2: './src/page2.js'
}
optimization: {
splitChunks: {
// 这会将所有入口 chunk 中的公共模块拆分出来。
chunks: 'all'
}
}
// 方式2:还可通过`cacheGroups`进行更细粒度的控制,如提取第三方库到`vendor` chunk 。
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
name: 'chunk-vendors',
minChunks: 1000,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
}
}
}
}
问题5:如何在 Webpack 中配置不同环境(开发、生产)的差异化配置?
- 使用
mode配置:Webpack 会根据不同的mode启用默认的优化和插件。- 例如,
production模式下会启用压缩和 Tree Shaking 等。 - 同时,还可在
configureWebpack或chainWebpack中根据process.env.NODE_ENV进行差异化配置,
- 例如,
- 使用多个配置文件:创建
webpack.common.js存放公共配置,webpack.dev.js存放开发环境配置,webpack.prod.js存放生产环境配置
问题6:Webpack 中的 resolve.alias 有什么作用?如何使用?
- 用于
配置路径别名,通过为常用的目录或模块设置别名,可简化模块导入路径,提高代码的可维护性。
问题7:在 Webpack 中如何优化图片资源的加载?
- 使用
url-loader或file-loader:url-loader可将小图片转换为 Data URL 嵌入到代码中,减少请求数量。file-loader用于处理较大图片,将其输出到指定目录并返回路径
- 图片压缩:使用
image-webpack-loader插件,在构建过程中对图片进行压缩 - 响应式图片:在 HTML 中使用
<picture>标签或srcset属性,根据不同的设备屏幕大小加载合适尺寸的图片,Webpack 可配合html - loader等进行处理
问题8: 常用的一些webpack loader都有哪些?
- babel-loader
- css-loader:处理@import 和url()语句,将css代码解析成模块,可以和css和
- style-loader:将css样式插入到DOM中
- less-loader
- sass-loader
- file-laoder:将文件移动到输出目录,并返回文件的相对路径
- url-loader: 可以将一些小文件转换为dataurl,直接嵌入到打包后的文件中,减少http请求数量
- postcss-loader
- ts-loader
- eslint-loader
问题9: 常用的一些webpack plugin都有哪些?
- HtmlWebpackPlugin:自动生成html文件
- CleanWebpackPlugin
- MiniCssExtractPlugin
- UglifyJsPlugin
- OptimizeCssAssetsPlugin
webpack中常见的三种哈希类型
- 全量hash:整个项目内容没变,就不会
- chunk哈希:具体代码块变化了,就改变
- 内容hash:文件内容本身生成哈希值,文件不变,哈希就不变
vue-cli-service底层是基于 Webpack 进行构建的,它将 Vue 项目的开发和构建需求转化为相应的 Webpack 配置,并使用 Webpack 进行文件的编译、打包和优化。
配置
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/main.js',
vendor: './src/vendor.js'
},
devServer: {
hot: true
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash].js',
publicPath: '/',
},
resolve: {
alias: {
vue$: 'vue/dist/vue.esm.js',
'@': path.resolve(__dirname, 'src'),
},
extensions: ['.js', '.vue', '.json'],
},
module: {
rules: [
{
test: /.vue$/,
use: 'vue-loader',
},
{
test: /.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
],
},
{
test: /.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
],
},
{
test: /.(png|jpe?g|gif|svg)(?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name].[hash:7].[ext]',
},
},
],
},
{
test: /.(woff2?|eot|ttf|otf)(?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]',
},
},
],
},
],
},
plugins: [
new VueLoaderPlugin(),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html',
inject: 'body',
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
}),
new webpack.HotModuleReplacementPlugin(),
],
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
},
},
}),
new OptimizeCSSAssetsPlugin({}),
],
splitChunks: {
chunks: 'all',
},
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8080,
hot: true,
historyApiFallback: true,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': '',
},
},
},
},
};