1. webpack是什么
Webpack是一个现代化的JavaScript应用程序打包工具,它可以将多个模块打包成一个或多个文件,使得应用程序的加载速度更快、代码更加优化。Webpack支持各种各样的文件类型,包括JavaScript、CSS、图片、字体等等。它还支持各种各样的插件和加载器,可以让开发者更加灵活地配置和定制打包过程。
2. webpack的基本使用
(1)初始化包环境
npm init
或者
yarn init
初始化包环境我们就得到了一个 package.json 文件 , 该文件就是用来保存,运行脚本 , 依赖包 , 版本信息等等配置文件。
然后,我们需要在根目录下创建一个 webpack.config.js文件 ,该文件就是编写webpack的一些配置的文件
(2)安装webpack
npm install webpack webpack-cli webpack-dev-server -D
这其实是一条合并的命令,分开来写就是
npm install webpack -D
npm install webpack-cli -D
npm install webpack-dev-server -D
-
install 是安装的意思; -D 表示安装到本地开发环境,不使用全局安装是因为每个项目可能用的 webpack 版本不一样导致冲突
-
第一条安装的是 webpack 的核心文件,就好比是安装包
-
第二条是让 webpack 支持类似 npm run dev 这种命令行命令
-
第三条安装的是可以使 webpack 支持实时编译的拓展包
(3)配置script
scripts: {
"build": "webpack"
}
(4)运行打包命令
yarn build
#或者 npm run build
默认会打包src下的index.js
3. webpack的配置
3.1 基础配置
1、入口
入口指 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
默认值是 ./src/index.js,但你可以通过配置 entry 属性,来指定一个(或多个)不同的入口起点。例如:
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js',
};
2、出口
出口属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
你可以通过在配置中指定一个 output 字段,来配置这些处理过程:
webpack.config.js
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
};
在上面的示例中,我们通过 output.filename 和 output.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里
3、修改package.json
自定义打包命令 - 让webpack使用配置文件
"scripts": {
"build": "webpack"
}
重点: 所有要被打包的资源都要跟入口产生直接/间接的引用关系
3.2 插件配置
loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。
想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
module: {
rules: [{ test: /.txt$/, use: 'raw-loader' }],
},
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};
(1). HtmlWebpackPlugin:用于生成HTML文件,并自动将打包后的资源文件(如JS、CSS)添加到HTML中。它还提供了一些选项,例如模板、文件名等。配置示例:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html', // HTML模板文件路径
filename: 'index.html' // 生成的HTML文件名
})
]
};
(
(2). MiniCssExtractPlugin:用于将CSS代码提取为独立的文件,并支持CSS文件的压缩和代码分割。配置示例
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ...
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css', // 提取的CSS文件名
chunkFilename: '[id].css' // 代码分割的CSS文件名
})
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
}
};
(3). CleanWebpackPlugin:用于清理构建目录中的旧文件,防止构建产生的垃圾文件堆积。配置示例:
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// ...
plugins: [
new CleanWebpackPlugin()
]
};
(4). DefinePlugin:用于在构建过程中定义全局变量,可以用于注入环境变量或设置全局常量。配置示例:
const webpack = require('webpack');
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'), // 注入环境变量
'API_BASE_URL': JSON.stringify('https://api.example.com') // 设置全局常量
})
]
};
(5). VueLoaderPlugin:用于解析Vue单文件组件(.vue文件),并将其转换为可被Webpack处理的模块。配置示例:
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
// ...
plugins: [
new VueLoaderPlugin()
],
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
}
};
3.3 mode模式
webpack中的mode是一个配置选项,用于指定构建的模式。它有三个可选的值:development、production和none。其默认值为 production。
development模式会将process.env.NODE_ENV的值设置为development。在这个模式下,webpack会开启一些针对开发环境的优化,例如添加注释、更易于调试的源映射等。同时,它也会启用一些用于开发的默认配置,如更快的构建速度和更好的开发体验。production模式会将process.env.NODE_ENV的值设置为production。在这个模式下,webpack会启用一系列针对生产环境的优化和压缩策略,例如代码压缩、去除无用代码、作用域提升等。这能够最大程度地减小打包文件的体积,提高加载速度,并为生产环境提供最佳性能。none模式意味着不启用任何默认配置。在这个模式下,你需要自己定义所有的优化和配置。
mode的配置方式非常简单。只需在webpack配置文件中添加如下代码:
module.exports = {
mode: 'development' // 或 'production' 或 'none'
// ...
};
3.4 webpack-dev-server
3.4.1 webpack-dev-server介绍
webpack-dev-server是Webpack提供的一个开发服务器,它能够实时监听文件的改动并自动重新构建项目,以便开发人员可以在开发过程中快速预览和调试应用程序的变化。
以下是webpack-dev-server的一些特点和配置方式:
实时刷新:webpack-dev-server使用WebSocket在浏览器和服务器之间创建一个实时连接,当文件发生改变时,它能够自动重新构建并刷新浏览器页面,使开发人员能够立即看到最新的更改。
热模块替换(HMR) :webpack-dev-server支持热模块替换,在不刷新整个页面的情况下,只更新发生更改的模块。这极大地提高了开发效率,可以在不丢失应用程序状态的情况下进行快速迭代开发。
配置方式:可以通过webpack配置文件或命令行选项进行配置。通过webpack配置文件的方式,可以在配置文件中设置devServer对象来配置webpack-dev-server。例如:
module.exports = {
// ...
devServer: {
port: 8080, // 指定开发服务器的端口号,默认为8080
contentBase: './dist', // 指定静态文件目录
hot: true // 启用热模块替换
}
};
此外,还可以使用命令行选项进行配置,例如npx webpack-dev-server --port 8080 --hot
代理:webpack-dev-server支持代理功能,可以将请求代理到其他服务器。这在开发过程中处理跨域资源请求时非常有用,可以通过配置proxy选项来设置代理。例如:
module.exports = {
// ...
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com',
pathRewrite: { '^/api': '' }
}
}
}
};
上述配置表示将以/api开头的请求代理到http://api.example.com,并将/api路径重写为空。
3.4.2 安装webpack-dev-server
npm install --save-dev webpack-dev-server
3.4.3 修改配置文件
告知 dev server,从什么位置查找文件: webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
print: './src/print.js',
},
devtool: 'inline-source-map',
+ devServer: {
+ static: './dist',
+ },
plugins: [
new HtmlWebpackPlugin({
title: 'Development',
}),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
+ optimization: {
+ runtimeChunk: 'single',
+ },
};
因为在示例中单个 HTML 页面有多个入口,所以添加了 optimization.runtimeChunk: 'single' 配置。没有这个配置的话,我们可能会遇到问题,具体配置根据项目进行调整。
package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"watch": "webpack --watch",
+ "start": "webpack serve --open",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^4.5.0",
"webpack": "^5.4.0",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"lodash": "^4.17.20"
}
}
3.5 常见问题
3.5.1 CSS文件未生效或样式丢失
如果在项目中的CSS文件未应用或样式丢失,可能原因是配置有误或加载顺序不正确。解决方法包括:
- 确保在Webpack配置中正确配置了CSS相关的loader,例如css-loader和style-loader。
- 检查Webpack Loader的加载顺序,确保style-loader在css-loader之前。
- 对于使用MiniCssExtractPlugin提取CSS文件的情况,确保MiniCssExtractPlugin.loader在其他loader之前,并在插件配置中设置正确的输出文件名。
使用css-loader和style-loader:
yarn add style-loader css-loader -D
css-loader负责解析CSS文件。而style-loader将解析后的CSS以<style>标签的形式插入到HTML文件中。这种方式适用于较小的项目或开发环境,配置示例:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
使用MiniCssExtractPlugin:MiniCssExtractPlugin可以将CSS提取为独立的文件,而不是以<style>标签的形式插入到HTML中,适用于生产环境。配置示例:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
})
]
};
3.5.2 处理CSS预处理器出错
处理预处理器(例如Sass、Less) :
yarn add less less-loader -D
如果你使用Sass、Less等预处理器,需要安装对应的loader和插件,并在Webpack配置中进行相应的配置。以处理Sass为例,需要安装sass-loader和node-sass模块,然后修改配置:
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}
]
}
};
这些配置可以根据具体需求和项目情况进行调整和扩展。
3.5.3 引用的图片或字体文件无法正确加载
当CSS文件中引用了图片或字体文件时,可能会出现路径错误或文件无法加载的问题。解决方法包括:
- 在Webpack配置的module.rules中添加相应的loader,例如file-loader或url-loader来处理图片和字体文件的引用,并配置正确的路径解析规则。
- 在CSS文件中使用正确的相对路径或URL来引用图片或字体文件,确保路径正确。
yarn add url-loader file-loader -D
{
test: /\.(png|jpg|gif|jpeg)$/i,
use: [
{
loader: 'url-loader', // 匹配文件, 尝试转base64字符串打包到js中
// 配置limit, 超过8k, 不转, file-loader复制, 随机名, 输出文件
options: {
limit: 8 * 1024,
},
},
],
}
url-loader 把文件转base64 打包进js中, 会有30%的增大, file-loader 把文件直接复制输出
3.5.4 处理字体文件
Webpack可以通过使用合适的loader来处理字体文件。以下是处理常见字体文件的示例配置:
处理WOFF文件:
module.exports = {
module: {
rules: [
{
test: /.woff$/,
use: [
{
loader: 'file-loader',
options: {
outputPath: 'fonts/', // 输出路径
},
},
],
},
],
},
};
你可以使用file-loader来处理WOFF文件,并将它们复制到指定的输出目录中。
处理TTF、EOT和SVG文件:
module.exports = {
module: {
rules: [
{
test: /.(ttf|eot|svg)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
},
},
},
],
},
};
这个配置使用file-loader处理TTF、EOT和SVG文件,并将它们保存到指定的输出目录中。
根据你的项目需求和字体文件类型,你可以按照上述示例进行适当修改和扩展。另外,你还可以使用url-loader来将字体文件转换为Data URI,并在CSS中内联使用,以减少HTTP请求。这样可以减小字体文件的加载时间,但会增加CSS文件的体积。
注意,在使用上述配置之前,确保你已经安装了相应的loader,例如file-loader或url-loader。另外,你也可以根据需要设置不同的选项,如输出路径、文件名等。
3.5.5 处理高版本的JavaScript语法
要让Webpack处理高版本的JavaScript语法,可以使用Babel来转换和兼容这些语法。以下是处理高版本JavaScript语法的示例配置:
安装Babel依赖:
npm install --save-dev @babel/core @babel/preset-env babel-loader
这会安装@babel/core作为Babel的核心库,@babel/preset-env用于转换高版本语法,以及babel-loader用于在Webpack中使用Babel。
在Webpack配置中,添加Babel的loader规则:
module.exports = {
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
};
这个配置会告诉Webpack在处理JavaScript文件时,使用Babel进行转换。.babelrc文件也可以用于存放Babel的配置信息。
创建.babelrc文件并配置Babel的转换规则:
{
"presets": ["@babel/preset-env"]
}
这里使用@babel/preset-env预设来兼容目标环境中所需的JavaScript语法和功能。
运行Webpack进行构建,Babel会根据配置转换和兼容高版本的JavaScript语法。
请注意,转换和兼容高版本JavaScript语法需要根据目标浏览器或环境的要求进行配置。你可以在.babelrc文件或babel-loader的选项中,通过设置targets来指定需要兼容的环境。例如,"targets": "> 0.5%, not dead"表示兼容市场份额大于0.5%的浏览器,并不包括不再活跃的浏览器。
3.5.6 常见优化
在Webpack打包过程中,可能会遇到一些常见的问题。以下是一些可能出现的问题以及解决方法:
打包速度慢:当项目较大或使用复杂的配置时,Webpack打包过程可能会变得缓慢。解决方法包括:
- 使用Webpack的生产模式(
mode: 'production'),以启用代码压缩和优化,提高打包速度。 - 使用Webpack的多线程或并行构建工具,如thread-loader或HappyPack,以并行处理多个任务,提高打包效率。
- 优化Webpack配置,减少不必要的处理和重复构建,例如使用
cache-loader进行缓存或合理设置exclude和include选项。
体积过大:生成的打包文件体积过大可能会导致加载时间延长,降低用户体验。解决方法包括:
- 使用Webpack的优化功能,如代码分割 (
splitChunks)、压缩插件(TerserWebpackPlugin、OptimizeCSSAssetsPlugin)、Tree Shaking等,减小打包体积。 - 检查项目中是否存在未使用的依赖,删除或优化掉未使用的代码。
- 按需加载页面或模块,使用动态导入(Dynamic Import)或懒加载技术,减少初始加载的资源量。
依赖冲突:当项目中使用的第三方库存在版本冲突或兼容性问题时,可能会导致构建错误或运行时错误。解决方法包括:
- 升级或降级冲突的依赖项版本,使其兼容。
- 使用Webpack的
resolve选项指定正确的模块解析路径和顺序,以确保正确的依赖被加载。 - 使用
alias选项为某些依赖项创建别名,避免与其他依赖项的命名冲突。
缓存问题:Webpack使用了文件哈希来生成唯一的文件名,但有时可能会遇到缓存问题,导致旧文件被缓存而无法及时更新。解决方法包括:
- 使用
contenthash代替hash作为输出文件名中的哈希部分,以避免更改其他文件时触发全部文件的重新缓存。 - 使用Webpack的插件,如
CleanWebpackPlugin和HashedModuleIdsPlugin,清除旧的构建文件和模块 ID 的哈希值,以确保文件名和缓存一致性。
4. webpack5的模块联邦
4.1 Webpack5模块联邦是什么
Webpack 5 的模块联邦(Module Federation)是一项功能强大的技术,它允许将多个独立的应用程序或团队的代码组合在一起,实现跨应用程序的模块共享和交互。
4.2 模块联邦的优点
模块联邦的主要思想是将一个应用程序的模块(被称为提供者)暴露给另一个应用程序(被称为消费者)使用,而不需要将所有代码打包在一起。下面是模块联邦的一些优点:
1. 模块共享与代码复用: 模块联邦允许应用程序共享模块,避免重复编写相同的代码。提供者应用程序可以将某些模块暴露给其他应用程序作为共享功能,消费者应用程序可以直接使用这些模块,提高了代码复用性和开发效率。
2. 独立构建和部署: 模块联邦使每个应用程序可以独立进行构建和部署。它们可以具有自己的构建系统和独立的开发流程,而不需要将所有依赖项打包在一起。这样可以提高开发和部署的灵活性和效率。
3. 动态加载和远程模块: 模块联邦支持动态加载模块,应用程序可以在运行时根据需要从其他应用程序中动态加载模块。这也可以与远程服务器结合使用,从远程服务器获取并使用模块。这种动态加载的能力为构建分布式的应用程序提供了更大的灵活性。
4. 管理和隔离依赖: 模块联邦支持对依赖项的版本管理和隔离。每个应用程序可以有自己的依赖版本,从而避免版本冲突和兼容性问题。这有助于管理复杂的项目,保持不同应用程序之间的依赖关系清晰和可控。
4.3 为什么要使用模块联邦
模块联邦提供了一种更灵活、更可扩展的方式来构建应用程序。它适用于大型项目、多个团队或多个应用程序之间的协作开发。使用模块联邦可以提高代码复用性、加速开发速度、降低维护成本,并支持构建分布式、组合性强的应用程序。它使得应用程序之间的边界更加模糊,促进了更好的团队合作和代码共享。
4.4 模块联邦的配置
在Webpack 5的模块联邦中,有两个核心概念:提供者(Provider)和消费者(Consumer)。
提供者(Provider): 提供者是一个Webpack构建的应用程序,它通过模块联邦将自己的模块暴露给其他应用程序使用。提供者通常包含一些希望共享的功能或代码模块。通过配置Webpack的Module Federation插件,提供者可以将这些模块暴露出去,使其他应用程序可以使用它们。
提供者需要将自己的模块通过模块联邦插件中的exposes选项进行配置,指定要提供的模块。它会生成一个模块清单(例如 remoteEntry.js),包含可暴露模块的路径和信息。其他应用程序可以通过远程加载提供者的模块清单,然后动态加载并使用提供者的模块。
消费者(Consumer): 消费者是另一个Webpack构建的应用程序,它希望使用提供者暴露的模块。消费者通过配置Webpack的Module Federation插件,告诉Webpack要使用哪些远程模块。
消费者需要指定远程模块的名称和模块清单文件(例如 remoteEntry.js)的URL。在消费者应用程序的代码中,可以通过动态加载(如import()或require.ensure())提供者的模块,并在需要时使用这些模块。消费者可以以与本地模块相同的方式使用提供者的模块,从而实现代码共享和功能复用。
通过提供者和消费者的配合,模块联邦实现了应用程序之间的模块共享和交互,提供了更大的灵活性和可扩展性。不同应用程序可以独立构建和部署,并共享模块,实现代码复用和协作开发。
在Webpack 5中配置模块联邦需要以下步骤:
提供者(Provider)配置步骤:
在提供者的Webpack配置中添加experiments选项,启用模块联邦:
experiments: {
module: true,
},
在提供者的Webpack配置中,使用ContainerPlugin插件,将要共享的模块暴露给其他应用程序:
const { ModuleFederationPlugin } = require('webpack').container;
plugins: [
//...
new ModuleFederationPlugin({
name: 'myApp',
filename: 'remoteEntry.js',
exposes: {
'./sharedModule': './src/sharedModule',
},
}),
],
其中:
name是提供者应用程序的名称。filename是用于存储提供者的模块清单的文件名。exposes是要共享的模块列表。
执行提供者应用程序的构建命令,生成模块清单(remoteEntry.js)。
消费者(Consumer)配置步骤:
在消费者的Webpack配置中,同样启用实验特性 experiments:
experiments: {
module: true,
},
在消费者的Webpack配置中,使用ContainerPlugin插件,指定提供者应用程序的信息和要使用的模块:
const { ModuleFederationPlugin } = require('webpack').container;
plugins: [
//...
new ModuleFederationPlugin({
name: 'myApp',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
}),
],
其中:
name是消费者应用程序的名称。remotes是远程应用程序列表,包含远程应用程序名称和模块清单所在的URL。
在消费者应用程序的代码中,通过import()或require.ensure()等方式来动态加载和使用提供者的模块:
import('remoteApp/sharedModule')
.then((module) => {
// 使用提供者的模块
})
.catch((error) => {
// 处理加载失败的情况
});
配置完成后,通过构建提供者和消费者应用程序,就可以实现模块联邦的功能。
4.5 可能会遇到的问题
- 版本兼容性问题:不同应用程序使用不同的依赖版本可能导致冲突和兼容性问题。需要确保依赖的版本符合要求,并避免不同版本的依赖共存。
- 网络请求问题:在加载远程模块时,需要确保提供者应用程序的模块清单文件(remoteEntry.js)能够通过网络访问,并确保网络连接的稳定性。
- 调试和错误处理:由于模块联邦涉及到不同应用程序之间的交互,可能会导致一些调试和错误处理上的复杂性。需要进行适当的错误处理,以及良好的调试和日志记录机制。
- 性能问题:模块联邦涉及远程加载模块,可能会对性能产生一定影响。需要考虑模块的大小和依赖关系,以及网络环境和优化策略,以确保性能表现良好。