在我的前一篇博客中,已经介绍了webpack的基本配置和使用:【前端】webpack5的配置及基本使用。
但是如果想要webpack用于实际项目开发,我们还要配置许多loader和plugin。
Loader:模块代码转换器,让webpack能够去处理除了JS、JSON之外的其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
Plugin:扩展插件。在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的api改变输出结果。常见的有:打包优化,资源管理,注入环境变量。
在Webpack中,一切皆模块,我们常见的Javascript、CSS、Less、Typescript、Jsx、图片等文件都是模块,不同模块的加载是通过模块加载器来统一管理的,当我们需要使用不同的 Loader 来解析不同类型的文件时,我们可以在module.rules字段下配置相关规则。
loader本质: output=loader(input)
Babel是一个Javscript编译器,可以将高级语法(主要是ECMAScript 2015+ )编译成浏览器支持的低版本语法,它可以帮助你用最新版本的Javascript写代码,提高开发效率。
webpack通过babel-loader来使用babel,用于解析JavaScript文件。babel有丰富的预设和插件,babel的配置可以直接写到options里或者单独写道配置文件里。
npm install -D babel-loader @babel/core @babel/preset-env webpack
// webpack.config.js
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: "defaults" }]
],
plugins: ['@babel/plugin-proposal-class-properties'],
// 缓存 loader 的执行结果到指定目录,默认为node_modules/.cache/babel-loader,之后的 webpack 构建,将会尝试读取缓存
cacheDirectory: true,
}
}
}
]
}
详情配置:babel配置文档
webpack提供的 TypeScript loader,打包编译Typescript。
npm install ts-loader --save-dev
npm install typescript --dev
// webpack.config.json
module.exports = {
//...
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
// all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
{ test: /\.tsx?$/, loader: "ts-loader" }
]
}
};
将文件以字符串的形式引入。
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: 'raw-loader'
}
]
}
}
用于处理文件类型资源,如jpg,png等图片。返回值为publicPath为准。
// file.js
import img from './webpack.png';
console.log(img); // 编译后:https://www.tencent.com/webpack_605dc7bf.png
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/i,
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]',
publicPath: "https://www.tencent.com",
},
},
],
},
};
它与file-loader作用相似,也是处理图片的,只不过url-loader可以设置一个根据图片大小进行不同的操作,如果该图片大小大于指定的大小,则将图片进行打包资源,否则将图片转换为base64字符串合并到js文件里。
通过注入
仅处理css的各种加载语法(@import和url()函数等),就像 js 解import/require() 一样。
解析less,转换为css。
解析和转换.vue文件,提取出其中的逻辑代码script,样式代码style以及HTML模板template,再分别将它们交给对应的loader去处理。
vue-cli中默认配置了vue-loader,所以我们可以直接引入vue文件。
Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。
将已经存在的单个文件或者目录复制到打包构建的目录。
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: './template/page.html',
to: `${__dirname}/dist/page.html`
},
],
}),
],
};
上述配置在进行打包时会将template/page.html
复制到/dist/page.html
。
生成html文件,单页应用可以生成一个html入口,多页应用可以配置多个html-webpack-plugin实例来生成多个页面入口。
并且可以会为html中引入外部资源如script、link。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
news: [path.resolve(__dirname, '../src/news/index.js')],
video: path.resolve(__dirname, '../src/video/index.js'),
},
plugins: [
new HtmlWebpackPlugin({
title: 'news page',
// 生成的文件名称 相对于webpackConfig.output.path路径而言
filename: 'pages/news.html',
// 生成filename的文件模板
template: path.resolve(__dirname, '../template/news/index.html'),
chunks: ['news']
}),
new HtmlWebpackPlugin({
title: 'video page',
// 生成的文件名称
filename: 'pages/video.html',
// 生成filename的文件模板
template: path.resolve(__dirname, '../template/video/index.html'),
chunks: ['video']
}),
]
};
默认情况下,这个插件会删除webpack的output.path中的所有文件,以及每次成功重新构建后所有未使用的资源。
这个插件在生产环境用的频率非常高,因为生产环境经常会通过 hash 生成很多 bundle 文件,如果不进行清理的话每次都会生成新的,导致文件夹非常庞大。
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(),
]
};
会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件。
// 建议 mini-css-extract-plugin 与 css-loader 一起使用
// 将 loader 与 plugin 添加到 webpack 配置文件中
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
}
],
},
};
创建一个在编译时可以配置的全局常量
。这会对开发模式和生产模式的构建允许不同的行为非常有用。
因为这个插件直接执行文本替换,给定的值必须包含字符串本身内的实际引号。
通常,有两种方式来达到这个效果,使用’“production”', 或者使用 JSON.stringify(‘production’)。
// webpack.config.js
const isProd = process.env.NODE_ENV === 'production';
module.exports = {
plugins: [
new webpack.DefinePlugin({
PAGE_URL: JSON.stringify(isProd
? 'https://www.tencent.com/page'
: 'http://testsite.tencent.com/page'
)
}),
]
}
// 代码里面直接使用
console.log(PAGE_URL);
可以看到项目各模块的大小,可以按需优化。
一个webpack的bundle文件分析工具,将bundle文件以可交互缩放的treemap的形式展示。
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
用于代码分割。
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
optimization: {
splitChunks: {
// 分隔符
// automaticNameDelimiter: '~',
// all, async, and initial
chunks: 'all',
// 它可以继承/覆盖上面 splitChunks 中所有的参数值,除此之外还额外提供了三个配置,分别为:test, priority 和 reuseExistingChunk
cacheGroups: {
vendors: {
// 表示要过滤 modules,默认为所有的 modules,可匹配模块路径或 chunk 名字,当匹配的是 chunk 名字的时候,其里面的所有 modules 都会选中
test: /[\\/]node_modules\/antd\//,
// priority:表示抽取权重,数字越大表示优先级越高。因为一个 module 可能会满足多个 cacheGroups 的条件,那么抽取到哪个就由权重最高的说了算;
// priority: 3,
// reuseExistingChunk:表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
reuseExistingChunk: true,
name: 'antd'
}
}
}
},
}
可以看到,loader和plugin非常多,并且配置起来也并不容易,所以我们每次都是直接使用别人封装好的脚手架,然后进行项目开发,例如vue-cli或者create-react-app等常用脚手架。
但是了解了基本的loader和plugin作用,对于我们排除错误或者对项目的理解都有很大的帮助。