前言
如果你还不会webpack,就来一起学习一下吧,本文主要是依据webpack5 来进行学习
基础篇
mode
告诉webpack使用相应模式进行内置优化
- development
- 自动会设置 devtool: 'eval'
- production (默认)
devtool
控制是否生成source map 以及如何生成source map
- eval (默认)
- false
- source-map
- eval-source-map base64的方式放在eval 后面
- Inline-source-map 在最下面有一个sourceMappingUrl = base64
- cheap-source-map。只有行信息,没有列信息
- cheap-module-source-map(推荐) 对loader 处理的文件展示更加友好
- hidden-source-map // 有map 文件生成,但是定位不到源代码,需要手动加载到环境下
- nosource-source-map // 会生成map 文件,但是没有源文件提示
设置规则
(inline | hidden | eval )(nosources)(cheap|cheap-module )sourcemap
source Map
可以根据转换后的代码再次转换为源代码,方便定位调试
当将devtool 设置为source-map 的时候,在打包后,就会额外生成一个main.js.map 文件,格式化后,我们发现里面大致有这几个字段
version: 3 // 版本
sources: [] // 告诉我们将来的map文件是通过哪个源文件转换来的
names: [] // 对names里面的字符进行特殊的处理
mappings: 'xxx' // 类似于映射算法
file: 'x x x' // 源文件名
sourcesContent: [] // 源文件备份
sourceRoot: '' // 记录sourceMap文件的根路径
entry
打包的目录起点
entry: "./src/index.js",
output
-
path : 打包资源输出到哪一个目录
-
filename: 打包输出的文件名
-
publicPath: "", index.html内部引用打包后的js路径,默认是"",
拼接的规则就是拿到前面的域名 + publicPath + filename
output: {
path: path.resolve(__dirname, "dist"), // 打包的资源输出到哪个目录
filename: "main[hash:6].js",
publicPath: "",
assetModuleFilename: "[name].[hash:6].[ext]", // 打包统一的目录下的名称
},
resolve
配置当前模块解析规则
resolve: {
// 配置模块解析规则
extensions: [".js", ".jsx", ".tsx", ".ts", ".json"],
alias: { // 配置别名
"@": path.resolve(__dirname, "src"),
},
},
devServer
使用webpack-dev-server 的一系列配置
- publicPath: 指定本地服务所在的目录,默认值为 / (项目所在目录)
- contentBase: 打包之后的资源如果依赖了其他没有打包的资源,则告知去哪里找,绝对路径
- watchContentBase: 和contenBase 配套,进行监听热更新
devServer: {
// webpack-dev-server 配置
// 配置额外的静态文件目录
contentBase: path.resolve(__dirname, "dist"),
watchContentBase: true,
compress: true, // 默认为false, 开启服务端gzip 的压缩
port: 8080,
open: true,
hot: 'only';, // 热更新
// hotOnly: true, // 后面文档更新为了hot 报错信息不会刷新页面
publicPath: "/", // 后面文档更新为了static
// historyApiFallback: true
proxy: {}
},
建议output 里面的publicPath 和devServer 里面的publicPath 设置为一样的
proxy
设置代理
proxy: {
// http:localhost:8000/api/users
// https://api.github.com.api/api/users
"/api": {
targey: "https://api.github.com", // 实际代理的地址
pathRewrite: {
"^/api": "", // 将地址重写,将/api重写为“”, 想当于https://api.github.com.api/users
},
changeOrigin: true, // 修改host
},
},
loader
loader 是一个模块,将一些文件转换为webpack可以识别的模块,例如css模块不能进行webpack 进行打包,需要进行转换
css-loader 将css文件转换为一个对象,让webpack 能够识别css 语法,style-loader 将转换后的对象应用到页面上展示出style来,利用less-loader 来进行处理less文件(less 会把less 语法转为css)
-
css-loader
-
style-loader
-
less-loader
配置loader 语法
在webpack.config.js
module: {
rules: [ // 添加多个不同文件的loader
{
test: /\.css$/,
use: [ // 从下往上,从右边往左执行
{
loader: "style-loader",
// options: "" 暂时不需要参数,所以不传
},
{
loader: 'css-loader'
}
]
},
{ test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] }, // 简写方式
]
}
module: {
// loader 分类 pre前置 normal inline post
rules: [
{ test: /\.txt$/, type: "asset/source" },
// { test: /\.css$/, use: ["style-loader", "css-loader"] },
{
test: /\.css$/,
use: [
{
loader: "style-loader",
// options: "",
},
{
loader: "css-loader",
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
// require("autoprefixer"),
require("postcss-preset-env"),
],
},
},
},
],
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
// require("autoprefixer"),
require("postcss-preset-env"),
],
},
},
},
"less-loader",
],
},
],
},
我们发现在less 和css 中都会用到一样的loader,有一些冗余了,所以可以单独的拿出来一个文件进行配置
项目根目录下创建一个postcss.config.js
module.exports = {
plugins: [require("postcss-preset-env")],
};
然后model 就可以直接写postcss-loader,就会自动找到他的配置文件postcss.config.js
{
test: /\.less$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1, // 往前找一个,css-loader需要往前找一个loader执行,再次调用postcss-loader
},
},
"postcss-loader",
"less-loader",
]
}
打包图片 file-loader
将图片也当作模块导入,webpack 默认不能处理,需要用到file-loader
// 引入图片
import oImagesrc from "./img/es.png";
import "./css/img.css";
function packImg() {
// img 标签,src 属性
const oEle = document.createElement("div");
// 创建img标签,设置src
const oImg = document.createElement("img");
const requireEsModul = require("./img/es.png");
console.log(requireEsModul, "---->requireEsModul");
// file-loader 处理后,会将require 导入后的变为一个对象,里面的defalut属性才是图片地址
// oImg.src = require("./img/es.png").default;
// 如果file-loader 设置了options属性 esModule: false 则可以直接使用
// oImg.src = require("./img/es.png");
// 利用import 将图片引入来进行使用
oImg.src = oImagesrc;
oEle.appendChild(oImg);
// 设置背景图片
const oBgImg = document.createElement("div");
oBgImg.className = "bgBox";
oEle.appendChild(oBgImg);
return oEle;
}
document.body.appendChild(packImg());
- 使用require 导入图片,如果不给file-loader 配置esmodule: false, 则需要.default 使用
- 添加esmodule: false
- 采用import xxx from 图片资源地址
webpack.config.js 配置
{
test: /\.(png|svg|gif|jpe?g)$/,
use: [
{
loader: "file-loader",
options: {
esModule: false, // 是否转化后的包裹成esModule
// 修改打包的图片的名称
/**
* 占位符号
* [ext]: 扩展名称
* [name]:文件名称
* [hash]: 文件内容产出的hash
* [contentHash]: hash值
* [hash:<length>] 截取hash 长度
* [path]: 路径
*/
name: "[name].[hash:6].[ext]",
outputPath: "img",
// name: "img/[name].[hash:6].[ext]", 简写设置文件打包后的地址以及名称等
},
},
],
},
打包图片的时候还有一个是url-loader,使用和file-loader差不多,但是
url-loader 会将图片以base64来进行打包到代码中,减少请求次数,而file-loader则会将图片拷贝到dist 目录下,分开请求,在url-loader 内部也可以调用file-loader. 利用limit 属性
{
test: /\.(png|svg|gif|jpe?g)$/,
use: [
{
loader: "url-loader",
options: {
esModule: false,
name: "img/[name].[hash:6].[ext]",
limit: 25 * 1024, // 超过就拷贝,没有超过就转base64
},
},
],
}
webpack5不需要再去配置file-loader 或者url-loader,他新增了一个asset module type 模块
- asset/resource == file-loader
- asset/inline == url-loader
- asset/source == raw-loader
- asset. url-loader + limit
// 01 方式一
{
test: /\.(png|svg|gif|jpe?g)$/,
type: "asset/resource",
// 打包到统一的目录下面 这里也可以在output 下面配置。与这里的区别是,output配置是只要经过asset 都打包到统一的目录下面
generator: {
filename: "img/[name].[hash:6][ext]",
},
},
// 02 方式二
{
test: /\.(png|svg|gif|jpe?g)$/,
type: "asset",
generator: {
filename: "img/[name].[hash:6][ext]",
},
parser: {
dataUrlCondition: { // 相当于limit
maxSize: 25 * 1024,
},
},
}
output: {
path: path.resolve(__dirname, "dist"),
filename: "main.js",
// 只要经过asset 都打包到统一的目录下面
assetModuleFilename: "[name].[hash:6][ext]",
},
asset 处理图标字体资源
browerslistrc 配置
用来配置项目中兼容哪些平台 可以在caniuse.com/中查看(https:/…
安装webpack的时候,已经默认安装配置了,在nodeModules 中的 browserslist这个包,可以利用npx browserslist 来查看当前兼容的浏览器版本
两种配置方法:
- package.json
"browserlist": [
">1%",
"last 2 version",
"not dead"
]
- 项目下新建 .browserslistrc
>1%
last 2 version
not dead
postcss
就是通过javascript 来转换样式的工具
比如说给css 补充前缀,做一些css的样式做一些兼容性的处理满足更多的浏览器
npm i postcss posts-cli
- postcss。相当于一个转换样式的工具
- postcss-cli. // 安装后,可以在命令行直接使用npx postcss
npx postcss -o ret.css ./src/test.css // 将test.css 输出到ret.css 中
安装autoprefixer 插件 用来自动安装前缀( autoprefixer.github.io/ 添加前缀)
npx postcss --use autoprefixer -o ret.css ./src/test.css // 将test.css 输出到ret.css 中,添加前缀
如果很多样式都需要添加前缀,所以我们利用postcss-loader 这个loader,在配置文件中设置 npm i postcss-loader
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [require("autoprefixer")], // 利用插件
},
},
},
]
},
]
}
postcss-preset-env 预设(插件的集合)
集合了很多的常见的插件的集合
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
// require("autoprefixer"),
require("postcss-preset-env"),
],
},
},
babel-loader配置
babel 简单了解
安装babel核心包
npm i @babel/core -D
安装脚手架执行babel
@babel/cli -D
安装工具包
@babel/plugin-transform-arrow-function
@babel/plugin-transform-block-scoping
执行使用babel
npx babel src/babeltest.js --out-dir build --plugins=@babel/plugin-transform-arrow-functions
- npm i babel-loader 安装babel-loader
- npm i @babel/preset-env 安装预设 或者安装自己需要的plugin
{
test: /\.js/,
exclude: /node_modules/, // node_modules中的不做处理
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"], // 预设,就可以不用一个个写plugin
// 也可以在后面直接配置指定浏览器、
// presets: [["@babel/preset-env",{ targets: "chrome 91" }]],
plugins: [],
},
},
],
},
babel-loader 在webpack 打包的时候,是会根据.browserslistrc 文件来进行转换的
所以可以把babel-loader 配置文件单独拿出去
- bable.config.js(json cis mjs)
- babelrc.json(js)
项目根目录下新建babel.config.js 文件
module.exports = {
presets: [["@babel/preset-env" /*{ targets: "chrome 91" }*/]],
};
然后webpack.config.js 就可以改为
{
test: /\.js$/,
use: ['babel-loader']
}
polyfill
Babel 的预设不一定可以将所有的语法都能转为浏览器兼容可以使用的,
所以当遇到最新的语法的时候,需要使用polyfill
Webpack5 之前需要安装 @babel/polyfill npm i @babel/polyfill --save, 但是打包会消耗更多时间,因为不能按需配置
所以webpack5只要 core-js. 和 regenerator-runtime,不需要@babel/polyfill
npm i core-js regenerator-runtime
然后给babel.config.js 中进行配置
useBuiltIns
- usage
- entry
module.exports = {
presets: [
[
"@babel/preset-env",
{
// useBuiltIns: false, // 不对当前js处理做polyfill 填充
useBuiltIns: "usage", // 使用这个后,会根据用户源代码当中所使用的新语法进行填充,corejs 会默认使用2版本,所以需要指定版本corejs 为3
// useBuiltIns: "entry", // 依据所要兼容的浏览器browserslistrc来进行填充,不会去管源代码用没用,只要浏览器不兼容,就会把它全部填充进来,如果需要按需加载,需要在源代码中添加引入 core-js 和 regenerator-runtime
// import "core-js/stable"
// import "regenerator-runtime/runtime"
corejs: 3,
},
],
],
};
ts-loader
{
test: /\.ts$/,
use: ["ts-loader"],
}
转换ts 文件 ,将ts 文件语法转为javascript, 但是如果文件中用到新特性之类的,还是需要babel-loader 来进行添加polyfill转换,安装**@babel/preset-typescript**
{
test: /\.ts$/,
use: ["babel-loader"],
},
module.exports = {
presets: [
[
"@babel/preset-env",
{
// useBuiltIns: false, // 不对当前js处理做polyfill 填充
useBuiltIns: "usage", // 使用这个后,会根据用户源代码当中所使用的新语法进行填充,corejs 会使用2版本,所以需要指定版本corejs 为3
// useBuiltIns: "entry", // 依据所要兼容的浏览器browserslistrc来进行填充,不会去管源代码用没用,只要浏览器不兼容,就会把它全部填充进来,如果
// 需要按需加载,需要在源代码中添加
corejs: 3,
},
],
["@babel/preset-typescript"],
],
};
但是使用babel-loader 如果代码有语法错误,在编译阶段,是不会暴露出来的,只会在运行的时候发现,但是使用ts-loader 却可以
这个时候,我们即希望在编译的时候进行语法校验,也希望babel-loader进行polyfill 填充,所以利用pack.json 方式,在build的时候,同时利用tsc检查一个语法
"tscck": "tsc --noEmit" // 只会校验语法,但是不会产出新的js 文件
"scripts": {
"start": "webpack serve",
"build": "npm run tscck && webpack",
"tscck": "tsc --noEmit"
},
plugin
loader 和plugin 区别
loader 对特定的模块类型进行转换, 读取某一个资源类型的内容时候使用
plugin 可以做更多的事情,可以在webpack 打包的任意生命周期中做一些事情
clean-webpack-plugin
清空dist 目录
html-webpack-plugin
配置打包后的index.html
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWbpackPlugin = require("html-webpack-plugin");
webpack.config.js
plugins: [
new HtmlWbpackPlugin({ template: "./src/index.html" }),
new CleanWebpackPlugin(),
],
definePlugin
webpack 自带的默认的插件,可以使用全局的常量
const { DefinePlugin } = require("webpack");
// 比如配置一个BASE_URL的常量
plugins: [
new DefinePlugin({
BASE_URL: 'lalala', // 注意: 这里会把这个值直接赋值到常量中去
}),
],
copy-webpack-plugin
有时候,public 中不希望webpack进行打包,所以需要进行拷贝
注意: 这里需要9版本,10版本会报错
const CopyWebpackPlugin = require("copy-webpack-plugin");
new CopyWebpackPlugin({
patterns: [
{ from: "public" }, // 不写to 的话,会自动复制到配置的output 这个目录
// { from: "other", to: "public" },
],
webpack-dev-server
可以用webpack 打包的时候监听,用vscode 的live server 来进行查看,但是不能局部刷新,也可以在配置文件里面配置watch: true, 同样,有性能问题,
webpack-dev-server 可以实现局部刷新打包
npm i webpack-dev-server
"start": "webpack serve",
"build": "webpack"
"wacth": "webpack --watch"
实现plugin
webpack.config.js
为了区分生产环境打包配置和开发环境打包配置,我们进行了对webppack.config.js 进行拆分配置。分为三个文件,一个基础文件配置,在生产和开发都会用到,然后一个生产环境配置,一个开发环境配置。通过利用 webpack-merge 将配置文件合并
首先需要修改我们的package.json 文件中的script, 将打包命令执行的webpack配置文件目录通过--config指定为我们想要执行的那个文件,然后通过 --env 来传入当前环境的参数
"scripts": {
"build": "webpack --config ./config/webpack.common.js --env production",
"serve": "webpack serve --config ./config/webpack.common.js --env development",
},
在根目录下新建config 文件夹,里面创建四个文件
- webpack.common.js
- webpack.dev.js
- webpack.pro.js
- pathUtils.js // 来配置绑定当前上下文路径
因为我们在写配置别名和output的时候,需要传入一个绝对路径,但是目前我们修改完后,webpack的配置文件路径也就对应不上了,所以利用pathUtils.js 里面的方法来保存一个绝对路径地址。
// pathUtils.js
const path = require("path");
// 获取当前运行地址
const currentDir = process.cwd();
// 根据当前根目录生成绝对路径地址
const resolveCurrent = (relativePath) => {
return path.resolve(currentDir, relativePath);
};
module.exports = resolveCurrent;
然后来配置我们的webpack.common.js 文件,我们把公共的配置都抽离到这个文件中,然后利用 webpack-merge 将配置文件合并
const { merge } = require("webpack-merge");
// 导入确定路径的utils函数
const resolveCurrent = require("./pathalis");
// 导入公共插件
const HtmlWbpackPlugin = require("html-webpack-plugin");
// 导入其他配置文件
// 导入生产环境webpack配置
const prodConfig = require("./webpack.prod");
// 导入开发环境webpack配置
const devConfig = require("./webpack.dev");
// 导出webpack配置文件
// 这里导出一个函数是为了接受package.json里面传入的环境参数
module.exports = (env) => {
// 根据传入环境变量判断
const isProduction = env.production;
// 根据当前打包模式判断要和哪一个配置合并
const config = isProduction ? prodConfig : devConfig;
// 利用webpack-merge合并配置文件并返回
const mergeConfig = merge(baseConfig, config);
return mergeConfig;
};
// // 定义对象,保存基础配置信息
const baseConfig = {
entry: "./src/index.js",
resolve: {
// 配置模块解析规则
extensions: [".js", ".jsx", ".tsx", ".ts", ".json"],
alias: {
"@": resolveCurrent("./src"), // 修改路径
},
},
output: {
path: resolveCurrent("./dist"), // 打包的资源输出到哪个目录
filename: "main[hash:6].js",
publicPath: "",
},
module: {
// loader 分类 pre前置 normal inline post
rules: [
// { test: /\.css$/, use: ["style-loader", "css-loader"] },
{
test: /\.css$/,
use: [
{
loader: "style-loader",
// options: "",
},
{
loader: "css-loader",
options: {
importLoaders: 1,
},
},
{
loader: "postcss-loader",
// options: {
// postcssOptions: {
// plugins: [
// // require("autoprefixer"),
// require("postcss-preset-env"),
// ],
// },
// },
},
],
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"postcss-loader",
// {
// loader: "postcss-loader",
// options: {
// postcssOptions: {
// plugins: [
// // require("autoprefixer"),
// require("postcss-preset-env"),
// ],
// },
// },
// },
"less-loader",
],
},
// {
// test: /\.(png|svg|gif|jpe?g)$/,
// use: [
// {
// loader: "url-loader",
// options: {
// esModule: false, // 是否转化后的包裹成esModule
// // 修改打包的图片的名称
// // 占位符号
// // [ext]: 扩展名称
// // [name]:文件名称
// // [hash]: 文件内容产出的hash
// // [contentHash]: hash值
// // [hash:<length>] 截取hash 长度
// // [path]: 路径
// name: "[name].[hash:6].[ext]",
// // name: "img/[name].[hash:6].[ext]",
// outputPath: "img",
// limit: 25 * 1024, // 超过就拷贝,没有超过就转base64
// },
// },
// ],
// },
{
test: /\.(png|svg|gif|jpe?g)$/,
type: "asset",
// 只需要经过asset 都打包到统一的目录下面
generator: {
filename: "img/[name].[hash:6][ext]",
},
parser: {
dataUrlCondition: {
maxSize: 25 * 1024,
},
},
},
{
test: /\.js/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
// options: {
// presets: [["@babel/preset-env" /*{ targets: "chrome 91" }*/]],
// plugins: [],
// },
},
],
},
{
test: /\.ts$/,
use: ["babel-loader"],
},
],
},
plugins: [
new HtmlWbpackPlugin({
template: "./src/index.html",
title: "lalala",
}),
],
};
公共的配置完成后,只需要写入不同环境下的配置就可以了
在webpack.prod.js 中写入生产环境配置
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "production",
devtool: "source-map",
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{ from: "public" },
// { from: "other", to: "public" },
],
}),
],
};
同理,在webpack.dev.js中写入开发环境配置
用到的依赖版本,有的依赖在实际项目中其实并不需要安装
"dependencies": {
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
"autoprefixer": "^10.4.2",
"core-js": "^3.21.0",
"css-loader": "^5.2.6",
"html-webpack-plugin": "^5.3.1",
"less": "^4.1.1",
"less-loader": "^9.0.0",
"postcss-loader": "^6.2.1",
"regenerator-runtime": "^0.13.9",
"style-loader": "^2.0.0",
"ts-loader": "^9.2.6",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^3.11.2"
},
"devDependencies": {
"@babel/cli": "^7.17.0",
"@babel/core": "^7.17.2",
"@babel/plugin-transform-arrow-functions": "^7.16.7",
"babel-loader": "^8.2.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.0",
"file-loader": "^6.2.0",
"postcss": "^8.4.6",
"postcss-cli": "^9.1.0",
"postcss-preset-env": "^7.3.1",
"url-loader": "^4.1.1"
}
基础篇完结
原理篇
手写loader
// TODO:
手写plugin
// TODO:
如有错误,还请指出~~, 看到这里啦,点个👍吧~~