webpack5
为什么需要打包工具?
项目开发后需要将代码编译成常规的js,css,html文件,浏览器才能正确解析,此外代码压缩,性能优化,兼容处理也需要打包工具进行处理
webpack能干什么?
webpack是一个静态资源打包工具,可以将项目的所有文件编译组合成浏览器可以运行的一个或多个文件,打包完的文件一般叫做bundle
基础
基本功能介绍
webpack本身只能处理js文件,并且只能编译ES Module语法,有两种编译模式,编译时通过命令参数--mode=development/production指定:
--mode=development开发模式: 编译ES Module语法--mode=production生产模式: 编译ES Module语法,并压缩文件
对于其他es6语法,其他类型的文件,webpack本身是无法处理的
基本功能实践
新建一个空项目,来看一下webpack本身能做哪些事情
-
新建如下项目文件夹,并写入内容
webpack_first_tasks ├─ public │ └─ index.html ├─ src // 项目所有源文件 │ ├─ js │ │ ├─ one.js │ │ └─ two.js │ └─ main.js index.html: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>webpack 初体验</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <h1>webpack</h1> </body> </html> one.js: export default function (a, b) { return a + b; } two.js: export default (...args) => args.reduce((curr, acc) => acc + curr); // 除了模块化语法,使用一点其他es6语法 main.js import add from "./js/one.js"; import addPlus from "./js/two.js"; const a = add(1, 2); const b = addPlus(1, 2, 3, 4, 5, 6); console.log(a, b); -
使用npm管理依赖
根目录下运行: npm init -y -
在生成的
package.json文件中,修改入口文件地址{ "name": "webpack_first_tasks", "version": "1.0.0", "description": "", + "main": "./main.js", - "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "webpack": "^5.90.3", "webpack-cli": "^5.1.4" } } -
安装
webpacknpm i webpack webpack-cli -D webpack-cli用于在命令行执行webpack命令 -
打包构建
npx webpack './src/main.js' --mode=development main.js是入口文件打包完成后,自动生成
dist文件夹,文件夹内,即是打包后的bundle文件,下面截取了一部分内容,可以看到ES Module语法被成功编译了,但其他es6语法依然没变,如rest参数:...argsdist └─ main.js // 所有的js文件都被添加至这里了 ... "use strict"; eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((...args) => args.reduce((curr, acc) => acc + curr));\r\n\n\n//# sourceURL=webpack://webpack_first_tasks/./src/js/two.js?"); ... -
调整
index.html文件: 引入打包后 的js,之后浏览器打开html文件即可看到成功运行<script src="../dist/main.js"></script> -
使用生产模式打包,体验代码压缩功能
npx webpack './src/main.js' --mode=production压缩后的
main.js可以看到,只有一行:(()=>{"use strict";const c=[1,2,3,4,5,6].reduce(((c,e)=>e+c));console.log(3,c)})();
关于npx命令
打包时我们使用的是npx运行的命令,它可以将node_module下的.bin文件夹临时添加到环境变量中,就可以访问.bin中的应用程序,从而运行其中的命令
基础配置
通过配置webpack配置配置文件,可以使webpack对css,html,字体图标,vue等类型的文件进行处理.这也是学习webpack的重点
webpack.config.js
webpack的配置文件为固定名称webpack.config.js,且必须在项目的跟目录下,该配置文件运行在node环境,使用CommonJS模块化规范,
当配置好webpack.config.js文件后,运行npx webpack会自动寻找根目录下的webpack.config.js中的配置进行打包,不需要执行参数
五大核心概念
mode
指定webpack的打包模式: development/production
entry
入口: 指定webpack打包从哪个文件开始,相对路径
output
出口: 指定webpack打包后的文件输出路径和文件名称,绝对路径
module
加载器: webpack本身只能处理js,json类型的资源,其他资源需要通过各种loader进行解析,通过loader将不同类型的文件转换成webpack可以处理的文件
pulgins
插件: 扩展webpack的功能
const path = require("path");
module.exports = {
// 模式
mode: "development",
// 入口 相对路径,指定从哪个文件开始打包
entry: "./src/main.js",
// 出口
output: {
path: path.resolve(__dirname, "dist"), // 指定输出路径 绝对路径
filename: "main.js", // 输出文件名
},
// 加载器
module: {
rules: [
// 加载器配置
],
},
// 插件
plugins: [
// 插件配置
],
};
开发模式
就接下来是对开发模式的实践和介绍,使用webpack开发模式主要有两个要求:
- 编译各种类型资源,浏览器能够识别运行
- 代码质量和格式的管控
首先我们先学习webpack开发模式下的配置
css资源处理
webpack不能处理样式文件,因此需要一些loader进行处理,查找loader时,优先官方文档中查找,找不到再去社区找
css
处理css需要两个loader: style-loader和css-loader,他们的作用如下:
css-loader: 将css编译成commonJS模块到js文件中style-loader: 动态创建style标签,并将js文件中的css创建的style标签中
两个加载器的执行顺序是: 首先需要css-loader将css转换成js,然后需要style-loader将js中的css插入创建的style标签中,
安装加载器
npm i css-loader style-loader -D
添加配置
...
module: {
rules: [
{
test: /.css$/,
use: ["style-loader","css-loader"]
}
]
}
test: 只处理指定后缀的文件
use/loader: 指定加载器,loader: 只能指定一个加载器,use: 可以指定多个加载器
use中的加载器从后向前执行,所以需要将style-loader写在前面
less
处理less,首先需要使用less-loader将less文件编译成css,然后如上文中使用两个css的loader即可,对于其他css预编译的依赖步骤都是相同的
安装loader:
npm i less less-loader -D
添加配置:
module: {
rules: [
{
test: /.css$/,
use: ["style-loader","css-loader"]
},
{
test: /.less$/,
use: ["style-loader","css-loader","less-loader"]
}
]
}
sass
npm i sass sass-loader -D
module: {
rules: [
// 加载器配置
{
test: /.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
{
test: /.s[ac]ss$/,
use: ["style-loader", "css-loader", "sass-loader"],
}
],
},
stylus
npm i stylus stylus-loader -D
module: {
rules: [
// 加载器配置
{
test: /.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
{
test: /.s[ac]ss$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /.styl$/,
use: ["style-loader", "css-loader", "stylus-loader"],
},
],
},
图片资源处理
在webpack5之前图片资源使用以下加载器处理:
raw-loader: 将文件导入为字符串url-loader: 将文件作为data URI内联到bundle中file-loader: 将文件原封不动发送到输出目录
webpack5之后内置这些加载器,可以通过type字段来指定:
asset/resource: 相当于file-loader,发送一个单独文件并导出为URLasset/inline: 相当于url-loader,将资源导出为一个data URIasset/source: 相当于row-loader,导出资源的源代码asset: 在导出为data URI和 发送单独文件之间选择,具体根据资源大小来切换,默认大小为8kb
添加配置:
{
test: /.(jpg|pne?g|gif|svg|webp)$/,
type: "asset"
},
优化,对于十多k的图片,转化为base64,只会增大一点点大小,却可以减少一个网络请求性价比很高,可以通过配置来实现:
{
test: /.(jpg|pne?g|gif|svg|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 100 * 1024, // 小于10kb将转换为base64
},
},
},
字体图标资源处理
字体图标的处理和图片处理比较类似,都属于资源处理,参考资源通用模型
- 下载对应的字体资源,字体样式文件放在css中,字体文件放在font文件夹中,修改字体样式文件中字体的引入路径为
font下 - 通过
font-class使用图标 - 配置webpack解析
{
test: /.(ttf|woff2?)$/,
type: "asset/resource",// 这里资源处理使用的是resource,直接输出文件内容
generator: {
filename: "./iconFont/[hash:10][ext][query]", // 指定字体文件打包输出为单独路径,下文会讲
},
},
修改输出路径
现在打包后的文件都一股脑的输出在了dist文件夹中,比较混乱不方便管理,因此我们可以通过配置实现各种不同类型的资源输出到不同的文件夹中
js文件
修改output属性
output {
正确做法: filename: 'js/main.js', // 将入口文件打包后的js放在js文件夹下的main.js中
错误做法: path; path.join(__dirname,'dist/js') // path属性是用来指定打包后所有文件的更目录文件夹的,这样设置并不能将js单独放在着一个文件夹中
}
图片
在图片的加载器中配置图片的输出路径
{
test: /.(pne?g|jpg|webp|gif)$/,
type: 'asset',
generator: {
filename: './img/[hash:10][ext][query]'
}
}
打包后将会把所有图片放在dist/img文件夹下,
hash: 文件md5值,后面跟上10,表示使用文件hash值的前十位命名文件防止文件名过长
ext: 文件后缀
query: 参数,文件路径?后面的值
自动清空上次打包文件
在outoput中通过设置clean属性开启自动删除,实际上他是将output的path属性对应的文件夹删除
output {
clean: true
}
其他类型资源的处理
如音频,视频等这些资源和字体图标类似,都属于需要原封不动进行输出的,所以资源模式都选择type: resource,然后通过generator配置输出目录即可
// 其他资源的处理,类似字体处理,比如音频,视频等,都需要资源原封不动的输出,所以资源模式都可以选择: asset/resource,然后将这些资源统一放在媒体文件夹即可
{
test: /.(ttf|woff2?|视频|音频等等)$/,
type: "asset/resource",
generator: {
filename: "./媒体文件夹/[hash:10][ext][query]",
},
}
js资源处理
webpack本身只能处理js的es module语法,对于其他es6语法都不能处理,因此我们还需要对js的资源进行处理,主要分为两个部分:
- 代码兼容处理,将es6语法转换为低版本语法,以便在
ie浏览器中运行,主要使用Babel - 代码格式化处理,校验代码格式,主要使用
eslint
eslint配置
eslint本身只能对js和jsx进行校验,被脸书团队收购,天然支持jsx,像vue等语法就不支持校验,因此eslint的重点依然是在其配置文件上
webpack配置eslint步骤
-
官方文档中查找
eslint的plugin:eslintWebpackPlugin -
安装:
npm install eslint eslint-webpack-plugin --save-dev -
添加到
webpack.config.js中// 插件需要实例化才能使用 const ESLintPlugin = require("eslint-webpack-plugin"); ... plugin:[ new ESLintPlugin({ context: path.join(__dirname, "src"), // 指定eslint校验的文件范围 }), ] ... -
根目录添加
eslint配置文件:.eslintrc.js
配置文件
四种常用格式:
.eslintrc.eslint.js,最常用,使用common模块化语法导出一个对象即可,还能写注释,接下来就是用这个.eslint.jsonpackage.json中通过eslintConfig配置,
常用属性
parserOptions
指定当前语法环境,包括: 使用的js版本,模块化语法,其他es特性等
env
指定全局环境变量,哪些运行时的环境变量可以使用
extends
继承现有的规则,如
eslint官方规:eslint:recommenedvue官方规则:plugin:vue/essentialreact官方规则:react-app
rules
自定义规则, 与继承规则冲突时,会覆盖继承的规则.以键值对的形式配置: off/0关闭, warn/1级别为警告.不中断程序运行, error/2级别为错误,中断程序运行
文件示例
根目录下新建.eslintrc.js
module.exports = {
// 允许使用的全局环境变量,如console,window,global
env: {
node: true, // 允许使用node的全局变量
browser: true, // 允许使用浏览器中的全局变量
},
// 当前语法环境
parserOptions: {
ecmaVersion: "latest", // 使用最新版本
sourceType: "module", // 使用ES Module 模块化语法
ecmaFeatures: {
// es其他特性
jsx: true, // 使用jsx语法
},
},
// 继承的现有规则
extends: [
// eslint官方规: eslint:recommened
// vue官方规则: plugin:vue/essential
// react官方规则:react-app
],
// 自定义规则,与继承规则冲突时,会覆盖
// off/0 关闭 warn/1 级别为警告.不中断程序运行 error/2 级别为错误,中断程序运行
rules: {
"no-var": 2,
},
};
忽略文件
.eslintignore配置不需要校验的文件目录,上文中已经对webpack指定了检验的文件范围,但对于之后将要安装的eslint编辑器插件来说,依然会校验哪些不需要校验的文件如node_module,dist等
根目录新建.eslintignore文件
dist
node_module
编辑器插件
vscode中搜索eslint插件并安装,之前webpack的eslint在打包时才能校验出错误信息,使用插件可以在编写代码时实时提示错误
插件自动查找eslint配置文件和.eslintignore文件,对代码进行实时校验.
babel配置
babel一个js编译器,可以将高级js语法转换成向后兼容的语法,以便在比版本浏览器中运行,同时也可以将jsx,TS等语法转换为js.它和eslint一样都需要一个自身的配置文件.babel配置中预设presets是重点
webpack配置babel步骤
-
官方文档查找loader: babel-loader
-
安装依赖:
npm install -D babel-loader @babel/core @babel/preset-env -
webpack.config.js中添加babel加载器的配置{ test: /.js$/, loader: "babel-loader", use: { loader: "babel-loader", exclude: /node_modules/, // 排除不进行编译的文件夹 options: { presets: ["@babel/preset-env"], }, }, }, presets: ["@babel/preset-env"],也可以不在这里配置,单独在babel的配置文件中配置 最终使用以下配置,预设放在babel配置文件中进行配置 { test: /.js$/, exclude: /node_modules/, use: ["babel-loader"], }, -
编写
babel配置文件,具体请看下文
配置文件
常用格式:
babel.config.jsbabel.config.json.babelrc.js为了统一,还使用这种js格式.babelrc.babelrc.jons
常用属性
presets预设: 简单理解就是一组插件,扩展babel功能,下述预设用到哪个写哪个就行
@babel/preset-env: 智能预设,编译js@babel/preset-react:编译react的jsx语法@babel/preset-typescript: 编译TS
.babelrc.js
module.exports = {
presets: ["@babel/preset-env"],
};
html资源处理
自动将打包后的js,css引入到html文件中
-
官网找插件:htmlWebpackPlugin
-
安装:
npm install --save-dev html-webpack-plugin -
webpack.config.js中配置插件const HtmlWebpackPlugin = require("html-webpack-plugin"); ... plugin:[ new HtmlWebpackPlugin({ template: path.join(__dirname,"./public/index.html") // 以这个html文件为模板 }) ]
开发服务器
配置开发服务器devServer可以监视src下文件变化,并自动更新
-
安装
npm install webpack-dev-server --save-dev -
webpack.config.js中添加配置
devServer: { host: "127.0.0.1", port: 8080, open: true, proxy: [ { "/api": { target: "http://localhost:3000", pathRewrite: { "^/api": "" }, }, }, ], }, -
启动命令为:
npx webpack server
开发服务器是在内存中打包的,所以不会生成dist文件夹
生产模式
生产模式是代码开发完毕需要部署上线的打包模式,这个模式下我们主要对代码进行优化,让其性能更好,主要包含以下两个方面:
- 代码运行性能
- 代码打包速度
准备工作
根目录新建config文件,存放开发模式下的配置文件和生产模式下的配置文件: webpack.dev.js和webpack.prod.js
开发模式配置文件修改:
- 新增了
config文件夹所以绝对路径需要回退一层目录,相对路径不需要修改 output的path可以指定为undefined,不需要输出了
生产模式配置文件修改
- 新增了
config文件夹,所以绝对路径需要回退一层目录,相对路径不需要修改 - 删除
devServer配置 - 开启自动删除旧的打包文件:
clean: true
添加命令
运行webpack打包时,需要指定配置文件的地址:
npx webpack --config ./config/开发模式配置文件或生产模式配置文件
为了方便对于两种模式可以在package.json下的script配置命令:
script: {
dev: 'webpack server --config ./config/webpack.dev.js', // 开发模式只需要启动server就行了
build: 'webpack --config ./config/webpack.prod.js' // 生产模式只需要打包就行了
}
这里就不需要npx了,script会自动在node_module/.bin下查找对应应用程序
完整文件:
webpack.dev.js
const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// 模式
mode: "development",
// 入口 相对路径,指定从哪个文件开始打包
entry: "./src/main.js",
// 出口
output: {
path: undefined,
filename: "js/main.js", // 入口文件打包输出文件名
},
// 加载器
module: {
rules: [
// 加载器配置
// 样式处理
{
test: /.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
{
test: /.s[ac]ss$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /.styl$/,
use: ["style-loader", "css-loader", "stylus-loader"],
},
// 图片处理 内置了loader
{
test: /.(jpg|pne?g|gif|svg|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 4kb
},
},
generator: {
filename: "./img/[hash:10][ext][query]",
},
},
// 对字体图标的处理
{
test: /.(ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "./iconFont/[hash:10][ext][query]",
},
},
// 其他资源的处理,类似字体处理,比如音频,视频等,都需要资源原封不动的输出,所以资源模式都可以选择: asset/resource,然后将这些资源统一放在媒体文件夹即可
// {
// test: /.(ttf|woff2?|视频|音频等等)$/,
// type: "asset/resource",
// generator: {
// filename: "./媒体文件夹/[hash:10][ext][query]",
// },
// },
// babel配置
{
test: /.js$/,
exclude: /node_modules/,
use: ["babel-loader"],
},
],
},
// 插件
plugins: [
// 插件配置
// eslint 配置
new ESLintPlugin({
context: path.join(__dirname, "../src"), // 指定eslint校验的文件范围
}),
// html 配置
new HtmlWebpackPlugin({
template: path.join(__dirname, "../public/index.html"),
}),
],
// 开发服务器
devServer: {
host: "127.0.0.1",
port: 8080,
open: true,
proxy: [
{
"/api": {
target: "http://localhost:3000",
pathRewrite: { "^/api": "" },
},
},
],
},
};
webpack.prod.js
const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// 模式
mode: "production",
// 入口 相对路径,指定从哪个文件开始打包
entry: "./src/main.js",
// 出口
output: {
path: path.resolve(__dirname, "../dist"), // 指定输出路径 绝对路径
filename: "js/main.js", // 入口文件打包输出文件名
clean: true,
},
// 加载器
module: {
rules: [
// 加载器配置
// 样式处理
{
test: /.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
{
test: /.s[ac]ss$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /.styl$/,
use: ["style-loader", "css-loader", "stylus-loader"],
},
// 图片处理 内置了loader
{
test: /.(jpg|pne?g|gif|svg|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 4kb
},
},
generator: {
filename: "./img/[hash:10][ext][query]",
},
},
// 对字体图标的处理
{
test: /.(ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "./iconFont/[hash:10][ext][query]",
},
},
// 其他资源的处理,类似字体处理,比如音频,视频等,都需要资源原封不动的输出,所以资源模式都可以选择: asset/resource,然后将这些资源统一放在媒体文件夹即可
// {
// test: /.(ttf|woff2?|视频|音频等等)$/,
// type: "asset/resource",
// generator: {
// filename: "./媒体文件夹/[hash:10][ext][query]",
// },
// },
// babel配置
{
test: /.js$/,
exclude: /node_modules/,
use: ["babel-loader"],
},
],
},
// 插件
plugins: [
// 插件配置
// eslint 配置
new ESLintPlugin({
context: path.join(__dirname, "../src"), // 指定eslint校验的文件范围
}),
// html 配置
new HtmlWebpackPlugin({
template: path.join(__dirname, "../public/index.html"),
}),
],
};
css文件的处理
将css打包为文件
之前开发模式的css是通过style-loader动态创建style标签的形式加载的,这种形式会有闪屏问题,原因是: 只有等到js执行完毕,就会插入style标签,执行js的时间,页面就一直处于白屏状态.
我们希望的是将css打包到文件中,然后通过link标签进行引入,从而避免闪屏问题,使用mini-css-extract-plugin这个插件
-
安装:
npm install --save-dev mini-css-extract-plugin -
在
webapck.prod.js中配置,代替原来的style-loaderconst MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { plugins: [ new MiniCssExtractPlugin({ filename: "./css/[name].css", chunkFilename: "./css/[name].chunk.js", }), ], module: { rules: [ { test: /.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, };
兼容性处理
和babel对于js一样,css也需要兼容处理,这里使用postcss进行处理
-
npm install --save-dev postcss-loader postcss postcss-preset-env -
webpack.prod.js中配置loader,将该loader加在css-loader之前,css-loader处理出css之后交给postcss-loader进行处理{ test: /.css$/, use: [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [["postcss-preset-env"]], }, }, }, ], } 类似的less,sass,stylus,也是将这个loader加在css-loader之前即可 -
指定兼容的浏览器范围,
package.json添加"browserslist": ["last 2 version","> 1%","not dead"] 兼容最近两个版本的浏览器,覆盖百分之99的浏览器,不要淘汰的浏览器这三种结果的交集
封装loader函数
上述样式处理,css,less,sass,.stylus可以看到有很多重复代码,因此我们可以封装一个统一的loader函数,减少代码冗余
function getStyleLoader(preLoader = "") {
return [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
preLoader,
];
}
css代码压缩
html和js只要打开了webpack的生产模式,就可以进行压缩了,不用做额外配置
-
官方文档查找插件并安装css-minimizer-webpack-plugin
npm install css-minimizer-webpack-plugin --save-dev -
引入并调用
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); plugin: [new CssMinimizerPlugin()]
基础总结
基础部分到此就结束了,主要是对webpack的基本配置和使用进行熟悉,包括webpack核心模块的配置,css,html,js等资源文件的处理.接下来进入高级部分,对代码进行优化.