Loaders
说到webpack,自然离不开他的一大堆loaders,没有loaders的webpack只能打包js文件,得益于众多的loaders,我们可以打包各种类型的文件。
loaders实际上可以理解为一种打包方案,webpack本身碰到除了js文件之外的其他类型文件时,就不知道该如何打包了,然后打包就会失败,而loaders则是对应某种文件类型的文件一种单独的打包方案,通过config配置了loaders之后,webpack就可以打包该类型的文件了
打包图片
图片可以使用file-loader来进行打包,在项目目录执行npm i file-loader -D之后进行一些简单的配置就可以了
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
// 要打包的文件类型
test: /\.(png|jpg|gif)$/,
use: [
{
// 使用file-loader打包
loader: "file-loader",
options: {
// 生成的文件名是 原文件名_哈希值.原文件扩展名
name:'[name]_[hash].[ext]',
// 输出目录是在打包目录下的images文件夹下
outputPath:'images/'
},
},
],
},
],
},
};
也可以使用url-loader打包,url-loader会多一个limit配置项,低于该大小的文件会直接被转换成base64,不会再被打包
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
// 要打包的文件类型
test: /\.(png|jpg|gif)$/,
use: [
{
// 使用file-loader打包
loader: "file-loader",
options: {
// 生成的文件名是 原文件名_哈希值.原文件扩展名
name:'[name]_[hash].[ext]',
// 输出目录是在打包目录下的images文件夹下
outputPath:'images/',
// 单位是字节,这里代表大于10kb的文件正常打包,小于10kb的文件转成base64
limit:10240
},
},
],
},
],
},
};
引入2张图片作为测试
// index.js
import sayHi from './sayHi'
// 小的图片大小为5kb
import smallImg from './images/small.png'
// 大的图片大小为15kb
import bigImg from './images/big.png'
// 将图片挂载至页面
const root = document.getElementById('root');
const smallImage = new Image()
smallImage.src = smallImg
const bigImage = new Image()
bigImage.src = bigImg
root.append(smallImage)
root.append(bigImage)
sayHi()
可以看到打包后的images文件夹下只有1张大的图片

但是页面上2张图片均可以正常显示
打包样式
普通的css需要通过style-loader和css-loader来打包
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
test:/\.css$/,
use:[
// style-loader将样式挂载至dom节点
'style-loader',
// css-loader将css文件整理为字符串
'css-loader'
]
}
],
},
};
css预编译语言需要额外使用对应语言的loader来多做一次解析,这里以less为示例
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
test:/\.less$/,
use:[
'style-loader',
{
loader:'css-loader',
options:{
// 如果我们引入的less文件里通过@import引入了其他的less文件,那引入的文件默认会直接进入css-loader导致出错
// 这里是配置在进入css-loader之前要经过几个loader,这里前面只有1个less-loader,故配置为1
importLoaders: 1,
}
},
'less-loader'
]
}
],
},
};
webpack打包的样式默认是全局通用的,这也就导致会出现样式穿透的问题,我们可以通过modules配置来解决
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
rules: [
{
test:/\.less$/,
use:[
'style-loader',
{
loader:'css-loader',
options:{
importLoaders: 1,
// 开启模块化样式
modules:true
}
},
'less-loader'
]
}
],
},
};
引入样式时候就需要发生一些改变
// 非模块化引用
import './index.less'
// 模块化引用
import style from './index.less'
// 使用模块化之后的样式
el.className = style.xxx
还有诸多其他各种类型的loader,可以让我们完成各种各样的打包需求
附上文档地址
在需要打包不同的文件时,查阅文档即可
Plugins
plugins(插件)可以在webpack运行到某些时刻时候,帮你做一些事情,比如clean-webpack-plugin可以在打包开始前帮我们清理掉上一次打包后的文件,而html-webpack-plugin则可以在打包后自动生成一个引用了打包完成的js文件的html文件,并且支持指定模板
const path = require("path");
// 引入插件
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
module: {
...
},
// 使用插件
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
// 自定义title
title:'html模板',
// 自定义文件名
filename:'index.html',
// 自定义使用模板的路径
template:'src/public/index.html'
}),
],
};
devServer
webpack-dev-server 提供了一个简单的 web 服务器,并且能够实时重新加载,安装后在webpack.config中做简单的配置即可使用
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
devServer:{
// 告诉devServer在哪里查找文件
contentBase: './myDist',
// 启动后自动浏览器打开
open:true,
// 端口
port:4396
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
...
};
但是这里有一个坑,我用的是webpack5.x版本,运行后报错了
查阅了文档的各种配置依旧没有搞清楚问题所在,遂求助百度
原来是版本的问题
webpack、webpack-cli、webpack-dev-server是需要版本匹配的
找到了一个可以运行的版本
“webpack”: “^4.43.0”,
“webpack-cli”: “^3.3.12”,
“webpack-dev-server”: “^3.11.0”
安装代码
更新版本之后,dev-server就可以正常使用了
但不得不说webpack的文档实在是难以恭维…
模块热替换(HMR)
模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一,它允许在运行时更新各种模块,而无需进行完全刷新。
现在我们的dev-server在运行的时候,更改代码虽然效果会更新,但是这个更新是直接刷新页面来更新的,而使用了HMR之后,就可以做到不刷新页面直接更新了
功能的启用很简单,插件也已经内置在webpack内部无需额外安装
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入webpack
const webpack = require("webpack")
module.exports = {
entry: "./src/index.js",
devServer:{
contentBase: './myDist',
open:true,
port:4396,
// 启用模块热更新
hot:true
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:'html模板',
filename:'index.html',
template:'src/public/index.html'
}),
// 使用插件
new webpack.HotModuleReplacementPlugin()
],
};
这样就可以实现HMR了,接下来修改样式部分的代码,页面可以做到不刷新直接更新,但js部分的代码还是会刷新的,如果想做到js代码不刷新,则需要通过HMR的api accept来手动配置相应的逻辑
首先要在配置文件里加多一个hotOnly配置
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入webpack
const webpack = require("webpack")
module.exports = {
entry: "./src/index.js",
devServer:{
contentBase: './myDist',
open:true,
port:4396,
// 启用模块热更新
hot:true,
// 只使用热更新来更新页面,不会自动刷新页面来更新
hotOnly:true
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:'html模板',
filename:'index.html',
template:'src/public/index.html'
}),
// 使用插件
new webpack.HotModuleReplacementPlugin()
],
};
然后在引用了其他模块的代码里加上对应处理的逻辑
这里sayHi添加了一个id为hi的dom节点
// index.js
import sayHi from "./sayHi";
if (module.hot) {
module.hot.accept(
"./sayHi", // 监听具体哪个模块的改变
function () {
//手动的删除原来saiHi添加的dom节点,然后重新执行saiHi方法
console.log('这样就可以不刷新页面重新加载js文件了')
document.getElementById('root').removeChild(document.getElementById("hi"));
sayHi();
} // 用于在模块更新后触发的函数
);
}
sayHi();
但是如果引入的模块比较复杂,这里的配置也会随之复杂很多,所有具体的情况还是要看业务的需求,刷新一下页面其实也问题不大…
Babel
通常我们会配置babel来帮我们将es6的语法转换成es5的语法,来避免一些奇奇怪怪的问题(ie说的就是你)
首先需要安装babel以及它的loader
npm install --save-dev babel-loader @babel/core @babel/preset-env
在index.js里引入babel/polyfill
// 注入低版本浏览器不支持的一些es6的新方法,诸如promise,map等
import "@babel/polyfill"
然后在loader里面进行简单的配置即可
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
]
}
这时候再进行打包,就可以看到打包后代码里es6的语法已经转换成es5的语法了
也可以做更多的一些适配
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [["@babel/preset-env",{
// 只将用到的es6语法整合进去,没有用到的不整合,减少打包后的大小
useBuiltIns:'usage'
}]],
},
},
]
}
这样配置之后就可以把引入的babel/polyfill去掉了,他会在需要的时候被自动引入
更多的配置可以参考babel的文档
SourceMap
SourceMap是一种映射关系,可以帮助我们在代码出现错误的时候定位到错误的位置,他的配置对应的是config中的devtool项
先将它设置为none
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
devtool:false,
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
...
};
// index.js
// 在代码里添加一句错误的语句
console.llog('错啦')
打包是可以正常执行的,但是我们打开页面后控制台会报错
点击查看source后会发现对应的地方是打包之后的代码位置,而不是我们源代码的位置,这对于我们定位错误没有太大帮助,而想要让他定位到我们源代码的位置,就需要配置SourceMap
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
devtool:'source-map',
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "myDist"),
},
...
};
更改devtool配置再次打包后,点击查看source就会帮我们定位到源码的位置了,打包的文件也会多一个map文件
官方的source-map有很多的配置
inline代表不生成map文件,将map文件以base64格式打包至js文件内
cheap代表是否定位错误出现在代码的第几行和第几个字符,带上cheap代表不定位出现在第几个字符,只定位第几行
module代表是否定位一些模块的错误
eval代表以js的eval形式来执行代码,不过这种方式在代码量较大的环境下可能无法准确定位错误
开启SourceMap会造成性能方面的影响,所以通常我们的做法是在开发环境下开启source-map,正式上线时候关闭
我个人会在开发环境下使用’cheap-module-eval-source-map’这种方式提示的错误比较全面,同时打包的速度也比较快
线上环境通常是直接设置为false不开启,如果出了问题需要迅速定位错误的话,使用’cheap-module-source-map’可以帮助我们能准确定位错误
关于webpack一些核心的理念基本已经介绍完毕,后续的博文会介绍webpack进阶的一些高级概念