webpack最重要的四大模块
-
入口起点:开始构建的起始文件。
-
出口文件:构建的文件,存放的位置、名称以及是否覆盖上一次构建文件等。
-
加载器loader:webpack只能理解 js和json 文件,使用 loader 之后可以处理其他类型的文件,并将他们转换成webpack可识别的模块。比如babel-loader: 将es6转成es5,react-loader: 加载react文件,vue-loader同理,以及css-loader,加载css文件,sass-loader将sass代码转成css代码,image-loader,ts-loader:将ts转成js代码等。
-
插件plugin:打包优化,资源管理,注入环境变量。
plugin插件
plugins
选项用于以各种方式自定义 webpack 构建过程。webpack 附带了各种内置插件,可以通过 webpack.[plugin-name]
访问这些插件。
请查看 内置插件 ,获取插件列表和对应文档,但请注意这只是其中一部分,社区中还有许多插件。如果其中没有想要的插件,还可以编写一个插件。
插件目的在于解决 loader 无法实现的其他事。
现在能在webpack中使用的插件,种类很多,功能也很强大,下面介绍几种,其他使用方法都类似
CleanWebpackPlugin插件
这个插件是用来清除打包的文件。当我们修改要打包的文件名称时,如果我们不加上这个插件,就会另外生成一个打包的文件,原来的那个不会自动删除,所以这个插件是用来偷懒的,会自动帮助我们删除原来打包的文件。
js
// webpack 4中的使用方法
const path = require('path');
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
mode:'production',
entry:'./src/index.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'index.js'
},
//注册插件
//webpack 会自动调用 apply 方法,把插件注册到 webpack 中
plugins:[
new CleanWebpackPlugin();
]
}
// webpack 5中的使用方法
const path = require('path');
module.exports = {
mode:'production',
entry:'./src/index.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'index.js',
//webpack 5使用这个这个方法
clean:true
}
}
HTMLWebpackPlugin插件
简化了 HTML 文件的创建,并且自动在 html 引入 js 文件。有了这个插件,就可以在修改了打包文件的名称后,自动修改 index.html 中引用打包文件的名称。
安装
css
npm install --save-dev html-webpack-plugin
只需添加该插件到你的 webpack 配置中,该插件将为你生成一个 HTML5 文件, 在 body 中使用 script
标签引入你所有 webpack 生成的 bundle。
js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js',
},
plugins: [new HtmlWebpackPlugin()],
};
这将会生成一个包含以下内容的 dist/index.html
文件
js
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>webpack App</title>
</head>
<body>
<script src="index_bundle.js"></script>
</body>
</html>
MiniCssExtractPlugin 插件
这个插件是用来处理当我们使用 css-loader 时候,我们样式会展示在 html 中的 header 中的 style 里面这样不方便我们以后进行优化,这个插件可以把相应的 css 生成一个单独的 css 文件中,放在打包的目录中。
js
const path = require('path');
module.exports = {
mode:'production',
entry:'./src/index.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'index.js',
//webpack 5使用这个这个方法
clean:true
},
module:{
//使用raw-loader,用来处理txt文本文件
rules:[
{
test:/\.css$/,
//同一类loader中执行顺序,先下后上,先右后左。先执行css-loader->style-loader
use:[
//替换style-loader 为插件内置的loader
{
loader:MiniCssExtractPlugin.loader
},
'css-loader'
]
},
plugins:[
new MiniCssExtractPlugin({
//这个插件内置一个loader,需要我们替换掉 style-loader
filename:'[name].css'
})
]
}
访问内置方法
js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.(js|jsx)$/,
use: 'babel-loader',
},
],
},
plugins: [
new webpack.ProgressPlugin(), // ProgressPlugin方法就是内置方法
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
};
写一个插件
plugin原理:
webpack 在编译代码过程中,会通过compilation触发一系列 Tapable 钩子事件。我们可以注入事件,拥有更强的构建能力。Tapable 还统一暴露了三个方法。
tap:可以注册同步钩子和异步钩子。
tapAsync:回调方式注册异步钩子。
tapPromise:tapPromise 方式注册异步钩子。
需求:可以删除console.log()
js
class DelConsoleWebpackPlugin {
apply(compiler) {
// 在资源输出之前触发
compiler.hooks.emit.tapAsync("pluginName", (compilation,callback) => {
// 遍历编译后的资产对象
for (const name in compilation.assets) {
//文件内容 compilation.assets[name].source()
let source = compilation.assets[name].source()
//匹配文件 name.endsWith('.js')
if (name.endsWith('.js')) {
// 匹配所有的 `console.log` 语句
let rConsole =/console.log(.*?)/g;
// 将源文件内容中的所有 `console.log` 语句替换为空字符串
source = source.replace(rConsole, "");
// 将修改后的源文件重新赋值给资产对象
compilation.assets[name] = {
source: function () {
return source;
},
size: function () {
return source.length;
}
}
}
}
callback()
});
}
}
module.exports = DelConsoleWebpackPlugin;
这个插件使用 compiler.hooks.emit.tapAsync
方法在 Webpack 构建的资源输出之前触发。然后,它遍历编译后的资产对象,检查文件名是否以 .js
结尾。如果是,它使用正则表达式 console.log(.*?)/g
来匹配所有的 console.log
语句,并使用 replace
方法将其替换为空字符串。
最后,它将修改后的源文件重新赋值给资产对象,并返回该对象。
模式(Mode)
提供 mode
配置选项,告知 webpack 使用相应模式的 内置优化。
支持三个配置项:
选项 | 描述 |
---|---|
development |
会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development . 为模块和 chunk 启用有效的名。 |
production |
会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production 。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin ,FlagIncludedChunksPlugin ,ModuleConcatenationPlugin ,NoEmitOnErrorsPlugin 和 TerserPlugin 。 |
none |
不使用任何默认优化选项 |
开发模式(development)
- 启用快速迭代和调试功能,例如实时重新加载(hot module replacement)、详细的错误信息和Source Map。
- 优化侧重于快速的构建时间和快速的开发体验。
- 不会进行代码压缩、tree-shaking(摇树优化)等优化操作,以确保代码的可调试性。
- 生成的代码体积较大,不适合用于生产环境。
生产模式(production) :
- 启用优化功能,例如代码压缩、tree-shaking、代码分割(Code Splitting)等。
- 优化侧重于减小构建后的代码体积和提高性能。
- 会启用各种优化插件,如 UglifyJS、Terser 等,以压缩和混淆代码。
- 会删除不必要的代码和注释,以减小代码体积。
- 生成的代码体积较小,适合用于生产环境。
用法:
js
module.exports = {
mode: 'development', // 开发模式
mode: 'production', // 生产模式
mode: 'none',
};
tree shaking 【摇树】(过滤掉没有调用到的代码)
检查整个链路中属性的使用情况,确定没有使用就去掉,但是只会在生产模式下才会过滤,开发模式都会打包。
js
// 生产模式就会触发 摇树
module.exports = {
mode: 'production', // 生产模式
};
optimization(优化)
webpack中优化有很多配置项,根据不同需求,选择合适的方法即可
下面介绍一个常用的,压缩优化和代码拆分
optimization.minimize
用于控制是否启用代码压缩,false
表示不启用代码压缩,即生成的代码不会被压缩。如果设置为true
,则会启用代码压缩,生成的代码体积会更小。
js
module.exports = {
//...
optimization: {
minimize: false,
},
};
注意:想在开发环境使用优化,要将
optimization.minimize
设置为true
optimization.minimizer
用于配置代码压缩的插件
js
// webpack.config.js
let path = require('path');
let HtmlWebpackPlugin = require("html-webpack-plugin"); // html 插件 自动在html 引入js文件
let MiniCssExtractPlugin = require('mini-css-extract-plugin'); //插件 抽取css作为单独的文件
let TerserPlugin = require('terser-webpack-plugin') // js压缩
let OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin') // css压缩
let CssPlugin = require('css-minimizer-webpack-plugin') // css压缩
module.exports = {
optimization: {
//压缩
minimizer: [new TerserPlugin(), new CssPlugin()]
},
//入口 出口 loader plugin
entry: "./src/index.js", //输入
mode: 'production', //production development // 模式
output: { // 输出
//__dirname 表示当前目录
path: path.resolve(__dirname, "dist"), //绝对路径
filename: "index.js" //输出的文件名
},
devServer: { //配置webpack-dev-server
port: 8083, //配置web服务端口
open: true, //自动打开浏览器
progress: true, //进度
contentBase: './dist' //指定web服务器的根目录
},
module: { //装载器
rules: [
{
test:/\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
"@babel/preset-env"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
}
},
{
test: /\.css$/,
// use: ['style-loader','css-loader'] // 执行顺序:从右到从,其他规则一致
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/,
// use: ['style-loader','css-loader','less-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
}
]
},
plugins: [ // 插件
new HtmlWebpackPlugin({
template: './src/index.html', //指定输出的文件模板
filename: 'index.html' //指定输出dist的文件名
}),
new MiniCssExtractPlugin({
filename: 'index.css'
})
]
}
optimization.usedExports
上面说到的摇树,也是优化的一种,不在生产环境中,也是可以用usedExports
属性实现
js
module.exports = {
//...
optimization: {
usedExports: true, // boolean方式
// 或者
usedExports: 'global', // string方式
},
};
optimization.splitChunks
使用 SplitChunks 优化,可以将代码根据配置项,拆分成较小的块
js
optimization: {
splitChunks: { // 控制代码分割
// 有效值为 `all`,`async` 和 `initial`
chunks: 'all', // 对所有的块进行分割
name: true, // 表示为每个块生成一个唯一的名称
minSize: 1, // 表示最小分割块的大小为 1 字节
minChunks: 1, // 表示如果一个块被多个模块引用,它将被分割
cacheGroups: { // 定义了多个缓存组,用于根据不同的规则对代码进行分割
default: false, // 禁用任何默认缓存组
defaultVendors: { // 缓存组适用于 node_modules 目录下的所有模块
name: 'common', // 缓存组的名称,用于在缓存中标识
chunks: 'initial', // initial 表示应用程序的入口模块
minChunks: 1, // 最小的 chunk 数量,只有在满足最小 chunk 数量时才会创建缓存
enforce: true, // 是否强制使用缓存。如果为 `true`,则在满足条件时必须使用缓存,否则将抛出错误。
test: /node_modules/, // 一个正则表达式,用于匹配要缓存的模块
reuseExistingChunk: true // - - 是否重用现有的 chunk。如果为 `true`,则如果已经存在匹配的缓存,则直接使用,而不是创建新的缓存。
},
vuePhotoPreviewVenodr: {
test: /(vue-photo-preview)/, // 表示匹配 `vue-photo-preview` 模块
priority: 103, // 缓存的优先级。优先级越高,越先被使用。
name: 'vuePhotoPreviewVenodr',
chunks: 'async' // `async`表示适用于异步加载的 chunks
},
'async-commons': { // 这个缓存组适用于其他异步加载的包
// 其余异步加载包
chunks: 'async',
minChunks: 2,
name: 'async-commons',
priority: 90
},
commons: { // 这个缓存组适用于其他同步加载的包
// 其余同步加载包
chunks: 'all',
minChunks: 2,
name: 'commons',
priority: 80
},
styles: { // 这个缓存组适用于所有的样式文件
name: 'common',
chunks: 'initial',
minChunks: 1,
test: /\.(css|less|scss|stylus)$/,
enforce: true,
priority: 50
}
}
}
}
上面的例子中,只是其中一部分配置项
外部扩展(Externals)
防止 import 引入的依赖包也进行了打包(将依赖排除在打包外),在运行时再去外部获取这些依赖,可以减小构建的 bundle 的大小,提高加载速度,并且可以更好地管理依赖关系。
有多种不同的方式
字符串
js
module.exports = {
//...
externals: 'jquery',
};
注意:外部模块设置为字符串时,Webpack 会将其视为一个全局变量,并在构建的代码中使用该全局变量来引用外部模块。如果你希望将外部模块作为一个模块来使用,而不是作为一个全局变量,可以使用其他方式来配置
externals
。
对象
js
module.exports = {
//...
externals: {
jquery: 'jquery', // 全局变量
},
};
还可以根据不同的环境,来指定不同的别名,再使用不同的别名,来将外部模块排除掉
js
module.exports = {
//...
externals: {
react: 'react',
},
// 或者
externals: {
lodash: {
commonjs: 'lodash',
amd: 'lodash',
root: '_', // 指向全局变量
},
},
};
commonjs: 'lodash'
:指定了在 CommonJS 环境下的别名,即在使用require('lodash')
时,Webpack 会将其解析为require('lodash')
。amd: 'lodash'
:指定了在 AMD 环境下的别名,即在使用define(['lodash'], function(lodash) {})
时,Webpack 会将其解析为define(['lodash'], function(lodash) {})
。root: '_'
:指定了在根目录下的别名,即在代码中使用_
时,Webpack 会将其解析为lodash
。
数组
js
module.exports = {
//...
externals: {
subtract: ['./math', 'subtract'],
},
};
外部模块为 subtract
,['./math', 'subtract']
:这是一个数组,其中包含了两个别名。第一个别名是 ./math
,表示在代码中使用 import subtract from './math'
时,Webpack 会将其解析为 import subtract from './math'
。第二个别名是 subtract
,表示在代码中使用 import subtract from 'subtract'
时,Webpack 会将其解析为 import subtract from './math'
。
函数
用函数匹配一个正则表达式,将匹配成功的,都进行外部化(排除)
js
module.exports = {
//...
externals: [
function ({ context, request }, callback) {
if (/^yourregex$/.test(request)) {
// 使用 request 路径,将一个 commonjs 模块外部化
return callback(null, 'commonjs ' + request);
}
// 继续下一步且不外部化引用
callback();
},
],
};
正则
正则表达式匹配成功的依赖,都将从输出包中排除。
js
module.exports = {
//...
externals: /^(jquery|$)$/i,
};
混用语法
将前面的字符串、对象、数组、函数、正则混合使用,在externals
数组中,使用多个语法。
js
module.exports = {
//...
externals: [
{
// 字符串
react: 'react',
// 对象
lodash: {
commonjs: 'lodash',
amd: 'lodash',
root: '_', // indicates global variable
},
// 字符串数组
subtract: ['./math', 'subtract'],
},
// 函数
function ({ context, request }, callback) {
if (/^yourregex$/.test(request)) {
return callback(null, 'commonjs ' + request);
}
callback();
},
// 正则表达式
/^(jquery|$)$/i,
],
};
devtool 源映射
此选项控制是否生成,以及如何生成 source map(映射文件)
作用:
它将编译后的代码映射回原始的源代码,使得在调试时能够在代码中显示行号和列号等信息,方便定位错误和进行调试。
配置项很多,下面举个例子
js
// vue.config.js
const isBuildProduction = process.env.NODE_ENV === 'production';
module.exports = {
lintOnSave: !isBuildProduction,
configureWebpack: {
// 生产环境: 打包 source-map 且不泄露源码,并且报错时有组件信息而不是编译后代码的信息
devtool: isBuildProduction
? 'nosources-source-map'
: 'eval-cheap-module-source-map',
}
}
devtool
:指定构建时使用的 source map 类型。在生产环境下,使用'nosources-source-map'
表示不包含源文件的 source map,以保护源代码的隐私。在非生产环境下,使用'eval-cheap-module-source-map'
表示使用快速的 eval 源映射,以便在开发时更好地调试代码。
使用 SourceMapDevToolPlugin
插件进行更细粒度的配置。
使用 source-map-loader
来处理已有的源映射。